/* * Copyright 2016 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.rest.client; import android.text.TextUtils; import org.matrix.androidsdk.rest.model.KeyChangesResponse; import org.matrix.androidsdk.util.Log; import org.matrix.androidsdk.HomeserverConnectionConfig; import org.matrix.androidsdk.RestClient; import org.matrix.androidsdk.crypto.data.MXKey; import org.matrix.androidsdk.crypto.data.MXUsersDevicesMap; import org.matrix.androidsdk.rest.api.CryptoApi; import org.matrix.androidsdk.rest.callback.ApiCallback; import org.matrix.androidsdk.rest.callback.RestAdapterCallback; import org.matrix.androidsdk.rest.model.DeleteDeviceParams; import org.matrix.androidsdk.rest.model.DevicesListResponse; import org.matrix.androidsdk.rest.model.crypto.KeysClaimResponse; import org.matrix.androidsdk.rest.model.crypto.KeysQueryResponse; import org.matrix.androidsdk.rest.model.crypto.KeysUploadResponse; import org.matrix.androidsdk.util.JsonUtils; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Random; import retrofit.Callback; import retrofit.client.Response; import retrofit.http.Body; import retrofit.http.PUT; import retrofit.http.Path; public class CryptoRestClient extends RestClient<CryptoApi> { private static final String LOG_TAG = "CryptoRestClient"; /** * {@inheritDoc} */ public CryptoRestClient(HomeserverConnectionConfig hsConfig) { super(hsConfig, CryptoApi.class, URI_API_PREFIX_PATH_UNSTABLE, false, false); } /** * Upload device and/or one-time keys. * @param deviceKeys the device keys to send. * @param oneTimeKeys the one-time keys to send. * @param deviceId he explicit device_id to use for upload (default is to use the same as that used during auth). * @param callback the asynchronous callback */ public void uploadKeys(final Map<String, Object> deviceKeys, final Map<String, Object> oneTimeKeys, final String deviceId, final ApiCallback<KeysUploadResponse> callback) { final String description = "uploadKeys"; String encodedDeviceId = JsonUtils.convertToUTF8(deviceId); HashMap<String, Object> params = new HashMap<>(); if (null != deviceKeys) { params.put("device_keys", deviceKeys); } if (null != oneTimeKeys) { params.put("one_time_keys", oneTimeKeys); } if (!TextUtils.isEmpty(encodedDeviceId)) { mApi.uploadKeys(encodedDeviceId, params, new RestAdapterCallback<KeysUploadResponse>(description, null, callback, new RestAdapterCallback.RequestRetryCallBack() { @Override public void onRetry() { try { uploadKeys(deviceKeys, oneTimeKeys, deviceId, callback); } catch (Exception e) { Log.e(LOG_TAG, "resend uploadKeys : failed " + e.getMessage()); } } })); } else { mApi.uploadKeys(params, new RestAdapterCallback<KeysUploadResponse>(description, null, callback, new RestAdapterCallback.RequestRetryCallBack() { @Override public void onRetry() { try { uploadKeys(deviceKeys, oneTimeKeys, deviceId, callback); } catch (Exception e) { Log.e(LOG_TAG, "resend uploadKeys : failed " + e.getMessage()); } } })); } } /** * Download device keys. * @param userIds list of users to get keys for. * @param token the up-to token * @param callback the asynchronous callback */ public void downloadKeysForUsers(final List<String> userIds, final String token, final ApiCallback<KeysQueryResponse> callback) { final String description = "downloadKeysForUsers"; HashMap<String, Map<String, Object>> downloadQuery = new HashMap<>(); if (null != userIds) { for(String userId : userIds) { downloadQuery.put(userId, new HashMap<String, Object>()); } } HashMap<String, Object> parameters = new HashMap<>(); parameters.put("device_keys", downloadQuery); if (!TextUtils.isEmpty(token)) { parameters.put("token", token); } mApi.downloadKeysForUsers(parameters, new RestAdapterCallback<KeysQueryResponse>(description, mUnsentEventsManager, callback, new RestAdapterCallback.RequestRetryCallBack() { @Override public void onRetry() { try { downloadKeysForUsers(userIds, token, callback); } catch (Exception e) { Log.e(LOG_TAG, "resend downloadKeysForUsers : failed " + e.getMessage()); } } })); } /** * Claim one-time keys. * @param usersDevicesKeyTypesMap a list of users, devices and key types to retrieve keys for. * @param callback the asynchronous callback */ public void claimOneTimeKeysForUsersDevices(final MXUsersDevicesMap<String> usersDevicesKeyTypesMap , final ApiCallback<MXUsersDevicesMap<MXKey>> callback) { final String description = "claimOneTimeKeysForUsersDevices"; HashMap<String, Object> params = new HashMap<>(); params.put("one_time_keys", usersDevicesKeyTypesMap.getMap()); mApi.claimOneTimeKeysForUsersDevices(params, new RestAdapterCallback<KeysClaimResponse>(description, mUnsentEventsManager, callback, new RestAdapterCallback.RequestRetryCallBack() { @Override public void onRetry() { try { claimOneTimeKeysForUsersDevices(usersDevicesKeyTypesMap, callback); } catch (Exception e) { Log.e(LOG_TAG, "resend claimOneTimeKeysForUsersDevices : failed " + e.getMessage()); } } }) { @Override public void success(KeysClaimResponse keysClaimResponse, Response response) { onEventSent(); HashMap<String, Map<String, MXKey>> map = new HashMap(); if (null != keysClaimResponse.oneTimeKeys) { for(String userId : keysClaimResponse.oneTimeKeys.keySet()) { Map<String, Map<String, Map<String, Object>>> mapByUserId = keysClaimResponse.oneTimeKeys.get(userId); HashMap<String, MXKey> keysMap = new HashMap<>(); for(String deviceId : mapByUserId.keySet()) { try { keysMap.put(deviceId, new MXKey(mapByUserId.get(deviceId))); } catch (Exception e) { Log.e(LOG_TAG, "## claimOneTimeKeysForUsersDevices : fail to create a MXKey " + e.getMessage()); } } if (keysMap.size() != 0) { map.put(userId, keysMap); } } } callback.onSuccess(new MXUsersDevicesMap<>(map)); } }); } /** * Send an event to a specific list of devices * @param eventType the type of event to send * @param contentMap content to send. Map from user_id to device_id to content dictionary. * @param callback the asynchronous callback. */ public void sendToDevice(final String eventType, final MXUsersDevicesMap<Map<String, Object>> contentMap, final ApiCallback<Void> callback) { final String description = "sendToDevice " + eventType; HashMap<String, Object> content = new HashMap<>(); content.put("messages", contentMap.getMap()); Random rand = new Random(); mApi.sendToDevice(eventType, rand.nextInt(Integer.MAX_VALUE), content, new RestAdapterCallback<Void>(description, null, callback, new RestAdapterCallback.RequestRetryCallBack() { @Override public void onRetry() { sendToDevice(eventType, contentMap, callback); } })); } /** * Retrieves the devices informaty * @param callback the asynchronous callback. */ public void getDevices(final ApiCallback<DevicesListResponse> callback) { final String description = "getDevicesListInfo"; mApi.getDevices(new RestAdapterCallback<DevicesListResponse>(description, mUnsentEventsManager, callback, new RestAdapterCallback.RequestRetryCallBack() { @Override public void onRetry() { try { getDevices(callback); } catch (Exception e) { Log.e(LOG_TAG, "resend getDevices : failed " + e.getMessage()); } } })); } /** * Delete a device. * @param deviceId the device id * @param params the deletion parameters * @param callback the asynchronous callback */ public void deleteDevice(final String deviceId, final DeleteDeviceParams params, final ApiCallback<Void> callback) { final String description = "deleteDevice"; mApi.deleteDevice(deviceId, params, new RestAdapterCallback<Void>(description, mUnsentEventsManager, callback, new RestAdapterCallback.RequestRetryCallBack() { @Override public void onRetry() { try { deleteDevice(deviceId, params, callback); } catch (Exception e) { Log.e(LOG_TAG, "resend deleteDevice : failed " + e.getMessage()); } } })); } /** * 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) { final String description = "setDeviceName"; HashMap<String, String> params = new HashMap<>(); params.put("display_name", TextUtils.isEmpty(deviceName) ? "" : deviceName); mApi.updateDeviceInfo(deviceId, params, new RestAdapterCallback<Void>(description, mUnsentEventsManager, callback, new RestAdapterCallback.RequestRetryCallBack() { @Override public void onRetry() { try { setDeviceName(deviceId, deviceName, callback); } catch (Exception e) { Log.e(LOG_TAG, "resend setDeviceName : failed " + e.getMessage()); } } })); } /** * Get the update devices list from two sync token. * @param from the start token. * @param to the up-to token. * @param callback the asynchronous callback */ public void getKeyChanges(final String from, final String to, final ApiCallback<KeyChangesResponse> callback) { final String description = "getKeyChanges"; mApi.getKeyChanges(from, to, new RestAdapterCallback<KeyChangesResponse>(description, mUnsentEventsManager, callback, new RestAdapterCallback.RequestRetryCallBack() { @Override public void onRetry() { try { getKeyChanges(from, to, callback); } catch (Exception e) { Log.e(LOG_TAG, "resend getKeyChanges : failed " + e.getMessage()); } } })); } }