/* * Copyright 2014 OpenMarket Ltd * Copyright 2017 Vector Creations 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.androidsdk; import android.content.Context; import android.content.IntentFilter; import android.net.ConnectivityManager; import android.os.Handler; import android.os.Looper; import android.text.TextUtils; import com.google.gson.JsonObject; import org.matrix.androidsdk.call.MXCallsManager; import org.matrix.androidsdk.crypto.MXCrypto; import org.matrix.androidsdk.data.DataRetriever; import org.matrix.androidsdk.data.MyUser; import org.matrix.androidsdk.data.Room; import org.matrix.androidsdk.data.RoomState; import org.matrix.androidsdk.data.RoomSummary; import org.matrix.androidsdk.data.RoomTag; import org.matrix.androidsdk.data.cryptostore.IMXCryptoStore; import org.matrix.androidsdk.data.cryptostore.MXFileCryptoStore; import org.matrix.androidsdk.data.store.IMXStore; import org.matrix.androidsdk.data.store.MXStoreListener; import org.matrix.androidsdk.db.MXLatestChatMessageCache; import org.matrix.androidsdk.db.MXMediasCache; import org.matrix.androidsdk.network.NetworkConnectivityReceiver; import org.matrix.androidsdk.rest.callback.ApiCallback; import org.matrix.androidsdk.rest.callback.ApiFailureCallback; import org.matrix.androidsdk.rest.callback.SimpleApiCallback; import org.matrix.androidsdk.rest.client.AccountDataRestClient; import org.matrix.androidsdk.rest.client.BingRulesRestClient; import org.matrix.androidsdk.rest.client.CallRestClient; import org.matrix.androidsdk.rest.client.CryptoRestClient; import org.matrix.androidsdk.rest.client.EventsRestClient; import org.matrix.androidsdk.rest.client.LoginRestClient; import org.matrix.androidsdk.rest.client.PresenceRestClient; import org.matrix.androidsdk.rest.client.ProfileRestClient; import org.matrix.androidsdk.rest.client.PushersRestClient; import org.matrix.androidsdk.rest.client.RoomsRestClient; import org.matrix.androidsdk.rest.client.ThirdPidRestClient; import org.matrix.androidsdk.rest.model.CreateRoomResponse; import org.matrix.androidsdk.rest.model.DeleteDeviceAuth; import org.matrix.androidsdk.rest.model.DeleteDeviceParams; import org.matrix.androidsdk.rest.model.DevicesListResponse; import org.matrix.androidsdk.rest.model.Event; import org.matrix.androidsdk.rest.model.MatrixError; import org.matrix.androidsdk.rest.model.ReceiptData; import org.matrix.androidsdk.rest.model.RoomMember; import org.matrix.androidsdk.rest.model.RoomResponse; import org.matrix.androidsdk.rest.model.Search.SearchResponse; import org.matrix.androidsdk.rest.model.User; import org.matrix.androidsdk.rest.model.bingrules.BingRule; import org.matrix.androidsdk.rest.model.login.Credentials; import org.matrix.androidsdk.rest.model.login.RegistrationFlowResponse; import org.matrix.androidsdk.sync.DefaultEventsThreadListener; import org.matrix.androidsdk.sync.EventsThread; import org.matrix.androidsdk.sync.EventsThreadListener; import org.matrix.androidsdk.util.BingRulesManager; import org.matrix.androidsdk.util.ContentManager; import org.matrix.androidsdk.util.JsonUtils; import org.matrix.androidsdk.util.Log; import org.matrix.androidsdk.util.UnsentEventsManager; import org.matrix.olm.OlmManager; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.regex.Pattern; /** * Class that represents one user's session with a particular home server. * There can potentially be multiple sessions for handling multiple accounts. */ public class MXSession { private static final String LOG_TAG = "MXSession"; private DataRetriever mDataRetriever; private MXDataHandler mDataHandler; private EventsThread mEventsThread; private Credentials mCredentials; // Api clients private EventsRestClient mEventsRestClient; private ProfileRestClient mProfileRestClient; private PresenceRestClient mPresenceRestClient; private RoomsRestClient mRoomsRestClient; private BingRulesRestClient mBingRulesRestClient; private PushersRestClient mPushersRestClient; private ThirdPidRestClient mThirdPidRestClient; private CallRestClient mCallRestClient; private AccountDataRestClient mAccountDataRestClient; private CryptoRestClient mCryptoRestClient; private LoginRestClient mLoginRestClient; private ApiFailureCallback mFailureCallback; private ContentManager mContentManager; public MXCallsManager mCallsManager; private Context mAppContent; private NetworkConnectivityReceiver mNetworkConnectivityReceiver; private UnsentEventsManager mUnsentEventsManager; private MXLatestChatMessageCache mLatestChatMessageCache; private MXMediasCache mMediasCache; private BingRulesManager mBingRulesManager = null; private boolean mIsAliveSession = true; // online status private boolean mIsOnline = true; private HomeserverConnectionConfig mHsConfig; // the application is launched from a notification // so, mEventsThread.start might be not ready private boolean mIsCatchupPending = false; // load the crypto libs. public static OlmManager mOlmManager = new OlmManager(); // regex pattern to find matrix user ids in a string. public static final String MATRIX_USER_IDENTIFIER_REGEX = "@[A-Z0-9._=-]+:[A-Z0-9.-]+\\.[A-Z]{2,}"; public static final Pattern PATTERN_CONTAIN_MATRIX_USER_IDENTIFIER = Pattern.compile(MATRIX_USER_IDENTIFIER_REGEX, Pattern.CASE_INSENSITIVE); // regex pattern to find room aliases in a string. public static final String MATRIX_ROOM_ALIAS_REGEX = "#[A-Z0-9._%#+-]+:[A-Z0-9.-]+\\.[A-Z]{2,}"; public static final Pattern PATTERN_CONTAIN_MATRIX_ALIAS = Pattern.compile(MATRIX_ROOM_ALIAS_REGEX, Pattern.CASE_INSENSITIVE); // regex pattern to find room ids in a string. public static final String MATRIX_ROOM_IDENTIFIER_REGEX = "![A-Z0-9]+:[A-Z0-9.-]+\\.[A-Z]{2,}"; public static final Pattern PATTERN_CONTAIN_MATRIX_ROOM_IDENTIFIER = Pattern.compile(MATRIX_ROOM_IDENTIFIER_REGEX, Pattern.CASE_INSENSITIVE); // regex pattern to find message ids in a string. public static final String MATRIX_MESSAGE_IDENTIFIER_REGEX = "\\$[A-Z0-9]+:[A-Z0-9.-]+\\.[A-Z]{2,}"; public static final Pattern PATTERN_CONTAIN_MATRIX_MESSAGE_IDENTIFIER = Pattern.compile(MATRIX_MESSAGE_IDENTIFIER_REGEX, Pattern.CASE_INSENSITIVE); // regex pattern to find permalink with message id. // Android does not support in URL so extract it. public static final Pattern PATTERN_CONTAIN_MATRIX_TO_PERMALINK_ROOM_ID = Pattern.compile("https:\\/\\/matrix\\.to\\/#\\/"+ MATRIX_ROOM_IDENTIFIER_REGEX +"\\/" + MATRIX_MESSAGE_IDENTIFIER_REGEX, Pattern.CASE_INSENSITIVE); public static final Pattern PATTERN_CONTAIN_MATRIX_TO_PERMALINK_ROOM_ALIAS = Pattern.compile("https:\\/\\/matrix\\.to\\/#\\/"+ MATRIX_ROOM_ALIAS_REGEX +"\\/" + MATRIX_MESSAGE_IDENTIFIER_REGEX, Pattern.CASE_INSENSITIVE); public static final Pattern PATTERN_CONTAIN_APP_LINK_PERMALINK_ROOM_ID = Pattern.compile("https:\\/\\/[A-Z0-9.-]+\\.[A-Z]{2,}\\/[A-Z]{3,}\\/#\\/room\\/"+ MATRIX_ROOM_IDENTIFIER_REGEX +"\\/" + MATRIX_MESSAGE_IDENTIFIER_REGEX, Pattern.CASE_INSENSITIVE); public static final Pattern PATTERN_CONTAIN_APP_LINK_PERMALINK_ROOM_ALIAS = Pattern.compile("https:\\/\\/[A-Z0-9.-]+\\.[A-Z]{2,}\\/[A-Z]{3,}\\/#\\/room\\/"+ MATRIX_ROOM_ALIAS_REGEX +"\\/" + MATRIX_MESSAGE_IDENTIFIER_REGEX, Pattern.CASE_INSENSITIVE); /** * Create a basic session for direct API calls. * * @param hsConfig the home server connection config */ private MXSession(HomeserverConnectionConfig hsConfig) { mCredentials = hsConfig.getCredentials(); mHsConfig = hsConfig; mEventsRestClient = new EventsRestClient(hsConfig); mProfileRestClient = new ProfileRestClient(hsConfig); mPresenceRestClient = new PresenceRestClient(hsConfig); mRoomsRestClient = new RoomsRestClient(hsConfig); mBingRulesRestClient = new BingRulesRestClient(hsConfig); mPushersRestClient = new PushersRestClient(hsConfig); mThirdPidRestClient = new ThirdPidRestClient(hsConfig); mCallRestClient = new CallRestClient(hsConfig); mAccountDataRestClient = new AccountDataRestClient(hsConfig); mCryptoRestClient = new CryptoRestClient(hsConfig); mLoginRestClient = new LoginRestClient(hsConfig); } /** * Create a user session with a data handler. * * @param hsConfig the home server connection config * @param dataHandler the data handler * @param appContext the application context */ public MXSession(HomeserverConnectionConfig hsConfig, MXDataHandler dataHandler, Context appContext) { this(hsConfig); mDataHandler = dataHandler; mDataHandler.getStore().addMXStoreListener(new MXStoreListener() { @Override public void postProcess(String accountId) { MXFileCryptoStore store = new MXFileCryptoStore(); store.initWithCredentials(mAppContent, mCredentials); if (store.hasData() || mEnableCryptoWhenStartingMXSession) { // open the store store.open(); // enable mCrypto = new MXCrypto(MXSession.this, store); mDataHandler.setCrypto(mCrypto); // the room summaries are not stored with decrypted content decryptRoomSummaries(); } } @Override public void onReadReceiptsLoaded(final String roomId) { final List<ReceiptData> receipts = mDataHandler.getStore().getEventReceipts(roomId, null, false, false); final ArrayList<String> senders = new ArrayList<>(); for(ReceiptData receipt : receipts) { senders.add(receipt.userId); } mDataHandler.onReceiptEvent(roomId, senders); } }); // Initialize a data retriever with rest clients mDataRetriever = new DataRetriever(); mDataRetriever.setRoomsRestClient(mRoomsRestClient); mDataHandler.setDataRetriever(mDataRetriever); mDataHandler.setProfileRestClient(mProfileRestClient); mDataHandler.setPresenceRestClient(mPresenceRestClient); mDataHandler.setThirdPidRestClient(mThirdPidRestClient); mDataHandler.setRoomsRestClient(mRoomsRestClient); // application context mAppContent = appContext; mNetworkConnectivityReceiver = new NetworkConnectivityReceiver(); mNetworkConnectivityReceiver.checkNetworkConnection(appContext); mDataHandler.setNetworkConnectivityReceiver(mNetworkConnectivityReceiver); mAppContent.registerReceiver(mNetworkConnectivityReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); mBingRulesManager = new BingRulesManager(this, mNetworkConnectivityReceiver); mDataHandler.setPushRulesManager(mBingRulesManager); mUnsentEventsManager = new UnsentEventsManager(mNetworkConnectivityReceiver, mDataHandler); mContentManager = new ContentManager(hsConfig, mUnsentEventsManager); // mCallsManager = new MXCallsManager(this, mAppContent); mDataHandler.setCallsManager(mCallsManager); // the rest client mEventsRestClient.setUnsentEventsManager(mUnsentEventsManager); mProfileRestClient.setUnsentEventsManager(mUnsentEventsManager); mPresenceRestClient.setUnsentEventsManager(mUnsentEventsManager); mRoomsRestClient.setUnsentEventsManager(mUnsentEventsManager); mBingRulesRestClient.setUnsentEventsManager(mUnsentEventsManager); mThirdPidRestClient.setUnsentEventsManager(mUnsentEventsManager); mCallRestClient.setUnsentEventsManager(mUnsentEventsManager); mAccountDataRestClient.setUnsentEventsManager(mUnsentEventsManager); mCryptoRestClient.setUnsentEventsManager(mUnsentEventsManager); mLoginRestClient.setUnsentEventsManager(mUnsentEventsManager); // return the default cache manager mLatestChatMessageCache = new MXLatestChatMessageCache(mCredentials.userId); mMediasCache = new MXMediasCache(mContentManager, mCredentials.userId, appContext); mDataHandler.setMediasCache(mMediasCache); } private void checkIfAlive() { synchronized (this) { if (!mIsAliveSession) { Log.e(LOG_TAG, "Use of a release session"); //throw new AssertionError("Should not used a cleared mxsession "); } } } /** * Init the user-agent used by the REST requests. * @param context the application context */ public static void initUserAgent(Context context) { RestClient.initUserAgent(context); } /** * @return the SDK version. */ public String getVersion(boolean longFormat) { checkIfAlive(); String versionName = BuildConfig.VERSION_NAME; if (!TextUtils.isEmpty(versionName)) { String gitVersion = mAppContent.getResources().getString(R.string.git_sdk_revision); if (longFormat) { String date = mAppContent.getResources().getString(R.string.git_sdk_revision_date); versionName += " (" + gitVersion + "-" + date + ")"; } else { versionName += " (" + gitVersion + ")"; } } return versionName; } /** * @return the crypto lib version */ public String getCryptoVersion(Context context, boolean longFormat) { String version = ""; if (null != mOlmManager) { version = longFormat ? mOlmManager.getDetailedVersion(context) : mOlmManager.getVersion(); } return version; } /** * Get the data handler. * * @return the data handler. */ public MXDataHandler getDataHandler() { checkIfAlive(); return mDataHandler; } /** * Get the user credentials. * * @return the credentials */ public Credentials getCredentials() { checkIfAlive(); return mCredentials; } /** * Get the API client for requests to the events API. * * @return the events API client */ public EventsRestClient getEventsApiClient() { checkIfAlive(); return mEventsRestClient; } /** * Get the API client for requests to the profile API. * * @return the profile API client */ public ProfileRestClient getProfileApiClient() { checkIfAlive(); return mProfileRestClient; } /** * Get the API client for requests to the presence API. * * @return the presence API client */ public PresenceRestClient getPresenceApiClient() { checkIfAlive(); return mPresenceRestClient; } /** * Refresh the presence info of a dedicated user. * * @param userId the user userID. * @param callback the callback. */ public void refreshUserPresence(final String userId, final ApiCallback<Void> callback) { mPresenceRestClient.getPresence(userId, new ApiCallback<User>() { @Override public void onSuccess(User user) { User currentUser = mDataHandler.getStore().getUser(userId); if (null != currentUser) { currentUser.presence = user.presence; currentUser.currently_active = user.currently_active; currentUser.lastActiveAgo = user.lastActiveAgo; } else { currentUser = user; } currentUser.setLatestPresenceTs(System.currentTimeMillis()); mDataHandler.getStore().storeUser(currentUser); if (null != callback) { callback.onSuccess(null); } } @Override public void onNetworkError(Exception e) { if (null != callback) { callback.onNetworkError(e); } } @Override public void onMatrixError(MatrixError e) { if (null != callback) { callback.onMatrixError(e); } } @Override public void onUnexpectedError(Exception e) { if (null != callback) { callback.onUnexpectedError(e); } } }); } /** * Get the API client for requests to the bing rules API. * * @return the bing rules API client */ public BingRulesRestClient getBingRulesApiClient() { checkIfAlive(); return mBingRulesRestClient; } public ThirdPidRestClient getThirdPidRestClient() { checkIfAlive(); return mThirdPidRestClient; } public CallRestClient getCallRestClient() { checkIfAlive(); return mCallRestClient; } public PushersRestClient getPushersRestClient() { checkIfAlive(); return mPushersRestClient; } public CryptoRestClient getCryptoRestClient() { checkIfAlive(); return mCryptoRestClient; } public HomeserverConnectionConfig getHomeserverConfig() { checkIfAlive(); return mHsConfig; } /** * Get the API client for requests to the rooms API. * * @return the rooms API client */ public RoomsRestClient getRoomsApiClient() { checkIfAlive(); return mRoomsRestClient; } protected void setEventsApiClient(EventsRestClient eventsRestClient) { checkIfAlive(); this.mEventsRestClient = eventsRestClient; } protected void setProfileApiClient(ProfileRestClient profileRestClient) { checkIfAlive(); this.mProfileRestClient = profileRestClient; } protected void setPresenceApiClient(PresenceRestClient presenceRestClient) { checkIfAlive(); this.mPresenceRestClient = presenceRestClient; } protected void setRoomsApiClient(RoomsRestClient roomsRestClient) { checkIfAlive(); this.mRoomsRestClient = roomsRestClient; } public MXLatestChatMessageCache getLatestChatMessageCache() { checkIfAlive(); return mLatestChatMessageCache; } public MXMediasCache getMediasCache() { checkIfAlive(); return mMediasCache; } /** * Clear the session data */ public void clear(Context context) { checkIfAlive(); synchronized (this) { mIsAliveSession = false; } // stop events stream stopEventStream(); // cancel any listener mDataHandler.clear(); // network event will not be listened anymore mAppContent.unregisterReceiver(mNetworkConnectivityReceiver); mNetworkConnectivityReceiver.removeListeners(); // auto resent messages will not be resent mUnsentEventsManager.clear(); mLatestChatMessageCache.clearCache(context); mMediasCache.clear(); if (null != mCrypto) { mCrypto.close(); } } /** * @return true if the session is active i.e. has not been cleared after a logout. */ public boolean isAlive() { synchronized (this) { return mIsAliveSession; } } /** * Get the content manager (for uploading and downloading content) associated with the session. * * @return the content manager */ public ContentManager getContentManager() { checkIfAlive(); return mContentManager; } /** * Get the session's current user. The MyUser object provides methods for updating user properties which are not possible for other users. * * @return the session's MyUser object */ public MyUser getMyUser() { checkIfAlive(); return mDataHandler.getMyUser(); } /** * Get the session's current userid. * * @return the session's MyUser id */ public String getMyUserId() { checkIfAlive(); if (null != mDataHandler.getMyUser()) { return mDataHandler.getMyUser().user_id; } return null; } /** * Start the event stream (events thread that listens for events) with an event listener. * * @param anEventsListener the event listener or null if using a DataHandler * @param networkConnectivityReceiver the network connectivity listener. * @param initialToken the initial sync token (null to start from scratch) */ public void startEventStream(final EventsThreadListener anEventsListener, final NetworkConnectivityReceiver networkConnectivityReceiver, final String initialToken) { checkIfAlive(); if (mEventsThread != null) { Log.e(LOG_TAG, "Ignoring startEventStream() : Thread already created."); return; } if (mDataHandler == null) { Log.e(LOG_TAG, "Error starting the event stream: No data handler is defined"); return; } Log.d(LOG_TAG, "startEventStream : create the event stream"); final EventsThreadListener fEventsListener = (null == anEventsListener) ? new DefaultEventsThreadListener(mDataHandler) : anEventsListener; mEventsThread = new EventsThread(mEventsRestClient, fEventsListener, initialToken); mEventsThread.setNetworkConnectivityReceiver(networkConnectivityReceiver); if (mFailureCallback != null) { mEventsThread.setFailureCallback(mFailureCallback); } if (mCredentials.accessToken != null && !mEventsThread.isAlive()) { // GA issue try { mEventsThread.start(); } catch (Exception e) { Log.e(LOG_TAG, "## startEventStream() : mEventsThread.start failed " + e.getMessage()); } if (mIsCatchupPending) { Log.d(LOG_TAG, "startEventStream : there was a pending catchup : the catchup will be triggered in 5 seconds"); mIsCatchupPending = false; Handler handler = new Handler(Looper.getMainLooper()); handler.postDelayed(new Runnable() { @Override public void run() { Log.d(LOG_TAG, "startEventStream : pause the stream"); pauseEventStream(); } }, 5000); } } } /** * Refresh the access token */ public void refreshToken() { checkIfAlive(); mProfileRestClient.refreshTokens(new ApiCallback<Credentials>() { @Override public void onSuccess(Credentials info) { Log.d(LOG_TAG, "refreshToken : succeeds."); } @Override public void onNetworkError(Exception e) { Log.d(LOG_TAG, "refreshToken : onNetworkError " + e.getLocalizedMessage()); } @Override public void onMatrixError(MatrixError e) { Log.d(LOG_TAG, "refreshToken : onMatrixError " + e.getLocalizedMessage()); } @Override public void onUnexpectedError(Exception e) { Log.d(LOG_TAG, "refreshToken : onMatrixError " + e.getLocalizedMessage()); } }); } /** * Update the online status * @param isOnline true if the client must be seen as online */ public void setIsOnline(boolean isOnline) { if (isOnline != mIsOnline) { mIsOnline = isOnline; if (null != mEventsThread) { mEventsThread.setIsOnline(isOnline); } } } /** * Tell if the client is seen as "online" */ public boolean isOnline() { return mIsOnline; } /** * Update the heartbeat request timeout. * @param ms the delay in ms */ public void setSyncTimeout(int ms) { if (null != mEventsThread) { mEventsThread.setServerLongPollTimeout(ms); } } /** * @return the heartbeat request timeout */ public int getSyncTimeout() { if (null != mEventsThread) { return mEventsThread.getServerLongPollTimeout(); } return 0; } /** * Set a delay between two sync requests. * @param ms the delay in ms */ public void setSyncDelay(int ms) { if (null != mEventsThread) { mEventsThread.setSyncDelay(ms); } } /** * @return the delay between two sync requests. */ public int getSyncDelay() { if (null != mEventsThread) { mEventsThread.getSyncDelay(); } return 0; } /** * Shorthand for {@link #startEventStream(EventsThreadListener, NetworkConnectivityReceiver, String)} with no eventListener * using a DataHandler and no specific failure callback. * * @param initialToken the initial sync token (null to sync from scratch). */ public void startEventStream(String initialToken) { checkIfAlive(); startEventStream(null, this.mNetworkConnectivityReceiver, initialToken); } /** * Gracefully stop the event stream. */ public void stopEventStream() { if (null != mCallsManager) { mCallsManager.stopTurnServerRefresh(); } if (null != mEventsThread) { Log.d(LOG_TAG, "stopEventStream"); mEventsThread.kill(); mEventsThread = null; } else { Log.e(LOG_TAG, "stopEventStream : mEventsThread is already null"); } } /** * Pause the event stream */ public void pauseEventStream() { checkIfAlive(); if (null != mCallsManager) { mCallsManager.pauseTurnServerRefresh(); } if (null != mEventsThread) { Log.d(LOG_TAG, "pauseEventStream"); mEventsThread.pause(); } else { Log.e(LOG_TAG, "pauseEventStream : mEventsThread is null"); } } /** * Resume the event stream */ public void resumeEventStream() { checkIfAlive(); if (null != mNetworkConnectivityReceiver) { // mNetworkConnectivityReceiver is a broadcastReceiver // but some users reported that the network updates wre not broadcasted. mNetworkConnectivityReceiver.checkNetworkConnection(mAppContent); } if (null != mCallsManager) { mCallsManager.unpauseTurnServerRefresh(); } if (null != mEventsThread) { Log.d(LOG_TAG, "unpause"); mEventsThread.unpause(); } else { Log.e(LOG_TAG, "resumeEventStream : mEventsThread is null"); } } /** * Trigger a catchup */ public void catchupEventStream() { checkIfAlive(); if (null != mEventsThread) { Log.d(LOG_TAG, "catchupEventStream"); mEventsThread.catchup(); } else { Log.e(LOG_TAG, "catchupEventStream : mEventsThread is null so catchup when the thread will be created"); mIsCatchupPending = true; } } /** * Set a global failure callback implementation. * * @param failureCallback the failure callback */ public void setFailureCallback(ApiFailureCallback failureCallback) { checkIfAlive(); mFailureCallback = failureCallback; if (mEventsThread != null) { mEventsThread.setFailureCallback(failureCallback); } } /** * Create a direct message room with one participant.<br> * The participant can be a user ID or mail address. Once the room is created, on success, the room * is set as a "direct message" with the participant. * @param aParticipantUserId user ID (or user mail) to be invited in the direct message room * @param aCreateRoomCallBack async call back response * @return true if the invite was performed, false otherwise */ public boolean createRoomDirectMessage(final String aParticipantUserId, final ApiCallback<String> aCreateRoomCallBack) { boolean retCode = false; if(!TextUtils.isEmpty(aParticipantUserId)) { retCode = true; HashMap<String, Object> params = new HashMap<>(); params.put("preset","trusted_private_chat"); params.put("is_direct", true); if (android.util.Patterns.EMAIL_ADDRESS.matcher(aParticipantUserId).matches()) { // build the invite third party object HashMap<String, String> parameters = new HashMap<>(); parameters.put("id_server", mHsConfig.getIdentityServerUri().getHost()); parameters.put("medium", "email"); parameters.put("address", aParticipantUserId); params.put("invite_3pid", Arrays.asList(parameters)); } else { if (!aParticipantUserId.equals(getMyUserId())) { // send invite only if the participant ID is not the user ID params.put("invite", Arrays.asList(aParticipantUserId)); } } createRoom(params, new ApiCallback<String>() { @Override public void onSuccess(String roomId) { final String fRoomId = roomId; toggleDirectChatRoom(roomId, aParticipantUserId, new ApiCallback<Void>() { @Override public void onSuccess(Void info) { if(null != aCreateRoomCallBack) { aCreateRoomCallBack.onSuccess(fRoomId); } } @Override public void onNetworkError(Exception e) { if(null != aCreateRoomCallBack) { aCreateRoomCallBack.onNetworkError(e); } } @Override public void onMatrixError(MatrixError e) { if(null != aCreateRoomCallBack) { aCreateRoomCallBack.onMatrixError(e); } } @Override public void onUnexpectedError(Exception e) { if(null != aCreateRoomCallBack) { aCreateRoomCallBack.onUnexpectedError(e); } } }); } @Override public void onNetworkError(Exception e) { if(null != aCreateRoomCallBack) { aCreateRoomCallBack.onNetworkError(e); } } @Override public void onMatrixError(MatrixError e) { if(null != aCreateRoomCallBack) { aCreateRoomCallBack.onMatrixError(e); } } @Override public void onUnexpectedError(Exception e) { if(null != aCreateRoomCallBack) { aCreateRoomCallBack.onUnexpectedError(e); } } }); } return retCode; } /** * Create a new room. * * @param callback the async callback once the room is ready */ public void createRoom(final ApiCallback<String> callback) { createRoom(null, null, null, callback); } /** * Create a new room with given properties. * * @param params the creation parameters. * @param callback the async callback once the room is ready */ public void createRoom(final Map<String, Object> params, final ApiCallback<String> callback) { mRoomsRestClient.createRoom(params, new SimpleApiCallback<CreateRoomResponse>(callback) { @Override public void onSuccess(CreateRoomResponse info) { final String roomId = info.roomId; Room createdRoom = mDataHandler.getRoom(roomId); // the creation events are not be called during the creation if (createdRoom.getState().getMember(mCredentials.userId) == null) { createdRoom.setOnInitialSyncCallback(new ApiCallback<Void>() { @Override public void onSuccess(Void info) { callback.onSuccess(roomId); } @Override public void onNetworkError(Exception e) { callback.onNetworkError(e); } @Override public void onMatrixError(MatrixError e) { callback.onMatrixError(e); } @Override public void onUnexpectedError(Exception e) { callback.onUnexpectedError(e); } }); } else { callback.onSuccess(roomId); } } }); } /** * Create a new room with given properties. Needs the data handler. * * @param name the room name * @param topic the room topic * @param alias the room alias * @param callback the async callback once the room is ready */ public void createRoom(String name, String topic, String alias, final ApiCallback<String> callback) { createRoom(name, topic, RoomState.DIRECTORY_VISIBILITY_PRIVATE, alias, RoomState.GUEST_ACCESS_CAN_JOIN, RoomState.HISTORY_VISIBILITY_SHARED, callback); } /** * Create a new room with given properties. Needs the data handler. * * @param name the room name * @param topic the room topic * @param visibility the room visibility * @param alias the room alias * @param guestAccess the guest access rule (see {@link RoomState#GUEST_ACCESS_CAN_JOIN} or {@link RoomState#GUEST_ACCESS_FORBIDDEN}) * @param historyVisibility the history visibility * @param callback the async callback once the room is ready */ public void createRoom(String name, String topic, String visibility, String alias, String guestAccess, String historyVisibility, final ApiCallback<String> callback) { checkIfAlive(); mRoomsRestClient.createRoom(name, topic, visibility, alias, guestAccess, historyVisibility, new SimpleApiCallback<CreateRoomResponse>(callback) { @Override public void onSuccess(CreateRoomResponse info) { final String roomId = info.roomId; Room createdRoom = mDataHandler.getRoom(roomId); // the creation events are not be called during the creation if (createdRoom.getState().getMember(mCredentials.userId) == null) { createdRoom.setOnInitialSyncCallback(new ApiCallback<Void>() { @Override public void onSuccess(Void info) { callback.onSuccess(roomId); } @Override public void onNetworkError(Exception e) { callback.onNetworkError(e); } @Override public void onMatrixError(MatrixError e) { callback.onMatrixError(e); } @Override public void onUnexpectedError(Exception e) { callback.onUnexpectedError(e); } }); } else { callback.onSuccess(roomId); } } }); } /** * Join a room by its roomAlias * * @param roomIdOrAlias the room alias * @param callback the async callback once the room is joined. The RoomId is provided. */ public void joinRoom(String roomIdOrAlias, final ApiCallback<String> callback) { checkIfAlive(); // sanity check if ((null != mDataHandler) && (null != roomIdOrAlias)) { mDataRetriever.getRoomsRestClient().joinRoom(roomIdOrAlias, new SimpleApiCallback<RoomResponse>(callback) { @Override public void onSuccess(final RoomResponse roomResponse) { final String roomId = roomResponse.roomId; Room joinedRoom = mDataHandler.getRoom(roomId); RoomMember member = joinedRoom.getState().getMember(mCredentials.userId); String state = (null != member) ? member.membership : null; // wait until the initial sync is done if ((state == null) || TextUtils.equals(state, RoomMember.MEMBERSHIP_INVITE)) { joinedRoom.setOnInitialSyncCallback(new ApiCallback<Void>() { @Override public void onSuccess(Void info) { callback.onSuccess(roomId); } @Override public void onNetworkError(Exception e) { callback.onNetworkError(e); } @Override public void onMatrixError(MatrixError e) { callback.onMatrixError(e); } @Override public void onUnexpectedError(Exception e) { callback.onUnexpectedError(e); } }); } else { callback.onSuccess(roomId); } } }); } } /** * Retrieve user matrix id from a 3rd party id. * * @param address the user id. * @param media the media. * @param callback the 3rd party callback */ public void lookup3Pid(String address, String media, final ApiCallback<String> callback) { checkIfAlive(); mThirdPidRestClient.lookup3Pid(address, media, callback); } /** * Retrieve user matrix id from a 3rd party id. * * @param addresses 3rd party ids * @param mediums the medias. * @param callback the 3rd parties callback */ public void lookup3Pids(List<String> addresses, List<String> mediums, ApiCallback<List<String>> callback) { checkIfAlive(); mThirdPidRestClient.lookup3Pids(addresses, mediums, callback); } /** * Perform a remote text search. * * @param text the text to search for. * @param rooms a list of rooms to search in. nil means all rooms the user is in. * @param beforeLimit the number of events to get before the matching results. * @param afterLimit the number of events to get after the matching results. * @param nextBatch the token to pass for doing pagination from a previous response. * @param callback the request callback */ public void searchMessageText(String text, List<String> rooms, int beforeLimit, int afterLimit, String nextBatch, final ApiCallback<SearchResponse> callback) { checkIfAlive(); if (null != callback) { mEventsRestClient.searchMessagesByText(text, rooms, beforeLimit, afterLimit, nextBatch, callback); } } /** * Perform a remote text search. * * @param text the text to search for. * @param rooms a list of rooms to search in. nil means all rooms the user is in. * @param nextBatch the token to pass for doing pagination from a previous response. * @param callback the request callback */ public void searchMessagesByText(String text, List<String> rooms, String nextBatch, final ApiCallback<SearchResponse> callback) { checkIfAlive(); if (null != callback) { mEventsRestClient.searchMessagesByText(text, rooms, 0, 0, nextBatch, callback); } } /** * Perform a remote text search. * * @param text the text to search for. * @param nextBatch the token to pass for doing pagination from a previous response. * @param callback the request callback */ public void searchMessagesByText(String text, String nextBatch, final ApiCallback<SearchResponse> callback) { checkIfAlive(); if (null != callback) { mEventsRestClient.searchMessagesByText(text, null, 0, 0, nextBatch, callback); } } /** * Cancel any pending search request */ public void cancelSearchMessagesByText() { checkIfAlive(); mEventsRestClient.cancelSearchMessagesByText(); } /** * Perform a remote text search for a dedicated media types list * * @param name the text to search for. * @param rooms a list of rooms to search in. nil means all rooms the user is in. * @param nextBatch the token to pass for doing pagination from a previous response. * @param callback the request callback */ public void searchMediasByName(String name, List<String> rooms, String nextBatch, final ApiCallback<SearchResponse> callback) { checkIfAlive(); if (null != callback) { mEventsRestClient.searchMediasByText(name, rooms, 0, 0, nextBatch, callback); } } /** * Cancel any pending file search request */ public void cancelSearchMediasByText() { checkIfAlive(); mEventsRestClient.cancelSearchMediasByText(); } /** * Return the fulfilled active BingRule for the event. * * @param event the event * @return the fulfilled bingRule */ public BingRule fulfillRule(Event event) { checkIfAlive(); return mBingRulesManager.fulfilledBingRule(event); } /** * @return true if the calls are supported */ public boolean isVoipCallSupported() { if (null != mCallsManager) { return mCallsManager.isSupported(); } else { return false; } } /** * Get the list of rooms that are tagged the specified tag. * The returned array is ordered according to the room tag order. * * @param tag RoomTag.ROOM_TAG_XXX values * @return the rooms list. */ public List<Room> roomsWithTag(final String tag) { ArrayList<Room> taggedRooms = new ArrayList<>(); if (!TextUtils.equals(tag, RoomTag.ROOM_TAG_NO_TAG)) { Collection<Room> rooms = mDataHandler.getStore().getRooms(); for (Room room : rooms) { if (null != room.getAccountData().roomTag(tag)) { taggedRooms.add(room); } } if (taggedRooms.size() > 0) { Collections.sort(taggedRooms, new Comparator<Room>() { @Override public int compare(Room r1, Room r2) { int res = 0; RoomTag tag1 = r1.getAccountData().roomTag(tag); RoomTag tag2 = r2.getAccountData().roomTag(tag); if ((null != tag1.mOrder) && (null != tag2.mOrder)) { double diff = (tag1.mOrder - tag2.mOrder); res = (diff == 0) ? 0 : (diff > 0) ? +1 : -1; } else if (null != tag1.mOrder) { res = -1; } else if (null != tag2.mOrder) { res = +1; } // In case of same order, order rooms by their last event if (0 == res) { IMXStore store = mDataHandler.getStore(); Event latestEvent1 = store.getLatestEvent(r1.getRoomId()); Event latestEvent2 = store.getLatestEvent(r2.getRoomId()); // sanity check if ((null != latestEvent2) && (null != latestEvent1)) { long diff = (latestEvent2.getOriginServerTs() - latestEvent1.getOriginServerTs()); res = (diff == 0) ? 0 : (diff > 0) ? +1 : -1; } } return res; } }); } } else { Collection<Room> rooms = mDataHandler.getStore().getRooms(); for (Room room : rooms) { if (!room.getAccountData().hasTags()) { taggedRooms.add(room); } } } return taggedRooms; } /** * Get the list of roomIds that are tagged the specified tag. * The returned array is ordered according to the room tag order. * * @param tag RoomTag.ROOM_TAG_XXX values * @return the room IDs list. */ public List<String> roomIdsWithTag(final String tag) { List<Room> roomsWithTag = roomsWithTag(tag); ArrayList<String> roomIdsList = new ArrayList<>(); for (Room room : roomsWithTag) { roomIdsList.add(room.getRoomId()); } return roomIdsList; } /** * Compute the tag order to use for a room tag so that the room will appear in the expected position * in the list of rooms stamped with this tag. * * @param index the targeted index of the room in the list of rooms with the tag `tag`. * @param originIndex the origin index. Integer.MAX_VALUE if there is none. * @param tag the tag * @return the tag order to apply to get the expected position. */ public Double tagOrderToBeAtIndex(int index, int originIndex, String tag) { // Algo (and the [0.0, 1.0] assumption) inspired from matrix-react-sdk: // We sort rooms by the lexicographic ordering of the 'order' metadata on their tags. // For convenience, we calculate this for now a floating point number between 0.0 and 1.0. Double orderA = 0.0; // by default we're next to the beginning of the list Double orderB = 1.0; // by default we're next to the end of the list too List<Room> roomsWithTag = roomsWithTag(tag); if (roomsWithTag.size() > 0) { // when an object is moved down, the index must be incremented // because the object will be removed from the list to be inserted after its destination if ((originIndex != Integer.MAX_VALUE) && (originIndex < index)) { index++; } if (index > 0) { // Bound max index to the array size int prevIndex = (index < roomsWithTag.size()) ? index : roomsWithTag.size(); RoomTag prevTag = roomsWithTag.get(prevIndex - 1).getAccountData().roomTag(tag); if (null == prevTag.mOrder) { Log.e(LOG_TAG, "computeTagOrderForRoom: Previous room in sublist has no ordering metadata. This should never happen."); } else { orderA = prevTag.mOrder; } } if (index <= roomsWithTag.size() - 1) { RoomTag nextTag = roomsWithTag.get(index).getAccountData().roomTag(tag); if (null == nextTag.mOrder) { Log.e(LOG_TAG, "computeTagOrderForRoom: Next room in sublist has no ordering metadata. This should never happen."); } else { orderB = nextTag.mOrder; } } } return (orderA + orderB) / 2.0; } /** * @return the direct chat room ids list */ public List<String> getDirectChatRoomIdsList() { IMXStore store = getDataHandler().getStore(); ArrayList<String> directChatRoomIdsList = new ArrayList<>(); if (null == store) { Log.e(LOG_TAG,"## getDirectChatRoomIdsList() : null store"); return directChatRoomIdsList; } Collection<List<String>> listOfList = null; if (null != store.getDirectChatRoomsDict()) { listOfList = store.getDirectChatRoomsDict().values(); } // if the direct messages entry has been defined if (null != listOfList) { for (List<String> list : listOfList) { for (String roomId : list) { // test if the room is defined once and exists if ((directChatRoomIdsList.indexOf(roomId) < 0) && (null != store.getRoom(roomId))) { directChatRoomIdsList.add(roomId); } } } } else { // background compatibility heuristic (named looksLikeDirectMessageRoom in the JS) ArrayList<RoomIdsListRetroCompat> directChatRoomIdsListRetValue = new ArrayList<>(); getDirectChatRoomIdsListRetroCompat(store, directChatRoomIdsListRetValue); // force direct chat room list to be updated with retro compatibility rooms values if(0 != directChatRoomIdsListRetValue.size()) { forceDirectChatRoomValue(directChatRoomIdsListRetValue, new ApiCallback<Void>() { @Override public void onSuccess(Void info) { Log.d(LOG_TAG, "## getDirectChatRoomIdsList(): background compatibility heuristic => account_data update succeed"); } @Override public void onMatrixError(MatrixError e) { if (MatrixError.FORBIDDEN.equals(e.errcode)) { Log.e(LOG_TAG, "## getDirectChatRoomIdsList(): onMatrixError Msg=" + e.error); } } @Override public void onNetworkError(Exception e) { Log.e(LOG_TAG, "## getDirectChatRoomIdsList(): onNetworkError Msg=" + e.getMessage()); } @Override public void onUnexpectedError(Exception e) { Log.e(LOG_TAG, "## getDirectChatRoomIdsList(): onUnexpectedError Msg=" + e.getMessage()); } }); } } return directChatRoomIdsList; } /** * This class defines a direct chat backward compliancyc structure */ private class RoomIdsListRetroCompat { String mRoomId; String mParticipantUserId; public RoomIdsListRetroCompat(String aParticipantUserId, String aRoomId) { this.mParticipantUserId = aParticipantUserId; this.mRoomId = aRoomId; } } /** * Return the direct chat room list for retro compatibility with 1:1 rooms. * @param aStore strore instance * @param aDirectChatRoomIdsListRetValue the other participants in the 1:1 room */ private void getDirectChatRoomIdsListRetroCompat(IMXStore aStore, ArrayList<RoomIdsListRetroCompat> aDirectChatRoomIdsListRetValue) { RoomIdsListRetroCompat item; if((null != aStore) && (null != aDirectChatRoomIdsListRetValue)) { ArrayList<Room> rooms = new ArrayList<>(aStore.getRooms()); ArrayList<RoomMember> members; int otherParticipantIndex; for (Room r : rooms) { // Show 1:1 chats in separate "Direct Messages" section as long as they haven't // been moved to a different tag section if ((r.getActiveMembers().size() == 2) && (null != r.getAccountData()) && (!r.getAccountData().hasTags())) { RoomMember roomMember = r.getMember(getMyUserId()); members = new ArrayList<>(r.getActiveMembers()); if (null != roomMember) { String membership = roomMember.membership; if (TextUtils.equals(membership, RoomMember.MEMBERSHIP_JOIN) || TextUtils.equals(membership, RoomMember.MEMBERSHIP_BAN) || TextUtils.equals(membership, RoomMember.MEMBERSHIP_LEAVE)) { if(TextUtils.equals(members.get(0).getUserId(), getMyUserId())) { otherParticipantIndex = 1; } else { otherParticipantIndex = 0; } item = new RoomIdsListRetroCompat(members.get(otherParticipantIndex).getUserId(), r.getRoomId()); aDirectChatRoomIdsListRetValue.add(item); } } } } } } /** * Return the list of the direct chat room IDs for the user given in parameter.<br> * Based on the account_data map content, the entry associated with aSearchedUserId is returned. * @param aSearchedUserId user ID * @return the list of the direct chat room Id */ public List<String> getDirectChatRoomIdsList(String aSearchedUserId) { ArrayList<String> directChatRoomIdsList = new ArrayList<>(); IMXStore store = getDataHandler().getStore(); Room room; HashMap<String, List<String>> params; if(null != store.getDirectChatRoomsDict()) { params = new HashMap<>(store.getDirectChatRoomsDict()); if (params.containsKey(aSearchedUserId)) { directChatRoomIdsList = new ArrayList<>(); for(String roomId: params.get(aSearchedUserId)) { room = store.getRoom(roomId); if(null != room) { // skipp empty rooms directChatRoomIdsList.add(roomId); } } } else { Log.w(LOG_TAG,"## getDirectChatRoomIdsList(): UserId "+aSearchedUserId+" has no entry in account_data"); } } else { Log.w(LOG_TAG,"## getDirectChatRoomIdsList(): failure - getDirectChatRoomsDict()=null"); } return directChatRoomIdsList; } /** * Toggles the direct chat status of a room.<br> * Create a new direct chat room in the account data section if the room does not exist, * otherwise the room is removed from the account data section. * Direct chat room user ID choice algorithm:<br> * 1- oldest joined room member * 2- oldest invited room member * 3- the user himself * @param roomId the room roomId * @param callback the asynchronous callback */ public void toggleDirectChatRoom(String roomId, String aParticipantUserId, ApiCallback<Void> callback) { IMXStore store = getDataHandler().getStore(); Room room = store.getRoom(roomId); if (null != room) { HashMap<String, List<String>> params; if (null != store.getDirectChatRoomsDict()) { params = new HashMap<>(store.getDirectChatRoomsDict()); } else { params = new HashMap<>(); } // if the room was not yet seen as direct chat if (getDirectChatRoomIdsList().indexOf(roomId) < 0) { ArrayList<String> roomIdsList = new ArrayList<>(); RoomMember directChatMember = null; String chosenUserId; if(null == aParticipantUserId) { ArrayList<RoomMember> members = new ArrayList<>(room.getActiveMembers()); // should never happen but it was reported by a GA issue if (0 == members.size()) { return; } if (members.size() > 1) { // sort algo: oldest join first, then oldest invited Collections.sort(members, new Comparator<RoomMember>() { @Override public int compare(RoomMember r1, RoomMember r2) { int res; long diff; if (RoomMember.MEMBERSHIP_JOIN.equals(r2.membership) && RoomMember.MEMBERSHIP_INVITE.equals(r1.membership)) { res = 1; } else if (r2.membership.equals(r1.membership)) { diff = r1.getOriginServerTs() - r2.getOriginServerTs(); res = (0 == diff) ? 0 : ((diff > 0) ? 1 : -1); } else { res = -1; } return res; } }); int nextIndexSearch = 0; // take the oldest join member if (!TextUtils.equals(members.get(0).getUserId(), getMyUserId())) { if (RoomMember.MEMBERSHIP_JOIN.equals(members.get(0).membership)) { directChatMember = members.get(0); } } else { nextIndexSearch = 1; if (RoomMember.MEMBERSHIP_JOIN.equals(members.get(1).membership)) { directChatMember = members.get(1); } } // no join member found, test the oldest join member if (null == directChatMember) { if (RoomMember.MEMBERSHIP_INVITE.equals(members.get(nextIndexSearch).membership)) { directChatMember = members.get(nextIndexSearch); } } } // last option: get the logged user if (null == directChatMember) { directChatMember = members.get(0); } chosenUserId = directChatMember.getUserId(); } else { chosenUserId = aParticipantUserId; } // search if there is an entry with the same user if (params.containsKey(chosenUserId)) { roomIdsList = new ArrayList<>(params.get(chosenUserId)); } roomIdsList.add(roomId); // update room list with the new room params.put(chosenUserId, roomIdsList); } else { // remove the current room from the direct chat list rooms if (null != store.getDirectChatRoomsDict()) { Collection<List<String>> listOfList = store.getDirectChatRoomsDict().values(); for (List<String> list : listOfList) { if (list.contains(roomId)) { list.remove(roomId); } } } else { // should not happen: if the room has to be removed, it means the room has been // previously detected as being part of the listOfList Log.e(LOG_TAG, "## toggleDirectChatRoom(): failed to remove a direct chat room (not seen as direct chat room)"); return; } } HashMap<String, Object> requestParams = new HashMap<>(); Collection<String> userIds = params.keySet(); for(String userId : userIds) { requestParams.put(userId, params.get(userId)); } mAccountDataRestClient.setAccountData(getMyUserId(), AccountDataRestClient.ACCOUNT_DATA_TYPE_DIRECT_MESSAGES, requestParams, callback); } } /** * For the value account_data with the rooms list passed in aRoomIdsListToAdd for a given user ID (aParticipantUserId)<br> * WARNING: this method must be used with care because it erases the account_data object. * @param aRoomParticipantUserIdList the couple direct chat rooms ID / user IDs * @param callback the asynchronous response callback */ public void forceDirectChatRoomValue(ArrayList<RoomIdsListRetroCompat> aRoomParticipantUserIdList, ApiCallback<Void> callback) { HashMap<String, List<String>> params = new HashMap<>(); ArrayList<String> roomIdsList; if(null != aRoomParticipantUserIdList) { for(RoomIdsListRetroCompat item: aRoomParticipantUserIdList) { if(params.containsKey(item.mParticipantUserId)) { roomIdsList = new ArrayList<>(params.get(item.mParticipantUserId)); roomIdsList.add(item.mRoomId); } else { roomIdsList = new ArrayList<>(); roomIdsList.add(item.mRoomId); } params.put(item.mParticipantUserId, roomIdsList); } HashMap<String, Object> requestParams = new HashMap<>(); Collection<String> userIds = params.keySet(); for(String userId : userIds) { requestParams.put(userId, params.get(userId)); } mAccountDataRestClient.setAccountData(getMyUserId(), AccountDataRestClient.ACCOUNT_DATA_TYPE_DIRECT_MESSAGES, requestParams, callback); } } /** * Update the account password * * @param oldPassword the former account password * @param newPassword the new account password * @param callback the callback */ public void updatePassword(String oldPassword, String newPassword, ApiCallback<Void> callback) { mProfileRestClient.updatePassword(getMyUserId(), oldPassword, newPassword, callback); } /** * Reset the password to a new one. * * @param newPassword the new password * @param threepid_creds the three pids. * @param callback the callback */ public void resetPassword(final String newPassword, final Map<String, String> threepid_creds, final ApiCallback<Void> callback) { mProfileRestClient.resetPassword(newPassword, threepid_creds, callback); } /** * Triggers a request to update the userId to ignore * @param userIds the userIds to ignoer * @param callback the callback */ private void updateUsers(ArrayList<String> userIds, ApiCallback<Void> callback) { HashMap<String, Object> ignoredUsersDict = new HashMap<>(); for (String userId : userIds) { ignoredUsersDict.put(userId, new ArrayList<>()); } HashMap<String, Object> params = new HashMap<>(); params.put(AccountDataRestClient.ACCOUNT_DATA_KEY_IGNORED_USERS, ignoredUsersDict); mAccountDataRestClient.setAccountData(getMyUserId(), AccountDataRestClient.ACCOUNT_DATA_TYPE_IGNORED_USER_LIST, params, callback); } /** * Tells if an user is in the ignored user ids list * @param userId the user id to test * @return true if the user is ignored */ public boolean isUserIgnored(String userId) { if (null != userId) { return getDataHandler().getIgnoredUserIds().indexOf(userId) >= 0; } return false; } /** * Ignore a list of users. * @param userIds the user ids list to ignore * @param callback the result callback */ public void ignoreUsers(ArrayList<String> userIds, ApiCallback<Void> callback) { List<String> curUserIdsToIgnore = getDataHandler().getIgnoredUserIds(); ArrayList<String> userIdsToIgnore = new ArrayList<>(getDataHandler().getIgnoredUserIds()); // something to add if ((null != userIds) && (userIds.size() > 0)) { // add the new one for (String userId : userIds) { if (userIdsToIgnore.indexOf(userId) < 0) { userIdsToIgnore.add(userId); } } // some items have been added if (curUserIdsToIgnore.size() != userIdsToIgnore.size()) { updateUsers(userIdsToIgnore, callback); } } } /** * Unignore a list of users. * @param userIds the user ids list to unignore * @param callback the result callback */ public void unIgnoreUsers(ArrayList<String> userIds, ApiCallback<Void> callback) { List<String> curUserIdsToIgnore = getDataHandler().getIgnoredUserIds(); ArrayList<String> userIdsToIgnore = new ArrayList<>(getDataHandler().getIgnoredUserIds()); // something to add if ((null != userIds) && (userIds.size() > 0)) { // add the new one for (String userId : userIds) { userIdsToIgnore.remove(userId); } // some items have been added if (curUserIdsToIgnore.size() != userIdsToIgnore.size()) { updateUsers(userIdsToIgnore, callback); } } } /** * @return the network receiver. */ public NetworkConnectivityReceiver getNetworkConnectivityReceiver() { return mNetworkConnectivityReceiver; } /** * Invalidate the access token, so that it can no longer be used for authorization. * @param context the application context * @param callback the callback success and failure callback */ public void logout(final Context context, final ApiCallback<Void> callback) { // Clear crypto data // For security and because it will be no more useful as we will get a new device id // on the next log in enableCrypto(false, null); mLoginRestClient.logout(new ApiCallback<JsonObject>() { @Override public void onSuccess(JsonObject info) { clear(context); if (null != callback) { callback.onSuccess(null); } } @Override public void onNetworkError(Exception e) { clear(context); if (null != callback) { callback.onNetworkError(e); } } @Override public void onMatrixError(MatrixError e) { clear(context); if (null != callback) { callback.onMatrixError(e); } } @Override public void onUnexpectedError(Exception e) { clear(context); if (null != callback) { callback.onUnexpectedError(e); } } }); } //============================================================================================================== // Crypto //============================================================================================================== /** * The module that manages E2E encryption. * Null if the feature is not enabled */ private MXCrypto mCrypto; /** * @return the crypto instance */ public MXCrypto getCrypto() { return mCrypto; } /** * @return true if the crypto is enabled */ public boolean isCryptoEnabled() { return null != mCrypto; } /** * enable encryption by default when launching the session */ private boolean mEnableCryptoWhenStartingMXSession = false; /** * Enable the crypto when initializing a new session. */ public void enableCryptoWhenStarting() { mEnableCryptoWhenStartingMXSession = true; } /** * When the encryption is toogled, the room summaries must be updated * to display the right messages. */ private void decryptRoomSummaries() { Collection<RoomSummary> summaries = getDataHandler().getStore().getSummaries(); for(RoomSummary summary :summaries) { mDataHandler.decryptEvent(summary.getLatestReceivedEvent(), null); } } /** * Enable / disable the crypto * @param cryptoEnabled true to enable the crypto */ public void enableCrypto(boolean cryptoEnabled, final ApiCallback<Void> callback) { if (cryptoEnabled != isCryptoEnabled()) { if (cryptoEnabled) { Log.d(LOG_TAG, "Crypto is enabled"); MXFileCryptoStore fileCryptoStore = new MXFileCryptoStore(); fileCryptoStore.initWithCredentials(mAppContent, mCredentials); fileCryptoStore.open(); mCrypto = new MXCrypto(this, fileCryptoStore); mCrypto.start(true, new ApiCallback<Void>() { @Override public void onSuccess(Void info) { decryptRoomSummaries(); if (null != callback) { callback.onSuccess(null); } } @Override public void onNetworkError(Exception e) { if (null != callback) { callback.onNetworkError(e); } } @Override public void onMatrixError(MatrixError e) { if (null != callback) { callback.onMatrixError(e); } } @Override public void onUnexpectedError(Exception e) { if (null != callback) { callback.onUnexpectedError(e); } } }); } else if (null != mCrypto) { Log.d(LOG_TAG, "Crypto is disabled"); IMXCryptoStore store = mCrypto.mCryptoStore; mCrypto.close(); store.deleteStore(); mCrypto = null; mDataHandler.setCrypto(null); decryptRoomSummaries(); if (null != callback) { callback.onSuccess(null); } } mDataHandler.setCrypto(mCrypto); } else { if (null != callback) { callback.onSuccess(null); } } } /** * Retrieves the devices list * @param callback the asynchronous callback */ public void getDevicesList(ApiCallback<DevicesListResponse> callback) { mCryptoRestClient.getDevices(callback); } /** * Set a device name. * @param deviceId the device id * @param deviceName the device name * @param callback the asynchronous callback */ public void setDeviceName(final String deviceId, final String deviceName, final ApiCallback<Void> callback) { mCryptoRestClient.setDeviceName(deviceId, deviceName, callback); } /** * Delete a device * @param deviceId the device id * @param password the passwoerd * @param callback the asynchronous callback. */ public void deleteDevice(final String deviceId, final String password, final ApiCallback<Void> callback) { DeleteDeviceParams dummyparams = new DeleteDeviceParams(); mCryptoRestClient.deleteDevice(deviceId, dummyparams, new ApiCallback<Void>() { @Override public void onSuccess(Void info) { // should never happen if (null != callback) { callback.onSuccess(null); } } @Override public void onNetworkError(Exception e) { if (null != callback) { callback.onNetworkError(e); } } @Override public void onMatrixError(MatrixError matrixError) { Log.d(LOG_TAG, "## checkNameAvailability(): The registration continues"); RegistrationFlowResponse registrationFlowResponse = null; // expected status code is 401 if ((null != matrixError.mStatus) && (matrixError.mStatus == 401)) { try { registrationFlowResponse = JsonUtils.toRegistrationFlowResponse(matrixError.mErrorBodyAsString); } catch (Exception castExcept) { Log.e(LOG_TAG, "## deleteDevice(): Received status 401 - Exception - JsonUtils.toRegistrationFlowResponse()"); } } else { Log.d(LOG_TAG, "## deleteDevice(): Received not expected status 401 ="+ matrixError.mStatus); } // check if the server response can be casted if (null != registrationFlowResponse) { DeleteDeviceParams params = new DeleteDeviceParams(); params.auth = new DeleteDeviceAuth(); params.auth.session = registrationFlowResponse.session; params.auth.type = "m.login.password"; params.auth.user = mCredentials.userId; params.auth.password = password; mCryptoRestClient.deleteDevice(deviceId, params, callback); } else { if (null != callback) { callback.onMatrixError(matrixError); } } } @Override public void onUnexpectedError(Exception e) { if (null != callback) { callback.onNetworkError(e); } } }); } /** * Tells if a string is a valid user Id. * @param anUserId the string to test * @return true if the string is a valid user id */ public static boolean isUserId(String anUserId) { return (null != anUserId) && PATTERN_CONTAIN_MATRIX_USER_IDENTIFIER.matcher(anUserId).matches(); } /** * Tells if a string is a valid room id. * @param aRoomId the string to test * @return true if the string is a valid room Id */ public static boolean isRoomId(String aRoomId) { return (null != aRoomId) && PATTERN_CONTAIN_MATRIX_ROOM_IDENTIFIER.matcher(aRoomId).matches(); } /** * Tells if a string is a valid room alias. * @param aRoomAlias the string to test * @return true if the string is a valid room alias. */ public static boolean isRoomAlias(String aRoomAlias) { return (null != aRoomAlias) && PATTERN_CONTAIN_MATRIX_ALIAS.matcher(aRoomAlias).matches(); } /** * Tells if a string is a valid message id. * @param aMessageId the string to test * @return true if the string is a valid message id. */ public static boolean isMessageId(String aMessageId) { return (null != aMessageId) && PATTERN_CONTAIN_MATRIX_MESSAGE_IDENTIFIER.matcher(aMessageId).matches(); } }