diff --git 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 index 3807cb277272..adc8ecb9de8a 100644 --- 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 @@ -11,8 +11,6 @@ import org.mozilla.gecko.background.fxa.FxAccountClient20.TwoKeys; import org.mozilla.gecko.fxa.devices.FxAccountDevice; import org.mozilla.gecko.sync.ExtendedJSONObject; -import java.util.List; - public interface FxAccountClient { public void accountStatus(String uid, RequestDelegate requestDelegate); public void recoveryEmailStatus(byte[] sessionToken, RequestDelegate requestDelegate); @@ -21,5 +19,5 @@ public interface FxAccountClient { public void registerOrUpdateDevice(byte[] sessionToken, FxAccountDevice device, RequestDelegate requestDelegate); public void destroyDevice(byte[] sessionToken, String deviceId, RequestDelegate requestDelegate); public void deviceList(byte[] sessionToken, RequestDelegate requestDelegate); - public void notifyDevices(byte[] sessionToken, List deviceIds, ExtendedJSONObject payload, Long TTL, RequestDelegate requestDelegate); + public void notifyDevices(byte[] sessionToken, ExtendedJSONObject body, RequestDelegate delegate); } diff --git 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 index b11df5177e75..e35fca9e045a 100644 --- 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 @@ -35,7 +35,6 @@ 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; @@ -898,7 +897,7 @@ public class FxAccountClient20 implements FxAccountClient { } @Override - public void notifyDevices(@NonNull byte[] sessionToken, @NonNull List deviceIds, ExtendedJSONObject payload, Long TTL, RequestDelegate delegate) { + public void notifyDevices(@NonNull byte[] sessionToken, ExtendedJSONObject body, RequestDelegate delegate) { final byte[] tokenId = new byte[32]; final byte[] reqHMACKey = new byte[32]; final byte[] requestKey = new byte[32]; @@ -910,7 +909,6 @@ public class FxAccountClient20 implements FxAccountClient { } final BaseResource resource; - final ExtendedJSONObject body = createNotifyDevicesBody(deviceIds, payload, TTL); try { resource = getBaseResource("account/devices/notify"); } catch (URISyntaxException | UnsupportedEncodingException e) { @@ -931,20 +929,4 @@ public class FxAccountClient20 implements FxAccountClient { post(resource, body); } - - @NonNull - @SuppressWarnings("unchecked") - private ExtendedJSONObject createNotifyDevicesBody(@NonNull List 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; - } } diff --git 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 index e66db1d21aa4..7951a2ce2325 100644 --- 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 @@ -128,6 +128,9 @@ public class SyncClientsEngineStage extends AbstractSessionManagingSyncStage { @Override public void handleRequestSuccess(SyncStorageResponse response) { + final Context context = session.getContext(); + final Account account = FirefoxAccounts.getFirefoxAccount(context); + final AndroidFxAccount fxAccount = new AndroidFxAccount(context, account); // Hang onto the server's last modified timestamp to use // in X-If-Unmodified-Since for upload. @@ -139,9 +142,11 @@ public class SyncClientsEngineStage extends AbstractSessionManagingSyncStage { // If we successfully downloaded all records but ours was not one of them // then reset the timestamp. + boolean isFirstLocalClientRecordUpload = false; if (!localAccountGUIDDownloaded) { Logger.info(LOG_TAG, "Local client GUID does not exist on the server. Upload timestamp will be reset."); session.config.persistServerClientRecordTimestamp(0); + isFirstLocalClientRecordUpload = true; } localAccountGUIDDownloaded = false; @@ -176,25 +181,31 @@ public class SyncClientsEngineStage extends AbstractSessionManagingSyncStage { // before we actually uploaded the records uploadRemoteRecords(); - // Notify the clients who got their record written - notifyClients(devicesToNotify); + // We will send a push notification later anyway. + if (!isFirstLocalClientRecordUpload) { + // Notify the clients who got their record written + notifyClients(fxAccount, devicesToNotify); + } return; } checkAndUpload(); + if (isFirstLocalClientRecordUpload) { + notifyAllClients(fxAccount); + } } - private void notifyClients(final List 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(); + private void notifyClients(@NonNull AndroidFxAccount fxAccount, @NonNull List devicesToNotify) { + final ExtendedJSONObject body = createNotifyClientsBody(devicesToNotify); + notifyClientsHelper(fxAccount, body); + } + private void notifyAllClients(@NonNull AndroidFxAccount fxAccount) { + final ExtendedJSONObject body = createNotifyAllClientsBody(fxAccount.getDeviceId()); + notifyClientsHelper(fxAccount, body); + } + + private void notifyClientsHelper(@NonNull AndroidFxAccount fxAccount, @NonNull ExtendedJSONObject body) { final byte[] sessionToken; try { sessionToken = fxAccount.getState().getSessionToken(); @@ -205,9 +216,10 @@ public class SyncClientsEngineStage extends AbstractSessionManagingSyncStage { return; } + final ExecutorService executor = Executors.newSingleThreadExecutor(); // 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() { + fxAccountClient.notifyDevices(sessionToken, body, new FxAccountClient20.RequestDelegate() { @Override public void handleError(Exception e) { Log.e(LOG_TAG, "Error while notifying devices", e); @@ -220,11 +232,39 @@ public class SyncClientsEngineStage extends AbstractSessionManagingSyncStage { @Override public void handleSuccess(ExtendedJSONObject result) { - Log.i(LOG_TAG, devicesToNotify.size() + " devices notified"); + Log.i(LOG_TAG, "Devices notified"); } }); } + @NonNull + @SuppressWarnings("unchecked") + private ExtendedJSONObject createNotifyClientsBody(@NonNull List devicesToNotify) { + final ExtendedJSONObject body = new ExtendedJSONObject(); + final JSONArray to = new JSONArray(); + to.addAll(devicesToNotify); + body.put("to", to); + createNotifyClientsHelper(body); + return body; + } + + @NonNull + @SuppressWarnings("unchecked") + private ExtendedJSONObject createNotifyAllClientsBody(@NonNull String localFxADeviceId) { + final ExtendedJSONObject body = new ExtendedJSONObject(); + body.put("to", "all"); + final JSONArray excluded = new JSONArray(); + excluded.add(localFxADeviceId); + body.put("excluded", excluded); + createNotifyClientsHelper(body); + return body; + } + + private void createNotifyClientsHelper(ExtendedJSONObject body) { + body.put("payload", createNotifyDevicesPayload()); + body.put("TTL", NOTIFY_TAB_SENT_TTL_SECS); + } + @NonNull @SuppressWarnings("unchecked") private ExtendedJSONObject createNotifyDevicesPayload() { diff --git 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 index 806568dc672f..a84ff974ab3b 100644 --- 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 @@ -6,6 +6,7 @@ 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; @@ -239,7 +240,7 @@ public class MockFxAccountClient implements FxAccountClient { } @Override - public void notifyDevices(byte[] sessionToken, List deviceIds, ExtendedJSONObject payload, Long TTL, RequestDelegate requestDelegate) { + public void notifyDevices(byte[] sessionToken, ExtendedJSONObject body, RequestDelegate requestDelegate) { requestDelegate.handleSuccess(new ExtendedJSONObject()); } }