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 
17 package com.android.settings.webview;
18 
19 import static android.provider.Settings.ACTION_WEBVIEW_SETTINGS;
20 
21 import static com.google.common.truth.Truth.assertThat;
22 
23 import static org.mockito.ArgumentMatchers.any;
24 import static org.mockito.ArgumentMatchers.eq;
25 import static org.mockito.Mockito.doNothing;
26 import static org.mockito.Mockito.doReturn;
27 import static org.mockito.Mockito.mock;
28 import static org.mockito.Mockito.never;
29 import static org.mockito.Mockito.spy;
30 import static org.mockito.Mockito.times;
31 import static org.mockito.Mockito.verify;
32 import static org.mockito.Mockito.when;
33 
34 import android.content.Context;
35 import android.content.Intent;
36 import android.content.pm.ApplicationInfo;
37 import android.content.pm.PackageInfo;
38 import android.content.pm.UserInfo;
39 import android.graphics.drawable.ColorDrawable;
40 import android.webkit.UserPackage;
41 
42 import androidx.fragment.app.FragmentActivity;
43 
44 import com.android.settings.testutils.shadow.ShadowUserManager;
45 import com.android.settingslib.applications.DefaultAppInfo;
46 import com.android.settingslib.core.instrumentation.MetricsFeatureProvider;
47 import com.android.settingslib.widget.SelectorWithWidgetPreference;
48 
49 import org.junit.After;
50 import org.junit.Before;
51 import org.junit.Ignore;
52 import org.junit.Test;
53 import org.junit.runner.RunWith;
54 import org.mockito.Mock;
55 import org.mockito.MockitoAnnotations;
56 import org.robolectric.RobolectricTestRunner;
57 import org.robolectric.RuntimeEnvironment;
58 import org.robolectric.Shadows;
59 import org.robolectric.annotation.Config;
60 import org.robolectric.shadow.api.Shadow;
61 import org.robolectric.shadows.ShadowPackageManager;
62 import org.robolectric.util.ReflectionHelpers;
63 
64 import java.util.Arrays;
65 import java.util.Collections;
66 
67 @RunWith(RobolectricTestRunner.class)
68 @Config(shadows = {
69         ShadowUserManager.class,
70         com.android.settings.testutils.shadow.ShadowFragment.class,
71 })
72 public class WebViewAppPickerTest {
73 
74     private final static String PACKAGE_NAME = "com.example.test";
75     private final static String PACKAGE_VERSION = "1.0.0";
76 
77     @Mock
78     private FragmentActivity mActivity;
79 
80     private Context mContext;
81     private UserInfo mFirstUser;
82     private UserInfo mSecondUser;
83     private ShadowPackageManager mPackageManager;
84     private WebViewAppPicker mPicker;
85     private WebViewUpdateServiceWrapper mWvusWrapper;
86     private ShadowUserManager mUserManager;
87 
88     @Before
setUp()89     public void setUp() {
90         MockitoAnnotations.initMocks(this);
91         mContext = spy(RuntimeEnvironment.application);
92         mUserManager = Shadow.extract(mContext.getSystemService(Context.USER_SERVICE));
93         mPackageManager = Shadows.shadowOf(mContext.getPackageManager());
94 
95         final ApplicationInfo applicationInfo = new ApplicationInfo();
96         applicationInfo.name = PACKAGE_NAME;
97         applicationInfo.uid = 0;
98         applicationInfo.flags = 0;
99         applicationInfo.packageName = PACKAGE_NAME;
100 
101         final PackageInfo packageInfo = new PackageInfo();
102         packageInfo.packageName = PACKAGE_NAME;
103         packageInfo.applicationInfo = applicationInfo;
104         packageInfo.versionName = PACKAGE_VERSION;
105         mPackageManager.addPackage(packageInfo);
106         mPackageManager.setUnbadgedApplicationIcon(PACKAGE_NAME, new ColorDrawable());
107 
108         mFirstUser = new UserInfo(0, "FIRST_USER", 0);
109         mSecondUser = new UserInfo(0, "SECOND_USER", 0);
110         mPicker = new WebViewAppPicker();
111         mPicker = spy(mPicker);
112         doNothing().when(mPicker).updateCandidates();
113         doNothing().when(mPicker).updateCheckedState(any());
114         doReturn(mActivity).when(mPicker).getActivity();
115 
116         ReflectionHelpers.setField(mPicker, "mMetricsFeatureProvider",
117                 mock(MetricsFeatureProvider.class));
118         mWvusWrapper = mock(WebViewUpdateServiceWrapper.class);
119         mPicker.setWebViewUpdateServiceWrapper(mWvusWrapper);
120     }
121 
122     @After
tearDown()123     public void tearDown() {
124         mPackageManager.removePackage(PACKAGE_NAME);
125     }
126 
127     @Test
testClickingItemChangesProvider()128     public void testClickingItemChangesProvider() {
129         testSuccessfulClickChangesProvider();
130     }
131 
132     @Test
testFailingClick()133     public void testFailingClick() {
134         testFailingClickUpdatesSetting();
135     }
136 
137     @Test
testClickingItemInActivityModeChangesProviderAndFinishes()138     public void testClickingItemInActivityModeChangesProviderAndFinishes() {
139         useWebViewSettingIntent();
140         testSuccessfulClickChangesProvider();
141         verify(mActivity, times(1)).finish();
142     }
143 
144     @Test
testFailingClickInActivityMode()145     public void testFailingClickInActivityMode() {
146         useWebViewSettingIntent();
147         testFailingClick();
148     }
149 
150     @Test
testDisabledPackageShownAsDisabled()151     public void testDisabledPackageShownAsDisabled() {
152         DefaultAppInfo webviewAppInfo = mPicker.createDefaultAppInfo(mContext,
153                 mContext.getPackageManager(),
154                 createApplicationInfo(PACKAGE_NAME), "disabled");
155 
156         SelectorWithWidgetPreference preference = mock(SelectorWithWidgetPreference.class);
157         mPicker.bindPreference(preference, PACKAGE_NAME, webviewAppInfo, null);
158         mPicker.bindPreferenceExtra(preference, PACKAGE_NAME, webviewAppInfo, null, null);
159         verify(preference, times(1)).setEnabled(eq(false));
160         verify(preference, never()).setEnabled(eq(true));
161     }
162 
163     @Test
testEnabledPackageShownAsEnabled()164     public void testEnabledPackageShownAsEnabled() {
165         String disabledReason = "";
166         DefaultAppInfo webviewAppInfo = mPicker.createDefaultAppInfo(mContext,
167                 mContext.getPackageManager(),
168                 createApplicationInfo(PACKAGE_NAME), disabledReason);
169 
170         SelectorWithWidgetPreference preference = mock(SelectorWithWidgetPreference.class);
171         mPicker.bindPreference(preference, PACKAGE_NAME, webviewAppInfo, null);
172         mPicker.bindPreferenceExtra(preference, PACKAGE_NAME, webviewAppInfo, null, null);
173         verify(preference, times(1)).setEnabled(eq(true));
174         verify(preference, never()).setEnabled(eq(false));
175     }
176 
177     @Test
testDisabledPackageShowsDisabledReasonSummary()178     public void testDisabledPackageShowsDisabledReasonSummary() {
179         String disabledReason = "disabled";
180         DefaultAppInfo webviewAppInfo = mPicker.createDefaultAppInfo(mContext,
181                 mContext.getPackageManager(),
182                 createApplicationInfo(PACKAGE_NAME), disabledReason);
183 
184         SelectorWithWidgetPreference preference = mock(SelectorWithWidgetPreference.class);
185         mPicker.bindPreference(preference, PACKAGE_NAME, webviewAppInfo, null);
186         mPicker.bindPreferenceExtra(preference, PACKAGE_NAME, webviewAppInfo, null, null);
187         verify(preference, times(1)).setSummary(eq(disabledReason));
188         // Ensure we haven't called setSummary several times.
189         verify(preference, times(1)).setSummary(any());
190     }
191 
192     @Test
testEnabledPackageShowsEmptySummary()193     public void testEnabledPackageShowsEmptySummary() {
194         DefaultAppInfo webviewAppInfo = mPicker.createDefaultAppInfo(mContext,
195                 mContext.getPackageManager(),
196                 createApplicationInfo(PACKAGE_NAME), null);
197 
198         SelectorWithWidgetPreference preference = mock(SelectorWithWidgetPreference.class);
199         mPicker.bindPreference(preference, PACKAGE_NAME, webviewAppInfo, null);
200         mPicker.bindPreferenceExtra(preference, PACKAGE_NAME, webviewAppInfo, null, null);
201         verify(preference, never()).setSummary(any());
202     }
203 
204     @Test
testFinishIfNotAdmin()205     public void testFinishIfNotAdmin() {
206         mUserManager.setIsAdminUser(false);
207         mPicker.onAttach(mContext);
208         verify(mActivity, times(1)).finish();
209     }
210 
211     @Ignore("b/313615637")
212     @Test
testNotFinishedIfAdmin()213     public void testNotFinishedIfAdmin() {
214         mUserManager.setIsAdminUser(true);
215         mPicker.onAttach(mContext);
216         verify(mActivity, never()).finish();
217     }
218 
219     @Test
testDisabledReasonNullIfPackagesOk()220     public void testDisabledReasonNullIfPackagesOk() {
221         UserPackage packageForFirstUser = mock(UserPackage.class);
222         when(packageForFirstUser.isEnabledPackage()).thenReturn(true);
223         when(packageForFirstUser.isInstalledPackage()).thenReturn(true);
224 
225         UserPackage packageForSecondUser = mock(UserPackage.class);
226         when(packageForSecondUser.isEnabledPackage()).thenReturn(true);
227         when(packageForSecondUser.isInstalledPackage()).thenReturn(true);
228 
229         WebViewUpdateServiceWrapper wvusWrapper = mock(WebViewUpdateServiceWrapper.class);
230         when(wvusWrapper.getPackageInfosAllUsers(any(), eq(PACKAGE_NAME)))
231                 .thenReturn(Arrays.asList(packageForFirstUser, packageForSecondUser));
232 
233         assertThat(mPicker.getDisabledReason(wvusWrapper, mContext, PACKAGE_NAME)).isNull();
234     }
235 
236     @Test
testDisabledReasonForSingleUserDisabledPackage()237     public void testDisabledReasonForSingleUserDisabledPackage() {
238         UserPackage packageForFirstUser = mock(UserPackage.class);
239         when(packageForFirstUser.isEnabledPackage()).thenReturn(false);
240         when(packageForFirstUser.isInstalledPackage()).thenReturn(true);
241         when(packageForFirstUser.getUserInfo()).thenReturn(mFirstUser);
242 
243         WebViewUpdateServiceWrapper wvusWrapper = mock(WebViewUpdateServiceWrapper.class);
244         when(wvusWrapper.getPackageInfosAllUsers(any(), eq(PACKAGE_NAME)))
245                 .thenReturn(Collections.singletonList(packageForFirstUser));
246 
247         final String expectedReason = String.format("(disabled for user %s)", mFirstUser.name);
248         assertThat(mPicker.getDisabledReason(wvusWrapper, mContext, PACKAGE_NAME))
249                 .isEqualTo(expectedReason);
250     }
251 
252     @Test
testDisabledReasonForSingleUserUninstalledPackage()253     public void testDisabledReasonForSingleUserUninstalledPackage() {
254         UserPackage packageForFirstUser = mock(UserPackage.class);
255         when(packageForFirstUser.isEnabledPackage()).thenReturn(true);
256         when(packageForFirstUser.isInstalledPackage()).thenReturn(false);
257         when(packageForFirstUser.getUserInfo()).thenReturn(mFirstUser);
258 
259         WebViewUpdateServiceWrapper wvusWrapper = mock(WebViewUpdateServiceWrapper.class);
260         when(wvusWrapper.getPackageInfosAllUsers(any(), eq(PACKAGE_NAME)))
261                 .thenReturn(Collections.singletonList(packageForFirstUser));
262 
263         final String expectedReason = String.format("(uninstalled for user %s)", mFirstUser.name);
264         assertThat(mPicker.getDisabledReason(wvusWrapper, mContext, PACKAGE_NAME))
265                 .isEqualTo(expectedReason);
266     }
267 
268     @Test
testDisabledReasonSeveralUsers()269     public void testDisabledReasonSeveralUsers() {
270         UserPackage packageForFirstUser = mock(UserPackage.class);
271         when(packageForFirstUser.isEnabledPackage()).thenReturn(false);
272         when(packageForFirstUser.isInstalledPackage()).thenReturn(true);
273         when(packageForFirstUser.getUserInfo()).thenReturn(mFirstUser);
274 
275         UserPackage packageForSecondUser = mock(UserPackage.class);
276         when(packageForSecondUser.isEnabledPackage()).thenReturn(true);
277         when(packageForSecondUser.isInstalledPackage()).thenReturn(false);
278         when(packageForSecondUser.getUserInfo()).thenReturn(mSecondUser);
279 
280         WebViewUpdateServiceWrapper wvusWrapper = mock(WebViewUpdateServiceWrapper.class);
281         when(wvusWrapper.getPackageInfosAllUsers(any(), eq(PACKAGE_NAME)))
282                 .thenReturn(Arrays.asList(packageForFirstUser, packageForSecondUser));
283 
284         final String expectedReason = String.format("(disabled for user %s)", mFirstUser.name);
285         assertThat(mPicker.getDisabledReason(wvusWrapper, mContext, PACKAGE_NAME))
286                 .isEqualTo(expectedReason);
287     }
288 
289     /**
290      * Ensure we only proclaim a package as uninstalled for a certain user if it's both uninstalled
291      * and disabled.
292      */
293     @Test
testDisabledReasonUninstalledAndDisabled()294     public void testDisabledReasonUninstalledAndDisabled() {
295         UserPackage packageForFirstUser = mock(UserPackage.class);
296         when(packageForFirstUser.isEnabledPackage()).thenReturn(false);
297         when(packageForFirstUser.isInstalledPackage()).thenReturn(false);
298         when(packageForFirstUser.getUserInfo()).thenReturn(mFirstUser);
299 
300         UserPackage packageForSecondUser = mock(UserPackage.class);
301         when(packageForSecondUser.isEnabledPackage()).thenReturn(true);
302         when(packageForSecondUser.isInstalledPackage()).thenReturn(true);
303         when(packageForSecondUser.getUserInfo()).thenReturn(mSecondUser);
304 
305         WebViewUpdateServiceWrapper wvusWrapper = mock(WebViewUpdateServiceWrapper.class);
306         when(wvusWrapper.getPackageInfosAllUsers(any(), eq(PACKAGE_NAME)))
307                 .thenReturn(Arrays.asList(packageForFirstUser, packageForSecondUser));
308 
309         final String expectedReason = String.format("(uninstalled for user %s)", mFirstUser.name);
310         assertThat(mPicker.getDisabledReason(wvusWrapper, mContext, PACKAGE_NAME))
311                 .isEqualTo(expectedReason);
312     }
313 
314     /**
315      * Ensure that the version name of a WebView package is displayed after its name in the
316      * preference title.
317      */
318     @Test
testWebViewVersionAddedAfterLabel()319     public void testWebViewVersionAddedAfterLabel() {
320         final DefaultAppInfo webviewAppInfo = mPicker.createDefaultAppInfo(mContext,
321                 mContext.getPackageManager(),
322                 createApplicationInfo(PACKAGE_NAME), "" /* disabledReason */);
323 
324         final SelectorWithWidgetPreference mockPreference =
325                 mock(SelectorWithWidgetPreference.class);
326         mPicker.bindPreference(mockPreference, PACKAGE_NAME, webviewAppInfo, null);
327         mPicker.bindPreferenceExtra(
328                 mockPreference, PACKAGE_NAME, webviewAppInfo, null, null);
329 
330         verify(mockPreference).setTitle(eq(PACKAGE_NAME + " " + PACKAGE_VERSION));
331         verify(mockPreference).setTitle(any());
332     }
333 
createApplicationInfo(String packageName)334     private static ApplicationInfo createApplicationInfo(String packageName) {
335         ApplicationInfo ai = new ApplicationInfo();
336         ai.packageName = packageName;
337         return ai;
338     }
339 
useWebViewSettingIntent()340     private void useWebViewSettingIntent() {
341         Intent intent = new Intent(ACTION_WEBVIEW_SETTINGS);
342         when(mActivity.getIntent()).thenReturn(intent);
343     }
344 
testSuccessfulClickChangesProvider()345     private void testSuccessfulClickChangesProvider() {
346         when(mWvusWrapper.getValidWebViewApplicationInfos(any()))
347                 .thenReturn(Collections.singletonList(createApplicationInfo(PACKAGE_NAME)));
348         when(mWvusWrapper.setWebViewProvider(eq(PACKAGE_NAME))).thenReturn(true);
349 
350         SelectorWithWidgetPreference defaultPackagePref = mock(SelectorWithWidgetPreference.class);
351         when(defaultPackagePref.getKey()).thenReturn(PACKAGE_NAME);
352         mPicker.onRadioButtonClicked(defaultPackagePref);
353 
354         verify(mWvusWrapper, times(1)).setWebViewProvider(eq(PACKAGE_NAME));
355         verify(mPicker, times(1)).updateCheckedState(PACKAGE_NAME);
356         verify(mWvusWrapper, never()).showInvalidChoiceToast(any());
357     }
358 
testFailingClickUpdatesSetting()359     private void testFailingClickUpdatesSetting() {
360         when(mWvusWrapper.getValidWebViewApplicationInfos(any()))
361                 .thenReturn(Collections.singletonList(createApplicationInfo(PACKAGE_NAME)));
362         when(mWvusWrapper.setWebViewProvider(eq(PACKAGE_NAME))).thenReturn(false);
363 
364         SelectorWithWidgetPreference defaultPackagePref = mock(SelectorWithWidgetPreference.class);
365         when(defaultPackagePref.getKey()).thenReturn(PACKAGE_NAME);
366         mPicker.onRadioButtonClicked(defaultPackagePref);
367 
368         verify(mWvusWrapper, times(1)).setWebViewProvider(eq(PACKAGE_NAME));
369         // Ensure we update the list of packages when we click a non-valid package - the list must
370         // have changed, otherwise this click wouldn't fail.
371         verify(mPicker, times(1)).updateCandidates();
372         verify(mWvusWrapper, times(1)).showInvalidChoiceToast(any());
373     }
374 }
375