/* * Copyright (C) 2016 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.server.webkit; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.Signature; import android.os.Build; import android.os.Bundle; import android.support.test.InstrumentationRegistry; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.MediumTest; import android.util.Base64; import android.webkit.UserPackage; import android.webkit.WebViewFactory; import android.webkit.WebViewProviderInfo; import android.webkit.WebViewProviderResponse; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentMatcher; import org.mockito.Mockito; import org.mockito.Matchers; import java.lang.Integer; import java.util.concurrent.CountDownLatch; /** * Tests for WebViewUpdateService runtest --path frameworks/base/services/tests/servicestests/ \ -c com.android.server.webkit.WebViewUpdateServiceTest */ // Use MediumTest instead of SmallTest as the implementation of WebViewUpdateService // is intended to work on several threads and uses at least one sleep/wait-statement. @RunWith(AndroidJUnit4.class) @MediumTest public class WebViewUpdateServiceTest { private final static String TAG = WebViewUpdateServiceTest.class.getSimpleName(); private WebViewUpdateServiceImpl mWebViewUpdateServiceImpl; private TestSystemImpl mTestSystemImpl; private static final String WEBVIEW_LIBRARY_FLAG = "com.android.webview.WebViewLibrary"; /** * Creates a new instance. */ public WebViewUpdateServiceTest() { } private void setupWithPackages(WebViewProviderInfo[] packages) { setupWithPackages(packages, true); } private void setupWithPackages(WebViewProviderInfo[] packages, boolean fallbackLogicEnabled) { setupWithPackages(packages, fallbackLogicEnabled, 1); } private void setupWithPackages(WebViewProviderInfo[] packages, boolean fallbackLogicEnabled, int numRelros) { setupWithPackages(packages, fallbackLogicEnabled, numRelros, true /* isDebuggable == true -> don't check package signatures */); } private void setupWithPackages(WebViewProviderInfo[] packages, boolean fallbackLogicEnabled, int numRelros, boolean isDebuggable) { setupWithPackages(packages, fallbackLogicEnabled, numRelros, isDebuggable, false /* multiProcessDefault */); } private void setupWithPackages(WebViewProviderInfo[] packages, boolean fallbackLogicEnabled, int numRelros, boolean isDebuggable, boolean multiProcessDefault) { TestSystemImpl testing = new TestSystemImpl(packages, fallbackLogicEnabled, numRelros, isDebuggable, multiProcessDefault); mTestSystemImpl = Mockito.spy(testing); mWebViewUpdateServiceImpl = new WebViewUpdateServiceImpl(null /*Context*/, mTestSystemImpl); } private void setEnabledAndValidPackageInfos(WebViewProviderInfo[] providers) { // Set package infos for the primary user (user 0). setEnabledAndValidPackageInfosForUser(TestSystemImpl.PRIMARY_USER_ID, providers); } private void setEnabledAndValidPackageInfosForUser(int userId, WebViewProviderInfo[] providers) { for(WebViewProviderInfo wpi : providers) { mTestSystemImpl.setPackageInfoForUser(userId, createPackageInfo(wpi.packageName, true /* enabled */, true /* valid */, true /* installed */)); } } private void checkCertainPackageUsedAfterWebViewBootPreparation(String expectedProviderName, WebViewProviderInfo[] webviewPackages) { checkCertainPackageUsedAfterWebViewBootPreparation( expectedProviderName, webviewPackages, 1); } private void checkCertainPackageUsedAfterWebViewBootPreparation(String expectedProviderName, WebViewProviderInfo[] webviewPackages, int numRelros) { setupWithPackages(webviewPackages, true, numRelros); // Add (enabled and valid) package infos for each provider setEnabledAndValidPackageInfos(webviewPackages); runWebViewBootPreparationOnMainSync(); Mockito.verify(mTestSystemImpl).onWebViewProviderChanged( Mockito.argThat(new IsPackageInfoWithName(expectedProviderName))); for (int n = 0; n < numRelros; n++) { mWebViewUpdateServiceImpl.notifyRelroCreationCompleted(); } WebViewProviderResponse response = mWebViewUpdateServiceImpl.waitForAndGetProvider(); assertEquals(WebViewFactory.LIBLOAD_SUCCESS, response.status); assertEquals(expectedProviderName, response.packageInfo.packageName); } // For matching the package name of a PackageInfo private class IsPackageInfoWithName implements ArgumentMatcher { private final String mPackageName; IsPackageInfoWithName(String name) { mPackageName = name; } @Override public boolean matches(PackageInfo p) { return p.packageName.equals(mPackageName); } @Override public String toString() { return String.format("PackageInfo with name '%s'", mPackageName); } } private static PackageInfo createPackageInfo( String packageName, boolean enabled, boolean valid, boolean installed) { PackageInfo p = new PackageInfo(); p.packageName = packageName; p.applicationInfo = new ApplicationInfo(); p.applicationInfo.enabled = enabled; p.applicationInfo.metaData = new Bundle(); if (installed) { p.applicationInfo.flags |= ApplicationInfo.FLAG_INSTALLED; } else { p.applicationInfo.flags &= ~ApplicationInfo.FLAG_INSTALLED; } if (valid) { // no flag means invalid p.applicationInfo.metaData.putString(WEBVIEW_LIBRARY_FLAG, "blah"); } // Default to this package being valid in terms of targetSdkVersion. p.applicationInfo.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT; return p; } private static PackageInfo createPackageInfo(String packageName, boolean enabled, boolean valid, boolean installed, Signature[] signatures, long updateTime) { PackageInfo p = createPackageInfo(packageName, enabled, valid, installed); p.signatures = signatures; p.lastUpdateTime = updateTime; return p; } private static PackageInfo createPackageInfo(String packageName, boolean enabled, boolean valid, boolean installed, Signature[] signatures, long updateTime, boolean hidden) { PackageInfo p = createPackageInfo(packageName, enabled, valid, installed, signatures, updateTime); if (hidden) { p.applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_HIDDEN; } else { p.applicationInfo.privateFlags &= ~ApplicationInfo.PRIVATE_FLAG_HIDDEN; } return p; } private static PackageInfo createPackageInfo(String packageName, boolean enabled, boolean valid, boolean installed, Signature[] signatures, long updateTime, boolean hidden, long versionCode, boolean isSystemApp) { PackageInfo p = createPackageInfo(packageName, enabled, valid, installed, signatures, updateTime, hidden); p.setLongVersionCode(versionCode); p.applicationInfo.setVersionCode(versionCode); if (isSystemApp) p.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM; return p; } private void checkPreparationPhasesForPackage(String expectedPackage, int numPreparation) { // Verify that onWebViewProviderChanged was called for the numPreparation'th time for the // expected package Mockito.verify(mTestSystemImpl, Mockito.times(numPreparation)).onWebViewProviderChanged( Mockito.argThat(new IsPackageInfoWithName(expectedPackage))); mWebViewUpdateServiceImpl.notifyRelroCreationCompleted(); WebViewProviderResponse response = mWebViewUpdateServiceImpl.waitForAndGetProvider(); assertEquals(WebViewFactory.LIBLOAD_SUCCESS, response.status); assertEquals(expectedPackage, response.packageInfo.packageName); } /** * The WebView preparation boot phase is run on the main thread (especially on a thread with a * looper) so to avoid bugs where our tests fail because a looper hasn't been attached to the * thread running prepareWebViewInSystemServer we run it on the main thread. */ private void runWebViewBootPreparationOnMainSync() { InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() { @Override public void run() { mWebViewUpdateServiceImpl.prepareWebViewInSystemServer(); } }); } // **************** // Tests // **************** @Test public void testWithSinglePackage() { String testPackageName = "test.package.name"; checkCertainPackageUsedAfterWebViewBootPreparation(testPackageName, new WebViewProviderInfo[] { new WebViewProviderInfo(testPackageName, "", true /*default available*/, false /* fallback */, null)}); } @Test public void testDefaultPackageUsedOverNonDefault() { String defaultPackage = "defaultPackage"; String nonDefaultPackage = "nonDefaultPackage"; WebViewProviderInfo[] packages = new WebViewProviderInfo[] { new WebViewProviderInfo(nonDefaultPackage, "", false, false, null), new WebViewProviderInfo(defaultPackage, "", true, false, null)}; checkCertainPackageUsedAfterWebViewBootPreparation(defaultPackage, packages); } @Test public void testSeveralRelros() { String singlePackage = "singlePackage"; checkCertainPackageUsedAfterWebViewBootPreparation( singlePackage, new WebViewProviderInfo[] { new WebViewProviderInfo(singlePackage, "", true /*def av*/, false, null)}, 2); } // Ensure that package with valid signatures is chosen rather than package with invalid // signatures. @Test public void testWithSignatures() { String validPackage = "valid package"; String invalidPackage = "invalid package"; Signature validSignature = new Signature("11"); Signature invalidExpectedSignature = new Signature("22"); Signature invalidPackageSignature = new Signature("33"); WebViewProviderInfo[] packages = new WebViewProviderInfo[] { new WebViewProviderInfo(invalidPackage, "", true, false, new String[]{ Base64.encodeToString( invalidExpectedSignature.toByteArray(), Base64.DEFAULT)}), new WebViewProviderInfo(validPackage, "", true, false, new String[]{ Base64.encodeToString( validSignature.toByteArray(), Base64.DEFAULT)}) }; setupWithPackages(packages, true /* fallback logic enabled */, 1 /* numRelros */, false /* isDebuggable */); mTestSystemImpl.setPackageInfo(createPackageInfo(invalidPackage, true /* enabled */, true /* valid */, true /* installed */, new Signature[]{invalidPackageSignature} , 0 /* updateTime */)); mTestSystemImpl.setPackageInfo(createPackageInfo(validPackage, true /* enabled */, true /* valid */, true /* installed */, new Signature[]{validSignature} , 0 /* updateTime */)); runWebViewBootPreparationOnMainSync(); checkPreparationPhasesForPackage(validPackage, 1 /* first preparation for this package */); WebViewProviderInfo[] validPackages = mWebViewUpdateServiceImpl.getValidWebViewPackages(); assertEquals(1, validPackages.length); assertEquals(validPackage, validPackages[0].packageName); } @Test public void testFailWaitingForRelro() { WebViewProviderInfo[] packages = new WebViewProviderInfo[] { new WebViewProviderInfo("packagename", "", true, true, null)}; setupWithPackages(packages); setEnabledAndValidPackageInfos(packages); runWebViewBootPreparationOnMainSync(); Mockito.verify(mTestSystemImpl).onWebViewProviderChanged( Mockito.argThat(new IsPackageInfoWithName(packages[0].packageName))); // Never call notifyRelroCreation() WebViewProviderResponse response = mWebViewUpdateServiceImpl.waitForAndGetProvider(); assertEquals(WebViewFactory.LIBLOAD_FAILED_WAITING_FOR_RELRO, response.status); } @Test public void testFailListingEmptyWebviewPackages() { WebViewProviderInfo[] packages = new WebViewProviderInfo[0]; setupWithPackages(packages); setEnabledAndValidPackageInfos(packages); runWebViewBootPreparationOnMainSync(); Mockito.verify(mTestSystemImpl, Mockito.never()).onWebViewProviderChanged( Matchers.anyObject()); WebViewProviderResponse response = mWebViewUpdateServiceImpl.waitForAndGetProvider(); assertEquals(WebViewFactory.LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES, response.status); assertEquals(null, mWebViewUpdateServiceImpl.getCurrentWebViewPackage()); // Now install a package String singlePackage = "singlePackage"; packages = new WebViewProviderInfo[]{ new WebViewProviderInfo(singlePackage, "", true, false, null)}; setupWithPackages(packages); setEnabledAndValidPackageInfos(packages); mWebViewUpdateServiceImpl.packageStateChanged(singlePackage, WebViewUpdateService.PACKAGE_ADDED, TestSystemImpl.PRIMARY_USER_ID); checkPreparationPhasesForPackage(singlePackage, 1 /* number of finished preparations */); assertEquals(singlePackage, mWebViewUpdateServiceImpl.getCurrentWebViewPackage().packageName); // Remove the package again mTestSystemImpl.removePackageInfo(singlePackage); mWebViewUpdateServiceImpl.packageStateChanged(singlePackage, WebViewUpdateService.PACKAGE_ADDED, TestSystemImpl.PRIMARY_USER_ID); // Package removed - ensure our interface states that there is no package response = mWebViewUpdateServiceImpl.waitForAndGetProvider(); assertEquals(WebViewFactory.LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES, response.status); assertEquals(null, mWebViewUpdateServiceImpl.getCurrentWebViewPackage()); } @Test public void testFailListingInvalidWebviewPackage() { WebViewProviderInfo wpi = new WebViewProviderInfo("package", "", true, true, null); WebViewProviderInfo[] packages = new WebViewProviderInfo[] {wpi}; setupWithPackages(packages); mTestSystemImpl.setPackageInfo( createPackageInfo(wpi.packageName, true /* enabled */, false /* valid */, true /* installed */)); runWebViewBootPreparationOnMainSync(); Mockito.verify(mTestSystemImpl, Mockito.never()).onWebViewProviderChanged( Matchers.anyObject()); WebViewProviderResponse response = mWebViewUpdateServiceImpl.waitForAndGetProvider(); assertEquals(WebViewFactory.LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES, response.status); // Verify that we can recover from failing to list webview packages. mTestSystemImpl.setPackageInfo( createPackageInfo(wpi.packageName, true /* enabled */, true /* valid */, true /* installed */)); mWebViewUpdateServiceImpl.packageStateChanged(wpi.packageName, WebViewUpdateService.PACKAGE_ADDED_REPLACED, TestSystemImpl.PRIMARY_USER_ID); checkPreparationPhasesForPackage(wpi.packageName, 1); } // Test that switching provider using changeProviderAndSetting works. @Test public void testSwitchingProvider() { String firstPackage = "first"; String secondPackage = "second"; WebViewProviderInfo[] packages = new WebViewProviderInfo[] { new WebViewProviderInfo(firstPackage, "", true, false, null), new WebViewProviderInfo(secondPackage, "", true, false, null)}; checkSwitchingProvider(packages, firstPackage, secondPackage); } @Test public void testSwitchingProviderToNonDefault() { String defaultPackage = "defaultPackage"; String nonDefaultPackage = "nonDefaultPackage"; WebViewProviderInfo[] packages = new WebViewProviderInfo[] { new WebViewProviderInfo(defaultPackage, "", true, false, null), new WebViewProviderInfo(nonDefaultPackage, "", false, false, null)}; checkSwitchingProvider(packages, defaultPackage, nonDefaultPackage); } private void checkSwitchingProvider(WebViewProviderInfo[] packages, String initialPackage, String finalPackage) { checkCertainPackageUsedAfterWebViewBootPreparation(initialPackage, packages); mWebViewUpdateServiceImpl.changeProviderAndSetting(finalPackage); checkPreparationPhasesForPackage(finalPackage, 1 /* first preparation for this package */); Mockito.verify(mTestSystemImpl).killPackageDependents(Mockito.eq(initialPackage)); } // Change provider during relro creation by using changeProviderAndSetting @Test public void testSwitchingProviderDuringRelroCreation() { checkChangingProviderDuringRelroCreation(true); } // Change provider during relro creation by enabling a provider @Test public void testChangingProviderThroughEnablingDuringRelroCreation() { checkChangingProviderDuringRelroCreation(false); } private void checkChangingProviderDuringRelroCreation(boolean settingsChange) { String firstPackage = "first"; String secondPackage = "second"; WebViewProviderInfo[] packages = new WebViewProviderInfo[] { new WebViewProviderInfo(firstPackage, "", true, false, null), new WebViewProviderInfo(secondPackage, "", true, false, null)}; setupWithPackages(packages); // Have all packages be enabled, so that we can change provider however we want to setEnabledAndValidPackageInfos(packages); CountDownLatch countdown = new CountDownLatch(1); runWebViewBootPreparationOnMainSync(); Mockito.verify(mTestSystemImpl).onWebViewProviderChanged( Mockito.argThat(new IsPackageInfoWithName(firstPackage))); assertEquals(firstPackage, mWebViewUpdateServiceImpl.getCurrentWebViewPackage().packageName); new Thread(new Runnable() { @Override public void run() { WebViewProviderResponse threadResponse = mWebViewUpdateServiceImpl.waitForAndGetProvider(); assertEquals(WebViewFactory.LIBLOAD_SUCCESS, threadResponse.status); assertEquals(secondPackage, threadResponse.packageInfo.packageName); // Verify that we killed the first package if we performed a settings change - // otherwise we had to disable the first package, in which case its dependents // should have been killed by the framework. if (settingsChange) { Mockito.verify(mTestSystemImpl).killPackageDependents(Mockito.eq(firstPackage)); } countdown.countDown(); } }).start(); try { Thread.sleep(500); // Let the new thread run / be blocked } catch (InterruptedException e) { } if (settingsChange) { mWebViewUpdateServiceImpl.changeProviderAndSetting(secondPackage); } else { // Enable the second provider mTestSystemImpl.setPackageInfo(createPackageInfo(secondPackage, true /* enabled */, true /* valid */, true /* installed */)); mWebViewUpdateServiceImpl.packageStateChanged( secondPackage, WebViewUpdateService.PACKAGE_CHANGED, TestSystemImpl.PRIMARY_USER_ID); // Ensure we haven't changed package yet. assertEquals(firstPackage, mWebViewUpdateServiceImpl.getCurrentWebViewPackage().packageName); // Switch provider by disabling the first one mTestSystemImpl.setPackageInfo(createPackageInfo(firstPackage, false /* enabled */, true /* valid */, true /* installed */)); mWebViewUpdateServiceImpl.packageStateChanged( firstPackage, WebViewUpdateService.PACKAGE_CHANGED, TestSystemImpl.PRIMARY_USER_ID); } mWebViewUpdateServiceImpl.notifyRelroCreationCompleted(); // first package done, should start on second Mockito.verify(mTestSystemImpl).onWebViewProviderChanged( Mockito.argThat(new IsPackageInfoWithName(secondPackage))); mWebViewUpdateServiceImpl.notifyRelroCreationCompleted(); // second package done, the other thread should now be unblocked try { countdown.await(); } catch (InterruptedException e) { } } @Test public void testRunFallbackLogicIfEnabled() { checkFallbackLogicBeingRun(true); } @Test public void testDontRunFallbackLogicIfDisabled() { checkFallbackLogicBeingRun(false); } private void checkFallbackLogicBeingRun(boolean fallbackLogicEnabled) { String primaryPackage = "primary"; String fallbackPackage = "fallback"; WebViewProviderInfo[] packages = new WebViewProviderInfo[] { new WebViewProviderInfo( primaryPackage, "", true /* default available */, false /* fallback */, null), new WebViewProviderInfo( fallbackPackage, "", true /* default available */, true /* fallback */, null)}; setupWithPackages(packages, fallbackLogicEnabled); setEnabledAndValidPackageInfos(packages); runWebViewBootPreparationOnMainSync(); // Verify that we disable the fallback package if fallback logic enabled, and don't disable // the fallback package if that logic is disabled if (fallbackLogicEnabled) { Mockito.verify(mTestSystemImpl).uninstallAndDisablePackageForAllUsers( Matchers.anyObject(), Mockito.eq(fallbackPackage)); } else { Mockito.verify(mTestSystemImpl, Mockito.never()).uninstallAndDisablePackageForAllUsers( Matchers.anyObject(), Matchers.anyObject()); } Mockito.verify(mTestSystemImpl).onWebViewProviderChanged( Mockito.argThat(new IsPackageInfoWithName(primaryPackage))); // Enable fallback package mTestSystemImpl.setPackageInfo(createPackageInfo(fallbackPackage, true /* enabled */, true /* valid */, true /* installed */)); mWebViewUpdateServiceImpl.packageStateChanged( fallbackPackage, WebViewUpdateService.PACKAGE_CHANGED, TestSystemImpl.PRIMARY_USER_ID); if (fallbackLogicEnabled) { // Check that we have now disabled the fallback package twice Mockito.verify(mTestSystemImpl, Mockito.times(2)).uninstallAndDisablePackageForAllUsers( Matchers.anyObject(), Mockito.eq(fallbackPackage)); } else { // Check that we still haven't disabled any package Mockito.verify(mTestSystemImpl, Mockito.never()).uninstallAndDisablePackageForAllUsers( Matchers.anyObject(), Matchers.anyObject()); } } /** * Scenario for installing primary package when fallback enabled. * 1. Start with only fallback installed * 2. Install non-fallback * 3. Fallback should be disabled */ @Test public void testInstallingNonFallbackPackage() { String primaryPackage = "primary"; String fallbackPackage = "fallback"; WebViewProviderInfo[] packages = new WebViewProviderInfo[] { new WebViewProviderInfo( primaryPackage, "", true /* default available */, false /* fallback */, null), new WebViewProviderInfo( fallbackPackage, "", true /* default available */, true /* fallback */, null)}; setupWithPackages(packages, true /* isFallbackLogicEnabled */); mTestSystemImpl.setPackageInfo( createPackageInfo(fallbackPackage, true /* enabled */ , true /* valid */, true /* installed */)); runWebViewBootPreparationOnMainSync(); Mockito.verify(mTestSystemImpl, Mockito.never()).uninstallAndDisablePackageForAllUsers( Matchers.anyObject(), Matchers.anyObject()); checkPreparationPhasesForPackage(fallbackPackage, 1 /* first preparation for this package*/); // Install primary package mTestSystemImpl.setPackageInfo( createPackageInfo(primaryPackage, true /* enabled */ , true /* valid */, true /* installed */)); mWebViewUpdateServiceImpl.packageStateChanged(primaryPackage, WebViewUpdateService.PACKAGE_ADDED_REPLACED, TestSystemImpl.PRIMARY_USER_ID); // Verify fallback disabled, primary package used as provider, and fallback package killed Mockito.verify(mTestSystemImpl).uninstallAndDisablePackageForAllUsers( Matchers.anyObject(), Mockito.eq(fallbackPackage)); checkPreparationPhasesForPackage(primaryPackage, 1 /* first preparation for this package*/); Mockito.verify(mTestSystemImpl).killPackageDependents(Mockito.eq(fallbackPackage)); } @Test public void testFallbackChangesEnabledStateSingleUser() { for (PackageRemovalType removalType : REMOVAL_TYPES) { checkFallbackChangesEnabledState(false /* multiUser */, removalType); } } @Test public void testFallbackChangesEnabledStateMultiUser() { for (PackageRemovalType removalType : REMOVAL_TYPES) { checkFallbackChangesEnabledState(true /* multiUser */, removalType); } } /** * Represents how to remove a package during a tests (disabling it / uninstalling it / hiding * it). */ private enum PackageRemovalType { UNINSTALL, DISABLE, HIDE } private PackageRemovalType[] REMOVAL_TYPES = PackageRemovalType.class.getEnumConstants(); public void checkFallbackChangesEnabledState(boolean multiUser, PackageRemovalType removalType) { String primaryPackage = "primary"; String fallbackPackage = "fallback"; WebViewProviderInfo[] packages = new WebViewProviderInfo[] { new WebViewProviderInfo( primaryPackage, "", true /* default available */, false /* fallback */, null), new WebViewProviderInfo( fallbackPackage, "", true /* default available */, true /* fallback */, null)}; setupWithPackages(packages, true /* fallbackLogicEnabled */); int secondaryUserId = 10; int userIdToChangePackageFor = multiUser ? secondaryUserId : TestSystemImpl.PRIMARY_USER_ID; if (multiUser) { mTestSystemImpl.addUser(secondaryUserId); setEnabledAndValidPackageInfosForUser(secondaryUserId, packages); } setEnabledAndValidPackageInfosForUser(TestSystemImpl.PRIMARY_USER_ID, packages); runWebViewBootPreparationOnMainSync(); // Verify fallback disabled at boot when primary package enabled checkEnablePackageForUserCalled(fallbackPackage, false, multiUser ? new int[] {TestSystemImpl.PRIMARY_USER_ID, secondaryUserId} : new int[] {TestSystemImpl.PRIMARY_USER_ID}, 1 /* numUsages */); checkPreparationPhasesForPackage(primaryPackage, 1); boolean enabled = !(removalType == PackageRemovalType.DISABLE); boolean installed = !(removalType == PackageRemovalType.UNINSTALL); boolean hidden = (removalType == PackageRemovalType.HIDE); // Disable primary package and ensure fallback becomes enabled and used mTestSystemImpl.setPackageInfoForUser(userIdToChangePackageFor, createPackageInfo(primaryPackage, enabled /* enabled */, true /* valid */, installed /* installed */, null /* signature */, 0 /* updateTime */, hidden /* hidden */)); mWebViewUpdateServiceImpl.packageStateChanged(primaryPackage, removalType == PackageRemovalType.DISABLE ? WebViewUpdateService.PACKAGE_CHANGED : WebViewUpdateService.PACKAGE_REMOVED, userIdToChangePackageFor); // USER ID checkEnablePackageForUserCalled(fallbackPackage, true, multiUser ? new int[] {TestSystemImpl.PRIMARY_USER_ID, secondaryUserId} : new int[] {TestSystemImpl.PRIMARY_USER_ID}, 1 /* numUsages */); checkPreparationPhasesForPackage(fallbackPackage, 1); // Again enable primary package and verify primary is used and fallback becomes disabled mTestSystemImpl.setPackageInfoForUser(userIdToChangePackageFor, createPackageInfo(primaryPackage, true /* enabled */, true /* valid */, true /* installed */)); mWebViewUpdateServiceImpl.packageStateChanged(primaryPackage, removalType == PackageRemovalType.DISABLE ? WebViewUpdateService.PACKAGE_CHANGED : WebViewUpdateService.PACKAGE_ADDED, userIdToChangePackageFor); // Verify fallback is disabled a second time when primary package becomes enabled checkEnablePackageForUserCalled(fallbackPackage, false, multiUser ? new int[] {TestSystemImpl.PRIMARY_USER_ID, secondaryUserId} : new int[] {TestSystemImpl.PRIMARY_USER_ID}, 2 /* numUsages */); checkPreparationPhasesForPackage(primaryPackage, 2); } private void checkEnablePackageForUserCalled(String packageName, boolean expectEnabled, int[] userIds, int numUsages) { for (int userId : userIds) { Mockito.verify(mTestSystemImpl, Mockito.times(numUsages)).enablePackageForUser( Mockito.eq(packageName), Mockito.eq(expectEnabled), Mockito.eq(userId)); } } @Test public void testAddUserWhenFallbackLogicEnabled() { checkAddingNewUser(true); } @Test public void testAddUserWhenFallbackLogicDisabled() { checkAddingNewUser(false); } public void checkAddingNewUser(boolean fallbackLogicEnabled) { String primaryPackage = "primary"; String fallbackPackage = "fallback"; WebViewProviderInfo[] packages = new WebViewProviderInfo[] { new WebViewProviderInfo( primaryPackage, "", true /* default available */, false /* fallback */, null), new WebViewProviderInfo( fallbackPackage, "", true /* default available */, true /* fallback */, null)}; setupWithPackages(packages, fallbackLogicEnabled); setEnabledAndValidPackageInfosForUser(TestSystemImpl.PRIMARY_USER_ID, packages); int newUser = 100; mTestSystemImpl.addUser(newUser); setEnabledAndValidPackageInfosForUser(newUser, packages); mWebViewUpdateServiceImpl.handleNewUser(newUser); if (fallbackLogicEnabled) { // Verify fallback package becomes disabled for new user Mockito.verify(mTestSystemImpl).enablePackageForUser( Mockito.eq(fallbackPackage), Mockito.eq(false) /* enable */, Mockito.eq(newUser)); } else { // Verify that we don't disable fallback for new user Mockito.verify(mTestSystemImpl, Mockito.never()).enablePackageForUser( Mockito.anyObject(), Matchers.anyBoolean() /* enable */, Matchers.anyInt() /* user */); } } /** * Ensures that adding a new user for which the current WebView package is uninstalled causes a * change of WebView provider. */ @Test public void testAddingNewUserWithUninstalledPackage() { String primaryPackage = "primary"; String fallbackPackage = "fallback"; WebViewProviderInfo[] packages = new WebViewProviderInfo[] { new WebViewProviderInfo( primaryPackage, "", true /* default available */, false /* fallback */, null), new WebViewProviderInfo( fallbackPackage, "", true /* default available */, true /* fallback */, null)}; setupWithPackages(packages, true /* fallbackLogicEnabled */); setEnabledAndValidPackageInfosForUser(TestSystemImpl.PRIMARY_USER_ID, packages); int newUser = 100; mTestSystemImpl.addUser(newUser); // Let the primary package be uninstalled for the new user mTestSystemImpl.setPackageInfoForUser(newUser, createPackageInfo(primaryPackage, true /* enabled */, true /* valid */, false /* installed */)); mTestSystemImpl.setPackageInfoForUser(newUser, createPackageInfo(fallbackPackage, false /* enabled */, true /* valid */, true /* installed */)); mWebViewUpdateServiceImpl.handleNewUser(newUser); // Verify fallback package doesn't become disabled for the primary user. Mockito.verify(mTestSystemImpl, Mockito.never()).enablePackageForUser( Mockito.anyObject(), Mockito.eq(false) /* enable */, Mockito.eq(TestSystemImpl.PRIMARY_USER_ID) /* user */); // Verify that we enable the fallback package for the secondary user. Mockito.verify(mTestSystemImpl, Mockito.times(1)).enablePackageForUser( Mockito.eq(fallbackPackage), Mockito.eq(true) /* enable */, Mockito.eq(newUser) /* user */); checkPreparationPhasesForPackage(fallbackPackage, 1 /* numRelros */); } /** * Timing dependent test where we verify that the list of valid webview packages becoming empty * at a certain point doesn't crash us or break our state. */ @Test public void testNotifyRelroDoesntCrashIfNoPackages() { String firstPackage = "first"; String secondPackage = "second"; WebViewProviderInfo[] packages = new WebViewProviderInfo[] { new WebViewProviderInfo(firstPackage, "", true /* default available */, false /* fallback */, null), new WebViewProviderInfo(secondPackage, "", true /* default available */, false /* fallback */, null)}; setupWithPackages(packages); // Add (enabled and valid) package infos for each provider setEnabledAndValidPackageInfos(packages); runWebViewBootPreparationOnMainSync(); Mockito.verify(mTestSystemImpl).onWebViewProviderChanged( Mockito.argThat(new IsPackageInfoWithName(firstPackage))); // Change provider during relro creation to enter a state where we are // waiting for relro creation to complete just to re-run relro creation. // (so that in next notifyRelroCreationCompleted() call we have to list webview packages) mWebViewUpdateServiceImpl.changeProviderAndSetting(secondPackage); // Make packages invalid to cause exception to be thrown mTestSystemImpl.setPackageInfo(createPackageInfo(firstPackage, true /* enabled */, false /* valid */, true /* installed */, null /* signatures */, 0 /* updateTime */)); mTestSystemImpl.setPackageInfo(createPackageInfo(secondPackage, true /* enabled */, false /* valid */, true /* installed */)); // This shouldn't throw an exception! mWebViewUpdateServiceImpl.notifyRelroCreationCompleted(); WebViewProviderResponse response = mWebViewUpdateServiceImpl.waitForAndGetProvider(); assertEquals(WebViewFactory.LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES, response.status); // Now make a package valid again and verify that we can switch back to that mTestSystemImpl.setPackageInfo(createPackageInfo(firstPackage, true /* enabled */, true /* valid */, true /* installed */, null /* signatures */, 1 /* updateTime */ )); mWebViewUpdateServiceImpl.packageStateChanged(firstPackage, WebViewUpdateService.PACKAGE_ADDED_REPLACED, TestSystemImpl.PRIMARY_USER_ID); // Ensure we use firstPackage checkPreparationPhasesForPackage(firstPackage, 2 /* second preparation for this package */); } /** * Verify that even if a user-chosen package is removed temporarily we start using it again when * it is added back. */ @Test public void testTempRemovePackageDoesntSwitchProviderPermanently() { String firstPackage = "first"; String secondPackage = "second"; WebViewProviderInfo[] packages = new WebViewProviderInfo[] { new WebViewProviderInfo(firstPackage, "", true /* default available */, false /* fallback */, null), new WebViewProviderInfo(secondPackage, "", true /* default available */, false /* fallback */, null)}; checkCertainPackageUsedAfterWebViewBootPreparation(firstPackage, packages); // Explicitly use the second package mWebViewUpdateServiceImpl.changeProviderAndSetting(secondPackage); checkPreparationPhasesForPackage(secondPackage, 1 /* first time for this package */); // Remove second package (invalidate it) and verify that first package is used mTestSystemImpl.setPackageInfo(createPackageInfo(secondPackage, true /* enabled */, false /* valid */, true /* installed */)); mWebViewUpdateServiceImpl.packageStateChanged(secondPackage, WebViewUpdateService.PACKAGE_ADDED, TestSystemImpl.PRIMARY_USER_ID); checkPreparationPhasesForPackage(firstPackage, 2 /* second time for this package */); // Now make the second package valid again and verify that it is used again mTestSystemImpl.setPackageInfo(createPackageInfo(secondPackage, true /* enabled */, true /* valid */, true /* installed */)); mWebViewUpdateServiceImpl.packageStateChanged(secondPackage, WebViewUpdateService.PACKAGE_ADDED, TestSystemImpl.PRIMARY_USER_ID); checkPreparationPhasesForPackage(secondPackage, 2 /* second time for this package */); } /** * Ensure that we update the user-chosen setting across boots if the chosen package is no * longer installed and valid. */ @Test public void testProviderSettingChangedDuringBootIfProviderNotAvailable() { String chosenPackage = "chosenPackage"; String nonChosenPackage = "non-chosenPackage"; WebViewProviderInfo[] packages = new WebViewProviderInfo[] { new WebViewProviderInfo(chosenPackage, "", true /* default available */, false /* fallback */, null), new WebViewProviderInfo(nonChosenPackage, "", true /* default available */, false /* fallback */, null)}; setupWithPackages(packages); // Only 'install' nonChosenPackage mTestSystemImpl.setPackageInfo( createPackageInfo(nonChosenPackage, true /* enabled */, true /* valid */, true /* installed */)); // Set user-chosen package mTestSystemImpl.updateUserSetting(null, chosenPackage); runWebViewBootPreparationOnMainSync(); // Verify that we switch the setting to point to the current package Mockito.verify(mTestSystemImpl).updateUserSetting( Mockito.anyObject(), Mockito.eq(nonChosenPackage)); assertEquals(nonChosenPackage, mTestSystemImpl.getUserChosenWebViewProvider(null)); checkPreparationPhasesForPackage(nonChosenPackage, 1); } @Test public void testRecoverFailedListingWebViewPackagesSettingsChange() { checkRecoverAfterFailListingWebviewPackages(true); } @Test public void testRecoverFailedListingWebViewPackagesAddedPackage() { checkRecoverAfterFailListingWebviewPackages(false); } /** * Test that we can recover correctly from failing to list WebView packages. * settingsChange: whether to fail during changeProviderAndSetting or packageStateChanged */ public void checkRecoverAfterFailListingWebviewPackages(boolean settingsChange) { String firstPackage = "first"; String secondPackage = "second"; WebViewProviderInfo[] packages = new WebViewProviderInfo[] { new WebViewProviderInfo(firstPackage, "", true /* default available */, false /* fallback */, null), new WebViewProviderInfo(secondPackage, "", true /* default available */, false /* fallback */, null)}; checkCertainPackageUsedAfterWebViewBootPreparation(firstPackage, packages); // Make both packages invalid so that we fail listing WebView packages mTestSystemImpl.setPackageInfo(createPackageInfo(firstPackage, true /* enabled */, false /* valid */, true /* installed */)); mTestSystemImpl.setPackageInfo(createPackageInfo(secondPackage, true /* enabled */, false /* valid */, true /* installed */)); // Change package to hit the webview packages listing problem. if (settingsChange) { mWebViewUpdateServiceImpl.changeProviderAndSetting(secondPackage); } else { mWebViewUpdateServiceImpl.packageStateChanged(secondPackage, WebViewUpdateService.PACKAGE_ADDED_REPLACED, TestSystemImpl.PRIMARY_USER_ID); } WebViewProviderResponse response = mWebViewUpdateServiceImpl.waitForAndGetProvider(); assertEquals(WebViewFactory.LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES, response.status); // Make second package valid and verify that we can load it again mTestSystemImpl.setPackageInfo(createPackageInfo(secondPackage, true /* enabled */, true /* valid */, true /* installed */)); mWebViewUpdateServiceImpl.packageStateChanged(secondPackage, WebViewUpdateService.PACKAGE_ADDED_REPLACED, TestSystemImpl.PRIMARY_USER_ID); checkPreparationPhasesForPackage(secondPackage, 1); } @Test public void testDontKillIfPackageReplaced() { checkDontKillIfPackageRemoved(true); } @Test public void testDontKillIfPackageRemoved() { checkDontKillIfPackageRemoved(false); } public void checkDontKillIfPackageRemoved(boolean replaced) { String firstPackage = "first"; String secondPackage = "second"; WebViewProviderInfo[] packages = new WebViewProviderInfo[] { new WebViewProviderInfo(firstPackage, "", true /* default available */, false /* fallback */, null), new WebViewProviderInfo(secondPackage, "", true /* default available */, false /* fallback */, null)}; checkCertainPackageUsedAfterWebViewBootPreparation(firstPackage, packages); // Replace or remove the current webview package if (replaced) { mTestSystemImpl.setPackageInfo( createPackageInfo(firstPackage, true /* enabled */, false /* valid */, true /* installed */)); mWebViewUpdateServiceImpl.packageStateChanged(firstPackage, WebViewUpdateService.PACKAGE_ADDED_REPLACED, TestSystemImpl.PRIMARY_USER_ID); } else { mTestSystemImpl.removePackageInfo(firstPackage); mWebViewUpdateServiceImpl.packageStateChanged(firstPackage, WebViewUpdateService.PACKAGE_REMOVED, TestSystemImpl.PRIMARY_USER_ID); } checkPreparationPhasesForPackage(secondPackage, 1); Mockito.verify(mTestSystemImpl, Mockito.never()).killPackageDependents( Mockito.anyObject()); } @Test public void testKillIfSettingChanged() { String firstPackage = "first"; String secondPackage = "second"; WebViewProviderInfo[] packages = new WebViewProviderInfo[] { new WebViewProviderInfo(firstPackage, "", true /* default available */, false /* fallback */, null), new WebViewProviderInfo(secondPackage, "", true /* default available */, false /* fallback */, null)}; checkCertainPackageUsedAfterWebViewBootPreparation(firstPackage, packages); mWebViewUpdateServiceImpl.changeProviderAndSetting(secondPackage); checkPreparationPhasesForPackage(secondPackage, 1); Mockito.verify(mTestSystemImpl).killPackageDependents(Mockito.eq(firstPackage)); } /** * Test that we kill apps using an old provider when we change the provider setting, even if the * new provider is not the one we intended to change to. */ @Test public void testKillIfChangeProviderIncorrectly() { String firstPackage = "first"; String secondPackage = "second"; String thirdPackage = "third"; WebViewProviderInfo[] packages = new WebViewProviderInfo[] { new WebViewProviderInfo(firstPackage, "", true /* default available */, false /* fallback */, null), new WebViewProviderInfo(secondPackage, "", true /* default available */, false /* fallback */, null), new WebViewProviderInfo(thirdPackage, "", true /* default available */, false /* fallback */, null)}; setupWithPackages(packages); setEnabledAndValidPackageInfos(packages); // Start with the setting pointing to the third package mTestSystemImpl.updateUserSetting(null, thirdPackage); runWebViewBootPreparationOnMainSync(); checkPreparationPhasesForPackage(thirdPackage, 1); mTestSystemImpl.setPackageInfo( createPackageInfo(secondPackage, true /* enabled */, false /* valid */, true /* installed */)); // Try to switch to the invalid second package, this should result in switching to the first // package, since that is more preferred than the third one. assertEquals(firstPackage, mWebViewUpdateServiceImpl.changeProviderAndSetting(secondPackage)); checkPreparationPhasesForPackage(firstPackage, 1); Mockito.verify(mTestSystemImpl).killPackageDependents(Mockito.eq(thirdPackage)); } @Test public void testLowerPackageVersionNotValid() { checkPackageVersions(new int[]{200000} /* system version */, 100000/* candidate version */, false /* expected validity */); } @Test public void testEqualPackageVersionValid() { checkPackageVersions(new int[]{100000} /* system version */, 100000 /* candidate version */, true /* expected validity */); } @Test public void testGreaterPackageVersionValid() { checkPackageVersions(new int[]{100000} /* system versions */, 200000 /* candidate version */, true /* expected validity */); } @Test public void testLastFiveDigitsIgnored() { checkPackageVersions(new int[]{654321} /* system version */, 612345 /* candidate version */, true /* expected validity */); } @Test public void testMinimumSystemVersionUsedTwoDefaultsCandidateValid() { checkPackageVersions(new int[]{300000, 100000} /* system versions */, 200000 /* candidate version */, true /* expected validity */); } @Test public void testMinimumSystemVersionUsedTwoDefaultsCandidateInvalid() { checkPackageVersions(new int[]{300000, 200000} /* system versions */, 100000 /* candidate version */, false /* expected validity */); } @Test public void testMinimumSystemVersionUsedSeveralDefaultsCandidateValid() { checkPackageVersions(new int[]{100000, 200000, 300000, 400000, 500000} /* system versions */, 100000 /* candidate version */, true /* expected validity */); } @Test public void testMinimumSystemVersionUsedSeveralDefaultsCandidateInvalid() { checkPackageVersions(new int[]{200000, 300000, 400000, 500000, 600000} /* system versions */, 100000 /* candidate version */, false /* expected validity */); } @Test public void testMinimumSystemVersionUsedFallbackIgnored() { checkPackageVersions(new int[]{300000, 400000, 500000, 600000, 700000} /* system versions */, 200000 /* candidate version */, false /* expected validity */, true /* add fallback */, 100000 /* fallback version */, false /* expected validity of fallback */); } @Test public void testFallbackValid() { checkPackageVersions(new int[]{300000, 400000, 500000, 600000, 700000} /* system versions */, 200000/* candidate version */, false /* expected validity */, true /* add fallback */, 300000 /* fallback version */, true /* expected validity of fallback */); } private void checkPackageVersions(int[] systemVersions, int candidateVersion, boolean candidateShouldBeValid) { checkPackageVersions(systemVersions, candidateVersion, candidateShouldBeValid, false, 0, false); } /** * Utility method for checking that package version restriction works as it should. * I.e. that a package with lower version than the system-default is not valid and that a * package with greater than or equal version code is considered valid. */ private void checkPackageVersions(int[] systemVersions, int candidateVersion, boolean candidateShouldBeValid, boolean addFallback, int fallbackVersion, boolean fallbackShouldBeValid) { int numSystemPackages = systemVersions.length; int numFallbackPackages = (addFallback ? 1 : 0); int numPackages = systemVersions.length + 1 + numFallbackPackages; String candidatePackage = "candidatePackage"; String systemPackage = "systemPackage"; String fallbackPackage = "fallbackPackage"; // Each package needs a valid signature since we set isDebuggable to false Signature signature = new Signature("11"); String encodedSignatureString = Base64.encodeToString(signature.toByteArray(), Base64.DEFAULT); // Set up config // 1. candidatePackage // 2-N. default available non-fallback packages // N+1. default available fallback package WebViewProviderInfo[] packages = new WebViewProviderInfo[numPackages]; packages[0] = new WebViewProviderInfo(candidatePackage, "", false /* available by default */, false /* fallback */, new String[]{encodedSignatureString}); for(int n = 1; n < numSystemPackages + 1; n++) { packages[n] = new WebViewProviderInfo(systemPackage + n, "", true /* available by default */, false /* fallback */, new String[]{encodedSignatureString}); } if (addFallback) { packages[packages.length-1] = new WebViewProviderInfo(fallbackPackage, "", true /* available by default */, true /* fallback */, new String[]{encodedSignatureString}); } setupWithPackages(packages, true /* fallback logic enabled */, 1 /* numRelros */, false /* isDebuggable */); // Set package infos mTestSystemImpl.setPackageInfo( createPackageInfo(candidatePackage, true /* enabled */, true /* valid */, true /* installed */, new Signature[]{signature}, 0 /* updateTime */, false /* hidden */, candidateVersion, false /* isSystemApp */)); for(int n = 1; n < numSystemPackages + 1; n++) { mTestSystemImpl.setPackageInfo( createPackageInfo(systemPackage + n, true /* enabled */, true /* valid */, true /* installed */, new Signature[]{signature}, 0 /* updateTime */, false /* hidden */, systemVersions[n-1], true /* isSystemApp */)); } if (addFallback) { mTestSystemImpl.setPackageInfo( createPackageInfo(fallbackPackage, true /* enabled */, true /* valid */, true /* installed */, new Signature[]{signature}, 0 /* updateTime */, false /* hidden */, fallbackVersion, true /* isSystemApp */)); } WebViewProviderInfo[] validPackages = mWebViewUpdateServiceImpl.getValidWebViewPackages(); int expectedNumValidPackages = numSystemPackages; if (candidateShouldBeValid) { expectedNumValidPackages++; } else { // Ensure the candidate package is not one of the valid packages for(int n = 0; n < validPackages.length; n++) { assertFalse(candidatePackage.equals(validPackages[n].packageName)); } } if (fallbackShouldBeValid) { expectedNumValidPackages += numFallbackPackages; } else { // Ensure the fallback package is not one of the valid packages for(int n = 0; n < validPackages.length; n++) { assertFalse(fallbackPackage.equals(validPackages[n].packageName)); } } assertEquals(expectedNumValidPackages, validPackages.length); runWebViewBootPreparationOnMainSync(); // The non-system package is not available by default so it shouldn't be used here checkPreparationPhasesForPackage(systemPackage + "1", 1); // Try explicitly switching to the candidate package String packageChange = mWebViewUpdateServiceImpl.changeProviderAndSetting(candidatePackage); if (candidateShouldBeValid) { assertEquals(candidatePackage, packageChange); checkPreparationPhasesForPackage(candidatePackage, 1); } else { assertEquals(systemPackage + "1", packageChange); // We didn't change package so the webview preparation won't run here } } /** * Ensure that the update service does use an uninstalled package when that is the only * package available. */ @Test public void testWithSingleUninstalledPackage() { String testPackageName = "test.package.name"; WebViewProviderInfo[] webviewPackages = new WebViewProviderInfo[] { new WebViewProviderInfo(testPackageName, "", true /*default available*/, false /* fallback */, null)}; setupWithPackages(webviewPackages, true /* fallback logic enabled */, 1 /* numRelros */); mTestSystemImpl.setPackageInfo(createPackageInfo(testPackageName, true /* enabled */, true /* valid */, false /* installed */)); runWebViewBootPreparationOnMainSync(); Mockito.verify(mTestSystemImpl, Mockito.never()).onWebViewProviderChanged( Matchers.anyObject()); WebViewProviderResponse response = mWebViewUpdateServiceImpl.waitForAndGetProvider(); assertEquals(WebViewFactory.LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES, response.status); assertEquals(null, mWebViewUpdateServiceImpl.getCurrentWebViewPackage()); } @Test public void testNonhiddenPackageUserOverHidden() { checkVisiblePackageUserOverNonVisible(false /* multiUser*/, PackageRemovalType.HIDE); checkVisiblePackageUserOverNonVisible(true /* multiUser*/, PackageRemovalType.HIDE); } @Test public void testInstalledPackageUsedOverUninstalled() { checkVisiblePackageUserOverNonVisible(false /* multiUser*/, PackageRemovalType.UNINSTALL); checkVisiblePackageUserOverNonVisible(true /* multiUser*/, PackageRemovalType.UNINSTALL); } private void checkVisiblePackageUserOverNonVisible(boolean multiUser, PackageRemovalType removalType) { assert removalType != PackageRemovalType.DISABLE; boolean testUninstalled = removalType == PackageRemovalType.UNINSTALL; boolean testHidden = removalType == PackageRemovalType.HIDE; String installedPackage = "installedPackage"; String uninstalledPackage = "uninstalledPackage"; WebViewProviderInfo[] webviewPackages = new WebViewProviderInfo[] { new WebViewProviderInfo(uninstalledPackage, "", true /* available by default */, false /* fallback */, null), new WebViewProviderInfo(installedPackage, "", true /* available by default */, false /* fallback */, null)}; setupWithPackages(webviewPackages, true /* fallback logic enabled */, 1 /* numRelros */); int secondaryUserId = 5; if (multiUser) { mTestSystemImpl.addUser(secondaryUserId); // Install all packages for the primary user. setEnabledAndValidPackageInfosForUser(TestSystemImpl.PRIMARY_USER_ID, webviewPackages); mTestSystemImpl.setPackageInfoForUser(secondaryUserId, createPackageInfo( installedPackage, true /* enabled */, true /* valid */, true /* installed */)); // Hide or uninstall the primary package for the second user mTestSystemImpl.setPackageInfo(createPackageInfo(uninstalledPackage, true /* enabled */, true /* valid */, (testUninstalled ? false : true) /* installed */, null /* signatures */, 0 /* updateTime */, (testHidden ? true : false))); } else { mTestSystemImpl.setPackageInfo(createPackageInfo(installedPackage, true /* enabled */, true /* valid */, true /* installed */)); // Hide or uninstall the primary package mTestSystemImpl.setPackageInfo(createPackageInfo(uninstalledPackage, true /* enabled */, true /* valid */, (testUninstalled ? false : true) /* installed */, null /* signatures */, 0 /* updateTime */, (testHidden ? true : false))); } runWebViewBootPreparationOnMainSync(); checkPreparationPhasesForPackage(installedPackage, 1 /* first preparation phase */); } @Test public void testCantSwitchToHiddenPackage () { checkCantSwitchToNonVisiblePackage(false /* true == uninstalled, false == hidden */); } @Test public void testCantSwitchToUninstalledPackage () { checkCantSwitchToNonVisiblePackage(true /* true == uninstalled, false == hidden */); } /** * Ensure that we won't prioritize an uninstalled (or hidden) package even if it is user-chosen. */ private void checkCantSwitchToNonVisiblePackage(boolean uninstalledNotHidden) { boolean testUninstalled = uninstalledNotHidden; boolean testHidden = !uninstalledNotHidden; String installedPackage = "installedPackage"; String uninstalledPackage = "uninstalledPackage"; WebViewProviderInfo[] webviewPackages = new WebViewProviderInfo[] { new WebViewProviderInfo(uninstalledPackage, "", true /* available by default */, false /* fallback */, null), new WebViewProviderInfo(installedPackage, "", true /* available by default */, false /* fallback */, null)}; setupWithPackages(webviewPackages, true /* fallback logic enabled */, 1 /* numRelros */); int secondaryUserId = 412; mTestSystemImpl.addUser(secondaryUserId); // Let all packages be installed and enabled for the primary user. setEnabledAndValidPackageInfosForUser(TestSystemImpl.PRIMARY_USER_ID, webviewPackages); // Only uninstall the 'uninstalled package' for the secondary user. mTestSystemImpl.setPackageInfoForUser(secondaryUserId, createPackageInfo(installedPackage, true /* enabled */, true /* valid */, true /* installed */)); mTestSystemImpl.setPackageInfoForUser(secondaryUserId, createPackageInfo(uninstalledPackage, true /* enabled */, true /* valid */, !testUninstalled /* installed */, null /* signatures */, 0 /* updateTime */, testHidden /* hidden */)); runWebViewBootPreparationOnMainSync(); checkPreparationPhasesForPackage(installedPackage, 1 /* first preparation phase */); // ensure that we don't switch to the uninstalled package (it will be used if it becomes // installed later) assertEquals(installedPackage, mWebViewUpdateServiceImpl.changeProviderAndSetting(uninstalledPackage)); // Ensure both packages are considered valid. assertEquals(2, mWebViewUpdateServiceImpl.getValidWebViewPackages().length); // We should only have called onWebViewProviderChanged once (before calling // changeProviderAndSetting Mockito.verify(mTestSystemImpl, Mockito.times(1)).onWebViewProviderChanged( Mockito.argThat(new IsPackageInfoWithName(installedPackage))); } @Test public void testHiddenPackageNotPrioritizedEvenIfChosen() { checkNonvisiblePackageNotPrioritizedEvenIfChosen( false /* true == uninstalled, false == hidden */); } @Test public void testUninstalledPackageNotPrioritizedEvenIfChosen() { checkNonvisiblePackageNotPrioritizedEvenIfChosen( true /* true == uninstalled, false == hidden */); } public void checkNonvisiblePackageNotPrioritizedEvenIfChosen(boolean uninstalledNotHidden) { boolean testUninstalled = uninstalledNotHidden; boolean testHidden = !uninstalledNotHidden; String installedPackage = "installedPackage"; String uninstalledPackage = "uninstalledPackage"; WebViewProviderInfo[] webviewPackages = new WebViewProviderInfo[] { new WebViewProviderInfo(uninstalledPackage, "", true /* available by default */, false /* fallback */, null), new WebViewProviderInfo(installedPackage, "", true /* available by default */, false /* fallback */, null)}; setupWithPackages(webviewPackages, true /* fallback logic enabled */, 1 /* numRelros */); int secondaryUserId = 4; mTestSystemImpl.addUser(secondaryUserId); setEnabledAndValidPackageInfosForUser(TestSystemImpl.PRIMARY_USER_ID, webviewPackages); mTestSystemImpl.setPackageInfoForUser(secondaryUserId, createPackageInfo(installedPackage, true /* enabled */, true /* valid */, true /* installed */)); mTestSystemImpl.setPackageInfoForUser(secondaryUserId, createPackageInfo(uninstalledPackage, true /* enabled */, true /* valid */, (testUninstalled ? false : true) /* installed */, null /* signatures */, 0 /* updateTime */, (testHidden ? true : false) /* hidden */)); // Start with the setting pointing to the uninstalled package mTestSystemImpl.updateUserSetting(null, uninstalledPackage); runWebViewBootPreparationOnMainSync(); checkPreparationPhasesForPackage(installedPackage, 1 /* first preparation phase */); } @Test public void testFallbackEnabledIfPrimaryUninstalledSingleUser() { checkFallbackEnabledIfPrimaryUninstalled(false /* multiUser */); } @Test public void testFallbackEnabledIfPrimaryUninstalledMultiUser() { checkFallbackEnabledIfPrimaryUninstalled(true /* multiUser */); } /** * Ensures that fallback becomes enabled at boot if the primary package is uninstalled for some * user. */ private void checkFallbackEnabledIfPrimaryUninstalled(boolean multiUser) { String primaryPackage = "primary"; String fallbackPackage = "fallback"; WebViewProviderInfo[] packages = new WebViewProviderInfo[] { new WebViewProviderInfo( primaryPackage, "", true /* default available */, false /* fallback */, null), new WebViewProviderInfo( fallbackPackage, "", true /* default available */, true /* fallback */, null)}; setupWithPackages(packages, true /* fallback logic enabled */); int secondaryUserId = 5; if (multiUser) { mTestSystemImpl.addUser(secondaryUserId); // Install all packages for the primary user. setEnabledAndValidPackageInfosForUser(TestSystemImpl.PRIMARY_USER_ID, packages); // Only install fallback package for secondary user. mTestSystemImpl.setPackageInfoForUser(secondaryUserId, createPackageInfo(primaryPackage, true /* enabled */, true /* valid */, false /* installed */)); mTestSystemImpl.setPackageInfoForUser(secondaryUserId, createPackageInfo(fallbackPackage, false /* enabled */, true /* valid */, true /* installed */)); } else { mTestSystemImpl.setPackageInfo(createPackageInfo(primaryPackage, true /* enabled */, true /* valid */, false /* installed */)); mTestSystemImpl.setPackageInfo(createPackageInfo(fallbackPackage, false /* enabled */, true /* valid */, true /* installed */)); } runWebViewBootPreparationOnMainSync(); // Verify that we enable the fallback package Mockito.verify(mTestSystemImpl).enablePackageForAllUsers( Mockito.anyObject(), Mockito.eq(fallbackPackage), Mockito.eq(true) /* enable */); checkPreparationPhasesForPackage(fallbackPackage, 1 /* first preparation phase */); } @Test public void testPreparationRunsIffNewPackage() { String primaryPackage = "primary"; String fallbackPackage = "fallback"; WebViewProviderInfo[] packages = new WebViewProviderInfo[] { new WebViewProviderInfo( primaryPackage, "", true /* default available */, false /* fallback */, null), new WebViewProviderInfo( fallbackPackage, "", true /* default available */, true /* fallback */, null)}; setupWithPackages(packages, true /* fallback logic enabled */); mTestSystemImpl.setPackageInfo(createPackageInfo(primaryPackage, true /* enabled */, true /* valid */, true /* installed */, null /* signatures */, 10 /* lastUpdateTime*/ )); mTestSystemImpl.setPackageInfo(createPackageInfo(fallbackPackage, true /* enabled */, true /* valid */, true /* installed */)); runWebViewBootPreparationOnMainSync(); checkPreparationPhasesForPackage(primaryPackage, 1 /* first preparation phase */); Mockito.verify(mTestSystemImpl, Mockito.times(1)).enablePackageForUser( Mockito.eq(fallbackPackage), Mockito.eq(false) /* enable */, Matchers.anyInt() /* user */); mWebViewUpdateServiceImpl.packageStateChanged(primaryPackage, WebViewUpdateService.PACKAGE_ADDED_REPLACED, 0 /* userId */); mWebViewUpdateServiceImpl.packageStateChanged(primaryPackage, WebViewUpdateService.PACKAGE_ADDED_REPLACED, 1 /* userId */); mWebViewUpdateServiceImpl.packageStateChanged(primaryPackage, WebViewUpdateService.PACKAGE_ADDED_REPLACED, 2 /* userId */); // package still has the same update-time so we shouldn't run preparation here Mockito.verify(mTestSystemImpl, Mockito.times(1)).onWebViewProviderChanged( Mockito.argThat(new IsPackageInfoWithName(primaryPackage))); Mockito.verify(mTestSystemImpl, Mockito.times(1)).enablePackageForUser( Mockito.eq(fallbackPackage), Mockito.eq(false) /* enable */, Matchers.anyInt() /* user */); // Ensure we can still load the package WebViewProviderResponse response = mWebViewUpdateServiceImpl.waitForAndGetProvider(); assertEquals(WebViewFactory.LIBLOAD_SUCCESS, response.status); assertEquals(primaryPackage, response.packageInfo.packageName); mTestSystemImpl.setPackageInfo(createPackageInfo(primaryPackage, true /* enabled */, true /* valid */, true /* installed */, null /* signatures */, 20 /* lastUpdateTime*/ )); mWebViewUpdateServiceImpl.packageStateChanged(primaryPackage, WebViewUpdateService.PACKAGE_ADDED_REPLACED, 0); // The package has now changed - ensure that we have run the preparation phase a second time checkPreparationPhasesForPackage(primaryPackage, 2 /* second preparation phase */); mTestSystemImpl.setPackageInfo(createPackageInfo(primaryPackage, true /* enabled */, true /* valid */, true /* installed */, null /* signatures */, 50 /* lastUpdateTime*/ )); // Receive intent for different user mWebViewUpdateServiceImpl.packageStateChanged(primaryPackage, WebViewUpdateService.PACKAGE_ADDED_REPLACED, 2); checkPreparationPhasesForPackage(primaryPackage, 3 /* third preparation phase */); } @Test public void testGetCurrentWebViewPackage() { PackageInfo firstPackage = createPackageInfo("first", true /* enabled */, true /* valid */, true /* installed */); firstPackage.setLongVersionCode(100); firstPackage.versionName = "first package version"; WebViewProviderInfo[] packages = new WebViewProviderInfo[] { new WebViewProviderInfo(firstPackage.packageName, "", true, false, null)}; setupWithPackages(packages, true); mTestSystemImpl.setPackageInfo(firstPackage); runWebViewBootPreparationOnMainSync(); Mockito.verify(mTestSystemImpl).onWebViewProviderChanged( Mockito.argThat(new IsPackageInfoWithName(firstPackage.packageName))); mWebViewUpdateServiceImpl.notifyRelroCreationCompleted(); // Ensure the API is correct before running waitForAndGetProvider assertEquals(firstPackage.packageName, mWebViewUpdateServiceImpl.getCurrentWebViewPackage().packageName); assertEquals(firstPackage.getLongVersionCode(), mWebViewUpdateServiceImpl.getCurrentWebViewPackage().getLongVersionCode()); assertEquals(firstPackage.versionName, mWebViewUpdateServiceImpl.getCurrentWebViewPackage().versionName); WebViewProviderResponse response = mWebViewUpdateServiceImpl.waitForAndGetProvider(); assertEquals(WebViewFactory.LIBLOAD_SUCCESS, response.status); assertEquals(firstPackage.packageName, response.packageInfo.packageName); // Ensure the API is still correct after running waitForAndGetProvider assertEquals(firstPackage.packageName, mWebViewUpdateServiceImpl.getCurrentWebViewPackage().packageName); assertEquals(firstPackage.getLongVersionCode(), mWebViewUpdateServiceImpl.getCurrentWebViewPackage().getLongVersionCode()); assertEquals(firstPackage.versionName, mWebViewUpdateServiceImpl.getCurrentWebViewPackage().versionName); } @Test public void testMultiProcessEnabledByDefault() { testMultiProcessByDefault(true /* enabledByDefault */); } @Test public void testMultiProcessDisabledByDefault() { testMultiProcessByDefault(false /* enabledByDefault */); } private void testMultiProcessByDefault(boolean enabledByDefault) { String primaryPackage = "primary"; WebViewProviderInfo[] packages = new WebViewProviderInfo[] { new WebViewProviderInfo( primaryPackage, "", true /* default available */, false /* fallback */, null)}; setupWithPackages(packages, true /* fallback logic enabled */, 1 /* numRelros */, true /* debuggable */, enabledByDefault /* not multiprocess by default */); mTestSystemImpl.setPackageInfo(createPackageInfo(primaryPackage, true /* enabled */, true /* valid */, true /* installed */, null /* signatures */, 10 /* lastUpdateTime*/, false /* not hidden */, 1000 /* versionCode */, false /* isSystemApp */)); runWebViewBootPreparationOnMainSync(); checkPreparationPhasesForPackage(primaryPackage, 1 /* first preparation phase */); // Check it's off by default assertEquals(enabledByDefault, mWebViewUpdateServiceImpl.isMultiProcessEnabled()); // Test toggling it mWebViewUpdateServiceImpl.enableMultiProcess(!enabledByDefault); assertEquals(!enabledByDefault, mWebViewUpdateServiceImpl.isMultiProcessEnabled()); mWebViewUpdateServiceImpl.enableMultiProcess(enabledByDefault); assertEquals(enabledByDefault, mWebViewUpdateServiceImpl.isMultiProcessEnabled()); } @Test public void testMultiProcessEnabledByDefaultWithSettingsValue() { testMultiProcessByDefaultWithSettingsValue( true /* enabledByDefault */, Integer.MIN_VALUE, false /* expectEnabled */); testMultiProcessByDefaultWithSettingsValue( true /* enabledByDefault */, -999999, true /* expectEnabled */); testMultiProcessByDefaultWithSettingsValue( true /* enabledByDefault */, 0, true /* expectEnabled */); testMultiProcessByDefaultWithSettingsValue( true /* enabledByDefault */, 999999, true /* expectEnabled */); } @Test public void testMultiProcessDisabledByDefaultWithSettingsValue() { testMultiProcessByDefaultWithSettingsValue( false /* enabledByDefault */, Integer.MIN_VALUE, false /* expectEnabled */); testMultiProcessByDefaultWithSettingsValue( false /* enabledByDefault */, 0, false /* expectEnabled */); testMultiProcessByDefaultWithSettingsValue( false /* enabledByDefault */, 999999, false /* expectEnabled */); testMultiProcessByDefaultWithSettingsValue( false /* enabledByDefault */, Integer.MAX_VALUE, true /* expectEnabled */); } /** * Test the logic of the multiprocess setting depending on whether multiprocess is enabled by * default, and what the setting is set to. * @param enabledByDefault whether multiprocess is enabled by default. * @param settingValue value of the multiprocess setting. */ private void testMultiProcessByDefaultWithSettingsValue( boolean enabledByDefault, int settingValue, boolean expectEnabled) { String primaryPackage = "primary"; WebViewProviderInfo[] packages = new WebViewProviderInfo[] { new WebViewProviderInfo( primaryPackage, "", true /* default available */, false /* fallback */, null)}; setupWithPackages(packages, true /* fallback logic enabled */, 1 /* numRelros */, true /* debuggable */, enabledByDefault /* multiprocess by default */); mTestSystemImpl.setPackageInfo(createPackageInfo(primaryPackage, true /* enabled */, true /* valid */, true /* installed */, null /* signatures */, 10 /* lastUpdateTime*/, false /* not hidden */, 1000 /* versionCode */, false /* isSystemApp */)); runWebViewBootPreparationOnMainSync(); checkPreparationPhasesForPackage(primaryPackage, 1 /* first preparation phase */); mTestSystemImpl.setMultiProcessSetting(null /* context */, settingValue); assertEquals(expectEnabled, mWebViewUpdateServiceImpl.isMultiProcessEnabled()); } /** * Ensure that packages with a targetSdkVersion targeting the current platform are valid, and * that packages targeting an older version are not valid. */ @Test public void testTargetSdkVersionValidity() { PackageInfo newSdkPackage = createPackageInfo("newTargetSdkPackage", true /* enabled */, true /* valid */, true /* installed */); newSdkPackage.applicationInfo.targetSdkVersion = Build.VERSION_CODES.CUR_DEVELOPMENT; PackageInfo currentSdkPackage = createPackageInfo("currentTargetSdkPackage", true /* enabled */, true /* valid */, true /* installed */); currentSdkPackage.applicationInfo.targetSdkVersion = UserPackage.MINIMUM_SUPPORTED_SDK; PackageInfo oldSdkPackage = createPackageInfo("oldTargetSdkPackage", true /* enabled */, true /* valid */, true /* installed */); oldSdkPackage.applicationInfo.targetSdkVersion = UserPackage.MINIMUM_SUPPORTED_SDK - 1; WebViewProviderInfo newSdkProviderInfo = new WebViewProviderInfo(newSdkPackage.packageName, "", true, false, null); WebViewProviderInfo currentSdkProviderInfo = new WebViewProviderInfo(currentSdkPackage.packageName, "", true, false, null); WebViewProviderInfo[] packages = new WebViewProviderInfo[] { new WebViewProviderInfo(oldSdkPackage.packageName, "", true, false, null), currentSdkProviderInfo, newSdkProviderInfo}; setupWithPackages(packages, true); ; mTestSystemImpl.setPackageInfo(newSdkPackage); mTestSystemImpl.setPackageInfo(currentSdkPackage); mTestSystemImpl.setPackageInfo(oldSdkPackage); assertArrayEquals(new WebViewProviderInfo[]{currentSdkProviderInfo, newSdkProviderInfo}, mWebViewUpdateServiceImpl.getValidWebViewPackages()); runWebViewBootPreparationOnMainSync(); checkPreparationPhasesForPackage(currentSdkPackage.packageName, 1 /* first preparation phase */); } }