Add header and remove button to account detail page.

- Add account header which contains the account icon and name to the top
  of the account detail page.
- Also add the remove account button to the bottom of the page.

Change-Id: I24f1a17a7f9c4dd8c840a2b0329c0965e4e612e9
Fix: 33543525
Test: make RunSettingsRoboTests
This commit is contained in:
Doris Ling
2016-12-29 14:41:15 -08:00
parent 61fef6051c
commit fd06d2fcf4
10 changed files with 517 additions and 11 deletions

53
res/layout/account_header.xml Executable file
View File

@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2016 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.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
style="@style/EntityHeader"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="?android:attr/listPreferredItemPaddingStart"
android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"
android:gravity="center_vertical"
android:paddingTop="24dip"
android:paddingBottom="24dip"
android:orientation="horizontal">
<LinearLayout
android:id="@+id/icon_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minWidth="60dp"
android:orientation="horizontal"
android:paddingEnd="12dp"
android:paddingTop="12dp"
android:paddingBottom="12dp">
<ImageView
android:id="@android:id/icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxWidth="48dp"
android:maxHeight="48dp"/>
</LinearLayout>
<TextView
android:id="@android:id/title"
style="@style/TextAppearance.EntityHeaderTitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"/>
</LinearLayout>

View File

@@ -18,7 +18,7 @@
<LinearLayout <LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/app_snippet" android:id="@+id/app_snippet"
style="@style/AppHeader" style="@style/EntityHeader"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_horizontal|top" android:layout_gravity="center_horizontal|top"
@@ -37,7 +37,7 @@
<TextView <TextView
android:id="@android:id/title" android:id="@android:id/title"
style="@style/TextAppearance.AppHeaderTitle" style="@style/TextAppearance.EntityHeaderTitle"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:singleLine="true" android:singleLine="true"

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2016 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.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<Button
android:id="@+id/button"
android:text="@string/remove_account_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="20dip"
android:layout_marginBottom="12dip"
android:gravity="center" />
</LinearLayout>

View File

@@ -417,7 +417,7 @@
<item name="android:progressDrawable">@drawable/ring_progress</item> <item name="android:progressDrawable">@drawable/ring_progress</item>
</style> </style>
<style name="AppHeader"> <style name="EntityHeader">
<item name="android:background">@color/card_background_grey</item> <item name="android:background">@color/card_background_grey</item>
<item name="android:gravity">center_horizontal</item> <item name="android:gravity">center_horizontal</item>
<item name="android:paddingTop">16dp</item> <item name="android:paddingTop">16dp</item>
@@ -426,7 +426,7 @@
<item name="android:paddingBottom">8dp</item> <item name="android:paddingBottom">8dp</item>
</style> </style>
<style name="TextAppearance.AppHeaderTitle" <style name="TextAppearance.EntityHeaderTitle"
parent="@android:style/TextAppearance.Material.Subhead"> parent="@android:style/TextAppearance.Material.Subhead">
<item name="android:textColor">?android:attr/textColorPrimary</item> <item name="android:textColor">?android:attr/textColorPrimary</item>
<item name="android:textSize">24sp</item> <item name="android:textSize">24sp</item>

View File

@@ -19,6 +19,12 @@
android:title="@string/account_settings_title" android:title="@string/account_settings_title"
settings:keywords="@string/keywords_accounts"> settings:keywords="@string/keywords_accounts">
<Preference
android:key="account_header"
android:layout="@layout/account_header"
android:selectable="false"
android:order="0"/>
<Preference <Preference
android:key="account_sync" android:key="account_sync"
android:title="@string/account_sync_title" android:title="@string/account_sync_title"
@@ -28,4 +34,9 @@
android:key="dashboard_tile_placeholder" android:key="dashboard_tile_placeholder"
android:order="10"/> android:order="10"/>
<com.android.settings.applications.LayoutPreference
android:key="remove_account"
android:layout="@layout/remove_account_button"
android:order="100" />
</PreferenceScreen> </PreferenceScreen>

View File

@@ -23,11 +23,13 @@ import android.os.UserHandle;
import android.os.UserManager; import android.os.UserManager;
import android.support.annotation.VisibleForTesting; import android.support.annotation.VisibleForTesting;
import android.support.v7.preference.Preference;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settings.R; import com.android.settings.R;
import com.android.settings.Utils; import com.android.settings.Utils;
import com.android.settings.core.PreferenceController; import com.android.settings.core.PreferenceController;
import com.android.settings.dashboard.DashboardFragment; import com.android.settings.dashboard.DashboardFragment;
import com.android.settingslib.accounts.AuthenticatorHelper;
import com.android.settingslib.drawer.CategoryKey; import com.android.settingslib.drawer.CategoryKey;
import com.android.settingslib.drawer.Tile; import com.android.settingslib.drawer.Tile;
@@ -43,11 +45,16 @@ public class AccountDetailDashboardFragment extends DashboardFragment {
public static final String KEY_ACCOUNT_TYPE = "account_type"; public static final String KEY_ACCOUNT_TYPE = "account_type";
public static final String KEY_ACCOUNT_LABEL = "account_label"; public static final String KEY_ACCOUNT_LABEL = "account_label";
public static final String KEY_ACCOUNT_TITLE_RES = "account_title_res"; public static final String KEY_ACCOUNT_TITLE_RES = "account_title_res";
public static final String KEY_ACCOUNT_HEADER = "account_header";
public static final String KEY_USER_HANDLE = "user_handle";
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
Account mAccount;
private String mAccountLabel; private String mAccountLabel;
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
String mAccountType; String mAccountType;
private AccountSyncPreferenceController mAccountSynController; private AccountSyncPreferenceController mAccountSynController;
private RemoveAccountPreferenceController mRemoveAccountController;
@Override @Override
public void onCreate(Bundle icicle) { public void onCreate(Bundle icicle) {
@@ -57,10 +64,9 @@ public class AccountDetailDashboardFragment extends DashboardFragment {
UserHandle userHandle = Utils.getSecureTargetUser(activity.getActivityToken(), UserHandle userHandle = Utils.getSecureTargetUser(activity.getActivityToken(),
(UserManager) getSystemService(Context.USER_SERVICE), args, (UserManager) getSystemService(Context.USER_SERVICE), args,
activity.getIntent().getExtras()); activity.getIntent().getExtras());
Account account = null;
if (args != null) { if (args != null) {
if (args.containsKey(KEY_ACCOUNT)) { if (args.containsKey(KEY_ACCOUNT)) {
account = args.getParcelable(KEY_ACCOUNT); mAccount = args.getParcelable(KEY_ACCOUNT);
} }
if (args.containsKey(KEY_ACCOUNT_LABEL)) { if (args.containsKey(KEY_ACCOUNT_LABEL)) {
mAccountLabel = args.getString(KEY_ACCOUNT_LABEL); mAccountLabel = args.getString(KEY_ACCOUNT_LABEL);
@@ -69,7 +75,8 @@ public class AccountDetailDashboardFragment extends DashboardFragment {
mAccountType = args.getString(KEY_ACCOUNT_TYPE); mAccountType = args.getString(KEY_ACCOUNT_TYPE);
} }
} }
mAccountSynController.init(account, userHandle); mAccountSynController.init(mAccount, userHandle);
mRemoveAccountController.setAccount(mAccount);
} }
@Override @Override
@@ -78,6 +85,7 @@ public class AccountDetailDashboardFragment extends DashboardFragment {
if (mAccountLabel != null) { if (mAccountLabel != null) {
getActivity().setTitle(mAccountLabel); getActivity().setTitle(mAccountLabel);
} }
updateAccountHeader();
} }
@Override @Override
@@ -105,6 +113,8 @@ public class AccountDetailDashboardFragment extends DashboardFragment {
final List<PreferenceController> controllers = new ArrayList<>(); final List<PreferenceController> controllers = new ArrayList<>();
mAccountSynController = new AccountSyncPreferenceController(context); mAccountSynController = new AccountSyncPreferenceController(context);
controllers.add(mAccountSynController); controllers.add(mAccountSynController);
mRemoveAccountController = new RemoveAccountPreferenceController(context, this);
controllers.add(mRemoveAccountController);
return controllers; return controllers;
} }
@@ -120,4 +130,18 @@ public class AccountDetailDashboardFragment extends DashboardFragment {
return mAccountType.equals(metadata.getString(METADATA_IA_ACCOUNT)); return mAccountType.equals(metadata.getString(METADATA_IA_ACCOUNT));
} }
@VisibleForTesting
void updateAccountHeader() {
final Preference headerPreference = findPreference(KEY_ACCOUNT_HEADER);
headerPreference.setTitle(mAccount.name);
final Context context = getContext();
UserHandle userHandle = null;
Bundle args = getArguments();
if (args != null && args.containsKey(KEY_USER_HANDLE)) {
userHandle = args.getParcelable(KEY_USER_HANDLE);
}
final AuthenticatorHelper helper = new AuthenticatorHelper(context, userHandle, null);
headerPreference.setIcon(helper.getDrawableForType(context, mAccountType));
}
} }

View File

@@ -465,6 +465,8 @@ public class AccountPreferenceController extends PreferenceController
final Bundle fragmentArguments = new Bundle(); final Bundle fragmentArguments = new Bundle();
fragmentArguments.putParcelable(AccountDetailDashboardFragment.KEY_ACCOUNT, fragmentArguments.putParcelable(AccountDetailDashboardFragment.KEY_ACCOUNT,
account); account);
fragmentArguments.putParcelable(AccountDetailDashboardFragment.KEY_USER_HANDLE,
userHandle);
fragmentArguments.putString(AccountDetailDashboardFragment.KEY_ACCOUNT_TYPE, fragmentArguments.putString(AccountDetailDashboardFragment.KEY_ACCOUNT_TYPE,
accountType); accountType);
fragmentArguments.putString(AccountDetailDashboardFragment.KEY_ACCOUNT_LABEL, fragmentArguments.putString(AccountDetailDashboardFragment.KEY_ACCOUNT_LABEL,

View File

@@ -0,0 +1,199 @@
/*
* Copyright (C) 2017 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.settings.accounts;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountManagerCallback;
import android.accounts.AccountManagerFuture;
import android.accounts.AuthenticatorException;
import android.accounts.OperationCanceledException;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.Fragment;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle;
import android.os.Process;
import android.support.v7.preference.PreferenceScreen;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import com.android.internal.logging.nano.MetricsProto;
import com.android.settings.R;
import com.android.settings.applications.LayoutPreference;
import com.android.settings.core.PreferenceController;
import com.android.settings.core.instrumentation.InstrumentedDialogFragment;
import java.io.IOException;
public class RemoveAccountPreferenceController extends PreferenceController
implements OnClickListener {
private static final String KEY_REMOVE_ACCOUNT = "remove_account";
private Account mAccount;
private Fragment mParentFragment;
public RemoveAccountPreferenceController(Context context, Fragment parent) {
super(context);
mParentFragment = parent;
}
@Override
public void displayPreference(PreferenceScreen screen) {
super.displayPreference(screen);
final LayoutPreference removeAccountPreference =
(LayoutPreference) screen.findPreference(KEY_REMOVE_ACCOUNT);
Button removeAccountButton = (Button) removeAccountPreference.findViewById(R.id.button);
removeAccountButton.setOnClickListener(this);
}
@Override
public boolean isAvailable() {
return true;
}
@Override
public String getPreferenceKey() {
return KEY_REMOVE_ACCOUNT;
}
@Override
public void onClick(View v) {
ConfirmRemoveAccountDialog.show(mParentFragment, mAccount);
}
public void setAccount(Account account) {
mAccount = account;
}
/**
* Dialog to confirm with user about account removal
*/
public static class ConfirmRemoveAccountDialog extends InstrumentedDialogFragment implements
DialogInterface.OnClickListener {
private static final String SAVE_ACCOUNT = "account";
private static final String REMOVE_ACCOUNT_DIALOG = "confirmRemoveAccount";
private Account mAccount;
public static ConfirmRemoveAccountDialog show(Fragment parent, Account account) {
if (!parent.isAdded()) {
return null;
}
final ConfirmRemoveAccountDialog dialog = new ConfirmRemoveAccountDialog();
dialog.mAccount = account;
dialog.setTargetFragment(parent, 0);
dialog.show(parent.getFragmentManager(), REMOVE_ACCOUNT_DIALOG);
return dialog;
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Context context = getActivity();
if (savedInstanceState != null) {
mAccount = (Account) savedInstanceState.getParcelable(SAVE_ACCOUNT);
}
return new AlertDialog.Builder(context)
.setTitle(R.string.really_remove_account_title)
.setMessage(R.string.really_remove_account_message)
.setNegativeButton(android.R.string.cancel, null)
.setPositiveButton(R.string.remove_account_label, this)
.create();
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelable(SAVE_ACCOUNT, mAccount);
}
@Override
public int getMetricsCategory() {
return MetricsProto.MetricsEvent.DIALOG_ACCOUNT_SYNC_REMOVE;
}
@Override
public void onClick(DialogInterface dialog, int which) {
Activity activity = getTargetFragment().getActivity();
AccountManager.get(activity).removeAccountAsUser(mAccount, activity,
new AccountManagerCallback<Bundle>() {
@Override
public void run(AccountManagerFuture<Bundle> future) {
// If already out of this screen, don't proceed.
if (!getTargetFragment().isResumed()) {
return;
}
boolean failed = true;
try {
if (future.getResult()
.getBoolean(AccountManager.KEY_BOOLEAN_RESULT)) {
failed = false;
}
} catch (OperationCanceledException e) {
// handled below
} catch (IOException e) {
// handled below
} catch (AuthenticatorException e) {
// handled below
}
final Activity activity = getTargetFragment().getActivity();
if (failed && activity != null && !activity.isFinishing()) {
RemoveAccountFailureDialog.show(getTargetFragment());
} else {
activity.finish();
}
}
}, null, Process.myUserHandle());
}
}
/**
* Dialog to tell user about account removal failure
*/
public static class RemoveAccountFailureDialog extends InstrumentedDialogFragment {
private static final String FAILED_REMOVAL_DIALOG = "removeAccountFailed";
public static void show(Fragment parent) {
if (!parent.isAdded()) {
return;
}
final RemoveAccountFailureDialog dialog = new RemoveAccountFailureDialog();
dialog.setTargetFragment(parent, 0);
dialog.show(parent.getFragmentManager(), FAILED_REMOVAL_DIALOG);
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Context context = getActivity();
return new AlertDialog.Builder(context)
.setTitle(R.string.really_remove_account_title)
.setMessage(R.string.remove_account_failed)
.setPositiveButton(android.R.string.ok, null)
.create();
}
@Override
public int getMetricsCategory() {
return MetricsProto.MetricsEvent.DIALOG_ACCOUNT_SYNC_FAILED_REMOVAL;
}
}
}

View File

@@ -15,35 +15,69 @@
*/ */
package com.android.settings.accounts; package com.android.settings.accounts;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AuthenticatorDescription;
import android.content.Context;
import android.os.Bundle; import android.os.Bundle;
import android.os.UserHandle;
import android.support.v7.preference.Preference;
import com.android.settings.R;
import com.android.settings.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig; import com.android.settings.TestConfig;
import com.android.settings.testutils.shadow.ShadowAccountManager;
import com.android.settings.testutils.shadow.ShadowContentResolver;
import com.android.settingslib.drawer.CategoryKey; import com.android.settingslib.drawer.CategoryKey;
import com.android.settingslib.drawer.Tile; import com.android.settingslib.drawer.Tile;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner; import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.annotation.Config; import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowApplication;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Answers.RETURNS_DEEP_STUBS;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@RunWith(RobolectricTestRunner.class) @RunWith(SettingsRobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION) @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
public class AccountDetailDashboardFragmentTest { public class AccountDetailDashboardFragmentTest {
private static final String METADATA_CATEGORY = "com.android.settings.category"; private static final String METADATA_CATEGORY = "com.android.settings.category";
private static final String METADATA_ACCOUNT_TYPE = "com.android.settings.ia.account"; private static final String METADATA_ACCOUNT_TYPE = "com.android.settings.ia.account";
private static final String METADATA_USER_HANDLE = "user_handle";
private static final String PREF_ACCOUNT_HEADER = "account_header";
@Mock(answer = RETURNS_DEEP_STUBS)
private AccountManager mAccountManager;
@Mock
private Preference mPreference;
private AccountDetailDashboardFragment mFragment; private AccountDetailDashboardFragment mFragment;
private Context mContext;
@Before @Before
public void setUp() { public void setUp() {
mFragment = new AccountDetailDashboardFragment(); MockitoAnnotations.initMocks(this);
ShadowApplication shadowContext = ShadowApplication.getInstance();
shadowContext.setSystemService(Context.ACCOUNT_SERVICE, mAccountManager);
mContext = spy(shadowContext.getApplicationContext());
mFragment = spy(new AccountDetailDashboardFragment());
final Bundle args = new Bundle(); final Bundle args = new Bundle();
args.putString(METADATA_ACCOUNT_TYPE, "com.abc"); args.putParcelable(METADATA_USER_HANDLE, UserHandle.CURRENT);
mFragment.setArguments(args);
mFragment.mAccountType = "com.abc"; mFragment.mAccountType = "com.abc";
mFragment.mAccount = new Account("name1@abc.com", "com.abc");
} }
@Test @Test
@@ -83,4 +117,18 @@ public class AccountDetailDashboardFragmentTest {
assertThat(mFragment.displayTile(tile)).isFalse(); assertThat(mFragment.displayTile(tile)).isFalse();
} }
@Test
@Config(shadows = {ShadowAccountManager.class, ShadowContentResolver.class})
public void updateAccountHeader_shouldShowAccountName() throws Exception {
when(mAccountManager.getAuthenticatorTypesAsUser(anyInt())).thenReturn(
new AuthenticatorDescription[0]);
when(mAccountManager.getAccountsAsUser(anyInt())).thenReturn(new Account[0]);
when(mFragment.getContext()).thenReturn(mContext);
doReturn(mPreference).when(mFragment).findPreference(PREF_ACCOUNT_HEADER);
mFragment.updateAccountHeader();
verify(mPreference).setTitle("name1@abc.com");
}
} }

View File

@@ -0,0 +1,137 @@
/*
* Copyright (C) 2017 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.settings.accounts;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountManagerCallback;
import android.accounts.AuthenticatorDescription;
import android.app.Activity;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.content.Context;
import android.os.Handler;
import android.os.UserHandle;
import android.support.v7.preference.PreferenceScreen;
import android.support.v14.preference.PreferenceFragment;
import android.widget.Button;
import com.android.settings.AccessiblePreferenceCategory;
import com.android.settings.R;
import com.android.settings.SettingsRobolectricTestRunner;
import com.android.settings.TestConfig;
import com.android.settings.applications.LayoutPreference;
import com.android.settings.search.SearchIndexableRaw;
import com.android.settings.testutils.FakeFeatureFactory;
import com.android.settings.testutils.shadow.ShadowAccountManager;
import com.android.settings.testutils.shadow.ShadowContentResolver;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowApplication;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Answers.RETURNS_DEEP_STUBS;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@RunWith(SettingsRobolectricTestRunner.class)
@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
public class RemoveAccountPreferenceControllerTest {
private static final String KEY_REMOVE_ACCOUNT = "remove_account";
private static final String TAG_REMOVE_ACCOUNT_DIALOG = "confirmRemoveAccount";
@Mock(answer = RETURNS_DEEP_STUBS)
private AccountManager mAccountManager;
@Mock(answer = RETURNS_DEEP_STUBS)
private PreferenceFragment mFragment;
@Mock
private PreferenceScreen mScreen;
@Mock
private FragmentManager mFragmentManager;
@Mock
private FragmentTransaction mFragmentTransaction;
@Mock
private LayoutPreference mPreference;
private Context mContext;
private RemoveAccountPreferenceController mController;
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
ShadowApplication shadowContext = ShadowApplication.getInstance();
shadowContext.setSystemService(Context.ACCOUNT_SERVICE, mAccountManager);
mContext = spy(shadowContext.getApplicationContext());
when(mFragment.getPreferenceScreen()).thenReturn(mScreen);
when(mFragment.getPreferenceManager().getContext()).thenReturn(mContext);
when(mFragment.getFragmentManager()).thenReturn(mFragmentManager);
when(mFragmentManager.beginTransaction()).thenReturn(mFragmentTransaction);
when(mAccountManager.getAuthenticatorTypesAsUser(anyInt())).thenReturn(
new AuthenticatorDescription[0]);
when(mAccountManager.getAccountsAsUser(anyInt())).thenReturn(new Account[0]);
mController = new RemoveAccountPreferenceController(mContext, mFragment);
}
@Test
public void displayPreference_shouldAddClickListener() {
when(mScreen.findPreference(KEY_REMOVE_ACCOUNT)).thenReturn(mPreference);
final Button button = mock(Button.class);
when(mPreference.findViewById(R.id.button)).thenReturn(button);
mController.displayPreference(mScreen);
verify(button).setOnClickListener(mController);
}
@Test
public void onClick_shouldStartConfirmDialog() {
when(mFragment.isAdded()).thenReturn(true);
mController.onClick(null);
verify(mFragmentTransaction).add(
any(RemoveAccountPreferenceController.ConfirmRemoveAccountDialog.class),
eq(TAG_REMOVE_ACCOUNT_DIALOG));
}
@Test
@Config(shadows = {ShadowAccountManager.class, ShadowContentResolver.class})
public void confirmRemove_shouldRemoveAccount() {
when(mFragment.isAdded()).thenReturn(true);
Activity activity = mock(Activity.class);
when(activity.getSystemService(Context.ACCOUNT_SERVICE)).thenReturn(mAccountManager);
when(mFragment.getActivity()).thenReturn(activity);
Account account = new Account("Account11", "com.acct1");
RemoveAccountPreferenceController.ConfirmRemoveAccountDialog dialog =
RemoveAccountPreferenceController.ConfirmRemoveAccountDialog.show(mFragment, account);
dialog.onClick(null, 0);
verify(mAccountManager).removeAccountAsUser(eq(account), any(Activity.class),
any(AccountManagerCallback.class), any(Handler.class), any(UserHandle.class));
}
}