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.graphics.drawable.ColorDrawable; 39 import android.os.UserHandle; 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.Test; 52 import org.junit.runner.RunWith; 53 import org.mockito.Mock; 54 import org.mockito.MockitoAnnotations; 55 import org.robolectric.RobolectricTestRunner; 56 import org.robolectric.RuntimeEnvironment; 57 import org.robolectric.Shadows; 58 import org.robolectric.annotation.Config; 59 import org.robolectric.shadow.api.Shadow; 60 import org.robolectric.shadows.ShadowPackageManager; 61 import org.robolectric.util.ReflectionHelpers; 62 63 import java.util.Arrays; 64 import java.util.Collections; 65 66 @RunWith(RobolectricTestRunner.class) 67 @Config(shadows = { 68 ShadowUserManager.class, 69 com.android.settings.testutils.shadow.ShadowFragment.class, 70 }) 71 public class WebViewAppPickerTest { 72 73 private static final String PACKAGE_NAME = "com.example.test"; 74 private static final String PACKAGE_VERSION = "1.0.0"; 75 private static final String FIRST_USER_NAME = "FIRST_USER"; 76 private static final String SECOND_USER_NAME = "SECOND_USER"; 77 78 @Mock 79 private FragmentActivity mActivity; 80 81 private Context mContext; 82 private UserHandle mFirstUser; 83 private UserHandle mSecondUser; 84 private ShadowPackageManager mPackageManager; 85 private WebViewAppPicker mPicker; 86 private WebViewUpdateServiceWrapper mWvusWrapper; 87 private ShadowUserManager mUserManager; 88 89 @Before setUp()90 public void setUp() { 91 MockitoAnnotations.initMocks(this); 92 mContext = spy(RuntimeEnvironment.application); 93 mUserManager = Shadow.extract(mContext.getSystemService(Context.USER_SERVICE)); 94 mPackageManager = Shadows.shadowOf(mContext.getPackageManager()); 95 96 final ApplicationInfo applicationInfo = new ApplicationInfo(); 97 applicationInfo.name = PACKAGE_NAME; 98 applicationInfo.uid = 0; 99 applicationInfo.flags = 0; 100 applicationInfo.packageName = PACKAGE_NAME; 101 102 final PackageInfo packageInfo = new PackageInfo(); 103 packageInfo.packageName = PACKAGE_NAME; 104 packageInfo.applicationInfo = applicationInfo; 105 packageInfo.versionName = PACKAGE_VERSION; 106 mPackageManager.addPackage(packageInfo); 107 mPackageManager.setUnbadgedApplicationIcon(PACKAGE_NAME, new ColorDrawable()); 108 109 mFirstUser = mUserManager.addUser(0, FIRST_USER_NAME, 0); 110 mSecondUser = mUserManager.addUser(1, SECOND_USER_NAME, 0); 111 mPicker = new WebViewAppPicker(); 112 mPicker = spy(mPicker); 113 doNothing().when(mPicker).updateCandidates(); 114 doNothing().when(mPicker).updateCheckedState(any()); 115 doReturn(mActivity).when(mPicker).getActivity(); 116 117 ReflectionHelpers.setField(mPicker, "mMetricsFeatureProvider", 118 mock(MetricsFeatureProvider.class)); 119 mWvusWrapper = mock(WebViewUpdateServiceWrapper.class); 120 mPicker.setWebViewUpdateServiceWrapper(mWvusWrapper); 121 } 122 123 @After tearDown()124 public void tearDown() { 125 mPackageManager.removePackage(PACKAGE_NAME); 126 } 127 128 @Test testClickingItemChangesProvider()129 public void testClickingItemChangesProvider() { 130 testSuccessfulClickChangesProvider(); 131 } 132 133 @Test testFailingClick()134 public void testFailingClick() { 135 testFailingClickUpdatesSetting(); 136 } 137 138 @Test testClickingItemInActivityModeChangesProviderAndFinishes()139 public void testClickingItemInActivityModeChangesProviderAndFinishes() { 140 useWebViewSettingIntent(); 141 testSuccessfulClickChangesProvider(); 142 verify(mActivity, times(1)).finish(); 143 } 144 145 @Test testFailingClickInActivityMode()146 public void testFailingClickInActivityMode() { 147 useWebViewSettingIntent(); 148 testFailingClick(); 149 } 150 151 @Test testDisabledPackageShownAsDisabled()152 public void testDisabledPackageShownAsDisabled() { 153 DefaultAppInfo webviewAppInfo = mPicker.createDefaultAppInfo(mContext, 154 mContext.getPackageManager(), 155 createApplicationInfo(PACKAGE_NAME), "disabled"); 156 157 SelectorWithWidgetPreference preference = mock(SelectorWithWidgetPreference.class); 158 mPicker.bindPreference(preference, PACKAGE_NAME, webviewAppInfo, null); 159 mPicker.bindPreferenceExtra(preference, PACKAGE_NAME, webviewAppInfo, null, null); 160 verify(preference, times(1)).setEnabled(eq(false)); 161 verify(preference, never()).setEnabled(eq(true)); 162 } 163 164 @Test testEnabledPackageShownAsEnabled()165 public void testEnabledPackageShownAsEnabled() { 166 String disabledReason = ""; 167 DefaultAppInfo webviewAppInfo = mPicker.createDefaultAppInfo(mContext, 168 mContext.getPackageManager(), 169 createApplicationInfo(PACKAGE_NAME), disabledReason); 170 171 SelectorWithWidgetPreference preference = mock(SelectorWithWidgetPreference.class); 172 mPicker.bindPreference(preference, PACKAGE_NAME, webviewAppInfo, null); 173 mPicker.bindPreferenceExtra(preference, PACKAGE_NAME, webviewAppInfo, null, null); 174 verify(preference, times(1)).setEnabled(eq(true)); 175 verify(preference, never()).setEnabled(eq(false)); 176 } 177 178 @Test testDisabledPackageShowsDisabledReasonSummary()179 public void testDisabledPackageShowsDisabledReasonSummary() { 180 String disabledReason = "disabled"; 181 DefaultAppInfo webviewAppInfo = mPicker.createDefaultAppInfo(mContext, 182 mContext.getPackageManager(), 183 createApplicationInfo(PACKAGE_NAME), disabledReason); 184 185 SelectorWithWidgetPreference preference = mock(SelectorWithWidgetPreference.class); 186 mPicker.bindPreference(preference, PACKAGE_NAME, webviewAppInfo, null); 187 mPicker.bindPreferenceExtra(preference, PACKAGE_NAME, webviewAppInfo, null, null); 188 verify(preference, times(1)).setSummary(eq(disabledReason)); 189 // Ensure we haven't called setSummary several times. 190 verify(preference, times(1)).setSummary(any()); 191 } 192 193 @Test testEnabledPackageShowsEmptySummary()194 public void testEnabledPackageShowsEmptySummary() { 195 DefaultAppInfo webviewAppInfo = mPicker.createDefaultAppInfo(mContext, 196 mContext.getPackageManager(), 197 createApplicationInfo(PACKAGE_NAME), null); 198 199 SelectorWithWidgetPreference preference = mock(SelectorWithWidgetPreference.class); 200 mPicker.bindPreference(preference, PACKAGE_NAME, webviewAppInfo, null); 201 mPicker.bindPreferenceExtra(preference, PACKAGE_NAME, webviewAppInfo, null, null); 202 verify(preference, never()).setSummary(any()); 203 } 204 205 @Test testFinishIfNotAdmin()206 public void testFinishIfNotAdmin() { 207 mUserManager.setIsAdminUser(false); 208 mPicker.onAttach(mContext); 209 verify(mActivity, times(1)).finish(); 210 } 211 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.getUser()).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)", FIRST_USER_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.getUser()).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)", FIRST_USER_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.getUser()).thenReturn(mFirstUser); 274 275 UserPackage packageForSecondUser = mock(UserPackage.class); 276 when(packageForSecondUser.isEnabledPackage()).thenReturn(true); 277 when(packageForSecondUser.isInstalledPackage()).thenReturn(false); 278 when(packageForSecondUser.getUser()).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)", FIRST_USER_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.getUser()).thenReturn(mFirstUser); 299 300 UserPackage packageForSecondUser = mock(UserPackage.class); 301 when(packageForSecondUser.isEnabledPackage()).thenReturn(true); 302 when(packageForSecondUser.isInstalledPackage()).thenReturn(true); 303 when(packageForSecondUser.getUser()).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)", FIRST_USER_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