console/src/main/java/org/matrix/console/MyPresenceManager.java
public class MyPresenceManager {
    private static final String LOG_TAG = "MyPresenceManager";

    // The delay we wait for before actually advertising in case it changes in the meantime.
    // This is useful when saying we're unavailable on an activity's onPause to differentiate between
    // backgrounding the app and just switching activities.
    private static final int DELAY_TS = 3000;

    // Array of presence states ordered by priority. If the current device thinks our user is online,
    // it will disregard a presence event saying the user is unavailable and advertise that they are in
    // fact online as a correction.
    private static String[] orderedPresenceArray = new String[] {
            User.PRESENCE_ONLINE,
            User.PRESENCE_UNAVAILABLE,
            User.PRESENCE_OFFLINE
    };
    // We need the reverse structure to associate an order to a given presence state
    private static Map<String, Integer> presenceOrderMap = new HashMap<String, Integer>();
    static {
        for (int i = 0; i < orderedPresenceArray.length; i++) {
            presenceOrderMap.put(orderedPresenceArray[i], i);
        }
    }

    private static HashMap<MXSession, MyPresenceManager> instances = new HashMap<MXSession, MyPresenceManager>();

    private MyUser myUser;
    private Handler mHandler;
    private String latestAdvertisedPresence = ""; // Presence we're advertising

    private MyPresenceManager(Context context, MXSession session) {
        myUser = session.getMyUser();
        mHandler = new Handler(Looper.getMainLooper());

        myUser.addEventListener(new MXEventListener() {
            @Override
            public void onPresenceUpdate(Event event, User user) {
                myUser.presence = user.presence;

                // If the received presence is the same as the last one we've advertised, this must be
                // the event stream sending back our own event => nothing more to do
                if (!user.presence.equals(latestAdvertisedPresence)) {
                    // If we're here, the presence event comes from another of this user's devices. If it's saying for example that it's
                    // offline but we're currently online, our presence takes precedence; in which case, we broadcast the correction
                    Integer newPresenceOrder = presenceOrderMap.get(user.presence);
                    if (newPresenceOrder != null) {
                        int ourPresenceOrder = presenceOrderMap.get(latestAdvertisedPresence);
                        // If the new presence is further down the order list, we correct it
                        if (newPresenceOrder > ourPresenceOrder) {
                            advertisePresence(latestAdvertisedPresence);
                        }
                    }
                }
            }
        });
    }

    /**
     * Create an instance without any check.
     * @param context
     * @param session
     * @return
     */
    private static MyPresenceManager createInstance(Context context, MXSession session) {
        MyPresenceManager instance = new MyPresenceManager(context, session);
        instances.put(session, instance);
        return instance;
    }

    /**
     * Search a presence manager from a dedicated session
     * @param context the context
     * @param session the session
     * @return the linked presence manager
     */
    public static synchronized MyPresenceManager getInstance(Context context, MXSession session) {
        MyPresenceManager instance = instances.get(session);
        if (instance == null) {
            instance = createInstance(context, session);
        }
        return instance;
    }

    /**
     * Create an MyPresenceManager instance for each session if it was not yet done.
     * @param context the context
     * @param sessions the sessions
     */
    public static synchronized void createPresenceManager(Context context, Collection<MXSession> sessions) {
        for(MXSession session : sessions) {
            if (!instances.containsKey(session)) {
                createInstance(context, session);
            }
        }
    }

    /**
     * Remove a presence manager for a session.
     * @param session the session
     */
    public static synchronized void remove(MXSession session) {
        instances.remove(session);
    }

    /**
     * Send the advertise presence message.
     * @param presence the presence message.
     */
    public void advertisePresence(String presence) {
        if (!latestAdvertisedPresence.equals(presence)) {
            latestAdvertisedPresence = presence;

            Log.d(LOG_TAG, "Advertising presence " + presence);
            myUser.updatePresence(presence, null, null);
        }
    }

    private static void advertiseAll(String presence) {
        Collection<MyPresenceManager> values = instances.values();

        for(MyPresenceManager myPresenceManager : values) {
            myPresenceManager.advertisePresence(presence);
        }
    }

    public static void advertiseAllOnline() {
        advertiseAll(User.PRESENCE_ONLINE);
    }

    public static void advertiseAllOffline() {
        advertiseAll(User.PRESENCE_OFFLINE);
    }

    public static void advertiseAllUnavailable() {
        advertiseAll(User.PRESENCE_UNAVAILABLE);
    }

    public void advertiseOffline() {
        advertisePresence(User.PRESENCE_OFFLINE);
    }
}
console/src/main/java/org/matrix/console/LoginHandler.java
public class LoginHandler {
    private static final String LOG_TAG = "LoginHandler";

    /**
     * Try to login.
     * The MXSession is created if the operation succeeds.
     * @param ctx the context.
     * @param hsConfig The homeserver config.
     * @param username The username.
     * @param password The password;
     * @param callback The callback.
     */
    public void login(Context ctx, final HomeserverConnectionConfig hsConfig, final String username, final String password,
                              final SimpleApiCallback<HomeserverConnectionConfig> callback) {
        final Context appCtx = ctx.getApplicationContext();
        LoginRestClient client = new LoginRestClient(hsConfig);

        client.loginWithPassword(username, password, new SimpleApiCallback<Credentials>() {
            @Override
            public void onSuccess(Credentials credentials) {
                Collection<MXSession> sessions = Matrix.getMXSessions(appCtx);
                Boolean isDuplicated = false;

                for (MXSession existingSession : sessions) {
                    Credentials cred = existingSession.getCredentials();
                    isDuplicated |= TextUtils.equals(credentials.userId, cred.userId) && TextUtils.equals(credentials.homeServer, cred.homeServer);
                }

                if (!isDuplicated) {
                    hsConfig.setCredentials(credentials);
                    MXSession session = Matrix.getInstance(appCtx).createSession(hsConfig);
                    Matrix.getInstance(appCtx).addSession(session);
                }

                Log.d(LOG_TAG, "client loginWithPassword succeeded.");
                callback.onSuccess(hsConfig);
            }

            @Override
            public void onNetworkError(final Exception e) {
                UnrecognizedCertificateException unrecCertEx = CertUtil.getCertificateException(e);
                if (unrecCertEx != null) {
                    final Fingerprint fingerprint = unrecCertEx.getFingerprint();
                    Log.d(LOG_TAG, "Found fingerprint: SHA-256: " + fingerprint.getBytesAsHexString());
                    // TODO: Handle this. For example by displaying a "Do you trust this cert?" dialog

                    UnrecognizedCertHandler.show(hsConfig, fingerprint, false, new UnrecognizedCertHandler.Callback() {
                        @Override
                        public void onAccept() {
                            login(appCtx, hsConfig, username, password, callback);
                        }

                        @Override
                        public void onIgnore() {
                            callback.onNetworkError(e);
                        }

                        @Override
                        public void onReject() {
                            callback.onNetworkError(e);
                        }
                    });
                } else {
                    callback.onNetworkError(e);
                }
            }

            @Override
            public void onUnexpectedError(Exception e) {
                callback.onUnexpectedError(e);
            }

            @Override
            public void onMatrixError(MatrixError e) {
                callback.onMatrixError(e);
            }
        });
    }

    /**
     * Retrieve the supported flows of a home server.
     * @param ctx the application conttext.
     * @param hsConfig the home server config.
     * @param callback the supported flows list callback.
     */
    public void getSupportedFlows(Context ctx, final HomeserverConnectionConfig hsConfig, final SimpleApiCallback<List<LoginFlow>> callback) {
        final Context appCtx = ctx.getApplicationContext();
        LoginRestClient client = new LoginRestClient(hsConfig);

        client.getSupportedLoginFlows(new SimpleApiCallback<List<LoginFlow>>() {
            @Override
            public void onSuccess(List<LoginFlow> flows) {
                Log.d(LOG_TAG, "getSupportedFlows " + flows);
                callback.onSuccess(flows);
            }

            @Override
            public void onNetworkError(final Exception e) {
                UnrecognizedCertificateException unrecCertEx = CertUtil.getCertificateException(e);
                if (unrecCertEx != null) {
                    final Fingerprint fingerprint = unrecCertEx.getFingerprint();
                    Log.d(LOG_TAG, "Found fingerprint: SHA-256: " + fingerprint.getBytesAsHexString());

                    UnrecognizedCertHandler.show(hsConfig, fingerprint, false, new UnrecognizedCertHandler.Callback() {
                        @Override
                        public void onAccept() {
                            getSupportedFlows(appCtx, hsConfig, callback);
                        }

                        @Override
                        public void onIgnore() {
                            callback.onNetworkError(e);
                        }

                        @Override
                        public void onReject() {
                            callback.onNetworkError(e);
                        }
                    });
                } else {
                    callback.onNetworkError(e);
                }
            }

            @Override
            public void onUnexpectedError(Exception e) {
                callback.onUnexpectedError(e);
            }

            @Override
            public void onMatrixError(MatrixError e) {
                callback.onMatrixError(e);
            }
        });
    }

}
console/src/main/java/org/matrix/console/db/ConsoleContentProvider.java
public class ConsoleContentProvider extends ContentProvider {

    public static String AUTHORITIES = "org.matrix.console.ConsoleApplication.provider";


    /**
     * Convert an absolute file path to a Content path
     * @param context the application context
     * @param path the absolute path to convert.
     * @return the content URI.
     */
    public static Uri absolutePathToUri(Context context, String path) {
        String basePath = context.getFilesDir().getAbsolutePath();

        if ((null != path) && path.startsWith(basePath)) {
            return Uri.parse("content://" + ConsoleContentProvider.AUTHORITIES + path.substring(basePath.length()));
        }

        return null;
    }

    @Override
    public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
        File privateFile = new File(getContext().getFilesDir(), uri.getPath());
        return ParcelFileDescriptor.open(privateFile, ParcelFileDescriptor.MODE_READ_ONLY);
    }

    @Override
    public int delete(Uri arg0, String arg1, String[] arg2) {
        return 0;
    }

    @Override
    public String getType(Uri arg0) {
        String type = null;
        String extension = MimeTypeMap.getFileExtensionFromUrl(arg0.toString());
        if (extension != null) {
            MimeTypeMap mime = MimeTypeMap.getSingleton();
            type = mime.getMimeTypeFromExtension(extension);
        }

        return type;
    }

    @Override
    public Uri insert(Uri arg0, ContentValues arg1) {
        return null;
    }

    @Override
    public boolean onCreate() {
        return false;
    }

    @Override
    public Cursor query(Uri arg0, String[] arg1, String arg2, String[] arg3,
                        String arg4) {
        return null;
    }

    @Override
    public int update(Uri arg0, ContentValues arg1, String arg2, String[] arg3) {
        return 0;
    }
}
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/view/AddAccountAlertDialog.java
public class AddAccountAlertDialog extends AlertDialog.Builder  {
    // the onActivityResult identitier
    public static final int FALLBACK_LOGIN_ACTIVITY_REQUEST_CODE = 2718;

    // UI items
    Activity mActivity;
    EditText mUsernameEditText;
    EditText mPasswordEditText;
    EditText mHomeServerEditText;
    View mSearchMaskView;
    AlertDialog mDialog;

    // the latest home server URL.
    String mHomeServerUrl;

    /**
     * Creator.
     * @param activity the activity creator
     */
    public AddAccountAlertDialog(Activity activity) {
        super(activity);
        mActivity = activity;

        LayoutInflater layoutInflater = LayoutInflater.from(activity);

        // extract the layout
        View layout = layoutInflater.inflate(R.layout.fragment_dialog_add_account, null);

        // and the useful UI items
        mUsernameEditText = (EditText) layout.findViewById(R.id.editText_username);
        mPasswordEditText = (EditText) layout.findViewById(R.id.editText_password);
        mHomeServerEditText = (EditText) layout.findViewById(R.id.editText_hs);
        mSearchMaskView = layout.findViewById(R.id.search_mask_view);
        mSearchMaskView.setVisibility(View.GONE);
        mHomeServerUrl = mHomeServerEditText.getText().toString();

        // detect if the user taps on the next / done button
        mHomeServerEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
            @Override
            public boolean onEditorAction(TextView view, int actionId, KeyEvent event) {
                if (actionId == EditorInfo.IME_ACTION_DONE) {
                    return onHomeServerUrlUpdate();
                }
                return false;
            }
        });

        // disable the OK if the home server url is not the same
        mHomeServerEditText.addTextChangedListener(new TextWatcher() {
            public void afterTextChanged(android.text.Editable s) {
                mDialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(TextUtils.equals(mHomeServerUrl, mHomeServerEditText.getText().toString()));
            }

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

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


        // detect the focus changes
        mHomeServerEditText.setOnFocusChangeListener(new View.OnFocusChangeListener() {
            public void onFocusChange(View v, boolean hasFocus) {
                if (!hasFocus) {
                    onHomeServerUrlUpdate();
                }
            }
        });

        // dialog title and layout
        setTitle(R.string.action_add_account);
        setView(layout);

        // buttons management.
        setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int whichButton) {
                onLogin();
            }
        });

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

            }
        });
    }

    /**
     * Create and show the dialog.
     * @return the shown dialog.
     */
    public AlertDialog show() {
        mDialog = super.show();
        return mDialog;
    }

    /**
     * Check if the home server URL after its edition.
     * The flows request is triggered to check if the dialog supports the login.
     * The fallback activity is launched if it is not supported.
     * @return true if a flows check request is triggered.
     */
    private Boolean onHomeServerUrlUpdate() {
        // the user validates the homeserver url
        if (!TextUtils.equals(mHomeServerUrl, mHomeServerEditText.getText().toString())) {
            mHomeServerUrl = mHomeServerEditText.getText().toString();
            checkFlows();
            return true;
        } else {
            mDialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(true);
            return false;
        }
    }

    /**
     * Trigger a flows check request.
     */
    private void checkFlows() {
        // warn the user there is something in progress
        mSearchMaskView.setVisibility(View.VISIBLE);
        mDialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);

        try {
            // build the home server config
            HomeserverConnectionConfig hsConfig = null;
            try {
                hsConfig = new HomeserverConnectionConfig(Uri.parse(mHomeServerUrl));
            } catch (Exception e) {
            }

            LoginHandler loginHandler = new LoginHandler();

            // sanity check
            if (null != hsConfig) {{
                loginHandler.getSupportedFlows(mActivity, hsConfig, new SimpleApiCallback<List<LoginFlow>>() {
                    @Override
                    public void onSuccess(List<LoginFlow> flows) {
                        mSearchMaskView.setVisibility(View.GONE);
                        mDialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(true);
                        Boolean isSupported = true;

                        // supported only m.login.password by now
                        for(LoginFlow flow : flows) {
                            isSupported &= TextUtils.equals("m.login.password", flow.type);
                        }

                        // if not supported, switch to the fallback login
                        if (!isSupported) {
                            mDialog.dismiss();

                            Intent intent = new Intent(mActivity, FallbackLoginActivity.class);
                            intent.putExtra(FallbackLoginActivity.EXTRA_HOME_SERVER_ID, mHomeServerUrl);
                            mActivity.startActivityForResult(intent, FALLBACK_LOGIN_ACTIVITY_REQUEST_CODE);
                        }
                    }

                    private void onError(String errorMessage) {
                        mSearchMaskView.setVisibility(View.GONE);
                        Toast.makeText(mActivity, errorMessage, Toast.LENGTH_LONG).show();
                    }

                    @Override
                    public void onNetworkError(Exception e) {
                        onError(mActivity.getString(R.string.login_error_unable_login) + " : " + e.getLocalizedMessage());
                    }

                    @Override
                    public void onUnexpectedError(Exception e) {
                        onError(mActivity.getString(R.string.login_error_unable_login) + " : " + e.getLocalizedMessage());
                    }

                    @Override
                    public void onMatrixError(MatrixError e) {
                        onError(mActivity.getString(R.string.login_error_unable_login) + " : " + e.error + "(" + e.errcode + ")");
                    }
                });
            }
            }
        } catch (Exception globalExcep) {
            mSearchMaskView.setVisibility(View.GONE);
            Toast.makeText(mActivity, mActivity.getString(R.string.login_error_invalid_home_server), Toast.LENGTH_SHORT).show();
        }
    }

    /**
     * The user triggers a login.
     */
    private void onLogin() {
        String hsUrlString = mHomeServerEditText.getText().toString();
        String username = mUsernameEditText.getText().toString();
        String password = mPasswordEditText.getText().toString();

        if (!hsUrlString.startsWith("http")) {
            Toast.makeText(mActivity, mActivity.getString(R.string.login_error_must_start_http), Toast.LENGTH_SHORT).show();
            return;
        }

        if (TextUtils.isEmpty(username) || TextUtils.isEmpty(password)) {
            Toast.makeText(mActivity, mActivity.getString(R.string.login_error_invalid_credentials), Toast.LENGTH_SHORT).show();
            return;
        }

        Uri hsUrl = Uri.parse(hsUrlString);

        LoginRestClient client = null;
        final HomeserverConnectionConfig hsConfig = new HomeserverConnectionConfig(hsUrl);

        try {
            client = new LoginRestClient(hsConfig);
        } catch (Exception e) {
        }

        if (null == client) {
            Toast.makeText(mActivity, mActivity.getString(R.string.login_error_invalid_home_server), Toast.LENGTH_SHORT).show();
            return;
        }

        try {
            LoginHandler loginHandler = new LoginHandler();
            loginHandler.login(mActivity, hsConfig, username, password, new SimpleApiCallback<HomeserverConnectionConfig>(mActivity) {
                @Override
                public void onSuccess(HomeserverConnectionConfig c) {
                    // loginHandler creates the session so just need to switch to the splash activity
                    mActivity.startActivity(new Intent(mActivity, SplashActivity.class));
                    mActivity.finish();
                }

                @Override
                public void onNetworkError(Exception e) {
                    Toast.makeText(mActivity, mActivity.getString(R.string.login_error_network_error), Toast.LENGTH_LONG).show();
                }

                @Override
                public void onUnexpectedError(Exception e) {
                    String msg = mActivity.getString(R.string.login_error_unable_login) + " : " + e.getMessage();
                    Toast.makeText(mActivity, msg, Toast.LENGTH_LONG).show();
                }

                @Override
                public void onMatrixError(MatrixError e) {
                    String msg = mActivity.getString(R.string.login_error_unable_login) + " : " + e.error + "(" + e.errcode + ")";
                    Toast.makeText(mActivity, msg, Toast.LENGTH_LONG).show();
                }
            });
        } catch (Exception e) {
            Toast.makeText(mActivity, mActivity.getString(R.string.login_error_invalid_home_server), Toast.LENGTH_SHORT).show();
        }
    }

    /**
     * onActivityResult parameters management
     * @param activity the caller activity.
     * @param requestCode the request code (should be FALLBACK_LOGIN_ACTIVITY_REQUEST_CODE)
     * @param resultCode the result code
     * @param data the data
     */
    public static void onFlowActivityResult(Activity activity, int requestCode, int resultCode, Intent data) {
        if (AddAccountAlertDialog.FALLBACK_LOGIN_ACTIVITY_REQUEST_CODE == requestCode) {
            if (resultCode == Activity.RESULT_OK) {
                String homeServer = data.getStringExtra("homeServer");
                String homeServerUrl = data.getStringExtra("homeServerUrl");
                String userId = data.getStringExtra("userId");
                String accessToken = data.getStringExtra("accessToken");

                // build a credential with the provided items
                Credentials credentials = new Credentials();
                credentials.userId = userId;
                credentials.homeServer = homeServer;
                credentials.accessToken = accessToken;

                final HomeserverConnectionConfig hsConfig = new HomeserverConnectionConfig(
                        Uri.parse(homeServerUrl), credentials
                );

                // let's go...
                MXSession session = Matrix.getInstance(activity).createSession(hsConfig);
                Matrix.getInstance(activity).addSession(session);

                // loginHandler creates the session so just need to switch to the splash activity
                activity.startActivity(new Intent(activity, SplashActivity.class));
                activity.finish();
            }
        }
    }
}
console/src/main/java/org/matrix/console/adapters/ConsoleRoomSummaryAdapter.java
public class ConsoleRoomSummaryAdapter extends RoomSummaryAdapter {

    private ArrayList<MXSession> mSessions = null;

    public ConsoleRoomSummaryAdapter(Context context, Collection<MXSession> sessions, int layoutResourceId, int headerLayoutResourceId)  {
        super(context, sessions.size(), layoutResourceId, headerLayoutResourceId);
        mSessions = new ArrayList<MXSession>(sessions);
    }

    public int getUnreadMessageBackgroundColor() {
        return mContext.getResources().getColor(R.color.room_summary_unread_background);
    }

    public int getHighlightMessageBackgroundColor() {
        return mContext.getResources().getColor(R.color.room_summary_highlight_background);
    }

    public int getPublicHighlightMessageBackgroundColor() {
        return mContext.getResources().getColor(R.color.room_summary_public_highlight_background);
    }

    /**
     * Retrieve a Room from a room summary
     * @param roomSummary the room roomId to retrieve.
     * @return the Room.
     */
    public Room roomFromRoomSummary(RoomSummary roomSummary) {
        // sanity check
        if ((null == roomSummary) || (null == roomSummary.getMatrixId())) {
            return null;
        }

        MXSession session = Matrix.getMXSession(mContext, roomSummary.getMatrixId());

        // check if the session is active
        if ((null == session) || (!session.isAlive())) {
            return null;
        }

        return Matrix.getMXSession(mContext, roomSummary.getMatrixId()).getDataHandler().getStore().getRoom(roomSummary.getRoomId());
    }

    public String memberDisplayName(String matrixId, String userId) {
        MXSession session = Matrix.getMXSession(mContext, matrixId);

        // check if the session is active
        if ((null == session) || (!session.isAlive())) {
            return null;
        }

        User user = session.getDataHandler().getStore().getUser(userId);

        if ((null != user) && !TextUtils.isEmpty(user.displayname)) {
            return user.displayname;
        }

        return userId;
    }

    public boolean displayPublicRooms() {
        // the user can force to clear the public rooms with the recents ones
        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(mContext);
        return preferences.getBoolean(mContext.getString(R.string.settings_key_display_public_rooms_recents), true);
    }

    public String myRoomsTitle(int section) {
        if (mSessions.size() == 1) {
            return mContext.getResources().getString(R.string.my_rooms);
        } else {
            if (mSessions.get(section).isAlive()) {
                return mSessions.get(section).getMyUserId();
            } else {
                return mContext.getResources().getString(R.string.my_rooms);
            }
        }
    }

    public String publicRoomsTitle(int section) {
        String title = mContext.getResources().getString(R.string.action_public_rooms);

        if ((null != mPublicRoomsHomeServerLists) && (mPublicRoomsHomeServerLists.size() > 1)) {
            title += "\n" + mPublicRoomsHomeServerLists.get(section - mSessions.size());
        }

        return title;
    }

    @Override
    public void removeSection(int section) {
        super.removeSection(section);
        mSessions.remove(section);
    }
}
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/RoomCreationDialogFragment.java
public class RoomCreationDialogFragment extends DialogFragment {
    private static final String LOG_TAG = "RoomCreationDialogFragment";

    private MXSession mSession;

    public static RoomCreationDialogFragment newInstance(MXSession session) {
        RoomCreationDialogFragment f = new RoomCreationDialogFragment();
        Bundle args = new Bundle();
        f.setArguments(args);
        f.setSession(session);
        return f;
    }

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

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (mSession == null) {
            throw new RuntimeException("No MXSession.");
        }
    }

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

        final Activity activity = getActivity();

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

        // get the user suffix
        final String userID = mSession.getCredentials().userId;
        final String homeServerSuffix = userID.substring(userID.indexOf(":"), userID.length());

        TextView homeServerText = (TextView) view.findViewById(R.id.textView_homeServer);
        homeServerText.setText(homeServerSuffix);

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


                        CheckBox publicCheckbox = (CheckBox) view.findViewById(R.id.checkbox_room_creation);
                        final String roomVisibility = publicCheckbox.isChecked() ? RoomState.VISIBILITY_PUBLIC : RoomState.VISIBILITY_PRIVATE;

                        EditText roomAliasEdittext = (EditText) view.findViewById(R.id.editText_roomAlias);
                        String roomAlias = roomAliasEdittext.getText().toString();

                        if (null != roomAlias) {
                            roomAlias = roomAlias.trim();
                        }

                        EditText roomNameEdittext = (EditText) view.findViewById(R.id.editText_roomName);
                        String roomName = roomNameEdittext.getText().toString();

                        if (null != roomName) {
                            roomName = roomName.trim();
                        }

                        EditText participantsEdittext = (EditText) view.findViewById(R.id.editText_participants);

                        // get the members list
                        final ArrayList<String> userIDsList = CommonActivityUtils.parseUserIDsList(participantsEdittext.getText().toString(), homeServerSuffix);

                        mSession.createRoom(roomName, null, roomVisibility, roomAlias, new SimpleApiCallback<String>(getActivity()) {
                            @Override
                            public void onSuccess(String roomId) {
                                CommonActivityUtils.goToRoomPage(mSession, roomId, activity, null);

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

                                if (null != room) {
                                    room.invite(userIDsList, new SimpleApiCallback<Void>(getActivity()) {
                                        @Override
                                        public void onSuccess(Void info) {
                                        }
                                    });
                                }
                            }
                        });
                    }
                });

        return builder.create();
    }
}
console/src/main/java/org/matrix/console/adapters/DrawerAdapter.java
public class DrawerAdapter extends ArrayAdapter<DrawerAdapter.Entry> {

    private static final int NUM_ROW_TYPES = 2;

    private static final int ROW_TYPE_HEADER = 0;
    private static final int ROW_TYPE_ENTRY = 1;

    public class Entry {
        public int mIconResourceId;
        public String mText;

        public Entry(int iconResourceId, String text) {
            mIconResourceId = iconResourceId;
            mText = text;
        }
    };

    private LayoutInflater mLayoutInflater;
    private int mHeaderLayoutResourceId;
    private int mEntryLayoutResourceId;

    private Context mContext;

    /**
     * Construct an adapter to display a drawer adapter
     * @param context the context
     * @param headerLayoutResourceId the header resource id
     * @param entryLayoutResourceId the entry layout id
     */
    public DrawerAdapter(Context context, int headerLayoutResourceId,  int entryLayoutResourceId) {
        super(context, headerLayoutResourceId, entryLayoutResourceId);
        mContext = context;
        mHeaderLayoutResourceId = headerLayoutResourceId;
        mEntryLayoutResourceId = entryLayoutResourceId;
        mLayoutInflater = LayoutInflater.from(mContext);

        // create a dummy section to insert the header
        this.add(new Entry(0, null));
        setNotifyOnChange(true);
    }

    /**
     * Add a new entry in the adapter
     * @param iconResourceId the entry icon
     * @param text the entry text
     */
    public void add(int iconResourceId, String text) {
        this.add(new Entry(iconResourceId, text));
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {

        if (0 == position) {
            if (convertView == null) {
                convertView = mLayoutInflater.inflate(mHeaderLayoutResourceId, parent, false);
            }
            return convertView;
        } else {
            if (convertView == null) {
                convertView = mLayoutInflater.inflate(mEntryLayoutResourceId, parent, false);
            }

            Entry entry = getItem(position);

            TextView textView = (TextView) convertView.findViewById(R.id.adapter_drawer_text);
            textView.setText(entry.mText);

            ImageView imageView = (ImageView) convertView.findViewById(R.id.adapter_drawer_thumbnail);
            imageView.setImageResource(entry.mIconResourceId);

            return convertView;
        }
    }

    @Override
    public int getViewTypeCount() {
        return NUM_ROW_TYPES;
    }

    @Override
    public int getItemViewType(int position) {
        return (position == 0) ? ROW_TYPE_HEADER : ROW_TYPE_ENTRY;
    }
}
console/src/main/java/org/matrix/console/fragments/ContactsListDialogFragment.java
public class ContactsListDialogFragment extends DialogFragment implements PIDsRetriever.PIDsRetrieverListener, ContactsManager.ContactsManagerListener  {
    private static final String LOG_TAG = "ContactsListDialogFragment";

    private ListView mListView;
    private ContactsListAdapter mAdapter;

    private ArrayList<Contact> mLocalContacts;
    private ArrayList<Contact> mFilteredContacts;
    private boolean mDisplayOnlyMatrixUsers = false;
    private String mSearchPattern = "";

    public static ContactsListDialogFragment newInstance() {
        ContactsListDialogFragment f = new ContactsListDialogFragment();
        Bundle args = new Bundle();
        f.setArguments(args);
        return f;
    }

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

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

        Collection<Contact> contacts = ContactsManager.getLocalContactsSnapshot(getActivity());

        if (contacts.size() != 0) {
            builder.setTitle(getString(R.string.contacts) + " (" + contacts.size() + ")");
        } else {
            builder.setTitle(getString(R.string.contacts));
        }

        View view = getActivity().getLayoutInflater().inflate(R.layout.fragment_contacts_list, null);
        builder.setView(view);
        initView(view);

        return builder.create();
    }

    // Comparator to order members alphabetically
    private Comparator<Contact> alphaComparator = new Comparator<Contact>() {
        @Override
        public int compare(Contact contact1, Contact contact2) {
            String displayname1 = (contact1.getDisplayName() == null)? contact1.mContactId : contact1.getDisplayName();
            String displayname2 = (contact2.getDisplayName() == null)? contact2.mContactId : contact2.getDisplayName();

            return String.CASE_INSENSITIVE_ORDER.compare(displayname1, displayname2);
        }
    };

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

        // get the local contacts
        mLocalContacts = new ArrayList<Contact>(ContactsManager.getLocalContactsSnapshot(getActivity()));

        mAdapter = new ContactsListAdapter(getActivity(), R.layout.adapter_item_contact);

        // sort them
        Collections.sort(mLocalContacts, alphaComparator);

        mListView.setFastScrollAlwaysVisible(true);
        mListView.setFastScrollEnabled(true);
        mListView.setAdapter(mAdapter);

        refreshAdapter();

        // a button could be added to filter the contacts to display only the matrix users
        // but the lookup method is too slow (1 address / request).
        // it could be enabled when a batch request will be implemented
        /*
        final Button button = (Button)v.findViewById(R.id.button_matrix_users);

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View arg0) {
                mDisplayOnlyMatrixUsers = !mDisplayOnlyMatrixUsers;

                if (mDisplayOnlyMatrixUsers) {
                    button.setBackgroundResource(R.drawable.matrix_user);
                } else {
                    button.setBackgroundResource(R.drawable.ic_menu_allfriends);
                }

                refreshAdapter();
            }
        });*/

        final EditText editText = (EditText)v.findViewById(R.id.editText_contactBox);

        editText.addTextChangedListener(new TextWatcher() {
            public void afterTextChanged(android.text.Editable s) {
                ContactsListDialogFragment.this.mSearchPattern = s.toString();
                refreshAdapter();
            }

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

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

        // tap on one of them
        // if he is a matrix, offer to start a chat
        // it he is not a matrix user, offer to invite him by email or SMS.
        mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                final Contact contact = mAdapter.getItem(position);
                final Activity activity = ContactsListDialogFragment.this.getActivity();

                if (contact.hasMatridIds(ContactsListDialogFragment.this.getActivity())) {
                    final  Contact.MXID mxid = contact.getFirstMatrixId();
                    // The user is trying to leave with unsaved changes. Warn about that
                    new AlertDialog.Builder(activity)
                            .setMessage(activity.getText(R.string.chat_with) + " " + mxid.mMatrixId + " ?")
                            .setPositiveButton("YES", new DialogInterface.OnClickListener() {
                                @Override
                                public void onClick(DialogInterface dialog, int which) {
                                    activity.runOnUiThread(new Runnable() {
                                        @Override
                                        public void run() {
                                            CommonActivityUtils.goToOneToOneRoom(mxid.mAccountId,  mxid.mMatrixId, activity, new SimpleApiCallback<Void>(getActivity()) {
                                            });
                                        }
                                    });
                                    dialog.dismiss();
                                    // dismiss the member list
                                    ContactsListDialogFragment.this.dismiss();

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

                } else {
                    // invite the user
                    final ArrayList<String> choicesList = new ArrayList<String>();

                    if (AdapterUtils.canSendSms(activity)) {
                        choicesList.addAll(contact.mPhoneNumbers);
                    }

                    choicesList.addAll(contact.mEmails);

                    // something to offer
                    if (choicesList.size() > 0) {
                        final String[] labels = new String[choicesList.size()];

                        for(int index = 0; index < choicesList.size(); index++) {
                            labels[index] = choicesList.get(index);
                        }

                        new AlertDialog.Builder(activity)
                                .setItems(labels, new DialogInterface.OnClickListener() {
                                    @Override
                                    public void onClick(DialogInterface dialog, int which) {
                                        String value = labels[which];

                                        // SMS ?
                                        if (contact.mPhoneNumbers.indexOf(value) >= 0) {
                                            AdapterUtils.launchSmsIntent(activity, value, activity.getString(R.string.invitation_message));
                                        } else {
                                            // emails
                                            AdapterUtils.launchEmailIntent(activity, value, activity.getString(R.string.invitation_message));
                                        }

                                        // dismiss the member list
                                        ContactsListDialogFragment.this.dismiss();
                                    }
                                }).setTitle(activity.getString(R.string.invite_this_user_to_use_matrix)).show();
                    }
                }
            }
        });
    }

    private void refreshAdapter() {

        // matrix users selection
        ArrayList<Contact> filteredContacts = mLocalContacts;

        if (mDisplayOnlyMatrixUsers || (mSearchPattern.length() != 0)) {
            filteredContacts = new ArrayList<Contact>();

            if (mDisplayOnlyMatrixUsers && !TextUtils.isEmpty(mSearchPattern)) {
                for (Contact contact : mLocalContacts) {
                    if (contact.hasMatridIds(getActivity()) && contact.matchWithPattern(mSearchPattern)) {
                        filteredContacts.add(contact);
                    }
                }
            } else if (!TextUtils.isEmpty(mSearchPattern)) {
                for (Contact contact : mLocalContacts) {

                    if (contact.matchWithPattern(mSearchPattern)) {
                        // trigger the matrixID retrieval.
                        contact.hasMatridIds(getActivity());
                        filteredContacts.add(contact);
                    }
                }
            } else if (mDisplayOnlyMatrixUsers) {
                for (Contact contact : mLocalContacts) {
                    if (contact.hasMatridIds(getActivity())) {
                        filteredContacts.add(contact);
                    }
                }
            }
        }

        // clear the list
        mAdapter.clear();

        // to replace with the new filtered list
        mFilteredContacts = filteredContacts;

        // display them
        mAdapter.addAll(mFilteredContacts);

        // refresh the title
        if (null != getDialog()) {
            if (mFilteredContacts.size() != 0) {
                getDialog().setTitle(getString(R.string.contacts) + " (" + mFilteredContacts.size() + ")");
            } else {
                getDialog().setTitle(getString(R.string.contacts));
            }
        }

        mAdapter.notifyDataSetChanged();
    }

    @Override
    public void onStart() {
        super.onStart();
        PIDsRetriever.getIntance().setPIDsRetrieverListener(this);
        ContactsManager.addListener(this);
    }

    @Override
    public void onStop() {
        super.onStop();
        PIDsRetriever.getIntance().setPIDsRetrieverListener(null);
        ContactsManager.removeListener(this);
    }

    /**
     * Called when the contact PIDs are retrieved
     */
    @Override public void onPIDsRetrieved(String accountId, Contact contact, final boolean has3PIDs) {
        getActivity().runOnUiThread(new Runnable() {
            @Override
            public void run() {
                // refresh only if there are some updates
                if (has3PIDs) {
                    refreshAdapter();
                }
            }
        });
    }

    /**
     * Called when the contacts list have been
     */
    public void onRefresh() {
        getActivity().runOnUiThread(new Runnable() {
            @Override
            public void run() {
                mLocalContacts = new ArrayList<Contact>(ContactsManager.getLocalContactsSnapshot(getActivity()));
                refreshAdapter();
            }
        });
    }
}
console/src/main/java/org/matrix/console/gcm/GcmRegistrationManager.java
public final class GcmRegistrationManager {
    private static String LOG_TAG = "GcmRegistrationManager";

    public static final String PREFS_GCM = "org.matrix.console.gcm.GcmRegistrationManager";
    public static final String PREFS_KEY_REG_ID_PREFIX = "REG_ID-";

    public static final String PREFS_PUSHER_APP_ID_KEY = "org.matrix.console.gcm.GcmRegistrationManager.pusherAppId";
    public static final String PREFS_SENDER_ID_KEY = "org.matrix.console.gcm.GcmRegistrationManager.senderId";
    public static final String PREFS_PUSHER_URL_KEY = "org.matrix.console.gcm.GcmRegistrationManager.pusherUrl";
    public static final String PREFS_PUSHER_FILE_TAG_KEY = "org.matrix.console.gcm.GcmRegistrationManager.pusherFileTag";

    private static String DEFAULT_PUSHER_APP_ID = "org.matrix.console.android";
    private static String DEFAULT_PUSHER_URL = "https://matrix.org/_matrix/push/v1/notify";
    private static String DEFAULT_PUSHER_FILE_TAG = "mobile";

    /**
     * GCM registration interface
     */
    public interface GcmRegistrationIdListener {
        void onPusherRegistered();
        void onPusherRegistrationFailed();
    }

    /**
     * 3rd party server registation interface
     */
    public interface GcmSessionRegistration {
        void onSessionRegistred();
        void onSessionRegistrationFailed();

        void onSessionUnregistred();
        void onSessionUnregistrationFailed();
    }

    // theses both entries can be updated from the settings page in debug mode
    private String mPusherAppId = null;
    private String mPusherUrl = null;
    private String mPusherBaseFileTag = null;

    private String mPusherAppName = null;
    private String mPusherLang = null;
    private ArrayList<GcmSessionRegistration> mSessionsregistrationListener = new ArrayList<GcmSessionRegistration>();

    private enum RegistrationState {
        UNREGISTRATED,
        GCM_REGISTRATING,
        GCM_REGISTRED,
        SERVER_REGISTRATING,
        SERVER_REGISTERED,
        SERVER_UNREGISTRATING,
    };

    private static String mBasePusherDeviceName = Build.MODEL.trim();

    private Context mContext;
    private RegistrationState mRegistrationState = RegistrationState.UNREGISTRATED;

    private android.os.Handler mUIHandler;
    private String mPushKey = null;


    public GcmRegistrationManager(Context appContext) {
        mContext = appContext.getApplicationContext();
        mUIHandler = new android.os.Handler(Looper.getMainLooper());

        try {
            PackageInfo pInfo = mContext.getPackageManager().getPackageInfo(mContext.getPackageName(), 0);
            mPusherAppName = pInfo.packageName;
            mPusherLang = mContext.getResources().getConfiguration().locale.getLanguage();
        } catch (Exception e) {
            mPusherAppName = "Matrix Console";
            mPusherLang = "en";
        }

        loadGcmData();
    }

    /**
     * reset the Registration
     */
    public void reset() {

        unregisterSessions(null);

        // remove the customized keys
        getSharedPreferences().
                edit().
                remove(PREFS_PUSHER_APP_ID_KEY).
                remove(PREFS_SENDER_ID_KEY).
                remove(PREFS_PUSHER_URL_KEY).
                remove(PREFS_PUSHER_FILE_TAG_KEY).
                commit();

        loadGcmData();
    }

    public String pusherUrl() {
        return mPusherUrl;
    }

    public void setPusherUrl(String pusherUrl) {
        if (!TextUtils.isEmpty(pusherUrl) && !pusherUrl.equals(mPusherUrl)) {
            mPusherUrl = pusherUrl;
            SaveGCMData();
        }
    }

    public String pusherFileTag() {
        return mPusherBaseFileTag;
    }

    public void setPusherFileTag(String pusherFileTag) {
        if (!TextUtils.isEmpty(pusherFileTag) && !pusherFileTag.equals(mPusherBaseFileTag)) {
            mPusherBaseFileTag = pusherFileTag;
            SaveGCMData();
        }
    }

    /**
     * Force to retrieve the
     * @param appContext
     * @param registrationListener
     */
    public void refreshPushToken(final Context appContext, final GcmRegistrationIdListener registrationListener) {
        setStoredPushKey(null);
        mRegistrationState = RegistrationState.UNREGISTRATED;
        registerPusher(appContext, registrationListener);
    }


    /**
     * Check if the GCM registration has been broken with a new token ID.
     * The GCM could have resetted it (onTokenRefresh).
     * @param appContext the application context
     */
    public void checkPusherRegistration(final Context appContext) {
        if (mRegistrationState == RegistrationState.UNREGISTRATED) {
            Log.d(LOG_TAG, "checkPusherRegistration : try to register to GCM server");

            registerPusher(appContext, new GcmRegistrationIdListener() {
                @Override
                public void onPusherRegistered() {
                    Log.d(LOG_TAG, "checkPusherRegistration : reregistered");
                }

                @Override
                public void onPusherRegistrationFailed() {
                    Log.d(LOG_TAG, "checkPusherRegistration : onPusherRegistrationFailed");
                }
            });
        }
    }

    /**
     * Register to the GCM.
     * @param registrationListener the events listener.
     */
    public void registerPusher(final Context appContext, final GcmRegistrationIdListener registrationListener) {
        // already registred
        if (mRegistrationState == RegistrationState.GCM_REGISTRED) {
            if (null != registrationListener) {
                registrationListener.onPusherRegistered();
            }
        } else if (mRegistrationState != RegistrationState.UNREGISTRATED) {
            if (null != registrationListener) {
                registrationListener.onPusherRegistrationFailed();
            }
        } else {

            mRegistrationState = RegistrationState.GCM_REGISTRATING;

            new AsyncTask<Void, Void, String>() {
                @Override
                protected String doInBackground(Void... voids) {
                    String pushKey = getPushKey(appContext);

                    if (pushKey != null) {
                        mPushKey = pushKey;
                    }

                    return mPushKey;
                }

                @Override
                protected void onPostExecute(String pushKey) {
                    // succeed to retrieve the push key
                    if (pushKey != null) {
                        mRegistrationState = RegistrationState.GCM_REGISTRED;

                        setStoredPushKey(pushKey);

                        // register the sessions to the 3rd party server
                        if (useGCM()) {
                            registerSessions(appContext, null);
                        }
                    } else {
                        // fail to retrieve the push key
                        // assume that a full registration is required.
                        setStoredPushKey(null);
                        mRegistrationState = RegistrationState.UNREGISTRATED;
                    }

                    // warn the listener
                    if (null != registrationListener) {
                        try {
                            if (pushKey != null) {
                                registrationListener.onPusherRegistered();
                            } else {
                                registrationListener.onPusherRegistrationFailed();
                            }
                        } catch (Exception e) {
                            Log.e(LOG_TAG, "registerPusher : onPusherRegistered/onPusherRegistrationFailed failed " + e.getLocalizedMessage());
                        }
                    }
                }
            }.execute();
        }
    }

    /**
     * @return true if use GCM
     */
    public Boolean useGCM() {
        final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(mContext);
        return preferences.getBoolean(mContext.getString(R.string.settings_key_use_google_cloud_messaging), true);
    }

    /**
     * Update the GCM status
     * @param use
     */
    public void setUseGCM(Boolean use) {
        SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(mContext).edit();
        editor.putBoolean(mContext.getString(R.string.settings_key_use_google_cloud_messaging), use);
        editor.commit();
    }

    public Boolean isGCMRegistred() {
        return (mRegistrationState == RegistrationState.GCM_REGISTRED) || (mRegistrationState == RegistrationState.SERVER_REGISTRATING) || (mRegistrationState == RegistrationState.SERVER_REGISTERED);
    }

    public Boolean is3rdPartyServerRegistred() {
        return mRegistrationState == RegistrationState.SERVER_REGISTERED;
    }

    public Boolean isRegistrating() {
        return (mRegistrationState == RegistrationState.SERVER_REGISTRATING) || (mRegistrationState == RegistrationState.SERVER_UNREGISTRATING);
    }

    private String getPushKey(Context appContext) {
        String pushKey = getStoredPushKey();

        if (pushKey == null) {
            try {
                Log.d(LOG_TAG, "Getting the GCM Registration Token");

                InstanceID instanceID = InstanceID.getInstance(appContext);

                pushKey = instanceID.getToken(appContext.getString(R.string.gcm_defaultSenderId),
                        GoogleCloudMessaging.INSTANCE_ID_SCOPE, null);


                Log.d(LOG_TAG, "GCM Registration Token: " + pushKey);

                //setStoredRegistrationId(registrationId);
            } catch (IOException e) {
                Log.e(LOG_TAG, "getPushKey failed with exception : " + e.getLocalizedMessage());
                pushKey = null;
            } catch (Exception e) {
                Log.e(LOG_TAG, "getPushKey failed with exception : " + e.getLocalizedMessage());
                pushKey = null;
            }
        }
        return pushKey;
    }

    /**
     * Compute the profileTag for a session
     * @param session the session
     * @return the profile tag
     */
    private String computePushTag(final MXSession session) {
        String tag =  mPusherBaseFileTag + "_" + Math.abs(session.getMyUserId().hashCode());

        // tag max length : 32 bytes
        if (tag.length() > 32) {
            tag = Math.abs(tag.hashCode()) + "";
        }

        return tag;
    }

    /**
     * Register the session to the 3rd-party app server
     * @param session the session to register.
     * @param listener the registration listener
     */
    public void registerSession(final MXSession session, boolean append, final GcmSessionRegistration listener) {
        session.getPushersRestClient()
                .addHttpPusher(mPushKey, mPusherAppId, computePushTag(session),
                        mPusherLang, mPusherAppName, mBasePusherDeviceName,
                        mPusherUrl, append, new ApiCallback<Void>() {
                            @Override
                            public void onSuccess(Void info) {
                                Log.d(LOG_TAG, "registerPusher succeeded");

                                if (null != listener) {
                                    try {
                                        listener.onSessionRegistred();
                                    } catch (Exception e) {
                                    }
                                }
                            }

                            private void onError(final String message) {
                                mUIHandler.post(new Runnable() {
                                    @Override
                                    public void run() {
                                        Toast.makeText(mContext, "fail to register " + session.getMyUserId() + " (" + message + ")", Toast.LENGTH_LONG).show();
                                    }
                                });

                                if (null != listener) {
                                    try {
                                        listener.onSessionRegistrationFailed();
                                    } catch (Exception e) {
                                    }
                                }
                            }

                            @Override
                            public void onNetworkError(Exception e) {
                                Log.e(LOG_TAG, "registerPusher onNetworkError " + e.getMessage());
                                onError(e.getLocalizedMessage());
                            }

                            @Override
                            public void onMatrixError(MatrixError e) {
                                Log.e(LOG_TAG, "registerPusher onMatrixError " + e.errcode);
                                onError(e.getLocalizedMessage());
                            }

                            @Override
                            public void onUnexpectedError(Exception e) {
                                Log.e(LOG_TAG, "registerPusher onUnexpectedError " + e.getMessage());
                                onError(e.getLocalizedMessage());
                            }
                        });
    }


    public void addSessionsRegistrationListener(final GcmSessionRegistration listener) {
        synchronized (this) {
            if ((null != listener) && (mSessionsregistrationListener.indexOf(listener) == -1)) {
                mSessionsregistrationListener.add(listener);
            }
        }
    }

    private void onSessionsRegistred() {
        synchronized (this) {
            for(GcmSessionRegistration listener : mSessionsregistrationListener) {
                try {
                    listener.onSessionRegistred();
                } catch (Exception e) {

                }
            }

            mSessionsregistrationListener.clear();
        }
    }

    private void onSessionsRegistrationFailed() {
        synchronized (this) {
            for(GcmSessionRegistration listener : mSessionsregistrationListener) {
                try {
                    listener.onSessionRegistrationFailed();
                } catch (Exception e) {

                }
            }

            mSessionsregistrationListener.clear();
        }
    }

    private void onSessionsUnregistred() {
        synchronized (this) {
            for(GcmSessionRegistration listener : mSessionsregistrationListener) {
                try {
                    listener.onSessionUnregistred();
                } catch (Exception e) {

                }
            }

            mSessionsregistrationListener.clear();
        }
    }

    private void onSessionsUnregistrationFailed() {
        synchronized (this) {
            for(GcmSessionRegistration listener : mSessionsregistrationListener) {
                try {
                    listener.onSessionUnregistrationFailed();
                } catch (Exception e) {

                }
            }

            mSessionsregistrationListener.clear();
        }
    }

    public void reregisterSessions(final Context appContext, final GcmSessionRegistration listener) {
        if ((mRegistrationState == RegistrationState.SERVER_REGISTERED) || (mRegistrationState == RegistrationState.GCM_REGISTRED)){
            mRegistrationState = RegistrationState.GCM_REGISTRED;

            registerSessions(appContext, listener);
        } else {
            if (null != listener) {
                try {
                    listener.onSessionRegistrationFailed();
                } catch (Exception e) {
                }
            }
        }
    }
    
    /**
     * Register the current sessions to the 3rd party GCM server
     * @param listener the registration listener.
     */
    public void registerSessions(final Context appContext, final GcmSessionRegistration listener) {
        if (mRegistrationState == RegistrationState.SERVER_REGISTRATING) {
            addSessionsRegistrationListener(listener);
        } else if (mRegistrationState == RegistrationState.UNREGISTRATED) {
            Log.d(LOG_TAG, "registerSessions unregistrated : try to register again");

            // if the registration failed
            // try to register again
            registerPusher(appContext, new GcmRegistrationIdListener() {
                @Override
                public void onPusherRegistered() {
                    Log.d(LOG_TAG, "GCM registration failed again : register on server side");
                    registerSessions(appContext, listener);
                }

                @Override
                public void onPusherRegistrationFailed() {
                    Log.d(LOG_TAG, "registerSessions unregistrated : GCM registration failed again");

                    if (null != listener) {
                        if (null != listener) {
                            try {
                                listener.onSessionRegistrationFailed();
                            } catch (Exception e) {
                            }
                        }
                    }
                }
            });
        } else if (mRegistrationState == RegistrationState.SERVER_REGISTERED) {
            Log.e(LOG_TAG, "registerSessions : already registred");

            if (null != listener) {
                try {
                    listener.onSessionRegistred();
                } catch (Exception e) {
                }
            }
        } else if (mRegistrationState != RegistrationState.GCM_REGISTRED) {
            Log.e(LOG_TAG, "registerSessions : invalid state " + mRegistrationState);

            if (null != listener) {
                try {
                    listener.onSessionRegistrationFailed();
                } catch (Exception e) {
                }
            }
        } else {
            mRegistrationState = RegistrationState.SERVER_REGISTRATING;
            addSessionsRegistrationListener(listener);
            registerSessions(new ArrayList<MXSession>(Matrix.getInstance(mContext).getSessions()), 0);
        }
    }

    /**
     * Recursive method to register a MXSessions list.
     * @param sessions the sessions list.
     * @param index the index of the MX sessions to register.
     */
    private void registerSessions(final ArrayList<MXSession> sessions, final int index) {
        // reach this end of the list ?
        if (index >= sessions.size()) {
            Log.d(LOG_TAG, "registerSessions : all the sessions are registred");
            mRegistrationState = RegistrationState.SERVER_REGISTERED;
            onSessionsRegistred();
            return;
        }

        final MXSession session = sessions.get(index);

        registerSession(session, (index > 0), new GcmSessionRegistration() {
            @Override
            public void onSessionRegistred() {
                Log.d(LOG_TAG, "registerSessions : session " + session.getMyUserId() + " is registred");
                registerSessions(sessions, index + 1);
            }

            @Override
            public void onSessionRegistrationFailed() {
                Log.d(LOG_TAG, "registerSessions : onSessionRegistrationFailed " + session.getMyUserId());

                mRegistrationState = RegistrationState.GCM_REGISTRED;
                onSessionsRegistrationFailed();
            }

            @Override
            public void onSessionUnregistred() {
            }

            @Override
            public void onSessionUnregistrationFailed() {
            }
        });
    }

    /**
     * Unregister the user identified from his matrix Id from the 3rd-party app server
     * @param session
     */
    public void unregisterSession(final MXSession session, final GcmSessionRegistration listener) {
        session.getPushersRestClient()
                .removeHttpPusher(mPushKey, mPusherAppId, computePushTag(session),
                        mPusherLang, mPusherAppName, mBasePusherDeviceName,
                        mPusherUrl, new ApiCallback<Void>() {
                            @Override
                            public void onSuccess(Void info) {
                                Log.d(LOG_TAG, "unregisterSession succeeded");

                                if (null != listener) {
                                    try {
                                        listener.onSessionUnregistred();
                                    } catch (Exception e) {
                                    }
                                }
                            }

                            private void onError(final String message) {
                                if (session.isAlive()) {
                                    mUIHandler.post(new Runnable() {
                                        @Override
                                        public void run() {
                                            Toast.makeText(mContext, "fail to unregister " + session.getMyUserId() + " (" + message + ")", Toast.LENGTH_LONG).show();
                                        }
                                    });

                                    if (null != listener) {
                                        try {
                                            listener.onSessionUnregistrationFailed();
                                        } catch (Exception e) {
                                        }
                                    }
                                }
                            }

                            @Override
                            public void onNetworkError(Exception e) {
                                Log.e(LOG_TAG, "unregisterSession onNetworkError " + e.getMessage());
                                onError(e.getLocalizedMessage());
                            }

                            @Override
                            public void onMatrixError(MatrixError e) {
                                Log.e(LOG_TAG, "unregisterSession onMatrixError " + e.errcode);
                                onError(e.getLocalizedMessage());
                            }

                            @Override
                            public void onUnexpectedError(Exception e) {
                                Log.e(LOG_TAG, "unregisterSession onUnexpectedError " + e.getMessage());
                                onError(e.getLocalizedMessage());
                            }
                        });
    }

    /**
     * Unregister the current sessions from the 3rd party GCM server
     * @param listener the registration listener.
     */
    public void unregisterSessions(final GcmSessionRegistration listener) {
        if (mRegistrationState == RegistrationState.SERVER_UNREGISTRATING) {
            addSessionsRegistrationListener(listener);
        } else if (mRegistrationState != RegistrationState.SERVER_REGISTERED) {
            Log.e(LOG_TAG, "unregisterSessions : invalid state " + mRegistrationState);

            if (null != listener) {
                try {
                    listener.onSessionUnregistrationFailed();
                } catch (Exception e) {
                }
            }
        } else {
            mRegistrationState = RegistrationState.SERVER_UNREGISTRATING;
            addSessionsRegistrationListener(listener);
            unregisterSessions(new ArrayList<MXSession>(Matrix.getInstance(mContext).getSessions()), 0);
        }
    }

    /**
     * Recursive method to unregister a MXSessions list.
     * @param sessions the sessions list.
     * @param index the index of the MX sessions to register.
     */
    private void unregisterSessions(final ArrayList<MXSession> sessions, final int index) {
        // reach this end of the list ?
        if (index >= sessions.size()) {
            mRegistrationState = RegistrationState.GCM_REGISTRED;
            onSessionsUnregistred();
            return;
        }

        MXSession session = sessions.get(index);

        unregisterSession(session , new GcmSessionRegistration() {
            @Override
            public void onSessionRegistred() {
            }

            @Override
            public void onSessionRegistrationFailed() {
            }

            @Override
            public void onSessionUnregistred() {
                unregisterSessions(sessions, index+1);
            }

            @Override
            public void onSessionUnregistrationFailed() {
                mRegistrationState = RegistrationState.SERVER_REGISTERED;
                onSessionsUnregistrationFailed();
            }
        });
    }

    /**
     * @return the GCM registration stored for this version of the app or null if none is stored.
     */
    private String getStoredPushKey() {
        return getSharedPreferences().getString(getPushKeyKey(), null);
    }

    /**
     * Set the GCM registration for the currently-running version of this app.
     * @param pushKey
     */
    private void setStoredPushKey(String pushKey) {
        String key = getPushKeyKey();
        if (key == null) {
            Log.e(LOG_TAG, "Failed to store registration ID");
            return;
        }

        Log.d(LOG_TAG, "Saving push key " + pushKey + " under key " + key);
        getSharedPreferences().edit()
                .putString(key, pushKey)
                .commit();
    }

    private SharedPreferences getSharedPreferences() {
        return mContext.getSharedPreferences(PREFS_GCM, Context.MODE_PRIVATE);
    }

    private String getPushKeyKey() {
        try {
            PackageInfo packageInfo = mContext.getPackageManager()
                    .getPackageInfo(mContext.getPackageName(), 0);
            return PREFS_KEY_REG_ID_PREFIX + Integer.toString(packageInfo.versionCode);
        } catch (PackageManager.NameNotFoundException e) {
            Log.e(LOG_TAG, "getPushKeyKey failed " + e.getLocalizedMessage());
            throw new RuntimeException(e);
        }
    }

    /**
     * Save the GCM info to the preferences
     */
    private void SaveGCMData() {
        try {
            SharedPreferences preferences = getSharedPreferences();
            SharedPreferences.Editor editor = preferences.edit();

            editor.putString(PREFS_PUSHER_APP_ID_KEY, mPusherAppId);
            editor.putString(PREFS_PUSHER_URL_KEY, mPusherUrl);
            editor.putString(PREFS_PUSHER_FILE_TAG_KEY, mPusherBaseFileTag);

            editor.commit();
        } catch (Exception e) {
            Log.e(LOG_TAG, "SaveGCMData failed " + e.getLocalizedMessage());
        }
    }

    /**
     * Load the GCM info from the preferences
     */
    private void loadGcmData() {
        try {
            SharedPreferences preferences = getSharedPreferences();

            String pusherAppId = preferences.getString(PREFS_PUSHER_APP_ID_KEY, null);
            mPusherAppId = TextUtils.isEmpty(pusherAppId) ? DEFAULT_PUSHER_APP_ID : pusherAppId;

            String pusherUrl = preferences.getString(PREFS_PUSHER_URL_KEY, null);
            mPusherUrl = TextUtils.isEmpty(pusherUrl) ? DEFAULT_PUSHER_URL : pusherUrl;

            String pusherFileTag = preferences.getString(PREFS_PUSHER_FILE_TAG_KEY, null);
            mPusherBaseFileTag = TextUtils.isEmpty(pusherFileTag) ? DEFAULT_PUSHER_FILE_TAG : pusherFileTag;

        } catch (Exception e) {
            Log.e(LOG_TAG, "loadGcmData failed " + e.getLocalizedMessage());
        }
    }
}
console/src/main/java/org/matrix/console/fragments/AccountsSelectionDialogFragment.java
public class AccountsSelectionDialogFragment extends DialogFragment {
    private static final String LOG_TAG = "AccountsSelectionDialogFragment";

    public static interface AccountsListener {
        public void onSelected(MXSession session);
    }

    public static AccountsSelectionDialogFragment newInstance(Collection<MXSession> sessions) {
        AccountsSelectionDialogFragment f= new AccountsSelectionDialogFragment();
        Bundle args = new Bundle();
        f.setArguments(args);
        f.setSessions(sessions);
        return f;
    }

    private ListView mListView;
    private AccountsAdapter mAdapter;
    private Collection<MXSession>  mSessions = null;

    private AccountsListener mListener = null;

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

    public void setSessions(Collection<MXSession> sessions) {
        mSessions = sessions;
    }

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

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

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        Dialog d = super.onCreateDialog(savedInstanceState);
        d.setTitle(getString(R.string.choose_account));
        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_accounts_list, container, false);
        mListView = ((ListView)v.findViewById(R.id.listView_accounts));

        mAdapter = new AccountsAdapter(getActivity(), R.layout.adapter_item_account, getMXMediasCache());

        mAdapter.addAll(mSessions);
        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(mAdapter.getItem(position));
                }

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

        return v;
    }
}
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/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; 
            }
        }
    }

}


Question: import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.PowerManager;
import android.support.v4.app.FragmentManager;
import android.text.Html;
import android.text.Layout;
import android.text.Spannable;
import android.text.Spanned;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.text.method.LinkMovementMethod;
import android.text.style.URLSpan;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView;
import android.widget.EditText;
import android.widget.ExpandableListView;
import android.widget.TextView;
import android.widget.Toast;
import org.matrix.androidsdk.HomeserverConnectionConfig;
import org.matrix.androidsdk.MXSession;
import org.matrix.androidsdk.call.IMXCall;
import org.matrix.androidsdk.call.MXCallsManager;
import org.matrix.androidsdk.data.Room;
import org.matrix.androidsdk.data.RoomState;
import org.matrix.androidsdk.data.RoomSummary;
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.client.LoginRestClient;
import org.matrix.androidsdk.rest.model.Event;
import org.matrix.androidsdk.rest.model.MatrixError;
import org.matrix.androidsdk.rest.model.PublicRoom;
import org.matrix.androidsdk.rest.model.RoomMember;
import org.matrix.androidsdk.rest.model.login.Credentials;
import org.matrix.androidsdk.util.EventUtils;
import org.matrix.androidsdk.util.JsonUtils;
import org.matrix.console.ConsoleApplication;
import org.matrix.console.LoginHandler;
import org.matrix.console.Matrix;
import org.matrix.console.MyPresenceManager;
import org.matrix.console.R;
import org.matrix.console.ViewedRoomTracker;
import org.matrix.console.adapters.ConsoleRoomSummaryAdapter;
import org.matrix.console.adapters.DrawerAdapter;
import org.matrix.console.db.ConsoleContentProvider;
import org.matrix.console.fragments.AccountsSelectionDialogFragment;
import org.matrix.console.fragments.ContactsListDialogFragment;
import org.matrix.console.fragments.RoomCreationDialogFragment;
import org.matrix.console.gcm.GcmRegistrationManager;
import org.matrix.console.services.EventStreamService;
import org.matrix.console.util.RageShake;
import org.matrix.console.view.AddAccountAlertDialog;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
/*
 * Copyright 2014 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 the main screen of the app, with rooms the user has joined and the ability to create
 * new rooms.
 */
public class HomeActivity extends MXCActionBarActivity {

    private static final String LOG_TAG = "HomeActivity";

    private ExpandableListView mMyRoomList = null;

    private static final String PUBLIC_ROOMS_LIST_LIST = "PUBLIC_ROOMS_LIST_LIST";

    private static final String TAG_FRAGMENT_CONTACTS_LIST = "org.matrix.console.HomeActivity.TAG_FRAGMENT_CONTACTS_LIST";
    private static final String TAG_FRAGMENT_CREATE_ROOM_DIALOG = "org.matrix.console.HomeActivity.TAG_FRAGMENT_CREATE_ROOM_DIALOG";

    private static final String TAG_FRAGMENT_ROOM_OPTIONS = "org.matrix.console.HomeActivity.TAG_FRAGMENT_ROOM_OPTIONS";

    public static final String EXTRA_JUMP_TO_ROOM_ID = "org.matrix.console.HomeActivity.EXTRA_JUMP_TO_ROOM_ID";
    public static final String EXTRA_JUMP_MATRIX_ID = "org.matrix.console.HomeActivity.EXTRA_JUMP_MATRIX_ID";
    public static final String EXTRA_ROOM_INTENT = "org.matrix.console.HomeActivity.EXTRA_ROOM_INTENT";

    private ArrayList<String> mHomeServerNames = null;
    private ArrayList<List<PublicRoom>> mPublicRoomsListList = null;

    private boolean mIsPaused = false;

    private ArrayList<Integer> mExpandedGroups = null;

    private String mAutomaticallyOpenedRoomId = null;
    private String mAutomaticallyOpenedMatrixId = null;
    private Intent mOpenedRoomIntent = null;

    private boolean refreshOnChunkEnd = false;

    private MenuItem mCallMenuItem = null;

    // about
    private AlertDialog mMainAboutDialog = null;
    private String mLicenseString = null;

    // sliding menu
    private final Integer[] mSlideMenuTitleIds = new Integer[]{
            //R.string.action_search_contact,
            //R.string.action_search_room,
            R.string.create_room,
            R.string.join_room,
           // R.string.action_mark_all_as_read,
            R.string.action_add_account,
            R.string.action_remove_account,
            R.string.action_settings,
            R.string.action_disconnect,
            R.string.action_logout,
            R.string.send_bug_report,
            R.string.about,
    };

    // sliding menu
    private final Integer[] mSlideMenuResourceIds = new Integer[]{
            //R.drawable.ic_material_search, // R.string.action_search_contact,
            //R.drawable.ic_material_find_in_page, // R.string.action_search_room,
            R.drawable.ic_material_group_add, //R.string.create_room,
            R.drawable.ic_material_group, // R.string.join_room,
            //R.drawable.ic_material_done_all, // R.string.action_mark_all_as_read,
            R.drawable.ic_material_person_add, // R.string.action_add_account,
            R.drawable.ic_material_remove_circle_outline, // R.string.action_remove_account,
            R.drawable.ic_material_settings, //  R.string.action_settings,
            R.drawable.ic_material_clear, // R.string.action_disconnect,
            R.drawable.ic_material_exit_to_app, // R.string.action_logout,
            R.drawable.ic_material_bug_report, // R.string.send_bug_report,
            R.drawable.ic_menu_matrix_transparent, // R.string.about,
    };

    private HashMap<MXSession, MXEventListener> mListenersBySession = new HashMap<MXSession, MXEventListener>();
    private HashMap<MXSession, MXCallsManager.MXCallsManagerListener> mCallListenersBySession = new HashMap<MXSession, MXCallsManager.MXCallsManagerListener>();

    private ConsoleRoomSummaryAdapter mAdapter;
    private EditText mSearchRoomEditText;

    private void refreshPublicRoomsList() {
Answer:         refreshPublicRoomsList(new ArrayList<MXSession>(Matrix.getInstance(getApplicationContext()).getSessions()), new ArrayList<String>(), 0, new ArrayList<List<PublicRoom>>());