Bug 1351805 part 3 - Refresh the remote devices list on Married/Engaged states. r=Grisha

MozReview-Commit-ID: 1Ktbtlzc1fI

--HG--
extra : rebase_source : 2d52926ee1ba8511a32b5a9cfdc13cd04ef4bbb8
This commit is contained in:
Edouard Oger 2017-04-19 17:45:49 -04:00
Родитель c48ef3d269
Коммит dd3f4bde18
9 изменённых файлов: 376 добавлений и 39 удалений

Просмотреть файл

@ -832,6 +832,7 @@ sync_java_files = [TOPSRCDIR + '/mobile/android/services/src/main/java/org/mozil
'fxa/authenticator/FxAccountLoginException.java', 'fxa/authenticator/FxAccountLoginException.java',
'fxa/authenticator/FxADefaultLoginStateMachineDelegate.java', 'fxa/authenticator/FxADefaultLoginStateMachineDelegate.java',
'fxa/devices/FxAccountDevice.java', 'fxa/devices/FxAccountDevice.java',
'fxa/devices/FxAccountDeviceListUpdater.java',
'fxa/devices/FxAccountDeviceRegistrator.java', 'fxa/devices/FxAccountDeviceRegistrator.java',
'fxa/FirefoxAccounts.java', 'fxa/FirefoxAccounts.java',
'fxa/FxAccountConstants.java', 'fxa/FxAccountConstants.java',

Просмотреть файл

@ -805,16 +805,15 @@ public class AndroidFxAccount {
}); });
} }
@SuppressWarnings("unchecked") private long getUserDataLong(String key, long defaultValue) {
private <T extends Number> T getUserDataNumber(String key, T defaultValue) {
final String numStr = accountManager.getUserData(account, key); final String numStr = accountManager.getUserData(account, key);
if (TextUtils.isEmpty(numStr)) { if (TextUtils.isEmpty(numStr)) {
return defaultValue; return defaultValue;
} }
try { try {
return (T) NumberFormat.getInstance().parse(numStr); return Long.parseLong(key);
} catch (ParseException e) { } catch (NumberFormatException e) {
Logger.warn(LOG_TAG, "Couldn't parse " + key + "; defaulting to 0L.", e); Logger.warn(LOG_TAG, "Couldn't parse " + key + "; defaulting to " + defaultValue, e);
return defaultValue; return defaultValue;
} }
} }
@ -825,19 +824,28 @@ public class AndroidFxAccount {
} }
public synchronized int getDeviceRegistrationVersion() { public synchronized int getDeviceRegistrationVersion() {
return getUserDataNumber(ACCOUNT_KEY_DEVICE_REGISTRATION_VERSION, 0); final String numStr = accountManager.getUserData(account, ACCOUNT_KEY_DEVICE_REGISTRATION_VERSION);
if (TextUtils.isEmpty(numStr)) {
return 0;
}
try {
return Integer.parseInt(numStr);
} catch (NumberFormatException e) {
Logger.warn(LOG_TAG, "Couldn't parse ACCOUNT_KEY_DEVICE_REGISTRATION_VERSION; defaulting to 0", e);
return 0;
}
} }
public synchronized long getDeviceRegistrationTimestamp() { public synchronized long getDeviceRegistrationTimestamp() {
return getUserDataNumber(ACCOUNT_KEY_DEVICE_REGISTRATION_TIMESTAMP, 0L); return getUserDataLong(ACCOUNT_KEY_DEVICE_REGISTRATION_TIMESTAMP, 0L);
} }
public synchronized long getDevicePushRegistrationError() { public synchronized long getDevicePushRegistrationError() {
return getUserDataNumber(ACCOUNT_KEY_DEVICE_PUSH_REGISTRATION_ERROR, 0L); return getUserDataLong(ACCOUNT_KEY_DEVICE_PUSH_REGISTRATION_ERROR, 0L);
} }
public synchronized long getDevicePushRegistrationErrorTime() { public synchronized long getDevicePushRegistrationErrorTime() {
return getUserDataNumber(ACCOUNT_KEY_DEVICE_PUSH_REGISTRATION_ERROR_TIME, 0L); return getUserDataLong(ACCOUNT_KEY_DEVICE_PUSH_REGISTRATION_ERROR_TIME, 0L);
} }
public synchronized void setDeviceId(String id) { public synchronized void setDeviceId(String id) {

Просмотреть файл

@ -8,42 +8,48 @@ import org.mozilla.gecko.sync.ExtendedJSONObject;
public class FxAccountDevice { public class FxAccountDevice {
public static final String JSON_KEY_NAME = "name"; private static final String JSON_KEY_NAME = "name";
public static final String JSON_KEY_ID = "id"; private static final String JSON_KEY_ID = "id";
public static final String JSON_KEY_TYPE = "type"; private static final String JSON_KEY_TYPE = "type";
public static final String JSON_KEY_ISCURRENTDEVICE = "isCurrentDevice"; private static final String JSON_KEY_ISCURRENTDEVICE = "isCurrentDevice";
public static final String JSON_KEY_PUSH_CALLBACK = "pushCallback"; private static final String JSON_KEY_PUSH_CALLBACK = "pushCallback";
public static final String JSON_KEY_PUSH_PUBLICKEY = "pushPublicKey"; private static final String JSON_KEY_PUSH_PUBLICKEY = "pushPublicKey";
public static final String JSON_KEY_PUSH_AUTHKEY = "pushAuthKey"; private static final String JSON_KEY_PUSH_AUTHKEY = "pushAuthKey";
private static final String JSON_LAST_ACCESS_TIME = "lastAccessTime";
public final String id; public final String id;
public final String name; public final String name;
public final String type; public final String type;
public final Boolean isCurrentDevice; public final Boolean isCurrentDevice;
public final Long lastAccessTime;
public final String pushCallback; public final String pushCallback;
public final String pushPublicKey; public final String pushPublicKey;
public final String pushAuthKey; public final String pushAuthKey;
public FxAccountDevice(String name, String id, String type, Boolean isCurrentDevice, public FxAccountDevice(String name, String id, String type, Boolean isCurrentDevice,
String pushCallback, String pushPublicKey, String pushAuthKey) { Long lastAccessTime, String pushCallback, String pushPublicKey,
String pushAuthKey) {
this.name = name; this.name = name;
this.id = id; this.id = id;
this.type = type; this.type = type;
this.isCurrentDevice = isCurrentDevice; this.isCurrentDevice = isCurrentDevice;
this.lastAccessTime = lastAccessTime;
this.pushCallback = pushCallback; this.pushCallback = pushCallback;
this.pushPublicKey = pushPublicKey; this.pushPublicKey = pushPublicKey;
this.pushAuthKey = pushAuthKey; this.pushAuthKey = pushAuthKey;
} }
public static FxAccountDevice fromJson(ExtendedJSONObject json) { public static FxAccountDevice fromJson(ExtendedJSONObject json) {
String name = json.getString(JSON_KEY_NAME); final String name = json.getString(JSON_KEY_NAME);
String id = json.getString(JSON_KEY_ID); final String id = json.getString(JSON_KEY_ID);
String type = json.getString(JSON_KEY_TYPE); final String type = json.getString(JSON_KEY_TYPE);
Boolean isCurrentDevice = json.getBoolean(JSON_KEY_ISCURRENTDEVICE); final Boolean isCurrentDevice = json.getBoolean(JSON_KEY_ISCURRENTDEVICE);
String pushCallback = json.getString(JSON_KEY_PUSH_CALLBACK); final Long lastAccessTime = json.getLong(JSON_LAST_ACCESS_TIME);
String pushPublicKey = json.getString(JSON_KEY_PUSH_PUBLICKEY); final String pushCallback = json.getString(JSON_KEY_PUSH_CALLBACK);
String pushAuthKey = json.getString(JSON_KEY_PUSH_AUTHKEY); final String pushPublicKey = json.getString(JSON_KEY_PUSH_PUBLICKEY);
return new FxAccountDevice(name, id, type, isCurrentDevice, pushCallback, pushPublicKey, pushAuthKey); final String pushAuthKey = json.getString(JSON_KEY_PUSH_AUTHKEY);
return new FxAccountDevice(name, id, type, isCurrentDevice, lastAccessTime, pushCallback,
pushPublicKey, pushAuthKey);
} }
public ExtendedJSONObject toJson() { public ExtendedJSONObject toJson() {
@ -73,7 +79,6 @@ public class FxAccountDevice {
private String id; private String id;
private String name; private String name;
private String type; private String type;
private Boolean isCurrentDevice;
private String pushCallback; private String pushCallback;
private String pushPublicKey; private String pushPublicKey;
private String pushAuthKey; private String pushAuthKey;
@ -90,10 +95,6 @@ public class FxAccountDevice {
this.type = type; this.type = type;
} }
public void isCurrentDevice() {
this.isCurrentDevice = Boolean.TRUE;
}
public void pushCallback(String pushCallback) { public void pushCallback(String pushCallback) {
this.pushCallback = pushCallback; this.pushCallback = pushCallback;
} }
@ -107,7 +108,7 @@ public class FxAccountDevice {
} }
public FxAccountDevice build() { public FxAccountDevice build() {
return new FxAccountDevice(this.name, this.id, this.type, this.isCurrentDevice, return new FxAccountDevice(this.name, this.id, this.type, null, null,
this.pushCallback, this.pushPublicKey, this.pushAuthKey); this.pushCallback, this.pushPublicKey, this.pushAuthKey);
} }
} }

Просмотреть файл

@ -0,0 +1,111 @@
/* 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.fxa.devices;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.VisibleForTesting;
import android.util.Log;
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.db.BrowserContract;
import org.mozilla.gecko.db.BrowserContract.RemoteDevices;
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
import org.mozilla.gecko.fxa.login.State;
import java.util.concurrent.Executor;
public class FxAccountDeviceListUpdater implements FxAccountClient20.RequestDelegate<FxAccountDevice[]> {
private static final String LOG_TAG = "FxADeviceListUpdater";
private final AndroidFxAccount fxAccount;
private final ContentResolver contentResolver;
public FxAccountDeviceListUpdater(final AndroidFxAccount fxAccount, final ContentResolver cr) {
this.fxAccount = fxAccount;
this.contentResolver = cr;
}
@Override
public void handleSuccess(final FxAccountDevice[] result) {
final Uri uri = RemoteDevices.CONTENT_URI
.buildUpon()
.appendQueryParameter(BrowserContract.PARAM_PROFILE, fxAccount.getProfile())
.build();
final Bundle valuesBundle = new Bundle();
final ContentValues[] insertValues = new ContentValues[result.length];
final long now = System.currentTimeMillis();
for (int i = 0; i < result.length; i++) {
final FxAccountDevice fxADevice = result[i];
final ContentValues deviceValues = new ContentValues();
deviceValues.put(RemoteDevices.GUID, fxADevice.id);
deviceValues.put(RemoteDevices.TYPE, fxADevice.type);
deviceValues.put(RemoteDevices.NAME, fxADevice.name);
deviceValues.put(RemoteDevices.IS_CURRENT_DEVICE, fxADevice.isCurrentDevice);
deviceValues.put(RemoteDevices.DATE_CREATED, now);
deviceValues.put(RemoteDevices.DATE_MODIFIED, now);
// TODO: Remove that line once FxA sends lastAccessTime all the time.
final Long lastAccessTime = fxADevice.lastAccessTime != null ? fxADevice.lastAccessTime : 0;
deviceValues.put(RemoteDevices.LAST_ACCESS_TIME, lastAccessTime);
insertValues[i] = deviceValues;
}
valuesBundle.putParcelableArray(BrowserContract.METHOD_PARAM_DATA, insertValues);
try {
contentResolver.call(uri, BrowserContract.METHOD_REPLACE_REMOTE_CLIENTS, uri.toString(),
valuesBundle);
Log.i(LOG_TAG, "FxA Device list update done.");
} catch (Exception e) {
Log.e(LOG_TAG, "Error persisting the new remote device list.", e);
}
}
@Override
public void handleError(Exception e) {
onError(e);
}
@Override
public void handleFailure(FxAccountClientException.FxAccountClientRemoteException e) {
onError(e);
}
private void onError(final Exception e) {
Log.e(LOG_TAG, "Error while getting the FxA device list.", e);
}
@VisibleForTesting
FxAccountClient getSynchronousFxaClient() {
return new FxAccountClient20(fxAccount.getAccountServerURI(),
// Current thread executor :)
new Executor() {
@Override
public void execute(@NonNull Runnable runnable) {
runnable.run();
}
}
);
}
public void update() {
Log.i(LOG_TAG, "Beginning FxA device list update.");
final byte[] sessionToken;
try {
sessionToken = fxAccount.getState().getSessionToken();
} catch (State.NotASessionTokenState e) {
// This should never happen, because the caller (FxAccountSyncAdapter) verifies that
// we are in a token state before calling this method.
throw new IllegalStateException("Could not get a session token during Sync (?) " + e);
}
final FxAccountClient fxaClient = getSynchronousFxaClient();
fxaClient.deviceList(sessionToken, this);
}
}

Просмотреть файл

@ -378,8 +378,9 @@ public class FxAccountDeviceRegistrator implements BundleEventListener {
break; break;
} }
final FxAccountDevice updatedDevice = new FxAccountDevice(device.name, fxaDevice.id, device.type, final FxAccountDevice updatedDevice = new FxAccountDevice(device.name, fxaDevice.id, device.type,
device.isCurrentDevice, device.pushCallback, null, null,
device.pushPublicKey, device.pushAuthKey); device.pushCallback, device.pushPublicKey,
device.pushAuthKey);
doFxaRegistration(context, fxAccount, updatedDevice, false); doFxaRegistration(context, fxAccount, updatedDevice, false);
return; return;
} }

Просмотреть файл

@ -5,8 +5,10 @@
package org.mozilla.gecko.fxa.receivers; package org.mozilla.gecko.fxa.receivers;
import android.app.IntentService; import android.app.IntentService;
import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.net.Uri;
import android.text.TextUtils; import android.text.TextUtils;
import org.mozilla.gecko.background.common.log.Logger; import org.mozilla.gecko.background.common.log.Logger;
@ -15,6 +17,7 @@ import org.mozilla.gecko.background.fxa.FxAccountClientException;
import org.mozilla.gecko.background.fxa.oauth.FxAccountAbstractClient; import org.mozilla.gecko.background.fxa.oauth.FxAccountAbstractClient;
import org.mozilla.gecko.background.fxa.oauth.FxAccountAbstractClientException.FxAccountAbstractClientRemoteException; import org.mozilla.gecko.background.fxa.oauth.FxAccountAbstractClientException.FxAccountAbstractClientRemoteException;
import org.mozilla.gecko.background.fxa.oauth.FxAccountOAuthClient10; import org.mozilla.gecko.background.fxa.oauth.FxAccountOAuthClient10;
import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.fxa.FxAccountConstants; import org.mozilla.gecko.fxa.FxAccountConstants;
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount; import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
import org.mozilla.gecko.fxa.sync.FxAccountNotificationManager; import org.mozilla.gecko.fxa.sync.FxAccountNotificationManager;
@ -75,6 +78,8 @@ public class FxAccountDeletedService extends IntentService {
return; return;
} }
clearRemoteDevicesList(intent, context);
// Delete current device the from FxA devices list. // Delete current device the from FxA devices list.
deleteFxADevice(intent); deleteFxADevice(intent);
@ -159,6 +164,17 @@ public class FxAccountDeletedService extends IntentService {
} }
} }
private void clearRemoteDevicesList(Intent intent, Context context) {
final Uri remoteDevicesUriWithProfile = BrowserContract.RemoteDevices.CONTENT_URI
.buildUpon()
.appendQueryParameter(BrowserContract.PARAM_PROFILE,
intent.getStringExtra(FxAccountConstants.ACCOUNT_DELETED_INTENT_ACCOUNT_PROFILE))
.build();
ContentResolver cr = context.getContentResolver();
cr.delete(remoteDevicesUriWithProfile, null, null);
}
// Remove our current device from the FxA device list. // Remove our current device from the FxA device list.
private void deleteFxADevice(Intent intent) { private void deleteFxADevice(Intent intent) {
final byte[] sessionToken = intent.getByteArrayExtra(FxAccountConstants.ACCOUNT_DELETED_INTENT_ACCOUNT_SESSION_TOKEN); final byte[] sessionToken = intent.getByteArrayExtra(FxAccountConstants.ACCOUNT_DELETED_INTENT_ACCOUNT_SESSION_TOKEN);

Просмотреть файл

@ -6,6 +6,7 @@ package org.mozilla.gecko.fxa.sync;
import android.accounts.Account; import android.accounts.Account;
import android.content.AbstractThreadedSyncAdapter; import android.content.AbstractThreadedSyncAdapter;
import android.content.ContentProvider;
import android.content.ContentProviderClient; import android.content.ContentProviderClient;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
@ -22,6 +23,7 @@ import org.mozilla.gecko.background.fxa.SkewHandler;
import org.mozilla.gecko.browserid.JSONWebTokenUtils; import org.mozilla.gecko.browserid.JSONWebTokenUtils;
import org.mozilla.gecko.fxa.FirefoxAccounts; import org.mozilla.gecko.fxa.FirefoxAccounts;
import org.mozilla.gecko.fxa.FxAccountConstants; import org.mozilla.gecko.fxa.FxAccountConstants;
import org.mozilla.gecko.fxa.devices.FxAccountDeviceListUpdater;
import org.mozilla.gecko.fxa.devices.FxAccountDeviceRegistrator; import org.mozilla.gecko.fxa.devices.FxAccountDeviceRegistrator;
import org.mozilla.gecko.fxa.authenticator.AccountPickler; import org.mozilla.gecko.fxa.authenticator.AccountPickler;
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount; import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
@ -418,6 +420,15 @@ public class FxAccountSyncAdapter extends AbstractThreadedSyncAdapter {
} }
} }
private void onSessionTokenStateReached(Context context, AndroidFxAccount fxAccount) {
// This does not block the main thread, if work has to be done it is executed in a new thread.
maybeRegisterDevice(context, fxAccount);
FxAccountDeviceListUpdater deviceListUpdater = new FxAccountDeviceListUpdater(fxAccount, context.getContentResolver());
// Since the clients stage requires a fresh list of remote devices, we update the device list synchronously.
deviceListUpdater.update();
}
/** /**
* A trivial Sync implementation that does not cache client keys, * A trivial Sync implementation that does not cache client keys,
* certificates, or tokens. * certificates, or tokens.
@ -545,7 +556,7 @@ public class FxAccountSyncAdapter extends AbstractThreadedSyncAdapter {
schedulePolicy.onHandleFinal(notMarried.getNeededAction()); schedulePolicy.onHandleFinal(notMarried.getNeededAction());
syncDelegate.handleCannotSync(notMarried); syncDelegate.handleCannotSync(notMarried);
if (notMarried.getStateLabel() == StateLabel.Engaged) { if (notMarried.getStateLabel() == StateLabel.Engaged) {
maybeRegisterDevice(context, fxAccount); onSessionTokenStateReached(context, fxAccount);
} }
} }
@ -588,6 +599,8 @@ public class FxAccountSyncAdapter extends AbstractThreadedSyncAdapter {
return; return;
} }
onSessionTokenStateReached(context, fxAccount);
final SessionCallback sessionCallback = new SessionCallback(syncDelegate, schedulePolicy); final SessionCallback sessionCallback = new SessionCallback(syncDelegate, schedulePolicy);
final KeyBundle syncKeyBundle = married.getSyncKeyBundle(); final KeyBundle syncKeyBundle = married.getSyncKeyBundle();
final String clientState = married.getClientState(); final String clientState = married.getClientState();
@ -595,8 +608,6 @@ public class FxAccountSyncAdapter extends AbstractThreadedSyncAdapter {
assertion, tokenServerEndpointURI, tokenBackoffHandler, sharedPrefs, assertion, tokenServerEndpointURI, tokenBackoffHandler, sharedPrefs,
syncKeyBundle, clientState, sessionCallback, extras, fxAccount, syncDeadline); syncKeyBundle, clientState, sessionCallback, extras, fxAccount, syncDeadline);
maybeRegisterDevice(context, fxAccount);
// Force fetch the profile avatar information. (asynchronous, in another thread) // Force fetch the profile avatar information. (asynchronous, in another thread)
Logger.info(LOG_TAG, "Fetching profile avatar information."); Logger.info(LOG_TAG, "Fetching profile avatar information.");
fxAccount.fetchProfileJSON(); fxAccount.fetchProfileJSON();

Просмотреть файл

@ -0,0 +1,188 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
package org.mozilla.gecko.fxa.devices;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.RemoteException;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mozilla.gecko.background.db.DelegatingTestContentProvider;
import org.mozilla.gecko.background.fxa.FxAccountClient;
import org.mozilla.gecko.background.testhelpers.TestRunner;
import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.db.BrowserProvider;
import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
import org.mozilla.gecko.fxa.login.State;
import org.robolectric.shadows.ShadowContentResolver;
import java.util.List;
import static java.util.Objects.deepEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.anyString;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@RunWith(TestRunner.class)
public class TestFxAccountDeviceListUpdater {
@Before
public void init() {
// Process Mockito annotations
MockitoAnnotations.initMocks(this);
fxaDevicesUpdater = spy(new FxAccountDeviceListUpdater(fxAccount, contentResolver));
}
FxAccountDeviceListUpdater fxaDevicesUpdater;
@Mock
AndroidFxAccount fxAccount;
@Mock
State state;
@Mock
ContentResolver contentResolver;
@Mock
FxAccountClient fxaClient;
@Test
public void testUpdate() throws Throwable {
byte[] token = "usertoken".getBytes();
when(fxAccount.getState()).thenReturn(state);
when(state.getSessionToken()).thenReturn(token);
doReturn(fxaClient).when(fxaDevicesUpdater).getSynchronousFxaClient();
fxaDevicesUpdater.update();
verify(fxaClient).deviceList(token, fxaDevicesUpdater);
}
@Test
public void testSuccessHandler() throws Throwable {
FxAccountDevice[] result = new FxAccountDevice[2];
FxAccountDevice device1 = new FxAccountDevice("Current device", "deviceid1", "mobile", true, System.currentTimeMillis(),
"https://localhost/push/callback1", "abc123", "321cba");
FxAccountDevice device2 = new FxAccountDevice("Desktop PC", "deviceid2", "desktop", true, System.currentTimeMillis(),
"https://localhost/push/callback2", "abc123", "321cba");
result[0] = device1;
result[1] = device2;
when(fxAccount.getProfile()).thenReturn("default");
long timeBeforeCall = System.currentTimeMillis();
fxaDevicesUpdater.handleSuccess(result);
ArgumentCaptor<Bundle> captor = ArgumentCaptor.forClass(Bundle.class);
verify(contentResolver).call(any(Uri.class), eq(BrowserContract.METHOD_REPLACE_REMOTE_CLIENTS), anyString(), captor.capture());
List<Bundle> allArgs = captor.getAllValues();
assertTrue(allArgs.size() == 1);
ContentValues[] allValues = (ContentValues[]) allArgs.get(0).getParcelableArray(BrowserContract.METHOD_PARAM_DATA);
ContentValues firstDevice = allValues[0];
checkInsertDeviceContentValues(device1, firstDevice, timeBeforeCall);
ContentValues secondDevice = allValues[1];
checkInsertDeviceContentValues(device2, secondDevice, timeBeforeCall);
}
private void checkInsertDeviceContentValues(FxAccountDevice device, ContentValues firstDevice, long timeBeforeCall) {
assertEquals(firstDevice.getAsString(BrowserContract.RemoteDevices.GUID), device.id);
assertEquals(firstDevice.getAsString(BrowserContract.RemoteDevices.TYPE), device.type);
assertEquals(firstDevice.getAsString(BrowserContract.RemoteDevices.NAME), device.name);
assertEquals(firstDevice.getAsBoolean(BrowserContract.RemoteDevices.IS_CURRENT_DEVICE), device.isCurrentDevice);
deepEquals(firstDevice.getAsString(BrowserContract.RemoteDevices.LAST_ACCESS_TIME), device.lastAccessTime);
assertTrue(firstDevice.getAsLong(BrowserContract.RemoteDevices.DATE_CREATED) < timeBeforeCall + 10000); // Give 10 secs of leeway
assertTrue(firstDevice.getAsLong(BrowserContract.RemoteDevices.DATE_MODIFIED) < timeBeforeCall + 10000);
assertEquals(firstDevice.getAsString(BrowserContract.RemoteDevices.NAME), device.name);
}
@Test
public void testBrowserProvider() {
Uri uri = testUri(BrowserContract.RemoteDevices.CONTENT_URI);
BrowserProvider provider = new BrowserProvider();
Cursor c = null;
try {
provider.onCreate();
ShadowContentResolver.registerProvider(BrowserContract.AUTHORITY, new DelegatingTestContentProvider(provider));
final ShadowContentResolver cr = new ShadowContentResolver();
ContentProviderClient remoteDevicesClient = cr.acquireContentProviderClient(BrowserContract.RemoteDevices.CONTENT_URI);
// First let's insert a client for initial state.
Bundle bundle = new Bundle();
ContentValues device1 = createMockRemoteClientValues("device1");
bundle.putParcelableArray(BrowserContract.METHOD_PARAM_DATA, new ContentValues[] { device1 });
remoteDevicesClient.call(BrowserContract.METHOD_REPLACE_REMOTE_CLIENTS, uri.toString(), bundle);
c = remoteDevicesClient.query(uri, null, null, null, "name ASC");
assertEquals(c.getCount(), 1);
c.moveToFirst();
int nameCol = c.getColumnIndexOrThrow("name");
assertEquals(c.getString(nameCol), "device1");
c.close();
// Then we replace our remote clients list with a new one.
bundle = new Bundle();
ContentValues device2 = createMockRemoteClientValues("device2");
ContentValues device3 = createMockRemoteClientValues("device3");
bundle.putParcelableArray(BrowserContract.METHOD_PARAM_DATA, new ContentValues[] { device2, device3 });
remoteDevicesClient.call(BrowserContract.METHOD_REPLACE_REMOTE_CLIENTS, uri.toString(), bundle);
c = remoteDevicesClient.query(uri, null, null, null, "name ASC");
assertEquals(c.getCount(), 2);
c.moveToFirst();
nameCol = c.getColumnIndexOrThrow("name");
assertEquals(c.getString(nameCol), "device2");
c.moveToNext();
assertEquals(c.getString(nameCol), "device3");
c.close();
} catch (RemoteException e) {
fail(e.getMessage());
} finally {
if (c != null && !c.isClosed()) {
c.close();
}
provider.shutdown();
}
}
private Uri testUri(Uri baseUri) {
return baseUri.buildUpon().appendQueryParameter(BrowserContract.PARAM_IS_TEST, "1").build();
}
private ContentValues createMockRemoteClientValues(String name) {
final long now = System.currentTimeMillis();
ContentValues cli = new ContentValues();
cli.put(BrowserContract.RemoteDevices.GUID, "R" + Math.floor(Math.random() * 10));
cli.put(BrowserContract.RemoteDevices.NAME, name);
cli.put(BrowserContract.RemoteDevices.TYPE, "mobile");
cli.put(BrowserContract.RemoteDevices.IS_CURRENT_DEVICE, false);
cli.put(BrowserContract.RemoteDevices.LAST_ACCESS_TIME, System.currentTimeMillis());
cli.put(BrowserContract.RemoteDevices.DATE_CREATED, now);
cli.put(BrowserContract.RemoteDevices.DATE_MODIFIED, now);
return cli;
}
}

Просмотреть файл

@ -179,7 +179,7 @@ public class MockFxAccountClient implements FxAccountClient {
String deviceId = deviceToRegister.id; String deviceId = deviceToRegister.id;
if (TextUtils.isEmpty(deviceId)) { // Create if (TextUtils.isEmpty(deviceId)) { // Create
deviceId = UUID.randomUUID().toString(); deviceId = UUID.randomUUID().toString();
FxAccountDevice device = new FxAccountDevice(deviceToRegister.name, deviceId, deviceToRegister.type, null, null, null, null); FxAccountDevice device = new FxAccountDevice(deviceToRegister.name, deviceId, deviceToRegister.type, null, null, null, null, null);
requestDelegate.handleSuccess(device); requestDelegate.handleSuccess(device);
} else { // Update } else { // Update
FxAccountDevice existingDevice = user.devices.get(deviceId); FxAccountDevice existingDevice = user.devices.get(deviceId);
@ -188,8 +188,8 @@ public class MockFxAccountClient implements FxAccountClient {
if (!TextUtils.isEmpty(deviceToRegister.name)) { if (!TextUtils.isEmpty(deviceToRegister.name)) {
deviceName = deviceToRegister.name; deviceName = deviceToRegister.name;
} // We could also update the other fields.. } // We could also update the other fields..
FxAccountDevice device = new FxAccountDevice(deviceName, existingDevice.id, existingDevice.type, FxAccountDevice device = new FxAccountDevice(deviceName, existingDevice.id, existingDevice.type, existingDevice.isCurrentDevice,
existingDevice.isCurrentDevice, existingDevice.pushCallback, existingDevice.pushPublicKey,existingDevice.pushAuthKey); existingDevice.lastAccessTime, existingDevice.pushCallback, existingDevice.pushPublicKey,existingDevice.pushAuthKey);
requestDelegate.handleSuccess(device); requestDelegate.handleSuccess(device);
} else { // Device unknown } else { // Device unknown
handleFailure(requestDelegate, HttpStatus.SC_BAD_REQUEST, FxAccountRemoteError.UNKNOWN_DEVICE, "device is unknown"); handleFailure(requestDelegate, HttpStatus.SC_BAD_REQUEST, FxAccountRemoteError.UNKNOWN_DEVICE, "device is unknown");