Bug 1131421 - Part 1: initial stub reading list service and SyncAdapter. r=nalexander
--- a/mobile/android/base/android-services.mozbuild
+++ b/mobile/android/base/android-services.mozbuild
@@ -877,16 +877,17 @@ sync_java_files = [
'fxa/login/TokensAndKeysState.java',
'fxa/receivers/FxAccountDeletedReceiver.java',
'fxa/receivers/FxAccountDeletedService.java',
'fxa/receivers/FxAccountUpgradeReceiver.java',
'fxa/sync/FxAccountGlobalSession.java',
'fxa/sync/FxAccountNotificationManager.java',
'fxa/sync/FxAccountSchedulePolicy.java',
'fxa/sync/FxAccountSyncAdapter.java',
+ 'fxa/sync/FxAccountSyncDelegate.java',
'fxa/sync/FxAccountSyncService.java',
'fxa/sync/FxAccountSyncStatusHelper.java',
'fxa/sync/SchedulePolicy.java',
'fxa/tasks/FxAccountCodeResender.java',
'fxa/tasks/FxAccountCreateAccountTask.java',
'fxa/tasks/FxAccountSetupTask.java',
'fxa/tasks/FxAccountSignInTask.java',
'fxa/tasks/FxAccountUnlockCodeResender.java',
@@ -1144,8 +1145,12 @@ sync_java_files = [
'sync/UnexpectedJSONException.java',
'sync/UnknownSynchronizerConfigurationVersionException.java',
'sync/Utils.java',
'tokenserver/TokenServerClient.java',
'tokenserver/TokenServerClientDelegate.java',
'tokenserver/TokenServerException.java',
'tokenserver/TokenServerToken.java',
]
+reading_list_service_java_files = [
+ 'reading/ReadingListSyncAdapter.java',
+ 'reading/ReadingListSyncService.java',
+]
--- a/mobile/android/base/fxa/authenticator/AndroidFxAccount.java
+++ b/mobile/android/base/fxa/authenticator/AndroidFxAccount.java
@@ -40,17 +40,18 @@ import android.os.Bundle;
* <p>
* Account user data is accessible only to the Android App(s) that own the
* Account type. Account user data is not removed when the App's private data is
* cleared.
*/
public class AndroidFxAccount {
protected static final String LOG_TAG = AndroidFxAccount.class.getSimpleName();
- public static final int CURRENT_PREFS_VERSION = 1;
+ public static final int CURRENT_SYNC_PREFS_VERSION = 1;
+ public static final int CURRENT_RL_PREFS_VERSION = 1;
// When updating the account, do not forget to update AccountPickler.
public static final int CURRENT_ACCOUNT_VERSION = 3;
public static final String ACCOUNT_KEY_ACCOUNT_VERSION = "version";
public static final String ACCOUNT_KEY_PROFILE = "profile";
public static final String ACCOUNT_KEY_IDP_SERVER = "idpServerURI";
// The audience should always be a prefix of the token server URI.
@@ -236,53 +237,66 @@ public class AndroidFxAccount {
public String getAudience() {
return accountManager.getUserData(account, ACCOUNT_KEY_AUDIENCE);
}
public String getTokenServerURI() {
return accountManager.getUserData(account, ACCOUNT_KEY_TOKEN_SERVER);
}
- /**
- * This needs to return a string because of the tortured prefs access in GlobalSession.
- */
- public String getSyncPrefsPath() throws GeneralSecurityException, UnsupportedEncodingException {
+ private String constructPrefsPath(String product, long version, String extra) throws GeneralSecurityException, UnsupportedEncodingException {
String profile = getProfile();
String username = account.name;
if (profile == null) {
throw new IllegalStateException("Missing profile. Cannot fetch prefs.");
}
if (username == null) {
throw new IllegalStateException("Missing username. Cannot fetch prefs.");
}
- final String tokenServerURI = getTokenServerURI();
- if (tokenServerURI == null) {
- throw new IllegalStateException("No token server URI. Cannot fetch prefs.");
- }
-
final String fxaServerURI = getAccountServerURI();
if (fxaServerURI == null) {
throw new IllegalStateException("No account server URI. Cannot fetch prefs.");
}
- final String product = GlobalConstants.BROWSER_INTENT_PACKAGE + ".fxa";
- final long version = CURRENT_PREFS_VERSION;
+ // This is unique for each syncing 'view' of the account.
+ final String serverURLThing = fxaServerURI + "!" + extra;
+ return Utils.getPrefsPath(product, username, serverURLThing, profile, version);
+ }
- // This is unique for each syncing 'view' of the account.
- final String serverURLThing = fxaServerURI + "!" + tokenServerURI;
- return Utils.getPrefsPath(product, username, serverURLThing, profile, version);
+ /**
+ * This needs to return a string because of the tortured prefs access in GlobalSession.
+ */
+ public String getSyncPrefsPath() throws GeneralSecurityException, UnsupportedEncodingException {
+ final String tokenServerURI = getTokenServerURI();
+ if (tokenServerURI == null) {
+ throw new IllegalStateException("No token server URI. Cannot fetch prefs.");
+ }
+
+ final String product = GlobalConstants.BROWSER_INTENT_PACKAGE + ".fxa";
+ final long version = CURRENT_SYNC_PREFS_VERSION;
+ return constructPrefsPath(product, version, tokenServerURI);
+ }
+
+ public String getReadingListPrefsPath() throws GeneralSecurityException, UnsupportedEncodingException {
+ final String product = GlobalConstants.BROWSER_INTENT_PACKAGE + ".reading";
+ final long version = CURRENT_RL_PREFS_VERSION;
+ return constructPrefsPath(product, version, "");
}
public SharedPreferences getSyncPrefs() throws UnsupportedEncodingException, GeneralSecurityException {
return context.getSharedPreferences(getSyncPrefsPath(), Utils.SHARED_PREFERENCES_MODE);
}
+ public SharedPreferences getReadingListPrefs() throws UnsupportedEncodingException, GeneralSecurityException {
+ return context.getSharedPreferences(getReadingListPrefsPath(), Utils.SHARED_PREFERENCES_MODE);
+ }
+
/**
* Extract a JSON dictionary of the string values associated to this account.
* <p>
* <b>For debugging use only!</b> The contents of this JSON object completely
* determine the user's Firefox Account status and yield access to whatever
* user data the device has access to.
*
* @return JSON-object of Strings.
--- a/mobile/android/base/fxa/sync/FxAccountSyncAdapter.java
+++ b/mobile/android/base/fxa/sync/FxAccountSyncAdapter.java
@@ -81,129 +81,55 @@ public class FxAccountSyncAdapter extend
protected final FxAccountNotificationManager notificationManager;
public FxAccountSyncAdapter(Context context, boolean autoInitialize) {
super(context, autoInitialize);
this.executor = Executors.newSingleThreadExecutor();
this.notificationManager = new FxAccountNotificationManager(NOTIFICATION_ID);
}
- protected static class SyncDelegate {
- protected final CountDownLatch latch;
- protected final SyncResult syncResult;
- protected final AndroidFxAccount fxAccount;
+ protected static class SyncDelegate extends FxAccountSyncDelegate {
+ @Override
+ public void handleSuccess() {
+ Logger.info(LOG_TAG, "Sync succeeded.");
+ super.handleSuccess();
+ }
+
+ @Override
+ public void handleError(Exception e) {
+ Logger.error(LOG_TAG, "Got exception syncing.", e);
+ super.handleError(e);
+ }
+
+ @Override
+ public void handleCannotSync(State finalState) {
+ Logger.warn(LOG_TAG, "Cannot sync from state: " + finalState.getStateLabel());
+ super.handleCannotSync(finalState);
+ }
+
+ @Override
+ public void postponeSync(long millis) {
+ if (millis <= 0) {
+ Logger.debug(LOG_TAG, "Asked to postpone sync, but zero delay.");
+ }
+ super.postponeSync(millis);
+ }
+
+ @Override
+ public void rejectSync() {
+ super.rejectSync();
+ }
+
protected final Collection<String> stageNamesToSync;
public SyncDelegate(CountDownLatch latch, SyncResult syncResult, AndroidFxAccount fxAccount, Collection<String> stageNamesToSync) {
- if (latch == null) {
- throw new IllegalArgumentException("latch must not be null");
- }
- if (syncResult == null) {
- throw new IllegalArgumentException("syncResult must not be null");
- }
- if (fxAccount == null) {
- throw new IllegalArgumentException("fxAccount must not be null");
- }
- this.latch = latch;
- this.syncResult = syncResult;
- this.fxAccount = fxAccount;
+ super(latch, syncResult, fxAccount);
this.stageNamesToSync = Collections.unmodifiableCollection(stageNamesToSync);
}
- /**
- * No error! Say that we made progress.
- */
- protected void setSyncResultSuccess() {
- syncResult.stats.numUpdates += 1;
- }
-
- /**
- * Soft error. Say that we made progress, so that Android will sync us again
- * after exponential backoff.
- */
- protected void setSyncResultSoftError() {
- syncResult.stats.numUpdates += 1;
- syncResult.stats.numIoExceptions += 1;
- }
-
- /**
- * Hard error. We don't want Android to sync us again, even if we make
- * progress, until the user intervenes.
- */
- protected void setSyncResultHardError() {
- syncResult.stats.numAuthExceptions += 1;
- }
-
- public void handleSuccess() {
- Logger.info(LOG_TAG, "Sync succeeded.");
- setSyncResultSuccess();
- latch.countDown();
- }
-
- public void handleError(Exception e) {
- Logger.error(LOG_TAG, "Got exception syncing.", e);
- setSyncResultSoftError();
- // This is awful, but we need to propagate bad assertions back up the
- // chain somehow, and this will do for now.
- if (e instanceof TokenServerException) {
- // We should only get here *after* we're locked into the married state.
- State state = fxAccount.getState();
- if (state.getStateLabel() == StateLabel.Married) {
- Married married = (Married) state;
- fxAccount.setState(married.makeCohabitingState());
- }
- }
- latch.countDown();
- }
-
- /**
- * When the login machine terminates, we might not be in the
- * <code>Married</code> state, and therefore we can't sync. This method
- * messages as much to the user.
- * <p>
- * To avoid stopping us syncing altogether, we set a soft error rather than
- * a hard error. In future, we would like to set a hard error if we are in,
- * for example, the <code>Separated</code> state, and then have some user
- * initiated activity mark the Android account as ready to sync again. This
- * is tricky, though, so we play it safe for now.
- *
- * @param finalState
- * that login machine ended in.
- */
- public void handleCannotSync(State finalState) {
- Logger.warn(LOG_TAG, "Cannot sync from state: " + finalState.getStateLabel());
- setSyncResultSoftError();
- latch.countDown();
- }
-
- public void postponeSync(long millis) {
- if (millis <= 0) {
- Logger.debug(LOG_TAG, "Asked to postpone sync, but zero delay. Short-circuiting.");
- } else {
- // delayUntil is broken: https://code.google.com/p/android/issues/detail?id=65669
- // So we don't bother doing this. Instead, we rely on the periodic sync
- // we schedule, and the backoff handler for the rest.
- /*
- Logger.warn(LOG_TAG, "Postponing sync by " + millis + "ms.");
- syncResult.delayUntil = millis / 1000;
- */
- }
- setSyncResultSoftError();
- latch.countDown();
- }
-
- /**
- * Simply don't sync, without setting any error flags.
- * This is the appropriate behavior when a routine backoff has not yet
- * been met.
- */
- public void rejectSync() {
- latch.countDown();
- }
-
public Collection<String> getStageNamesToSync() {
return this.stageNamesToSync;
}
}
protected static class SessionCallback implements BaseGlobalSessionCallback {
protected final SyncDelegate syncDelegate;
protected final SchedulePolicy schedulePolicy;
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/fxa/sync/FxAccountSyncDelegate.java
@@ -0,0 +1,122 @@
+/* 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.sync;
+
+import java.util.concurrent.CountDownLatch;
+
+import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
+import org.mozilla.gecko.fxa.login.Married;
+import org.mozilla.gecko.fxa.login.State;
+import org.mozilla.gecko.fxa.login.State.StateLabel;
+import org.mozilla.gecko.tokenserver.TokenServerException;
+
+import android.content.SyncResult;
+
+public class FxAccountSyncDelegate {
+ protected final CountDownLatch latch;
+ protected final SyncResult syncResult;
+ protected final AndroidFxAccount fxAccount;
+
+ public FxAccountSyncDelegate(CountDownLatch latch, SyncResult syncResult, AndroidFxAccount fxAccount) {
+ if (latch == null) {
+ throw new IllegalArgumentException("latch must not be null");
+ }
+ if (syncResult == null) {
+ throw new IllegalArgumentException("syncResult must not be null");
+ }
+ if (fxAccount == null) {
+ throw new IllegalArgumentException("fxAccount must not be null");
+ }
+ this.latch = latch;
+ this.syncResult = syncResult;
+ this.fxAccount = fxAccount;
+ }
+
+ /**
+ * No error! Say that we made progress.
+ */
+ protected void setSyncResultSuccess() {
+ syncResult.stats.numUpdates += 1;
+ }
+
+ /**
+ * Soft error. Say that we made progress, so that Android will sync us again
+ * after exponential backoff.
+ */
+ protected void setSyncResultSoftError() {
+ syncResult.stats.numUpdates += 1;
+ syncResult.stats.numIoExceptions += 1;
+ }
+
+ /**
+ * Hard error. We don't want Android to sync us again, even if we make
+ * progress, until the user intervenes.
+ */
+ protected void setSyncResultHardError() {
+ syncResult.stats.numAuthExceptions += 1;
+ }
+
+ public void handleSuccess() {
+ setSyncResultSuccess();
+ latch.countDown();
+ }
+
+ public void handleError(Exception e) {
+ setSyncResultSoftError();
+ // This is awful, but we need to propagate bad assertions back up the
+ // chain somehow, and this will do for now.
+ if (e instanceof TokenServerException) {
+ // We should only get here *after* we're locked into the married state.
+ State state = fxAccount.getState();
+ if (state.getStateLabel() == StateLabel.Married) {
+ Married married = (Married) state;
+ fxAccount.setState(married.makeCohabitingState());
+ }
+ }
+ latch.countDown();
+ }
+
+ /**
+ * When the login machine terminates, we might not be in the
+ * <code>Married</code> state, and therefore we can't sync. This method
+ * messages as much to the user.
+ * <p>
+ * To avoid stopping us syncing altogether, we set a soft error rather than
+ * a hard error. In future, we would like to set a hard error if we are in,
+ * for example, the <code>Separated</code> state, and then have some user
+ * initiated activity mark the Android account as ready to sync again. This
+ * is tricky, though, so we play it safe for now.
+ *
+ * @param finalState
+ * that login machine ended in.
+ */
+ public void handleCannotSync(State finalState) {
+ setSyncResultSoftError();
+ latch.countDown();
+ }
+
+ public void postponeSync(long millis) {
+ if (millis > 0) {
+ // delayUntil is broken: https://code.google.com/p/android/issues/detail?id=65669
+ // So we don't bother doing this. Instead, we rely on the periodic sync
+ // we schedule, and the backoff handler for the rest.
+ /*
+ Logger.warn(LOG_TAG, "Postponing sync by " + millis + "ms.");
+ syncResult.delayUntil = millis / 1000;
+ */
+ }
+ setSyncResultSoftError();
+ latch.countDown();
+ }
+
+ /**
+ * Simply don't sync, without setting any error flags.
+ * This is the appropriate behavior when a routine backoff has not yet
+ * been met.
+ */
+ public void rejectSync() {
+ latch.countDown();
+ }
+}
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/reading/ReadingListSyncAdapter.java
@@ -0,0 +1,26 @@
+/* -*- Mode: Java; c-basic-offset: 2; tab-width: 20; indent-tabs-mode: nil; -*-
+ * 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.reading;
+
+import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
+
+import android.accounts.Account;
+import android.content.AbstractThreadedSyncAdapter;
+import android.content.ContentProviderClient;
+import android.content.Context;
+import android.content.SyncResult;
+import android.os.Bundle;
+
+public class ReadingListSyncAdapter extends AbstractThreadedSyncAdapter {
+ public ReadingListSyncAdapter(Context context, boolean autoInitialize) {
+ super(context, autoInitialize);
+ }
+
+ @Override
+ public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) {
+ final AndroidFxAccount fxAccount = new AndroidFxAccount(getContext(), account);
+ }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/reading/ReadingListSyncService.java
@@ -0,0 +1,28 @@
+/* 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.reading;
+
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+
+public class ReadingListSyncService extends Service {
+ private static final Object syncAdapterLock = new Object();
+ private static ReadingListSyncAdapter syncAdapter;
+
+ @Override
+ public void onCreate() {
+ synchronized (syncAdapterLock) {
+ if (syncAdapter == null) {
+ syncAdapter = new ReadingListSyncAdapter(getApplicationContext(), true);
+ }
+ }
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return syncAdapter.getSyncAdapterBinder();
+ }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/resources/xml/readinglist_syncadapter.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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/. -->
+
+<sync-adapter xmlns:android="http://schemas.android.com/apk/res/android"
+ android:accountType="@string/moz_android_shared_fxaccount_type"
+ android:contentAuthority="@string/content_authority_db_readinglist"
+ android:isAlwaysSyncable="true"
+ android:supportsUploading="true"
+ android:userVisible="true"
+/>
--- a/mobile/android/services/manifests/FxAccountAndroidManifest_services.xml.in
+++ b/mobile/android/services/manifests/FxAccountAndroidManifest_services.xml.in
@@ -4,23 +4,41 @@
<intent-filter >
<action android:name="android.accounts.AccountAuthenticator" />
</intent-filter>
<meta-data
android:name="android.accounts.AccountAuthenticator"
android:resource="@xml/fxaccount_authenticator" />
</service>
+
+ <service
+ android:exported="false"
+ android:name="org.mozilla.gecko.fxa.receivers.FxAccountDeletedService" >
+ </service>
+
+ <!-- Firefox Sync. -->
<service
android:exported="false"
android:name="org.mozilla.gecko.fxa.sync.FxAccountSyncService" >
<intent-filter >
<action android:name="android.content.SyncAdapter" />
</intent-filter>
<meta-data
android:name="android.content.SyncAdapter"
android:resource="@xml/fxaccount_syncadapter" />
</service>
+
+ <!-- Reading List. -->
+#ifdef MOZ_ANDROID_READING_LIST_SERVICE
<service
android:exported="false"
- android:name="org.mozilla.gecko.fxa.receivers.FxAccountDeletedService" >
+ android:name="org.mozilla.gecko.reading.ReadingListSyncService" >
+ <intent-filter >
+ <action android:name="android.content.SyncAdapter" />
+ </intent-filter>
+
+ <meta-data
+ android:name="android.content.SyncAdapter"
+ android:resource="@xml/readinglist_syncadapter" />
</service>
+#endif