1 /*
2  * Copyright (C) 2021 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 
17 package com.android.car.settings.system;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 
21 import static org.mockito.ArgumentMatchers.anyInt;
22 import static org.mockito.Mockito.when;
23 import static org.mockito.Mockito.withSettings;
24 
25 import android.accounts.Account;
26 import android.accounts.AccountManager;
27 import android.accounts.AuthenticatorDescription;
28 import android.car.drivingstate.CarUxRestrictions;
29 import android.content.Context;
30 import android.content.pm.UserInfo;
31 import android.os.UserHandle;
32 import android.os.UserManager;
33 
34 import androidx.lifecycle.LifecycleOwner;
35 import androidx.preference.PreferenceCategory;
36 import androidx.preference.PreferenceGroup;
37 import androidx.preference.PreferenceManager;
38 import androidx.preference.PreferenceScreen;
39 import androidx.test.annotation.UiThreadTest;
40 import androidx.test.core.app.ApplicationProvider;
41 import androidx.test.ext.junit.runners.AndroidJUnit4;
42 
43 import com.android.car.settings.R;
44 import com.android.car.settings.common.FragmentController;
45 import com.android.car.settings.common.PreferenceControllerTestUtil;
46 import com.android.car.settings.testutils.TestLifecycleOwner;
47 import com.android.dx.mockito.inline.extended.ExtendedMockito;
48 
49 import org.junit.After;
50 import org.junit.Before;
51 import org.junit.Test;
52 import org.junit.runner.RunWith;
53 import org.mockito.Mock;
54 import org.mockito.MockitoAnnotations;
55 import org.mockito.MockitoSession;
56 import org.mockito.stubbing.Answer;
57 
58 import java.util.ArrayList;
59 import java.util.HashMap;
60 import java.util.List;
61 import java.util.Map;
62 
63 /** Unit test for {@link FactoryResetAccountsPreferenceController}. */
64 @RunWith(AndroidJUnit4.class)
65 public class FactoryResetAccountsPreferenceControllerTest {
66     private final int mUserId = UserHandle.myUserId();
67 
68     private Context mContext = ApplicationProvider.getApplicationContext();
69     private LifecycleOwner mLifecycleOwner;
70     private PreferenceGroup mPreference;
71     private FactoryResetAccountsPreferenceController mPreferenceController;
72     private CarUxRestrictions mCarUxRestrictions;
73     private MockitoSession mSession;
74 
75     private Map<Integer, List<AuthenticatorDescription>> mAuthenticatorDescriptionMap;
76     private Map<Integer, List<Account>> mAccountMap;
77 
78     @Mock
79     private FragmentController mFragmentController;
80     @Mock
81     private AccountManager mMockAccountManager;
82     @Mock
83     private UserManager mMockUserManager;
84 
85     @Before
86     @UiThreadTest
setUp()87     public void setUp() {
88         MockitoAnnotations.initMocks(this);
89         mLifecycleOwner = new TestLifecycleOwner();
90         mCarUxRestrictions = new CarUxRestrictions.Builder(/* reqOpt= */ true,
91                 CarUxRestrictions.UX_RESTRICTIONS_BASELINE, /* timestamp= */ 0).build();
92 
93         mAuthenticatorDescriptionMap = new HashMap<>();
94         mAccountMap = new HashMap<>();
95         mAuthenticatorDescriptionMap.put(mUserId, new ArrayList<>());
96         mAccountMap.put(mUserId, new ArrayList<>());
97         mSession = ExtendedMockito.mockitoSession()
98                 .mockStatic(AccountManager.class, withSettings().lenient())
99                 .mockStatic(UserManager.class, withSettings().lenient())
100                 .startMocking();
101         when(AccountManager.get(mContext)).thenReturn(mMockAccountManager);
102         updateAccountManagerMocks();
103         when(UserManager.get(mContext)).thenCallRealMethod();
104 
105         PreferenceManager preferenceManager = new PreferenceManager(mContext);
106         PreferenceScreen screen = preferenceManager.createPreferenceScreen(mContext);
107         mPreference = new PreferenceCategory(mContext);
108         screen.addPreference(mPreference);
109         mPreferenceController = new FactoryResetAccountsPreferenceController(mContext,
110                 "key", mFragmentController, mCarUxRestrictions);
111         PreferenceControllerTestUtil.assignPreference(mPreferenceController, mPreference);
112     }
113 
114     @After
115     @UiThreadTest
tearDown()116     public void tearDown() {
117         if (mSession != null) {
118             mSession.finishMocking();
119         }
120     }
121 
122     @Test
onCreate_addsTitlePreference()123     public void onCreate_addsTitlePreference() {
124         mPreferenceController.onCreate(mLifecycleOwner);
125 
126         assertThat(mPreference.getPreferenceCount()).isEqualTo(1);
127         assertThat(mPreference.getPreference(0).getTitle()).isEqualTo(
128                 mContext.getString(R.string.factory_reset_accounts));
129     }
130 
131     @Test
refreshUi_accountsPresent_showsGroup()132     public void refreshUi_accountsPresent_showsGroup() {
133         mPreferenceController.onCreate(mLifecycleOwner);
134         mPreferenceController.onStart(mLifecycleOwner);
135         addAccountAndDescription(mUserId, "accountName");
136 
137         mPreferenceController.refreshUi();
138 
139         assertThat(mPreference.isVisible()).isTrue();
140     }
141 
142     @Test
refreshUi_noAccountsPresent_hidesGroup()143     public void refreshUi_noAccountsPresent_hidesGroup() {
144         mPreferenceController.onCreate(mLifecycleOwner);
145         mPreferenceController.onStart(mLifecycleOwner);
146 
147         mPreferenceController.refreshUi();
148 
149         assertThat(mPreference.isVisible()).isFalse();
150     }
151 
152     @Test
refreshUi_multipleProfiles_showsAllAccounts()153     public void refreshUi_multipleProfiles_showsAllAccounts() {
154         int profileId1 = 112;
155         int profileId2 = 113;
156         List<UserInfo> profiles = new ArrayList<>();
157         profiles.add(new UserInfo(profileId1, String.valueOf(profileId1), 0));
158         profiles.add(new UserInfo(profileId2, String.valueOf(profileId2), 0));
159         String accountName1 = "accountName1";
160         addAccountAndDescription(profileId1, accountName1);
161         String accountName2 = "accountName2";
162         addAccountAndDescription(profileId2, accountName2);
163 
164         when(UserManager.get(mContext)).thenReturn(mMockUserManager);
165         when(mMockUserManager.getProfiles(mUserId)).thenReturn(profiles);
166 
167         mPreferenceController.onCreate(mLifecycleOwner);
168         mPreferenceController.onStart(mLifecycleOwner);
169 
170         mPreferenceController.refreshUi();
171 
172         // Title + two profiles with one account each.
173         assertThat(mPreference.getPreferenceCount()).isEqualTo(3);
174         assertThat(mPreference.getPreference(1).getTitle()).isEqualTo(accountName1);
175         assertThat(mPreference.getPreference(2).getTitle()).isEqualTo(accountName2);
176     }
177 
178     @Test
refreshUi_missingAccountDescription_skipsAccount()179     public void refreshUi_missingAccountDescription_skipsAccount() {
180         mPreferenceController.onCreate(mLifecycleOwner);
181         mPreferenceController.onStart(mLifecycleOwner);
182         addAccountAndDescription(mUserId, "account name with desc");
183         String accountNameNoDesc = "account name no desc";
184         addAccount(mUserId, accountNameNoDesc);
185 
186         mPreferenceController.refreshUi();
187 
188         // Title + one account with valid description.
189         assertThat(mPreference.getPreferenceCount()).isEqualTo(2);
190         assertThat(mPreference.getPreference(1).getTitle()).isNotEqualTo(accountNameNoDesc);
191     }
192 
193     @Test
refreshUi_accountAdded_addsPreferenceToGroup()194     public void refreshUi_accountAdded_addsPreferenceToGroup() {
195         addAccountAndDescription(mUserId, "accountName");
196         mPreferenceController.onCreate(mLifecycleOwner);
197         mPreferenceController.onStart(mLifecycleOwner);
198         assertThat(mPreference.getPreferenceCount()).isEqualTo(2);
199 
200         String addedAccountName = "added account name";
201         addAccountAndDescription(mUserId, addedAccountName);
202         mPreferenceController.refreshUi();
203 
204         // Title + one already present account + one newly added account.
205         assertThat(mPreference.getPreferenceCount()).isEqualTo(3);
206         assertThat(mPreference.getPreference(2).getTitle()).isEqualTo(addedAccountName);
207     }
208 
209     @Test
refreshUi_accountRemoved_removesPreferenceFromGroup()210     public void refreshUi_accountRemoved_removesPreferenceFromGroup() {
211         String accountNameToRemove = "account name to remove";
212         addAccountAndDescription(mUserId, accountNameToRemove);
213         mPreferenceController.onCreate(mLifecycleOwner);
214         mPreferenceController.onStart(mLifecycleOwner);
215         assertThat(mPreference.getPreferenceCount()).isEqualTo(2);
216 
217         clearAllAccounts();
218         mPreferenceController.refreshUi();
219 
220         // Title only, all accounts removed.
221         assertThat(mPreference.getPreferenceCount()).isEqualTo(1);
222         assertThat(mPreference.getPreference(0).getTitle()).isNotEqualTo(accountNameToRemove);
223     }
224 
addAccountAndDescription(int profileId, String accountName)225     private void addAccountAndDescription(int profileId, String accountName) {
226         addDescription(profileId, accountName);
227         addAccount(profileId, accountName);
228     }
229 
addAccount(int profileId, String accountName)230     private void addAccount(int profileId, String accountName) {
231         String type = accountName + "_type";
232         if (!mAccountMap.containsKey(profileId)) {
233             mAccountMap.put(profileId, new ArrayList<>());
234         }
235         mAccountMap.get(profileId).add(new Account(accountName, type));
236         updateAccountManagerMocks();
237     }
238 
addDescription(int profileId, String accountName)239     private void addDescription(int profileId, String accountName) {
240         String type = accountName + "_type";
241         if (!mAuthenticatorDescriptionMap.containsKey(profileId)) {
242             mAuthenticatorDescriptionMap.put(profileId, new ArrayList<>());
243         }
244         mAuthenticatorDescriptionMap.get(profileId).add(
245                 new AuthenticatorDescription(type, "packageName",
246                         /* labelId= */ 0, /* iconId= */ 0, /* smallIconId= */ 0, /* prefId= */ 0));
247         updateAccountManagerMocks();
248     }
249 
clearAllAccounts()250     private void clearAllAccounts() {
251         mAuthenticatorDescriptionMap.clear();
252         mAccountMap.clear();
253         updateAccountManagerMocks();
254     }
255 
updateAccountManagerMocks()256     private void updateAccountManagerMocks() {
257         when(mMockAccountManager.getAuthenticatorTypesAsUser(anyInt())).then(
258                 (Answer<AuthenticatorDescription[]>) invocation -> {
259                     Object[] args = invocation.getArguments();
260                     int profileId = (Integer) args[0];
261                     return mAuthenticatorDescriptionMap.containsKey(profileId)
262                             ? mAuthenticatorDescriptionMap.get(profileId).toArray(
263                             new AuthenticatorDescription[0])
264                             : new AuthenticatorDescription[0];
265                 });
266         when(mMockAccountManager.getAccountsAsUser(anyInt())).then(
267                 (Answer<Account[]>) invocation -> {
268                     Object[] args = invocation.getArguments();
269                     int profileId = (Integer) args[0];
270                     return mAccountMap.containsKey(profileId)
271                             ? mAccountMap.get(profileId).toArray(new Account[0])
272                             : new Account[0];
273                 });
274     }
275 }
276