Files
tubestation/mobile/android/base/sync/setup/SyncAccounts.java

266 lines
11 KiB
Java

/* 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;
import org.mozilla.gecko.db.BrowserContract;
import org.mozilla.gecko.sync.Logger;
import org.mozilla.gecko.sync.SyncConfiguration;
import org.mozilla.gecko.sync.Utils;
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.content.ContentResolver;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
/**
* This class contains utilities that are of use to Fennec
* and Sync setup activities.
* <p>
* Do not break these APIs without correcting upstream code!
*/
public class SyncAccounts {
public final static String DEFAULT_SERVER = "https://auth.services.mozilla.com/";
private static final String LOG_TAG = "SyncAccounts";
/**
* Returns true if a Sync account is set up.
* <p>
* Do not call this method from the main thread.
*/
public static boolean syncAccountsExist(Context c) {
return AccountManager.get(c).getAccountsByType(Constants.ACCOUNTTYPE_SYNC).length > 0;
}
/**
* This class provides background-thread abstracted access to whether a
* Firefox Sync account has been set up on this device.
* <p>
* Subclass this task and override `onPostExecute` to act on the result.
*/
public static class AccountsExistTask extends AsyncTask<Context, Void, Boolean> {
@Override
protected Boolean doInBackground(Context... params) {
Context c = params[0];
return syncAccountsExist(c);
}
}
/**
* This class encapsulates the parameters needed to create a new Firefox Sync
* account.
*/
public static class SyncAccountParameters {
public final Context context;
public final AccountManager accountManager;
public final String username; // services.sync.account
public final String syncKey; // in password manager: "chrome://weave (Mozilla Services Encryption Passphrase)"
public final String password; // in password manager: "chrome://weave (Mozilla Services Password)"
public final String serverURL; // services.sync.serverURL
public final String clusterURL; // services.sync.clusterURL
public final String clientName; // services.sync.client.name
public final String clientGuid; // services.sync.client.GUID
/**
* Encapsulate the parameters needed to create a new Firefox Sync account.
*
* @param context
* the current <code>Context</code>; cannot be null.
* @param accountManager
* an <code>AccountManager</code> instance to use; if null, get it
* from <code>context</code>.
* @param username
* the desired username; cannot be null.
* @param syncKey
* the desired sync key; cannot be null.
* @param password
* the desired password; cannot be null.
* @param serverURL
* the server URL to use; if null, use the default.
* @param clusterURL
* the cluster URL to use; if null, a fresh cluster URL will be
* retrieved from the server during the next sync.
* @param clientName
* the client name; if null, a fresh client record will be uploaded
* to the server during the next sync.
* @param clientGuid
* the client GUID; if null, a fresh client record will be uploaded
* to the server during the next sync.
*/
public SyncAccountParameters(Context context, AccountManager accountManager,
String username, String syncKey, String password,
String serverURL, String clusterURL,
String clientName, String clientGuid) {
if (context == null) {
throw new IllegalArgumentException("Null context passed to SyncAccountParameters constructor.");
}
if (username == null) {
throw new IllegalArgumentException("Null username passed to SyncAccountParameters constructor.");
}
if (syncKey == null) {
throw new IllegalArgumentException("Null syncKey passed to SyncAccountParameters constructor.");
}
if (password == null) {
throw new IllegalArgumentException("Null password passed to SyncAccountParameters constructor.");
}
this.context = context;
this.accountManager = accountManager;
this.username = username;
this.syncKey = syncKey;
this.password = password;
this.serverURL = serverURL;
this.clusterURL = clusterURL;
this.clientName = clientName;
this.clientGuid = clientGuid;
}
public SyncAccountParameters(Context context, AccountManager accountManager,
String username, String syncKey, String password, String serverURL) {
this(context, accountManager, username, syncKey, password, serverURL, null, null, null);
}
}
/**
* This class provides background-thread abstracted access to creating a
* Firefox Sync account.
* <p>
* Subclass this task and override `onPostExecute` to act on the result. The
* <code>Result</code> (of type <code>Account</code>) is null if an error
* occurred and the account could not be added.
*/
public static class CreateSyncAccountTask extends AsyncTask<SyncAccountParameters, Void, Account> {
@Override
protected Account doInBackground(SyncAccountParameters... params) {
SyncAccountParameters syncAccount = params[0];
try {
return createSyncAccount(syncAccount);
} catch (Exception e) {
Log.e(Logger.GLOBAL_LOG_TAG, "Unable to create account.", e);
return null;
}
}
}
/**
* Create a sync account.
* <p>
* Do not call this method from the main thread.
*
* @param syncAccount
* The parameters of the account to be created.
* @return The created <code>Account</code>, or null if an error occurred and
* the account could not be added.
*/
public static Account createSyncAccount(SyncAccountParameters syncAccount) {
final Context context = syncAccount.context;
final AccountManager accountManager = (syncAccount.accountManager == null) ?
AccountManager.get(syncAccount.context) : syncAccount.accountManager;
final String username = syncAccount.username;
final String syncKey = syncAccount.syncKey;
final String password = syncAccount.password;
final String serverURL = (syncAccount.serverURL == null) ?
DEFAULT_SERVER : syncAccount.serverURL;
Logger.debug(LOG_TAG, "Using account manager " + accountManager);
if (!RepoUtils.stringsEqual(syncAccount.serverURL, DEFAULT_SERVER)) {
Logger.info(LOG_TAG, "Setting explicit server URL: " + serverURL);
}
final Account account = new Account(username, Constants.ACCOUNTTYPE_SYNC);
final Bundle userbundle = new Bundle();
// Add sync key and server URL.
userbundle.putString(Constants.OPTION_SYNCKEY, syncKey);
userbundle.putString(Constants.OPTION_SERVER, serverURL);
Logger.debug(LOG_TAG, "Adding account for " + Constants.ACCOUNTTYPE_SYNC);
boolean result = false;
try {
result = accountManager.addAccountExplicitly(account, password, userbundle);
} catch (SecurityException e) {
// We use Log rather than Logger here to avoid possibly hiding these errors.
final String message = e.getMessage();
if (message != null && (message.indexOf("is different than the authenticator's uid") > 0)) {
Log.wtf(Logger.GLOBAL_LOG_TAG,
"Unable to create account. " +
"If you have more than one version of " +
"Firefox/Beta/Aurora/Nightly/Fennec installed, that's why.",
e);
} else {
Log.e(Logger.GLOBAL_LOG_TAG, "Unable to create account.", e);
}
}
if (!result) {
Logger.error(LOG_TAG, "Failed to add account " + account + "!");
return null;
}
Logger.debug(LOG_TAG, "Account " + account + " added successfully.");
setSyncAutomatically(account);
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.
Logger.info(LOG_TAG, "Clearing global prefs.");
SyncAdapter.purgeGlobalPrefs(context);
try {
Logger.info(LOG_TAG, "Clearing preferences path " + Utils.getPrefsPath(username, serverURL) + " for this account.");
SharedPreferences.Editor editor = Utils.getSharedPreferences(context, username, serverURL).edit().clear();
if (syncAccount.clusterURL != null) {
editor.putString(SyncConfiguration.PREF_CLUSTER_URL, syncAccount.clusterURL);
}
editor.commit();
} catch (Exception e) {
Logger.error(LOG_TAG, "Could not clear prefs path!", e);
}
return account;
}
public static void setIsSyncable(Account account, boolean isSyncable) {
String authority = BrowserContract.AUTHORITY;
ContentResolver.setIsSyncable(account, authority, isSyncable ? 1 : 0);
}
public static void setSyncAutomatically(Account account, boolean syncAutomatically) {
if (syncAutomatically) {
ContentResolver.setMasterSyncAutomatically(true);
}
String authority = BrowserContract.AUTHORITY;
Logger.debug(LOG_TAG, "Setting authority " + authority + " to " +
(syncAutomatically ? "" : "not ") + "sync automatically.");
ContentResolver.setSyncAutomatically(account, authority, syncAutomatically);
}
public static void setSyncAutomatically(Account account) {
setSyncAutomatically(account, true);
setIsSyncable(account, true);
}
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;
}
Logger.debug(LOG_TAG, "Client name and guid not both non-null, so not setting client data.");
}
}