| /* |
| * Copyright (C) 2017 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.settings.webview; |
| |
| import static android.provider.Settings.ACTION_WEBVIEW_SETTINGS; |
| |
| import static com.google.common.truth.Truth.assertThat; |
| |
| import static org.mockito.ArgumentMatchers.any; |
| import static org.mockito.ArgumentMatchers.eq; |
| import static org.mockito.Mockito.doNothing; |
| import static org.mockito.Mockito.doReturn; |
| import static org.mockito.Mockito.mock; |
| import static org.mockito.Mockito.never; |
| import static org.mockito.Mockito.spy; |
| import static org.mockito.Mockito.times; |
| import static org.mockito.Mockito.verify; |
| import static org.mockito.Mockito.when; |
| |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.pm.ApplicationInfo; |
| import android.content.pm.PackageInfo; |
| import android.content.pm.UserInfo; |
| import android.graphics.drawable.ColorDrawable; |
| import android.webkit.UserPackage; |
| |
| import androidx.fragment.app.FragmentActivity; |
| |
| import com.android.settings.widget.RadioButtonPreference; |
| import com.android.settingslib.applications.DefaultAppInfo; |
| import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; |
| |
| import org.junit.After; |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.mockito.Mock; |
| import org.mockito.MockitoAnnotations; |
| import org.robolectric.RobolectricTestRunner; |
| import org.robolectric.RuntimeEnvironment; |
| import org.robolectric.Shadows; |
| import org.robolectric.shadow.api.Shadow; |
| import org.robolectric.shadows.ShadowPackageManager; |
| import org.robolectric.shadows.ShadowUserManager; |
| import org.robolectric.util.ReflectionHelpers; |
| |
| import java.util.Arrays; |
| import java.util.Collections; |
| |
| @RunWith(RobolectricTestRunner.class) |
| public class WebViewAppPickerTest { |
| |
| private final static String PACKAGE_NAME = "com.example.test"; |
| private final static String PACKAGE_VERSION = "1.0.0"; |
| |
| @Mock |
| private FragmentActivity mActivity; |
| |
| private Context mContext; |
| private UserInfo mFirstUser; |
| private UserInfo mSecondUser; |
| private ShadowPackageManager mPackageManager; |
| private WebViewAppPicker mPicker; |
| private WebViewUpdateServiceWrapper mWvusWrapper; |
| private ShadowUserManager mUserManager; |
| |
| @Before |
| public void setUp() { |
| MockitoAnnotations.initMocks(this); |
| mContext = spy(RuntimeEnvironment.application); |
| mUserManager = Shadow.extract(mContext.getSystemService(Context.USER_SERVICE)); |
| mPackageManager = Shadows.shadowOf(mContext.getPackageManager()); |
| |
| final ApplicationInfo applicationInfo = new ApplicationInfo(); |
| applicationInfo.name = PACKAGE_NAME; |
| applicationInfo.uid = 0; |
| applicationInfo.flags = 0; |
| applicationInfo.packageName = PACKAGE_NAME; |
| |
| final PackageInfo packageInfo = new PackageInfo(); |
| packageInfo.packageName = PACKAGE_NAME; |
| packageInfo.applicationInfo = applicationInfo; |
| packageInfo.versionName = PACKAGE_VERSION; |
| mPackageManager.addPackage(packageInfo); |
| mPackageManager.setUnbadgedApplicationIcon(PACKAGE_NAME, new ColorDrawable()); |
| |
| mFirstUser = new UserInfo(0, "FIRST_USER", 0); |
| mSecondUser = new UserInfo(0, "SECOND_USER", 0); |
| mPicker = new WebViewAppPicker(); |
| mPicker = spy(mPicker); |
| doNothing().when(mPicker).updateCandidates(); |
| doNothing().when(mPicker).updateCheckedState(any()); |
| doReturn(mActivity).when(mPicker).getActivity(); |
| |
| ReflectionHelpers.setField(mPicker, "mMetricsFeatureProvider", |
| mock(MetricsFeatureProvider.class)); |
| mWvusWrapper = mock(WebViewUpdateServiceWrapper.class); |
| mPicker.setWebViewUpdateServiceWrapper(mWvusWrapper); |
| } |
| |
| @After |
| public void tearDown() { |
| mPackageManager.removePackage(PACKAGE_NAME); |
| } |
| |
| @Test |
| public void testClickingItemChangesProvider() { |
| testSuccessfulClickChangesProvider(); |
| } |
| |
| @Test |
| public void testFailingClick() { |
| testFailingClickUpdatesSetting(); |
| } |
| |
| @Test |
| public void testClickingItemInActivityModeChangesProviderAndFinishes() { |
| useWebViewSettingIntent(); |
| testSuccessfulClickChangesProvider(); |
| verify(mActivity, times(1)).finish(); |
| } |
| |
| @Test |
| public void testFailingClickInActivityMode() { |
| useWebViewSettingIntent(); |
| testFailingClick(); |
| } |
| |
| @Test |
| public void testDisabledPackageShownAsDisabled() { |
| DefaultAppInfo webviewAppInfo = mPicker.createDefaultAppInfo(mContext, |
| mContext.getPackageManager(), |
| createApplicationInfo(PACKAGE_NAME), "disabled"); |
| |
| RadioButtonPreference preference = mock(RadioButtonPreference.class); |
| mPicker.bindPreference(preference, PACKAGE_NAME, webviewAppInfo, null); |
| mPicker.bindPreferenceExtra(preference, PACKAGE_NAME, webviewAppInfo, null, null); |
| verify(preference, times(1)).setEnabled(eq(false)); |
| verify(preference, never()).setEnabled(eq(true)); |
| } |
| |
| @Test |
| public void testEnabledPackageShownAsEnabled() { |
| String disabledReason = ""; |
| DefaultAppInfo webviewAppInfo = mPicker.createDefaultAppInfo(mContext, |
| mContext.getPackageManager(), |
| createApplicationInfo(PACKAGE_NAME), disabledReason); |
| |
| RadioButtonPreference preference = mock(RadioButtonPreference.class); |
| mPicker.bindPreference(preference, PACKAGE_NAME, webviewAppInfo, null); |
| mPicker.bindPreferenceExtra(preference, PACKAGE_NAME, webviewAppInfo, null, null); |
| verify(preference, times(1)).setEnabled(eq(true)); |
| verify(preference, never()).setEnabled(eq(false)); |
| } |
| |
| @Test |
| public void testDisabledPackageShowsDisabledReasonSummary() { |
| String disabledReason = "disabled"; |
| DefaultAppInfo webviewAppInfo = mPicker.createDefaultAppInfo(mContext, |
| mContext.getPackageManager(), |
| createApplicationInfo(PACKAGE_NAME), disabledReason); |
| |
| RadioButtonPreference preference = mock(RadioButtonPreference.class); |
| mPicker.bindPreference(preference, PACKAGE_NAME, webviewAppInfo, null); |
| mPicker.bindPreferenceExtra(preference, PACKAGE_NAME, webviewAppInfo, null, null); |
| verify(preference, times(1)).setSummary(eq(disabledReason)); |
| // Ensure we haven't called setSummary several times. |
| verify(preference, times(1)).setSummary(any()); |
| } |
| |
| @Test |
| public void testEnabledPackageShowsEmptySummary() { |
| DefaultAppInfo webviewAppInfo = mPicker.createDefaultAppInfo(mContext, |
| mContext.getPackageManager(), |
| createApplicationInfo(PACKAGE_NAME), null); |
| |
| RadioButtonPreference preference = mock(RadioButtonPreference.class); |
| mPicker.bindPreference(preference, PACKAGE_NAME, webviewAppInfo, null); |
| mPicker.bindPreferenceExtra(preference, PACKAGE_NAME, webviewAppInfo, null, null); |
| verify(preference, never()).setSummary(any()); |
| } |
| |
| @Test |
| public void testFinishIfNotAdmin() { |
| mUserManager.setIsAdminUser(false); |
| mPicker.onAttach(mContext); |
| verify(mActivity, times(1)).finish(); |
| } |
| |
| @Test |
| public void testNotFinishedIfAdmin() { |
| mUserManager.setIsAdminUser(true); |
| mPicker.onAttach(mContext); |
| verify(mActivity, never()).finish(); |
| } |
| |
| @Test |
| public void testDisabledReasonNullIfPackagesOk() { |
| UserPackage packageForFirstUser = mock(UserPackage.class); |
| when(packageForFirstUser.isEnabledPackage()).thenReturn(true); |
| when(packageForFirstUser.isInstalledPackage()).thenReturn(true); |
| |
| UserPackage packageForSecondUser = mock(UserPackage.class); |
| when(packageForSecondUser.isEnabledPackage()).thenReturn(true); |
| when(packageForSecondUser.isInstalledPackage()).thenReturn(true); |
| |
| WebViewUpdateServiceWrapper wvusWrapper = mock(WebViewUpdateServiceWrapper.class); |
| when(wvusWrapper.getPackageInfosAllUsers(any(), eq(PACKAGE_NAME))) |
| .thenReturn(Arrays.asList(packageForFirstUser, packageForSecondUser)); |
| |
| assertThat(mPicker.getDisabledReason(wvusWrapper, mContext, PACKAGE_NAME)).isNull(); |
| } |
| |
| @Test |
| public void testDisabledReasonForSingleUserDisabledPackage() { |
| UserPackage packageForFirstUser = mock(UserPackage.class); |
| when(packageForFirstUser.isEnabledPackage()).thenReturn(false); |
| when(packageForFirstUser.isInstalledPackage()).thenReturn(true); |
| when(packageForFirstUser.getUserInfo()).thenReturn(mFirstUser); |
| |
| WebViewUpdateServiceWrapper wvusWrapper = mock(WebViewUpdateServiceWrapper.class); |
| when(wvusWrapper.getPackageInfosAllUsers(any(), eq(PACKAGE_NAME))) |
| .thenReturn(Collections.singletonList(packageForFirstUser)); |
| |
| final String expectedReason = String.format("(disabled for user %s)", mFirstUser.name); |
| assertThat(mPicker.getDisabledReason(wvusWrapper, mContext, PACKAGE_NAME)) |
| .isEqualTo(expectedReason); |
| } |
| |
| @Test |
| public void testDisabledReasonForSingleUserUninstalledPackage() { |
| UserPackage packageForFirstUser = mock(UserPackage.class); |
| when(packageForFirstUser.isEnabledPackage()).thenReturn(true); |
| when(packageForFirstUser.isInstalledPackage()).thenReturn(false); |
| when(packageForFirstUser.getUserInfo()).thenReturn(mFirstUser); |
| |
| WebViewUpdateServiceWrapper wvusWrapper = mock(WebViewUpdateServiceWrapper.class); |
| when(wvusWrapper.getPackageInfosAllUsers(any(), eq(PACKAGE_NAME))) |
| .thenReturn(Collections.singletonList(packageForFirstUser)); |
| |
| final String expectedReason = String.format("(uninstalled for user %s)", mFirstUser.name); |
| assertThat(mPicker.getDisabledReason(wvusWrapper, mContext, PACKAGE_NAME)) |
| .isEqualTo(expectedReason); |
| } |
| |
| @Test |
| public void testDisabledReasonSeveralUsers() { |
| UserPackage packageForFirstUser = mock(UserPackage.class); |
| when(packageForFirstUser.isEnabledPackage()).thenReturn(false); |
| when(packageForFirstUser.isInstalledPackage()).thenReturn(true); |
| when(packageForFirstUser.getUserInfo()).thenReturn(mFirstUser); |
| |
| UserPackage packageForSecondUser = mock(UserPackage.class); |
| when(packageForSecondUser.isEnabledPackage()).thenReturn(true); |
| when(packageForSecondUser.isInstalledPackage()).thenReturn(false); |
| when(packageForSecondUser.getUserInfo()).thenReturn(mSecondUser); |
| |
| WebViewUpdateServiceWrapper wvusWrapper = mock(WebViewUpdateServiceWrapper.class); |
| when(wvusWrapper.getPackageInfosAllUsers(any(), eq(PACKAGE_NAME))) |
| .thenReturn(Arrays.asList(packageForFirstUser, packageForSecondUser)); |
| |
| final String expectedReason = String.format("(disabled for user %s)", mFirstUser.name); |
| assertThat(mPicker.getDisabledReason(wvusWrapper, mContext, PACKAGE_NAME)) |
| .isEqualTo(expectedReason); |
| } |
| |
| /** |
| * Ensure we only proclaim a package as uninstalled for a certain user if it's both uninstalled |
| * and disabled. |
| */ |
| @Test |
| public void testDisabledReasonUninstalledAndDisabled() { |
| UserPackage packageForFirstUser = mock(UserPackage.class); |
| when(packageForFirstUser.isEnabledPackage()).thenReturn(false); |
| when(packageForFirstUser.isInstalledPackage()).thenReturn(false); |
| when(packageForFirstUser.getUserInfo()).thenReturn(mFirstUser); |
| |
| UserPackage packageForSecondUser = mock(UserPackage.class); |
| when(packageForSecondUser.isEnabledPackage()).thenReturn(true); |
| when(packageForSecondUser.isInstalledPackage()).thenReturn(true); |
| when(packageForSecondUser.getUserInfo()).thenReturn(mSecondUser); |
| |
| WebViewUpdateServiceWrapper wvusWrapper = mock(WebViewUpdateServiceWrapper.class); |
| when(wvusWrapper.getPackageInfosAllUsers(any(), eq(PACKAGE_NAME))) |
| .thenReturn(Arrays.asList(packageForFirstUser, packageForSecondUser)); |
| |
| final String expectedReason = String.format("(uninstalled for user %s)", mFirstUser.name); |
| assertThat(mPicker.getDisabledReason(wvusWrapper, mContext, PACKAGE_NAME)) |
| .isEqualTo(expectedReason); |
| } |
| |
| /** |
| * Ensure that the version name of a WebView package is displayed after its name in the |
| * preference title. |
| */ |
| @Test |
| public void testWebViewVersionAddedAfterLabel() { |
| final DefaultAppInfo webviewAppInfo = mPicker.createDefaultAppInfo(mContext, |
| mContext.getPackageManager(), |
| createApplicationInfo(PACKAGE_NAME), "" /* disabledReason */); |
| |
| final RadioButtonPreference mockPreference = mock(RadioButtonPreference.class); |
| mPicker.bindPreference(mockPreference, PACKAGE_NAME, webviewAppInfo, null); |
| mPicker.bindPreferenceExtra( |
| mockPreference, PACKAGE_NAME, webviewAppInfo, null, null); |
| |
| verify(mockPreference).setTitle(eq(PACKAGE_NAME + " " + PACKAGE_VERSION)); |
| verify(mockPreference).setTitle(any()); |
| } |
| |
| private static ApplicationInfo createApplicationInfo(String packageName) { |
| ApplicationInfo ai = new ApplicationInfo(); |
| ai.packageName = packageName; |
| return ai; |
| } |
| |
| private void useWebViewSettingIntent() { |
| Intent intent = new Intent(ACTION_WEBVIEW_SETTINGS); |
| when(mActivity.getIntent()).thenReturn(intent); |
| } |
| |
| private void testSuccessfulClickChangesProvider() { |
| when(mWvusWrapper.getValidWebViewApplicationInfos(any())) |
| .thenReturn(Collections.singletonList(createApplicationInfo(PACKAGE_NAME))); |
| when(mWvusWrapper.setWebViewProvider(eq(PACKAGE_NAME))).thenReturn(true); |
| |
| RadioButtonPreference defaultPackagePref = mock(RadioButtonPreference.class); |
| when(defaultPackagePref.getKey()).thenReturn(PACKAGE_NAME); |
| mPicker.onRadioButtonClicked(defaultPackagePref); |
| |
| verify(mWvusWrapper, times(1)).setWebViewProvider(eq(PACKAGE_NAME)); |
| verify(mPicker, times(1)).updateCheckedState(PACKAGE_NAME); |
| verify(mWvusWrapper, never()).showInvalidChoiceToast(any()); |
| } |
| |
| private void testFailingClickUpdatesSetting() { |
| when(mWvusWrapper.getValidWebViewApplicationInfos(any())) |
| .thenReturn(Collections.singletonList(createApplicationInfo(PACKAGE_NAME))); |
| when(mWvusWrapper.setWebViewProvider(eq(PACKAGE_NAME))).thenReturn(false); |
| |
| RadioButtonPreference defaultPackagePref = mock(RadioButtonPreference.class); |
| when(defaultPackagePref.getKey()).thenReturn(PACKAGE_NAME); |
| mPicker.onRadioButtonClicked(defaultPackagePref); |
| |
| verify(mWvusWrapper, times(1)).setWebViewProvider(eq(PACKAGE_NAME)); |
| // Ensure we update the list of packages when we click a non-valid package - the list must |
| // have changed, otherwise this click wouldn't fail. |
| verify(mPicker, times(1)).updateCandidates(); |
| verify(mWvusWrapper, times(1)).showInvalidChoiceToast(any()); |
| } |
| } |