1 /*
2  * Copyright (C) 2016 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.settings;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 
21 import static org.mockito.Mockito.any;
22 import static org.mockito.Mockito.doNothing;
23 import static org.mockito.Mockito.doReturn;
24 import static org.mockito.Mockito.eq;
25 import static org.mockito.Mockito.mock;
26 import static org.mockito.Mockito.never;
27 import static org.mockito.Mockito.spy;
28 import static org.mockito.Mockito.times;
29 import static org.mockito.Mockito.verify;
30 import static org.mockito.Mockito.when;
31 
32 import android.accounts.Account;
33 import android.accounts.AccountManager;
34 import android.app.Activity;
35 import android.content.ComponentName;
36 import android.content.ContentResolver;
37 import android.content.Context;
38 import android.content.Intent;
39 import android.content.pm.ActivityInfo;
40 import android.content.pm.PackageManager;
41 import android.content.pm.ResolveInfo;
42 import android.os.UserManager;
43 import android.provider.Settings;
44 import android.view.LayoutInflater;
45 import android.view.View;
46 import android.view.ViewTreeObserver;
47 import android.widget.CheckBox;
48 import android.widget.LinearLayout;
49 import android.widget.ScrollView;
50 
51 import androidx.fragment.app.FragmentActivity;
52 
53 import com.android.settings.testutils.shadow.ShadowUserManager;
54 import com.android.settings.testutils.shadow.ShadowUtils;
55 import com.android.settingslib.development.DevelopmentSettingsEnabler;
56 
57 import org.junit.After;
58 import org.junit.Before;
59 import org.junit.Ignore;
60 import org.junit.Test;
61 import org.junit.runner.RunWith;
62 import org.mockito.ArgumentCaptor;
63 import org.mockito.Mock;
64 import org.mockito.MockitoAnnotations;
65 import org.robolectric.Robolectric;
66 import org.robolectric.RobolectricTestRunner;
67 import org.robolectric.Shadows;
68 import org.robolectric.annotation.Config;
69 import org.robolectric.shadow.api.Shadow;
70 import org.robolectric.shadows.ShadowActivity;
71 
72 @RunWith(RobolectricTestRunner.class)
73 @Config(shadows = {
74         ShadowUtils.class,
75         ShadowUserManager.class,
76         com.android.settings.testutils.shadow.ShadowFragment.class,
77 })
78 public class MainClearTest {
79 
80     private static final String TEST_ACCOUNT_TYPE = "android.test.account.type";
81     private static final String TEST_CONFIRMATION_PACKAGE = "android.test.conf.pkg";
82     private static final String TEST_CONFIRMATION_CLASS = "android.test.conf.pkg.ConfActivity";
83     private static final String TEST_ACCOUNT_NAME = "test@example.com";
84 
85     @Mock
86     private ScrollView mScrollView;
87     @Mock
88     private LinearLayout mLinearLayout;
89 
90     @Mock
91     private PackageManager mPackageManager;
92 
93     @Mock
94     private AccountManager mAccountManager;
95 
96     @Mock
97     private FragmentActivity mMockActivity;
98 
99     @Mock
100     private Intent mMockIntent;
101 
102     private MainClear mMainClear;
103     private ShadowActivity mShadowActivity;
104     private FragmentActivity mActivity;
105     private ShadowUserManager mShadowUserManager;
106     private View mContentView;
107 
108     @Before
setUp()109     public void setUp() {
110         MockitoAnnotations.initMocks(this);
111         mMainClear = spy(new MainClear() {
112             @Override
113             boolean showAnySubscriptionInfo(Context context) { return true; }
114         });
115         mActivity = Robolectric.setupActivity(FragmentActivity.class);
116         mShadowActivity = Shadows.shadowOf(mActivity);
117         UserManager userManager = mActivity.getSystemService(UserManager.class);
118         mShadowUserManager = Shadow.extract(userManager);
119         mShadowUserManager.setIsAdminUser(true);
120         mContentView = LayoutInflater.from(mActivity).inflate(R.layout.main_clear, null);
121 
122         // Make scrollView only have one child
123         when(mScrollView.getChildAt(0)).thenReturn(mLinearLayout);
124         when(mScrollView.getChildCount()).thenReturn(1);
125     }
126 
127     @After
tearDown()128     public void tearDown() {
129         mShadowUserManager.setIsAdminUser(false);
130     }
131 
132     @Test
testShowFinalConfirmation_eraseEsimVisible_eraseEsimChecked()133     public void testShowFinalConfirmation_eraseEsimVisible_eraseEsimChecked() {
134         final Context context = mock(Context.class);
135         when(mMainClear.getContext()).thenReturn(context);
136 
137         mMainClear.mEsimStorage = mContentView.findViewById(R.id.erase_esim);
138         mMainClear.mExternalStorage = mContentView.findViewById(R.id.erase_external);
139         mMainClear.mEsimStorageContainer = mContentView.findViewById(R.id.erase_esim_container);
140         mMainClear.mEsimStorageContainer.setVisibility(View.VISIBLE);
141         mMainClear.mEsimStorage.setChecked(true);
142         mMainClear.showFinalConfirmation();
143 
144         final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
145 
146         verify(context).startActivity(intent.capture());
147         assertThat(intent.getValue().getBundleExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS)
148                 .getBoolean(MainClear.ERASE_ESIMS_EXTRA, false))
149                 .isTrue();
150     }
151 
152     @Test
testShowFinalConfirmation_eraseEsimVisible_eraseEsimUnchecked()153     public void testShowFinalConfirmation_eraseEsimVisible_eraseEsimUnchecked() {
154         final Context context = mock(Context.class);
155         when(mMainClear.getContext()).thenReturn(context);
156 
157         mMainClear.mEsimStorage = mContentView.findViewById(R.id.erase_esim);
158         mMainClear.mExternalStorage = mContentView.findViewById(R.id.erase_external);
159         mMainClear.mEsimStorageContainer = mContentView.findViewById(R.id.erase_esim_container);
160         mMainClear.mEsimStorageContainer.setVisibility(View.VISIBLE);
161         mMainClear.mEsimStorage.setChecked(false);
162         mMainClear.showFinalConfirmation();
163         final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
164 
165         verify(context).startActivity(intent.capture());
166         assertThat(intent.getValue().getBundleExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS)
167                 .getBoolean(MainClear.ERASE_ESIMS_EXTRA, false))
168                 .isFalse();
169     }
170 
171     @Test
testShowFinalConfirmation_eraseEsimGone_eraseEsimChecked()172     public void testShowFinalConfirmation_eraseEsimGone_eraseEsimChecked() {
173         final Context context = mock(Context.class);
174         when(mMainClear.getContext()).thenReturn(context);
175 
176         mMainClear.mEsimStorage = mContentView.findViewById(R.id.erase_esim);
177         mMainClear.mExternalStorage = mContentView.findViewById(R.id.erase_external);
178         mMainClear.mEsimStorageContainer = mContentView.findViewById(R.id.erase_esim_container);
179         mMainClear.mEsimStorageContainer.setVisibility(View.GONE);
180         mMainClear.mEsimStorage.setChecked(true);
181         mMainClear.showFinalConfirmation();
182 
183         final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
184 
185         verify(context).startActivity(intent.capture());
186         assertThat(intent.getValue().getBundleExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS)
187             .getBoolean(MainClear.ERASE_ESIMS_EXTRA, false))
188             .isTrue();
189     }
190 
191     @Test
testShowFinalConfirmation_eraseEsimGone_eraseEsimUnchecked()192     public void testShowFinalConfirmation_eraseEsimGone_eraseEsimUnchecked() {
193         final Context context = mock(Context.class);
194         when(mMainClear.getContext()).thenReturn(context);
195 
196         mMainClear.mEsimStorage = mContentView.findViewById(R.id.erase_esim);
197         mMainClear.mExternalStorage = mContentView.findViewById(R.id.erase_external);
198         mMainClear.mEsimStorageContainer = mContentView.findViewById(R.id.erase_esim_container);
199         mMainClear.mEsimStorageContainer.setVisibility(View.GONE);
200         mMainClear.mEsimStorage.setChecked(false);
201         mMainClear.showFinalConfirmation();
202         final ArgumentCaptor<Intent> intent = ArgumentCaptor.forClass(Intent.class);
203 
204         verify(context).startActivity(intent.capture());
205         assertThat(intent.getValue().getBundleExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT_ARGUMENTS)
206             .getBoolean(MainClear.ERASE_ESIMS_EXTRA, false))
207             .isFalse();
208     }
209 
210     @Test
testShowWipeEuicc_euiccDisabled()211     public void testShowWipeEuicc_euiccDisabled() {
212         prepareEuiccState(
213                 false /* isEuiccEnabled */,
214                 true /* isEuiccProvisioned */,
215                 false /* isDeveloper */);
216         assertThat(mMainClear.showWipeEuicc()).isFalse();
217     }
218 
219     @Test
testShowWipeEuicc_euiccEnabled_unprovisioned()220     public void testShowWipeEuicc_euiccEnabled_unprovisioned() {
221         prepareEuiccState(
222                 true /* isEuiccEnabled */,
223                 false /* isEuiccProvisioned */,
224                 false /* isDeveloper */);
225         assertThat(mMainClear.showWipeEuicc()).isFalse();
226     }
227 
228     @Test
testShowWipeEuicc_euiccEnabled_provisioned()229     public void testShowWipeEuicc_euiccEnabled_provisioned() {
230         prepareEuiccState(
231                 true /* isEuiccEnabled */,
232                 true /* isEuiccProvisioned */,
233                 false /* isDeveloper */);
234         assertThat(mMainClear.showWipeEuicc()).isTrue();
235     }
236 
237     @Ignore("b/313566998")
238     @Test
testShowWipeEuicc_developerMode_unprovisioned()239     public void testShowWipeEuicc_developerMode_unprovisioned() {
240         prepareEuiccState(
241                 true /* isEuiccEnabled */,
242                 false /* isEuiccProvisioned */,
243                 true /* isDeveloper */);
244         assertThat(mMainClear.showWipeEuicc()).isTrue();
245     }
246 
247     @Test
testEsimRecheckBoxDefaultChecked()248     public void testEsimRecheckBoxDefaultChecked() {
249         assertThat(((CheckBox) mContentView.findViewById(R.id.erase_esim)).isChecked()).isFalse();
250     }
251 
252     @Test
testHasReachedBottom_NotScrollDown_returnFalse()253     public void testHasReachedBottom_NotScrollDown_returnFalse() {
254         initScrollView(100, 0, 200);
255 
256         assertThat(mMainClear.hasReachedBottom(mScrollView)).isFalse();
257     }
258 
259     @Test
testHasReachedBottom_CanNotScroll_returnTrue()260     public void testHasReachedBottom_CanNotScroll_returnTrue() {
261         initScrollView(100, 0, 80);
262 
263         assertThat(mMainClear.hasReachedBottom(mScrollView)).isTrue();
264     }
265 
266     @Test
testHasReachedBottom_ScrollToBottom_returnTrue()267     public void testHasReachedBottom_ScrollToBottom_returnTrue() {
268         initScrollView(100, 100, 200);
269 
270         assertThat(mMainClear.hasReachedBottom(mScrollView)).isTrue();
271     }
272 
273     @Test
testInitiateMainClear_inDemoMode_sendsIntent()274     public void testInitiateMainClear_inDemoMode_sendsIntent() {
275         ShadowUtils.setIsDemoUser(true);
276 
277         final ComponentName componentName =
278                 ComponentName.unflattenFromString("com.android.retaildemo/.DeviceAdminReceiver");
279         ShadowUtils.setDeviceOwnerComponent(componentName);
280 
281         mMainClear.mInitiateListener.onClick(mContentView);
282         final Intent intent = mShadowActivity.getNextStartedActivity();
283         assertThat(Intent.ACTION_FACTORY_RESET).isEqualTo(intent.getAction());
284         assertThat(componentName).isNotNull();
285         assertThat(componentName.getPackageName()).isEqualTo(intent.getPackage());
286     }
287 
288     @Test
testOnActivityResultInternal_invalideRequest()289     public void testOnActivityResultInternal_invalideRequest() {
290         int invalidRequestCode = -1;
291         doReturn(false).when(mMainClear).isValidRequestCode(eq(invalidRequestCode));
292 
293         mMainClear.onActivityResultInternal(invalidRequestCode, Activity.RESULT_OK, null);
294 
295         verify(mMainClear, times(1)).isValidRequestCode(eq(invalidRequestCode));
296         verify(mMainClear, times(0)).establishInitialState();
297         verify(mMainClear, times(0)).getAccountConfirmationIntent();
298         verify(mMainClear, times(0)).showFinalConfirmation();
299     }
300 
301     @Test
testOnActivityResultInternal_resultCanceled()302     public void testOnActivityResultInternal_resultCanceled() {
303         doReturn(true).when(mMainClear).isValidRequestCode(eq(MainClear.KEYGUARD_REQUEST));
304         doNothing().when(mMainClear).establishInitialState();
305 
306         mMainClear
307                 .onActivityResultInternal(MainClear.KEYGUARD_REQUEST, Activity.RESULT_CANCELED,
308                         null);
309 
310         verify(mMainClear, times(1)).isValidRequestCode(eq(MainClear.KEYGUARD_REQUEST));
311         verify(mMainClear, times(1)).establishInitialState();
312         verify(mMainClear, times(0)).getAccountConfirmationIntent();
313         verify(mMainClear, times(0)).showFinalConfirmation();
314     }
315 
316     @Test
testOnActivityResultInternal_keyguardRequestTriggeringConfirmAccount()317     public void testOnActivityResultInternal_keyguardRequestTriggeringConfirmAccount() {
318         doReturn(true).when(mMainClear).isValidRequestCode(eq(MainClear.KEYGUARD_REQUEST));
319         doReturn(mMockIntent).when(mMainClear).getAccountConfirmationIntent();
320         doNothing().when(mMainClear).showAccountCredentialConfirmation(eq(mMockIntent));
321 
322         mMainClear
323                 .onActivityResultInternal(MainClear.KEYGUARD_REQUEST, Activity.RESULT_OK, null);
324 
325         verify(mMainClear, times(1)).isValidRequestCode(eq(MainClear.KEYGUARD_REQUEST));
326         verify(mMainClear, times(0)).establishInitialState();
327         verify(mMainClear, times(1)).getAccountConfirmationIntent();
328         verify(mMainClear, times(1)).showAccountCredentialConfirmation(eq(mMockIntent));
329     }
330 
331     @Test
testOnActivityResultInternal_keyguardRequestTriggeringShowFinal()332     public void testOnActivityResultInternal_keyguardRequestTriggeringShowFinal() {
333         doReturn(true).when(mMainClear).isValidRequestCode(eq(MainClear.KEYGUARD_REQUEST));
334         doReturn(null).when(mMainClear).getAccountConfirmationIntent();
335         doNothing().when(mMainClear).showFinalConfirmation();
336 
337         mMainClear
338                 .onActivityResultInternal(MainClear.KEYGUARD_REQUEST, Activity.RESULT_OK, null);
339 
340         verify(mMainClear, times(1)).isValidRequestCode(eq(MainClear.KEYGUARD_REQUEST));
341         verify(mMainClear, times(0)).establishInitialState();
342         verify(mMainClear, times(1)).getAccountConfirmationIntent();
343         verify(mMainClear, times(1)).showFinalConfirmation();
344     }
345 
346     @Test
testOnActivityResultInternal_confirmRequestTriggeringShowFinal()347     public void testOnActivityResultInternal_confirmRequestTriggeringShowFinal() {
348         doReturn(true).when(mMainClear)
349                 .isValidRequestCode(eq(MainClear.CREDENTIAL_CONFIRM_REQUEST));
350         doNothing().when(mMainClear).showFinalConfirmation();
351 
352         mMainClear.onActivityResultInternal(
353                 MainClear.CREDENTIAL_CONFIRM_REQUEST, Activity.RESULT_OK, null);
354 
355         verify(mMainClear, times(1))
356                 .isValidRequestCode(eq(MainClear.CREDENTIAL_CONFIRM_REQUEST));
357         verify(mMainClear, times(0)).establishInitialState();
358         verify(mMainClear, times(0)).getAccountConfirmationIntent();
359         verify(mMainClear, times(1)).showFinalConfirmation();
360     }
361 
362     @Test
testGetAccountConfirmationIntent_unsupported()363     public void testGetAccountConfirmationIntent_unsupported() {
364         when(mMainClear.getActivity()).thenReturn(mActivity);
365         /* Using the default resources, account confirmation shouldn't trigger */
366         assertThat(mMainClear.getAccountConfirmationIntent()).isNull();
367     }
368 
369     @Test
testGetAccountConfirmationIntent_no_relevant_accounts()370     public void testGetAccountConfirmationIntent_no_relevant_accounts() {
371         when(mMainClear.getActivity()).thenReturn(mMockActivity);
372         when(mMockActivity.getString(R.string.account_type)).thenReturn(TEST_ACCOUNT_TYPE);
373         when(mMockActivity.getString(R.string.account_confirmation_package))
374                 .thenReturn(TEST_CONFIRMATION_PACKAGE);
375         when(mMockActivity.getString(R.string.account_confirmation_class))
376                 .thenReturn(TEST_CONFIRMATION_CLASS);
377 
378         Account[] accounts = new Account[0];
379         when(mMockActivity.getSystemService(Context.ACCOUNT_SERVICE)).thenReturn(mAccountManager);
380         when(mAccountManager.getAccountsByType(TEST_ACCOUNT_TYPE)).thenReturn(accounts);
381         assertThat(mMainClear.getAccountConfirmationIntent()).isNull();
382     }
383 
384     @Test
testGetAccountConfirmationIntent_unresolved()385     public void testGetAccountConfirmationIntent_unresolved() {
386         when(mMainClear.getActivity()).thenReturn(mMockActivity);
387         when(mMockActivity.getString(R.string.account_type)).thenReturn(TEST_ACCOUNT_TYPE);
388         when(mMockActivity.getString(R.string.account_confirmation_package))
389                 .thenReturn(TEST_CONFIRMATION_PACKAGE);
390         when(mMockActivity.getString(R.string.account_confirmation_class))
391                 .thenReturn(TEST_CONFIRMATION_CLASS);
392         Account[] accounts = new Account[]{new Account(TEST_ACCOUNT_NAME, TEST_ACCOUNT_TYPE)};
393         when(mMockActivity.getSystemService(Context.ACCOUNT_SERVICE)).thenReturn(mAccountManager);
394         when(mAccountManager.getAccountsByType(TEST_ACCOUNT_TYPE)).thenReturn(accounts);
395         // The package manager should not resolve the confirmation intent targeting the non-existent
396         // confirmation package.
397         when(mMockActivity.getPackageManager()).thenReturn(mPackageManager);
398         assertThat(mMainClear.getAccountConfirmationIntent()).isNull();
399     }
400 
401     @Test
testTryShowAccountConfirmation_ok()402     public void testTryShowAccountConfirmation_ok() {
403         when(mMainClear.getActivity()).thenReturn(mMockActivity);
404         // Only try to show account confirmation if the appropriate resource overlays are available.
405         when(mMockActivity.getString(R.string.account_type)).thenReturn(TEST_ACCOUNT_TYPE);
406         when(mMockActivity.getString(R.string.account_confirmation_package))
407                 .thenReturn(TEST_CONFIRMATION_PACKAGE);
408         when(mMockActivity.getString(R.string.account_confirmation_class))
409                 .thenReturn(TEST_CONFIRMATION_CLASS);
410         // Add accounts to trigger the search for a resolving intent.
411         Account[] accounts = new Account[]{new Account(TEST_ACCOUNT_NAME, TEST_ACCOUNT_TYPE)};
412         when(mMockActivity.getSystemService(Context.ACCOUNT_SERVICE)).thenReturn(mAccountManager);
413         when(mAccountManager.getAccountsByType(TEST_ACCOUNT_TYPE)).thenReturn(accounts);
414         // The package manager should not resolve the confirmation intent targeting the non-existent
415         // confirmation package.
416         when(mMockActivity.getPackageManager()).thenReturn(mPackageManager);
417 
418         ActivityInfo activityInfo = new ActivityInfo();
419         activityInfo.packageName = TEST_CONFIRMATION_PACKAGE;
420         ResolveInfo resolveInfo = new ResolveInfo();
421         resolveInfo.activityInfo = activityInfo;
422         when(mPackageManager.resolveActivity(any(), eq(0))).thenReturn(resolveInfo);
423 
424         Intent actualIntent = mMainClear.getAccountConfirmationIntent();
425         assertThat(TEST_CONFIRMATION_PACKAGE).isEqualTo(
426                 actualIntent.getComponent().getPackageName());
427         assertThat(TEST_CONFIRMATION_CLASS).isEqualTo(actualIntent.getComponent().getClassName());
428     }
429 
430     @Test
testShowAccountCredentialConfirmation()431     public void testShowAccountCredentialConfirmation() {
432         // Finally mock out the startActivityForResultCall
433         doNothing().when(mMainClear)
434                 .startActivityForResult(eq(mMockIntent),
435                         eq(MainClear.CREDENTIAL_CONFIRM_REQUEST));
436         mMainClear.showAccountCredentialConfirmation(mMockIntent);
437         verify(mMainClear, times(1))
438                 .startActivityForResult(eq(mMockIntent),
439                         eq(MainClear.CREDENTIAL_CONFIRM_REQUEST));
440     }
441 
442     @Test
testIsValidRequestCode()443     public void testIsValidRequestCode() {
444         assertThat(mMainClear.isValidRequestCode(MainClear.KEYGUARD_REQUEST)).isTrue();
445         assertThat(mMainClear.isValidRequestCode(MainClear.CREDENTIAL_CONFIRM_REQUEST))
446                 .isTrue();
447         assertThat(mMainClear.isValidRequestCode(0)).isFalse();
448     }
449 
450     @Test
testOnGlobalLayout_shouldNotRemoveListener()451     public void testOnGlobalLayout_shouldNotRemoveListener() {
452         final ViewTreeObserver viewTreeObserver = mock(ViewTreeObserver.class);
453         mMainClear.mScrollView = mScrollView;
454         doNothing().when(mMainClear).onGlobalLayout();
455         doReturn(true).when(mMainClear).hasReachedBottom(any());
456         when(mScrollView.getViewTreeObserver()).thenReturn(viewTreeObserver);
457 
458         mMainClear.onGlobalLayout();
459 
460         verify(viewTreeObserver, never()).removeOnGlobalLayoutListener(mMainClear);
461     }
462 
prepareEuiccState( boolean isEuiccEnabled, boolean isEuiccProvisioned, boolean isDeveloper)463     private void prepareEuiccState(
464             boolean isEuiccEnabled, boolean isEuiccProvisioned, boolean isDeveloper) {
465         doReturn(mActivity).when(mMainClear).getContext();
466         doReturn(isEuiccEnabled).when(mMainClear).isEuiccEnabled(any());
467         ContentResolver cr = mActivity.getContentResolver();
468         Settings.Global.putInt(cr, Settings.Global.EUICC_PROVISIONED, isEuiccProvisioned ? 1 : 0);
469         DevelopmentSettingsEnabler.setDevelopmentSettingsEnabled(mActivity, isDeveloper);
470     }
471 
initScrollView(int height, int scrollY, int childBottom)472     private void initScrollView(int height, int scrollY, int childBottom) {
473         when(mScrollView.getHeight()).thenReturn(height);
474         when(mScrollView.getScrollY()).thenReturn(scrollY);
475         when(mLinearLayout.getBottom()).thenReturn(childBottom);
476     }
477 }
478