Bug 1295348 - Send/Handle push messages for send tab to device on Fennec. r?sebastian
MozReview-Commit-ID: 1NSMPLQdoXv
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/FxAccountClient.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/FxAccountClient.java
@@ -1,21 +1,24 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.background.fxa;
import org.mozilla.gecko.background.fxa.FxAccountClient20.AccountStatusResponse;
+import org.mozilla.gecko.background.fxa.FxAccountClient20.RecoveryEmailStatusResponse;
import org.mozilla.gecko.background.fxa.FxAccountClient20.RequestDelegate;
-import org.mozilla.gecko.background.fxa.FxAccountClient20.RecoveryEmailStatusResponse;
import org.mozilla.gecko.background.fxa.FxAccountClient20.TwoKeys;
import org.mozilla.gecko.fxa.FxAccountDevice;
import org.mozilla.gecko.sync.ExtendedJSONObject;
+import java.util.List;
+
public interface FxAccountClient {
public void accountStatus(String uid, RequestDelegate<AccountStatusResponse> requestDelegate);
public void recoveryEmailStatus(byte[] sessionToken, RequestDelegate<RecoveryEmailStatusResponse> requestDelegate);
public void keys(byte[] keyFetchToken, RequestDelegate<TwoKeys> requestDelegate);
public void sign(byte[] sessionToken, ExtendedJSONObject publicKey, long certificateDurationInMilliseconds, RequestDelegate<String> requestDelegate);
public void registerOrUpdateDevice(byte[] sessionToken, FxAccountDevice device, RequestDelegate<FxAccountDevice> requestDelegate);
public void deviceList(byte[] sessionToken, RequestDelegate<FxAccountDevice[]> requestDelegate);
+ public void notifyDevices(byte[] sessionToken, List<String> deviceIds, ExtendedJSONObject payload, Long TTL, RequestDelegate<ExtendedJSONObject> requestDelegate);
}
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/FxAccountClient20.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/FxAccountClient20.java
@@ -1,14 +1,16 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.background.fxa;
+import android.support.annotation.NonNull;
+
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientMalformedResponseException;
import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException;
import org.mozilla.gecko.fxa.FxAccountConstants;
import org.mozilla.gecko.Locales;
import org.mozilla.gecko.fxa.FxAccountDevice;
@@ -28,16 +30,17 @@ import java.io.UnsupportedEncodingExcept
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.HashMap;
+import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.Executor;
import javax.crypto.Mac;
import ch.boye.httpclientandroidlib.HttpEntity;
@@ -827,17 +830,16 @@ public class FxAccountClient20 implement
try {
HKDF.deriveMany(sessionToken, new byte[0], FxAccountUtils.KW("sessionToken"), tokenId, reqHMACKey, requestKey);
} catch (Exception e) {
invokeHandleError(delegate, e);
return;
}
final BaseResource resource;
- final ExtendedJSONObject body;
try {
resource = getBaseResource("account/devices");
} catch (URISyntaxException | UnsupportedEncodingException e) {
invokeHandleError(delegate, e);
return;
}
resource.delegate = new ResourceDelegate<FxAccountDevice[]>(resource, delegate, ResponseType.JSON_ARRAY, tokenId, reqHMACKey) {
@@ -853,9 +855,60 @@ public class FxAccountClient20 implement
} catch (Exception e) {
delegate.handleError(e);
}
}
};
resource.get();
}
+
+ @Override
+ public void notifyDevices(@NonNull byte[] sessionToken, @NonNull List<String> deviceIds, ExtendedJSONObject payload, Long TTL, RequestDelegate<ExtendedJSONObject> delegate) {
+ final byte[] tokenId = new byte[32];
+ final byte[] reqHMACKey = new byte[32];
+ final byte[] requestKey = new byte[32];
+ try {
+ HKDF.deriveMany(sessionToken, new byte[0], FxAccountUtils.KW("sessionToken"), tokenId, reqHMACKey, requestKey);
+ } catch (Exception e) {
+ invokeHandleError(delegate, e);
+ return;
+ }
+
+ final BaseResource resource;
+ final ExtendedJSONObject body = createNotifyDevicesBody(deviceIds, payload, TTL);
+ try {
+ resource = getBaseResource("account/devices/notify");
+ } catch (URISyntaxException | UnsupportedEncodingException e) {
+ invokeHandleError(delegate, e);
+ return;
+ }
+
+ resource.delegate = new ResourceDelegate<ExtendedJSONObject>(resource, delegate, ResponseType.JSON_OBJECT, tokenId, reqHMACKey) {
+ @Override
+ public void handleSuccess(int status, HttpResponse response, ExtendedJSONObject body) {
+ try {
+ delegate.handleSuccess(body);
+ } catch (Exception e) {
+ delegate.handleError(e);
+ }
+ }
+ };
+
+ post(resource, body);
+ }
+
+ @NonNull
+ @SuppressWarnings("unchecked")
+ private ExtendedJSONObject createNotifyDevicesBody(@NonNull List<String> deviceIds, ExtendedJSONObject payload, Long TTL) {
+ final ExtendedJSONObject body = new ExtendedJSONObject();
+ final JSONArray to = new JSONArray();
+ to.addAll(deviceIds);
+ body.put("to", to);
+ if (payload != null) {
+ body.put("payload", payload);
+ }
+ if (TTL != null) {
+ body.put("TTL", TTL);
+ }
+ return body;
+ }
}
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/FxAccountDeviceRegistrator.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/FxAccountDeviceRegistrator.java
@@ -14,19 +14,18 @@ import android.util.Log;
import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.background.fxa.FxAccountClient;
import org.mozilla.gecko.background.fxa.FxAccountClient20;
import org.mozilla.gecko.background.fxa.FxAccountClient20.AccountStatusResponse;
import org.mozilla.gecko.background.fxa.FxAccountClient20.RequestDelegate;
import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException;
import org.mozilla.gecko.background.fxa.FxAccountRemoteError;
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
+import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount.InvalidFxAState;
import org.mozilla.gecko.fxa.login.State;
-import org.mozilla.gecko.fxa.login.State.StateLabel;
-import org.mozilla.gecko.fxa.login.TokensAndKeysState;
import org.mozilla.gecko.sync.SharedPreferencesClientsDataDelegate;
import org.mozilla.gecko.util.BundleEventListener;
import org.mozilla.gecko.util.EventCallback;
import java.io.UnsupportedEncodingException;
import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
@@ -107,17 +106,21 @@ public class FxAccountDeviceRegistrator
}
private static void doFxaRegistration(final Context context, final Bundle subscription, final boolean allowRecursion) throws InvalidFxAState {
String pushCallback = subscription.getString("pushCallback");
String pushPublicKey = subscription.getString("pushPublicKey");
String pushAuthKey = subscription.getString("pushAuthKey");
final AndroidFxAccount fxAccount = AndroidFxAccount.fromContext(context);
- final byte[] sessionToken = getSessionToken(fxAccount);
+ if (fxAccount == null) {
+ Log.e(LOG_TAG, "AndroidFxAccount is null");
+ return;
+ }
+ final byte[] sessionToken = fxAccount.getSessionToken();
final FxAccountDevice device;
String deviceId = fxAccount.getDeviceId();
String clientName = getClientName(fxAccount, context);
if (TextUtils.isEmpty(deviceId)) {
Log.i(LOG_TAG, "Attempting registration for a new device");
device = FxAccountDevice.forRegister(clientName, "mobile", pushCallback, pushPublicKey, pushAuthKey);
} else {
Log.i(LOG_TAG, "Attempting registration for an existing device");
@@ -175,27 +178,16 @@ public class FxAccountDeviceRegistrator
new SharedPreferencesClientsDataDelegate(fxAccount.getSyncPrefs(), context);
return clientsDataDelegate.getClientName();
} catch (UnsupportedEncodingException | GeneralSecurityException e) {
Log.e(LOG_TAG, "Unable to get client name.", e);
return null;
}
}
- @Nullable
- private static byte[] getSessionToken(final AndroidFxAccount fxAccount) throws InvalidFxAState {
- State state = fxAccount.getState();
- StateLabel stateLabel = state.getStateLabel();
- if (stateLabel == StateLabel.Cohabiting || stateLabel == StateLabel.Married) {
- TokensAndKeysState tokensAndKeysState = (TokensAndKeysState) state;
- return tokensAndKeysState.getSessionToken();
- }
- throw new InvalidFxAState("Cannot get sessionToken: not in a TokensAndKeysState state");
- }
-
private static void handleTokenError(final FxAccountClientRemoteException error,
final FxAccountClient fxAccountClient,
final AndroidFxAccount fxAccount) {
Log.i(LOG_TAG, "Recovering from invalid token error: ", error);
logErrorAndResetDeviceRegistrationVersion(error, fxAccount);
fxAccountClient.accountStatus(fxAccount.getState().uid,
new RequestDelegate<AccountStatusResponse>() {
@Override
@@ -282,17 +274,9 @@ public class FxAccountDeviceRegistrator
// We have no choice but to use reflection here, sorry :(
Class<?> eventDispatcher = Class.forName("org.mozilla.gecko.EventDispatcher");
Method getInstance = eventDispatcher.getMethod("getInstance");
Object instance = getInstance.invoke(null);
Method registerBackgroundThreadListener = eventDispatcher.getMethod("registerBackgroundThreadListener",
BundleEventListener.class, String[].class);
registerBackgroundThreadListener.invoke(instance, this, new String[] { "FxAccountsPush:Subscribe:Response" });
}
-
- public static class InvalidFxAState extends Exception {
- private static final long serialVersionUID = -8537626959811195978L;
-
- public InvalidFxAState(String message) {
- super(message);
- }
- }
}
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/FxAccountPushHandler.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/FxAccountPushHandler.java
@@ -2,24 +2,28 @@ package org.mozilla.gecko.fxa;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.content.Context;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
+import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
public class FxAccountPushHandler {
private static final String LOG_TAG = "FxAccountPush";
private static final String COMMAND_DEVICE_DISCONNECTED = "fxaccounts:device_disconnected";
+ private static final String COMMAND_COLLECTION_CHANGED = "sync:collection_changed";
+
+ private static final String CLIENTS_COLLECTION = "clients";
// Forbid instantiation
private FxAccountPushHandler() {}
public static void handleFxAPushMessage(Context context, Bundle bundle) {
Log.i(LOG_TAG, "Handling FxA Push Message");
String rawMessage = bundle.getString("message");
JSONObject message = null;
@@ -40,25 +44,45 @@ public class FxAccountPushHandler {
}
try {
String command = message.getString("command");
JSONObject data = message.getJSONObject("data");
switch (command) {
case COMMAND_DEVICE_DISCONNECTED:
handleDeviceDisconnection(context, data);
break;
+ case COMMAND_COLLECTION_CHANGED:
+ handleCollectionChanged(context, data);
+ break;
default:
Log.d(LOG_TAG, "No handler defined for FxA Push command " + command);
break;
}
} catch (JSONException e) {
Log.e(LOG_TAG, "Error while handling FxA push notification", e);
}
}
+ private static void handleCollectionChanged(Context context, JSONObject data) throws JSONException {
+ JSONArray collections = data.getJSONArray("collections");
+ int len = collections.length();
+ for (int i = 0; i < len; i++) {
+ if (collections.getString(i).equals(CLIENTS_COLLECTION)) {
+ final Account account = FirefoxAccounts.getFirefoxAccount(context);
+ if (account == null) {
+ Log.e(LOG_TAG, "The account does not exist anymore");
+ return;
+ }
+ final AndroidFxAccount fxAccount = new AndroidFxAccount(context, account);
+ fxAccount.requestImmediateSync(new String[] { CLIENTS_COLLECTION }, null);
+ return;
+ }
+ }
+ }
+
private static void handleDeviceDisconnection(Context context, JSONObject data) throws JSONException {
final Account account = FirefoxAccounts.getFirefoxAccount(context);
if (account == null) {
Log.e(LOG_TAG, "The account does not exist anymore");
return;
}
final AndroidFxAccount fxAccount = new AndroidFxAccount(context, account);
if (!fxAccount.getDeviceId().equals(data.getString("id"))) {
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/authenticator/AndroidFxAccount.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/authenticator/AndroidFxAccount.java
@@ -25,16 +25,17 @@ import org.mozilla.gecko.background.comm
import org.mozilla.gecko.background.common.log.Logger;
import org.mozilla.gecko.background.fxa.FxAccountUtils;
import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.fxa.FirefoxAccounts;
import org.mozilla.gecko.fxa.FxAccountConstants;
import org.mozilla.gecko.fxa.login.State;
import org.mozilla.gecko.fxa.login.State.StateLabel;
import org.mozilla.gecko.fxa.login.StateFactory;
+import org.mozilla.gecko.fxa.login.TokensAndKeysState;
import org.mozilla.gecko.fxa.sync.FxAccountProfileService;
import org.mozilla.gecko.sync.ExtendedJSONObject;
import org.mozilla.gecko.sync.Utils;
import org.mozilla.gecko.sync.setup.Constants;
import org.mozilla.gecko.util.ThreadUtils;
import java.io.UnsupportedEncodingException;
import java.net.URISyntaxException;
@@ -602,16 +603,34 @@ public class AndroidFxAccount {
StateLabel stateLabel = StateLabel.valueOf(stateLabelString);
Logger.debug(LOG_TAG, "Account is in state " + stateLabel);
return StateFactory.fromJSONObject(stateLabel, new ExtendedJSONObject(stateString));
} catch (Exception e) {
throw new IllegalStateException("could not get state", e);
}
}
+ public byte[] getSessionToken() throws InvalidFxAState {
+ State state = getState();
+ StateLabel stateLabel = state.getStateLabel();
+ if (stateLabel == StateLabel.Cohabiting || stateLabel == StateLabel.Married) {
+ TokensAndKeysState tokensAndKeysState = (TokensAndKeysState) state;
+ return tokensAndKeysState.getSessionToken();
+ }
+ throw new InvalidFxAState("Cannot get sessionToken: not in a TokensAndKeysState state");
+ }
+
+ public static class InvalidFxAState extends Exception {
+ private static final long serialVersionUID = -8537626959811195978L;
+
+ public InvalidFxAState(String message) {
+ super(message);
+ }
+ }
+
/**
* <b>For debugging only!</b>
*/
public void dump() {
if (!FxAccountUtils.LOG_PERSONAL_INFORMATION) {
return;
}
ExtendedJSONObject o = toJSONObject();
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/SyncClientsEngineStage.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/stage/SyncClientsEngineStage.java
@@ -1,29 +1,37 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.sync.stage;
import android.accounts.Account;
import android.content.Context;
+import android.support.annotation.NonNull;
import android.text.TextUtils;
+import android.util.Log;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.mozilla.gecko.AppConstants;
import org.mozilla.gecko.background.common.log.Logger;
+import org.mozilla.gecko.background.fxa.FxAccountClient;
+import org.mozilla.gecko.background.fxa.FxAccountClient20;
+import org.mozilla.gecko.background.fxa.FxAccountClientException;
import org.mozilla.gecko.fxa.FirefoxAccounts;
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
import org.mozilla.gecko.sync.CommandProcessor;
import org.mozilla.gecko.sync.CommandProcessor.Command;
import org.mozilla.gecko.sync.CryptoRecord;
import org.mozilla.gecko.sync.ExtendedJSONObject;
import org.mozilla.gecko.sync.HTTPFailureException;
import org.mozilla.gecko.sync.NoCollectionKeysSetException;
@@ -49,28 +57,29 @@ import ch.boye.httpclientandroidlib.Http
public class SyncClientsEngineStage extends AbstractSessionManagingSyncStage {
private static final String LOG_TAG = "SyncClientsEngineStage";
public static final String COLLECTION_NAME = "clients";
public static final String STAGE_NAME = COLLECTION_NAME;
public static final int CLIENTS_TTL_REFRESH = 604800000; // 7 days in milliseconds.
public static final int MAX_UPLOAD_FAILURE_COUNT = 5;
+ public static final long NOTIFY_TAB_SENT_TTL_SECS = TimeUnit.SECONDS.convert(1L, TimeUnit.HOURS); // 1 hour
protected final ClientRecordFactory factory = new ClientRecordFactory();
protected ClientUploadDelegate clientUploadDelegate;
protected ClientDownloadDelegate clientDownloadDelegate;
// Be sure to use this safely via getClientsDatabaseAccessor/closeDataAccessor.
protected ClientsDatabaseAccessor db;
protected volatile boolean shouldWipe;
protected volatile boolean shouldUploadLocalRecord; // Set if, e.g., we received commands or need to refresh our version.
protected final AtomicInteger uploadAttemptsCount = new AtomicInteger();
- protected final List<ClientRecord> toUpload = new ArrayList<ClientRecord>();
+ protected final List<ClientRecord> modifiedClientsToUpload = new ArrayList<ClientRecord>();
protected int getClientsCount() {
return getClientsDatabaseAccessor().clientsCount();
}
protected synchronized ClientsDatabaseAccessor getClientsDatabaseAccessor() {
if (db == null) {
db = new ClientsDatabaseAccessor(session.getContext());
@@ -146,23 +155,90 @@ public class SyncClientsEngineStage exte
Logger.debug(LOG_TAG, "Server response asserts " + response.weaveRecords() + " records.");
// TODO: persist the response timestamp to know whether to download next time (Bug 726055).
clientUploadDelegate = new ClientUploadDelegate();
clientsDelegate.setClientsCount(clientsCount);
// If we upload remote records, checkAndUpload() will be called upon
// upload success in the delegate. Otherwise call checkAndUpload() now.
- if (toUpload.size() > 0) {
+ if (modifiedClientsToUpload.size() > 0) {
+ // modifiedClientsToUpload is cleared in uploadRemoteRecords, save what we need here
+ final List<String> devicesToNotify = new ArrayList<>();
+ for (ClientRecord record : modifiedClientsToUpload) {
+ if (!TextUtils.isEmpty(record.fxaDeviceId)) {
+ devicesToNotify.add(record.fxaDeviceId);
+ }
+ }
+
+ // This method is synchronous, there's no risk of notifying the clients
+ // before we actually uploaded the records
uploadRemoteRecords();
+
+ // Notify the clients who got their record written
+ notifyClients(devicesToNotify);
+
return;
}
checkAndUpload();
}
+ private void notifyClients(final List<String> devicesToNotify) {
+ final ExecutorService executor = Executors.newSingleThreadExecutor();
+ final Context context = session.getContext();
+ final Account account = FirefoxAccounts.getFirefoxAccount(context);
+ if (account == null) {
+ Log.e(LOG_TAG, "Can't notify other clients: no account");
+ return;
+ }
+ final AndroidFxAccount fxAccount = new AndroidFxAccount(context, account);
+ final ExtendedJSONObject payload = createNotifyDevicesPayload();
+
+ final byte[] sessionToken;
+ try {
+ sessionToken = fxAccount.getSessionToken();
+ } catch (AndroidFxAccount.InvalidFxAState invalidFxAState) {
+ Log.e(LOG_TAG, "Could not get session token", invalidFxAState);
+ return;
+ }
+
+ // API doc : https://github.com/mozilla/fxa-auth-server/blob/master/docs/api.md#post-v1accountdevicesnotify
+ final FxAccountClient fxAccountClient = new FxAccountClient20(fxAccount.getAccountServerURI(), executor);
+ fxAccountClient.notifyDevices(sessionToken, devicesToNotify, payload, NOTIFY_TAB_SENT_TTL_SECS, new FxAccountClient20.RequestDelegate<ExtendedJSONObject>() {
+ @Override
+ public void handleError(Exception e) {
+ Log.e(LOG_TAG, "Error while notifying devices", e);
+ }
+
+ @Override
+ public void handleFailure(FxAccountClientException.FxAccountClientRemoteException e) {
+ Log.e(LOG_TAG, "Error while notifying devices", e);
+ }
+
+ @Override
+ public void handleSuccess(ExtendedJSONObject result) {
+ Log.i(LOG_TAG, devicesToNotify.size() + " devices notified");
+ }
+ });
+ }
+
+ @NonNull
+ @SuppressWarnings("unchecked")
+ private ExtendedJSONObject createNotifyDevicesPayload() {
+ final ExtendedJSONObject payload = new ExtendedJSONObject();
+ payload.put("version", 1);
+ payload.put("command", "sync:collection_changed");
+ final ExtendedJSONObject data = new ExtendedJSONObject();
+ final JSONArray collections = new JSONArray();
+ collections.add("clients");
+ data.put("collections", collections);
+ payload.put("data", data);
+ return payload;
+ }
+
@Override
public void handleRequestFailure(SyncStorageResponse response) {
BaseResource.consumeEntity(response); // We don't need the response at all, and any exception handling shouldn't need the response body.
localAccountGUIDDownloaded = false;
try {
Logger.info(LOG_TAG, "Client upload failed. Aborting sync.");
session.abort(new HTTPFailureException(response), "Client download failed.");
@@ -285,17 +361,17 @@ public class SyncClientsEngineStage exte
// If upload failed because of `ifUnmodifiedSince` then there are new
// commands uploaded to our record. We must download and process them first.
if (!shouldUploadLocalRecord ||
statusCode == HttpStatus.SC_PRECONDITION_FAILED ||
uploadAttemptsCount.incrementAndGet() > MAX_UPLOAD_FAILURE_COUNT) {
Logger.debug(LOG_TAG, "Client upload failed. Aborting sync.");
if (!currentlyUploadingLocalRecord) {
- toUpload.clear(); // These will be redownloaded.
+ modifiedClientsToUpload.clear(); // These will be redownloaded.
}
BaseResource.consumeEntity(response); // The exception thrown should need the response body.
session.abort(new HTTPFailureException(response), "Client upload failed.");
return;
}
Logger.trace(LOG_TAG, "Retrying upload…");
// Preconditions:
// shouldUploadLocalRecord == true &&
@@ -469,41 +545,41 @@ public class SyncClientsEngineStage exte
for (Command command : commands) {
JSONObject jsonCommand = command.asJSONObject();
if (record.commands == null) {
record.commands = new JSONArray();
}
record.commands.add(jsonCommand);
}
- toUpload.add(record);
+ modifiedClientsToUpload.add(record);
}
@SuppressWarnings("unchecked")
protected void uploadRemoteRecords() {
- Logger.trace(LOG_TAG, "In uploadRemoteRecords. Uploading " + toUpload.size() + " records" );
+ Logger.trace(LOG_TAG, "In uploadRemoteRecords. Uploading " + modifiedClientsToUpload.size() + " records" );
- for (ClientRecord r : toUpload) {
+ for (ClientRecord r : modifiedClientsToUpload) {
Logger.trace(LOG_TAG, ">> Uploading record " + r.guid + ": " + r.name);
}
- if (toUpload.size() == 1) {
- ClientRecord record = toUpload.get(0);
+ if (modifiedClientsToUpload.size() == 1) {
+ ClientRecord record = modifiedClientsToUpload.get(0);
Logger.debug(LOG_TAG, "Only 1 remote record to upload.");
Logger.debug(LOG_TAG, "Record last modified: " + record.lastModified);
CryptoRecord cryptoRecord = encryptClientRecord(record);
if (cryptoRecord != null) {
clientUploadDelegate.setUploadDetails(false);
this.uploadClientRecord(cryptoRecord);
}
return;
}
JSONArray cryptoRecords = new JSONArray();
- for (ClientRecord record : toUpload) {
+ for (ClientRecord record : modifiedClientsToUpload) {
Logger.trace(LOG_TAG, "Record " + record.guid + " is being uploaded" );
CryptoRecord cryptoRecord = encryptClientRecord(record);
cryptoRecords.add(cryptoRecord.toJSONObject());
}
Logger.debug(LOG_TAG, "Uploading records: " + cryptoRecords.size());
clientUploadDelegate.setUploadDetails(false);
this.uploadClientRecords(cryptoRecords);
@@ -542,17 +618,17 @@ public class SyncClientsEngineStage exte
session.abort(e, encryptionFailure);
}
return null;
}
public void clearRecordsToUpload() {
try {
getClientsDatabaseAccessor().wipeCommandsTable();
- toUpload.clear();
+ modifiedClientsToUpload.clear();
} finally {
closeDataAccessor();
}
}
protected void downloadClientRecords() {
shouldWipe = true;
clientDownloadDelegate = makeClientDownloadDelegate();
--- a/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/net/test/TestClientsEngineStage.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/android/sync/net/test/TestClientsEngineStage.java
@@ -760,36 +760,36 @@ public class TestClientsEngineStage exte
public void testAddCommandsToUnversionedClient() throws NullCursorException {
db = new TestAddCommandsMockClientsDatabaseAccessor();
final ClientRecord remoteRecord = new ClientRecord();
remoteRecord.version = null;
final String expectedGUID = remoteRecord.guid;
this.addCommands(remoteRecord);
- assertEquals(1, toUpload.size());
+ assertEquals(1, modifiedClientsToUpload.size());
- final ClientRecord recordToUpload = toUpload.get(0);
+ final ClientRecord recordToUpload = modifiedClientsToUpload.get(0);
assertEquals(4, recordToUpload.commands.size());
assertEquals(expectedGUID, recordToUpload.guid);
assertEquals(null, recordToUpload.version);
}
@Test
public void testAddCommandsToVersionedClient() throws NullCursorException {
db = new TestAddCommandsMockClientsDatabaseAccessor();
final ClientRecord remoteRecord = new ClientRecord();
remoteRecord.version = "12a1";
final String expectedGUID = remoteRecord.guid;
this.addCommands(remoteRecord);
- assertEquals(1, toUpload.size());
+ assertEquals(1, modifiedClientsToUpload.size());
- final ClientRecord recordToUpload = toUpload.get(0);
+ final ClientRecord recordToUpload = modifiedClientsToUpload.get(0);
assertEquals(4, recordToUpload.commands.size());
assertEquals(expectedGUID, recordToUpload.guid);
assertEquals("12a1", recordToUpload.version);
}
@Test
public void testLastModifiedTimestamp() throws NullCursorException {
// If we uploaded a record a moment ago, we shouldn't upload another.
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/fxa/login/MockFxAccountClient.java
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/fxa/login/MockFxAccountClient.java
@@ -1,16 +1,17 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.gecko.fxa.login;
import android.text.TextUtils;
import org.mozilla.gecko.background.fxa.FxAccountClient;
+import org.mozilla.gecko.background.fxa.FxAccountClient20;
import org.mozilla.gecko.background.fxa.FxAccountClient20.AccountStatusResponse;
import org.mozilla.gecko.background.fxa.FxAccountClient20.RequestDelegate;
import org.mozilla.gecko.background.fxa.FxAccountClient20.RecoveryEmailStatusResponse;
import org.mozilla.gecko.background.fxa.FxAccountClient20.TwoKeys;
import org.mozilla.gecko.background.fxa.FxAccountClient20.LoginResponse;
import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException;
import org.mozilla.gecko.background.fxa.FxAccountRemoteError;
import org.mozilla.gecko.background.fxa.FxAccountUtils;
@@ -18,16 +19,17 @@ import org.mozilla.gecko.fxa.FxAccountDe
import org.mozilla.gecko.browserid.MockMyIDTokenFactory;
import org.mozilla.gecko.browserid.RSACryptoImplementation;
import org.mozilla.gecko.sync.ExtendedJSONObject;
import org.mozilla.gecko.sync.Utils;
import java.io.UnsupportedEncodingException;
import java.util.Collection;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.UUID;
import ch.boye.httpclientandroidlib.HttpStatus;
import ch.boye.httpclientandroidlib.ProtocolVersion;
import ch.boye.httpclientandroidlib.entity.StringEntity;
import ch.boye.httpclientandroidlib.message.BasicHttpResponse;
@@ -211,9 +213,14 @@ public class MockFxAccountClient impleme
if (!user.verified) {
handleFailure(requestDelegate, HttpStatus.SC_BAD_REQUEST, FxAccountRemoteError.ATTEMPT_TO_OPERATE_ON_AN_UNVERIFIED_ACCOUNT, "user is unverified");
return;
}
Collection<FxAccountDevice> devices = user.devices.values();
FxAccountDevice[] devicesArray = devices.toArray(new FxAccountDevice[devices.size()]);
requestDelegate.handleSuccess(devicesArray);
}
+
+ @Override
+ public void notifyDevices(byte[] sessionToken, List<String> deviceIds, ExtendedJSONObject payload, Long TTL, RequestDelegate<ExtendedJSONObject> requestDelegate) {
+ requestDelegate.handleSuccess(new ExtendedJSONObject());
+ }
}