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