console/src/main/java/org/matrix/console/ViewedRoomTracker.java
public class ViewedRoomTracker {

    private static ViewedRoomTracker instance = null;

    private String mViewedRoomId = null;
    private String mMatrixId = null;

    private ViewedRoomTracker(){
    }

    public static synchronized ViewedRoomTracker getInstance() {
        if (instance == null) {
            instance = new ViewedRoomTracker();
        }
        return instance;
    }

    public String getViewedRoomId() {
        return mViewedRoomId;
    }

    public String getMatrixId() {
        return mMatrixId;
    }

    public void setViewedRoomId(String roomId) {
        mViewedRoomId = roomId;
    }

    public void setMatrixId(String matrixId) {
        mMatrixId = matrixId;
    }
}
console/src/main/java/org/matrix/console/services/EventStreamService.java
public class EventStreamService extends Service {
    public enum StreamAction {
        UNKNOWN,
        STOP,
        START,
        PAUSE,
        RESUME,
        CATCHUP,
        GCM_STATUS_UPDATE
    }

    public static final String EXTRA_STREAM_ACTION = "org.matrix.console.services.EventStreamService.EXTRA_STREAM_ACTION";
    public static final String EXTRA_MATRIX_IDS = "org.matrix.console.services.EventStreamService.EXTRA_MATRIX_IDS";

    private static final String LOG_TAG = "EventStreamService";
    private static final int NOTIFICATION_ID = 42;
    private static final int MSG_NOTIFICATION_ID = 43;
    private static final int PENDING_CALL_ID = 44;

    private ArrayList<MXSession> mSessions;
    private ArrayList<String> mMatrixIds;
    private StreamAction mState = StreamAction.UNKNOWN;

    // store the notifications description
    private String mNotificationSessionId = null;
    private String mNotificationRoomId = null;
    private String mNotificationEventId = null;

    // call in progress
    // foreground notification
    private String mCallId = null;

    // current displayed notification
    // use to hide the "incoming call" notification
    private String mNotifiedCallId = null;

    private Boolean mIsForegound = false;
    private int mUnreadMessagesCounter = 0;
    private HashMap<String, HashMap<String, Integer>> mUnreadMessagesMapByRoomId = new HashMap<String, HashMap<String, Integer>>();

    private Notification mLatestNotification = null;

    private static EventStreamService mActiveEventStreamService = null;

    public static EventStreamService getInstance() {
        return mActiveEventStreamService;
    }

    /**
     * Cancel the push notifications for a dedicated roomId.
     * If the roomId is null, cancel all the push notification.
     * @param roomId
     */
    public static void cancelNotificationsForRoomId(String accountId, String roomId) {
        if (null != mActiveEventStreamService) {
            mActiveEventStreamService.cancelNotifications(accountId ,roomId);
        }
    }

    private void clearNotification() {
        NotificationManager nm = (NotificationManager) EventStreamService.this.getSystemService(Context.NOTIFICATION_SERVICE);
        nm.cancelAll();

        // reset the identifiers
        mNotificationSessionId = null;
        mNotificationRoomId = null;
        mNotificationEventId = null;
    }

    private void cancelNotifications(String accountId, String roomId) {
        // sanity checks
        if ((null != accountId) && (null != roomId)) {
            // cancel the notifications
            if (TextUtils.equals(mNotificationRoomId, roomId) && TextUtils.equals(accountId, mNotificationSessionId)) {
                clearNotification();
            }
        }
    }

    /**
     * Check if a notification must be cleared because the linked event has been read, deleted ...
     */
    public static void checkDisplayedNotification() {
        if (null != mActiveEventStreamService) {
            mActiveEventStreamService.checkNotification();
        }
    }

    private void checkNotification() {
        if (null != mNotificationRoomId) {
            boolean clearNotification = true;
            MXSession session = Matrix.getInstance(this).getSession(mNotificationSessionId);

            if (null != session) {
                Room room = session.getDataHandler().getRoom(mNotificationRoomId);

                if (null != room) {
                    // invitation notification
                    if (null == mNotificationEventId) {
                        clearNotification = !room.isInvited();
                    } else {
                        clearNotification = room.isEventRead(mNotificationEventId);
                    }
                }
            }

            if (clearNotification) {
                clearNotification();
            }
        }
    }

    private MXEventListener mListener = new MXEventListener() {
        /**
         * Manage hangup event.
         * The ringing sound is disabled and pending incoming call is dismissed.
         * @param event the hangup event.
         */
        private void manageHangUpEvent(Event event) {
            // check if the user answer from another device
            if (Event.EVENT_TYPE_CALL_ANSWER.equals(event.type)) {
                MXSession session = Matrix.getMXSession(getApplicationContext(), event.getMatrixId());

                // ignore the answer event if it was sent by another member
                if (!TextUtils.equals(event.getSender(), session.getCredentials().userId)) {
                    return;
                }
            }

            String callId = null;

            try {
                callId = event.getContentAsJsonObject().get("call_id").getAsString();
            } catch (Exception e) {}

            if (null != callId) {
                // hide the "call in progress notification"
                hidePendingCallNotification(callId);
            }

            Log.d(LOG_TAG, "manageHangUpEvent stopRinging");
            CallViewActivity.stopRinging();
        }

        // White list of displayable events
        private boolean isDisplayableEvent(Event event) {
            return Event.EVENT_TYPE_MESSAGE.equals(event.type)
                    || Event.EVENT_TYPE_STATE_ROOM_MEMBER.equals(event.type)
                    || Event.EVENT_TYPE_STATE_ROOM_CREATE.equals(event.type)
                    || Event.EVENT_TYPE_STATE_ROOM_NAME.equals(event.type)
                    || Event.EVENT_TYPE_STATE_ROOM_ALIASES.equals(event.type)
                    || Event.EVENT_TYPE_STATE_ROOM_TOPIC.equals(event.type);
        }


        @Override
        public void onLiveEvent(Event event, RoomState roomState) {
            if (Event.EVENT_TYPE_CALL_HANGUP.equals(event.type) || Event.EVENT_TYPE_CALL_ANSWER.equals(event.type)) {
                manageHangUpEvent(event);
            }
        }

        @Override
        public void onBingEvent(Event event, RoomState roomState, BingRule bingRule) {
            Log.i(LOG_TAG, "onMessageEvent >>>> " + event);

            final String roomId = event.roomId;

            // Just don't bing for the room the user's currently in
            if (!ConsoleApplication.isAppInBackground() && (roomId != null) && event.roomId.equals(ViewedRoomTracker.getInstance().getViewedRoomId())) {
                return;
            }

            String senderID = event.getSender();
            // FIXME: Support event contents with no body
            if (!event.content.getAsJsonObject().has("body")) {
                // only the membership events are supported
                if (!Event.EVENT_TYPE_STATE_ROOM_MEMBER.equals(event.type) && !event.isCallEvent()) {
                    return;
                }

                // display only the invitation messages by now
                // because the other ones are not displayed.
                if (event.isCallEvent() && !event.type.equals(Event.EVENT_TYPE_CALL_INVITE)) {
                    // dismiss the call notifications
                    if (event.type.equals(Event.EVENT_TYPE_CALL_HANGUP)) {
                        manageHangUpEvent(event);
                    }
                    return;
                }
            }

            MXSession session = Matrix.getMXSession(getApplicationContext(), event.getMatrixId());

            // invalid session ?
            // should never happen.
            // But it could be triggered because of multi accounts management.
            // The dedicated account is removing but some pushes are still received.
            if ((null == session) || !session.isAlive()) {
                return;
            }

            Room room = session.getDataHandler().getRoom(roomId);

            // invalid room ?
            if (null == room) {
                return;
            }

            Boolean isInvitationEvent = false;
            String body;

            mNotifiedCallId = null;

            // call invitation
            if (event.isCallEvent()) {
                if (event.type.equals(Event.EVENT_TYPE_CALL_INVITE)) {
                    body = getApplicationContext().getString(R.string.incoming_call);

                    try {
                        mNotifiedCallId = event.getContentAsJsonObject().get("call_id").getAsString();
                     } catch (Exception e) {}
                } else {
                    EventDisplay eventDisplay = new EventDisplay(getApplicationContext(), event, room.getLiveState());
                    body = eventDisplay.getTextualDisplay().toString();
                }
            } else if (Event.EVENT_TYPE_STATE_ROOM_MEMBER.equals(event.type)) {
                body = EventDisplay.getMembershipNotice(getApplicationContext(), event, roomState);

                try {
                    isInvitationEvent = "invite".equals(event.getContentAsJsonObject().getAsJsonPrimitive("membership").getAsString());
                } catch (Exception e) {}

            } else {
                body = event.getContentAsJsonObject().getAsJsonPrimitive("body").getAsString();
            }

            int unreadNotifForThisUser = 0;

            // update the badge
            if (ConsoleApplication.isAppInBackground()) {
                CommonActivityUtils.updateUnreadMessagesBadge(getApplicationContext(), ++mUnreadMessagesCounter);

                HashMap<String, Integer> countByUserIds = null;

                if (mUnreadMessagesMapByRoomId.containsKey(roomId)) {
                    countByUserIds = mUnreadMessagesMapByRoomId.get(roomId);
                } else {
                    countByUserIds = new HashMap<String, Integer>();
                    mUnreadMessagesMapByRoomId.put(roomId, countByUserIds);
                }

                if (countByUserIds.containsKey(senderID)) {
                    unreadNotifForThisUser = countByUserIds.get(senderID);
                }

                unreadNotifForThisUser++;
                countByUserIds.put(senderID, unreadNotifForThisUser);
            }

            String from = "";
            Bitmap largeBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.ic_contact_picture_holo_light);

            // when the event is an invitation one
            // don't check if the sender ID is known because the members list are not yet downloaded
            if (!isInvitationEvent) {
                RoomMember member = room.getMember(senderID);

                // invalid member
                if (null == member) {
                    return;
                }

                from = member.getName();

                int size = getApplicationContext().getResources().getDimensionPixelSize(org.matrix.androidsdk.R.dimen.chat_avatar_size);

                File f = session.getMediasCache().thumbnailCacheFile(member.avatarUrl, size);

                if (null != f) {
                    BitmapFactory.Options options = new BitmapFactory.Options();
                    options.inPreferredConfig = Bitmap.Config.ARGB_8888;
                    largeBitmap = BitmapFactory.decodeFile(f.getPath(), options);
                }
            }

            String roomName = null;
            if(session.getMyUser() != null) {
                roomName = room.getName(session.getMyUserId());
            }

            mNotificationSessionId = session.getCredentials().userId;
            mNotificationRoomId = roomId;
            mNotificationEventId = event.eventId;

            if (bingRule.isCallRingNotificationSound(bingRule.notificationSound())) {
                if (null == CallViewActivity.getActiveCall()) {
                    Log.d(LOG_TAG, "onBingEvent starting");
                    CallViewActivity.startRinging(EventStreamService.this);
                }
            }

            mLatestNotification = NotificationUtils.buildMessageNotification(
                    EventStreamService.this,
                    from, session.getCredentials().userId,
                    mNotifiedCallId,
                    Matrix.getMXSessions(getApplicationContext()).size() > 1,
                    largeBitmap,
                    mUnreadMessagesCounter,
                    unreadNotifForThisUser,
                    body,
                    event.roomId,
                    roomName,
                    bingRule.isDefaultNotificationSound(bingRule.notificationSound()));
        }

        @Override
        public void onLiveEventsChunkProcessed() {
            if (null != mLatestNotification) {

                try {
                    NotificationManager nm = (NotificationManager) EventStreamService.this.getSystemService(Context.NOTIFICATION_SERVICE);
                    nm.cancelAll();
                    nm.notify(MSG_NOTIFICATION_ID, mLatestNotification);

                    // turn the screen on for 3 seconds
                    PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
                    PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK, "MXEventListener");
                    wl.acquire(3000);
                    wl.release();
                } catch (Exception e) {
                    Log.e(LOG_TAG, "onLiveEventsChunkProcessed crashed "+ e.getLocalizedMessage());
                }

                mLatestNotification = null;
            }

            // special catchup cases
            if (mState == StreamAction.CATCHUP) {

                Boolean hasActiveCalls = false;

                for(MXSession session : mSessions) {
                    hasActiveCalls |= session.mCallsManager.hasActiveCalls();
                }

                // if there are some active calls, the catchup should not be stopped.
                // because an user could answer to a call from another device.
                // there will no push because it is his own message.
                // so, the client has no choice to catchup until the ring is shutdown
                if (hasActiveCalls) {
                    Log.d(LOG_TAG, "Catchup again because there are active calls");
                    catchup();
                }
            }
        }
    };

    /**
     * Add some accounts to the current service.
     * @param matrixIds the account identifiers to add.
     */
    public void startAccounts(List<String> matrixIds) {
        for(String matrixId : matrixIds) {
            // not yet started
            if (mMatrixIds.indexOf(matrixId) < 0) {
                MXSession session = Matrix.getInstance(getApplicationContext()).getSession(matrixId);

                mSessions.add(session);
                mMatrixIds.add(matrixId);
                session.getDataHandler().addListener(mListener);
                // perform a full sync
                session.startEventStream(null);
            }
        }
    }

    /**
     * Stop some accounts of the current service.
     * @param matrixIds the account identifiers to add.
     */
    public void stopAccounts(List<String> matrixIds) {
        for(String matrixId : matrixIds) {
            // not yet started
            if (mMatrixIds.indexOf(matrixId) >= 0) {
                MXSession session = Matrix.getInstance(getApplicationContext()).getSession(matrixId);

                if (null != session) {

                    session.stopEventStream();
                    session.getDataHandler().removeListener(mListener);

                    mSessions.remove(session);
                    mMatrixIds.remove(matrixId);
                }
            }
        }
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        StreamAction action = StreamAction.values()[intent.getIntExtra(EXTRA_STREAM_ACTION, StreamAction.UNKNOWN.ordinal())];

        if (intent.hasExtra(EXTRA_MATRIX_IDS)) {
            if (null == mMatrixIds) {
                mMatrixIds = new ArrayList<String>(Arrays.asList(intent.getStringArrayExtra(EXTRA_MATRIX_IDS)));

                mSessions = new ArrayList<MXSession>();

                for(String matrixId : mMatrixIds) {
                    mSessions.add(Matrix.getInstance(getApplicationContext()).getSession(matrixId));
                }
            }
        }

        Log.d(LOG_TAG, "onStartCommand >> "+action);
        switch (action) {
            case START:
            case RESUME:
                start();
                break;
            case STOP:
                stop();
                break;
            case PAUSE:
                pause();
                break;
            case CATCHUP:
                catchup();
                break;
            case GCM_STATUS_UPDATE:
                gcmStatusUpdate();
            default:
                break;
        }

        return START_NOT_STICKY;
    }

    @Override
    public void onDestroy() {
        stop();
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    private void startEventStream(final MXSession session, final IMXStore store) {
        session.getDataHandler().checkPermanentStorageData();
        session.startEventStream(store.getEventStreamToken());
    }

    private void start() {
        // reset the badge counter when resuming the application
        if (0 != mUnreadMessagesCounter) {
            mUnreadMessagesCounter = 0;
            CommonActivityUtils.updateUnreadMessagesBadge(this, mUnreadMessagesCounter);
            mUnreadMessagesMapByRoomId = new HashMap<String, HashMap<String, Integer>>();
        }

        if (mState == StreamAction.START) {
            Log.e(LOG_TAG, "Already started.");
            return;
        }
        else if ((mState == StreamAction.PAUSE) || (mState == StreamAction.CATCHUP)) {
            Log.e(LOG_TAG, "Resuming active stream.");
            resume();
            return;
        }

        if (mSessions == null) {
            Log.e(LOG_TAG, "No valid MXSession.");
            return;
        }
        mActiveEventStreamService = this;

        for(MXSession session : mSessions) {
            session.getDataHandler().addListener(mListener);
            final IMXStore store = session.getDataHandler().getStore();

            // the store is ready (no data loading in progress...)
            if (store.isReady()) {
                startEventStream(session, store);
            } else {
                final MXSession fSession = session;
                // wait that the store is ready  before starting the events listener
                store.setMXStoreListener(new IMXStore.MXStoreListener() {
                    @Override
                    public void onStoreReady(String accountId) {
                        startEventStream(fSession, store);
                    }

                    @Override
                    public void onStoreCorrupted(String accountId, String description) {
                        Toast.makeText(getApplicationContext(), accountId + " : " + description, Toast.LENGTH_LONG).show();
                        startEventStream(fSession, store);
                    }
                });
            };
        }

        updateListenerNotification();

        mState = StreamAction.START;
    }

    private void stop() {
        if (mIsForegound) {
            stopForeground(true);
        }

        if (mSessions != null) {
            for(MXSession session : mSessions) {
                if (session.isAlive()) {
                    session.stopEventStream();
                    session.getDataHandler().removeListener(mListener);
                }
            }
        }
        mMatrixIds = null;
        mSessions = null;
        mState = StreamAction.STOP;

        mActiveEventStreamService = null;
    }

    private void pause() {
        if ((mState == StreamAction.START) || (mState == StreamAction.RESUME)) {
            Log.d(LOG_TAG, "onStartCommand pause");

            if (mSessions != null) {
                for(MXSession session : mSessions) {
                    session.pauseEventStream();
                }
                mState = StreamAction.PAUSE;
            }
        } else {
            Log.e(LOG_TAG, "onStartCommand invalid state pause " + mState);
        }
    }

    private void catchup() {
        Log.d(LOG_TAG, "catchup with state " + mState + " CurrentActivity " + ConsoleApplication.getCurrentActivity());

        // the catchup should only be done when the thread is suspended
        Boolean canCatchup = (mState == StreamAction.PAUSE) || (mState == StreamAction.CATCHUP);
        //

        // other use case
        // the application has been launched by a push
        // so there is no displayed activity
        if (!canCatchup && (mState == StreamAction.START)) {
            canCatchup = (null == ConsoleApplication.getCurrentActivity());
        }

        if (canCatchup) {
            if (mSessions != null) {
                for (MXSession session : mSessions) {
                    session.catchupEventStream();
                }
            } else {
                Log.e(LOG_TAG, "catchup no session");
            }

            mState = StreamAction.CATCHUP;
        } else {
            Log.d(LOG_TAG, "No catchup is triggered because there is already a running event thread");
        }
    }

    private void resume() {
        if (mSessions != null) {
            for(MXSession session : mSessions) {
                session.resumeEventStream();
            }
        }

        mState = StreamAction.START;
    }

    private void gcmStatusUpdate() {
        if (mIsForegound) {
            stopForeground(true);
            mIsForegound = false;
        }

        updateListenerNotification();
    }

    private void updateListenerNotification() {
        if (!Matrix.getInstance(this).getSharedGcmRegistrationManager().useGCM()) {
            Notification notification = buildNotification();
            startForeground(NOTIFICATION_ID, notification);
            mIsForegound = true;
        } else {
            stopForeground(true);
            mIsForegound = false;
        }
    }

    private Notification buildNotification() {
        Notification notification = new Notification(
                (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.LOLLIPOP) ? R.drawable.ic_menu_small_matrix : R.drawable.ic_menu_small_matrix_transparent,
                "Matrix",
                System.currentTimeMillis()
        );

        // go to the home screen if this is clicked.
        Intent i = new Intent(this, HomeActivity.class);

        i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP |
                Intent.FLAG_ACTIVITY_SINGLE_TOP);

        PendingIntent pi = PendingIntent.getActivity(this, 0, i, 0);

        notification.setLatestEventInfo(this, getString(R.string.app_name),
                "Listening for events",
                pi);
        notification.flags |= Notification.FLAG_NO_CLEAR;
        return notification;
    }


    /**
     * A call is in progress.
     * @param session the session
     * @param callId the callId
     */
    public void displayPendingCallNotification(MXSession session, Room room, String callId) {
        if (null != callId) {
            Notification notification = NotificationUtils.buildCallNotification(getApplicationContext(), room.getName(session.getCredentials().userId), room.getRoomId(), session.getCredentials().userId, callId);
            startForeground(PENDING_CALL_ID, notification);
            mCallId = callId;
        }
    }

    /**
     * @param callId the ended call call id
     */
    public void hidePendingCallNotification(String callId) {
        if (TextUtils.equals(mCallId, callId)) {
            stopForeground(true);
            updateListenerNotification();
            mCallId = null;
        }

        // hide the "incoming call" notification
        if (TextUtils.equals(mNotifiedCallId, callId)) {
            NotificationManager nm = (NotificationManager) EventStreamService.this.getSystemService(Context.NOTIFICATION_SERVICE);
            nm.cancelAll();
            mNotifiedCallId = null;
        }
    }
}
console/src/main/java/org/matrix/console/fragments/ConsoleMessageListFragment.java
public class ConsoleMessageListFragment extends MatrixMessageListFragment {

    private static final String TAG_FRAGMENT_RECEIPTS_DIALOG = "ConsoleMessageListFragment.TAG_FRAGMENT_RECEIPTS_DIALOG";

    public static ConsoleMessageListFragment newInstance(String matrixId, String roomId, int layoutResId) {
        ConsoleMessageListFragment f = new ConsoleMessageListFragment();
        Bundle args = new Bundle();
        args.putString(ARG_ROOM_ID, roomId);
        args.putInt(ARG_LAYOUT_ID, layoutResId);
        args.putString(ARG_MATRIX_ID, matrixId);
        f.setArguments(args);
        return f;
    }

    protected View mBackProgressView;

    @Override
    public MXSession getSession(String matrixId) {
        return Matrix.getMXSession(getActivity(), matrixId);
    }

    @Override
    public MXMediasCache getMXMediasCache() {
       return Matrix.getInstance(getActivity()).getMediasCache();
    }

    @Override
    public MessagesAdapter createMessagesAdapter() {
        return new ConsoleMessagesAdapter(mSession, getActivity(), getMXMediasCache());
    }

    /**
     * The user scrolls the list.
     * Apply an expected behaviour
     * @param event the scroll event
     */
    @Override
    public void onListTouch(MotionEvent event) {
        // the user scroll over the keyboard
        // hides the keyboard
        if (mCheckSlideToHide && (event.getY() > mMessageListView.getHeight())) {
            mCheckSlideToHide = false;
            MXCActionBarActivity.dismissKeyboard(getActivity());
        }
    }

    /**
     * return true to display all the events.
     * else the unknown events will be hidden.
     */
    @Override
    public boolean isDisplayAllEvents() {
        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getActivity());
        return preferences.getBoolean(getString(R.string.settings_key_display_all_events), false);
    }


    private void setViewVisibility(View view, int visibility) {
        if ((null != view) && (null != getActivity())) {
            view.setVisibility(visibility);
        }
    }

    @Override
    public void showLoadingBackProgress() {
        setViewVisibility(mBackProgressView, View.VISIBLE);
    }

    @Override
    public void hideLoadingBackProgress() {
        setViewVisibility(mBackProgressView, View.GONE);
    }

    @Override
    public void showLoadingForwardProgress() {
        setViewVisibility(mBackProgressView, View.VISIBLE);
    }

    @Override
    public void hideLoadingForwardProgress() {
        setViewVisibility(mBackProgressView, View.GONE);
    }

    @Override
    public void showInitLoading() {
        setViewVisibility(mBackProgressView, View.VISIBLE);
    }

    @Override
    public void hideInitLoading() {
        setViewVisibility(mBackProgressView, View.GONE);
    }


    /**
     * Called when a fragment is first attached to its activity.
     * {@link #onCreate(Bundle)} will be called after this.
     *
     * @param aHostActivity parent activity
     */
    @Override
    public void onAttach(Activity aHostActivity) {
        super.onAttach(aHostActivity);
        mBackProgressView = aHostActivity.findViewById(R.id.loading_room_content_progress);
    }

    /***  MessageAdapter listener  ***/
    @Override
    public void onRowClick(int position) {
        final MessageRow messageRow = mAdapter.getItem(position);
        final List<Integer> textIds = new ArrayList<>();
        final List<Integer> iconIds = new ArrayList<Integer>();

        String mediaUrl = null;
        String mediaMimeType = null;
        Uri mediaUri = null;
        Message message = JsonUtils.toMessage(messageRow.getEvent().content);

        if (Event.EVENT_TYPE_STATE_ROOM_TOPIC.equals(messageRow.getEvent().type) ||
                Event.EVENT_TYPE_STATE_ROOM_MEMBER.equals(messageRow.getEvent().type) ||
                Event.EVENT_TYPE_STATE_ROOM_NAME.equals(messageRow.getEvent().type) ||
                Message.MSGTYPE_EMOTE.equals(message.msgtype)
                ) {

            if (!messageRow.getEvent().getSender().equals(getSession().getCredentials().userId)) {
                textIds.add(R.string.paste_username);
                iconIds.add(R.drawable.ic_material_paste);
            }

            textIds.add(R.string.copy);
            iconIds.add(R.drawable.ic_material_copy);
        } else  {

            // copy the message body
            if (Event.EVENT_TYPE_MESSAGE.equals(messageRow.getEvent().type)) {

                if (!messageRow.getEvent().getSender().equals(getSession().getCredentials().userId)) {
                    textIds.add(R.string.paste_username);
                    iconIds.add(R.drawable.ic_material_paste);
                }

                if (Message.MSGTYPE_TEXT.equals(message.msgtype)) {
                    textIds.add(R.string.copy);
                    iconIds.add(R.drawable.ic_material_copy);
                }
            }

            if (messageRow.getEvent().canBeResent()) {
                textIds.add(R.string.resend);
                iconIds.add(R.drawable.ic_material_send);
            } else if (messageRow.getEvent().mSentState == Event.SentState.SENT) {
                textIds.add(R.string.redact);
                iconIds.add(R.drawable.ic_material_clear);
                if (Event.EVENT_TYPE_MESSAGE.equals(messageRow.getEvent().type)) {
                    Boolean supportShare = true;

                    // check if the media has been downloaded
                    if ((message instanceof ImageMessage) || (message instanceof FileMessage)) {
                        if (message instanceof ImageMessage) {
                            ImageMessage imageMessage = (ImageMessage) message;

                            mediaUrl = imageMessage.url;
                            mediaMimeType = imageMessage.getMimeType();
                        } else {
                            FileMessage fileMessage = (FileMessage) message;

                            mediaUrl = fileMessage.url;
                            mediaMimeType = fileMessage.getMimeType();
                        }

                        supportShare = false;
                        MXMediasCache cache = getMXMediasCache();

                        File mediaFile = cache.mediaCacheFile(mediaUrl, mediaMimeType);

                        if (null != mediaFile) {
                            try {
                                mediaUri = ConsoleContentProvider.absolutePathToUri(getActivity(), mediaFile.getAbsolutePath());
                                supportShare = true;
                            } catch (Exception e) {
                            }
                        }
                    }

                    if (supportShare) {
                        textIds.add(R.string.share);
                        iconIds.add(R.drawable.ic_material_share);

                        textIds.add(R.string.forward);
                        iconIds.add(R.drawable.ic_material_forward);

                        textIds.add(R.string.save);
                        iconIds.add(R.drawable.ic_material_save);
                    }
                }
            }
        }

        // display the JSON
        textIds.add(R.string.message_details);
        iconIds.add(R.drawable.ic_material_description);

        FragmentManager fm = getActivity().getSupportFragmentManager();
        IconAndTextDialogFragment fragment = (IconAndTextDialogFragment) fm.findFragmentByTag(TAG_FRAGMENT_MESSAGE_OPTIONS);

        if (fragment != null) {
            fragment.dismissAllowingStateLoss();
        }

        Integer[] lIcons = iconIds.toArray(new Integer[iconIds.size()]);
        Integer[] lTexts = textIds.toArray(new Integer[iconIds.size()]);

        final String  fmediaMimeType = mediaMimeType;
        final Uri fmediaUri = mediaUri;
        final String fmediaUrl = mediaUrl;
        final Message fMessage = message;

        fragment = IconAndTextDialogFragment.newInstance(lIcons, lTexts);
        fragment.setOnClickListener(new IconAndTextDialogFragment.OnItemClickListener() {
            @Override
            public void onItemClick(IconAndTextDialogFragment dialogFragment, int position) {
                final Integer selectedVal = textIds.get(position);

                if (selectedVal == R.string.copy) {
                    getActivity().runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            ClipboardManager clipboard = (ClipboardManager) getActivity().getSystemService(Context.CLIPBOARD_SERVICE);
                            Event event = messageRow.getEvent();
                            String text = "";

                            if (Event.EVENT_TYPE_STATE_ROOM_TOPIC.equals(messageRow.getEvent().type) ||
                                    Event.EVENT_TYPE_STATE_ROOM_MEMBER.equals(messageRow.getEvent().type) ||
                                    Event.EVENT_TYPE_STATE_ROOM_NAME.equals(messageRow.getEvent().type)) {

                                RoomState roomState = messageRow.getRoomState();
                                EventDisplay display = new EventDisplay(getActivity(), event, roomState);
                                text = display.getTextualDisplay().toString();
                            } else {
                                text = JsonUtils.toMessage(event.content).body;
                            }

                            ClipData clip = ClipData.newPlainText("", text);
                            clipboard.setPrimaryClip(clip);
                        }
                    });
                } else if (selectedVal == R.string.resend) {
                    getActivity().runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            resend(messageRow.getEvent());
                        }
                    });
                } else if (selectedVal == R.string.save) {
                    getActivity().runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            save(fMessage, fmediaUrl, fmediaMimeType);
                        }
                    });
                } else if (selectedVal == R.string.redact) {
                    getActivity().runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            redactEvent(messageRow.getEvent().eventId);
                        }
                    });
                } else if ((selectedVal == R.string.share) || (selectedVal == R.string.forward)) {
                    getActivity().runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            Intent sendIntent = new Intent();
                            sendIntent.setAction(Intent.ACTION_SEND);

                            Event event = messageRow.getEvent();
                            Message message = JsonUtils.toMessage(event.content);

                            if (null != fmediaUri) {
                                sendIntent.setType(fmediaMimeType);
                                sendIntent.putExtra(Intent.EXTRA_STREAM, fmediaUri);
                            } else {
                                sendIntent.putExtra(Intent.EXTRA_TEXT, message.body);
                                sendIntent.setType("text/plain");
                            }

                            if (selectedVal == R.string.forward) {
                                CommonActivityUtils.sendFilesTo(getActivity(), sendIntent);
                            } else {
                                startActivity(sendIntent);
                            }
                        }
                    });
                } else if (selectedVal == R.string.message_details) {
                    getActivity().runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            FragmentManager fm =  getActivity().getSupportFragmentManager();

                            MessageDetailsFragment fragment = (MessageDetailsFragment) fm.findFragmentByTag(TAG_FRAGMENT_MESSAGE_DETAILS);
                            if (fragment != null) {
                                fragment.dismissAllowingStateLoss();
                            }
                            fragment = MessageDetailsFragment.newInstance(messageRow.getEvent().toString());
                            fragment.show(fm, TAG_FRAGMENT_MESSAGE_DETAILS);
                        }
                    });
                } else if (selectedVal == R.string.paste_username) {
                    String displayName = messageRow.getEvent().getSender();
                    RoomState state = messageRow.getRoomState();

                    if (null != state) {
                        displayName = state.getMemberName(displayName);
                    }

                    onSenderNameClick(messageRow.getEvent().getSender(), displayName);
                }
            }
        });

        // GA issue
        // can not perform this action after onSaveInstanceState....
        // it seems that the linked activity is stopped.
        try {
            fragment.show(fm, TAG_FRAGMENT_MESSAGE_OPTIONS);
        } catch (Exception e) {
        }
    }

    public boolean onRowLongClick(int position) {
        return false;
    }

    /**
     * @return the imageMessages list
     */
    private ArrayList<SlidableImageInfo> listImageMessages() {
        ArrayList<SlidableImageInfo> res = new ArrayList<SlidableImageInfo>();

        for(int position = 0; position < mAdapter.getCount(); position++) {
            MessageRow row = mAdapter.getItem(position);
            Message message = JsonUtils.toMessage(row.getEvent().content);

            if (Message.MSGTYPE_IMAGE.equals(message.msgtype)) {
                ImageMessage imageMessage = (ImageMessage)message;

                SlidableImageInfo info = new SlidableImageInfo();

                info.mImageUrl = imageMessage.url;
                info.mRotationAngle = imageMessage.getRotation();
                info.mOrientation = imageMessage.getOrientation();
                info.mMimeType = imageMessage.getMimeType();
                info.midentifier = row.getEvent().eventId;
                res.add(info);
            }
        }

        return res;
    }

    /**
     * Returns the imageMessages position in listImageMessages.
     * @param listImageMessages the messages list.
     * @param imageMessage the imageMessage
     * @return the imageMessage position. -1 if not found.
     */
    private int getImageMessagePosition(ArrayList<SlidableImageInfo> listImageMessages, ImageMessage imageMessage) {
        for(int index = 0; index < listImageMessages.size(); index++) {
            if (listImageMessages.get(index).mImageUrl.equals(imageMessage.url)) {
                return index;
            }
        }

        return -1;
    }


    public void onContentClick(int position) {
        MessageRow row = mAdapter.getItem(position);
        Event event = row.getEvent();
        Message message = JsonUtils.toMessage(event.content);

        // image message -> display it within the medias swipper
        if (Message.MSGTYPE_IMAGE.equals(message.msgtype)) {
            ImageMessage imageMessage = JsonUtils.toImageMessage(event.content);

            if (null != imageMessage.url) {
                ArrayList<SlidableImageInfo> listImageMessages = listImageMessages();
                int listPosition = getImageMessagePosition(listImageMessages, imageMessage);

                if (listPosition >= 0) {
                    Intent viewImageIntent = new Intent(getActivity(), ImageSliderActivity.class);

                    viewImageIntent.putExtra(ImageSliderActivity.KEY_THUMBNAIL_WIDTH, mAdapter.getMaxThumbnailWith());
                    viewImageIntent.putExtra(ImageSliderActivity.KEY_THUMBNAIL_HEIGHT, mAdapter.getMaxThumbnailHeight());
                    viewImageIntent.putExtra(ImageSliderActivity.KEY_INFO_LIST, listImageMessages);
                    viewImageIntent.putExtra(ImageSliderActivity.KEY_INFO_LIST_INDEX, listPosition);

                    getActivity().startActivity(viewImageIntent);
                }
            }
        } else if (Message.MSGTYPE_FILE.equals(message.msgtype)) {
            FileMessage fileMessage = JsonUtils.toFileMessage(event.content);

            if (null != fileMessage.url) {
                File mediaFile =  mSession.getMediasCache().mediaCacheFile(fileMessage.url, fileMessage.getMimeType());

                // is the file already saved
                if (null != mediaFile) {
                    String savedMediaPath = CommonActivityUtils.saveMediaIntoDownloads(getActivity(), mediaFile, fileMessage.body, fileMessage.getMimeType());
                    CommonActivityUtils.openMedia(getActivity(), savedMediaPath, fileMessage.getMimeType());
                } else {
                    mSession.getMediasCache().downloadMedia(getActivity(), mSession.getHomeserverConfig(), fileMessage.url, fileMessage.getMimeType());
                    mAdapter.notifyDataSetChanged();
                }
            }
        } else if (Message.MSGTYPE_VIDEO.equals(message.msgtype)) {
           final VideoMessage videoMessage = JsonUtils.toVideoMessage(event.content);

            if (null != videoMessage.url) {
                File mediaFile =  mSession.getMediasCache().mediaCacheFile(videoMessage.url, videoMessage.getVideoMimeType());

                // is the file already saved
                if (null != mediaFile) {
                    String savedMediaPath = CommonActivityUtils.saveMediaIntoDownloads(getActivity(), mediaFile, videoMessage.body, videoMessage.getVideoMimeType());
                    CommonActivityUtils.openMedia(getActivity(), savedMediaPath, videoMessage.getVideoMimeType());
                } else {
                    final String expectedDownloadId = mSession.getMediasCache().downloadMedia(getActivity(), mSession.getHomeserverConfig(), videoMessage.url, videoMessage.getVideoMimeType());

                    mSession.getMediasCache().addDownloadListener(expectedDownloadId, new MXMediasCache.DownloadCallback() {
                        @Override
                        public void onDownloadStart(String downloadId) {
                        }

                        @Override
                        public void onError(String downloadId, JsonElement jsonElement) {
                        }

                        @Override
                        public void onDownloadProgress(String aDownloadId, int percentageProgress) {
                        }

                        @Override
                        public void onDownloadComplete(String aDownloadId) {
                            if (TextUtils.equals(aDownloadId, expectedDownloadId)) {
                                File mediaFile =  mSession.getMediasCache().mediaCacheFile(videoMessage.url, videoMessage.getVideoMimeType());

                                // is the file already saved
                                if (null != mediaFile) {
                                    String savedMediaPath = CommonActivityUtils.saveMediaIntoDownloads(getActivity(), mediaFile, videoMessage.body, videoMessage.getVideoMimeType());
                                    CommonActivityUtils.openMedia(getActivity(), savedMediaPath, videoMessage.getVideoMimeType());
                                }

                            }
                        }
                    });

                    mAdapter.notifyDataSetChanged();
                }
            }
        } else {
            onRowClick(position);
        }
    }

    public boolean onContentLongClick(int position) {
        return false;
    }

    public void onAvatarClick(String userId) {
        Intent startRoomInfoIntent = new Intent(getActivity(), MemberDetailsActivity.class);
        startRoomInfoIntent.putExtra(MemberDetailsActivity.EXTRA_ROOM_ID, mRoom.getRoomId());
        startRoomInfoIntent.putExtra(MemberDetailsActivity.EXTRA_MEMBER_ID, userId);
        startRoomInfoIntent.putExtra(MemberDetailsActivity.EXTRA_MATRIX_ID, mSession.getCredentials().userId);
        getActivity().startActivity(startRoomInfoIntent);
    }

    public boolean onAvatarLongClick(String userId) {
        return false;
    }

    public void onSenderNameClick(String userId, String displayName) {
        if (getActivity() instanceof RoomActivity) {
            ((RoomActivity)getActivity()).insertInTextEditor(mRoom.getLiveState().getMemberName(userId));
        }
    }

    public void onMediaDownloaded(int position) {
    }


    private void save(final Message message, final String mediaUrl, final String mediaMimeType) {
        AlertDialog.Builder builderSingle = new AlertDialog.Builder(getActivity());

        builderSingle.setTitle(getActivity().getText(R.string.save_files_in));
        final ArrayAdapter<String> arrayAdapter = new ArrayAdapter<String>(getActivity(), R.layout.dialog_room_selection);

        ArrayList<String> entries = new ArrayList<String>();

        entries.add(getActivity().getText(R.string.downloads).toString());

        if ((null == mediaMimeType) || mediaMimeType.startsWith("image/")) {
            entries.add(getActivity().getText(R.string.gallery).toString());
        }

        if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
            entries.add(getActivity().getText(R.string.other).toString());
        }

        arrayAdapter.addAll(entries);

        final ArrayList<String> fEntries = entries;

        builderSingle.setNegativeButton(getActivity().getText(R.string.cancel),
                new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        dialog.dismiss();
                    }
                });

        builderSingle.setAdapter(arrayAdapter,
                new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, final int which) {
                        dialog.dismiss();

                        MXMediasCache cache = getMXMediasCache();
                        File cacheFile = cache.mediaCacheFile(mediaUrl, mediaMimeType);

                        String entry = fEntries.get(which);
                        String savedFilename = null;

                        if (getActivity().getText(R.string.gallery).toString().equals(entry)) {
                            // save in the gallery
                            savedFilename = CommonActivityUtils.saveImageIntoGallery(getActivity(), cacheFile);
                        } else if (getActivity().getText(R.string.downloads).toString().equals(entry)) {
                            String filename = null;

                            if (message instanceof FileMessage)  {
                                filename = ((FileMessage)message).body;
                            }

                            // save into downloads
                            savedFilename = CommonActivityUtils.saveMediaIntoDownloads(getActivity(), cacheFile, filename, mediaMimeType);
                        } else {
                            if (getActivity() instanceof RoomActivity) {
                                ((RoomActivity)getActivity()).createDocument(message, mediaUrl, mediaMimeType);
                            }
                        }

                        if (null != savedFilename) {
                            final String fSavedFilename = new File(savedFilename).getName();
                            getActivity().runOnUiThread(new Runnable() {
                                @Override
                                public void run() {
                                    Toast.makeText(getActivity(), getActivity().getString(R.string.file_is_saved, fSavedFilename), Toast.LENGTH_SHORT).show();
                                }
                            });
                        }
                    }
                });

        // GA issue
        // can not perform this action after onSaveInstanceState....
        // it seems that the linked activity is stopped.
        try {
            builderSingle.show();
        } catch (Exception e) {
        }
    }

    public void onReadReceiptClick(String eventId, String userId, ReceiptData receipt) {
        RoomMember member = mRoom.getMember(userId);

        // sanity check
        if (null != member) {
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());

            SpannableStringBuilder body = new SpannableStringBuilder(getActivity().getString(R.string.read_receipt) + " : " + dateFormat.format(new Date(receipt.originServerTs)));
            body.setSpan(new android.text.style.StyleSpan(android.graphics.Typeface.BOLD), 0, getActivity().getString(R.string.read_receipt).length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

            new AlertDialog.Builder(ConsoleApplication.getCurrentActivity())
                    .setTitle(member.getName())
                    .setMessage(body)
                    .setPositiveButton("OK", new DialogInterface.OnClickListener() {
                        @Override
                        public void onClick(DialogInterface dialog, int which) {
                            dialog.dismiss();
                        }
                    })
                            .create()
                            .show();
        }
    }

    public void onMoreReadReceiptClick(String eventId) {
        FragmentManager fm = getActivity().getSupportFragmentManager();

        ReadReceiptsDialogFragment fragment = (ReadReceiptsDialogFragment) fm.findFragmentByTag(TAG_FRAGMENT_RECEIPTS_DIALOG);
        if (fragment != null) {
            fragment.dismissAllowingStateLoss();
        }
        fragment = ReadReceiptsDialogFragment.newInstance(mSession, mRoom.getRoomId(), eventId);
        fragment.show(fm, TAG_FRAGMENT_RECEIPTS_DIALOG);
    }
}
console/src/main/java/org/matrix/console/fragments/RoomMembersDialogFragment.java
public class RoomMembersDialogFragment extends DialogFragment {
    private static final String LOG_TAG = "RoomMembersDialogFragment";

    public static final String ARG_ROOM_ID = "org.matrix.console.fragments.RoomMembersDialogFragment.ARG_ROOM_ID";

    public static RoomMembersDialogFragment newInstance(MXSession session, String roomId) {
        RoomMembersDialogFragment f= new RoomMembersDialogFragment();
        Bundle args = new Bundle();
        args.putString(ARG_ROOM_ID, roomId);
        f.setArguments(args);
        f.setSession(session);
        return f;
    }

    private ListView mListView;
    private ConsoleRoomMembersAdapter mAdapter;
    private String mRoomId;
    private MXSession mSession;

    private Handler uiThreadHandler;

    private IMXEventListener mEventsListenener = new MXEventListener() {
        @Override
        public void onPresenceUpdate(Event event, final User user) {
            // Someone's presence has changed, reprocess the whole list
            uiThreadHandler.post(new Runnable() {
                @Override
                public void run() {
                    mAdapter.saveUser(user);
                    mAdapter.sortMembers();
                    mAdapter.notifyDataSetChanged();
                }
            });
        }

        @Override
        public void onLiveEvent(final Event event, RoomState roomState) {
            uiThreadHandler.post(new Runnable() {
                @Override
                public void run() {
                    if (Event.EVENT_TYPE_STATE_ROOM_MEMBER.equals(event.type)) {
                        RoomMember member = JsonUtils.toRoomMember(event.content);
                        User user = mSession.getDataHandler().getStore().getUser(member.getUserId());

                        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getActivity());
                        boolean displayLeftMembers = preferences.getBoolean(getString(R.string.settings_key_display_left_members), false);

                        if (member.hasLeft() && !displayLeftMembers) {
                            mAdapter.deleteUser(user);
                            mAdapter.remove(member);
                            mAdapter.notifyDataSetChanged();
                        } else {
                            // the user can be a new one
                            boolean mustResort = mAdapter.saveUser(user);
                            mAdapter.updateMember(event.stateKey, JsonUtils.toRoomMember(event.content));

                            if (mustResort) {
                                mAdapter.sortMembers();
                                mAdapter.notifyDataSetChanged();
                            }
                        }
                    } else if (Event.EVENT_TYPE_STATE_ROOM_POWER_LEVELS.equals(event.type)) {
                        mAdapter.setPowerLevels(JsonUtils.toPowerLevels(event.content));
                    }
                }
            });
        }
    };

    public void setSession(MXSession session) {
        mSession = session;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mRoomId = getArguments().getString(ARG_ROOM_ID);
        uiThreadHandler = new Handler();

        if (mSession == null) {
            throw new RuntimeException("No MXSession.");
        }
    }

    @Override
    public void onPause() {
        super.onPause();
        mSession.getDataHandler().getRoom(mRoomId).removeEventListener(mEventsListenener);

    }

    @Override
    public void onResume() {
        super.onResume();
        mSession.getDataHandler().getRoom(mRoomId).addEventListener(mEventsListenener);
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        Dialog d = super.onCreateDialog(savedInstanceState);
        d.setTitle(getString(R.string.members_list));
        return d;
    }

    /**
     * Return the used medias cache.
     * This method can be overridden to use another medias cache
     * @return the used medias cache
     */
    public MXMediasCache getMXMediasCache() {
        return Matrix.getInstance(getActivity()).getMediasCache();
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        super.onCreateView(inflater, container, savedInstanceState);
        View v = inflater.inflate(R.layout.fragment_dialog_member_list, container, false);
        mListView = ((ListView)v.findViewById(R.id.listView_members));

        final Room room = mSession.getDataHandler().getRoom(mRoomId);

        HashMap<String, String> membershipStrings = new HashMap<String, String>();
        membershipStrings.put(RoomMember.MEMBERSHIP_INVITE, getActivity().getString(R.string.membership_invite));
        membershipStrings.put(RoomMember.MEMBERSHIP_JOIN, getActivity().getString(R.string.membership_join));
        membershipStrings.put(RoomMember.MEMBERSHIP_LEAVE, getActivity().getString(R.string.membership_leave));
        membershipStrings.put(RoomMember.MEMBERSHIP_BAN, getActivity().getString(R.string.membership_ban));

        mAdapter = new ConsoleRoomMembersAdapter(getActivity(), mSession.getHomeserverConfig(), R.layout.adapter_item_room_members, room.getLiveState(), getMXMediasCache(), membershipStrings);

        // apply the sort settings
        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getActivity());
        mAdapter.sortByLastActivePresence(preferences.getBoolean(getString(R.string.settings_key_sort_by_last_seen), true));
        mAdapter.displayMembership(preferences.getBoolean(getString(R.string.settings_key_display_left_members), false));

        boolean displayLeftMembers = preferences.getBoolean(getString(R.string.settings_key_display_left_members), false);

        Collection<RoomMember> members = room.getMembers();
        if (members != null) {
            IMXStore store = mSession.getDataHandler().getStore();

            for (RoomMember m : members) {
                // by default the
                if ((!m.hasLeft()) || displayLeftMembers) {
                    mAdapter.add(m);
                    mAdapter.saveUser(store.getUser(m.getUserId()));
                }
            }
            mAdapter.sortMembers();
        }

        mAdapter.setPowerLevels(room.getLiveState().getPowerLevels());
        mListView.setAdapter(mAdapter);

        // display the number of members in this room
        // don't update it dynamically
        // assume that the number of members will not be updated
        this.getDialog().setTitle(getString(R.string.members_list) + " (" + mAdapter.getCount() + ")");

        mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                // Get the member and display the possible actions for them
                final RoomMember roomMember = mAdapter.getItem(position);
                final Activity activity = ConsoleApplication.getCurrentActivity();

                activity.runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        Intent startRoomInfoIntent = new Intent(activity, MemberDetailsActivity.class);
                        startRoomInfoIntent.putExtra(MemberDetailsActivity.EXTRA_ROOM_ID, mRoomId);
                        startRoomInfoIntent.putExtra(MemberDetailsActivity.EXTRA_MEMBER_ID, roomMember.getUserId());
                        startRoomInfoIntent.putExtra(MemberDetailsActivity.EXTRA_MATRIX_ID, mSession.getCredentials().userId);
                        startActivity(startRoomInfoIntent);
                    }
                });

                // dismiss the member list
                RoomMembersDialogFragment.this.dismiss();
            }
        });

        return v;
    }
}
console/src/main/java/org/matrix/console/util/ResourceUtils.java
public class ResourceUtils {

    private static final String LOG_TAG = "ResourceUtils";

    public static class Resource {
        public final InputStream contentStream;
        public final String mimeType;

        public Resource(InputStream contentStream, String mimeType) {
            this.contentStream = contentStream;
            this.mimeType = mimeType;
        }
    }

    /**
     * Get a resource stream and metadata about it given its URI returned from onActivityResult.
     *
     * @param activity the activity
     * @param uri the URI
     * @return a {@link Resource} encapsulating the opened resource stream and associated metadata
     *      or {@code null} if opening the resource stream failed.
     */
    public static Resource openResource(Activity activity, Uri uri) {
        try {
            String mimetype = activity.getContentResolver().getType(uri);

            // try to find the mimetype from the filename
            if (null == mimetype) {
                String extension = MimeTypeMap.getFileExtensionFromUrl(uri.toString());
                if (extension != null) {
                    mimetype = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
                }
            }

            return new Resource(
                    activity.getContentResolver().openInputStream(uri),
                    mimetype);

        } catch (Exception e) {
            Log.e(LOG_TAG, "Failed to open resource input stream", e);
        }

        return null;
    }
}
console/src/main/java/org/matrix/console/Matrix.java
public class Matrix {

    private static final String LOG_TAG = "Matrix";

    private static Matrix instance = null;

    private LoginStorage mLoginStorage;
    private ArrayList<MXSession> mMXSessions;
    private GcmRegistrationManager mGcmRegistrationManager;
    private Context mAppContext;

    public boolean mHasBeenDisconnected = false;

    protected Matrix(Context appContext) {
        mAppContext = appContext.getApplicationContext();
        mLoginStorage = new LoginStorage(mAppContext);
        mMXSessions = new ArrayList<MXSession>();
        mGcmRegistrationManager = new GcmRegistrationManager(mAppContext);
        RageShake.getInstance().start(mAppContext);
    }

    public synchronized static Matrix getInstance(Context appContext) {
        if ((instance == null) && (null != appContext)) {
            instance = new Matrix(appContext);
        }
        return instance;
    }

    public LoginStorage getLoginStorage() {
        return mLoginStorage;
    }

    /**
     * @return the application version
     */
    public String getVersion(boolean longformat) {
        String versionName = "";
        try {
            PackageInfo pInfo = mAppContext.getPackageManager().getPackageInfo(mAppContext.getPackageName(), 0);
            versionName = pInfo.versionName;
        } catch (Exception e) {
        }

        String gitVersion = mAppContext.getResources().getString(R.string.git_revision);
        if (longformat) {
            String date = mAppContext.getResources().getString(R.string.git_revision_date);
            versionName += " (" + gitVersion + "-" + date + ")";
        } else {
            versionName += " (" + gitVersion + ")";
        }

        return versionName;
    }

    /**
     * Static method top the MXSession list
     * @param context the application content
     * @return the sessions list
     */
    public static ArrayList<MXSession> getMXSessions(Context context) {
        if ((null != context) && (null != instance)) {
            return instance.getSessions();
        } else {
            return null;
        }
    }

    /**
     * @return The list of sessions
     */
    public ArrayList<MXSession> getSessions() {
        ArrayList<MXSession> sessions = new ArrayList<MXSession>();

        synchronized (instance) {
            if (null != mMXSessions) {
                sessions = new ArrayList<MXSession>(mMXSessions);
            }
        }

        return sessions;
    }

    /**
     * Retrieve the default session if one exists.
     *
     * The default session may be user-configured, or it may be the last session the user was using.
     * @return The default session or null.
     */
    public synchronized MXSession getDefaultSession() {
        ArrayList<MXSession> sessions = getSessions();

        if (sessions.size() > 0) {
            return sessions.get(0);
        }

        ArrayList<HomeserverConnectionConfig> hsConfigList = mLoginStorage.getCredentialsList();

        // any account ?
        if ((hsConfigList == null) || (hsConfigList.size() == 0)) {
            return null;
        }

        ArrayList<String> matrixIds = new ArrayList<String>();
        sessions = new ArrayList<MXSession>();

        for(HomeserverConnectionConfig config: hsConfigList) {
            // avoid duplicated accounts.
            if (config.getCredentials() != null && matrixIds.indexOf(config.getCredentials().userId) < 0) {
                MXSession session = createSession(config);
                sessions.add(session);
                matrixIds.add(config.getCredentials().userId);
            }
        }

        synchronized (instance) {
            mMXSessions = sessions;
        }

        return sessions.get(0);
    }

    /**
     * Static method to return a MXSession from an account Id.
     * @param matrixId the matrix id
     * @return the MXSession.
     */
    public static MXSession getMXSession(Context context, String matrixId) {
        return Matrix.getInstance(context.getApplicationContext()).getSession(matrixId);
    }

    /**
     *Retrieve a session from an user Id.
     * The application should be able to manage multi session.
     * @param matrixId the matrix id
     * @return the MXsession if it exists.
     */
    public synchronized MXSession getSession(String matrixId) {
        if (null != matrixId) {
            ArrayList<MXSession> sessions;

            synchronized (this) {
                sessions = getSessions();
            }

            for (MXSession session : sessions) {
                Credentials credentials = session.getCredentials();

                if ((null != credentials) && (credentials.userId.equals(matrixId))) {
                    return session;
                }
            }
        }

        return getDefaultSession();
    }

    /**
     * Add an error listener to each sessions
     * @param activity the activity.
     */
    public static void setSessionErrorListener(Activity activity) {
        if ((null != instance) && (null != activity)) {
            Collection<MXSession> sessions = getMXSessions(activity);

            for(MXSession session : sessions) {
                if (session.isAlive()) {
                    session.setFailureCallback(new ErrorListener(session, activity));
                }
            }
        }
    }

    /**
     * Remove the sessions error listener to each
     */
    public static void removeSessionErrorListener(Activity activity) {
        if ((null != instance) && (null != activity)) {
            Collection<MXSession> sessions = getMXSessions(activity);

            for(MXSession session : sessions) {
                if (session.isAlive()) {
                    session.setFailureCallback(null);
                }
            }
        }
    }

    /**
     * Return the used media caches.
     * This class can inherited to customized it.
     * @return the mediasCache.
     */
    public MXMediasCache getMediasCache() {
        if (getSessions().size() > 0) {
            return getSessions().get(0).getMediasCache();
        }
        return null;
    }

    /**
     * Return the used latestMessages caches.
     * This class can inherited to customized it.
     * @return the latest messages cache.
     */
    public MXLatestChatMessageCache getDefaultLatestChatMessageCache() {
        if (getSessions().size() > 0) {
            return getSessions().get(0).getLatestChatMessageCache();
        }
        return null;
    }
    /**
     *
     * @return true if the matrix client instance defines a valid session
     */
    public static Boolean hasValidSessions() {
        if (null == instance) {
            Log.e(LOG_TAG, "hasValidSessions : has no instance");
            return false;
        }

        Boolean res;

        synchronized (instance) {
            res = (null != instance.mMXSessions) && (instance.mMXSessions.size() > 0);

            if (!res) {
                Log.e(LOG_TAG, "hasValidSessions : has no session");
            } else {
                for(MXSession session : instance.mMXSessions) {
                    // some GA issues reported that the data handler can be null
                    // so assume the application should be restarted
                    res &= (null != session.getDataHandler());
                }

                if (!res) {
                    Log.e(LOG_TAG, "hasValidSessions : one sesssion has no valid data hanlder");
                }
            }
        }

        return res;
    }

    /**
     * Refresh the sessions push rules.
     */
    public void refreshPushRules() {
        ArrayList<MXSession> sessions = null;

        synchronized (this) {
            sessions = getSessions();
        }

        for(MXSession session : sessions) {
            if (null != session.getDataHandler()) {
                session.getDataHandler().refreshPushRules();
            }
        }
    }

    /**
     * Clear a session.
     * @param context the context.
     * @param session the session to clear.
     * @param clearCredentials true to clear the credentials.
     */
    public synchronized void clearSession(Context context, MXSession session, Boolean clearCredentials) {
        if (clearCredentials) {
            mLoginStorage.removeCredentials(session.getHomeserverConfig());
        }

        session.clear(context);

        synchronized (instance) {
            mMXSessions.remove(session);
        }
    }

    /**
     * Clear any existing session.
     * @param context the context.
     * @param clearCredentials  true to clear the credentials.
     */
    public synchronized void clearSessions(Context context, Boolean clearCredentials) {
        synchronized (instance) {
            while (mMXSessions.size() > 0) {
                clearSession(context, mMXSessions.get(0), clearCredentials);
            }
        }
    }

    /**
     * Set a default session.
     * @param session The session to store as the default session.
     */
    public synchronized void addSession(MXSession session) {
        mLoginStorage.addCredentials(session.getHomeserverConfig());
        synchronized (instance) {
            mMXSessions.add(session);
        }
    }

    /**
     * Creates an MXSession from some credentials.
     * @param hsConfig The HomeserverConnectionConfig to create a session from.
     * @return The session.
     */
    public MXSession createSession(HomeserverConnectionConfig hsConfig) {
        return createSession(mAppContext, hsConfig);
    }

    /**
     * Creates an MXSession from some credentials.
     * @param context the context.
     * @param hsConfig The HomeserverConnectionConfig to create a session from.
     * @return The session.
     */
    public MXSession createSession(Context context, HomeserverConnectionConfig hsConfig) {
        IMXStore store;

        Credentials credentials = hsConfig.getCredentials();

        if (true) {
            store = new MXFileStore(hsConfig, context);
        } else {
            store = new MXMemoryStore(hsConfig.getCredentials());
        }

        return new MXSession(hsConfig, new MXDataHandler(store, credentials, new MXDataHandler.InvalidTokenListener() {
            @Override
            public void onTokenCorrupted() {
                if (null != ConsoleApplication.getCurrentActivity()) {
                    CommonActivityUtils.logout(ConsoleApplication.getCurrentActivity());
                }
            }
        }), mAppContext);
    }

    /**
     * Reload the matrix sessions.
     * The session caches are cleared before being reloaded.
     * Any opened activity is closed and the application switches to the splash screen.
     * @param fromActivity the caller activity
     */
    public void reloadSessions(Activity fromActivity) {
        ArrayList<MXSession> sessions = getMXSessions(fromActivity);

        for(MXSession session : sessions) {
            CommonActivityUtils.logout(fromActivity, session, false);
        }

        clearSessions(fromActivity, false);

        synchronized (instance) {
            // build a new sessions list
            ArrayList<HomeserverConnectionConfig> configs = mLoginStorage.getCredentialsList();

            for(HomeserverConnectionConfig config : configs) {
                MXSession session = createSession(config);
                mMXSessions.add(session);
            }
        }

        Intent intent = new Intent(fromActivity, SplashActivity.class);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);

        fromActivity.startActivity(intent);
        fromActivity.finish();
    }

    public GcmRegistrationManager getSharedGcmRegistrationManager() {
        return mGcmRegistrationManager;
    }

}
console/src/main/java/org/matrix/console/fragments/RoomInfoUpdateDialogFragment.java
public class RoomInfoUpdateDialogFragment extends DialogFragment {

    public static final String EXTRA_MATRIX_ID = "org.matrix.console.fragments.RoomInfoUpdateDialogFragment.EXTRA_MATRIX_ID";
    public static final String EXTRA_ROOM_ID = "org.matrix.console.fragments.RoomInfoUpdateDialogFragment.EXTRA_ROOM_ID";

    public static RoomInfoUpdateDialogFragment newInstance(String matrixId, String roomId) {
        RoomInfoUpdateDialogFragment f = new RoomInfoUpdateDialogFragment();
        Bundle args = new Bundle();
        args.putString(EXTRA_MATRIX_ID, matrixId);
        args.putString(EXTRA_ROOM_ID, roomId);
        f.setArguments(args);
        return f;
    }

    private String mMatrixId = null;
    private String mRoomId = null;
    private Room mRoom = null;

    private EditText mEditTextName;
    private EditText mEditTextTopic;
    private EditText mEditTextCanonical;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mMatrixId = getArguments().getString(EXTRA_MATRIX_ID);
        mRoomId = getArguments().getString(EXTRA_ROOM_ID);

        MXSession session = Matrix.getInstance(getActivity()).getSession(mMatrixId);

        if (null != session) {
            mRoom = session.getDataHandler().getRoom(mRoomId);
        }

        if (null == mRoom) {
            dismiss();
        }
    }

    private void saveRoomInfo() {
        // Save things
        RoomState roomState = mRoom.getLiveState();
        String nameFromForm = mEditTextName.getText().toString();
        String topicFromForm = mEditTextTopic.getText().toString();
        String canonicalFromForm = mEditTextCanonical.getText().toString();

        ApiCallback<Void> changeCallback = UIUtils.buildOnChangeCallback(null);

        if (UIUtils.hasFieldChanged(roomState.name, nameFromForm)) {
            mRoom.updateName(nameFromForm, changeCallback);
        }

        if (UIUtils.hasFieldChanged(roomState.topic, topicFromForm)) {
            mRoom.updateTopic(topicFromForm, changeCallback);
        }

        if (UIUtils.hasFieldChanged(roomState.alias, canonicalFromForm)) {
            mRoom.updateCanonicalAlias(canonicalFromForm, changeCallback);
        }
    }

    private boolean hasChanges() {
        // Save things
        RoomState roomState = mRoom.getLiveState();
        String nameFromForm = mEditTextName.getText().toString();
        String topicFromForm = mEditTextTopic.getText().toString();
        String canonicalFromForm = mEditTextCanonical.getText().toString();

        return UIUtils.hasFieldChanged(roomState.alias, canonicalFromForm) || UIUtils.hasFieldChanged(roomState.name, nameFromForm) || UIUtils.hasFieldChanged(roomState.topic, topicFromForm);
    }

    private void manageOkButton(final Button okButton) {
        okButton.setEnabled(hasChanges());
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());

        final View view = getActivity().getLayoutInflater().inflate(R.layout.fragment_dialog_room_info, null);
        builder.setView(view);
        builder.setTitle(getString(R.string.action_room_info));

        mEditTextName =  (EditText)view.findViewById(R.id.editText_name);
        mEditTextTopic = (EditText)view.findViewById(R.id.editText_topic);
        mEditTextCanonical = (EditText)view.findViewById(R.id.editText_canonical);

        mEditTextName.setText(mRoom.getLiveState().name);
        mEditTextTopic.setText(mRoom.getLiveState().topic);
        mEditTextCanonical.setText(mRoom.getLiveState().alias);

        final Button okButton = (Button) view.findViewById(R.id.room_info_ok);
        final Button cancelButton = (Button) view.findViewById(R.id.room_info_cancel);

        mEditTextName.addTextChangedListener(new TextWatcher() {
            public void afterTextChanged(android.text.Editable s) {
                manageOkButton(okButton);
            }

            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            }

            public void onTextChanged(CharSequence s, int start, int before, int count) {
            }
        });

        mEditTextTopic.addTextChangedListener(new TextWatcher() {
            public void afterTextChanged(android.text.Editable s) {
                manageOkButton(okButton);
            }

            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            }

            public void onTextChanged(CharSequence s, int start, int before, int count) {
            }
        });

        mEditTextCanonical.addTextChangedListener(new TextWatcher() {
            public void afterTextChanged(android.text.Editable s) {
                manageOkButton(okButton);
            }

            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            }

            public void onTextChanged(CharSequence s, int start, int before, int count) {
            }
        });

        okButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                saveRoomInfo();
                RoomInfoUpdateDialogFragment.this.dismiss();
            }
        });

        cancelButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // something has been updated ?
                if (hasChanges()) {
                    AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
                    builder.setMessage(
                            R.string.room_info_room_discard_changes)
                            .setCancelable(false)
                            .setPositiveButton(R.string.room_info_room_discard,
                                    new DialogInterface.OnClickListener() {
                                        public void onClick(DialogInterface dialog,
                                                            int id) {
                                            RoomInfoUpdateDialogFragment.this.dismiss();
                                        }
                                    })
                            .setNegativeButton(R.string.save,
                                    new DialogInterface.OnClickListener() {
                                        public void onClick(DialogInterface dialog,
                                                            int id) {
                                            saveRoomInfo();
                                            RoomInfoUpdateDialogFragment.this.dismiss();
                                        }
                                    });

                    AlertDialog alert = builder.create();
                    alert.show();
                } else {
                    RoomInfoUpdateDialogFragment.this.dismiss();
                }
            }
        });

        return builder.create();
    }
}
console/src/main/java/org/matrix/console/adapters/ImageCompressionDescription.java
public class ImageCompressionDescription  {
    public String mCompressionText;
    public String mCompressionInfoText;
}
console/src/main/java/org/matrix/console/util/NotificationUtils.java
public class NotificationUtils {

    public static final String QUICK_LAUNCH_ACTION = "org.matrix.console.services.EventStreamService.QUICK_LAUNCH_ACTION";
    public static final String TAP_TO_VIEW_ACTION = "org.matrix.console.services.EventStreamService.TAP_TO_VIEW_ACTION";
    public static final String CAR_VOICE_REPLY_KEY = "org.matrix.console.services.EventStreamService.CAR_VOICE_REPLY_KEY" ;
    public static final String ACTION_MESSAGE_HEARD = "org.matrix.console.ACTION_MESSAGE_HEARD";
    public static final String ACTION_MESSAGE_REPLY = "org.matrix.console.ACTION_MESSAGE_REPLY";
    public static final String EXTRA_ROOM_ID = "org.matrix.console.EXTRA_ROOM_ID";

    // the bubble radius is computed for 99 
    static int mUnreadBubbleWidth = -1;


    public static Notification buildCallNotification(Context context, String roomName, String roomId, String matrixId, String callId) {
        NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
        builder.setWhen(System.currentTimeMillis());

        builder.setContentTitle(roomName);
        builder.setContentText(context.getString(R.string.call_in_progress));

        if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.LOLLIPOP) {
            builder.setSmallIcon(R.drawable.ic_menu_small_matrix);
        } else {
            builder.setSmallIcon(R.drawable.ic_menu_small_matrix_transparent);
        }


        // Build the pending intent for when the notification is clicked
        Intent roomIntent = new Intent(context, RoomActivity.class);
        roomIntent.putExtra(RoomActivity.EXTRA_ROOM_ID, roomId);
        roomIntent.putExtra(RoomActivity.EXTRA_MATRIX_ID, matrixId);
        roomIntent.putExtra(RoomActivity.EXTRA_START_CALL_ID, callId);

        // Recreate the back stack
        TaskStackBuilder stackBuilder = TaskStackBuilder.create(context)
                .addParentStack(RoomActivity.class)
                .addNextIntent(roomIntent);


        // android 4.3 issue
        // use a generator for the private requestCode.
        // When using 0, the intent is not created/launched when the user taps on the notification.
        //
        PendingIntent pendingIntent = stackBuilder.getPendingIntent((new Random()).nextInt(1000), PendingIntent.FLAG_UPDATE_CURRENT);
        builder.setContentIntent(pendingIntent);

        Notification n = builder.build();
        n.flags |= Notification.FLAG_SHOW_LIGHTS;
        n.defaults |= Notification.DEFAULT_LIGHTS;

        return n;
    }

    public static Notification buildMessageNotification(
            Context context, String from, String matrixId, String callId, Boolean displayMatrixId, Bitmap largeIcon, int globalUnseen, int memberUnseen, String body, String roomId, String roomName,
            boolean shouldPlaySound) {
        NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
        builder.setWhen(System.currentTimeMillis());

        if (null != from) {
            // don't display the room name for 1:1 room notifications.
            if (!TextUtils.isEmpty(roomName) && !roomName.equals(from)) {
                builder.setContentTitle(from + " (" + roomName + ")");
            } else {
                builder.setContentTitle(from);
            }
        }

        builder.setContentText(body);
        builder.setAutoCancel(true);

        if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.LOLLIPOP) {
            builder.setSmallIcon(R.drawable.ic_menu_small_matrix);
        } else {
            builder.setSmallIcon(R.drawable.ic_menu_small_matrix_transparent);
        }

        if (null != largeIcon) {
        	// add a bubble in the top right
            if (0 != memberUnseen) {
                try {
                    android.graphics.Bitmap.Config bitmapConfig = largeIcon.getConfig();

                    // set default bitmap config if none
                    if(bitmapConfig == null) {
                        bitmapConfig = android.graphics.Bitmap.Config.ARGB_8888;
                    }

                    // setLargeIcon must used a 64 * 64 pixels bitmap
                    // rescale to have the same text UI.
                    float densityScale = context.getResources().getDisplayMetrics().density;
                    int side = (int)(64 * densityScale);

                    Bitmap bitmapCopy = Bitmap.createBitmap(side, side, bitmapConfig);
                    Canvas canvas = new Canvas(bitmapCopy);

                    // resize the bitmap to fill in size
                    int bitmapWidth = largeIcon.getWidth();
                    int bitmapHeight = largeIcon.getHeight();

                    float scale = Math.max((float) canvas.getWidth() / (float) bitmapWidth, (float) canvas.getHeight() / (float) bitmapHeight);

                    int scaledWidth = (int) (bitmapWidth * scale);
                    int scaledHeight = (int) (bitmapHeight * scale);

                    Bitmap rescaledBitmap = Bitmap.createScaledBitmap(largeIcon, scaledWidth, scaledHeight, true);
                    canvas.drawBitmap(rescaledBitmap, (side - scaledWidth) / 2, (side - scaledHeight) / 2, null);

                    String text = "" + memberUnseen;
                    
                    // prepare the text drawing
                    Paint textPaint = new Paint();
                    textPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.BOLD));
                    textPaint.setColor(Color.WHITE);
                    textPaint.setTextSize(10 * densityScale);

                    // get its size
                    Rect textBounds = new Rect();

                    if (-1 == mUnreadBubbleWidth) {
                        textPaint.getTextBounds("99", 0, 2, textBounds);
                        mUnreadBubbleWidth = textBounds.width();
                    }

                    textPaint.getTextBounds(text, 0, text.length(), textBounds);

                    // draw a red circle
                    int radius = mUnreadBubbleWidth;
                    Paint paint = new Paint();
                    paint.setStyle(Paint.Style.FILL);
                    paint.setColor(Color.RED);
                    canvas.drawCircle(canvas.getWidth() - radius, radius,  radius , paint);

                    // draw the text
                    canvas.drawText(text, canvas.getWidth() - textBounds.width() - (radius - (textBounds.width() / 2)), -textBounds.top + (radius - (-textBounds.top / 2)), textPaint);

                    // get the new bitmap
                    largeIcon = bitmapCopy;
                } catch (Exception e) {
                }
            }

            builder.setLargeIcon(largeIcon);
        }

        if (0 != globalUnseen) {
            String unseenText = context.getString((globalUnseen == 1) ? R.string.unseen_message : R.string.unseen_messages, globalUnseen);

            SpannableString spannable = new SpannableString(unseenText);
            spannable.setSpan(new ForegroundColorSpan(Color.RED), 0, unseenText.length(), 0);

            StyleSpan boldSpan = new StyleSpan(Typeface.BOLD);
            spannable.setSpan(boldSpan, 0, unseenText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

            builder.setSubText(spannable);
        }

        String name = ": ";
        if(!TextUtils.isEmpty(roomName)) {
            name = " (" + roomName + "): ";
        }

        if (displayMatrixId) {
            from = "[" + matrixId + "]\n" + from;
        }

        builder.setTicker(from + name + body);

        // Build the pending intent for when the notification is clicked
        Intent roomIntent = new Intent(context, RoomActivity.class);
        roomIntent.putExtra(RoomActivity.EXTRA_ROOM_ID, roomId);

        if (null != matrixId) {
            roomIntent.putExtra(RoomActivity.EXTRA_MATRIX_ID, matrixId);
        }

        if (null != callId) {
            roomIntent.putExtra(RoomActivity.EXTRA_START_CALL_ID, callId);
        }

        roomIntent.putExtra(RoomActivity.EXTRA_START_FROM_PUSH, "");

        // Recreate the back stack
        TaskStackBuilder stackBuilder = TaskStackBuilder.create(context)
                .addParentStack(RoomActivity.class)
                .addNextIntent(roomIntent);


        // android 4.3 issue
        // use a generator for the private requestCode.
        // When using 0, the intent is not created/launched when the user taps on the notification.
        //
        PendingIntent pendingIntent = stackBuilder.getPendingIntent((new Random()).nextInt(1000), PendingIntent.FLAG_UPDATE_CURRENT);
        builder.setContentIntent(pendingIntent);

        // display the message with more than 1 lines when the device supports it
        NotificationCompat.BigTextStyle textStyle = new NotificationCompat.BigTextStyle();
        textStyle.bigText(from + ":" + body);
        builder.setStyle(textStyle);

        // do not offer to quick respond if the user did not dismiss the previous one
        if (!LockScreenActivity.isDisplayingALockScreenActivity() && (null == callId)) {
            // offer to type a quick answer (i.e. without launching the application)
            Intent quickReplyIntent = new Intent(context, LockScreenActivity.class);
            quickReplyIntent.putExtra(LockScreenActivity.EXTRA_ROOM_ID, roomId);
            quickReplyIntent.putExtra(LockScreenActivity.EXTRA_SENDER_NAME, from);
            quickReplyIntent.putExtra(LockScreenActivity.EXTRA_MESSAGE_BODY, body);

            if (null != matrixId) {
                quickReplyIntent.putExtra(LockScreenActivity.EXTRA_MATRIX_ID, matrixId);
            }

            // the action must be unique else the parameters are ignored
            quickReplyIntent.setAction(QUICK_LAUNCH_ACTION + ((int) (System.currentTimeMillis())));
            PendingIntent pIntent = PendingIntent.getActivity(context, 0, quickReplyIntent, 0);
            builder.addAction(
                    R.drawable.ic_menu_edit,
                    context.getString(R.string.action_quick_reply),
                    pIntent);

            // Build the pending intent for when the notification is clicked
            Intent roomIntentTap = new Intent(context, RoomActivity.class);
            roomIntentTap.putExtra(RoomActivity.EXTRA_ROOM_ID, roomId);
            // the action must be unique else the parameters are ignored
            roomIntentTap.setAction(TAP_TO_VIEW_ACTION + ((int) (System.currentTimeMillis())));
            // Recreate the back stack
            TaskStackBuilder stackBuildertap = TaskStackBuilder.create(context)
                    .addParentStack(RoomActivity.class)
                    .addNextIntent(roomIntentTap);
            builder.addAction(
                    R.drawable.ic_menu_start_conversation,
                    context.getString(R.string.action_open),
                    stackBuildertap.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT));
        }

        extendForCar(context, builder, roomId, roomName, from, body);

        Notification n = builder.build();
        n.flags |= Notification.FLAG_SHOW_LIGHTS;
        n.defaults |= Notification.DEFAULT_LIGHTS;

        if (shouldPlaySound) {
            n.defaults |= Notification.DEFAULT_SOUND;
        }

        // some devices crash if this field is not set
        // even if it is deprecated
        n.setLatestEventInfo(context, from, body, pendingIntent);

        return n;
    }

    private static void extendForCar(Context context, NotificationCompat.Builder builder, String roomId, String roomName, String from, String body) {
        int carConversationId = roomId.hashCode();
        Intent msgHeardIntent = new Intent()
                .addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES)
                .setAction(ACTION_MESSAGE_HEARD)
                .putExtra(EXTRA_ROOM_ID, roomId);

        PendingIntent msgHeardPendingIntent =
                PendingIntent.getBroadcast(context,
                        carConversationId,
                        msgHeardIntent,
                        PendingIntent.FLAG_UPDATE_CURRENT);

        Intent msgReplyIntent = new Intent()
                .addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES)
                .setAction(ACTION_MESSAGE_REPLY)
                .putExtra(EXTRA_ROOM_ID, roomId);

        PendingIntent msgReplyPendingIntent = PendingIntent.getBroadcast(
                context,
                carConversationId,
                msgReplyIntent,
                PendingIntent.FLAG_UPDATE_CURRENT);

        // Build a RemoteInput for receiving voice input in a Car Notification
        RemoteInput remoteInput = new RemoteInput.Builder(CAR_VOICE_REPLY_KEY)
                .setLabel(context.getString(R.string.action_quick_reply))
                .build();

        // Create an unread conversation object to organize a group of messages
        // from a room.
        NotificationCompat.CarExtender.UnreadConversation.Builder unreadConvBuilder =
                new NotificationCompat.CarExtender.UnreadConversation.Builder(roomName)
                        .setReadPendingIntent(msgHeardPendingIntent)
                        .setReplyAction(msgReplyPendingIntent, remoteInput);

        unreadConvBuilder.addMessage(context.getString(R.string.user_says_body, from, body))
                .setLatestTimestamp(System.currentTimeMillis());
        builder.extend(new NotificationCompat.CarExtender()
                .setUnreadConversation(unreadConvBuilder.build()));

    }

    private NotificationUtils() {}
}
console/src/main/java/org/matrix/console/fragments/ImageSizeSelectionDialogFragment.java
public class ImageSizeSelectionDialogFragment  extends DialogFragment {
    private static final String LOG_TAG = "ImageSizeSelectionDialogFragment";

    private static final String SELECTIONS_LIST = "SELECTIONS_LIST";

    public static interface ImageSizeListener {
        public void onSelected(int pos);
    }

    public static ImageSizeSelectionDialogFragment newInstance(Collection<ImageCompressionDescription> entries) {
        ImageSizeSelectionDialogFragment f= new ImageSizeSelectionDialogFragment();
        Bundle args = new Bundle();
        f.setArguments(args);
        f.setEntries(entries);
        return f;
    }

    private ListView mListView;
    private ImageSizesAdapter mAdapter;
    private ArrayList<ImageCompressionDescription> mEntries = null;
    private ImageSizeListener mListener = null;

    public void setEntries(Collection<ImageCompressionDescription> entries) {
        mEntries = new ArrayList<ImageCompressionDescription>(entries);
    }

    public void setListener(ImageSizeListener listener) {
        mListener = listener;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    public void onSaveInstanceState(Bundle savedInstanceState) {
        // Always call the superclass so it can save the view hierarchy state
        super.onSaveInstanceState(savedInstanceState);

        if (null != mEntries) {
            savedInstanceState.putSerializable(SELECTIONS_LIST, mEntries);
        }
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        Dialog d = super.onCreateDialog(savedInstanceState);

        if (null != savedInstanceState) {
            if (savedInstanceState.containsKey(SELECTIONS_LIST)) {
                mEntries = (ArrayList<ImageCompressionDescription>)savedInstanceState.getSerializable(SELECTIONS_LIST);
            }
        }

        d.setTitle(getString(R.string.compression_options));
        return d;
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        super.onCreateView(inflater, container, savedInstanceState);
        View v = inflater.inflate(R.layout.fragment_dialog_accounts_list, container, false);
        mListView = ((ListView)v.findViewById(R.id.listView_accounts));

        mAdapter = new ImageSizesAdapter(getActivity(), R.layout.adapter_item_image_size);

        if (null != mEntries) {
            mAdapter.addAll(mEntries);
        }
        mListView.setAdapter(mAdapter);

        mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {

                if (null != mListener) {
                    mListener.onSelected(position);
                }

                // dismiss the list
                ImageSizeSelectionDialogFragment.this.dismiss();
            }
        });

        return v;
    }
}
console/src/main/java/org/matrix/console/ConsoleApplication.java
public class ConsoleApplication extends Application {
    private static final String LOG_TAG = "ConsoleApplication";

    private Timer mActivityTransitionTimer;
    private TimerTask mActivityTransitionTimerTask;
    private boolean mIsInBackground = true;
    private final long MAX_ACTIVITY_TRANSITION_TIME_MS = 2000;

    // google analytics
    private static GoogleAnalytics sGoogleAnalytics;
    private int VERSION_BUILD = -1;
    private String VERSION_STRING = "";

    private Boolean mIsCallingInBackground = false;

    private static ConsoleApplication instance = null;

    private EventEmitter<Activity> mOnActivityDestroyedListener;

    @Override
    public void onCreate() {
        super.onCreate();

        instance = this;

        mOnActivityDestroyedListener = new EventEmitter<>();

        mActivityTransitionTimer = null;
        mActivityTransitionTimerTask = null;

        try {
            PackageInfo pinfo = getPackageManager().getPackageInfo(getPackageName(), 0);
            VERSION_BUILD = pinfo.versionCode;
        }
        catch (PackageManager.NameNotFoundException e) {}

        LogUtilities.setLogDirectory(new File(getCacheDir().getAbsolutePath() + "/logs"));
        LogUtilities.storeLogcat();

        VERSION_STRING = Matrix.getInstance(this).getVersion(false);

        initGoogleAnalytics();

        // reset the application badge at application launch
        CommonActivityUtils.updateUnreadMessagesBadge(this, 0);

        // get the contact update at application launch
        ContactsManager.refreshLocalContactsSnapshot(this);
    }

    public static ConsoleApplication getInstance() {
        return instance;
    }

    public EventEmitter<Activity> getOnActivityDestroyedListener() {
        return mOnActivityDestroyedListener;
    }

    /**
     * Suspend background threads.
     */
    private void suspendApp() {
        Log.d(LOG_TAG, "SuspendApp");

        // suspend the events thread if the client uses GCM
        if (Matrix.getInstance(ConsoleApplication.this).getSharedGcmRegistrationManager().useGCM()) {
            CommonActivityUtils.pauseEventStream(ConsoleApplication.this);
        }
        PIDsRetriever.getIntance().onAppBackgrounded();

        MyPresenceManager.advertiseAllUnavailable();
    }

    /**
     * The application is warned that a call is ended.
     */
    public void onCallEnd() {
        if (isAppInBackground() && mIsCallingInBackground) {
            Log.d(LOG_TAG, "onCallEnd : Suspend the events thread because the call was ended whereas the application was in background");
            suspendApp();
        }

        mIsCallingInBackground = false;
    }

    private void startActivityTransitionTimer() {
        // reset the application badge when displaying a new activity
        // when the user taps on a notification, it is the first called method.
        CommonActivityUtils.updateUnreadMessagesBadge(this, 0);

        this.mActivityTransitionTimer = new Timer();
        this.mActivityTransitionTimerTask = new TimerTask() {
            public void run() {
                ConsoleApplication theApp = ConsoleApplication.this;

                if (theApp.mActivityTransitionTimerTask != null) {
                    theApp.mActivityTransitionTimerTask.cancel();
                    theApp.mActivityTransitionTimerTask = null;
                }

                if (theApp.mActivityTransitionTimer != null) {
                    theApp.mActivityTransitionTimer.cancel();
                    theApp.mActivityTransitionTimer = null;
                }

                theApp.mIsInBackground = true;
                mIsCallingInBackground = (null != CallViewActivity.getActiveCall());

                // if there is a pending call
                // the application is not suspended
                if (!mIsCallingInBackground) {
                    Log.d(LOG_TAG, "Suspend the application because there was no resumed activity within 2 seconds");
                    suspendApp();
                }
            }
        };

        this.mActivityTransitionTimer.schedule(mActivityTransitionTimerTask, MAX_ACTIVITY_TRANSITION_TIME_MS);
    }

    private void stopActivityTransitionTimer() {
        if (this.mActivityTransitionTimerTask != null) {
            this.mActivityTransitionTimerTask.cancel();
            this.mActivityTransitionTimerTask = null;
        }

        if (this.mActivityTransitionTimer != null) {
            this.mActivityTransitionTimer.cancel();
            this.mActivityTransitionTimer = null;
        }

        if (isAppInBackground() && !mIsCallingInBackground) {
            // resume the events thread if the client uses GCM
            if (Matrix.getInstance(ConsoleApplication.this).getSharedGcmRegistrationManager().useGCM()) {

                // the event stream service has been killed
                if (null == EventStreamService.getInstance()) {
                    CommonActivityUtils.startEventStreamService(ConsoleApplication.this);
                } else {
                    CommonActivityUtils.resumeEventStream(ConsoleApplication.this);

                    // try to perform a GCM registration if it failed
                    // or if the GCM server generated a new push key
                    GcmRegistrationManager gcmRegistrationManager = Matrix.getInstance(this).getSharedGcmRegistrationManager();
                    if (null != gcmRegistrationManager) {
                        gcmRegistrationManager.checkPusherRegistration(this);
                    }
                }
            }

            // get the contact update at application launch
            ContactsManager.refreshLocalContactsSnapshot(this);
        }

        MyPresenceManager.advertiseAllOnline();

        this.mIsCallingInBackground = false;
        this.mIsInBackground = false;
    }

    static private Activity mCurrentActivity = null;
    public static void setCurrentActivity(Activity activity) {

        // wait 2s to check that the application is put in background
        if (null != getInstance()) {
            if (null == activity) {
                getInstance().startActivityTransitionTimer();
            } else {
                getInstance().stopActivityTransitionTimer();
            }
        }

        mCurrentActivity = activity;
    }
    public static Activity getCurrentActivity() { return mCurrentActivity; }

    /**
     * Return true if the application is in background.
     */
    public static boolean isAppInBackground() {
     	return (null == mCurrentActivity) && (null != getInstance()) && getInstance().mIsInBackground;
    }

    private void initGoogleAnalytics() {
        // pull tracker resource ID from res/values/analytics.xml
        int trackerResId = getResources().getIdentifier("ga_trackingId", "string", getPackageName());
        if (trackerResId == 0) {
            Log.e(LOG_TAG, "Unable to find tracker id for Google Analytics");
            return;
        }

        String trackerId = getString(trackerResId);
        Log.d(LOG_TAG, "Tracker ID: "+trackerId);
        // init google analytics with this tracker ID
        if (!TextUtils.isEmpty(trackerId)) {
            Analytics.initialiseGoogleAnalytics(this, trackerId, new ExceptionParser() {
                @Override
                public String getDescription(String threadName, Throwable throwable) {
                    StringBuilder b = new StringBuilder();
                    b.append((BuildConfig.DEBUG ? "DEBUG"  : "RELEASE") + "\n");
                    b.append("Build : " +  VERSION_BUILD + "\n");
                    b.append("Version : " + VERSION_STRING + "\n");
                    b.append("Phone : " + Build.MODEL.trim() + " (" + Build.VERSION.INCREMENTAL + " " + Build.VERSION.RELEASE + " " + Build.VERSION.CODENAME  + ")\n");
                    b.append("Thread: ");
                    b.append(threadName);

                    Activity a = ConsoleApplication.getCurrentActivity();
                    if (a != null) {
                        b.append(", Activity:");
                        b.append(a.getLocalClassName());
                    }

                    b.append(", Exception: ");
                    b.append(Analytics.getStackTrace(throwable));
                    Log.e("FATAL EXCEPTION", b.toString());
                    return b.toString();
                }
            });
        }
    }
}
console/src/main/java/org/matrix/console/util/RageShake.java
public class RageShake implements SensorEventListener {
    private static final String LOG_TAG = "RageShake";

    private static RageShake instance;

    private Context mContext;

    // weak refs so dead dialogs can be GCed
    private List<WeakReference<Dialog>> mDialogs;
    
    protected RageShake() {
        mDialogs = new ArrayList<WeakReference<Dialog>>();

        // Samsung devices for some reason seem to be less sensitive than others so the threshold is being
        // lowered for them. A possible lead for a better formula is the fact that the sensitivity detected 
        // with the calculated force below seems to relate to the sample rate: The higher the sample rate,
        // the higher the sensitivity.
        String model = Build.MODEL.trim();
        // S3, S1(Brazil), Galaxy Pocket
        if ("GT-I9300".equals(model) || "GT-I9000B".equals(model) || "GT-S5300B".equals(model)) {
            threshold = 20.0f;
        }
    }

    public synchronized static RageShake getInstance() {
        if (instance == null) {
            instance = new RageShake();
        }
        return instance;
    }

    public void registerDialog(Dialog d) {
        mDialogs.add(new WeakReference<Dialog>(d));
    }

    public void sendBugReport() {
        Bitmap screenShot = this.takeScreenshot();

        if (null != screenShot) {
            try {
                // store the file in shared place
                String path = MediaStore.Images.Media.insertImage(mContext.getContentResolver(), screenShot, "screenshot-" + new Date(), null);

                Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
                intent.setType("text/html");
                intent.putExtra(Intent.EXTRA_EMAIL, new String[]{"rageshake@matrix.org"});
                intent.putExtra(Intent.EXTRA_SUBJECT, "Matrix bug report");

                String message = "Something went wrong on my Matrix client : \n\n\n";
                message += "-----> my comments <-----\n\n\n";
                message += "------------------------------\n";

                message += "Application info\n";

                Collection<MXSession> sessions = Matrix.getMXSessions(mContext);
                int profileIndex = 1;

                for(MXSession session : sessions) {
                    message += "Profile " + profileIndex + " :\n";
                    profileIndex++;

                    MyUser mMyUser = session.getMyUser();
                    message += "userId : "+ mMyUser.user_id + "\n";
                    message += "displayname : " + mMyUser.displayname + "\n";
                    message += "homeServer :" + session.getCredentials().homeServer + "\n";
                }

                message += "\n";

                message += "matrixConsole version: " + Matrix.getInstance(mContext).getVersion(true) + "\n";
                message += "SDK version:  " + Matrix.getInstance(mContext).getDefaultSession().getVersion(true) + "\n";

                message += "\n\n\n";

                intent.putExtra(Intent.EXTRA_TEXT, message);

                ArrayList<Uri> attachmentUris = new ArrayList<Uri>();

                // attachments
                intent.setType("image/jpg");
                attachmentUris.add(Uri.parse(path));

                String errorLog = LogUtilities.getLogCatError();
                String debugLog = LogUtilities.getLogCatDebug();

                errorLog += "\n\n\n\n\n\n\n\n\n\n------------------ Debug logs ------------------\n\n\n\n\n\n\n\n";
                errorLog += debugLog;

                try {

                    // add the current device logs
                    {
                        ByteArrayOutputStream os = new ByteArrayOutputStream();
                        GZIPOutputStream gzip = new GZIPOutputStream(os);
                        gzip.write(errorLog.getBytes());
                        gzip.finish();

                        File debugLogFile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), "logs-" + new Date() + ".gz");
                        FileOutputStream fos = new FileOutputStream(debugLogFile);
                        os.writeTo(fos);
                        os.flush();
                        os.close();

                        attachmentUris.add(Uri.fromFile(debugLogFile));
                    }

                    // add the stored logs
                    ArrayList<File> logsList = LogUtilities.getLogsFileList();

                    long marker = System.currentTimeMillis();

                    for(File file : logsList) {
                        ByteArrayOutputStream bos = new ByteArrayOutputStream();
                        GZIPOutputStream glogzip = new GZIPOutputStream(bos);

                        FileInputStream inputStream = new FileInputStream(file);

                        byte[] buffer = new byte[1024 * 10];
                        int len;
                        while ((len = inputStream.read(buffer)) != -1) {
                            glogzip.write(buffer, 0, len);
                        }
                        glogzip.finish();

                        File storedLogFile = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), marker + "-" + file.getName() + ".gz");
                        FileOutputStream flogOs = new FileOutputStream(storedLogFile);
                        bos.writeTo(flogOs);
                        flogOs.flush();
                        flogOs.close();

                        attachmentUris.add(Uri.fromFile(storedLogFile));
                    }
                }
                catch (IOException e) {
                    Log.e(LOG_TAG, "" + e);
                }

                intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, attachmentUris);

                ConsoleApplication.getCurrentActivity().startActivity(intent);
            } catch (Exception e) {
            }
        }
    }

    public void promptForReport() {
        // Cannot prompt for bug, no active activity.
        if (ConsoleApplication.getCurrentActivity() == null) {
            return;
        }

        // The user is trying to leave with unsaved changes. Warn about that
        new AlertDialog.Builder(ConsoleApplication.getCurrentActivity())
                .setMessage("You seem to be shaking the phone in frustration. Would you like to submit a bug report?")
                .setPositiveButton("YES", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        dialog.dismiss();
                        sendBugReport();
                    }
                })
                .setNeutralButton("Disable", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(mContext);

                        SharedPreferences.Editor editor = preferences.edit();
                        editor.putBoolean(mContext.getString(R.string.settings_key_use_rage_shake), false);
                        editor.commit();

                        dialog.dismiss();
                    }
                })
                .setNegativeButton("NO", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        dialog.dismiss();
                    }
                })
                .create()
                .show();

    }

    private Bitmap takeScreenshot() {
        if (ConsoleApplication.getCurrentActivity() == null) {
            return null;
        }
        
        // get content view
        View contentView = ConsoleApplication.getCurrentActivity().findViewById(android.R.id.content);
        if (contentView == null) {
            Log.e(LOG_TAG, "Cannot find content view on " + ConsoleApplication.getCurrentActivity() + ". Cannot take screenshot.");
            return null;
        }
        
        // get the root view to snapshot
        View rootView = contentView.getRootView();
        if (rootView == null) {
            Log.e(LOG_TAG, "Cannot find root view on " + ConsoleApplication.getCurrentActivity() + ". Cannot take screenshot.");
            return null;
        }
        // refresh it
        rootView.setDrawingCacheEnabled(false);
        rootView.setDrawingCacheEnabled(true);
        
        try {
            Bitmap baseScreen = rootView.getDrawingCache();
            
            // loop the dialogs and prune old/not visible ones
            List<Dialog> onScreenDialogs = new ArrayList<Dialog>();
            for (int i=0; i<mDialogs.size(); i++) {
                WeakReference<Dialog> wfd = mDialogs.get(i);
                Dialog d = wfd.get();
                if (d == null || (d != null && !d.isShowing())) {
                    Log.d(LOG_TAG,  "Discarding empty/null dialog. "+d);
                    mDialogs.remove(i);
                    i--;
                    continue;
                }
                onScreenDialogs.add(d);
            }
        
            if (onScreenDialogs.size() == 0) {
                Log.d(LOG_TAG, "No on screen dialogs.");
                return baseScreen;
            }
            else {
                // use a canvas to draw on top of the base screen.
                Canvas c = new Canvas(baseScreen);
                for (Dialog d : onScreenDialogs) {
                    if (d.getWindow() != null && d.getWindow().getAttributes() != null) {
                        View dialogView = d.getWindow().peekDecorView();
                        Bitmap dialogBitmap = null;
                        // get the dialog bitmap
                        if (dialogView != null) {
                            dialogView.setDrawingCacheEnabled(false);
                            dialogView.setDrawingCacheEnabled(true);
                            dialogBitmap = dialogView.getDrawingCache();
                        }
                        if (dialogBitmap == null) {
                            Log.w(LOG_TAG, "Cannot get dialog bitmap.");
                            continue;
                        }
                        
                        // draw it to the canvas in the right place
                        WindowManager.LayoutParams params = d.getWindow().getAttributes();
                        int x = params.x;
                        int y = params.y;
                        int w = dialogView.getWidth();
                        int h = dialogView.getHeight();
                        int gravity = params.gravity;
                        Log.d(LOG_TAG, "Dialog x "+x+" y "+y+" w "+w+" h "+h+" gravity "+gravity);
                        if (x == 0 && y == 0 && w < baseScreen.getWidth() && h < baseScreen.getHeight()) {
                            switch (gravity) {
                            case Gravity.CENTER:
                                // mid-point - 1/2
                                x = baseScreen.getWidth()/2 - (w/2);
                                y = baseScreen.getHeight()/2 - (h/2);
                                break;
                            default:
                                Log.w(LOG_TAG, "Unhandled gravity: "+gravity);
                                break;
                            }
                        }
                        c.drawBitmap(dialogBitmap, x, y, null);
                        Log.d(LOG_TAG, "Drew a dialog to the canvas");
                    }
                }
                return baseScreen;
            }
        }
        catch (OutOfMemoryError oom) {
            Log.e(LOG_TAG, "Cannot get drawing cache for "+ ConsoleApplication.getCurrentActivity() +" OOM.");
        }
        catch (Exception e) {
            Log.e(LOG_TAG, "Cannot get snapshot of screen: "+e);
        }
        return null;
    }

    /**
     * start the sensor detector
     */
    public void start(Context context) {
        mContext = context;

        SensorManager sm = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
        Sensor s = sm.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
        if (s == null) {
            Log.e(LOG_TAG, "No accelerometer in this device. Cannot use rage shake.");
            return;
        }
        sm.registerListener(this, s, SensorManager.SENSOR_DELAY_NORMAL);
    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {
        // don't care
    }

    private long now = 0;
    private long timeDiff = 0;
    private long lastUpdate = 0;
    private long lastShake = 0;

    private float x = 0;
    private float y = 0;
    private float z = 0;
    private float lastX = 0;
    private float lastY = 0;
    private float lastZ = 0;
    private float force = 0;
    
    private float threshold = 35.0f;
    
    private long intervalNanos = 1000 * 1000 * 10000; // 10sec
    
    private long timeToNextShakeMs = 10 * 1000;
    private long lastShakeTimestamp = 0L;
    
    @Override
    public void onSensorChanged(SensorEvent event) {
        if (event.sensor.getType() != Sensor.TYPE_ACCELEROMETER) {
            return;
        }
        
        now = event.timestamp;
        
        x = event.values[0];
        y = event.values[1];
        z = event.values[2];

        if (lastUpdate == 0) {
            // set some default vals
            lastUpdate = now;
            lastShake = now;
            lastX = x;
            lastY = y;
            lastZ = z;
        }
        else {
            timeDiff = now - lastUpdate;
            
            if (timeDiff > 0) { 
                force = Math.abs(x + y + z - lastX - lastY - lastZ);
                if (Float.compare(force, threshold) >0 ) {
                    if (now - lastShake >= intervalNanos && (System.currentTimeMillis() - lastShakeTimestamp) > timeToNextShakeMs) { 
                        Log.d(LOG_TAG, "Shaking detected.");
                        lastShakeTimestamp = System.currentTimeMillis();

                        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(mContext);

                        if (preferences.getBoolean(mContext.getString(R.string.settings_key_use_rage_shake), true)) {
                            promptForReport();
                        }
                    }
                    else {
                        Log.d(LOG_TAG, "Suppress shaking - not passed interval. Ms to go: "+(timeToNextShakeMs - 
                                (System.currentTimeMillis() - lastShakeTimestamp))+" ms");
                    }
                    lastShake = now;
                }
                lastX = x;
                lastY = y;
                lastZ = z;
                lastUpdate = now; 
            }
        }
    }

}
console/src/main/java/org/matrix/console/fragments/MembersInvitationDialogFragment.java
public class MembersInvitationDialogFragment extends DialogFragment {
    private static final String LOG_TAG = "MembersInvitationDialogFragment";

    public static final String ARG_ROOM_ID = "org.matrix.console.fragments.MembersInvitationDialogFragment.ARG_ROOM_ID";

    private ListView mListView;
    private MembersInvitationAdapter mAdapter;
    private MXSession mSession;
    private String mRoomId;

    public static MembersInvitationDialogFragment newInstance(MXSession session, String roomId) {
        MembersInvitationDialogFragment f = new MembersInvitationDialogFragment();
        Bundle args = new Bundle();
        args.putString(ARG_ROOM_ID, roomId);
        f.setArguments(args);
        f.setSession(session);
        return f;
    }

    public void setSession(MXSession session) {
        mSession = session;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Context context = getActivity().getApplicationContext();
        mRoomId = getArguments().getString(ARG_ROOM_ID);
    }

    /**
     * Return the used medias cache.
     * This method can be overridden to use another medias cache
     * @return the used medias cache
     */
    public MXMediasCache getMXMediasCache() {
        return Matrix.getInstance(getActivity()).getMediasCache();
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());

        View view = getActivity().getLayoutInflater().inflate(R.layout.fragment_dialog_invitation_member_list, null);
        builder.setView(view);
        initView(view);
        builder.setTitle(getString(R.string.title_activity_invite_user));

        builder.setPositiveButton(R.string.ok,
                new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog,
                                        int whichButton) {

                        ArrayList<RoomMember> members = mAdapter.getSelectedMembers();
                        ArrayList<String> userIDs = new ArrayList<String>();

                        for(RoomMember member : members) {
                            userIDs.add(member.getUserId());
                        }

                        Room room = mSession.getDataHandler().getRoom(mRoomId);
                        room.invite(userIDs, new SimpleApiCallback<Void>(getActivity()) {
                            @Override
                            public void onSuccess(Void info) {
                            }
                        });
                    }
                });


        builder.setNegativeButton(R.string.cancel,
                new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog,
                                        int whichButton) {

                    }
                });

        return builder.create();
    }

    // Comparator to order members alphabetically
    private Comparator<RoomMember> alphaComparator = new Comparator<RoomMember>() {
        @Override
        public int compare(RoomMember member1, RoomMember member2) {
            String lhs = member1.displayname;

            if (null == lhs) {
                lhs = member1.getUserId();
            }

            String rhs = member2.displayname;

            if (null == rhs) {
                rhs = member2.getUserId();
            }

            if (lhs == null) {
                return -1;
            }
            else if (rhs == null) {
                return 1;
            }
            if (lhs.startsWith("@")) {
                lhs = lhs.substring(1);
            }
            if (rhs.startsWith("@")) {
                rhs = rhs.substring(1);
            }
            return String.CASE_INSENSITIVE_ORDER.compare(lhs, rhs);
        }
    };

    /**
     * Init the dialog view.
     * @param v the dialog view.
     */
    void initView(View v) {
        mListView = ((ListView)v.findViewById(R.id.listView_members));

        IMXStore store = mSession.getDataHandler().getStore();

        // remove the current room members
        ArrayList<String> idsToIgnore = new ArrayList<String>();
        Room fromRoom = store.getRoom(mRoomId);
        Collection<RoomMember> currentMembers = fromRoom.getMembers();

        for(RoomMember member : currentMembers) {
            idsToIgnore.add(member.getUserId());
        }

        // get the members list
        ArrayList<RoomMember> members_OneToOne = new ArrayList<RoomMember>();
        ArrayList<String> ids_OneToOne = new ArrayList<String>();

        ArrayList<RoomMember> members_MaxTenMembers = new ArrayList<RoomMember>();
        ArrayList<String> ids_MaxTenMembers = new ArrayList<String>();

        ArrayList<RoomMember> members_BigRooms = new ArrayList<RoomMember>();
        ArrayList<String> ids_BigRooms = new ArrayList<String>();

        Collection<RoomSummary> summaries = store.getSummaries();

        for(RoomSummary summary : summaries) {
            // not the current summary
            if (!summary.getRoomId().equals(mRoomId)) {
                Room curRoom = store.getRoom(summary.getRoomId());
                Collection<RoomMember> otherRoomMembers = curRoom.getMembers();

                for (RoomMember member : otherRoomMembers) {
                    String userID = member.getUserId();

                    // accepted User ID or still active users
                    if ((idsToIgnore.indexOf(userID) < 0) && (RoomMember.MEMBERSHIP_JOIN.equals(member.membership))) {
                        int posOneToOne = ids_OneToOne.indexOf(userID);
                        int posTenMembers = ids_MaxTenMembers.indexOf(userID);
                        int posBigRooms = ids_BigRooms.indexOf(userID);

                        if (otherRoomMembers.size() <= 2) {
                            if (posBigRooms >= 0) {
                                members_BigRooms.remove(posBigRooms);
                                ids_BigRooms.remove(posBigRooms);
                            }

                            if (posTenMembers >= 0) {
                                members_MaxTenMembers.remove(posTenMembers);
                                ids_MaxTenMembers.remove(posTenMembers);
                            }

                            if (posOneToOne < 0) {
                                members_OneToOne.add(member);
                                ids_OneToOne.add(member.getUserId());
                            }
                        } else if (otherRoomMembers.size() <= 10) {
                            if (posBigRooms >= 0) {
                                members_BigRooms.remove(posBigRooms);
                                ids_BigRooms.remove(posBigRooms);
                            }

                            if ((posTenMembers < 0) && (posOneToOne < 0)) {
                                members_MaxTenMembers.add(member);
                                ids_MaxTenMembers.add(member.getUserId());
                            }
                        } else {
                            if ((posBigRooms < 0) && (posTenMembers < 0) && (posOneToOne < 0)) {
                                members_BigRooms.add(member);
                                ids_BigRooms.add(member.getUserId());
                            }
                        }
                    }
                }
            }
        }

        mAdapter = new MembersInvitationAdapter(getActivity(), mSession.getHomeserverConfig(), R.layout.adapter_item_members_invitation, getMXMediasCache());

        Collections.sort(members_OneToOne, alphaComparator);
        Collections.sort(members_MaxTenMembers, alphaComparator);
        Collections.sort(members_BigRooms, alphaComparator);

        mAdapter.addAll(members_OneToOne);
        mAdapter.addAll(members_MaxTenMembers);
        mAdapter.addAll(members_BigRooms);

        ArrayList<Integer> bounds = new ArrayList<Integer>();
        ArrayList<String> sectionTitles = new ArrayList<String>();

        int index = 0;

        if (members_OneToOne.size() > 0) {
            bounds.add(index);
            sectionTitles.add(getActivity().getResources().getString(R.string.members_one_to_one));
            index += members_OneToOne.size();
        }

        if (members_MaxTenMembers.size() > 0) {
            bounds.add(index);
            sectionTitles.add(getActivity().getResources().getString(R.string.members_small_room_members));
            index += members_MaxTenMembers.size();
        }

        if (members_BigRooms.size() > 0) {
            bounds.add(index);
            sectionTitles.add(getActivity().getResources().getString(R.string.members_large_room_members));
            index += members_BigRooms.size();
        }

        mAdapter.setSectionTiles(bounds, sectionTitles);

        mListView.setAdapter(mAdapter);
    }
}


Question: import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.app.NotificationManager;
import android.content.ClipData;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.ExifInterface;
import android.net.Uri;
import android.os.Build;
import android.os.HandlerThread;
import android.os.ParcelFileDescriptor;
import android.provider.MediaStore;
import android.provider.OpenableColumns;
import android.support.v4.app.FragmentManager;
import android.os.Bundle;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.webkit.MimeTypeMap;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.Toast;
import org.matrix.androidsdk.MXSession;
import org.matrix.androidsdk.call.IMXCall;
import org.matrix.androidsdk.data.MyUser;
import org.matrix.androidsdk.data.Room;
import org.matrix.androidsdk.data.RoomState;
import org.matrix.androidsdk.db.MXLatestChatMessageCache;
import org.matrix.androidsdk.db.MXMediasCache;
import org.matrix.androidsdk.fragments.IconAndTextDialogFragment;
import org.matrix.androidsdk.listeners.MXEventListener;
import org.matrix.androidsdk.rest.callback.ApiCallback;
import org.matrix.androidsdk.rest.callback.SimpleApiCallback;
import org.matrix.androidsdk.rest.model.Event;
import org.matrix.androidsdk.rest.model.FileMessage;
import org.matrix.androidsdk.rest.model.MatrixError;
import org.matrix.androidsdk.rest.model.Message;
import org.matrix.androidsdk.rest.model.RoomMember;
import org.matrix.androidsdk.rest.model.bingrules.BingRule;
import org.matrix.androidsdk.util.BingRulesManager;
import org.matrix.androidsdk.util.ImageUtils;
import org.matrix.androidsdk.util.JsonUtils;
import org.matrix.console.ConsoleApplication;
import org.matrix.console.Matrix;
import org.matrix.console.R;
import org.matrix.console.ViewedRoomTracker;
import org.matrix.console.adapters.ImageCompressionDescription;
import org.matrix.console.fragments.ConsoleMessageListFragment;
import org.matrix.console.fragments.ImageSizeSelectionDialogFragment;
import org.matrix.console.fragments.MembersInvitationDialogFragment;
import org.matrix.console.fragments.RoomInfoUpdateDialogFragment;
import org.matrix.console.fragments.RoomMembersDialogFragment;
import org.matrix.console.services.EventStreamService;
import org.matrix.console.util.NotificationUtils;
import org.matrix.console.util.RageShake;
import org.matrix.console.util.ResourceUtils;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Timer;
import java.util.TimerTask;
/*
 * Copyright 2015 OpenMarket Ltd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.matrix.console.activity;




/**
 * Displays a single room with messages.
 */
public class RoomActivity extends MXCActionBarActivity {

    public static final String EXTRA_ROOM_ID = "org.matrix.console.RoomActivity.EXTRA_ROOM_ID";

    // the room is launched but it expects to start the dedicated call activity
    public static final String EXTRA_START_CALL_ID = "org.matrix.console.RoomActivity.EXTRA_START_CALL_ID";

    public static final String EXTRA_START_FROM_PUSH = "org.matrix.console.RoomActivity.EXTRA_START_FROM_PUSH";

    private static final String TAG_FRAGMENT_MATRIX_MESSAGE_LIST = "org.matrix.console.RoomActivity.TAG_FRAGMENT_MATRIX_MESSAGE_LIST";
    private static final String TAG_FRAGMENT_MEMBERS_DIALOG = "org.matrix.console.RoomActivity.TAG_FRAGMENT_MEMBERS_DIALOG";
    private static final String TAG_FRAGMENT_INVITATION_MEMBERS_DIALOG = "org.matrix.console.RoomActivity.TAG_FRAGMENT_INVITATION_MEMBERS_DIALOG";
    private static final String TAG_FRAGMENT_ATTACHMENTS_DIALOG = "org.matrix.console.RoomActivity.TAG_FRAGMENT_ATTACHMENTS_DIALOG";
    private static final String TAG_FRAGMENT_IMAGE_SIZE_DIALOG = "org.matrix.console.RoomActivity.TAG_FRAGMENT_IMAGE_SIZE_DIALOG";
    private static final String TAG_FRAGMENT_ROOM_INFO = "org.matrix.console.RoomActivity.TAG_FRAGMENT_ROOM_INFO";


    private static final String LOG_TAG = "RoomActivity";
    private static final int TYPING_TIMEOUT_MS = 10000;

    private static final String PENDING_THUMBNAIL_URL = "PENDING_THUMBNAIL_URL";
    private static final String PENDING_MEDIA_URL = "PENDING_MEDIA_URL";
    private static final String PENDING_MIMETYPE = "PENDING_MIMETYPE";
    private static final String PENDING_FILENAME = "PENDING_FILENAME";

    private static final String CAMERA_VALUE_TITLE = "attachment"; // Samsung devices need a filepath to write to or else won't return a Uri (!!!)

    // defines the command line operations
    // the user can write theses messages to perform some room events
    private static final String CMD_CHANGE_DISPLAY_NAME = "/nick";
    private static final String CMD_EMOTE = "/me";
    private static final String CMD_JOIN_ROOM = "/join";
    private static final String CMD_KICK_USER = "/kick";
    private static final String CMD_BAN_USER = "/ban";
    private static final String CMD_UNBAN_USER = "/unban";
    private static final String CMD_SET_USER_POWER_LEVEL = "/op";
    private static final String CMD_RESET_USER_POWER_LEVEL = "/deop";

    private static final int REQUEST_FILES = 0;
    private static final int TAKE_IMAGE = 1;
    private static final int CREATE_DOCUMENT = 2;
    private static final int TAKE_VIDEO = 3;

    // max image sizes
    private static final int LARGE_IMAGE_SIZE  = 2000;
    private static final int MEDIUM_IMAGE_SIZE = 1000;
    private static final int SMALL_IMAGE_SIZE  = 500;

    private ConsoleMessageListFragment mConsoleMessageListFragment;
    private MXSession mSession;
    private Room mRoom;
    private String mMyUserId;

    private MXLatestChatMessageCache mLatestChatMessageCache;
    private MXMediasCache mMediasCache;

    private ImageButton mSendButton;
    private ImageButton mAttachmentButton;
    private EditText mEditText;

    private View mImagePreviewLayout;
    private ImageView mImagePreviewView;
    private ImageButton mImagePreviewButton;

    private String mPendingThumbnailUrl;
    private String mPendingMediaUrl;
    private String mPendingMimeType;
    private String mPendingFilename;

    private MenuItem mVoiceMenuItem = null;
    private MenuItem mVideoMenuItem = null;

    private Boolean mRuleInProgress = false;
    private BingRule mBingRule = null;
    private MenuItem mEnableNotifItem = null;
    private MenuItem mDisableNotifItem = null;

    private String mCallId = null;

    private static String mLatestTakePictureCameraUri = null; // has to be String not Uri because of Serializable

    // typing event management
    private Timer mTypingTimer = null;
    private TimerTask mTypingTimerTask;
    private long  mLastTypingDate = 0;

    private Boolean mIgnoreTextUpdate = false;

    private MXEventListener mEventListener = new MXEventListener() {
        @Override
        public void onLiveEvent(final Event event, RoomState roomState) {
            RoomActivity.this.runOnUiThread(new Runnable() {

                @Override
                public void run() {
                    // The various events that could possibly change the room title
                    if (Event.EVENT_TYPE_STATE_ROOM_NAME.equals(event.type)
                            || Event.EVENT_TYPE_STATE_ROOM_ALIASES.equals(event.type)
                            || Event.EVENT_TYPE_STATE_ROOM_MEMBER.equals(event.type)) {
                        setTitle(mRoom.getName(mMyUserId));
                        updateMenuEntries();
                    }
                    else if (Event.EVENT_TYPE_STATE_ROOM_TOPIC.equals(event.type)) {
                        Log.e(LOG_TAG, "Updating room topic.");
                        RoomState roomState = JsonUtils.toRoomState(event.content);
                        setTopic(roomState.topic);
                    }

Answer:                     if (!ConsoleApplication.isAppInBackground()) {