diff --git a/mobile/android/base/sync/GlobalSession.java b/mobile/android/base/sync/GlobalSession.java index 8e87a1cd907..856c9d4db1d 100644 --- a/mobile/android/base/sync/GlobalSession.java +++ b/mobile/android/base/sync/GlobalSession.java @@ -190,10 +190,7 @@ public class GlobalSession implements CredentialsSource, PrefsSource, HttpRespon // TODO: more stages. stages.put(Stage.syncTabs, new FennecTabsServerSyncStage()); - - // Disable until Bug 744816 is fixed. - // stages.put(Stage.syncPasswords, new PasswordsServerSyncStage()); - + stages.put(Stage.syncPasswords, new PasswordsServerSyncStage()); stages.put(Stage.syncBookmarks, new AndroidBrowserBookmarksServerSyncStage()); stages.put(Stage.syncHistory, new AndroidBrowserHistoryServerSyncStage()); stages.put(Stage.syncFormHistory, new FormHistoryServerSyncStage()); diff --git a/mobile/android/base/sync/SyncConfiguration.java b/mobile/android/base/sync/SyncConfiguration.java index ee3c4f73730..27edeb19f70 100644 --- a/mobile/android/base/sync/SyncConfiguration.java +++ b/mobile/android/base/sync/SyncConfiguration.java @@ -192,6 +192,8 @@ public class SyncConfiguration implements CredentialsSource { public PrefsSource prefsSource; public static final String CLIENT_RECORD_TIMESTAMP = "serverClientRecordTimestamp"; + public static final String PREF_CLUSTER_URL = "clusterURL"; + public static final String PREF_SYNC_ID = "syncID"; /** * Create a new SyncConfiguration instance. Pass in a PrefsSource to @@ -220,8 +222,8 @@ public class SyncConfiguration implements CredentialsSource { public void loadFromPrefs(SharedPreferences prefs) { - if (prefs.contains("clusterURL")) { - String u = prefs.getString("clusterURL", null); + if (prefs.contains(PREF_CLUSTER_URL)) { + String u = prefs.getString(PREF_CLUSTER_URL, null); try { clusterURL = new URI(u); Logger.info(LOG_TAG, "Set clusterURL from bundle: " + u); @@ -229,8 +231,8 @@ public class SyncConfiguration implements CredentialsSource { Logger.warn(LOG_TAG, "Ignoring bundle clusterURL (" + u + "): invalid URI.", e); } } - if (prefs.contains("syncID")) { - syncID = prefs.getString("syncID", null); + if (prefs.contains(PREF_SYNC_ID)) { + syncID = prefs.getString(PREF_SYNC_ID, null); Logger.info(LOG_TAG, "Set syncID from bundle: " + syncID); } // We don't set crypto/keys here because we need the syncKeyBundle to decrypt the JSON @@ -245,12 +247,12 @@ public class SyncConfiguration implements CredentialsSource { public void persistToPrefs(SharedPreferences prefs) { Editor edit = prefs.edit(); if (clusterURL == null) { - edit.remove("clusterURL"); + edit.remove(PREF_CLUSTER_URL); } else { - edit.putString("clusterURL", clusterURL.toASCIIString()); + edit.putString(PREF_CLUSTER_URL, clusterURL.toASCIIString()); } if (syncID != null) { - edit.putString("syncID", syncID); + edit.putString(PREF_SYNC_ID, syncID); } edit.commit(); // TODO: keys. @@ -339,7 +341,7 @@ public class SyncConfiguration implements CredentialsSource { clusterURL = u; if (shouldPersist) { Editor edit = prefs.edit(); - edit.putString("clusterURL", clusterURL.toASCIIString()); + edit.putString(PREF_CLUSTER_URL, clusterURL.toASCIIString()); edit.commit(); } } diff --git a/mobile/android/base/sync/setup/SyncAccounts.java b/mobile/android/base/sync/setup/SyncAccounts.java index fd7b1c1f5d6..072e29d6b9b 100644 --- a/mobile/android/base/sync/setup/SyncAccounts.java +++ b/mobile/android/base/sync/setup/SyncAccounts.java @@ -6,13 +6,16 @@ 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.Intent; +import android.content.SharedPreferences; import android.os.AsyncTask; import android.os.Bundle; import android.util.Log; @@ -20,27 +23,28 @@ import android.util.Log; /** * This class contains utilities that are of use to Fennec * and Sync setup activities. - * + *

* Do not break these APIs without correcting upstream code! */ public class SyncAccounts { - private final static String DEFAULT_SERVER = "https://auth.services.mozilla.com/"; + public final static String DEFAULT_SERVER = "https://auth.services.mozilla.com/"; private static final String LOG_TAG = "SyncAccounts"; + private static final String GLOBAL_LOG_TAG = "FxSync"; /** * Returns true if a Sync account is set up. - * + *

* Do not call this method from the main thread. */ public static boolean syncAccountsExist(Context c) { - return AccountManager.get(c).getAccountsByType("org.mozilla.firefox_sync").length > 0; + 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. - * + *

* Subclass this task and override `onPostExecute` to act on the result. */ public static class AccountsExistTask extends AsyncTask { @@ -51,53 +55,160 @@ public class SyncAccounts { } } - public static Intent createAccount(Context context, - AccountManager accountManager, - String username, - String syncKey, - String password, - String serverURL) { + /** + * 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 Password)" + public final String password; // in password manager: "chrome://weave (Mozilla Services Encryption Passphrase)" + 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 Context; cannot be null. + * @param accountManager + * an AccountManager instance to use; if null, get it + * from context. + * @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. + *

+ * Subclass this task and override `onPostExecute` to act on the result. The + * Result (of type Account) is null if an error + * occurred and the account could not be added. + */ + public static class CreateSyncAccountTask extends AsyncTask { + @Override + protected Account doInBackground(SyncAccountParameters... params) { + SyncAccountParameters syncAccount = params[0]; + try { + return createSyncAccount(syncAccount); + } catch (Exception e) { + Log.e(GLOBAL_LOG_TAG, "Unable to create account.", e); + return null; + } + } + } + + /** + * Create a sync account. + *

+ * Do not call this method from the main thread. + * + * @param syncAccount + * The parameters of the account to be created. + * @return The created Account, 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); - if (serverURL != null) { - Logger.info(LOG_TAG, "Setting explicit server URL: " + serverURL); - userbundle.putString(Constants.OPTION_SERVER, serverURL); - } else { - userbundle.putString(Constants.OPTION_SERVER, DEFAULT_SERVER); - } + 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("FirefoxSync", + Log.wtf(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("FirefoxSync", "Unable to create account.", e); + Log.e(GLOBAL_LOG_TAG, "Unable to create account.", e); } } - Logger.debug(LOG_TAG, "Account: " + account + " added successfully? " + result); if (!result) { - Logger.error(LOG_TAG, "Failed to add account!"); + Logger.error(LOG_TAG, "Failed to add account " + account + "!"); + return null; } + Logger.debug(LOG_TAG, "Account " + account + " added successfully."); - // Set components to sync (default: all). - ContentResolver.setMasterSyncAutomatically(true); - - String authority = BrowserContract.AUTHORITY; - Logger.debug(LOG_TAG, "Setting authority " + authority + " to sync automatically."); - ContentResolver.setSyncAutomatically(account, authority, true); - ContentResolver.setIsSyncable(account, authority, 1); + 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 @@ -106,15 +217,33 @@ public class SyncAccounts { // TODO: correctly implement Sync Options. Logger.info(LOG_TAG, "Clearing preferences for this account."); try { - Utils.getSharedPreferences(context, username, serverURL).edit().clear().commit(); + 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; + } - final Intent intent = new Intent(); - intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, username); - intent.putExtra(AccountManager.KEY_ACCOUNT_TYPE, Constants.ACCOUNTTYPE_SYNC); - intent.putExtra(AccountManager.KEY_AUTHTOKEN, Constants.ACCOUNTTYPE_SYNC); - return intent; + protected static void setSyncAutomatically(Account account) { + ContentResolver.setMasterSyncAutomatically(true); + String authority = BrowserContract.AUTHORITY; + Logger.debug(LOG_TAG, "Setting authority " + authority + " to sync automatically."); + ContentResolver.setSyncAutomatically(account, authority, true); + ContentResolver.setIsSyncable(account, authority, 1); + } + + 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."); } } diff --git a/mobile/android/base/sync/setup/activities/AccountActivity.java b/mobile/android/base/sync/setup/activities/AccountActivity.java index 9d5b4e36299..19b3b2bba4d 100644 --- a/mobile/android/base/sync/setup/activities/AccountActivity.java +++ b/mobile/android/base/sync/setup/activities/AccountActivity.java @@ -10,7 +10,9 @@ import org.mozilla.gecko.R; import org.mozilla.gecko.sync.setup.Constants; import org.mozilla.gecko.sync.setup.InvalidSyncKeyException; import org.mozilla.gecko.sync.setup.SyncAccounts; +import org.mozilla.gecko.sync.setup.SyncAccounts.SyncAccountParameters; +import android.accounts.Account; import android.accounts.AccountAuthenticatorActivity; import android.accounts.AccountManager; import android.content.Context; @@ -178,21 +180,34 @@ public class AccountActivity extends AccountAuthenticatorActivity { private void authCallback() { // Create and add account to AccountManager // TODO: only allow one account to be added? - Log.d(LOG_TAG, "Using account manager " + mAccountManager); - final Intent intent = SyncAccounts.createAccount(mContext, mAccountManager, - username, - key, password, server); + final SyncAccountParameters syncAccount = new SyncAccountParameters(mContext, mAccountManager, + username, key, password, server); + final Account account = SyncAccounts.createSyncAccount(syncAccount); + final boolean result = (account != null); + + final Intent intent = new Intent(); // The intent to return. + intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, syncAccount.username); + intent.putExtra(AccountManager.KEY_ACCOUNT_TYPE, Constants.ACCOUNTTYPE_SYNC); + intent.putExtra(AccountManager.KEY_AUTHTOKEN, Constants.ACCOUNTTYPE_SYNC); setAccountAuthenticatorResult(intent.getExtras()); - // Testing out the authFailure case - // authFailure(); + if (!result) { + // Failed to add account! + setResult(RESULT_CANCELED, intent); + runOnUiThread(new Runnable() { + @Override + public void run() { + authFailure(); + } + }); + return; + } // TODO: Currently, we do not actually authenticate username/pass against // Moz sync server. - // Successful authentication result + // Successfully added account. setResult(RESULT_OK, intent); - runOnUiThread(new Runnable() { @Override public void run() { @@ -201,7 +216,6 @@ public class AccountActivity extends AccountAuthenticatorActivity { }); } - @SuppressWarnings("unused") private void authFailure() { enableCredEntry(true); Intent intent = new Intent(mContext, SetupFailureActivity.class); diff --git a/mobile/android/base/sync/setup/activities/SetupSyncActivity.java b/mobile/android/base/sync/setup/activities/SetupSyncActivity.java index 3745455ad10..01f48b79a93 100644 --- a/mobile/android/base/sync/setup/activities/SetupSyncActivity.java +++ b/mobile/android/base/sync/setup/activities/SetupSyncActivity.java @@ -14,6 +14,7 @@ import org.mozilla.gecko.sync.jpake.JPakeClient; import org.mozilla.gecko.sync.jpake.JPakeNoActivePairingException; 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.AccountAuthenticatorActivity; @@ -356,26 +357,38 @@ public class SetupSyncActivity extends AccountAuthenticatorActivity { * @param jCreds */ public void onComplete(JSONObject jCreds) { + boolean result = true; + if (!pairWithPin) { String accountName = (String) jCreds.get(Constants.JSON_KEY_ACCOUNT); String password = (String) jCreds.get(Constants.JSON_KEY_PASSWORD); String syncKey = (String) jCreds.get(Constants.JSON_KEY_SYNCKEY); String serverURL = (String) jCreds.get(Constants.JSON_KEY_SERVER); - Logger.debug(LOG_TAG, "Using account manager " + mAccountManager); - final Intent intent = SyncAccounts.createAccount(mContext, mAccountManager, - accountName, - syncKey, password, serverURL); + final SyncAccountParameters syncAccount = new SyncAccountParameters(mContext, mAccountManager, + accountName, syncKey, password, serverURL); + final Account account = SyncAccounts.createSyncAccount(syncAccount); + result = (account != null); + + final Intent intent = new Intent(); // The intent to return. + intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, syncAccount.username); + intent.putExtra(AccountManager.KEY_ACCOUNT_TYPE, Constants.ACCOUNTTYPE_SYNC); + intent.putExtra(AccountManager.KEY_AUTHTOKEN, Constants.ACCOUNTTYPE_SYNC); setAccountAuthenticatorResult(intent.getExtras()); - setResult(RESULT_OK, intent); + if (result) { + setResult(RESULT_OK, intent); + } else { + setResult(RESULT_CANCELED, intent); + } } - jClient = null; // Sync is set up. Kill reference to JPakeClient object. + jClient = null; // Sync should be set up. Kill reference to JPakeClient object. + final boolean res = result; runOnUiThread(new Runnable() { @Override public void run() { - displayAccount(true); + displayResult(res); } }); } @@ -396,16 +409,20 @@ public class SetupSyncActivity extends AccountAuthenticatorActivity { } /** - * Displays Sync account setup completed feedback to user. + * Displays Sync account setup result to user. * * @param isSetup - * boolean for whether success screen is reached during setup - * completion, or otherwise. + * true is account was set up successfully, false otherwise. */ - private void displayAccount(boolean isSetup) { - Intent intent = new Intent(mContext, SetupSuccessActivity.class); + private void displayResult(boolean isSuccess) { + Intent intent = null; + if (isSuccess) { + intent = new Intent(mContext, SetupSuccessActivity.class); + } else { + intent = new Intent(mContext, SetupFailureActivity.class); + } intent.setFlags(Constants.FLAG_ACTIVITY_REORDER_TO_FRONT_NO_ANIMATION); - intent.putExtra(Constants.INTENT_EXTRA_IS_SETUP, isSetup); + intent.putExtra(Constants.INTENT_EXTRA_IS_SETUP, !pairWithPin); startActivity(intent); finish(); } diff --git a/mobile/android/base/sync/stage/GlobalSyncStage.java b/mobile/android/base/sync/stage/GlobalSyncStage.java index a4fc82ed43a..18aa4198330 100644 --- a/mobile/android/base/sync/stage/GlobalSyncStage.java +++ b/mobile/android/base/sync/stage/GlobalSyncStage.java @@ -25,7 +25,7 @@ public interface GlobalSyncStage { updateEnabledEngines, */ syncTabs, - // syncPasswords, + syncPasswords, syncBookmarks, syncHistory, syncFormHistory, diff --git a/mobile/android/base/sync/syncadapter/SyncAdapter.java b/mobile/android/base/sync/syncadapter/SyncAdapter.java index 56cf87b94d2..094eb74bfdb 100644 --- a/mobile/android/base/sync/syncadapter/SyncAdapter.java +++ b/mobile/android/base/sync/syncadapter/SyncAdapter.java @@ -421,21 +421,29 @@ public class SyncAdapter extends AbstractThreadedSyncAdapter implements GlobalSe if (accountGUID == null) { Logger.info(LOG_TAG, "Account GUID was null. Creating a new one."); accountGUID = Utils.generateGuid(); - mAccountManager.setUserData(localAccount, Constants.ACCOUNT_GUID, accountGUID); + setAccountGUID(mAccountManager, localAccount, accountGUID); } 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); if (clientName == null) { clientName = GlobalConstants.PRODUCT_NAME + " on " + android.os.Build.MODEL; - mAccountManager.setUserData(localAccount, Constants.CLIENT_NAME, clientName); + setClientName(mAccountManager, localAccount, clientName); } 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,