1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.settings.accounts; 17 18 import static com.google.common.truth.Truth.assertThat; 19 20 import static org.mockito.Answers.RETURNS_DEEP_STUBS; 21 import static org.mockito.ArgumentMatchers.any; 22 import static org.mockito.ArgumentMatchers.anyInt; 23 import static org.mockito.ArgumentMatchers.eq; 24 import static org.mockito.ArgumentMatchers.nullable; 25 import static org.mockito.Mockito.mock; 26 import static org.mockito.Mockito.never; 27 import static org.mockito.Mockito.verify; 28 import static org.mockito.Mockito.when; 29 30 import android.accounts.Account; 31 import android.accounts.AccountManager; 32 import android.accounts.AccountManagerCallback; 33 import android.accounts.AccountManagerFuture; 34 import android.accounts.AuthenticatorDescription; 35 import android.accounts.AuthenticatorException; 36 import android.accounts.OperationCanceledException; 37 import android.app.Activity; 38 import android.content.ComponentName; 39 import android.content.Context; 40 import android.os.Bundle; 41 import android.os.Handler; 42 import android.os.UserHandle; 43 import android.os.UserManager; 44 import android.widget.Button; 45 46 import androidx.fragment.app.FragmentActivity; 47 import androidx.fragment.app.FragmentManager; 48 import androidx.fragment.app.FragmentTransaction; 49 import androidx.preference.PreferenceFragmentCompat; 50 import androidx.preference.PreferenceManager; 51 import androidx.preference.PreferenceScreen; 52 53 import com.android.settings.R; 54 import com.android.settings.testutils.shadow.ShadowAccountManager; 55 import com.android.settings.testutils.shadow.ShadowContentResolver; 56 import com.android.settings.testutils.shadow.ShadowDevicePolicyManager; 57 import com.android.settings.testutils.shadow.ShadowFragment; 58 import com.android.settings.testutils.shadow.ShadowUserManager; 59 import com.android.settingslib.widget.LayoutPreference; 60 61 import org.junit.After; 62 import org.junit.Before; 63 import org.junit.Test; 64 import org.junit.runner.RunWith; 65 import org.mockito.ArgumentCaptor; 66 import org.mockito.Mock; 67 import org.mockito.MockitoAnnotations; 68 import org.robolectric.Robolectric; 69 import org.robolectric.RobolectricTestRunner; 70 import org.robolectric.RuntimeEnvironment; 71 import org.robolectric.annotation.Config; 72 import org.robolectric.shadows.ShadowApplication; 73 74 import java.io.IOException; 75 import java.util.ArrayList; 76 import java.util.List; 77 78 @RunWith(RobolectricTestRunner.class) 79 @Config(shadows = { 80 ShadowUserManager.class, 81 ShadowDevicePolicyManager.class 82 }) 83 public class RemoveAccountPreferenceControllerTest { 84 85 private static final String KEY_REMOVE_ACCOUNT = "remove_account"; 86 private static final String TAG_REMOVE_ACCOUNT_DIALOG = "confirmRemoveAccount"; 87 88 @Mock(answer = RETURNS_DEEP_STUBS) 89 private AccountManager mAccountManager; 90 @Mock 91 private PreferenceFragmentCompat mFragment; 92 @Mock 93 private PreferenceManager mPreferenceManager; 94 @Mock 95 private PreferenceScreen mScreen; 96 @Mock 97 private FragmentManager mFragmentManager; 98 @Mock 99 private FragmentTransaction mFragmentTransaction; 100 @Mock 101 private LayoutPreference mPreference; 102 103 private RemoveAccountPreferenceController mController; 104 105 @Before setUp()106 public void setUp() { 107 MockitoAnnotations.initMocks(this); 108 ShadowApplication.getInstance().setSystemService(Context.ACCOUNT_SERVICE, mAccountManager); 109 110 when(mFragment.getPreferenceScreen()).thenReturn(mScreen); 111 when(mFragment.getPreferenceManager()).thenReturn(mPreferenceManager); 112 when(mPreferenceManager.getContext()).thenReturn(RuntimeEnvironment.application); 113 when(mFragment.getFragmentManager()).thenReturn(mFragmentManager); 114 when(mFragmentManager.beginTransaction()).thenReturn(mFragmentTransaction); 115 when(mAccountManager.getAuthenticatorTypesAsUser(anyInt())) 116 .thenReturn(new AuthenticatorDescription[0]); 117 when(mAccountManager.getAccountsAsUser(anyInt())).thenReturn(new Account[0]); 118 mController = new RemoveAccountPreferenceController( 119 Robolectric.setupActivity(Activity.class), mFragment); 120 } 121 122 @After tearDown()123 public void tearDown() { 124 ShadowContentResolver.reset(); 125 } 126 127 @Test displayPreference_shouldAddClickListener()128 public void displayPreference_shouldAddClickListener() { 129 when(mScreen.findPreference(KEY_REMOVE_ACCOUNT)).thenReturn(mPreference); 130 final Button button = mock(Button.class); 131 when(mPreference.findViewById(R.id.button)).thenReturn(button); 132 133 mController.displayPreference(mScreen); 134 135 verify(button).setOnClickListener(mController); 136 } 137 138 @Test onClick_shouldStartConfirmDialog()139 public void onClick_shouldStartConfirmDialog() { 140 when(mFragment.isAdded()).thenReturn(true); 141 mController.onClick(null); 142 143 verify(mFragmentTransaction).add( 144 any(RemoveAccountPreferenceController.ConfirmRemoveAccountDialog.class), 145 eq(TAG_REMOVE_ACCOUNT_DIALOG)); 146 } 147 148 @Test onClick_modifyAccountsIsDisallowed_shouldNotStartConfirmDialog()149 public void onClick_modifyAccountsIsDisallowed_shouldNotStartConfirmDialog() { 150 when(mFragment.isAdded()).thenReturn(true); 151 152 final int userId = UserHandle.myUserId(); 153 mController.init(new Account("test", "test"), UserHandle.of(userId)); 154 155 List<UserManager.EnforcingUser> enforcingUsers = new ArrayList<>(); 156 enforcingUsers.add(new UserManager.EnforcingUser(userId, 157 UserManager.RESTRICTION_SOURCE_DEVICE_OWNER)); 158 ComponentName componentName = new ComponentName("test", "test"); 159 // Ensure that RestrictedLockUtils.checkIfRestrictionEnforced doesn't return null. 160 ShadowUserManager.getShadow().setUserRestrictionSources( 161 UserManager.DISALLOW_MODIFY_ACCOUNTS, 162 UserHandle.of(userId), 163 enforcingUsers); 164 ShadowDevicePolicyManager.getShadow().setDeviceOwnerComponentOnAnyUser(componentName); 165 166 mController.onClick(null); 167 168 verify(mFragmentTransaction, never()).add( 169 any(RemoveAccountPreferenceController.ConfirmRemoveAccountDialog.class), 170 eq(TAG_REMOVE_ACCOUNT_DIALOG)); 171 } 172 173 @Test 174 @Config(shadows = {ShadowAccountManager.class, ShadowContentResolver.class, 175 ShadowFragment.class}) confirmRemove_shouldRemoveAccount()176 public void confirmRemove_shouldRemoveAccount() 177 throws AuthenticatorException, OperationCanceledException, IOException { 178 when(mFragment.isAdded()).thenReturn(true); 179 FragmentActivity activity = mock(FragmentActivity.class); 180 when(activity.getSystemService(Context.ACCOUNT_SERVICE)).thenReturn(mAccountManager); 181 when(mFragment.getActivity()).thenReturn(activity); 182 183 Account account = new Account("Account11", "com.acct1"); 184 UserHandle userHandle = new UserHandle(10); 185 RemoveAccountPreferenceController.ConfirmRemoveAccountDialog dialog = 186 RemoveAccountPreferenceController.ConfirmRemoveAccountDialog.show( 187 mFragment, account, userHandle); 188 dialog.onCreate(new Bundle()); 189 dialog.onClick(null, 0); 190 ArgumentCaptor<AccountManagerCallback<Bundle>> callbackCaptor = ArgumentCaptor.forClass( 191 AccountManagerCallback.class); 192 verify(mAccountManager).removeAccountAsUser(eq(account), nullable(Activity.class), 193 callbackCaptor.capture(), nullable(Handler.class), eq(userHandle)); 194 195 AccountManagerCallback<Bundle> callback = callbackCaptor.getValue(); 196 assertThat(callback).isNotNull(); 197 AccountManagerFuture<Bundle> future = mock(AccountManagerFuture.class); 198 Bundle resultBundle = new Bundle(); 199 resultBundle.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, true); 200 when(future.getResult()).thenReturn(resultBundle); 201 202 callback.run(future); 203 verify(activity).finish(); 204 } 205 206 @Test 207 @Config(shadows = {ShadowAccountManager.class, ShadowContentResolver.class, 208 ShadowFragment.class}) confirmRemove_activityGone_shouldSilentlyRemoveAccount()209 public void confirmRemove_activityGone_shouldSilentlyRemoveAccount() 210 throws AuthenticatorException, OperationCanceledException, IOException { 211 final Account account = new Account("Account11", "com.acct1"); 212 final UserHandle userHandle = new UserHandle(10); 213 final FragmentActivity activity = mock(FragmentActivity.class); 214 when(mFragment.isAdded()).thenReturn(true); 215 when(activity.getSystemService(Context.ACCOUNT_SERVICE)).thenReturn(mAccountManager); 216 when(mFragment.getActivity()).thenReturn(activity).thenReturn(null); 217 218 final RemoveAccountPreferenceController.ConfirmRemoveAccountDialog dialog = 219 RemoveAccountPreferenceController.ConfirmRemoveAccountDialog.show( 220 mFragment, account, userHandle); 221 dialog.onCreate(new Bundle()); 222 dialog.onClick(null, 0); 223 224 ArgumentCaptor<AccountManagerCallback<Bundle>> callbackCaptor = ArgumentCaptor.forClass( 225 AccountManagerCallback.class); 226 verify(mAccountManager).removeAccountAsUser(eq(account), nullable(Activity.class), 227 callbackCaptor.capture(), nullable(Handler.class), eq(userHandle)); 228 229 AccountManagerCallback<Bundle> callback = callbackCaptor.getValue(); 230 assertThat(callback).isNotNull(); 231 AccountManagerFuture<Bundle> future = mock(AccountManagerFuture.class); 232 Bundle resultBundle = new Bundle(); 233 resultBundle.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, true); 234 when(future.getResult()).thenReturn(resultBundle); 235 236 callback.run(future); 237 verify(activity, never()).finish(); 238 } 239 } 240