Bug 763020 and Bug 763021 - Make Android Sync's Send Tab activity request an immediate clients-only sync. r=rnewman

--HG--
extra : rebase_source : 7b0074c68e2fa92f59cc19c2720f828455af6c3a
This commit is contained in:
Nick Alexander 2012-06-25 13:31:42 -07:00
parent 9403ce7ce0
commit f9fd0cd40d
9 changed files with 209 additions and 6 deletions

View File

@ -162,6 +162,11 @@ public class GlobalSession implements CredentialsSource, PrefsSource, HttpRespon
registerCommands();
prepareStages();
Collection<String> knownStageNames = new HashSet<String>();
for (Stage stage : Stage.getNamedStages()) {
knownStageNames.add(stage.getRepositoryName());
}
config.stagesToSync = Utils.getStagesToSyncFromBundle(knownStageNames, extras);
// TODO: data-driven plan for the sync, referring to prepareStages.
}

View File

@ -6,6 +6,7 @@ package org.mozilla.gecko.sync;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
@ -195,7 +196,19 @@ public class SyncConfiguration implements CredentialsSource {
* Copied from latest downloaded meta/global record and used to generate a
* fresh meta/global record for upload.
*/
public Set<String> enabledEngineNames;
public Set<String> enabledEngineNames;
/**
* Names of stages to sync <it>this sync</it>, or <code>null</code> to sync
* all known stages.
* <p>
* Generated <it>each sync</it> from extras bundle passed to
* <code>SyncAdapter.onPerformSync</code> and not persisted.
* <p>
* Not synchronized! Set this exactly once per global session and don't modify
* it -- especially not from multiple threads.
*/
public Collection<String> stagesToSync;
// Fields that maintain a reference to a SharedPreferences instance, used for
// persistence.

View File

@ -14,6 +14,7 @@ import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.TreeMap;
@ -21,9 +22,11 @@ import java.util.TreeMap;
import org.json.simple.JSONArray;
import org.mozilla.apache.commons.codec.binary.Base32;
import org.mozilla.apache.commons.codec.binary.Base64;
import org.mozilla.gecko.sync.setup.Constants;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
public class Utils {
@ -337,4 +340,105 @@ public class Utils {
public static String toCommaSeparatedString(Collection<String> items) {
return toDelimitedString(", ", items);
}
/**
* Names of stages to sync: (ALL intersect SYNC) intersect (ALL minus SKIP).
*
* @param knownStageNames collection of known stage names (set ALL above).
* @param toSync set SYNC above, or <code>null</code> to sync all known stages.
* @param toSkip set SKIP above, or <code>null</code> to not skip any stages.
* @return stage names.
*/
public static Collection<String> getStagesToSync(final Collection<String> knownStageNames, Collection<String> toSync, Collection<String> toSkip) {
if (toSkip == null) {
toSkip = new HashSet<String>();
} else {
toSkip = new HashSet<String>(toSkip);
}
if (toSync == null) {
toSync = new HashSet<String>(knownStageNames);
} else {
toSync = new HashSet<String>(toSync);
}
toSync.retainAll(knownStageNames);
toSync.removeAll(toSkip);
return toSync;
}
/**
* Get names of stages to sync: (ALL intersect SYNC) intersect (ALL minus SKIP).
*
* @param knownStageNames collection of known stage names (set ALL above).
* @param bundle
* a <code>Bundle</code> instance (possibly null) optionally containing keys
* <code>EXTRAS_KEY_STAGES_TO_SYNC</code> (set SYNC above) and
* <code>EXTRAS_KEY_STAGES_TO_SKIP</code> (set SKIP above).
* @return stage names.
*/
public static Collection<String> getStagesToSyncFromBundle(final Collection<String> knownStageNames, final Bundle extras) {
if (extras == null) {
return knownStageNames;
}
String toSyncString = extras.getString(Constants.EXTRAS_KEY_STAGES_TO_SYNC);
String toSkipString = extras.getString(Constants.EXTRAS_KEY_STAGES_TO_SKIP);
if (toSyncString == null && toSkipString == null) {
return knownStageNames;
}
ArrayList<String> toSync = null;
ArrayList<String> toSkip = null;
if (toSyncString != null) {
try {
toSync = new ArrayList<String>(ExtendedJSONObject.parseJSONObject(toSyncString).keySet());
} catch (Exception e) {
Logger.warn(LOG_TAG, "Got exception parsing stages to sync: '" + toSyncString + "'.", e);
}
}
if (toSkipString != null) {
try {
toSkip = new ArrayList<String>(ExtendedJSONObject.parseJSONObject(toSkipString).keySet());
} catch (Exception e) {
Logger.warn(LOG_TAG, "Got exception parsing stages to skip: '" + toSkipString + "'.", e);
}
}
Logger.info(LOG_TAG, "Asked to sync '" + Utils.toCommaSeparatedString(toSync) +
"' and to skip '" + Utils.toCommaSeparatedString(toSkip) + "'.");
return getStagesToSync(knownStageNames, toSync, toSkip);
}
/**
* Put names of stages to sync and to skip into sync extras bundle.
*
* @param bundle
* a <code>Bundle</code> instance (possibly null).
* @param stagesToSync
* collection of stage names to sync: key
* <code>EXTRAS_KEY_STAGES_TO_SYNC</code>; ignored if <code>null</code>.
* @param stagesToSkip
* collection of stage names to skip: key
* <code>EXTRAS_KEY_STAGES_TO_SKIP</code>; ignored if <code>null</code>.
*/
public static void putStageNamesToSync(final Bundle bundle, final String[] stagesToSync, final String[] stagesToSkip) {
if (bundle == null) {
return;
}
if (stagesToSync != null) {
ExtendedJSONObject o = new ExtendedJSONObject();
for (String stageName : stagesToSync) {
o.put(stageName, 0);
}
bundle.putString(Constants.EXTRAS_KEY_STAGES_TO_SYNC, o.toJSONString());
}
if (stagesToSkip != null) {
ExtendedJSONObject o = new ExtendedJSONObject();
for (String stageName : stagesToSkip) {
o.put(stageName, 0);
}
bundle.putString(Constants.EXTRAS_KEY_STAGES_TO_SKIP, o.toJSONString());
}
}
}

View File

@ -18,6 +18,24 @@ public class Constants {
public static final String NUM_CLIENTS = "account.numClients";
public static final String DATA_ENABLE_ON_UPGRADE = "data.enableOnUpgrade";
/**
* Key in sync extras bundle specifying stages to sync this sync session.
* <p>
* Corresponding value should be a String JSON-encoding an object, the keys of
* which are the stage names to sync. For example:
* <code>"{ \"stageToSync\": 0 }"</code>.
*/
public static final String EXTRAS_KEY_STAGES_TO_SYNC = "sync";
/**
* Key in sync extras bundle specifying stages to skip this sync session.
* <p>
* Corresponding value should be a String JSON-encoding an object, the keys of
* which are the stage names to skip. For example:
* <code>"{ \"stageToSkip\": 0 }"</code>.
*/
public static final String EXTRAS_KEY_STAGES_TO_SKIP = "skip";
// Constants for Activities.
public static final String INTENT_EXTRA_IS_SETUP = "isSetup";
public static final String INTENT_EXTRA_IS_PAIR = "isPair";

View File

@ -1,3 +1,7 @@
/* 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.setup.activities;
import java.util.List;
@ -11,6 +15,8 @@ import org.mozilla.gecko.sync.repositories.NullCursorException;
import org.mozilla.gecko.sync.repositories.android.ClientsDatabaseAccessor;
import org.mozilla.gecko.sync.repositories.domain.ClientRecord;
import org.mozilla.gecko.sync.setup.Constants;
import org.mozilla.gecko.sync.stage.SyncClientsEngineStage;
import org.mozilla.gecko.sync.syncadapter.SyncAdapter;
import android.accounts.Account;
import android.accounts.AccountManager;
@ -105,6 +111,9 @@ public class SendTabActivity extends Activity {
for (int i = 0; i < guids.length; i++) {
processor.sendURIToClientForDisplay(uri, guids[i], title, getAccountGUID(), getApplicationContext());
}
Logger.info(LOG_TAG, "Requesting immediate clients stage sync.");
SyncAdapter.requestImmediateSync(localAccount, new String[] { SyncClientsEngineStage.COLLECTION_NAME });
}
}.start();

View File

@ -23,7 +23,7 @@ public interface GlobalSyncStage {
ensureSpecialRecords,
updateEngineTimestamps,
*/
syncClientsEngine("clients"),
syncClientsEngine(SyncClientsEngineStage.STAGE_NAME),
/*
processFirstSyncPref,
processClientCommands,

View File

@ -80,8 +80,23 @@ public abstract class ServerSyncStage implements
// Fall through; null engineSettings will pass below.
}
// We can be disabled by the server's meta/global record, or malformed in the server's meta/global record.
// We catch the subclasses of MetaGlobalException to trigger various resets and wipes in execute().
return session.engineIsEnabled(this.getEngineName(), engineSettings);
boolean enabledInMetaGlobal = session.engineIsEnabled(this.getEngineName(), engineSettings);
if (!enabledInMetaGlobal) {
Logger.debug(LOG_TAG, "Stage " + this.getEngineName() + " disabled by server meta/global.");
return false;
}
// We can also be disabled just for this sync.
if (session.config.stagesToSync == null) {
return true;
}
boolean enabledThisSync = session.config.stagesToSync.contains(this.getEngineName()); // For ServerSyncStage, stage name == engine name.
if (!enabledThisSync) {
Logger.debug(LOG_TAG, "Stage " + this.getEngineName() + " disabled just for this sync.");
}
return enabledThisSync;
}
protected EngineSettings getEngineSettings() throws NonObjectJSONException, IOException, ParseException {
@ -160,7 +175,7 @@ public abstract class ServerSyncStage implements
* Reset timestamps and possibly set syncID.
* @param syncID if non-null, new syncID to persist.
*/
public void resetLocal(String syncID) {
protected void resetLocal(String syncID) {
// Clear both timestamps.
SynchronizerConfiguration config;
try {
@ -437,7 +452,7 @@ public abstract class ServerSyncStage implements
try {
if (!this.isEnabled()) {
Logger.info(LOG_TAG, "Stage " + name + " disabled; skipping.");
Logger.info(LOG_TAG, "Skipping stage " + name + ".");
session.advance();
return;
}

View File

@ -44,6 +44,7 @@ public class SyncClientsEngineStage implements GlobalSyncStage {
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;
@ -324,6 +325,15 @@ public class SyncClientsEngineStage implements GlobalSyncStage {
@Override
public void execute() throws NoSuchStageException {
// We can be disabled just for this sync.
boolean disabledThisSync = session.config.stagesToSync != null &&
!session.config.stagesToSync.contains(STAGE_NAME);
if (disabledThisSync) {
Logger.debug(LOG_TAG, "Stage " + STAGE_NAME + " disabled just for this sync.");
session.advance();
return;
}
if (shouldDownload()) {
downloadClientRecords(); // Will kick off upload, too
} else {

View File

@ -10,6 +10,7 @@ import java.security.NoSuchAlgorithmException;
import java.util.concurrent.TimeUnit;
import org.json.simple.parser.ParseException;
import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.sync.AlreadySyncingException;
import org.mozilla.gecko.sync.GlobalConstants;
import org.mozilla.gecko.sync.GlobalSession;
@ -36,6 +37,7 @@ import android.accounts.AuthenticatorException;
import android.accounts.OperationCanceledException;
import android.content.AbstractThreadedSyncAdapter;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
@ -221,6 +223,29 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter implements GlobalSe
return delayMilliseconds() > 0;
}
/**
* Asynchronously request an immediate sync, optionally syncing only the given
* named stages.
* <p>
* Returns immediately.
*
* @param account
* the Android <code>Account</code> instance to sync.
* @param stageNames
* stage names to sync, or <code>null</code> to sync all known stages.
*/
public static void requestImmediateSync(final Account account, final String[] stageNames) {
if (account == null) {
Logger.warn(LOG_TAG, "Not requesting immediate sync because Android Account is null.");
return;
}
final Bundle extras = new Bundle();
Utils.putStageNamesToSync(extras, stageNames, null);
extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
ContentResolver.requestSync(account, BrowserContract.AUTHORITY, extras);
}
@Override
public void onPerformSync(final Account account,
final Bundle extras,
@ -234,7 +259,11 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter implements GlobalSe
this.syncResult = syncResult;
this.localAccount = account;
thisSyncIsForced = (extras != null) && (extras.getBoolean("force", false));
Log.i(LOG_TAG, "Syncing client named " + getClientName() +
" with client guid " + getAccountGUID() +
" (sync account has " + getClientsCount() + " clients).");
thisSyncIsForced = (extras != null) && (extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false));
long delay = delayMilliseconds();
if (delay > 0) {
if (thisSyncIsForced) {