Bug 1131421 - Part 1: initial stub reading list service and SyncAdapter. r=nalexander

This commit is contained in:
Richard Newman 2015-02-09 21:08:05 -08:00
Родитель 06584886b6
Коммит ee6ea0102b
8 изменённых файлов: 273 добавлений и 122 удалений

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

@ -882,6 +882,7 @@ sync_java_files = [
'fxa/sync/FxAccountNotificationManager.java', 'fxa/sync/FxAccountNotificationManager.java',
'fxa/sync/FxAccountSchedulePolicy.java', 'fxa/sync/FxAccountSchedulePolicy.java',
'fxa/sync/FxAccountSyncAdapter.java', 'fxa/sync/FxAccountSyncAdapter.java',
'fxa/sync/FxAccountSyncDelegate.java',
'fxa/sync/FxAccountSyncService.java', 'fxa/sync/FxAccountSyncService.java',
'fxa/sync/FxAccountSyncStatusHelper.java', 'fxa/sync/FxAccountSyncStatusHelper.java',
'fxa/sync/SchedulePolicy.java', 'fxa/sync/SchedulePolicy.java',
@ -1149,3 +1150,7 @@ sync_java_files = [
'tokenserver/TokenServerException.java', 'tokenserver/TokenServerException.java',
'tokenserver/TokenServerToken.java', 'tokenserver/TokenServerToken.java',
] ]
reading_list_service_java_files = [
'reading/ReadingListSyncAdapter.java',
'reading/ReadingListSyncService.java',
]

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

@ -45,7 +45,8 @@ import android.os.Bundle;
public class AndroidFxAccount { public class AndroidFxAccount {
protected static final String LOG_TAG = AndroidFxAccount.class.getSimpleName(); 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. // When updating the account, do not forget to update AccountPickler.
public static final int CURRENT_ACCOUNT_VERSION = 3; public static final int CURRENT_ACCOUNT_VERSION = 3;
@ -241,10 +242,7 @@ public class AndroidFxAccount {
return accountManager.getUserData(account, ACCOUNT_KEY_TOKEN_SERVER); return accountManager.getUserData(account, ACCOUNT_KEY_TOKEN_SERVER);
} }
/** private String constructPrefsPath(String product, long version, String extra) throws GeneralSecurityException, UnsupportedEncodingException {
* This needs to return a string because of the tortured prefs access in GlobalSession.
*/
public String getSyncPrefsPath() throws GeneralSecurityException, UnsupportedEncodingException {
String profile = getProfile(); String profile = getProfile();
String username = account.name; String username = account.name;
@ -256,28 +254,44 @@ public class AndroidFxAccount {
throw new IllegalStateException("Missing username. Cannot fetch prefs."); 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(); final String fxaServerURI = getAccountServerURI();
if (fxaServerURI == null) { if (fxaServerURI == null) {
throw new IllegalStateException("No account server URI. Cannot fetch prefs."); 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. // This is unique for each syncing 'view' of the account.
final String serverURLThing = fxaServerURI + "!" + tokenServerURI; final String serverURLThing = fxaServerURI + "!" + extra;
return Utils.getPrefsPath(product, username, serverURLThing, profile, version); 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 { public SharedPreferences getSyncPrefs() throws UnsupportedEncodingException, GeneralSecurityException {
return context.getSharedPreferences(getSyncPrefsPath(), Utils.SHARED_PREFERENCES_MODE); 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. * Extract a JSON dictionary of the string values associated to this account.
* <p> * <p>

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

@ -86,119 +86,45 @@ public class FxAccountSyncAdapter extends AbstractThreadedSyncAdapter {
this.notificationManager = new FxAccountNotificationManager(NOTIFICATION_ID); this.notificationManager = new FxAccountNotificationManager(NOTIFICATION_ID);
} }
protected static class SyncDelegate { protected static class SyncDelegate extends FxAccountSyncDelegate {
protected final CountDownLatch latch; @Override
protected final SyncResult syncResult; public void handleSuccess() {
protected final AndroidFxAccount fxAccount; 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; protected final Collection<String> stageNamesToSync;
public SyncDelegate(CountDownLatch latch, SyncResult syncResult, AndroidFxAccount fxAccount, Collection<String> stageNamesToSync) { public SyncDelegate(CountDownLatch latch, SyncResult syncResult, AndroidFxAccount fxAccount, Collection<String> stageNamesToSync) {
if (latch == null) { super(latch, syncResult, fxAccount);
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;
this.stageNamesToSync = Collections.unmodifiableCollection(stageNamesToSync); 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() { public Collection<String> getStageNamesToSync() {
return this.stageNamesToSync; return this.stageNamesToSync;
} }

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

@ -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();
}
}

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

@ -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);
}
}

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

@ -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();
}
}

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

@ -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"
/>

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

@ -9,6 +9,13 @@
android:name="android.accounts.AccountAuthenticator" android:name="android.accounts.AccountAuthenticator"
android:resource="@xml/fxaccount_authenticator" /> android:resource="@xml/fxaccount_authenticator" />
</service> </service>
<service
android:exported="false"
android:name="org.mozilla.gecko.fxa.receivers.FxAccountDeletedService" >
</service>
<!-- Firefox Sync. -->
<service <service
android:exported="false" android:exported="false"
android:name="org.mozilla.gecko.fxa.sync.FxAccountSyncService" > android:name="org.mozilla.gecko.fxa.sync.FxAccountSyncService" >
@ -20,7 +27,18 @@
android:name="android.content.SyncAdapter" android:name="android.content.SyncAdapter"
android:resource="@xml/fxaccount_syncadapter" /> android:resource="@xml/fxaccount_syncadapter" />
</service> </service>
<!-- Reading List. -->
#ifdef MOZ_ANDROID_READING_LIST_SERVICE
<service <service
android:exported="false" 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> </service>
#endif