summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java13
-rw-r--r--services/tests/servicestests/src/com/android/server/webkit/TestSystemImpl.java112
-rw-r--r--services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java613
3 files changed, 736 insertions, 2 deletions
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
index cd976e753a0a..df5d0274c20a 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 000000000000..26b87c5d9283
--- /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<String, PackageInfo> 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 000000000000..c00520dc3552
--- /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<PackageInfo> {
+ 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
+}