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