From 53b78241ffc32e61d12afc6245633266e592db88 Mon Sep 17 00:00:00 2001 From: Gustav Sennton Date: Thu, 7 Apr 2016 15:56:10 +0100 Subject: [PATCH] Add initial unit tests for WebViewUpdateService. The logic in the WebViewUpdateService is now more complex and there are lots of edge cases that should be tested to make sure we don't regress anything. Also refrain from running fallback logic on boot if fallback logic not enabled (bug found through a failing test, yeeah buddy!). Catch uncaught MissingWebViewException (very timing-dependent!) and add test for this. Add tests for: Some package setups (single package, non-default vs. default packages, valid vs. invalid packages). Ensure correct error codes used Switching provider through settings-method Switching provider through adding more prioritized provider Switching provider in the middle of already running webview preparation Ensure fallback logic is run when enabled and not run when disabled (at boot, packageStateChanged, and when adding a new user). Bug: 27635535 Change-Id: I275ecb0f6129f71258da0fa053e7f7b36a675886 --- .../webkit/WebViewUpdateServiceImpl.java | 13 +- .../android/server/webkit/TestSystemImpl.java | 112 ++++ .../webkit/WebViewUpdateServiceTest.java | 613 ++++++++++++++++++ 3 files changed, 736 insertions(+), 2 deletions(-) create mode 100644 services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java create mode 100644 services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java index cd976e753a0a6..df5d0274c20ac 100644 --- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java +++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java @@ -119,6 +119,8 @@ public class WebViewUpdateServiceImpl { } private void updateFallbackStateOnBoot() { + if (!mSystemInterface.isFallbackLogicEnabled()) return; + WebViewProviderInfo[] webviewProviders = mSystemInterface.getWebViewPackages(); updateFallbackState(webviewProviders, true); } @@ -497,8 +499,15 @@ public class WebViewUpdateServiceImpl { mWebViewPackageDirty = false; // If we have changed provider since we started the relro creation we need to // redo the whole process using the new package instead. - PackageInfo newPackage = findPreferredWebViewPackage(); - onWebViewProviderChanged(newPackage); + try { + PackageInfo newPackage = findPreferredWebViewPackage(); + onWebViewProviderChanged(newPackage); + } catch (WebViewFactory.MissingWebViewPackageException e) { + // If we can't find any valid WebView package we are now in a state where + // mAnyWebViewInstalled is false, so loading WebView will be blocked and we + // should simply wait until we receive an intent declaring a new package was + // installed. + } } else { mLock.notifyAll(); } diff --git a/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java b/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java new file mode 100644 index 0000000000000..26b87c5d9283d --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java @@ -0,0 +1,112 @@ +/* + * 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 android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager.NameNotFoundException; +import android.webkit.WebViewProviderInfo; + +import java.util.HashMap; + +public class TestSystemImpl implements SystemInterface { + private String mUserProvider = ""; + private final WebViewProviderInfo[] mPackageConfigs; + HashMap mPackages = new HashMap(); + private boolean mFallbackLogicEnabled; + private final int mNumRelros; + private final boolean mIsDebuggable; + + public TestSystemImpl(WebViewProviderInfo[] packageConfigs, boolean fallbackLogicEnabled, + int numRelros, boolean isDebuggable) { + mPackageConfigs = packageConfigs; + mFallbackLogicEnabled = fallbackLogicEnabled; + mNumRelros = numRelros; + mIsDebuggable = isDebuggable; + } + + @Override + public WebViewProviderInfo[] getWebViewPackages() { + return mPackageConfigs; + } + + @Override + public int onWebViewProviderChanged(PackageInfo packageInfo) { + return mNumRelros; + } + + @Override + public String getUserChosenWebViewProvider(Context context) { return mUserProvider; } + + @Override + public void updateUserSetting(Context context, String newProviderName) { + mUserProvider = newProviderName; + } + + @Override + public void killPackageDependents(String packageName) {} + + @Override + public boolean isFallbackLogicEnabled() { + return mFallbackLogicEnabled; + } + + @Override + public void enableFallbackLogic(boolean enable) { + mFallbackLogicEnabled = enable; + } + + @Override + public void uninstallAndDisablePackageForAllUsers(Context context, String packageName) { + enablePackageForAllUsers(context, packageName, false); + } + + @Override + public void enablePackageForAllUsers(Context context, String packageName, boolean enable) { + enablePackageForUser(packageName, enable, 0); + } + + @Override + public void enablePackageForUser(String packageName, boolean enable, int userId) { + PackageInfo packageInfo = mPackages.get(packageName); + if (packageInfo == null) { + throw new IllegalArgumentException("There is no package called " + packageName); + } + packageInfo.applicationInfo.enabled = enable; + setPackageInfo(packageInfo); + } + + @Override + public boolean systemIsDebuggable() { return mIsDebuggable; } + + @Override + public PackageInfo getPackageInfoForProvider(WebViewProviderInfo info) throws + NameNotFoundException { + PackageInfo ret = mPackages.get(info.packageName); + if (ret == null) throw new NameNotFoundException(info.packageName); + return ret; + } + + public void setPackageInfo(PackageInfo pi) { + mPackages.put(pi.packageName, pi); + } + + @Override + public int getFactoryPackageVersion(String packageName) { + return 0; + } +} diff --git a/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java b/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java new file mode 100644 index 0000000000000..c00520dc35520 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java @@ -0,0 +1,613 @@ +/* + * 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 android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.Signature; +import android.os.Bundle; +import android.util.Base64; +import android.test.AndroidTestCase; + +import android.webkit.WebViewFactory; +import android.webkit.WebViewProviderInfo; +import android.webkit.WebViewProviderResponse; + +import java.util.concurrent.CountDownLatch; + +import org.hamcrest.Description; + +import org.mockito.Mockito; +import org.mockito.Matchers; +import org.mockito.ArgumentMatcher; + + +/** + * Tests for WebViewUpdateService + */ +public class WebViewUpdateServiceTest extends AndroidTestCase { + 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"; + + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + /** + * 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) { + TestSystemImpl testing = new TestSystemImpl(packages, fallbackLogicEnabled, numRelros, + isDebuggable); + mTestSystemImpl = Mockito.spy(testing); + mWebViewUpdateServiceImpl = + new WebViewUpdateServiceImpl(null /*Context*/, mTestSystemImpl); + } + + private void setEnabledAndValidPackageInfos(WebViewProviderInfo[] providers) { + for(WebViewProviderInfo wpi : providers) { + mTestSystemImpl.setPackageInfo(createPackageInfo(wpi.packageName, true /* enabled */, + true /* valid */)); + } + } + + private void checkCertainPackageUsedAfterWebViewPreparation(String expectedProviderName, + WebViewProviderInfo[] webviewPackages) { + checkCertainPackageUsedAfterWebViewPreparation(expectedProviderName, webviewPackages, 1); + } + + private void checkCertainPackageUsedAfterWebViewPreparation(String expectedProviderName, + WebViewProviderInfo[] webviewPackages, int numRelros) { + setupWithPackages(webviewPackages, true, numRelros); + // Add (enabled and valid) package infos for each provider + setEnabledAndValidPackageInfos(webviewPackages); + + mWebViewUpdateServiceImpl.prepareWebViewInSystemServer(); + + 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 extends ArgumentMatcher { + private final String mPackageName; + + IsPackageInfoWithName(String name) { + mPackageName = name; + } + + @Override + public boolean matches(Object p) { + return ((PackageInfo) p).packageName.equals(mPackageName); + } + + // Provide a more useful description in case of mismatch + @Override + public void describeTo (Description description) { + description.appendText(String.format("PackageInfo with name '%s'", mPackageName)); + } + } + + private static PackageInfo createPackageInfo( + String packageName, boolean enabled, boolean valid) { + PackageInfo p = new PackageInfo(); + p.packageName = packageName; + p.applicationInfo = new ApplicationInfo(); + p.applicationInfo.enabled = enabled; + p.applicationInfo.metaData = new Bundle(); + if (valid) { + // no flag means invalid + p.applicationInfo.metaData.putString(WEBVIEW_LIBRARY_FLAG, "blah"); + } + return p; + } + + private static PackageInfo createPackageInfo( + String packageName, boolean enabled, boolean valid, Signature[] signatures) { + PackageInfo p = createPackageInfo(packageName, enabled, valid); + p.signatures = signatures; + return p; + } + + + // **************** + // Tests + // **************** + + + public void testWithSinglePackage() { + String testPackageName = "test.package.name"; + checkCertainPackageUsedAfterWebViewPreparation(testPackageName, + new WebViewProviderInfo[] { + new WebViewProviderInfo(testPackageName, "", + true /*default available*/, false /* fallback */, null)}); + } + + 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)}; + checkCertainPackageUsedAfterWebViewPreparation(defaultPackage, packages); + } + + public void testSeveralRelros() { + String singlePackage = "singlePackage"; + checkCertainPackageUsedAfterWebViewPreparation( + 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. + 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 */, new Signature[]{invalidPackageSignature})); + mTestSystemImpl.setPackageInfo(createPackageInfo(validPackage, true /* enabled */, + true /* valid */, new Signature[]{validSignature})); + + mWebViewUpdateServiceImpl.prepareWebViewInSystemServer(); + + Mockito.verify(mTestSystemImpl).onWebViewProviderChanged( + Mockito.argThat(new IsPackageInfoWithName(validPackage))); + + mWebViewUpdateServiceImpl.notifyRelroCreationCompleted(); + + WebViewProviderResponse response = mWebViewUpdateServiceImpl.waitForAndGetProvider(); + assertEquals(WebViewFactory.LIBLOAD_SUCCESS, response.status); + assertEquals(validPackage, response.packageInfo.packageName); + + WebViewProviderInfo[] validPackages = mWebViewUpdateServiceImpl.getValidWebViewPackages(); + assertEquals(1, validPackages.length); + assertEquals(validPackage, validPackages[0].packageName); + } + + public void testFailWaitingForRelro() { + WebViewProviderInfo[] packages = new WebViewProviderInfo[] { + new WebViewProviderInfo("packagename", "", true, true, null)}; + setupWithPackages(packages); + setEnabledAndValidPackageInfos(packages); + + mWebViewUpdateServiceImpl.prepareWebViewInSystemServer(); + + 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); + } + + public void testFailListingEmptyWebviewPackages() { + WebViewProviderInfo[] packages = new WebViewProviderInfo[0]; + setupWithPackages(packages); + setEnabledAndValidPackageInfos(packages); + + mWebViewUpdateServiceImpl.prepareWebViewInSystemServer(); + + Mockito.verify(mTestSystemImpl, Mockito.never()).onWebViewProviderChanged( + Matchers.anyObject()); + + WebViewProviderResponse response = mWebViewUpdateServiceImpl.waitForAndGetProvider(); + assertEquals(WebViewFactory.LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES, response.status); + } + + public void testFailListingInvalidWebviewPackage() { + WebViewProviderInfo wpi = new WebViewProviderInfo("", "", true, true, null); + WebViewProviderInfo[] packages = new WebViewProviderInfo[] {wpi}; + setupWithPackages(packages); + mTestSystemImpl.setPackageInfo(createPackageInfo(wpi.packageName, true, false)); + + mWebViewUpdateServiceImpl.prepareWebViewInSystemServer(); + WebViewProviderResponse response = mWebViewUpdateServiceImpl.waitForAndGetProvider(); + assertEquals(WebViewFactory.LIBLOAD_FAILED_LISTING_WEBVIEW_PACKAGES, response.status); + } + + // Test that switching provider using changeProviderAndSetting works. + 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); + } + + 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) { + checkCertainPackageUsedAfterWebViewPreparation(initialPackage, packages); + + mWebViewUpdateServiceImpl.changeProviderAndSetting(finalPackage); + + Mockito.verify(mTestSystemImpl).onWebViewProviderChanged( + Mockito.argThat(new IsPackageInfoWithName(finalPackage))); + + mWebViewUpdateServiceImpl.notifyRelroCreationCompleted(); + + WebViewProviderResponse secondResponse = mWebViewUpdateServiceImpl.waitForAndGetProvider(); + assertEquals(WebViewFactory.LIBLOAD_SUCCESS, secondResponse.status); + assertEquals(finalPackage, secondResponse.packageInfo.packageName); + + Mockito.verify(mTestSystemImpl).killPackageDependents(Mockito.eq(initialPackage)); + } + + // Change provider during relro creation by using changeProviderAndSetting + public void testSwitchingProviderDuringRelroCreation() { + checkChangingProviderDuringRelroCreation(true); + } + + // Change provider during relro creation by enabling a provider + 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); + if (settingsChange) { + // Have all packages be enabled, so that we can change provider however we want to + setEnabledAndValidPackageInfos(packages); + } else { + // Have all packages be disabled so that we can change one to enabled later + for(WebViewProviderInfo wpi : packages) { + mTestSystemImpl.setPackageInfo(createPackageInfo(wpi.packageName, + false /* enabled */, true /* valid */)); + } + } + + CountDownLatch countdown = new CountDownLatch(1); + + mWebViewUpdateServiceImpl.prepareWebViewInSystemServer(); + + Mockito.verify(mTestSystemImpl).onWebViewProviderChanged( + Mockito.argThat(new IsPackageInfoWithName(firstPackage))); + + assertEquals(firstPackage, mWebViewUpdateServiceImpl.getCurrentWebViewPackageName()); + + 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 + Mockito.verify(mTestSystemImpl).killPackageDependents(Mockito.eq(firstPackage)); + countdown.countDown(); + } + }).start(); + try { + Thread.sleep(1000); // Let the new thread run / be blocked + } catch (InterruptedException e) { + } + + if (settingsChange) { + mWebViewUpdateServiceImpl.changeProviderAndSetting(secondPackage); + } else { + // Switch provider by enabling the second one + mTestSystemImpl.setPackageInfo(createPackageInfo(secondPackage, true /* enabled */, + true /* valid */)); + mWebViewUpdateServiceImpl.packageStateChanged( + secondPackage, WebViewUpdateService.PACKAGE_CHANGED); + } + 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) { + } + } + + public void testRunFallbackLogicIfEnabled() { + checkFallbackLogicBeingRun(true); + } + + 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); + + mWebViewUpdateServiceImpl.prepareWebViewInSystemServer(); + // 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 */)); + mWebViewUpdateServiceImpl.packageStateChanged( + fallbackPackage, WebViewUpdateService.PACKAGE_CHANGED); + + 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 + */ + 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 */)); + + mWebViewUpdateServiceImpl.prepareWebViewInSystemServer(); + Mockito.verify(mTestSystemImpl, Mockito.never()).uninstallAndDisablePackageForAllUsers( + Matchers.anyObject(), Matchers.anyObject()); + Mockito.verify(mTestSystemImpl).onWebViewProviderChanged( + Mockito.argThat(new IsPackageInfoWithName(fallbackPackage))); + + mWebViewUpdateServiceImpl.notifyRelroCreationCompleted(); + + WebViewProviderResponse response = mWebViewUpdateServiceImpl.waitForAndGetProvider(); + assertEquals(WebViewFactory.LIBLOAD_SUCCESS, response.status); + assertEquals(fallbackPackage, response.packageInfo.packageName); + + // Install primary package + mTestSystemImpl.setPackageInfo( + createPackageInfo(primaryPackage, true /* enabled */ , true /* valid */)); + mWebViewUpdateServiceImpl.packageStateChanged(primaryPackage, + WebViewUpdateService.PACKAGE_ADDED); + + // Verify fallback disabled and primary package used as provider + Mockito.verify(mTestSystemImpl).uninstallAndDisablePackageForAllUsers( + Matchers.anyObject(), Mockito.eq(fallbackPackage)); + Mockito.verify(mTestSystemImpl).onWebViewProviderChanged( + Mockito.argThat(new IsPackageInfoWithName(primaryPackage))); + + // Finish the webview preparation and ensure primary package used and fallback killed + mWebViewUpdateServiceImpl.notifyRelroCreationCompleted(); + response = mWebViewUpdateServiceImpl.waitForAndGetProvider(); + assertEquals(WebViewFactory.LIBLOAD_SUCCESS, response.status); + assertEquals(primaryPackage, response.packageInfo.packageName); + Mockito.verify(mTestSystemImpl).killPackageDependents(Mockito.eq(fallbackPackage)); + } + + public void testFallbackChangesEnabledState() { + 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 */); + setEnabledAndValidPackageInfos(packages); + + mWebViewUpdateServiceImpl.prepareWebViewInSystemServer(); + + // Verify fallback disabled at boot when primary package enabled + Mockito.verify(mTestSystemImpl).enablePackageForUser( + Mockito.eq(fallbackPackage), Mockito.eq(false) /* enable */, + Matchers.anyInt()); + + mTestSystemImpl.setPackageInfo( + createPackageInfo(primaryPackage, false /* enabled */, true /* valid */)); + mWebViewUpdateServiceImpl.packageStateChanged(primaryPackage, + WebViewUpdateService.PACKAGE_CHANGED); + + // Verify fallback becomes enabled when primary package becomes disabled + Mockito.verify(mTestSystemImpl).enablePackageForUser( + Mockito.eq(fallbackPackage), Mockito.eq(true) /* enable */, + Matchers.anyInt()); + + mTestSystemImpl.setPackageInfo( + createPackageInfo(primaryPackage, true /* enabled */, true /* valid */)); + mWebViewUpdateServiceImpl.packageStateChanged(primaryPackage, + WebViewUpdateService.PACKAGE_CHANGED); + + // Verify fallback is disabled a second time when primary package becomes enabled + Mockito.verify(mTestSystemImpl, Mockito.times(2)).enablePackageForUser( + Mockito.eq(fallbackPackage), Mockito.eq(false) /* enable */, + Matchers.anyInt()); + } + + public void testAddUserWhenFallbackLogicEnabled() { + checkAddingNewUser(true); + } + + 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); + setEnabledAndValidPackageInfos(packages); + int newUser = 100; + 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 */); + } + } + + /** + * 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. + */ + 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); + + mWebViewUpdateServiceImpl.prepareWebViewInSystemServer(); + + Mockito.verify(mTestSystemImpl).onWebViewProviderChanged( + Mockito.argThat(new IsPackageInfoWithName(firstPackage))); + + mWebViewUpdateServiceImpl.changeProviderAndSetting(secondPackage); + + // Make packages invalid to cause exception to be thrown + mTestSystemImpl.setPackageInfo(createPackageInfo(firstPackage, true /* enabled */, + false /* valid */)); + mTestSystemImpl.setPackageInfo(createPackageInfo(secondPackage, true /* enabled */, + false /* valid */)); + + // 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 */)); + + mWebViewUpdateServiceImpl.packageStateChanged(firstPackage, + WebViewUpdateService.PACKAGE_ADDED); + + // Second time we call onWebViewProviderChanged for firstPackage + Mockito.verify(mTestSystemImpl, Mockito.times(2)).onWebViewProviderChanged( + Mockito.argThat(new IsPackageInfoWithName(firstPackage))); + + mWebViewUpdateServiceImpl.notifyRelroCreationCompleted(); + + response = mWebViewUpdateServiceImpl.waitForAndGetProvider(); + assertEquals(WebViewFactory.LIBLOAD_SUCCESS, response.status); + assertEquals(firstPackage, response.packageInfo.packageName); + } + + // TODO (gsennton) add more tests for ensuring killPackageDependents is called / not called +}