am 84185148: Add account listing and preference integration for current account

* commit '84185148c5506cd58ae6870102de6538bbc35042':
  Add account listing and preference integration for current account
This commit is contained in:
Sandeep Siddhartha 2014-09-18 18:05:04 +00:00 committed by Android Git Automerger
commit 8c8551e10b
6 changed files with 356 additions and 8 deletions

View File

@ -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];
}
}

View File

@ -158,5 +158,9 @@
<action android:name="android.intent.action.MAIN"/>
</intent-filter>
</activity>
<!-- Unexported activity used for tests. -->
<activity android:name=".settings.TestFragmentActivity"
android:exported="false" />
</application>
</manifest>

View File

@ -181,14 +181,20 @@
<!-- Title of the preference item for switching accounts [CHAR LIMIT=30] -->
<string name="switch_accounts">Switch accounts</string>
<!-- Summary of the preference item for switching accounts when no accounts
are selected [CHAR LIMIT=65] -->
<string name="no_accounts_selected">No accounts selected</string>
<!-- Summary of the preference item for switching accounts when an account
is selected [CHAR LIMIT=65] -->
<string name="account_selected">Currently using <xliff:g id="EMAIL_ADDRESS" example="someone@example.com">%1$s</xliff:g></string>
<!-- Positive text for selecting an account -->
<string name="account_select_ok">OK</string>
<!-- Negative text for selecting an account -->
<string name="account_select_cancel">Cancel</string>
<!-- Text for signing out of an account -->
<string name="account_select_sign_out">Sign out</string>
<!-- Title of the account picker dialog for selecting an account [CHAR LIMIT=40] -->
<string name="account_select_title">Select an account to use</string>
<!-- Description for English (UK) keyboard subtype [CHAR LIMIT=25]
(UK) should be an abbreviation of United Kingdom to fit in the CHAR LIMIT. -->

View File

@ -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
* <li> Account selection/management for IME
* <li> TODO: Sync preferences
* <li> 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();
}
}
}

View File

@ -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. <br/>
* Usage: <br/>
* 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();
}
}

View File

@ -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<TestFragmentActivity> {
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());
}
}