diff --git a/java-overridable/src/com/android/inputmethod/latin/utils/LoginAccountUtils.java b/java-overridable/src/com/android/inputmethod/latin/utils/LoginAccountUtils.java
new file mode 100644
index 000000000..faada29b8
--- /dev/null
+++ b/java-overridable/src/com/android/inputmethod/latin/utils/LoginAccountUtils.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.utils;
+
+import android.content.Context;
+
+import javax.annotation.Nonnull;
+
+/**
+ * Utility class for retrieving accounts that may be used for login.
+ */
+public class LoginAccountUtils {
+ private LoginAccountUtils() {
+ // This utility class is not publicly instantiable.
+ }
+
+ /**
+ * Get the accounts available for login.
+ *
+ * @return an array of accounts. Empty (never null) if no accounts are available for login.
+ */
+ @Nonnull
+ public static String[] getAccountsForLogin(final Context context) {
+ return new String[0];
+ }
+}
diff --git a/java/AndroidManifest.xml b/java/AndroidManifest.xml
index 054c415a4..b29a6e286 100644
--- a/java/AndroidManifest.xml
+++ b/java/AndroidManifest.xml
@@ -158,5 +158,9 @@
+
+
+
diff --git a/java/res/values/strings.xml b/java/res/values/strings.xml
index 3486cf581..d64444e01 100644
--- a/java/res/values/strings.xml
+++ b/java/res/values/strings.xml
@@ -181,14 +181,20 @@
Switch accounts
-
No accounts selected
-
Currently using %1$s
+
+ OK
+
+ Cancel
+
+ Sign out
+
+ Select an account to use
diff --git a/java/src/com/android/inputmethod/latin/settings/AccountsSettingsFragment.java b/java/src/com/android/inputmethod/latin/settings/AccountsSettingsFragment.java
index e9e73c735..06ab1e2d2 100644
--- a/java/src/com/android/inputmethod/latin/settings/AccountsSettingsFragment.java
+++ b/java/src/com/android/inputmethod/latin/settings/AccountsSettingsFragment.java
@@ -16,25 +16,41 @@
package com.android.inputmethod.latin.settings;
+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.Preference;
+import android.preference.Preference.OnPreferenceClickListener;
+import android.text.TextUtils;
+import android.widget.ListView;
+import android.widget.Toast;
import com.android.inputmethod.latin.R;
import com.android.inputmethod.latin.SubtypeSwitcher;
import com.android.inputmethod.latin.define.ProductionFlags;
+import com.android.inputmethod.latin.utils.LoginAccountUtils;
+
+import javax.annotation.Nullable;
/**
* "Accounts & Privacy" settings sub screen.
*
* This settings sub screen handles the following preferences:
- * - TODO: Account selection/management for IME
- * - TODO: Sync preferences
- * - TODO: Privacy preferences
+ *
Account selection/management for IME
+ * TODO: Sync preferences
+ * TODO: Privacy preferences
*/
public final class AccountsSettingsFragment extends SubScreenFragment {
+ static final String PREF_ACCCOUNT_SWITCHER = "account_switcher";
+
+ private final DialogInterface.OnClickListener mAccountSelectedListener =
+ new AccountSelectedListener();
+ private final DialogInterface.OnClickListener mAccountSignedOutListener =
+ new AccountSignedOutListener();
+
@Override
public void onCreate(final Bundle icicle) {
super.onCreate(icicle);
@@ -74,9 +90,104 @@ public final class AccountsSettingsFragment extends SubScreenFragment {
}
private void refreshAccountSelection() {
- // TODO: Fetch the currently selected account.
- // Set the summary for the account preference.
- // Depending on the account selection, enable/disable preferences that
+ 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));
+ } else {
+ // Set the currently selected account.
+ accountSwitcher.setSummary(getString(R.string.account_selected, currentAccount));
+ }
+ final Context context = getActivity();
+ final String[] accountsForLogin = LoginAccountUtils.getAccountsForLogin(context);
+ accountSwitcher.setOnPreferenceClickListener(new OnPreferenceClickListener() {
+ @Override
+ 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();
+ } else {
+ createAccountPicker(accountsForLogin, currentAccount).show();
+ }
+ return true;
+ }
+ });
+
+ // TODO: Depending on the account selection, enable/disable preferences that
// depend on an account.
}
+
+ @Nullable
+ private String getCurrentlySelectedAccount() {
+ return getSharedPreferences().getString(Settings.PREF_ACCOUNT_NAME, null);
+ }
+
+ /**
+ * 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.
+ *
+ * Package-private for testing.
+ */
+ AlertDialog createAccountPicker(final String[] accounts,
+ final String selectedAccount) {
+ if (accounts == null || accounts.length == 0) {
+ throw new IllegalArgumentException("List of accounts must not be empty");
+ }
+
+ // See if the currently selected account is in the list.
+ // If it is, the entry is selected, and a sign-out button is provided.
+ // If it isn't, select the 0th account by default which will get picked up
+ // if the user presses OK.
+ int index = 0;
+ boolean isSignedIn = false;
+ for (int i = 0; i < accounts.length; i++) {
+ if (TextUtils.equals(accounts[i], selectedAccount)) {
+ index = i;
+ isSignedIn = true;
+ break;
+ }
+ }
+ 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)
+ .setNegativeButton(R.string.account_select_cancel, null);
+ if (isSignedIn) {
+ builder.setNeutralButton(R.string.account_select_sign_out, mAccountSignedOutListener);
+ }
+ return builder.create();
+ }
+
+ /**
+ * Listener for an account being selected from the picker.
+ * Persists the account to shared preferences.
+ */
+ class AccountSelectedListener 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(Settings.PREF_ACCOUNT_NAME, (String) selectedItem)
+ .apply();
+ }
+ }
+
+ /**
+ * 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(Settings.PREF_ACCOUNT_NAME)
+ .apply();
+ }
+ }
}
diff --git a/java/src/com/android/inputmethod/latin/settings/TestFragmentActivity.java b/java/src/com/android/inputmethod/latin/settings/TestFragmentActivity.java
new file mode 100644
index 000000000..254bc6567
--- /dev/null
+++ b/java/src/com/android/inputmethod/latin/settings/TestFragmentActivity.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.settings;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.app.FragmentManager;
+import android.content.Intent;
+import android.os.Bundle;
+
+/**
+ * Test activity to use when testing preference fragments.
+ * Usage:
+ * Create an ActivityInstrumentationTestCase2 for this activity
+ * and call setIntent() with an intent that specifies the fragment to load in the activity.
+ * The fragment can then be obtained from this activity and used for testing/verification.
+ */
+public final class TestFragmentActivity extends Activity {
+ /**
+ * The fragment name that should be loaded when starting this activity.
+ * This must be specified when starting this activity, as this activity is only
+ * meant to test fragments from instrumentation tests.
+ */
+ public static final String EXTRA_SHOW_FRAGMENT = "show_fragment";
+
+ public Fragment mFragment;
+
+ @Override
+ protected void onCreate(final Bundle savedState) {
+ super.onCreate(savedState);
+ final Intent intent = getIntent();
+ final String fragmentName = intent.getStringExtra(EXTRA_SHOW_FRAGMENT);
+ if (fragmentName == null) {
+ throw new IllegalArgumentException("No fragment name specified for testing");
+ }
+
+ mFragment = Fragment.instantiate(this, fragmentName);
+ FragmentManager fragmentManager = getFragmentManager();
+ fragmentManager.beginTransaction().add(mFragment, fragmentName).commit();
+ }
+}
diff --git a/tests/src/com/android/inputmethod/latin/settings/AccountsSettingsFragmentTests.java b/tests/src/com/android/inputmethod/latin/settings/AccountsSettingsFragmentTests.java
new file mode 100644
index 000000000..2ef8b548f
--- /dev/null
+++ b/tests/src/com/android/inputmethod/latin/settings/AccountsSettingsFragmentTests.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.inputmethod.latin.settings;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Intent;
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.View;
+import android.widget.ListView;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@MediumTest
+public class AccountsSettingsFragmentTests
+ extends ActivityInstrumentationTestCase2 {
+ private static final String FRAG_NAME = AccountsSettingsFragment.class.getName();
+ private static final long TEST_TIMEOUT_MILLIS = 5000;
+
+ private AlertDialog mDialog;
+
+ public AccountsSettingsFragmentTests() {
+ super(TestFragmentActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ Intent intent = new Intent();
+ intent.putExtra(TestFragmentActivity.EXTRA_SHOW_FRAGMENT, FRAG_NAME);
+ setActivityIntent(intent);
+ }
+
+ public void testEmptyAccounts() {
+ final AccountsSettingsFragment fragment =
+ (AccountsSettingsFragment) getActivity().mFragment;
+ try {
+ fragment.createAccountPicker(new String[0], null);
+ fail("Expected IllegalArgumentException, never thrown");
+ } catch (IllegalArgumentException expected) {
+ // Expected.
+ }
+ }
+
+ public void testMultipleAccounts_noCurrentAccount() {
+ final AccountsSettingsFragment fragment =
+ (AccountsSettingsFragment) getActivity().mFragment;
+ final CountDownLatch latch = new CountDownLatch(1);
+ getActivity().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mDialog = fragment.createAccountPicker(
+ new String[] {
+ "1@example.com",
+ "2@example.com",
+ "3@example.com",
+ "4@example.com"},
+ null);
+ mDialog.show();
+ latch.countDown();
+ }
+ });
+
+ try {
+ latch.await(TEST_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException ex) {
+ fail();
+ }
+ getInstrumentation().waitForIdleSync();
+ final ListView lv = mDialog.getListView();
+ // The 1st account should be checked by default.
+ assertEquals("checked-item", 0, lv.getCheckedItemPosition());
+ // There should be 4 accounts in the list.
+ assertEquals("count", 4, lv.getCount());
+ // The sign-out button shouldn't exist
+ assertEquals(View.GONE, mDialog.getButton(Dialog.BUTTON_NEUTRAL).getVisibility());
+ assertEquals(View.VISIBLE, mDialog.getButton(Dialog.BUTTON_NEGATIVE).getVisibility());
+ assertEquals(View.VISIBLE, mDialog.getButton(Dialog.BUTTON_POSITIVE).getVisibility());
+ }
+
+ public void testMultipleAccounts_currentAccount() {
+ final AccountsSettingsFragment fragment =
+ (AccountsSettingsFragment) getActivity().mFragment;
+ final CountDownLatch latch = new CountDownLatch(1);
+ getActivity().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mDialog = fragment.createAccountPicker(
+ new String[] {
+ "1@example.com",
+ "2@example.com",
+ "3@example.com",
+ "4@example.com"},
+ "3@example.com");
+ mDialog.show();
+ latch.countDown();
+ }
+ });
+
+ try {
+ latch.await(TEST_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException ex) {
+ fail();
+ }
+ getInstrumentation().waitForIdleSync();
+ final ListView lv = mDialog.getListView();
+ // The 3rd account should be checked by default.
+ assertEquals("checked-item", 2, lv.getCheckedItemPosition());
+ // There should be 4 accounts in the list.
+ assertEquals("count", 4, lv.getCount());
+ // The sign-out button should be shown
+ assertEquals(View.VISIBLE, mDialog.getButton(Dialog.BUTTON_NEUTRAL).getVisibility());
+ assertEquals(View.VISIBLE, mDialog.getButton(Dialog.BUTTON_NEGATIVE).getVisibility());
+ assertEquals(View.VISIBLE, mDialog.getButton(Dialog.BUTTON_POSITIVE).getVisibility());
+ }
+}