Set up a sync preference and policy for syncing [2]

- Adds a preference for enabling sync, which controls the sync behavior
- Make the ProductionFlags depend on appropriate flags to guarantee that
  we don't mess things when flipping some flags
- Preferences now control the "syncable" property of the provider
  thereby controlling the policy and when this entry shows up in
  system settings.

Bug: 17464069
Change-Id: I1d58351188518c1ae9f1f9e147b5ea15d32a3427
diff --git a/java/res/values/strings.xml b/java/res/values/strings.xml
index 6aea637..ed05e91 100644
--- a/java/res/values/strings.xml
+++ b/java/res/values/strings.xml
@@ -56,9 +56,11 @@
     <!--  Option for enabling or disabling the split keyboard layout. [CHAR LIMIT=65]-->
     <string name="enable_split_keyboard">Enable split keyboard</string>
 
-    <string name="sync_now_title" translatable="false">Sync Now</string>
-    <string name="sync_now_summary" translatable="false">Sync your personal dictionary</string>
-    <string name="sync_now_summary_disabled_signed_out" translatable="false">Select an account to enable sync</string>
+    <!-- TODO: Enable translation for user-visible strings -->
+    <string name="cloud_sync_title" translatable="false">Enable sync</string>
+    <string name="cloud_sync_summary" translatable="false">Sync your personal dictionary across devices</string>
+    <string name="cloud_sync_summary_disabled_signed_out" translatable="false">Select an account to enable sync</string>
+    <string name="sync_now_title" translatable="false">[DEBUG] Sync Now</string>
 
     <!-- Option name for including other IMEs in the language switch list [CHAR LIMIT=30] -->
     <string name="include_other_imes_in_language_switch_list">Switch to other input methods</string>
diff --git a/java/res/xml/prefs_screen_accounts.xml b/java/res/xml/prefs_screen_accounts.xml
index 003a37f..41642bf 100644
--- a/java/res/xml/prefs_screen_accounts.xml
+++ b/java/res/xml/prefs_screen_accounts.xml
@@ -28,7 +28,15 @@
         android:title="@string/switch_accounts"
         android:summary="@string/no_accounts_selected" />
 
-    <!-- title will be set programmatically to embed application name -->
+    <!-- Summary will be set programmatically to reflect the account status -->
+    <CheckBoxPreference
+        android:key="pref_enable_cloud_sync"
+        android:title="@string/cloud_sync_title"
+        android:defaultValue="false"
+        android:persistent="true"
+        android:disableDependentsState="false" />
+
+    <!-- Title will be set programmatically to embed application name -->
     <CheckBoxPreference
         android:key="pref_enable_metrics_logging"
         android:summary="@string/enable_metrics_logging_summary"
@@ -38,5 +46,6 @@
     <!-- This preference (acts like a button) enables the user to initiate an one time sync. -->
     <Preference android:key="pref_beanstalk"
         android:persistent="false"
-        android:title="@string/sync_now_title" />
+        android:title="@string/sync_now_title"
+        android:dependency="pref_enable_cloud_sync" />
 </PreferenceScreen>
diff --git a/java/src/com/android/inputmethod/latin/LatinIME.java b/java/src/com/android/inputmethod/latin/LatinIME.java
index 743b570..3b995f9 100644
--- a/java/src/com/android/inputmethod/latin/LatinIME.java
+++ b/java/src/com/android/inputmethod/latin/LatinIME.java
@@ -87,7 +87,6 @@
 import com.android.inputmethod.latin.settings.SettingsValues;
 import com.android.inputmethod.latin.suggestions.SuggestionStripView;
 import com.android.inputmethod.latin.suggestions.SuggestionStripViewAccessor;
-import com.android.inputmethod.latin.sync.BeanstalkManager;
 import com.android.inputmethod.latin.touchinputconsumer.GestureConsumer;
 import com.android.inputmethod.latin.utils.ApplicationUtils;
 import com.android.inputmethod.latin.utils.CapsModeUtils;
@@ -561,7 +560,6 @@
         AudioAndHapticFeedbackManager.init(this);
         AccessibilityUtils.init(this);
         mStatsUtilsManager.onCreate(this /* context */);
-        BeanstalkManager.getInstance(this /* context */).onCreate();
         super.onCreate();
 
         mHandler.onCreate();
@@ -709,7 +707,6 @@
         unregisterReceiver(mDictionaryPackInstallReceiver);
         unregisterReceiver(mDictionaryDumpBroadcastReceiver);
         mStatsUtilsManager.onDestroy();
-        BeanstalkManager.getInstance(this /* context */).onDestroy();
         super.onDestroy();
     }
 
diff --git a/java/src/com/android/inputmethod/latin/settings/AccountsSettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/AccountsSettingsFragment.java
index fa71645..c382426 100644
--- a/java/src/com/android/inputmethod/latin/settings/AccountsSettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/settings/AccountsSettingsFragment.java
@@ -16,7 +16,12 @@
 
 package com.android.inputmethod.latin.settings;
 
+import static com.android.inputmethod.latin.settings.LocalSettingsConstants.PREF_ACCOUNT_NAME;
+import static com.android.inputmethod.latin.settings.LocalSettingsConstants.PREF_ENABLE_CLOUD_SYNC;
+
+import android.accounts.Account;
 import android.app.AlertDialog;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.SharedPreferences;
@@ -33,7 +38,6 @@
 import com.android.inputmethod.latin.SubtypeSwitcher;
 import com.android.inputmethod.latin.accounts.LoginAccountUtils;
 import com.android.inputmethod.latin.define.ProductionFlags;
-import com.android.inputmethod.latin.sync.BeanstalkManager;
 
 import javax.annotation.Nullable;
 
@@ -41,19 +45,18 @@
  * "Accounts & Privacy" settings sub screen.
  *
  * This settings sub screen handles the following preferences:
- * <li> Account selection/management for IME
- * <li> TODO: Sync preferences
- * <li> TODO: Privacy preferences
- * <li> Sync now
+ * <li> Account selection/management for IME </li>
+ * <li> Sync preferences </li>
+ * <li> Privacy preferences </li>
  */
 public final class AccountsSettingsFragment extends SubScreenFragment {
-    static final String PREF_ACCCOUNT_SWITCHER = "account_switcher";
-    static final String PREF_SYNC_NOW = "pref_beanstalk";
+    private static final String PREF_SYNC_NOW = "pref_beanstalk";
 
-    private final DialogInterface.OnClickListener mAccountSelectedListener =
-            new AccountSelectedListener();
-    private final DialogInterface.OnClickListener mAccountSignedOutListener =
-            new AccountSignedOutListener();
+    @UsedForTesting static final String AUTHORITY = "com.android.inputmethod.latin.provider";
+    static final String PREF_ACCCOUNT_SWITCHER = "account_switcher";
+
+    private final DialogInterface.OnClickListener mAccountChangedListener =
+            new AccountChangedListener();
     private final Preference.OnPreferenceClickListener mSyncNowListener = new SyncNowListener();
 
     @Override
@@ -81,47 +84,55 @@
             removePreference(Settings.PREF_ENABLE_METRICS_LOGGING);
         }
 
+        if (!ProductionFlags.ENABLE_ACCOUNT_SIGN_IN) {
+            removePreference(PREF_ACCCOUNT_SWITCHER);
+            removePreference(PREF_ENABLE_CLOUD_SYNC);
+            removePreference(PREF_SYNC_NOW);
+        }
         if (!ProductionFlags.ENABLE_PERSONAL_DICTIONARY_SYNC) {
+            removePreference(PREF_ENABLE_CLOUD_SYNC);
             removePreference(PREF_SYNC_NOW);
         } else {
             final Preference syncNowPreference = findPreference(PREF_SYNC_NOW);
-            if (syncNowPreference != null) {
-                syncNowPreference.setOnPreferenceClickListener(mSyncNowListener);
-            }
+            syncNowPreference.setOnPreferenceClickListener(mSyncNowListener);
         }
     }
 
     @Override
     public void onResume() {
         super.onResume();
-        refreshUi();
+        refreshAccountAndDependentPreferences(getCurrentlySelectedAccount());
     }
 
     @Override
     public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) {
-        // TODO: Look at the preference that changed before refreshing the view.
-        refreshUi();
+        if (TextUtils.equals(key, PREF_ACCOUNT_NAME)) {
+            refreshAccountAndDependentPreferences(
+                    prefs.getString(PREF_ACCOUNT_NAME, null));
+        } else if (TextUtils.equals(key, PREF_ENABLE_CLOUD_SYNC)) {
+            final boolean syncEnabled = prefs.getBoolean(PREF_ENABLE_CLOUD_SYNC, false);
+            updateSyncPolicy(syncEnabled, LoginAccountUtils.getCurrentAccount(getActivity()));
+        }
     }
 
-    private void refreshUi() {
-        refreshAccountSelection();
-        refreshSyncNow();
-    }
-
-    private void refreshAccountSelection() {
+    private void refreshAccountAndDependentPreferences(@Nullable final String currentAccount) {
         if (!ProductionFlags.ENABLE_ACCOUNT_SIGN_IN) {
             return;
         }
 
-        final String currentAccount = getCurrentlySelectedAccount();
         final Preference accountSwitcher = findPreference(PREF_ACCCOUNT_SWITCHER);
         if (currentAccount == null) {
             // No account is currently selected.
             accountSwitcher.setSummary(getString(R.string.no_accounts_selected));
+            // Disable the sync preference UI.
+            disableSyncPreference();
         } else {
             // Set the currently selected account.
             accountSwitcher.setSummary(getString(R.string.account_selected, currentAccount));
+            // Enable the sync preference UI.
+            enableSyncPreference();
         }
+        // Set up onClick listener for the account picker preference.
         final Context context = getActivity();
         final String[] accountsForLogin = LoginAccountUtils.getAccountsForLogin(context);
         accountSwitcher.setOnPreferenceClickListener(new OnPreferenceClickListener() {
@@ -129,39 +140,64 @@
             public boolean onPreferenceClick(Preference preference) {
                 if (accountsForLogin.length == 0) {
                     // TODO: Handle account addition.
-                    Toast.makeText(getActivity(),
-                            getString(R.string.account_select_cancel), Toast.LENGTH_SHORT).show();
+                    Toast.makeText(getActivity(), getString(R.string.account_select_cancel),
+                            Toast.LENGTH_SHORT).show();
                 } else {
                     createAccountPicker(accountsForLogin, currentAccount).show();
                 }
                 return true;
             }
         });
-
-        // TODO: Depending on the account selection, enable/disable preferences that
-        // depend on an account.
     }
 
     /**
-     * Refreshes the "Sync Now" feature
+     * Enables the Sync preference UI and updates its summary.
      */
-    private void refreshSyncNow() {
+    private void enableSyncPreference() {
         if (!ProductionFlags.ENABLE_PERSONAL_DICTIONARY_SYNC) {
             return;
         }
 
-        final Preference syncNowPreference = findPreference(PREF_SYNC_NOW);
-        if (syncNowPreference == null) {
+        final Preference syncPreference = findPreference(PREF_ENABLE_CLOUD_SYNC);
+        syncPreference.setEnabled(true);
+        syncPreference.setSummary(R.string.cloud_sync_summary);
+    }
+
+    /**
+     * Disables the Sync preference UI and updates its summary to indicate
+     * the fact that an account needs to be selected for sync.
+     */
+    private void disableSyncPreference() {
+        if (!ProductionFlags.ENABLE_PERSONAL_DICTIONARY_SYNC) {
             return;
         }
 
-        final String currentAccount = getCurrentlySelectedAccount();
-        if (currentAccount == null) {
-            syncNowPreference.setEnabled(false);
-            syncNowPreference.setSummary(R.string.sync_now_summary_disabled_signed_out);
+        final Preference syncPreference = findPreference(PREF_ENABLE_CLOUD_SYNC);
+        syncPreference.setEnabled(false);
+        syncPreference.setSummary(R.string.cloud_sync_summary_disabled_signed_out);
+    }
+
+    /**
+     * Given a non-null accountToUse, this method looks at the enabled value to either
+     * set or unset the syncable property of the sync authority.
+     * If the account is null, this method is a no-op currently, but we may want
+     * to perform some cleanup in the future.
+     */
+    @UsedForTesting
+    void updateSyncPolicy(boolean enabled, Account accountToUse) {
+        if (!ProductionFlags.ENABLE_PERSONAL_DICTIONARY_SYNC) {
+            return;
+        }
+
+        if (accountToUse != null) {
+            final int syncable = enabled ? 1 : 0;
+            ContentResolver.setIsSyncable(accountToUse, AUTHORITY, syncable);
+            // TODO: Also add a periodic sync here.
+            // See ContentResolver.addPeriodicSync
         } else {
-            syncNowPreference.setEnabled(true);
-            syncNowPreference.setSummary(R.string.sync_now_summary);
+            // Without an account, we cannot really set the sync to off.
+            // Hopefully the account sign-out listener would have taken care of that for us.
+            // But cases such as clear data are still not handled cleanly.
         }
     }
 
@@ -170,6 +206,10 @@
         return getSharedPreferences().getString(LocalSettingsConstants.PREF_ACCOUNT_NAME, null);
     }
 
+    private boolean isSyncEnabled() {
+        return getSharedPreferences().getBoolean(PREF_ENABLE_CLOUD_SYNC, false);
+    }
+
     /**
      * Creates an account picker dialog showing the given accounts in a list and selecting
      * the selected account by default.
@@ -200,51 +240,55 @@
         final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
                 .setTitle(R.string.account_select_title)
                 .setSingleChoiceItems(accounts, index, null)
-                .setPositiveButton(R.string.account_select_ok, mAccountSelectedListener)
+                .setPositiveButton(R.string.account_select_ok, mAccountChangedListener)
                 .setNegativeButton(R.string.account_select_cancel, null);
         if (isSignedIn) {
-            builder.setNeutralButton(R.string.account_select_sign_out, mAccountSignedOutListener);
+            builder.setNeutralButton(R.string.account_select_sign_out, mAccountChangedListener);
         }
         return builder.create();
     }
 
     /**
-     * Listener for an account being selected from the picker.
-     * Persists the account to shared preferences.
+     * Listener for a account selection changes from the picker.
+     * Persists/removes the account to/from shared preferences and sets up sync if required.
      */
-    class AccountSelectedListener implements DialogInterface.OnClickListener {
+    class AccountChangedListener implements DialogInterface.OnClickListener {
         @Override
         public void onClick(DialogInterface dialog, int which) {
-            final ListView lv = ((AlertDialog)dialog).getListView();
-            final Object selectedItem = lv.getItemAtPosition(lv.getCheckedItemPosition());
-            getSharedPreferences()
-                    .edit()
-                    .putString(LocalSettingsConstants.PREF_ACCOUNT_NAME, (String) selectedItem)
-                    .apply();
+            switch (which) {
+                case DialogInterface.BUTTON_POSITIVE: // Signed in
+                    final ListView lv = ((AlertDialog)dialog).getListView();
+                    final Object selectedItem = lv.getItemAtPosition(lv.getCheckedItemPosition());
+                    getSharedPreferences()
+                            .edit()
+                            .putString(PREF_ACCOUNT_NAME, (String) selectedItem)
+                            .apply();
+                    // Attempt starting sync for the new account if sync was
+                    // previously enabled.
+                    // If not, stop it.
+                    updateSyncPolicy(isSyncEnabled(),
+                            LoginAccountUtils.getCurrentAccount(getActivity()));
+                    break;
+                case DialogInterface.BUTTON_NEUTRAL: // Signed out
+                    // Stop sync for the account that's being signed out of.
+                    updateSyncPolicy(false, LoginAccountUtils.getCurrentAccount(getActivity()));
+                    getSharedPreferences()
+                            .edit()
+                            .remove(PREF_ACCOUNT_NAME)
+                            .apply();
+                    break;
+            }
         }
     }
 
     /**
-     * Listener for sign-out being initiated from from the picker.
-     * Removed the account from shared preferences.
-     */
-    class AccountSignedOutListener implements DialogInterface.OnClickListener {
-        @Override
-        public void onClick(DialogInterface dialog, int which) {
-            getSharedPreferences()
-                    .edit()
-                    .remove(LocalSettingsConstants.PREF_ACCOUNT_NAME)
-                    .apply();
-        }
-    }
-
-    /**
-     * Listener that initates the process of sync in the background.
+     * Listener that initiates the process of sync in the background.
      */
     class SyncNowListener implements Preference.OnPreferenceClickListener {
         @Override
         public boolean onPreferenceClick(final Preference preference) {
-            BeanstalkManager.getInstance(getActivity() /* context */).requestSync();
+            ContentResolver.requestSync(
+                    LoginAccountUtils.getCurrentAccount(getActivity()), AUTHORITY, Bundle.EMPTY);
             return true;
         }
     }
diff --git a/java/src/com/android/inputmethod/latin/settings/LocalSettingsConstants.java b/java/src/com/android/inputmethod/latin/settings/LocalSettingsConstants.java
index 71d6065..f4f2e03 100644
--- a/java/src/com/android/inputmethod/latin/settings/LocalSettingsConstants.java
+++ b/java/src/com/android/inputmethod/latin/settings/LocalSettingsConstants.java
@@ -27,6 +27,9 @@
     // Preference key for the current account.
     // Do not restore.
     public static final String PREF_ACCOUNT_NAME = "pref_account_name";
+    // Preference key for enabling cloud sync feature.
+    // Do not restore.
+    public static final String PREF_ENABLE_CLOUD_SYNC = "pref_enable_cloud_sync";
 
     // List of preference keys to skip from being restored by backup agent.
     // These preferences are tied to a device and hence should not be restored.
@@ -36,6 +39,7 @@
     // shared preferences which makes it non-trivial to move these out to
     // a different shared preferences file.
     public static final String[] PREFS_TO_SKIP_RESTORING = new String[] {
-        PREF_ACCOUNT_NAME
+        PREF_ACCOUNT_NAME,
+        PREF_ENABLE_CLOUD_SYNC
     };
 }