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

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/mobile/android/base/sync/setup/SyncAccounts.java	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,597 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +
     1.8 +package org.mozilla.gecko.sync.setup;
     1.9 +
    1.10 +import java.io.File;
    1.11 +import java.io.UnsupportedEncodingException;
    1.12 +import java.security.NoSuchAlgorithmException;
    1.13 +
    1.14 +import org.mozilla.gecko.background.common.GlobalConstants;
    1.15 +import org.mozilla.gecko.background.common.log.Logger;
    1.16 +import org.mozilla.gecko.db.BrowserContract;
    1.17 +import org.mozilla.gecko.sync.CredentialException;
    1.18 +import org.mozilla.gecko.sync.ExtendedJSONObject;
    1.19 +import org.mozilla.gecko.sync.SyncConfiguration;
    1.20 +import org.mozilla.gecko.sync.SyncConstants;
    1.21 +import org.mozilla.gecko.sync.ThreadPool;
    1.22 +import org.mozilla.gecko.sync.Utils;
    1.23 +import org.mozilla.gecko.sync.config.AccountPickler;
    1.24 +import org.mozilla.gecko.sync.repositories.android.RepoUtils;
    1.25 +
    1.26 +import android.accounts.Account;
    1.27 +import android.accounts.AccountManager;
    1.28 +import android.content.ActivityNotFoundException;
    1.29 +import android.content.ContentResolver;
    1.30 +import android.content.Context;
    1.31 +import android.content.Intent;
    1.32 +import android.content.SharedPreferences;
    1.33 +import android.content.pm.PackageManager.NameNotFoundException;
    1.34 +import android.os.Bundle;
    1.35 +import android.provider.Settings;
    1.36 +import android.util.Log;
    1.37 +
    1.38 +/**
    1.39 + * This class contains utilities that are of use to Fennec
    1.40 + * and Sync setup activities.
    1.41 + * <p>
    1.42 + * Do not break these APIs without correcting upstream code!
    1.43 + */
    1.44 +public class SyncAccounts {
    1.45 +  private static final String LOG_TAG = "SyncAccounts";
    1.46 +
    1.47 +  private static final String MOTO_BLUR_SETTINGS_ACTIVITY = "com.motorola.blur.settings.AccountsAndServicesPreferenceActivity";
    1.48 +  private static final String MOTO_BLUR_PACKAGE           = "com.motorola.blur.setup";
    1.49 +
    1.50 +  /**
    1.51 +   * Return Sync accounts.
    1.52 +   *
    1.53 +   * @param c
    1.54 +   *          Android context.
    1.55 +   * @return Sync accounts.
    1.56 +   */
    1.57 +  public static Account[] syncAccounts(final Context c) {
    1.58 +    return AccountManager.get(c).getAccountsByType(SyncConstants.ACCOUNTTYPE_SYNC);
    1.59 +  }
    1.60 +
    1.61 +  /**
    1.62 +   * Returns true if a Sync account is set up, or we have a pickled Sync account
    1.63 +   * on disk that should be un-pickled (Bug 769745). If we have a pickled Sync
    1.64 +   * account, try to un-pickle it and create the corresponding Sync account.
    1.65 +   * <p>
    1.66 +   * Do not call this method from the main thread.
    1.67 +   */
    1.68 +  public static boolean syncAccountsExist(Context c) {
    1.69 +    final boolean accountsExist = AccountManager.get(c).getAccountsByType(SyncConstants.ACCOUNTTYPE_SYNC).length > 0;
    1.70 +    if (accountsExist) {
    1.71 +      return true;
    1.72 +    }
    1.73 +
    1.74 +    final File file = c.getFileStreamPath(Constants.ACCOUNT_PICKLE_FILENAME);
    1.75 +    if (!file.exists()) {
    1.76 +      return false;
    1.77 +    }
    1.78 +
    1.79 +    // There is a small race window here: if the user creates a new Sync account
    1.80 +    // between our checks, this could erroneously report that no Sync accounts
    1.81 +    // exist.
    1.82 +    final Account account = AccountPickler.unpickle(c, Constants.ACCOUNT_PICKLE_FILENAME);
    1.83 +    return (account != null);
    1.84 +  }
    1.85 +
    1.86 +  /**
    1.87 +   * This class encapsulates the parameters needed to create a new Firefox Sync
    1.88 +   * account.
    1.89 +   */
    1.90 +  public static class SyncAccountParameters {
    1.91 +    public final Context context;
    1.92 +    public final AccountManager accountManager;
    1.93 +
    1.94 +
    1.95 +    public final String username;   // services.sync.account
    1.96 +    public final String syncKey;    // in password manager: "chrome://weave (Mozilla Services Encryption Passphrase)"
    1.97 +    public final String password;   // in password manager: "chrome://weave (Mozilla Services Password)"
    1.98 +    public final String serverURL;  // services.sync.serverURL
    1.99 +    public final String clusterURL; // services.sync.clusterURL
   1.100 +    public final String clientName; // services.sync.client.name
   1.101 +    public final String clientGuid; // services.sync.client.GUID
   1.102 +
   1.103 +    /**
   1.104 +     * Encapsulate the parameters needed to create a new Firefox Sync account.
   1.105 +     *
   1.106 +     * @param context
   1.107 +     *          the current <code>Context</code>; cannot be null.
   1.108 +     * @param accountManager
   1.109 +     *          an <code>AccountManager</code> instance to use; if null, get it
   1.110 +     *          from <code>context</code>.
   1.111 +     * @param username
   1.112 +     *          the desired username; cannot be null.
   1.113 +     * @param syncKey
   1.114 +     *          the desired sync key; cannot be null.
   1.115 +     * @param password
   1.116 +     *          the desired password; cannot be null.
   1.117 +     * @param serverURL
   1.118 +     *          the server URL to use; if null, use the default.
   1.119 +     * @param clusterURL
   1.120 +     *          the cluster URL to use; if null, a fresh cluster URL will be
   1.121 +     *          retrieved from the server during the next sync.
   1.122 +     * @param clientName
   1.123 +     *          the client name; if null, a fresh client record will be uploaded
   1.124 +     *          to the server during the next sync.
   1.125 +     * @param clientGuid
   1.126 +     *          the client GUID; if null, a fresh client record will be uploaded
   1.127 +     *          to the server during the next sync.
   1.128 +     */
   1.129 +    public SyncAccountParameters(Context context, AccountManager accountManager,
   1.130 +        String username, String syncKey, String password,
   1.131 +        String serverURL, String clusterURL,
   1.132 +        String clientName, String clientGuid) {
   1.133 +      if (context == null) {
   1.134 +        throw new IllegalArgumentException("Null context passed to SyncAccountParameters constructor.");
   1.135 +      }
   1.136 +      if (username == null) {
   1.137 +        throw new IllegalArgumentException("Null username passed to SyncAccountParameters constructor.");
   1.138 +      }
   1.139 +      if (syncKey == null) {
   1.140 +        throw new IllegalArgumentException("Null syncKey passed to SyncAccountParameters constructor.");
   1.141 +      }
   1.142 +      if (password == null) {
   1.143 +        throw new IllegalArgumentException("Null password passed to SyncAccountParameters constructor.");
   1.144 +      }
   1.145 +      this.context = context;
   1.146 +      this.accountManager = accountManager;
   1.147 +      this.username = username;
   1.148 +      this.syncKey = syncKey;
   1.149 +      this.password = password;
   1.150 +      this.serverURL = serverURL;
   1.151 +      this.clusterURL = clusterURL;
   1.152 +      this.clientName = clientName;
   1.153 +      this.clientGuid = clientGuid;
   1.154 +    }
   1.155 +
   1.156 +    public SyncAccountParameters(Context context, AccountManager accountManager,
   1.157 +        String username, String syncKey, String password, String serverURL) {
   1.158 +      this(context, accountManager, username, syncKey, password, serverURL, null, null, null);
   1.159 +    }
   1.160 +
   1.161 +    public SyncAccountParameters(final Context context, final AccountManager accountManager, final ExtendedJSONObject o) {
   1.162 +      this(context, accountManager,
   1.163 +          o.getString(Constants.JSON_KEY_ACCOUNT),
   1.164 +          o.getString(Constants.JSON_KEY_SYNCKEY),
   1.165 +          o.getString(Constants.JSON_KEY_PASSWORD),
   1.166 +          o.getString(Constants.JSON_KEY_SERVER),
   1.167 +          o.getString(Constants.JSON_KEY_CLUSTER),
   1.168 +          o.getString(Constants.JSON_KEY_CLIENT_NAME),
   1.169 +          o.getString(Constants.JSON_KEY_CLIENT_GUID));
   1.170 +    }
   1.171 +
   1.172 +    public ExtendedJSONObject asJSON() {
   1.173 +      final ExtendedJSONObject o = new ExtendedJSONObject();
   1.174 +      o.put(Constants.JSON_KEY_ACCOUNT, username);
   1.175 +      o.put(Constants.JSON_KEY_PASSWORD, password);
   1.176 +      o.put(Constants.JSON_KEY_SERVER, serverURL);
   1.177 +      o.put(Constants.JSON_KEY_SYNCKEY, syncKey);
   1.178 +      o.put(Constants.JSON_KEY_CLUSTER, clusterURL);
   1.179 +      o.put(Constants.JSON_KEY_CLIENT_NAME, clientName);
   1.180 +      o.put(Constants.JSON_KEY_CLIENT_GUID, clientGuid);
   1.181 +      return o;
   1.182 +    }
   1.183 +  }
   1.184 +
   1.185 +  /**
   1.186 +   * Create a sync account, clearing any existing preferences, and set it to
   1.187 +   * sync automatically.
   1.188 +   * <p>
   1.189 +   * Do not call this method from the main thread.
   1.190 +   *
   1.191 +   * @param syncAccount
   1.192 +   *          parameters of the account to be created.
   1.193 +   * @return created <code>Account</code>, or null if an error occurred and the
   1.194 +   *         account could not be added.
   1.195 +   */
   1.196 +  public static Account createSyncAccount(SyncAccountParameters syncAccount) {
   1.197 +    return createSyncAccount(syncAccount, true, true);
   1.198 +  }
   1.199 +
   1.200 +  /**
   1.201 +   * Create a sync account, clearing any existing preferences.
   1.202 +   * <p>
   1.203 +   * Do not call this method from the main thread.
   1.204 +   * <p>
   1.205 +   * Intended for testing; use
   1.206 +   * <code>createSyncAccount(SyncAccountParameters)</code> instead.
   1.207 +   *
   1.208 +   * @param syncAccount
   1.209 +   *          parameters of the account to be created.
   1.210 +   * @param syncAutomatically
   1.211 +   *          whether to start syncing this Account automatically (
   1.212 +   *          <code>false</code> for test accounts).
   1.213 +   * @return created Android <code>Account</code>, or null if an error occurred
   1.214 +   *         and the account could not be added.
   1.215 +   */
   1.216 +  public static Account createSyncAccount(SyncAccountParameters syncAccount,
   1.217 +      boolean syncAutomatically) {
   1.218 +    return createSyncAccount(syncAccount, syncAutomatically, true);
   1.219 +  }
   1.220 +
   1.221 +  public static Account createSyncAccountPreservingExistingPreferences(SyncAccountParameters syncAccount,
   1.222 +      boolean syncAutomatically) {
   1.223 +    return createSyncAccount(syncAccount, syncAutomatically, false);
   1.224 +  }
   1.225 +
   1.226 +  /**
   1.227 +   * Create a sync account.
   1.228 +   * <p>
   1.229 +   * Do not call this method from the main thread.
   1.230 +   * <p>
   1.231 +   * Intended for testing; use
   1.232 +   * <code>createSyncAccount(SyncAccountParameters)</code> instead.
   1.233 +   *
   1.234 +   * @param syncAccount
   1.235 +   *          parameters of the account to be created.
   1.236 +   * @param syncAutomatically
   1.237 +   *          whether to start syncing this Account automatically (
   1.238 +   *          <code>false</code> for test accounts).
   1.239 +   * @param clearPreferences
   1.240 +   *          <code>true</code> to clear existing preferences before creating.
   1.241 +   * @return created Android <code>Account</code>, or null if an error occurred
   1.242 +   *         and the account could not be added.
   1.243 +   */
   1.244 +  protected static Account createSyncAccount(SyncAccountParameters syncAccount,
   1.245 +      boolean syncAutomatically, boolean clearPreferences) {
   1.246 +    final Context context = syncAccount.context;
   1.247 +    final AccountManager accountManager = (syncAccount.accountManager == null) ?
   1.248 +          AccountManager.get(syncAccount.context) : syncAccount.accountManager;
   1.249 +    final String username  = syncAccount.username;
   1.250 +    final String syncKey   = syncAccount.syncKey;
   1.251 +    final String password  = syncAccount.password;
   1.252 +    final String serverURL = (syncAccount.serverURL == null) ?
   1.253 +        SyncConstants.DEFAULT_AUTH_SERVER : syncAccount.serverURL;
   1.254 +
   1.255 +    Logger.debug(LOG_TAG, "Using account manager " + accountManager);
   1.256 +    if (!RepoUtils.stringsEqual(syncAccount.serverURL, SyncConstants.DEFAULT_AUTH_SERVER)) {
   1.257 +      Logger.info(LOG_TAG, "Setting explicit server URL: " + serverURL);
   1.258 +    }
   1.259 +
   1.260 +    final Account account = new Account(username, SyncConstants.ACCOUNTTYPE_SYNC);
   1.261 +    final Bundle userbundle = new Bundle();
   1.262 +
   1.263 +    // Add sync key and server URL.
   1.264 +    userbundle.putString(Constants.OPTION_SYNCKEY, syncKey);
   1.265 +    userbundle.putString(Constants.OPTION_SERVER, serverURL);
   1.266 +    Logger.debug(LOG_TAG, "Adding account for " + SyncConstants.ACCOUNTTYPE_SYNC);
   1.267 +    boolean result = false;
   1.268 +    try {
   1.269 +      result = accountManager.addAccountExplicitly(account, password, userbundle);
   1.270 +    } catch (SecurityException e) {
   1.271 +      // We use Log rather than Logger here to avoid possibly hiding these errors.
   1.272 +      final String message = e.getMessage();
   1.273 +      if (message != null && (message.indexOf("is different than the authenticator's uid") > 0)) {
   1.274 +        Log.wtf(SyncConstants.GLOBAL_LOG_TAG,
   1.275 +                "Unable to create account. " +
   1.276 +                "If you have more than one version of " +
   1.277 +                "Firefox/Beta/Aurora/Nightly/Fennec installed, that's why.",
   1.278 +                e);
   1.279 +      } else {
   1.280 +        Log.e(SyncConstants.GLOBAL_LOG_TAG, "Unable to create account.", e);
   1.281 +      }
   1.282 +    }
   1.283 +
   1.284 +    if (!result) {
   1.285 +      Logger.error(LOG_TAG, "Failed to add account " + account + "!");
   1.286 +      return null;
   1.287 +    }
   1.288 +    Logger.debug(LOG_TAG, "Account " + account + " added successfully.");
   1.289 +
   1.290 +    setSyncAutomatically(account, syncAutomatically);
   1.291 +    setIsSyncable(account, syncAutomatically);
   1.292 +    Logger.debug(LOG_TAG, "Set account to sync automatically? " + syncAutomatically + ".");
   1.293 +
   1.294 +    try {
   1.295 +      final String product = GlobalConstants.BROWSER_INTENT_PACKAGE;
   1.296 +      final String profile = Constants.DEFAULT_PROFILE;
   1.297 +      final long version = SyncConfiguration.CURRENT_PREFS_VERSION;
   1.298 +
   1.299 +      final SharedPreferences.Editor editor = Utils.getSharedPreferences(context, product, username, serverURL, profile, version).edit();
   1.300 +      if (clearPreferences) {
   1.301 +        final String prefsPath = Utils.getPrefsPath(product, username, serverURL, profile, version);
   1.302 +        Logger.info(LOG_TAG, "Clearing preferences path " + prefsPath + " for this account.");
   1.303 +        editor.clear();
   1.304 +      }
   1.305 +
   1.306 +      if (syncAccount.clusterURL != null) {
   1.307 +        editor.putString(SyncConfiguration.PREF_CLUSTER_URL, syncAccount.clusterURL);
   1.308 +      }
   1.309 +
   1.310 +      if (syncAccount.clientName != null && syncAccount.clientGuid != null) {
   1.311 +        Logger.debug(LOG_TAG, "Setting client name to " + syncAccount.clientName + " and client GUID to " + syncAccount.clientGuid + ".");
   1.312 +        editor.putString(SyncConfiguration.PREF_CLIENT_NAME, syncAccount.clientName);
   1.313 +        editor.putString(SyncConfiguration.PREF_ACCOUNT_GUID, syncAccount.clientGuid);
   1.314 +      } else {
   1.315 +        Logger.debug(LOG_TAG, "Client name and guid not both non-null, so not setting client data.");
   1.316 +      }
   1.317 +
   1.318 +      editor.commit();
   1.319 +    } catch (Exception e) {
   1.320 +      Logger.error(LOG_TAG, "Could not clear prefs path!", e);
   1.321 +    }
   1.322 +    return account;
   1.323 +  }
   1.324 +
   1.325 +  public static void setIsSyncable(Account account, boolean isSyncable) {
   1.326 +    String authority = BrowserContract.AUTHORITY;
   1.327 +    ContentResolver.setIsSyncable(account, authority, isSyncable ? 1 : 0);
   1.328 +  }
   1.329 +
   1.330 +  public static void setSyncAutomatically(Account account, boolean syncAutomatically) {
   1.331 +    if (syncAutomatically) {
   1.332 +      ContentResolver.setMasterSyncAutomatically(true);
   1.333 +    }
   1.334 +
   1.335 +    String authority = BrowserContract.AUTHORITY;
   1.336 +    Logger.debug(LOG_TAG, "Setting authority " + authority + " to " +
   1.337 +                          (syncAutomatically ? "" : "not ") + "sync automatically.");
   1.338 +    ContentResolver.setSyncAutomatically(account, authority, syncAutomatically);
   1.339 +  }
   1.340 +
   1.341 +  public static void backgroundSetSyncAutomatically(final Account account, final boolean syncAutomatically) {
   1.342 +    ThreadPool.run(new Runnable() {
   1.343 +      @Override
   1.344 +      public void run() {
   1.345 +        setSyncAutomatically(account, syncAutomatically);
   1.346 +      }
   1.347 +    });
   1.348 +  }
   1.349 +  /**
   1.350 +   * Bug 721760: try to start a vendor-specific Accounts & Sync activity on Moto
   1.351 +   * Blur devices.
   1.352 +   * <p>
   1.353 +   * Bug 773562: actually start and catch <code>ActivityNotFoundException</code>,
   1.354 +   * rather than just returning the <code>Intent</code> only, because some
   1.355 +   * Moto devices fail to start the activity.
   1.356 +   *
   1.357 +   * @param context
   1.358 +   *          current Android context.
   1.359 +   * @param vendorPackage
   1.360 +   *          vendor specific package name.
   1.361 +   * @param vendorClass
   1.362 +   *          vendor specific class name.
   1.363 +   * @return null on failure, otherwise the <code>Intent</code> started.
   1.364 +   */
   1.365 +  protected static Intent openVendorSyncSettings(Context context, final String vendorPackage, final String vendorClass) {
   1.366 +    try {
   1.367 +      final int contextFlags = Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY;
   1.368 +      Context foreignContext = context.createPackageContext(vendorPackage, contextFlags);
   1.369 +      Class<?> klass = foreignContext.getClassLoader().loadClass(vendorClass);
   1.370 +
   1.371 +      final Intent intent = new Intent(foreignContext, klass);
   1.372 +      context.startActivity(intent);
   1.373 +      Logger.info(LOG_TAG, "Vendor package " + vendorPackage + " and class " +
   1.374 +          vendorClass + " found, and activity launched.");
   1.375 +      return intent;
   1.376 +    } catch (NameNotFoundException e) {
   1.377 +      Logger.debug(LOG_TAG, "Vendor package " + vendorPackage + " not found. Skipping.");
   1.378 +    } catch (ClassNotFoundException e) {
   1.379 +      Logger.debug(LOG_TAG, "Vendor package " + vendorPackage + " found but class " +
   1.380 +          vendorClass + " not found. Skipping.", e);
   1.381 +    } catch (ActivityNotFoundException e) {
   1.382 +      // Bug 773562 - android.content.ActivityNotFoundException on Motorola devices.
   1.383 +      Logger.warn(LOG_TAG, "Vendor package " + vendorPackage + " and class " +
   1.384 +          vendorClass + " found, but activity not launched. Skipping.", e);
   1.385 +    } catch (Exception e) {
   1.386 +      // Just in case.
   1.387 +      Logger.warn(LOG_TAG, "Caught exception launching activity from vendor package " + vendorPackage +
   1.388 +          " and class " + vendorClass + ". Ignoring.", e);
   1.389 +    }
   1.390 +    return null;
   1.391 +  }
   1.392 +
   1.393 +  /**
   1.394 +   * Start Sync settings activity.
   1.395 +   *
   1.396 +   * @param context
   1.397 +   *          current Android context.
   1.398 +   * @return the <code>Intent</code> started.
   1.399 +   */
   1.400 +  public static Intent openSyncSettings(Context context) {
   1.401 +    // Bug 721760 - opening Sync settings takes user to Battery & Data Manager
   1.402 +    // on a variety of Motorola devices. This work around tries to load the
   1.403 +    // correct Intent by hand. Oh, Android.
   1.404 +    Intent intent = openVendorSyncSettings(context, MOTO_BLUR_PACKAGE, MOTO_BLUR_SETTINGS_ACTIVITY);
   1.405 +    if (intent != null) {
   1.406 +      return intent;
   1.407 +    }
   1.408 +
   1.409 +    // Open default Sync settings activity.
   1.410 +    intent = new Intent(Settings.ACTION_SYNC_SETTINGS);
   1.411 +    // Bug 774233: do not start activity as a new task (second run fails on some HTC devices).
   1.412 +    context.startActivity(intent); // We should always find this Activity.
   1.413 +    return intent;
   1.414 +  }
   1.415 +
   1.416 +  /**
   1.417 +   * Synchronously extract Sync account parameters from Android account version
   1.418 +   * 0, using plain auth token type.
   1.419 +   * <p>
   1.420 +   * Safe to call from main thread.
   1.421 +   *
   1.422 +   * @param context
   1.423 +   *          Android context.
   1.424 +   * @param accountManager
   1.425 +   *          Android account manager.
   1.426 +   * @param account
   1.427 +   *          Android Account.
   1.428 +   * @return Sync account parameters, always non-null; fields username,
   1.429 +   *         password, serverURL, and syncKey always non-null.
   1.430 +   */
   1.431 +  public static SyncAccountParameters blockingFromAndroidAccountV0(final Context context, final AccountManager accountManager, final Account account)
   1.432 +      throws CredentialException {
   1.433 +    String username;
   1.434 +    try {
   1.435 +      username = Utils.usernameFromAccount(account.name);
   1.436 +    } catch (NoSuchAlgorithmException e) {
   1.437 +      throw new CredentialException.MissingCredentialException("username");
   1.438 +    } catch (UnsupportedEncodingException e) {
   1.439 +      throw new CredentialException.MissingCredentialException("username");
   1.440 +    }
   1.441 +
   1.442 +    /*
   1.443 +     * If we are accessing an Account that we don't own, Android will throw an
   1.444 +     * unchecked <code>SecurityException</code> saying
   1.445 +     * "W FxSync(XXXX) java.lang.SecurityException: caller uid XXXXX is different than the authenticator's uid".
   1.446 +     * We catch that error and throw accordingly.
   1.447 +     */
   1.448 +    String password;
   1.449 +    String syncKey;
   1.450 +    String serverURL;
   1.451 +    try {
   1.452 +      password = accountManager.getPassword(account);
   1.453 +      syncKey = accountManager.getUserData(account, Constants.OPTION_SYNCKEY);
   1.454 +      serverURL = accountManager.getUserData(account, Constants.OPTION_SERVER);
   1.455 +    } catch (SecurityException e) {
   1.456 +      Logger.warn(LOG_TAG, "Got security exception fetching Sync account parameters; throwing.");
   1.457 +      throw new CredentialException.MissingAllCredentialsException(e);
   1.458 +    }
   1.459 +
   1.460 +    if (password  == null &&
   1.461 +        username  == null &&
   1.462 +        syncKey   == null &&
   1.463 +        serverURL == null) {
   1.464 +      throw new CredentialException.MissingAllCredentialsException();
   1.465 +    }
   1.466 +
   1.467 +    if (password == null) {
   1.468 +      throw new CredentialException.MissingCredentialException("password");
   1.469 +    }
   1.470 +
   1.471 +    if (syncKey == null) {
   1.472 +      throw new CredentialException.MissingCredentialException("syncKey");
   1.473 +    }
   1.474 +
   1.475 +    if (serverURL == null) {
   1.476 +      throw new CredentialException.MissingCredentialException("serverURL");
   1.477 +    }
   1.478 +
   1.479 +    try {
   1.480 +      // SyncAccountParameters constructor throws on null inputs. This shouldn't
   1.481 +      // happen, but let's be safe.
   1.482 +      return new SyncAccountParameters(context, accountManager, username, syncKey, password, serverURL);
   1.483 +    } catch (Exception e) {
   1.484 +      Logger.warn(LOG_TAG, "Got exception fetching Sync account parameters; throwing.");
   1.485 +      throw new CredentialException.MissingAllCredentialsException(e);
   1.486 +    }
   1.487 +  }
   1.488 +
   1.489 +  /**
   1.490 +   * Bug 790931: create an intent announcing that a Sync account will be
   1.491 +   * deleted.
   1.492 +   * <p>
   1.493 +   * This intent <b>must</b> be broadcast with secure permissions, because it
   1.494 +   * contains sensitive user information including the Sync account password and
   1.495 +   * Sync key.
   1.496 +   * <p>
   1.497 +   * Version 1 of the created intent includes extras with keys
   1.498 +   * <code>Constants.JSON_KEY_VERSION</code>,
   1.499 +   * <code>Constants.JSON_KEY_TIMESTAMP</code>, and
   1.500 +   * <code>Constants.JSON_KEY_ACCOUNT</code> (which is the Android Account name,
   1.501 +   * not the encoded Sync Account name).
   1.502 +   * <p>
   1.503 +   * If possible, it contains the key <code>Constants.JSON_KEY_PAYLOAD</code>
   1.504 +   * with value the Sync account parameters as JSON, <b>except the Sync key has
   1.505 +   * been replaced with the empty string</b>. (We replace, rather than remove,
   1.506 +   * the Sync key because SyncAccountParameters expects a non-null Sync key.)
   1.507 +   *
   1.508 +   * @see SyncAccountParameters#asJSON
   1.509 +   *
   1.510 +   * @param context
   1.511 +   *          Android context.
   1.512 +   * @param accountManager
   1.513 +   *          Android account manager.
   1.514 +   * @param account
   1.515 +   *          Android account being removed.
   1.516 +   * @return <code>Intent</code> to broadcast.
   1.517 +   */
   1.518 +  public static Intent makeSyncAccountDeletedIntent(final Context context, final AccountManager accountManager, final Account account) {
   1.519 +    final Intent intent = new Intent(SyncConstants.SYNC_ACCOUNT_DELETED_ACTION);
   1.520 +
   1.521 +    intent.putExtra(Constants.JSON_KEY_VERSION, Long.valueOf(SyncConstants.SYNC_ACCOUNT_DELETED_INTENT_VERSION));
   1.522 +    intent.putExtra(Constants.JSON_KEY_TIMESTAMP, Long.valueOf(System.currentTimeMillis()));
   1.523 +    intent.putExtra(Constants.JSON_KEY_ACCOUNT, account.name);
   1.524 +
   1.525 +    SyncAccountParameters accountParameters = null;
   1.526 +    try {
   1.527 +      accountParameters = SyncAccounts.blockingFromAndroidAccountV0(context, accountManager, account);
   1.528 +    } catch (Exception e) {
   1.529 +      Logger.warn(LOG_TAG, "Caught exception fetching account parameters.", e);
   1.530 +    }
   1.531 +
   1.532 +    if (accountParameters != null) {
   1.533 +      ExtendedJSONObject json = accountParameters.asJSON();
   1.534 +      json.put(Constants.JSON_KEY_SYNCKEY, ""); // Reduce attack surface area by removing Sync key.
   1.535 +      intent.putExtra(Constants.JSON_KEY_PAYLOAD, json.toJSONString());
   1.536 +    }
   1.537 +
   1.538 +    return intent;
   1.539 +  }
   1.540 +
   1.541 +  /**
   1.542 +   * Synchronously fetch SharedPreferences of a profile associated with a Sync
   1.543 +   * account.
   1.544 +   * <p>
   1.545 +   * Safe to call from main thread.
   1.546 +   *
   1.547 +   * @param context
   1.548 +   *          Android context.
   1.549 +   * @param accountManager
   1.550 +   *          Android account manager.
   1.551 +   * @param account
   1.552 +   *          Android Account.
   1.553 +   * @param product
   1.554 +   *          package.
   1.555 +   * @param profile
   1.556 +   *          of account.
   1.557 +   * @param version
   1.558 +   *          number.
   1.559 +   * @return SharedPreferences associated with Sync account.
   1.560 +   * @throws CredentialException
   1.561 +   * @throws NoSuchAlgorithmException
   1.562 +   * @throws UnsupportedEncodingException
   1.563 +   */
   1.564 +  public static SharedPreferences blockingPrefsFromAndroidAccountV0(final Context context, final AccountManager accountManager, final Account account,
   1.565 +      final String product, final String profile, final long version)
   1.566 +          throws CredentialException, NoSuchAlgorithmException, UnsupportedEncodingException {
   1.567 +    SyncAccountParameters params = SyncAccounts.blockingFromAndroidAccountV0(context, accountManager, account);
   1.568 +    String prefsPath = Utils.getPrefsPath(product, params.username, params.serverURL, profile, version);
   1.569 +
   1.570 +    return context.getSharedPreferences(prefsPath, Utils.SHARED_PREFERENCES_MODE);
   1.571 +  }
   1.572 +
   1.573 +  /**
   1.574 +   * Synchronously fetch SharedPreferences of a profile associated with the
   1.575 +   * default Firefox profile of a Sync Account.
   1.576 +   * <p>
   1.577 +   * Uses the default package, default profile, and current version.
   1.578 +   * <p>
   1.579 +   * Safe to call from main thread.
   1.580 +   *
   1.581 +   * @param context
   1.582 +   *          Android context.
   1.583 +   * @param accountManager
   1.584 +   *          Android account manager.
   1.585 +   * @param account
   1.586 +   *          Android Account.
   1.587 +   * @return SharedPreferences associated with Sync account.
   1.588 +   * @throws CredentialException
   1.589 +   * @throws NoSuchAlgorithmException
   1.590 +   * @throws UnsupportedEncodingException
   1.591 +   */
   1.592 +  public static SharedPreferences blockingPrefsFromDefaultProfileV0(final Context context, final AccountManager accountManager, final Account account)
   1.593 +      throws CredentialException, NoSuchAlgorithmException, UnsupportedEncodingException {
   1.594 +    final String product = GlobalConstants.BROWSER_INTENT_PACKAGE;
   1.595 +    final String profile = Constants.DEFAULT_PROFILE;
   1.596 +    final long version = SyncConfiguration.CURRENT_PREFS_VERSION;
   1.597 +
   1.598 +    return blockingPrefsFromAndroidAccountV0(context, accountManager, account, product, profile, version);
   1.599 +  }
   1.600 +}

mercurial