Refresh pref settings for cloud sync

- Show the opt-in text
- Show the account picker if user presses 'enable sync'. Previously
  we disabled the sync pref which was confusing.
- Remove the debug tags from some prefs; and improve messaging overall

General rules;
- sync is turned ON : user checks 'enable sync' pref AND
  accepts the opt-in AND chooses an account
- sync is turned OFF: when user signs out

Demo link
https://drive.google.com/a/google.com/file/d/0B9tNQOWdRuiWSUdVVE5rVDJudlk/view?usp=sharing

Change-Id: I2e7933796b15e47005ba9970a8c1294416ef31a0
main
Jatin Matani 2015-02-24 09:59:02 -08:00
parent 1d5ec6136c
commit 8ec328fb2e
4 changed files with 169 additions and 109 deletions

View File

@ -39,11 +39,9 @@
<!-- Settings screen title for preferences [CHAR LIMIT=33]-->
<string name="settings_screen_preferences">Preferences</string>
<!-- Settings screen title for accounts and privacy preferences [CHAR LIMIT=33]-->
<string name="settings_screen_accounts">Accounts &amp; privacy</string>
<string name="settings_screen_accounts">Accounts &amp; Privacy</string>
<!-- Settings screen title for appearance & layouts preferences [CHAR LIMIT=33] -->
<string name="settings_screen_appearance">Appearance &amp; layouts</string>
<!-- Settings screen title for multilingual options [CHAR_LIMIT=33] -->
<string name="settings_screen_multilingual">Multilingual options</string>
<string name="settings_screen_appearance">Appearance &amp; Layouts</string>
<!-- Settings screen title for gesture typing preferences [CHAR_LIMIT=33] -->
<string name="settings_screen_gesture">Gesture Typing</string>
<!-- Settings screen title for text correction options [CHAR_LIMIT=33] -->
@ -56,17 +54,31 @@
<!-- Option for enabling or disabling the split keyboard layout. [CHAR LIMIT=65]-->
<string name="enable_split_keyboard">Enable split keyboard</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>
<string name="clear_sync_data_title" translatable="false">[DEBUG] Delete Google Keyboard cloud data</string>
<string name="clear_sync_data_summary" translable="false">Deletes your synced data from Google</string>
<string name="clear_sync_data_confirmation" translable="false">Your synced data will be deleted. Are you sure?</string>
<string name="clear_sync_data_ok" translable="false">Delete</string>
<string name="clear_sync_data_cancel" translable="false">Cancel</string>
<!-- Option title for enabling cloud sync feature [CHAR LIMIT=33]-->
<string name="cloud_sync_title">Google Keyboard Sync</string>
<!-- Option summary when cloud sync feature is enabled [CHAR LIMIT=65] -->
<string name="cloud_sync_summary">Sync is turned on</string>
<!-- Option summary when cloud sync feature is disabled [CHAR LIMIT=65] -->
<string name="cloud_sync_summary_disabled">Sync your personal dictionary across devices</string>
<!-- Option title for starting the sync cycle now. [CHAR LIMIT=33]-->
<string name="sync_now_title">Sync Now</string>
<!-- Option title for letting user delete data from Google servers. [CHAR LIMIT=33] -->
<string name="clear_sync_data_title">Delete Keyboard Cloud data</string>
<!-- Option summary for letting user delete data from Google servers. [CHAR LIMIT=65] -->
<string name="clear_sync_data_summary">Deletes your synced data from Google</string>
<!-- Text for confirmation dialog box asking user to confirm deletion of cloud data. [CHAR LIMIT=65] -->
<string name="clear_sync_data_confirmation">Your synced data will be deleted from the cloud. Are you sure?</string>
<!-- Option to confirm deleting of user data from cloud [CHAR LIMIT=20] -->
<string name="clear_sync_data_ok">Delete</string>
<!-- Option to cancel the deletion of user data from cloud [CHAR LIMIT=20] -->
<string name="cloud_sync_cancel">Cancel</string>
<!-- Option to agree to terms and conditions for enabling cloud sync feature. -->
<string name="cloud_sync_opt_in_text">Your personal dictionary will be synced &amp; backed up to
Google servers. The statistical information of word frequency may be collected to help
improve our products. The collection and usage of all the information will be compliant with
<a href="https://www.google.com/policies/privacy">Google\'s Privacy Policy</a>.
</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>
<!-- Option summary for including other IMEs in the language switch list [CHAR LIMIT=65] -->

View File

@ -36,21 +36,14 @@
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"
android:defaultValue="true"
android:persistent="true" />
<!-- This preference (acts like a button) enables the user to initiate an one time sync. -->
<Preference android:key="pref_beanstalk"
<Preference android:key="pref_sync_now"
android:persistent="false"
android:title="@string/sync_now_title"
android:dependency="pref_enable_cloud_sync" />
<!-- This preference (acts like a button) enables the user to clear data from the cloud. -->
<Preference android:key="pref_beanstalk_clear_data"
<Preference android:key="pref_clear_sync_data"
android:persistent="false"
android:title="@string/clear_sync_data_title"
android:summary="@string/clear_sync_data_summary"

View File

@ -23,8 +23,8 @@ import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.Preference;
import android.preference.Preference.OnPreferenceClickListener;
import android.text.TextUtils;
@ -48,36 +48,50 @@ import javax.annotation.Nullable;
* <li> Privacy preferences </li>
*/
public final class AccountsSettingsFragment extends SubScreenFragment {
private static final String PREF_SYNC_NOW = "pref_beanstalk";
private static final String PREF_CLEAR_SYNC_DATA = "pref_beanstalk_clear_data";
private static final String PREF_ENABLE_SYNC_NOW = "pref_enable_cloud_sync";
private static final String PREF_SYNC_NOW = "pref_sync_now";
private static final String PREF_CLEAR_SYNC_DATA = "pref_clear_sync_data";
static final String PREF_ACCCOUNT_SWITCHER = "account_switcher";
private final DialogInterface.OnClickListener mAccountChangedListener =
new AccountChangedListener();
private final Preference.OnPreferenceClickListener mSyncNowListener = new SyncNowListener();
private final Preference.OnPreferenceClickListener mClearSyncDataListener =
new ClearSyncDataListener();
/**
* Onclick listener for sync now pref.
*/
private final Preference.OnPreferenceClickListener mSyncNowListener =
new SyncNowListener();
/**
* Onclick listener for delete sync pref.
*/
private final Preference.OnPreferenceClickListener mDeleteSyncDataListener =
new DeleteSyncDataListener();
/**
* Onclick listener for enable sync pref.
*/
private final Preference.OnPreferenceClickListener mEnableSyncClickListener =
new EnableSyncClickListener();
/**
* Enable sync checkbox pref.
*/
private CheckBoxPreference mEnableSyncPreference;
/**
* Enable sync checkbox pref.
*/
private Preference mSyncNowPreference;
/**
* Clear sync data pref.
*/
private Preference mClearSyncDataPreference;
@Override
public void onCreate(final Bundle icicle) {
super.onCreate(icicle);
addPreferencesFromResource(R.xml.prefs_screen_accounts);
final Resources res = getResources();
if (ProductionFlags.IS_METRICS_LOGGING_SUPPORTED) {
final Preference enableMetricsLogging =
findPreference(Settings.PREF_ENABLE_METRICS_LOGGING);
if (enableMetricsLogging != null) {
final String enableMetricsLoggingTitle = res.getString(
R.string.enable_metrics_logging, getApplicationName());
enableMetricsLogging.setTitle(enableMetricsLoggingTitle);
}
} else {
removePreference(Settings.PREF_ENABLE_METRICS_LOGGING);
}
if (!ProductionFlags.ENABLE_ACCOUNT_SIGN_IN) {
removePreference(PREF_ACCCOUNT_SWITCHER);
removePreference(PREF_ENABLE_CLOUD_SYNC);
@ -89,11 +103,14 @@ public final class AccountsSettingsFragment extends SubScreenFragment {
removePreference(PREF_SYNC_NOW);
removePreference(PREF_CLEAR_SYNC_DATA);
} else {
final Preference syncNowPreference = findPreference(PREF_SYNC_NOW);
syncNowPreference.setOnPreferenceClickListener(mSyncNowListener);
mEnableSyncPreference = (CheckBoxPreference) findPreference(PREF_ENABLE_SYNC_NOW);
mEnableSyncPreference.setOnPreferenceClickListener(mEnableSyncClickListener);
final Preference clearSyncDataPreference = findPreference(PREF_CLEAR_SYNC_DATA);
clearSyncDataPreference.setOnPreferenceClickListener(mClearSyncDataListener);
mSyncNowPreference = findPreference(PREF_SYNC_NOW);
mSyncNowPreference.setOnPreferenceClickListener(mSyncNowListener);
mClearSyncDataPreference = findPreference(PREF_CLEAR_SYNC_DATA);
mClearSyncDataPreference.setOnPreferenceClickListener(mDeleteSyncDataListener);
}
}
@ -106,15 +123,25 @@ public final class AccountsSettingsFragment extends SubScreenFragment {
@Override
public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key) {
if (TextUtils.equals(key, PREF_ACCOUNT_NAME)) {
refreshAccountAndDependentPreferences(
prefs.getString(PREF_ACCOUNT_NAME, null));
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);
AccountStateChangedListener.onSyncPreferenceChanged(
getSignedInAccountName(), syncEnabled);
mEnableSyncPreference = (CheckBoxPreference)findPreference(PREF_ENABLE_SYNC_NOW);
mEnableSyncPreference.setChecked(syncEnabled);
if (syncEnabled) {
mEnableSyncPreference.setSummary(R.string.cloud_sync_summary);
} else {
mEnableSyncPreference.setSummary(R.string.cloud_sync_summary_disabled);
}
AccountStateChangedListener.onSyncPreferenceChanged(getSignedInAccountName(),
syncEnabled);
}
}
/**
* Summarizes what account is being used and turns off dependent preferences if no account
* is currently selected.
*/
private void refreshAccountAndDependentPreferences(@Nullable final String currentAccount) {
if (!ProductionFlags.ENABLE_ACCOUNT_SIGN_IN) {
return;
@ -122,61 +149,33 @@ public final class AccountsSettingsFragment extends SubScreenFragment {
final Preference accountSwitcher = findPreference(PREF_ACCCOUNT_SWITCHER);
if (currentAccount == null) {
// No account is currently selected.
// No account is currently selected; switch enable sync preference off.
accountSwitcher.setSummary(getString(R.string.no_accounts_selected));
// Disable the sync preference UI.
disableSyncPreference();
mEnableSyncPreference.setChecked(false);
} else {
// Set the currently selected account.
// Set the currently selected account as the summary text.
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);
// Set up on click listener for the account picker preference.
accountSwitcher.setOnPreferenceClickListener(new OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(final Preference preference) {
if (accountsForLogin.length == 0) {
// TODO: Handle account addition.
Toast.makeText(getActivity(), getString(R.string.account_select_cancel),
Toast.LENGTH_SHORT).show();
} else {
createAccountPicker(accountsForLogin, currentAccount).show();
@Override
public boolean onPreferenceClick(final Preference preference) {
final String[] accountsForLogin =
LoginAccountUtils.getAccountsForLogin(getActivity());
if (accountsForLogin.length == 0) {
// TODO: Handle account addition.
Toast.makeText(getActivity(), getString(R.string.account_select_cancel),
Toast.LENGTH_SHORT).show();
} else {
createAccountPicker(accountsForLogin, currentAccount,
new AccountChangedListener(null)).show();
}
return true;
}
return true;
}
});
}
/**
* Enables the Sync preference UI and updates its summary.
*/
private void enableSyncPreference() {
if (!ProductionFlags.ENABLE_USER_HISTORY_DICTIONARY_SYNC) {
return;
}
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_USER_HISTORY_DICTIONARY_SYNC) {
return;
}
final Preference syncPreference = findPreference(PREF_ENABLE_CLOUD_SYNC);
syncPreference.setEnabled(false);
syncPreference.setSummary(R.string.cloud_sync_summary_disabled_signed_out);
}
@Nullable
String getSignedInAccountName() {
return getSharedPreferences().getString(LocalSettingsConstants.PREF_ACCOUNT_NAME, null);
@ -188,14 +187,19 @@ public final class AccountsSettingsFragment extends SubScreenFragment {
/**
* Creates an account picker dialog showing the given accounts in a list and selecting
* the selected account by default.
* The list of accounts must not be null/empty.
* the selected account by default. The list of accounts must not be null/empty.
*
* Package-private for testing.
*
* @param accounts list of accounts on the device.
* @param selectedAccount currently selected account
* @param positiveButtonClickListener listener that gets called when positive button is
* clicked
*/
@UsedForTesting
AlertDialog createAccountPicker(final String[] accounts,
final String selectedAccount) {
final String selectedAccount,
final DialogInterface.OnClickListener positiveButtonClickListener) {
if (accounts == null || accounts.length == 0) {
throw new IllegalArgumentException("List of accounts must not be empty");
}
@ -216,10 +220,10 @@ public final class AccountsSettingsFragment extends SubScreenFragment {
final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity())
.setTitle(R.string.account_select_title)
.setSingleChoiceItems(accounts, index, null)
.setPositiveButton(R.string.account_select_ok, mAccountChangedListener)
.setPositiveButton(R.string.account_select_ok, positiveButtonClickListener)
.setNegativeButton(R.string.account_select_cancel, null);
if (isSignedIn) {
builder.setNeutralButton(R.string.account_select_sign_out, mAccountChangedListener);
builder.setNeutralButton(R.string.account_select_sign_out, positiveButtonClickListener);
}
return builder.create();
}
@ -229,6 +233,15 @@ public final class AccountsSettingsFragment extends SubScreenFragment {
* Persists/removes the account to/from shared preferences and sets up sync if required.
*/
class AccountChangedListener implements DialogInterface.OnClickListener {
/**
* Represents preference that should be changed based on account chosen.
*/
private CheckBoxPreference mDependentPreference;
AccountChangedListener(final CheckBoxPreference dependentPreference) {
mDependentPreference = dependentPreference;
}
@Override
public void onClick(final DialogInterface dialog, final int which) {
final String oldAccount = getSignedInAccountName();
@ -242,6 +255,9 @@ public final class AccountsSettingsFragment extends SubScreenFragment {
.putString(PREF_ACCOUNT_NAME, newAccount)
.apply();
AccountStateChangedListener.onAccountSignedIn(oldAccount, newAccount);
if (mDependentPreference != null) {
mDependentPreference.setChecked(true);
}
break;
case DialogInterface.BUTTON_NEUTRAL: // Signed out
AccountStateChangedListener.onAccountSignedOut(oldAccount);
@ -268,7 +284,7 @@ public final class AccountsSettingsFragment extends SubScreenFragment {
/**
* Listener that initiates the process of deleting user's data from the cloud.
*/
class ClearSyncDataListener implements Preference.OnPreferenceClickListener {
class DeleteSyncDataListener implements Preference.OnPreferenceClickListener {
@Override
public boolean onPreferenceClick(final Preference preference) {
final AlertDialog confirmationDialog = new AlertDialog.Builder(getActivity())
@ -290,4 +306,43 @@ public final class AccountsSettingsFragment extends SubScreenFragment {
return true;
}
}
/**
* Listens to events when user clicks on "Enable sync" feature.
*/
class EnableSyncClickListener implements Preference.OnPreferenceClickListener {
@Override
public boolean onPreferenceClick(final Preference preference) {
final CheckBoxPreference syncPreference = (CheckBoxPreference) preference;
if (syncPreference.isChecked()) {
// Uncheck for now.
syncPreference.setChecked(false);
// Show opt-in.
final AlertDialog optInDialog = new AlertDialog.Builder(getActivity())
.setTitle(R.string.cloud_sync_title)
.setMessage(R.string.cloud_sync_opt_in_text)
.setPositiveButton(R.string.account_select_ok,
new DialogInterface.OnClickListener() {
@Override
public void onClick(final DialogInterface dialog,
final int which) {
if (which == DialogInterface.BUTTON_POSITIVE) {
final Context context = getActivity();
final String[] accountsForLogin =
LoginAccountUtils.getAccountsForLogin(context);
createAccountPicker(accountsForLogin,
getSignedInAccountName(),
new AccountChangedListener(syncPreference))
.show();
}
}
})
.setNegativeButton(R.string.cloud_sync_cancel, null)
.create();
optInDialog.show();
}
return true;
}
}
}

View File

@ -49,7 +49,7 @@ public class AccountsSettingsFragmentTests
final AccountsSettingsFragment fragment =
(AccountsSettingsFragment) getActivity().mFragment;
try {
fragment.createAccountPicker(new String[0], null);
fragment.createAccountPicker(new String[0], null, null /* listener */);
fail("Expected IllegalArgumentException, never thrown");
} catch (IllegalArgumentException expected) {
// Expected.
@ -76,7 +76,7 @@ public class AccountsSettingsFragmentTests
"2@example.com",
"3@example.com",
"4@example.com"},
null);
null, null /* positiveButtonListner */);
dialog.show();
dialogHolder.mDialog = dialog;
latch.countDown();
@ -118,7 +118,7 @@ public class AccountsSettingsFragmentTests
"2@example.com",
"3@example.com",
"4@example.com"},
"3@example.com");
"3@example.com", null /* positiveButtonListner */);
dialog.show();
dialogHolder.mDialog = dialog;
latch.countDown();