зеркало из https://github.com/mozilla/gecko-dev.git
Bug 761682, Bug 777973 - Version prefs; don't always invalidate auth token. r=rnewman
This commit is contained in:
Родитель
78d197e3b7
Коммит
aaf4a4129a
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -8,7 +8,7 @@
|
|||
|
||||
<ListView
|
||||
android:id="@+id/device_list"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/sendtab_top"
|
||||
android:layout_above="@+id/sendtab_bottom" >
|
||||
|
@ -25,4 +25,4 @@
|
|||
android:enabled="false"
|
||||
android:text="@string/sync_button_send" />
|
||||
</LinearLayout>
|
||||
</RelativeLayout>
|
||||
</RelativeLayout>
|
|
@ -0,0 +1,55 @@
|
|||
/* 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;
|
||||
|
||||
import android.content.SyncResult;
|
||||
|
||||
/**
|
||||
* There was a problem with the Sync account's credentials: bad username,
|
||||
* missing password, malformed sync key, etc.
|
||||
*/
|
||||
public abstract class CredentialException extends SyncException {
|
||||
private static final long serialVersionUID = 833010553314100538L;
|
||||
|
||||
public CredentialException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public CredentialException(final Throwable e) {
|
||||
super(e);
|
||||
}
|
||||
|
||||
public void updateStats(GlobalSession globalSession, SyncResult syncResult) {
|
||||
syncResult.stats.numAuthExceptions += 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* No credentials at all.
|
||||
*/
|
||||
public static class MissingAllCredentialsException extends CredentialException {
|
||||
private static final long serialVersionUID = 3763937096217604611L;
|
||||
|
||||
public MissingAllCredentialsException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public MissingAllCredentialsException(final Throwable e) {
|
||||
super(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Some credential is missing.
|
||||
*/
|
||||
public static class MissingCredentialException extends CredentialException {
|
||||
private static final long serialVersionUID = -7543031216547596248L;
|
||||
|
||||
public final String missingCredential;
|
||||
|
||||
public MissingCredentialException(final String missingCredential) {
|
||||
this.missingCredential = missingCredential;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -216,6 +216,9 @@ public class SyncConfiguration implements CredentialsSource {
|
|||
public String prefsPath;
|
||||
public PrefsSource prefsSource;
|
||||
|
||||
public static final String PREF_PREFS_VERSION = "prefs.version";
|
||||
public static final long CURRENT_PREFS_VERSION = 1;
|
||||
|
||||
public static final String CLIENTS_COLLECTION_TIMESTAMP = "serverClientsTimestamp"; // When the collection was touched.
|
||||
public static final String CLIENT_RECORD_TIMESTAMP = "serverClientRecordTimestamp"; // When our record was touched.
|
||||
|
||||
|
@ -223,6 +226,13 @@ public class SyncConfiguration implements CredentialsSource {
|
|||
public static final String PREF_SYNC_ID = "syncID";
|
||||
public static final String PREF_ENABLED_ENGINE_NAMES = "enabledEngineNames";
|
||||
|
||||
public static final String PREF_EARLIEST_NEXT_SYNC = "earliestnextsync";
|
||||
public static final String PREF_CLUSTER_URL_IS_STALE = "clusterurlisstale";
|
||||
|
||||
public static final String PREF_ACCOUNT_GUID = "account.guid";
|
||||
public static final String PREF_CLIENT_NAME = "account.clientName";
|
||||
public static final String PREF_NUM_CLIENTS = "account.numClients";
|
||||
|
||||
/**
|
||||
* Create a new SyncConfiguration instance. Pass in a PrefsSource to
|
||||
* provide access to preferences.
|
||||
|
|
|
@ -7,14 +7,25 @@ package org.mozilla.gecko.sync;
|
|||
import android.content.SyncResult;
|
||||
|
||||
public abstract class SyncException extends Exception {
|
||||
public Exception cause = null;
|
||||
public SyncException(Exception ex) {
|
||||
cause = ex;
|
||||
}
|
||||
private static final long serialVersionUID = -6928990004393234738L;
|
||||
|
||||
public SyncException() {
|
||||
super();
|
||||
}
|
||||
|
||||
private static final long serialVersionUID = -6928990004393234738L;
|
||||
public SyncException(final Throwable e) {
|
||||
super(e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update sync result statistics with information particular to this
|
||||
* exception.
|
||||
*
|
||||
* @param globalSession
|
||||
* current session, or null.
|
||||
* @param syncResult
|
||||
* Android sync result to update.
|
||||
*/
|
||||
public void updateStats(GlobalSession globalSession, SyncResult syncResult) {
|
||||
// Assume storage error.
|
||||
// TODO: this logic is overly simplistic.
|
||||
|
|
|
@ -215,34 +215,34 @@ public class Utils {
|
|||
return sha1Base32(account.toLowerCase(Locale.US));
|
||||
}
|
||||
|
||||
public static SharedPreferences getSharedPreferences(final Context context, final String product, final String username, final String serverURL, final String profile, final long version)
|
||||
throws NoSuchAlgorithmException, UnsupportedEncodingException {
|
||||
String prefsPath = getPrefsPath(product, username, serverURL, profile, version);
|
||||
return context.getSharedPreferences(prefsPath, SHARED_PREFERENCES_MODE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get shared preferences path for a Sync account.
|
||||
*
|
||||
* @param product the Firefox Sync product package name (like "org.mozilla.firefox").
|
||||
* @param username the Sync account name, optionally encoded with <code>Utils.usernameFromAccount</code>.
|
||||
* @param serverURL the Sync account server URL.
|
||||
* @param profile the Firefox profile name.
|
||||
* @param version the version of preferences to reference.
|
||||
* @return the path.
|
||||
* @throws NoSuchAlgorithmException
|
||||
* @throws UnsupportedEncodingException
|
||||
*/
|
||||
public static String getPrefsPath(String username, String serverURL)
|
||||
throws NoSuchAlgorithmException, UnsupportedEncodingException {
|
||||
return "sync.prefs." + sha1Base32(serverURL + ":" + usernameFromAccount(username));
|
||||
}
|
||||
public static String getPrefsPath(final String product, final String username, final String serverURL, final String profile, final long version)
|
||||
throws NoSuchAlgorithmException, UnsupportedEncodingException {
|
||||
final String encodedAccount = sha1Base32(serverURL + ":" + usernameFromAccount(username));
|
||||
|
||||
/**
|
||||
* Get shared preferences for a Sync account.
|
||||
*
|
||||
* @param context Android <code>Context</code>.
|
||||
* @param username the Sync account name, optionally encoded with <code>Utils.usernameFromAccount</code>.
|
||||
* @param serverURL the Sync account server URL.
|
||||
* @return a <code>SharedPreferences</code> instance.
|
||||
* @throws NoSuchAlgorithmException
|
||||
* @throws UnsupportedEncodingException
|
||||
*/
|
||||
public static SharedPreferences getSharedPreferences(Context context, String username, String serverURL) throws NoSuchAlgorithmException, UnsupportedEncodingException {
|
||||
String prefsPath = getPrefsPath(username, serverURL);
|
||||
Logger.debug(LOG_TAG, "Shared preferences: " + prefsPath);
|
||||
return context.getSharedPreferences(prefsPath, SHARED_PREFERENCES_MODE);
|
||||
if (version <= 0) {
|
||||
return "sync.prefs." + encodedAccount;
|
||||
} else {
|
||||
final String sanitizedProduct = product.replace('.', '!').replace(' ', '!');
|
||||
return "sync.prefs." + sanitizedProduct + "." + encodedAccount + "." + profile + "." + version;
|
||||
}
|
||||
}
|
||||
|
||||
public static void addToIndexBucketMap(TreeMap<Long, ArrayList<String>> map, long index, String value) {
|
||||
|
|
|
@ -0,0 +1,382 @@
|
|||
/* 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.config;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.mozilla.gecko.sync.Logger;
|
||||
import org.mozilla.gecko.sync.SyncConfiguration;
|
||||
import org.mozilla.gecko.sync.Utils;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.accounts.AccountManager;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.SharedPreferences.Editor;
|
||||
|
||||
/**
|
||||
* Migrate Sync preferences between versions.
|
||||
* <p>
|
||||
* The original preferences were un-versioned; we refer to that as "version 0".
|
||||
* The original preferences were stored in three places:
|
||||
* <ul>
|
||||
* <li>most prefs were kept in per-Sync account Android shared prefs;</li>
|
||||
* <li>some prefs were kept in per-App Android shared prefs;</li>
|
||||
* <li>some client prefs were kept in the (assumed unique) Android Account.</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* Post version 0, all preferences are stored in per-Sync account Android shared prefs.
|
||||
*/
|
||||
public class ConfigurationMigrator {
|
||||
public static final String LOG_TAG = "ConfigMigrator";
|
||||
|
||||
/**
|
||||
* Copy and rename preferences.
|
||||
*
|
||||
* @param from source.
|
||||
* @param to sink.
|
||||
* @param map map from old preference names to new preference names.
|
||||
* @return the number of preferences migrated.
|
||||
*/
|
||||
protected static int copyPreferences(final SharedPreferences from, final Map<String, String> map, final Editor to) {
|
||||
int count = 0;
|
||||
|
||||
// SharedPreferences has no way to get a key/value pair without specifying the value type, so we do this instead.
|
||||
for (Entry<String, ?> entry : from.getAll().entrySet()) {
|
||||
String fromKey = entry.getKey();
|
||||
String toKey = map.get(fromKey);
|
||||
if (toKey == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Object value = entry.getValue();
|
||||
if (value instanceof Boolean) {
|
||||
to.putBoolean(toKey, ((Boolean) value).booleanValue());
|
||||
} else if (value instanceof Float) {
|
||||
to.putFloat(toKey, ((Float) value).floatValue());
|
||||
} else if (value instanceof Integer) {
|
||||
to.putInt(toKey, ((Integer) value).intValue());
|
||||
} else if (value instanceof Long) {
|
||||
to.putLong(toKey, ((Long) value).longValue());
|
||||
} else if (value instanceof String) {
|
||||
to.putString(toKey, (String) value);
|
||||
} else {
|
||||
// Do nothing -- perhaps SharedPreferences accepts types we don't know about.
|
||||
}
|
||||
|
||||
if (Logger.LOG_PERSONAL_INFORMATION) {
|
||||
Logger.debug(LOG_TAG, "Migrated '" + fromKey + "' to '" + toKey + "' (" + value + ").");
|
||||
} else {
|
||||
Logger.debug(LOG_TAG, "Migrated '" + fromKey + "' to '" + toKey + "'.");
|
||||
}
|
||||
count += 1;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
protected final static String V0_PREF_CLUSTER_URL_IS_STALE = "clusterurlisstale";
|
||||
protected final static String V1_PREF_CLUSTER_URL_IS_STALE = V0_PREF_CLUSTER_URL_IS_STALE;
|
||||
protected final static String V0_PREF_EARLIEST_NEXT_SYNC = "earliestnextsync";
|
||||
protected final static String V1_PREF_EARLIEST_NEXT_SYNC = V0_PREF_EARLIEST_NEXT_SYNC;
|
||||
|
||||
/**
|
||||
* Extract version 0 preferences from per-App Android shared prefs and write to version 1 per-Sync account shared prefs.
|
||||
*
|
||||
* @param from per-App version 0 Android shared prefs.
|
||||
* @param to per-Sync account version 1 shared prefs.
|
||||
* @return the number of preferences migrated.
|
||||
* @throws Exception
|
||||
*/
|
||||
protected static int upgradeGlobals0to1(final SharedPreferences from, final SharedPreferences to) throws Exception {
|
||||
Map<String, String> map = new HashMap<String, String>();
|
||||
map.put(V0_PREF_CLUSTER_URL_IS_STALE, V1_PREF_CLUSTER_URL_IS_STALE);
|
||||
map.put(V0_PREF_EARLIEST_NEXT_SYNC, V1_PREF_EARLIEST_NEXT_SYNC);
|
||||
|
||||
Editor editor = to.edit();
|
||||
int count = copyPreferences(from, map, editor);
|
||||
if (count > 0) {
|
||||
editor.commit();
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract version 1 per-Sync account shared prefs and write to version 0 preferences from per-App Android shared prefs.
|
||||
*
|
||||
* @param from per-Sync account version 1 shared prefs.
|
||||
* @param to per-App version 0 Android shared prefs.
|
||||
* @return the number of preferences migrated.
|
||||
* @throws Exception
|
||||
*/
|
||||
protected static int downgradeGlobals1to0(final SharedPreferences from, final SharedPreferences to) throws Exception {
|
||||
Map<String, String> map = new HashMap<String, String>();
|
||||
map.put(V1_PREF_CLUSTER_URL_IS_STALE, V0_PREF_CLUSTER_URL_IS_STALE);
|
||||
map.put(V1_PREF_EARLIEST_NEXT_SYNC, V0_PREF_EARLIEST_NEXT_SYNC);
|
||||
|
||||
Editor editor = to.edit();
|
||||
int count = copyPreferences(from, map, editor);
|
||||
if (count > 0) {
|
||||
editor.commit();
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
protected static final String V0_PREF_ACCOUNT_GUID = "account.guid";
|
||||
protected static final String V1_PREF_ACCOUNT_GUID = V0_PREF_ACCOUNT_GUID;
|
||||
protected static final String V0_PREF_CLIENT_NAME = "account.clientName";
|
||||
protected static final String V1_PREF_CLIENT_NAME = V0_PREF_CLIENT_NAME;
|
||||
protected static final String V0_PREF_NUM_CLIENTS = "account.numClients";
|
||||
protected static final String V1_PREF_NUM_CLIENTS = V0_PREF_NUM_CLIENTS;
|
||||
|
||||
/**
|
||||
* Extract version 0 per-Android account user data and write to version 1 per-Sync account shared prefs.
|
||||
*
|
||||
* @param accountManager Android account manager.
|
||||
* @param account Android account.
|
||||
* @param to per-Sync account version 1 shared prefs.
|
||||
* @return the number of preferences migrated.
|
||||
* @throws Exception
|
||||
*/
|
||||
protected static int upgradeAndroidAccount0to1(final AccountManager accountManager, final Account account, final SharedPreferences to) throws Exception {
|
||||
final String V0_PREF_ACCOUNT_GUID = "account.guid";
|
||||
final String V1_PREF_ACCOUNT_GUID = V0_PREF_ACCOUNT_GUID;
|
||||
final String V0_PREF_CLIENT_NAME = "account.clientName";
|
||||
final String V1_PREF_CLIENT_NAME = V0_PREF_CLIENT_NAME;
|
||||
final String V0_PREF_NUM_CLIENTS = "account.numClients";
|
||||
final String V1_PREF_NUM_CLIENTS = V0_PREF_NUM_CLIENTS;
|
||||
|
||||
String accountGUID = null;
|
||||
String clientName = null;
|
||||
long numClients = -1;
|
||||
try {
|
||||
accountGUID = accountManager.getUserData(account, V0_PREF_ACCOUNT_GUID);
|
||||
} catch (Exception e) {
|
||||
// Do nothing.
|
||||
}
|
||||
try {
|
||||
clientName = accountManager.getUserData(account, V0_PREF_CLIENT_NAME);
|
||||
} catch (Exception e) {
|
||||
// Do nothing.
|
||||
}
|
||||
try {
|
||||
numClients = Long.parseLong(accountManager.getUserData(account, V0_PREF_NUM_CLIENTS));
|
||||
} catch (Exception e) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
final Editor editor = to.edit();
|
||||
|
||||
int count = 0;
|
||||
if (accountGUID != null) {
|
||||
final String fromKey = V0_PREF_ACCOUNT_GUID;
|
||||
final String toKey = V1_PREF_ACCOUNT_GUID;
|
||||
if (Logger.LOG_PERSONAL_INFORMATION) {
|
||||
Logger.debug(LOG_TAG, "Migrated '" + fromKey + "' to '" + toKey + "' (" + accountGUID + ").");
|
||||
} else {
|
||||
Logger.debug(LOG_TAG, "Migrated '" + fromKey + "' to '" + toKey + "'.");
|
||||
}
|
||||
editor.putString(toKey, accountGUID);
|
||||
count += 1;
|
||||
}
|
||||
if (clientName != null) {
|
||||
final String fromKey = V0_PREF_CLIENT_NAME;
|
||||
final String toKey = V1_PREF_CLIENT_NAME;
|
||||
if (Logger.LOG_PERSONAL_INFORMATION) {
|
||||
Logger.debug(LOG_TAG, "Migrated '" + fromKey + "' to '" + toKey + "' (" + clientName + ").");
|
||||
} else {
|
||||
Logger.debug(LOG_TAG, "Migrated '" + fromKey + "' to '" + toKey + "'.");
|
||||
}
|
||||
editor.putString(toKey, clientName);
|
||||
count += 1;
|
||||
}
|
||||
if (numClients > -1) {
|
||||
final String fromKey = V0_PREF_NUM_CLIENTS;
|
||||
final String toKey = V1_PREF_NUM_CLIENTS;
|
||||
if (Logger.LOG_PERSONAL_INFORMATION) {
|
||||
Logger.debug(LOG_TAG, "Migrated '" + fromKey + "' to '" + toKey + "' (" + numClients + ").");
|
||||
} else {
|
||||
Logger.debug(LOG_TAG, "Migrated '" + fromKey + "' to '" + toKey + "'.");
|
||||
}
|
||||
editor.putLong(toKey, numClients);
|
||||
count += 1;
|
||||
}
|
||||
|
||||
if (count > 0) {
|
||||
editor.commit();
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract version 1 per-Sync account shared prefs and write to version 0 per-Android account user data.
|
||||
*
|
||||
* @param from per-Sync account version 1 shared prefs.
|
||||
* @param accountManager Android account manager.
|
||||
* @param account Android account.
|
||||
* @return the number of preferences migrated.
|
||||
* @throws Exception
|
||||
*/
|
||||
protected static int downgradeAndroidAccount1to0(final SharedPreferences from, final AccountManager accountManager, final Account account) throws Exception {
|
||||
final String accountGUID = from.getString(V1_PREF_ACCOUNT_GUID, null);
|
||||
final String clientName = from.getString(V1_PREF_CLIENT_NAME, null);
|
||||
final long numClients = from.getLong(V1_PREF_NUM_CLIENTS, -1L);
|
||||
|
||||
int count = 0;
|
||||
if (accountGUID != null) {
|
||||
Logger.debug(LOG_TAG, "Migrated account GUID.");
|
||||
accountManager.setUserData(account, V0_PREF_ACCOUNT_GUID, accountGUID);
|
||||
count += 1;
|
||||
}
|
||||
if (clientName != null) {
|
||||
Logger.debug(LOG_TAG, "Migrated client name.");
|
||||
accountManager.setUserData(account, V1_PREF_CLIENT_NAME, clientName);
|
||||
count += 1;
|
||||
}
|
||||
if (numClients > -1) {
|
||||
Logger.debug(LOG_TAG, "Migrated clients count.");
|
||||
accountManager.setUserData(account, V1_PREF_NUM_CLIENTS, new Long(numClients).toString());
|
||||
count += 1;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract version 0 per-Android account user data and write to version 1 per-Sync account shared prefs.
|
||||
*
|
||||
* @param from per-Sync account version 0 shared prefs.
|
||||
* @param to per-Sync account version 1 shared prefs.
|
||||
* @return the number of preferences migrated.
|
||||
* @throws Exception
|
||||
*/
|
||||
protected static int upgradeShared0to1(final SharedPreferences from, final SharedPreferences to) {
|
||||
final Map<String, String> map = new HashMap<String, String>();
|
||||
final String[] prefs = new String [] {
|
||||
"syncID",
|
||||
"clusterURL",
|
||||
"enabledEngineNames",
|
||||
|
||||
"metaGlobalLastModified", "metaGlobalServerResponseBody",
|
||||
|
||||
"crypto5KeysLastModified", "crypto5KeysServerResponseBody",
|
||||
|
||||
"serverClientsTimestamp", "serverClientRecordTimestamp",
|
||||
|
||||
"forms.remote", "forms.local", "forms.syncID",
|
||||
"tabs.remote", "tabs.local", "tabs.syncID",
|
||||
"passwords.remote", "passwords.local", "passwords.syncID",
|
||||
"history.remote", "history.local", "history.syncID",
|
||||
"bookmarks.remote", "bookmarks.local", "bookmarks.syncID",
|
||||
};
|
||||
for (String pref : prefs) {
|
||||
map.put(pref, pref);
|
||||
}
|
||||
|
||||
Editor editor = to.edit();
|
||||
int count = copyPreferences(from, map, editor);
|
||||
if (count > 0) {
|
||||
editor.commit();
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract version 1 per-Sync account shared prefs and write to version 0 per-Android account user data.
|
||||
*
|
||||
* @param from per-Sync account version 1 shared prefs.
|
||||
* @param to per-Sync account version 0 shared prefs.
|
||||
* @return the number of preferences migrated.
|
||||
* @throws Exception
|
||||
*/
|
||||
protected static int downgradeShared1to0(final SharedPreferences from, final SharedPreferences to) {
|
||||
// Strictly a copy, no re-naming, no deletions -- so just invert.
|
||||
return upgradeShared0to1(from, to);
|
||||
}
|
||||
|
||||
public static void upgrade0to1(final Context context, final AccountManager accountManager, final Account account,
|
||||
final String product, final String username, final String serverURL, final String profile) throws Exception {
|
||||
|
||||
final String GLOBAL_SHARED_PREFS = "sync.prefs.global";
|
||||
|
||||
final SharedPreferences globalPrefs = context.getSharedPreferences(GLOBAL_SHARED_PREFS, Utils.SHARED_PREFERENCES_MODE);
|
||||
final SharedPreferences accountPrefs = Utils.getSharedPreferences(context, product, username, serverURL, profile, 0);
|
||||
final SharedPreferences newPrefs = Utils.getSharedPreferences(context, product, username, serverURL, profile, 1);
|
||||
|
||||
upgradeGlobals0to1(globalPrefs, newPrefs);
|
||||
upgradeAndroidAccount0to1(accountManager, account, newPrefs);
|
||||
upgradeShared0to1(accountPrefs, newPrefs);
|
||||
}
|
||||
|
||||
public static void downgrade1to0(final Context context, final AccountManager accountManager, final Account account,
|
||||
final String product, final String username, final String serverURL, final String profile) throws Exception {
|
||||
|
||||
final String GLOBAL_SHARED_PREFS = "sync.prefs.global";
|
||||
|
||||
final SharedPreferences globalPrefs = context.getSharedPreferences(GLOBAL_SHARED_PREFS, Utils.SHARED_PREFERENCES_MODE);
|
||||
final SharedPreferences accountPrefs = Utils.getSharedPreferences(context, product, username, serverURL, profile, 0);
|
||||
final SharedPreferences oldPrefs = Utils.getSharedPreferences(context, product, username, serverURL, profile, 1);
|
||||
|
||||
downgradeGlobals1to0(oldPrefs, globalPrefs);
|
||||
downgradeAndroidAccount1to0(oldPrefs, accountManager, account);
|
||||
downgradeShared1to0(oldPrefs, accountPrefs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate, if necessary, existing prefs to a certain version.
|
||||
* <p>
|
||||
* Stores current prefs version in Android shared prefs with root
|
||||
* "sync.prefs.version", which corresponds to the file
|
||||
* "sync.prefs.version.xml".
|
||||
*
|
||||
* @param desiredVersion
|
||||
* version to finish it.
|
||||
* @param context
|
||||
* @param accountManager
|
||||
* @param account
|
||||
* @param product
|
||||
* @param username
|
||||
* @param serverURL
|
||||
* @param profile
|
||||
* @throws Exception
|
||||
*/
|
||||
public static void ensurePrefsAreVersion(final long desiredVersion,
|
||||
final Context context, final AccountManager accountManager, final Account account,
|
||||
final String product, final String username, final String serverURL, final String profile) throws Exception {
|
||||
if (desiredVersion < 0 || desiredVersion > SyncConfiguration.CURRENT_PREFS_VERSION) {
|
||||
throw new IllegalArgumentException("Cannot migrate to unknown version " + desiredVersion + ".");
|
||||
}
|
||||
|
||||
SharedPreferences versionPrefs = context.getSharedPreferences("sync.prefs.version", Utils.SHARED_PREFERENCES_MODE);
|
||||
|
||||
// We default to 0 since clients getting this code for the first time will
|
||||
// not have "sync.prefs.version.xml" *at all*, and upgrading when all old
|
||||
// data is missing is expected to be safe.
|
||||
long currentVersion = versionPrefs.getLong(SyncConfiguration.PREF_PREFS_VERSION, 0);
|
||||
if (currentVersion == desiredVersion) {
|
||||
Logger.info(LOG_TAG, "Current version (" + currentVersion + ") is desired version; no need to migrate.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentVersion < 0 || currentVersion > SyncConfiguration.CURRENT_PREFS_VERSION) {
|
||||
throw new IllegalStateException("Cannot migrate from unknown version " + currentVersion + ".");
|
||||
}
|
||||
|
||||
// Now we're down to either version 0 or version 1.
|
||||
if (currentVersion == 0 && desiredVersion == 1) {
|
||||
Logger.info(LOG_TAG, "Upgrading from version 0 to version 1.");
|
||||
upgrade0to1(context, accountManager, account, product, username, serverURL, profile);
|
||||
} else if (currentVersion == 1 && desiredVersion == 0) {
|
||||
Logger.info(LOG_TAG, "Upgrading from version 0 to version 1.");
|
||||
upgrade0to1(context, accountManager, account, product, username, serverURL, profile);
|
||||
} else {
|
||||
Logger.warn(LOG_TAG, "Don't know how to migrate from version " + currentVersion + " to " + desiredVersion + ".");
|
||||
}
|
||||
|
||||
Logger.info(LOG_TAG, "Migrated from version " + currentVersion + " to version " + desiredVersion + ".");
|
||||
versionPrefs.edit().putLong(SyncConfiguration.PREF_PREFS_VERSION, desiredVersion).commit();
|
||||
}
|
||||
}
|
|
@ -4,10 +4,15 @@
|
|||
|
||||
package org.mozilla.gecko.sync.receivers;
|
||||
|
||||
import org.mozilla.gecko.sync.CredentialException;
|
||||
import org.mozilla.gecko.sync.GlobalConstants;
|
||||
import org.mozilla.gecko.sync.Logger;
|
||||
import org.mozilla.gecko.sync.SyncConfiguration;
|
||||
import org.mozilla.gecko.sync.ThreadPool;
|
||||
import org.mozilla.gecko.sync.config.ConfigurationMigrator;
|
||||
import org.mozilla.gecko.sync.setup.Constants;
|
||||
import org.mozilla.gecko.sync.setup.SyncAccounts;
|
||||
import org.mozilla.gecko.sync.setup.SyncAccounts.SyncAccountParameters;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.accounts.AccountManager;
|
||||
|
@ -26,8 +31,9 @@ public class UpgradeReceiver extends BroadcastReceiver {
|
|||
ThreadPool.run(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
AccountManager accountManager = AccountManager.get(context);
|
||||
Account[] accounts = accountManager.getAccounts();
|
||||
final AccountManager accountManager = AccountManager.get(context);
|
||||
final Account[] accounts = SyncAccounts.syncAccounts(context);
|
||||
|
||||
for (Account a : accounts) {
|
||||
if ("1".equals(accountManager.getUserData(a, Constants.DATA_ENABLE_ON_UPGRADE))) {
|
||||
SyncAccounts.setSyncAutomatically(a, true);
|
||||
|
@ -36,5 +42,40 @@ public class UpgradeReceiver extends BroadcastReceiver {
|
|||
}
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Bug 761682: migrate preferences forward.
|
||||
*/
|
||||
ThreadPool.run(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
AccountManager accountManager = AccountManager.get(context);
|
||||
final Account[] accounts = SyncAccounts.syncAccounts(context);
|
||||
|
||||
for (Account account : accounts) {
|
||||
Logger.info(LOG_TAG, "Migrating preferences on upgrade for Android account named " + account.name + ".");
|
||||
|
||||
SyncAccountParameters params;
|
||||
try {
|
||||
params = SyncAccounts.blockingFromAndroidAccountV0(context, accountManager, account);
|
||||
} catch (CredentialException e) {
|
||||
Logger.warn(LOG_TAG, "Caught exception fetching account parameters while trying to migrate preferences; ignoring.", e);
|
||||
continue;
|
||||
}
|
||||
|
||||
final String product = GlobalConstants.BROWSER_INTENT_PACKAGE;
|
||||
final String username = params.username;
|
||||
final String serverURL = params.serverURL;
|
||||
final String profile = "default";
|
||||
try {
|
||||
ConfigurationMigrator.ensurePrefsAreVersion(SyncConfiguration.CURRENT_PREFS_VERSION, context, accountManager, account,
|
||||
product, username, serverURL, profile);
|
||||
} catch (Exception e) {
|
||||
Logger.warn(LOG_TAG, "Caught exception trying to migrate preferences; ignoring.", e);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,11 +12,10 @@ public class Constants {
|
|||
public static final String OPTION_USERNAME = "option.username";
|
||||
public static final String AUTHTOKEN_TYPE_PLAIN = "auth.plain";
|
||||
public static final String OPTION_SERVER = "option.serverUrl";
|
||||
public static final String ACCOUNT_GUID = "account.guid";
|
||||
public static final String CLIENT_NAME = "account.clientName";
|
||||
public static final String NUM_CLIENTS = "account.numClients";
|
||||
public static final String DATA_ENABLE_ON_UPGRADE = "data.enableOnUpgrade";
|
||||
|
||||
public static final String DEFAULT_PROFILE = "default";
|
||||
|
||||
/**
|
||||
* Name of file to pickle current account preferences to each sync.
|
||||
* <p>
|
||||
|
|
|
@ -5,19 +5,26 @@
|
|||
package org.mozilla.gecko.sync.setup;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.mozilla.gecko.db.BrowserContract;
|
||||
import org.mozilla.gecko.sync.CredentialException;
|
||||
import org.mozilla.gecko.sync.ExtendedJSONObject;
|
||||
import org.mozilla.gecko.sync.GlobalConstants;
|
||||
import org.mozilla.gecko.sync.Logger;
|
||||
import org.mozilla.gecko.sync.SyncConfiguration;
|
||||
import org.mozilla.gecko.sync.ThreadPool;
|
||||
import org.mozilla.gecko.sync.Utils;
|
||||
import org.mozilla.gecko.sync.config.AccountPickler;
|
||||
import org.mozilla.gecko.sync.repositories.android.RepoUtils;
|
||||
import org.mozilla.gecko.sync.syncadapter.SyncAdapter;
|
||||
|
||||
import android.accounts.Account;
|
||||
import android.accounts.AccountManager;
|
||||
import android.accounts.AccountManagerCallback;
|
||||
import android.accounts.AccountManagerFuture;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
|
@ -43,6 +50,44 @@ public class SyncAccounts {
|
|||
|
||||
public final static String DEFAULT_SERVER = "https://auth.services.mozilla.com/";
|
||||
|
||||
/**
|
||||
* Return Sync accounts.
|
||||
*
|
||||
* @param c Android context.
|
||||
* @return Sync accounts.
|
||||
*/
|
||||
public static Account[] syncAccounts(final Context c) {
|
||||
return AccountManager.get(c).getAccountsByType(GlobalConstants.ACCOUNTTYPE_SYNC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously invalidate the auth token for a Sync account.
|
||||
*
|
||||
* @param accountManager Android account manager.
|
||||
* @param account Android account.
|
||||
*/
|
||||
public static void invalidateAuthToken(final AccountManager accountManager, final Account account) {
|
||||
if (account == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// blockingGetAuthToken must not be called from the main thread.
|
||||
ThreadPool.run(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
String authToken;
|
||||
try {
|
||||
authToken = accountManager.blockingGetAuthToken(account, Constants.AUTHTOKEN_TYPE_PLAIN, true);
|
||||
} catch (Exception e) {
|
||||
Logger.warn(LOG_TAG, "Got exception while invalidating auth token.", e);
|
||||
return;
|
||||
}
|
||||
|
||||
accountManager.invalidateAuthToken(GlobalConstants.ACCOUNTTYPE_SYNC, authToken);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if a Sync account is set up, or we have a pickled Sync account
|
||||
* on disk that should be un-pickled (Bug 769745). If we have a pickled Sync
|
||||
|
@ -321,28 +366,30 @@ public class SyncAccounts {
|
|||
setIsSyncable(account, syncAutomatically);
|
||||
Logger.debug(LOG_TAG, "Set account to sync automatically? " + syncAutomatically + ".");
|
||||
|
||||
setClientRecord(context, accountManager, account, syncAccount.clientName, syncAccount.clientGuid);
|
||||
|
||||
// TODO: add other ContentProviders as needed (e.g. passwords)
|
||||
// TODO: for each, also add to res/xml to make visible in account settings
|
||||
Logger.debug(LOG_TAG, "Finished setting syncables.");
|
||||
|
||||
// Purging global prefs assumes we have only a single Sync account at one time.
|
||||
// TODO: Bug 761682: don't do anything with global prefs here.
|
||||
if (clearPreferences) {
|
||||
Logger.info(LOG_TAG, "Clearing global prefs.");
|
||||
SyncAdapter.purgeGlobalPrefs(context);
|
||||
}
|
||||
|
||||
try {
|
||||
SharedPreferences.Editor editor = Utils.getSharedPreferences(context, username, serverURL).edit();
|
||||
final String product = GlobalConstants.BROWSER_INTENT_PACKAGE;
|
||||
final String profile = Constants.DEFAULT_PROFILE;
|
||||
final long version = SyncConfiguration.CURRENT_PREFS_VERSION;
|
||||
|
||||
final SharedPreferences.Editor editor = Utils.getSharedPreferences(context, product, username, serverURL, profile, version).edit();
|
||||
if (clearPreferences) {
|
||||
Logger.info(LOG_TAG, "Clearing preferences path " + Utils.getPrefsPath(username, serverURL) + " for this account.");
|
||||
final String prefsPath = Utils.getPrefsPath(product, username, serverURL, profile, version);
|
||||
Logger.info(LOG_TAG, "Clearing preferences path " + prefsPath + " for this account.");
|
||||
editor.clear();
|
||||
}
|
||||
|
||||
if (syncAccount.clusterURL != null) {
|
||||
editor.putString(SyncConfiguration.PREF_CLUSTER_URL, syncAccount.clusterURL);
|
||||
}
|
||||
|
||||
if (syncAccount.clientName != null && syncAccount.clientGuid != null) {
|
||||
Logger.debug(LOG_TAG, "Setting client name to " + syncAccount.clientName + " and client GUID to " + syncAccount.clientGuid + ".");
|
||||
editor.putString(SyncConfiguration.PREF_CLIENT_NAME, syncAccount.clientName);
|
||||
editor.putString(SyncConfiguration.PREF_ACCOUNT_GUID, syncAccount.clientGuid);
|
||||
} else {
|
||||
Logger.debug(LOG_TAG, "Client name and guid not both non-null, so not setting client data.");
|
||||
}
|
||||
|
||||
editor.commit();
|
||||
} catch (Exception e) {
|
||||
Logger.error(LOG_TAG, "Could not clear prefs path!", e);
|
||||
|
@ -433,14 +480,126 @@ public class SyncAccounts {
|
|||
return intent;
|
||||
}
|
||||
|
||||
protected static void setClientRecord(Context context, AccountManager accountManager, Account account,
|
||||
String clientName, String clientGuid) {
|
||||
if (clientName != null && clientGuid != null) {
|
||||
Logger.debug(LOG_TAG, "Setting client name to " + clientName + " and client GUID to " + clientGuid + ".");
|
||||
SyncAdapter.setAccountGUID(accountManager, account, clientGuid);
|
||||
SyncAdapter.setClientName(accountManager, account, clientName);
|
||||
return;
|
||||
protected static class SyncAccountVersion0Callback implements AccountManagerCallback<Bundle> {
|
||||
protected final Context context;
|
||||
protected final CountDownLatch latch;
|
||||
|
||||
public String authToken = null;
|
||||
|
||||
public SyncAccountVersion0Callback(final Context context, final CountDownLatch latch) {
|
||||
this.context = context;
|
||||
this.latch = latch;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(AccountManagerFuture<Bundle> future) {
|
||||
try {
|
||||
Bundle bundle = future.getResult(60L, TimeUnit.SECONDS);
|
||||
if (bundle.containsKey(AccountManager.KEY_INTENT)) {
|
||||
throw new IllegalStateException("KEY_INTENT included in AccountManagerFuture bundle.");
|
||||
}
|
||||
if (bundle.containsKey(AccountManager.KEY_ERROR_MESSAGE)) {
|
||||
throw new IllegalStateException("KEY_ERROR_MESSAGE (= " + bundle.getString(AccountManager.KEY_ERROR_MESSAGE) + ") "
|
||||
+ " included in AccountManagerFuture bundle.");
|
||||
}
|
||||
|
||||
authToken = bundle.getString(AccountManager.KEY_AUTHTOKEN);
|
||||
} catch (Exception e) {
|
||||
// Do nothing -- caller will find null authToken.
|
||||
Logger.warn(LOG_TAG, "Got exception fetching auth token; ignoring and returning null auth token instead.", e);
|
||||
} finally {
|
||||
latch.countDown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronously extract Sync account parameters from Android account version
|
||||
* 0, using plain auth token type.
|
||||
* <p>
|
||||
* Safe to call from main thread.
|
||||
*
|
||||
* @param context
|
||||
* @param accountManager
|
||||
* Android account manager.
|
||||
* @param account
|
||||
* Android account.
|
||||
* @return Sync account parameters, always non-null; fields username,
|
||||
* password, serverURL, and syncKey always non-null.
|
||||
*/
|
||||
public static SyncAccountParameters blockingFromAndroidAccountV0(final Context context, final AccountManager accountManager, final Account account)
|
||||
throws CredentialException {
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
final SyncAccountVersion0Callback callback = new SyncAccountVersion0Callback(context, latch);
|
||||
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// Get an auth token.
|
||||
accountManager.getAuthToken(account, Constants.AUTHTOKEN_TYPE_PLAIN, true, callback, null);
|
||||
}
|
||||
}).start();
|
||||
|
||||
try {
|
||||
latch.await();
|
||||
} catch (InterruptedException e) {
|
||||
Logger.warn(LOG_TAG, "Got exception waiting for Sync account parameters; throwing.");
|
||||
throw new CredentialException.MissingAllCredentialsException(e);
|
||||
}
|
||||
|
||||
String username;
|
||||
try {
|
||||
username = Utils.usernameFromAccount(account.name);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new CredentialException.MissingCredentialException("username");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new CredentialException.MissingCredentialException("username");
|
||||
}
|
||||
|
||||
final String password = callback.authToken;
|
||||
|
||||
/*
|
||||
* If we are accessing an Account that we don't own, Android will throw an
|
||||
* unchecked <code>SecurityException</code> saying
|
||||
* "W FxSync(XXXX) java.lang.SecurityException: caller uid XXXXX is different than the authenticator's uid".
|
||||
* We catch that error and throw accordingly.
|
||||
*/
|
||||
String syncKey;
|
||||
String serverURL;
|
||||
try {
|
||||
syncKey = accountManager.getUserData(account, Constants.OPTION_SYNCKEY);
|
||||
serverURL = accountManager.getUserData(account, Constants.OPTION_SERVER);
|
||||
} catch (SecurityException e) {
|
||||
Logger.warn(LOG_TAG, "Got security exception fetching Sync account parameters; throwing.");
|
||||
throw new CredentialException.MissingAllCredentialsException(e);
|
||||
}
|
||||
|
||||
if (password == null &&
|
||||
username == null &&
|
||||
syncKey == null &&
|
||||
serverURL == null) {
|
||||
throw new CredentialException.MissingAllCredentialsException();
|
||||
}
|
||||
|
||||
if (password == null) {
|
||||
throw new CredentialException.MissingCredentialException("password");
|
||||
}
|
||||
|
||||
if (syncKey == null) {
|
||||
throw new CredentialException.MissingCredentialException("syncKey");
|
||||
}
|
||||
|
||||
if (serverURL == null) {
|
||||
throw new CredentialException.MissingCredentialException("serverURL");
|
||||
}
|
||||
|
||||
try {
|
||||
// SyncAccountParameters constructor throws on null inputs. This shouldn't
|
||||
// happen, but let's be safe.
|
||||
return new SyncAccountParameters(context, accountManager, username, syncKey, password, serverURL);
|
||||
} catch (Exception e) {
|
||||
Logger.warn(LOG_TAG, "Got exception fetching Sync account parameters; throwing.");
|
||||
throw new CredentialException.MissingAllCredentialsException(e);
|
||||
}
|
||||
Logger.debug(LOG_TAG, "Client name and guid not both non-null, so not setting client data.");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,6 +49,87 @@ public class SyncAuthenticatorService extends Service {
|
|||
return sAccountAuthenticator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a "plain" auth token.
|
||||
* <p>
|
||||
* Android caches only the value of the key
|
||||
* <code>AccountManager.KEY_AUTHTOKEN</code>, so if a caller needs the other
|
||||
* keys in this bundle, it needs to invalidate the token (so that the bundle
|
||||
* is re-generated).
|
||||
*
|
||||
* @param context
|
||||
* Android context.
|
||||
* @param account
|
||||
* Android account.
|
||||
* @return a <code>Bundle</code> instance containing a subset of the following
|
||||
* keys: (caller's must check for missing keys)
|
||||
* <ul>
|
||||
* <li><code>AccountManager.KEY_ACCOUNT_TYPE</code>: the Android
|
||||
* Account's type</li>
|
||||
*
|
||||
* <li><code>AccountManager.KEY_ACCOUNT_NAME</code>: the Android
|
||||
* Account's name</li>
|
||||
*
|
||||
* <li><code>AccountManager.KEY_AUTHTOKEN</code>: the Sync account's
|
||||
* password </li>
|
||||
*
|
||||
* <li><code> Constants.OPTION_USERNAME</code>: the Sync account's
|
||||
* hashed username</li>
|
||||
*
|
||||
* <li><code>Constants.OPTION_SERVER</code>: the Sync account's
|
||||
* server</li>
|
||||
*
|
||||
* <li><code> Constants.OPTION_SYNCKEY</code>: the Sync account's
|
||||
* sync key</li>
|
||||
*
|
||||
* </ul>
|
||||
* @throws NetworkErrorException
|
||||
*/
|
||||
public static Bundle getPlainAuthToken(final Context context, final Account account)
|
||||
throws NetworkErrorException {
|
||||
// Extract the username and password from the Account Manager, and ask
|
||||
// the server for an appropriate AuthToken.
|
||||
final AccountManager am = AccountManager.get(context);
|
||||
final String password = am.getPassword(account);
|
||||
if (password == null) {
|
||||
Logger.warn(LOG_TAG, "Returning null bundle for getPlainAuthToken since Account password is null.");
|
||||
return null;
|
||||
}
|
||||
|
||||
final Bundle result = new Bundle();
|
||||
|
||||
// This is a Sync account.
|
||||
result.putString(AccountManager.KEY_ACCOUNT_TYPE, GlobalConstants.ACCOUNTTYPE_SYNC);
|
||||
|
||||
// Server.
|
||||
String serverURL = am.getUserData(account, Constants.OPTION_SERVER);
|
||||
result.putString(Constants.OPTION_SERVER, serverURL);
|
||||
|
||||
// Full username, before hashing.
|
||||
result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
|
||||
|
||||
// Username after hashing.
|
||||
try {
|
||||
String username = Utils.usernameFromAccount(account.name);
|
||||
Logger.pii(LOG_TAG, "Account " + account.name + " hashes to " + username + ".");
|
||||
Logger.debug(LOG_TAG, "Setting username. Null? " + (username == null));
|
||||
result.putString(Constants.OPTION_USERNAME, username);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
// Do nothing. Calling code must check for missing value.
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
// Do nothing. Calling code must check for missing value.
|
||||
}
|
||||
|
||||
// Sync key.
|
||||
final String syncKey = am.getUserData(account, Constants.OPTION_SYNCKEY);
|
||||
Logger.debug(LOG_TAG, "Setting sync key. Null? " + (syncKey == null));
|
||||
result.putString(Constants.OPTION_SYNCKEY, syncKey);
|
||||
|
||||
// Password.
|
||||
result.putString(AccountManager.KEY_AUTHTOKEN, password);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static class SyncAccountAuthenticator extends AbstractAccountAuthenticator {
|
||||
private Context mContext;
|
||||
public SyncAccountAuthenticator(Context context) {
|
||||
|
@ -93,53 +174,14 @@ public class SyncAuthenticatorService extends Service {
|
|||
Account account, String authTokenType, Bundle options)
|
||||
throws NetworkErrorException {
|
||||
Logger.debug(LOG_TAG, "getAuthToken()");
|
||||
if (!authTokenType.equals(Constants.AUTHTOKEN_TYPE_PLAIN)) {
|
||||
final Bundle result = new Bundle();
|
||||
result.putString(AccountManager.KEY_ERROR_MESSAGE,
|
||||
"invalid authTokenType");
|
||||
return result;
|
||||
|
||||
if (Constants.AUTHTOKEN_TYPE_PLAIN.equals(authTokenType)) {
|
||||
return getPlainAuthToken(mContext, account);
|
||||
}
|
||||
|
||||
// Extract the username and password from the Account Manager, and ask
|
||||
// the server for an appropriate AuthToken.
|
||||
final AccountManager am = AccountManager.get(mContext);
|
||||
final String password = am.getPassword(account);
|
||||
if (password != null) {
|
||||
final Bundle result = new Bundle();
|
||||
|
||||
// This is a Sync account.
|
||||
result.putString(AccountManager.KEY_ACCOUNT_TYPE, GlobalConstants.ACCOUNTTYPE_SYNC);
|
||||
|
||||
// Server.
|
||||
String serverURL = am.getUserData(account, Constants.OPTION_SERVER);
|
||||
result.putString(Constants.OPTION_SERVER, serverURL);
|
||||
|
||||
// Full username, before hashing.
|
||||
result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name);
|
||||
|
||||
// Username after hashing.
|
||||
try {
|
||||
String username = Utils.usernameFromAccount(account.name);
|
||||
Logger.pii(LOG_TAG, "Account " + account.name + " hashes to " + username + ".");
|
||||
Logger.debug(LOG_TAG, "Setting username. Null? " + (username == null));
|
||||
result.putString(Constants.OPTION_USERNAME, username);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
// Do nothing. Calling code must check for missing value.
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
// Do nothing. Calling code must check for missing value.
|
||||
}
|
||||
|
||||
// Sync key.
|
||||
final String syncKey = am.getUserData(account, Constants.OPTION_SYNCKEY);
|
||||
Logger.debug(LOG_TAG, "Setting sync key. Null? " + (syncKey == null));
|
||||
result.putString(Constants.OPTION_SYNCKEY, syncKey);
|
||||
|
||||
// Password.
|
||||
result.putString(AccountManager.KEY_AUTHTOKEN, password);
|
||||
return result;
|
||||
}
|
||||
Logger.warn(LOG_TAG, "Returning null bundle for getAuthToken.");
|
||||
return null;
|
||||
final Bundle result = new Bundle();
|
||||
result.putString(AccountManager.KEY_ERROR_MESSAGE, "invalid authTokenType");
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -9,13 +9,18 @@ import java.util.List;
|
|||
import org.mozilla.gecko.R;
|
||||
import org.mozilla.gecko.sync.CommandProcessor;
|
||||
import org.mozilla.gecko.sync.CommandRunner;
|
||||
import org.mozilla.gecko.sync.CredentialException;
|
||||
import org.mozilla.gecko.sync.GlobalConstants;
|
||||
import org.mozilla.gecko.sync.GlobalSession;
|
||||
import org.mozilla.gecko.sync.Logger;
|
||||
import org.mozilla.gecko.sync.SyncConfiguration;
|
||||
import org.mozilla.gecko.sync.Utils;
|
||||
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.setup.SyncAccounts;
|
||||
import org.mozilla.gecko.sync.setup.SyncAccounts.SyncAccountParameters;
|
||||
import org.mozilla.gecko.sync.stage.SyncClientsEngineStage;
|
||||
import org.mozilla.gecko.sync.syncadapter.SyncAdapter;
|
||||
|
||||
|
@ -24,6 +29,7 @@ import android.accounts.AccountManager;
|
|||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
@ -106,17 +112,47 @@ public class SendTabActivity extends Activity {
|
|||
* @return Return null if there is no account set up. Return the account GUID otherwise.
|
||||
*/
|
||||
private String getAccountGUID() {
|
||||
if (accountManager == null || localAccount == null) {
|
||||
if (localAccount == null) {
|
||||
Logger.warn(LOG_TAG, "Null local account; aborting.");
|
||||
return null;
|
||||
}
|
||||
|
||||
SyncAccountParameters params;
|
||||
try {
|
||||
params = SyncAccounts.blockingFromAndroidAccountV0(this, accountManager, localAccount);
|
||||
} catch (CredentialException e) {
|
||||
Logger.warn(LOG_TAG, "Could not get sync account parameters; aborting.");
|
||||
return null;
|
||||
}
|
||||
|
||||
SharedPreferences prefs;
|
||||
try {
|
||||
final String product = GlobalConstants.BROWSER_INTENT_PACKAGE;
|
||||
final String profile = Constants.DEFAULT_PROFILE;
|
||||
final long version = SyncConfiguration.CURRENT_PREFS_VERSION;
|
||||
prefs = Utils.getSharedPreferences(getApplicationContext(), product, params.username, params.serverURL, profile, version);
|
||||
return prefs.getString(SyncConfiguration.PREF_ACCOUNT_GUID, null);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
return accountManager.getUserData(localAccount, Constants.ACCOUNT_GUID);
|
||||
}
|
||||
|
||||
public void sendClickHandler(View view) {
|
||||
Logger.info(LOG_TAG, "Send was clicked.");
|
||||
Bundle extras = this.getIntent().getExtras();
|
||||
if (extras == null) {
|
||||
Logger.warn(LOG_TAG, "extras was null; aborting without sending tab.");
|
||||
notifyAndFinish(false);
|
||||
return;
|
||||
}
|
||||
|
||||
final String uri = extras.getString(Intent.EXTRA_TEXT);
|
||||
final String title = extras.getString(Intent.EXTRA_SUBJECT);
|
||||
final List<String> guids = arrayAdapter.getCheckedGUIDs();
|
||||
|
||||
if (title == null) {
|
||||
Logger.warn(LOG_TAG, "title was null; ignoring and sending tab anyway.");
|
||||
}
|
||||
|
||||
if (uri == null) {
|
||||
Logger.warn(LOG_TAG, "uri was null; aborting without sending tab.");
|
||||
|
@ -124,37 +160,43 @@ public class SendTabActivity extends Activity {
|
|||
return;
|
||||
}
|
||||
|
||||
if (title == null) {
|
||||
Logger.warn(LOG_TAG, "title was null; ignoring and sending tab anyway.");
|
||||
}
|
||||
|
||||
final String clientGUID = getAccountGUID();
|
||||
final List<String> guids = arrayAdapter.getCheckedGUIDs();
|
||||
|
||||
if (clientGUID == null || guids == null) {
|
||||
if (guids == null) {
|
||||
// Should never happen.
|
||||
Logger.warn(LOG_TAG, "clientGUID? " + (clientGUID == null) + " or guids? " + (guids == null) +
|
||||
" was null; aborting without sending tab.");
|
||||
Logger.warn(LOG_TAG, "guids was null; aborting without sending tab.");
|
||||
notifyAndFinish(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Perform tab sending on another thread.
|
||||
new Thread() {
|
||||
// Fetching local client GUID hits the DB, and we want to update the UI
|
||||
// afterward, so we perform the tab sending on another thread.
|
||||
new AsyncTask<Void, Void, Boolean>() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
protected Boolean doInBackground(Void... params) {
|
||||
final CommandProcessor processor = CommandProcessor.getProcessor();
|
||||
|
||||
final String accountGUID = getAccountGUID();
|
||||
Logger.debug(LOG_TAG, "Retrieved local account GUID '" + accountGUID + "'.");
|
||||
if (accountGUID == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (String guid : guids) {
|
||||
processor.sendURIToClientForDisplay(uri, guid, title, clientGUID, getApplicationContext());
|
||||
processor.sendURIToClientForDisplay(uri, guid, title, accountGUID, getApplicationContext());
|
||||
}
|
||||
|
||||
Logger.info(LOG_TAG, "Requesting immediate clients stage sync.");
|
||||
SyncAdapter.requestImmediateSync(localAccount, new String[] { SyncClientsEngineStage.COLLECTION_NAME });
|
||||
}
|
||||
}.start();
|
||||
|
||||
notifyAndFinish(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(final Boolean success) {
|
||||
// We're allowed to update the UI from here.
|
||||
notifyAndFinish(success.booleanValue());
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -7,11 +7,12 @@ package org.mozilla.gecko.sync.syncadapter;
|
|||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.json.simple.parser.ParseException;
|
||||
import org.mozilla.gecko.db.BrowserContract;
|
||||
import org.mozilla.gecko.sync.AlreadySyncingException;
|
||||
import org.mozilla.gecko.sync.CredentialException;
|
||||
import org.mozilla.gecko.sync.GlobalConstants;
|
||||
import org.mozilla.gecko.sync.GlobalSession;
|
||||
import org.mozilla.gecko.sync.Logger;
|
||||
|
@ -34,8 +35,6 @@ import org.mozilla.gecko.sync.stage.GlobalSyncStage.Stage;
|
|||
|
||||
import android.accounts.Account;
|
||||
import android.accounts.AccountManager;
|
||||
import android.accounts.AccountManagerCallback;
|
||||
import android.accounts.AccountManagerFuture;
|
||||
import android.accounts.AuthenticatorException;
|
||||
import android.accounts.OperationCanceledException;
|
||||
import android.content.AbstractThreadedSyncAdapter;
|
||||
|
@ -48,21 +47,14 @@ import android.content.SyncResult;
|
|||
import android.database.sqlite.SQLiteConstraintException;
|
||||
import android.database.sqlite.SQLiteException;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
|
||||
public class SyncAdapter extends AbstractThreadedSyncAdapter implements GlobalSessionCallback, ClientsDataDelegate {
|
||||
private static final String LOG_TAG = "SyncAdapter";
|
||||
|
||||
private static final String PREFS_EARLIEST_NEXT_SYNC = "earliestnextsync";
|
||||
private static final String PREFS_INVALIDATE_AUTH_TOKEN = "invalidateauthtoken";
|
||||
private static final String PREFS_CLUSTER_URL_IS_STALE = "clusterurlisstale";
|
||||
|
||||
private static final int SHARED_PREFERENCES_MODE = 0;
|
||||
private static final int BACKOFF_PAD_SECONDS = 5;
|
||||
public static final int MULTI_DEVICE_INTERVAL_MILLISECONDS = 5 * 60 * 1000; // 5 minutes.
|
||||
public static final int SINGLE_DEVICE_INTERVAL_MILLISECONDS = 24 * 60 * 60 * 1000; // 24 hours.
|
||||
|
||||
private final AccountManager mAccountManager;
|
||||
private final Context mContext;
|
||||
|
||||
protected long syncStartTimestamp;
|
||||
|
@ -70,60 +62,43 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter implements GlobalSe
|
|||
public SyncAdapter(Context context, boolean autoInitialize) {
|
||||
super(context, autoInitialize);
|
||||
mContext = context;
|
||||
mAccountManager = AccountManager.get(context);
|
||||
}
|
||||
|
||||
public static SharedPreferences getGlobalPrefs(Context context) {
|
||||
return context.getSharedPreferences("sync.prefs.global", SHARED_PREFERENCES_MODE);
|
||||
}
|
||||
|
||||
public static void purgeGlobalPrefs(Context context) {
|
||||
getGlobalPrefs(context).edit().clear().commit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Backoff.
|
||||
*/
|
||||
public synchronized long getEarliestNextSync() {
|
||||
SharedPreferences sharedPreferences = getGlobalPrefs(mContext);
|
||||
return sharedPreferences.getLong(PREFS_EARLIEST_NEXT_SYNC, 0);
|
||||
return accountSharedPreferences.getLong(SyncConfiguration.PREF_EARLIEST_NEXT_SYNC, 0);
|
||||
}
|
||||
|
||||
public synchronized void setEarliestNextSync(long next) {
|
||||
SharedPreferences sharedPreferences = getGlobalPrefs(mContext);
|
||||
Editor edit = sharedPreferences.edit();
|
||||
edit.putLong(PREFS_EARLIEST_NEXT_SYNC, next);
|
||||
Editor edit = accountSharedPreferences.edit();
|
||||
edit.putLong(SyncConfiguration.PREF_EARLIEST_NEXT_SYNC, next);
|
||||
edit.commit();
|
||||
}
|
||||
|
||||
public synchronized void extendEarliestNextSync(long next) {
|
||||
SharedPreferences sharedPreferences = getGlobalPrefs(mContext);
|
||||
if (sharedPreferences.getLong(PREFS_EARLIEST_NEXT_SYNC, 0) >= next) {
|
||||
if (accountSharedPreferences.getLong(SyncConfiguration.PREF_EARLIEST_NEXT_SYNC, 0) >= next) {
|
||||
return;
|
||||
}
|
||||
Editor edit = sharedPreferences.edit();
|
||||
edit.putLong(PREFS_EARLIEST_NEXT_SYNC, next);
|
||||
Editor edit = accountSharedPreferences.edit();
|
||||
edit.putLong(SyncConfiguration.PREF_EARLIEST_NEXT_SYNC, next);
|
||||
edit.commit();
|
||||
}
|
||||
|
||||
public synchronized boolean getShouldInvalidateAuthToken() {
|
||||
SharedPreferences sharedPreferences = getGlobalPrefs(mContext);
|
||||
return sharedPreferences.getBoolean(PREFS_INVALIDATE_AUTH_TOKEN, false);
|
||||
}
|
||||
public synchronized void clearShouldInvalidateAuthToken() {
|
||||
SharedPreferences sharedPreferences = getGlobalPrefs(mContext);
|
||||
Editor edit = sharedPreferences.edit();
|
||||
edit.remove(PREFS_INVALIDATE_AUTH_TOKEN);
|
||||
edit.commit();
|
||||
}
|
||||
public synchronized void setShouldInvalidateAuthToken() {
|
||||
SharedPreferences sharedPreferences = getGlobalPrefs(mContext);
|
||||
Editor edit = sharedPreferences.edit();
|
||||
edit.putBoolean(PREFS_INVALIDATE_AUTH_TOKEN, true);
|
||||
edit.commit();
|
||||
}
|
||||
|
||||
private void handleException(Exception e, SyncResult syncResult) {
|
||||
setShouldInvalidateAuthToken();
|
||||
/**
|
||||
* Handle an exception: update stats, invalidate auth token, log errors, etc.
|
||||
*
|
||||
* @param globalSession
|
||||
* current global session, or null.
|
||||
* @param e
|
||||
* Exception to handle.
|
||||
*/
|
||||
protected void processException(final GlobalSession globalSession, final Exception e) {
|
||||
try {
|
||||
// Just in case, invalidate auth token.
|
||||
SyncAccounts.invalidateAuthToken(AccountManager.get(mContext), localAccount);
|
||||
|
||||
if (e instanceof SQLiteConstraintException) {
|
||||
Logger.error(LOG_TAG, "Constraint exception. Aborting sync.", e);
|
||||
syncResult.stats.numParseExceptions++; // This is as good as we can do.
|
||||
|
@ -149,27 +124,50 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter implements GlobalSe
|
|||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
syncResult.stats.numIoExceptions++;
|
||||
|
||||
// Blanket stats updating for SyncException subclasses.
|
||||
if (e instanceof SyncException) {
|
||||
((SyncException) e).updateStats(globalSession, syncResult);
|
||||
} else {
|
||||
// Generic exception.
|
||||
syncResult.stats.numIoExceptions++;
|
||||
}
|
||||
|
||||
if (e instanceof CredentialException.MissingAllCredentialsException) {
|
||||
// This is bad: either we couldn't fetch credentials, or the credentials
|
||||
// were totally blank. Most likely the user has two copies of Firefox
|
||||
// installed, and something is misbehaving.
|
||||
// Either way, disable this account.
|
||||
if (localAccount == null) {
|
||||
// Should not happen, but be safe.
|
||||
Logger.error(LOG_TAG, "No credentials attached to account. Aborting sync.");
|
||||
return;
|
||||
}
|
||||
|
||||
Logger.error(LOG_TAG, "No credentials attached to account " + localAccount.name + ". Aborting sync.");
|
||||
try {
|
||||
SyncAccounts.setSyncAutomatically(localAccount, false);
|
||||
} catch (Exception ex) {
|
||||
Logger.error(LOG_TAG, "Unable to disable account " + localAccount.name + ".", ex);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (e instanceof CredentialException.MissingCredentialException) {
|
||||
Logger.error(LOG_TAG, "Credentials attached to account, but missing " +
|
||||
((CredentialException.MissingCredentialException) e).missingCredential + ". Aborting sync.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (e instanceof CredentialException) {
|
||||
Logger.error(LOG_TAG, "Credentials attached to account were bad.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Generic exception.
|
||||
Logger.error(LOG_TAG, "Unknown exception. Aborting sync.", e);
|
||||
} catch (Exception ex) {
|
||||
Logger.error(LOG_TAG, "Unknown exception. Aborting sync.", e);
|
||||
} finally {
|
||||
notifyMonitor();
|
||||
}
|
||||
}
|
||||
|
||||
private AccountManagerFuture<Bundle> getAuthToken(final Account account,
|
||||
AccountManagerCallback<Bundle> callback,
|
||||
Handler handler) {
|
||||
return mAccountManager.getAuthToken(account, Constants.AUTHTOKEN_TYPE_PLAIN, true, callback, handler);
|
||||
}
|
||||
|
||||
private void invalidateAuthToken(Account account) {
|
||||
AccountManagerFuture<Bundle> future = getAuthToken(account, null, null);
|
||||
String token;
|
||||
try {
|
||||
token = future.getResult().getString(AccountManager.KEY_AUTHTOKEN);
|
||||
mAccountManager.invalidateAuthToken(GlobalConstants.ACCOUNTTYPE_SYNC, token);
|
||||
} catch (Exception e) {
|
||||
Logger.error(LOG_TAG, "Couldn't invalidate auth token: " + e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -187,6 +185,7 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter implements GlobalSe
|
|||
|
||||
public Account localAccount;
|
||||
protected boolean thisSyncIsForced = false;
|
||||
public SharedPreferences accountSharedPreferences;
|
||||
|
||||
/**
|
||||
* Return the number of milliseconds until we're allowed to sync again,
|
||||
|
@ -277,112 +276,86 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter implements GlobalSe
|
|||
this.syncResult = syncResult;
|
||||
this.localAccount = account;
|
||||
|
||||
Logger.info(LOG_TAG,
|
||||
"Syncing account named " + account.name +
|
||||
" for 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) {
|
||||
Logger.info(LOG_TAG, "Forced sync: overruling remaining backoff of " + delay + "ms.");
|
||||
} else {
|
||||
Logger.info(LOG_TAG, "Not syncing: must wait another " + delay + "ms.");
|
||||
long remainingSeconds = delay / 1000;
|
||||
syncResult.delayUntil = remainingSeconds + BACKOFF_PAD_SECONDS;
|
||||
return;
|
||||
}
|
||||
SyncAccountParameters params;
|
||||
try {
|
||||
params = SyncAccounts.blockingFromAndroidAccountV0(mContext, AccountManager.get(mContext), this.localAccount);
|
||||
} catch (Exception e) {
|
||||
// Updates syncResult and (harmlessly) calls notifyMonitor().
|
||||
processException(null, e);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: don't clear the auth token unless we have a sync error.
|
||||
Logger.debug(LOG_TAG, "Got onPerformSync. Extras bundle is " + extras);
|
||||
|
||||
// TODO: don't always invalidate; use getShouldInvalidateAuthToken.
|
||||
// However, this fixes Bug 716815, so it'll do for now.
|
||||
Logger.trace(LOG_TAG, "Invalidating auth token.");
|
||||
invalidateAuthToken(account);
|
||||
// params and the following fields are non-null at this point.
|
||||
final String username = params.username; // Encoded with Utils.usernameFromAccount.
|
||||
final String password = params.password;
|
||||
final String serverURL = params.serverURL;
|
||||
final String syncKey = params.syncKey;
|
||||
|
||||
final AtomicBoolean setNextSync = new AtomicBoolean(true);
|
||||
final SyncAdapter self = this;
|
||||
final AccountManagerCallback<Bundle> callback = new AccountManagerCallback<Bundle>() {
|
||||
final Runnable runnable = new Runnable() {
|
||||
@Override
|
||||
public void run(AccountManagerFuture<Bundle> future) {
|
||||
public void run() {
|
||||
Logger.trace(LOG_TAG, "AccountManagerCallback invoked.");
|
||||
// TODO: N.B.: Future must not be used on the main thread.
|
||||
try {
|
||||
Bundle bundle = future.getResult(60L, TimeUnit.SECONDS);
|
||||
if (bundle.containsKey("KEY_INTENT")) {
|
||||
Logger.warn(LOG_TAG, "KEY_INTENT included in AccountManagerFuture bundle. Problem?");
|
||||
}
|
||||
String username = bundle.getString(Constants.OPTION_USERNAME);
|
||||
String syncKey = bundle.getString(Constants.OPTION_SYNCKEY);
|
||||
String serverURL = bundle.getString(Constants.OPTION_SERVER);
|
||||
String password = bundle.getString(AccountManager.KEY_AUTHTOKEN);
|
||||
Logger.info(LOG_TAG, "Syncing account named " + account.name +
|
||||
" for authority " + authority + ".");
|
||||
|
||||
// We dump this information right away to help with debugging.
|
||||
Logger.debug(LOG_TAG, "Username: " + username);
|
||||
Logger.debug(LOG_TAG, "Server: " + serverURL);
|
||||
Logger.debug(LOG_TAG, "Password? " + (password != null));
|
||||
Logger.debug(LOG_TAG, "Key? " + (syncKey != null));
|
||||
|
||||
if (password == null &&
|
||||
username == null &&
|
||||
syncKey == null &&
|
||||
serverURL == null) {
|
||||
|
||||
// Totally blank. Most likely the user has two copies of Firefox
|
||||
// installed, and something is misbehaving.
|
||||
// Disable this account.
|
||||
Logger.error(LOG_TAG, "No credentials attached to account. Aborting sync.");
|
||||
try {
|
||||
SyncAccounts.setSyncAutomatically(account, false);
|
||||
} catch (Exception e) {
|
||||
Logger.error(LOG_TAG, "Unable to disable account " + account.name + " for " + authority + ".", e);
|
||||
}
|
||||
syncResult.stats.numAuthExceptions++;
|
||||
localAccount = null;
|
||||
notifyMonitor();
|
||||
return;
|
||||
}
|
||||
|
||||
// Now catch the individual cases.
|
||||
if (password == null) {
|
||||
Logger.error(LOG_TAG, "No password: aborting sync.");
|
||||
syncResult.stats.numAuthExceptions++;
|
||||
notifyMonitor();
|
||||
return;
|
||||
}
|
||||
|
||||
if (syncKey == null) {
|
||||
Logger.error(LOG_TAG, "No Sync Key: aborting sync.");
|
||||
syncResult.stats.numAuthExceptions++;
|
||||
notifyMonitor();
|
||||
return;
|
||||
if (Logger.LOG_PERSONAL_INFORMATION) {
|
||||
Logger.debug(LOG_TAG, "Password: " + password);
|
||||
Logger.debug(LOG_TAG, "Sync key: " + syncKey);
|
||||
} else {
|
||||
Logger.debug(LOG_TAG, "Password? " + (password != null));
|
||||
Logger.debug(LOG_TAG, "Sync key? " + (syncKey != null));
|
||||
}
|
||||
|
||||
// Support multiple accounts by mapping each server/account pair to a branch of the
|
||||
// shared preferences space.
|
||||
String prefsPath = Utils.getPrefsPath(username, serverURL);
|
||||
final String product = GlobalConstants.BROWSER_INTENT_PACKAGE;
|
||||
final String profile = Constants.DEFAULT_PROFILE;
|
||||
final long version = SyncConfiguration.CURRENT_PREFS_VERSION;
|
||||
self.accountSharedPreferences = Utils.getSharedPreferences(mContext, product, username, serverURL, profile, version);
|
||||
|
||||
Logger.info(LOG_TAG,
|
||||
"Client is named '" + getClientName() + "'" +
|
||||
", has client guid " + getAccountGUID() +
|
||||
", and has " + getClientsCount() + " clients.");
|
||||
|
||||
thisSyncIsForced = (extras != null) && (extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false));
|
||||
long delay = delayMilliseconds();
|
||||
if (delay > 0) {
|
||||
if (thisSyncIsForced) {
|
||||
Logger.info(LOG_TAG, "Forced sync: overruling remaining backoff of " + delay + "ms.");
|
||||
} else {
|
||||
Logger.info(LOG_TAG, "Not syncing: must wait another " + delay + "ms.");
|
||||
long remainingSeconds = delay / 1000;
|
||||
syncResult.delayUntil = remainingSeconds + BACKOFF_PAD_SECONDS;
|
||||
setNextSync.set(false);
|
||||
self.notifyMonitor();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
final String prefsPath = Utils.getPrefsPath(product, username, serverURL, profile, version);
|
||||
self.performSync(account, extras, authority, provider, syncResult,
|
||||
username, password, prefsPath, serverURL, syncKey);
|
||||
} catch (Exception e) {
|
||||
self.handleException(e, syncResult);
|
||||
self.processException(null, e);
|
||||
notifyMonitor();
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
final Handler handler = null;
|
||||
final Runnable fetchAuthToken = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
getAuthToken(account, callback, handler);
|
||||
}
|
||||
};
|
||||
synchronized (syncMonitor) {
|
||||
// Perform the work in a new thread from within this synchronized block,
|
||||
// which allows us to be waiting on the monitor before the callback can
|
||||
// notify us in a failure case. Oh, concurrent programming.
|
||||
new Thread(fetchAuthToken).start();
|
||||
new Thread(runnable).start();
|
||||
|
||||
// Start our stale connection monitor thread.
|
||||
ConnectionMonitorThread stale = new ConnectionMonitorThread();
|
||||
|
@ -391,10 +364,13 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter implements GlobalSe
|
|||
Logger.trace(LOG_TAG, "Waiting on sync monitor.");
|
||||
try {
|
||||
syncMonitor.wait();
|
||||
long interval = getSyncInterval();
|
||||
long next = System.currentTimeMillis() + interval;
|
||||
Logger.info(LOG_TAG, "Setting minimum next sync time to " + next + " (" + interval + "ms from now).");
|
||||
extendEarliestNextSync(next);
|
||||
|
||||
if (setNextSync.get()) {
|
||||
long interval = getSyncInterval();
|
||||
long next = System.currentTimeMillis() + interval;
|
||||
Logger.info(LOG_TAG, "Setting minimum next sync time to " + next + " (" + interval + "ms from now).");
|
||||
extendEarliestNextSync(next);
|
||||
}
|
||||
Logger.info(LOG_TAG, "Sync took " + Utils.formatDuration(syncStartTimestamp, System.currentTimeMillis()) + ".");
|
||||
} catch (InterruptedException e) {
|
||||
Logger.warn(LOG_TAG, "Waiting on sync monitor interrupted.", e);
|
||||
|
@ -403,7 +379,7 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter implements GlobalSe
|
|||
stale.shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int getSyncInterval() {
|
||||
// Must have been a problem that means we can't access the Account.
|
||||
|
@ -419,10 +395,8 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter implements GlobalSe
|
|||
return MULTI_DEVICE_INTERVAL_MILLISECONDS;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Now that we have a sync key and password, go ahead and do the work.
|
||||
* @param prefsPath TODO
|
||||
* @throws NoSuchAlgorithmException
|
||||
* @throws IllegalArgumentException
|
||||
* @throws SyncConfigurationException
|
||||
|
@ -457,7 +431,7 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter implements GlobalSe
|
|||
*/
|
||||
try {
|
||||
// Constructor can throw on nulls, which should not happen -- but let's be safe.
|
||||
final SyncAccountParameters params = new SyncAccountParameters(mContext, mAccountManager,
|
||||
final SyncAccountParameters params = new SyncAccountParameters(mContext, null,
|
||||
account.name, // Un-encoded, like "test@mozilla.com".
|
||||
syncKey,
|
||||
password,
|
||||
|
@ -503,9 +477,8 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter implements GlobalSe
|
|||
// Implementing GlobalSession callbacks.
|
||||
@Override
|
||||
public void handleError(GlobalSession globalSession, Exception ex) {
|
||||
Logger.info(LOG_TAG, "GlobalSession indicated error. Flagging auth token as invalid, just in case.");
|
||||
setShouldInvalidateAuthToken();
|
||||
this.updateStats(globalSession, ex);
|
||||
Logger.info(LOG_TAG, "GlobalSession indicated error.");
|
||||
this.processException(globalSession, ex);
|
||||
notifyMonitor();
|
||||
}
|
||||
|
||||
|
@ -515,22 +488,6 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter implements GlobalSe
|
|||
notifyMonitor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Introspect the exception, incrementing the appropriate stat counters.
|
||||
* TODO: increment number of inserts, deletes, conflicts.
|
||||
*
|
||||
* @param globalSession
|
||||
* @param ex
|
||||
*/
|
||||
private void updateStats(GlobalSession globalSession,
|
||||
Exception ex) {
|
||||
if (ex instanceof SyncException) {
|
||||
((SyncException) ex).updateStats(globalSession, syncResult);
|
||||
}
|
||||
// TODO: non-SyncExceptions.
|
||||
// TODO: wouldn't it be nice to update stats for *every* exception we get?
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleSuccess(GlobalSession globalSession) {
|
||||
Logger.info(LOG_TAG, "GlobalSession indicated success.");
|
||||
|
@ -547,37 +504,28 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter implements GlobalSe
|
|||
|
||||
@Override
|
||||
public synchronized String getAccountGUID() {
|
||||
String accountGUID = mAccountManager.getUserData(localAccount, Constants.ACCOUNT_GUID);
|
||||
String accountGUID = accountSharedPreferences.getString(SyncConfiguration.PREF_ACCOUNT_GUID, null);
|
||||
if (accountGUID == null) {
|
||||
Logger.debug(LOG_TAG, "Account GUID was null. Creating a new one.");
|
||||
accountGUID = Utils.generateGuid();
|
||||
setAccountGUID(mAccountManager, localAccount, accountGUID);
|
||||
accountSharedPreferences.edit().putString(SyncConfiguration.PREF_ACCOUNT_GUID, accountGUID).commit();
|
||||
}
|
||||
return accountGUID;
|
||||
}
|
||||
|
||||
public static void setAccountGUID(AccountManager accountManager, Account account, String accountGUID) {
|
||||
accountManager.setUserData(account, Constants.ACCOUNT_GUID, accountGUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized String getClientName() {
|
||||
String clientName = mAccountManager.getUserData(localAccount, Constants.CLIENT_NAME);
|
||||
String clientName = accountSharedPreferences.getString(SyncConfiguration.PREF_CLIENT_NAME, null);
|
||||
if (clientName == null) {
|
||||
clientName = GlobalConstants.PRODUCT_NAME + " on " + android.os.Build.MODEL;
|
||||
setClientName(mAccountManager, localAccount, clientName);
|
||||
accountSharedPreferences.edit().putString(SyncConfiguration.PREF_CLIENT_NAME, clientName).commit();
|
||||
}
|
||||
return clientName;
|
||||
}
|
||||
|
||||
public static void setClientName(AccountManager accountManager, Account account, String clientName) {
|
||||
accountManager.setUserData(account, Constants.CLIENT_NAME, clientName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void setClientsCount(int clientsCount) {
|
||||
mAccountManager.setUserData(localAccount, Constants.NUM_CLIENTS,
|
||||
Integer.toString(clientsCount));
|
||||
accountSharedPreferences.edit().putLong(SyncConfiguration.PREF_NUM_CLIENTS, (long) clientsCount).commit();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -587,23 +535,16 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter implements GlobalSe
|
|||
|
||||
@Override
|
||||
public synchronized int getClientsCount() {
|
||||
String clientsCount = mAccountManager.getUserData(localAccount, Constants.NUM_CLIENTS);
|
||||
if (clientsCount == null) {
|
||||
clientsCount = "0";
|
||||
mAccountManager.setUserData(localAccount, Constants.NUM_CLIENTS, clientsCount);
|
||||
}
|
||||
return Integer.parseInt(clientsCount);
|
||||
return (int) accountSharedPreferences.getLong(SyncConfiguration.PREF_NUM_CLIENTS, 0);
|
||||
}
|
||||
|
||||
public synchronized boolean getClusterURLIsStale() {
|
||||
SharedPreferences sharedPreferences = getGlobalPrefs(mContext);
|
||||
return sharedPreferences.getBoolean(PREFS_CLUSTER_URL_IS_STALE, false);
|
||||
return accountSharedPreferences.getBoolean(SyncConfiguration.PREF_CLUSTER_URL_IS_STALE, false);
|
||||
}
|
||||
|
||||
public synchronized void setClusterURLIsStale(boolean clusterURLIsStale) {
|
||||
SharedPreferences sharedPreferences = getGlobalPrefs(mContext);
|
||||
Editor edit = sharedPreferences.edit();
|
||||
edit.putBoolean(PREFS_CLUSTER_URL_IS_STALE, clusterURLIsStale);
|
||||
Editor edit = accountSharedPreferences.edit();
|
||||
edit.putBoolean(SyncConfiguration.PREF_CLUSTER_URL_IS_STALE, clusterURLIsStale);
|
||||
edit.commit();
|
||||
}
|
||||
|
||||
|
@ -631,7 +572,7 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter implements GlobalSe
|
|||
|
||||
@Override
|
||||
public void informUpgradeRequiredResponse(final GlobalSession session) {
|
||||
final AccountManager manager = mAccountManager;
|
||||
final AccountManager manager = AccountManager.get(mContext);
|
||||
final Account toDisable = localAccount;
|
||||
if (toDisable == null || manager == null) {
|
||||
Logger.warn(LOG_TAG, "Attempting to disable account, but null found.");
|
||||
|
|
|
@ -3,6 +3,8 @@ sync/CollectionKeys.java
|
|||
sync/CommandProcessor.java
|
||||
sync/CommandRunner.java
|
||||
sync/config/AccountPickler.java
|
||||
sync/config/ConfigurationMigrator.java
|
||||
sync/CredentialException.java
|
||||
sync/CredentialsSource.java
|
||||
sync/crypto/CryptoException.java
|
||||
sync/crypto/CryptoInfo.java
|
||||
|
|
Загрузка…
Ссылка в новой задаче