Support using uninstalled WebView packages as WebView implementation.

A package can be uninstalled for the system user but still be installed
on the device. Currently, we do not use such a package as WebView
implementation which means we fall back to using the fallback WebView
package.
However, if the fallback package is not valid (because it needs to be
updated first) loading WebView will fail.

With this change we fetch allow the fetching of information from
packages that are uninstalled for the system user so that we can still
load WebView when the only valid WebView provider is uninstalled for the
system user.

Also listen to package additions/changes/removals for all users -
otherwise we won't notice when a package becomes replaced if it was
already uninstalled for the system user.

Bug: 29321185
Change-Id: Ia23c4493844877aea1b4eab7e666fd37540c4f97
diff --git a/services/core/java/com/android/server/webkit/SystemImpl.java b/services/core/java/com/android/server/webkit/SystemImpl.java
index 361f0d4..bb76449 100644
--- a/services/core/java/com/android/server/webkit/SystemImpl.java
+++ b/services/core/java/com/android/server/webkit/SystemImpl.java
@@ -270,5 +270,6 @@
 
     // flags declaring we want extra info from the package manager for webview providers
     private final static int PACKAGE_FLAGS = PackageManager.GET_META_DATA
-            | PackageManager.GET_SIGNATURES | PackageManager.MATCH_DEBUG_TRIAGED_MISSING;
+            | PackageManager.GET_SIGNATURES | PackageManager.MATCH_DEBUG_TRIAGED_MISSING
+            | PackageManager.MATCH_UNINSTALLED_PACKAGES;
 }
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateService.java b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
index 9b971e0..846169c 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateService.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateService.java
@@ -63,6 +63,7 @@
         mWebViewUpdatedReceiver = new BroadcastReceiver() {
                 @Override
                 public void onReceive(Context context, Intent intent) {
+                    int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
                     switch (intent.getAction()) {
                         case Intent.ACTION_PACKAGE_REMOVED:
                             // When a package is replaced we will receive two intents, one
@@ -73,24 +74,22 @@
                             // run the update-logic twice.
                             if (intent.getExtras().getBoolean(Intent.EXTRA_REPLACING)) return;
                             mImpl.packageStateChanged(packageNameFromIntent(intent),
-                                    PACKAGE_REMOVED);
+                                    PACKAGE_REMOVED, userId);
                             break;
                         case Intent.ACTION_PACKAGE_CHANGED:
                             // Ensure that we only heed PACKAGE_CHANGED intents if they change an
                             // entire package, not just a component
                             if (entirePackageChanged(intent)) {
                                 mImpl.packageStateChanged(packageNameFromIntent(intent),
-                                        PACKAGE_CHANGED);
+                                        PACKAGE_CHANGED, userId);
                             }
                             break;
                         case Intent.ACTION_PACKAGE_ADDED:
                             mImpl.packageStateChanged(packageNameFromIntent(intent),
                                     (intent.getExtras().getBoolean(Intent.EXTRA_REPLACING)
-                                     ? PACKAGE_ADDED_REPLACED : PACKAGE_ADDED));
+                                     ? PACKAGE_ADDED_REPLACED : PACKAGE_ADDED), userId);
                             break;
                         case Intent.ACTION_USER_ADDED:
-                            int userId =
-                                intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
                             mImpl.handleNewUser(userId);
                             break;
                     }
@@ -105,11 +104,14 @@
         for (WebViewProviderInfo provider : mImpl.getWebViewPackages()) {
             filter.addDataSchemeSpecificPart(provider.packageName, PatternMatcher.PATTERN_LITERAL);
         }
-        getContext().registerReceiver(mWebViewUpdatedReceiver, filter);
+
+        getContext().registerReceiverAsUser(mWebViewUpdatedReceiver, UserHandle.ALL, filter,
+                null /* broadcast permission */, null /* handler */);
 
         IntentFilter userAddedFilter = new IntentFilter();
         userAddedFilter.addAction(Intent.ACTION_USER_ADDED);
-        getContext().registerReceiver(mWebViewUpdatedReceiver, userAddedFilter);
+        getContext().registerReceiverAsUser(mWebViewUpdatedReceiver, UserHandle.ALL,
+                userAddedFilter, null /* broadcast permission */, null /* handler */);
 
         publishBinderService("webviewupdate", new BinderService(), true /*allowIsolated*/);
     }
diff --git a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
index ecab009..2cf1722 100644
--- a/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
+++ b/services/core/java/com/android/server/webkit/WebViewUpdateServiceImpl.java
@@ -20,6 +20,7 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.Signature;
+import android.os.UserHandle;
 import android.util.Base64;
 import android.util.Slog;
 import android.webkit.WebViewFactory;
@@ -49,7 +50,10 @@
         mWebViewUpdater = new WebViewUpdater(mContext, mSystemInterface);
     }
 
-    void packageStateChanged(String packageName, int changedState) {
+    void packageStateChanged(String packageName, int changedState, int userId) {
+        // We don't early out here in different cases where we could potentially early-out (e.g. if
+        // we receive PACKAGE_CHANGED for another user than the system user) since that would
+        // complicate this logic further and open up for more edge cases.
         updateFallbackStateOnPackageChange(packageName, changedState);
         mWebViewUpdater.packageStateChanged(packageName, changedState);
     }
@@ -64,7 +68,7 @@
             if (provider.availableByDefault && !provider.isFallback) {
                 try {
                     PackageInfo packageInfo = mSystemInterface.getPackageInfoForProvider(provider);
-                    if (isEnabledPackage(packageInfo)
+                    if (isInstalledPackage(packageInfo) && isEnabledPackage(packageInfo)
                             && mWebViewUpdater.isValidProvider(provider, packageInfo)) {
                         return true;
                     }
@@ -103,7 +107,7 @@
     }
 
     WebViewProviderInfo[] getValidWebViewPackages() {
-        return mWebViewUpdater.getValidWebViewPackages();
+        return mWebViewUpdater.getValidAndInstalledWebViewPackages();
     }
 
     WebViewProviderInfo[] getWebViewPackages() {
@@ -254,6 +258,12 @@
                                     // (not if it has been enabled/disabled).
                                     return;
                                 }
+                                if (newPackage.packageName.equals(oldProviderName)
+                                        && (newPackage.lastUpdateTime
+                                            == mCurrentWebViewPackage.lastUpdateTime)) {
+                                    // If the chosen package hasn't been updated, then early-out
+                                    return;
+                                }
                             }
                             // Only trigger update actions if the updated package is the one
                             // that will be used, or the one that was in use before the
@@ -373,14 +383,15 @@
             }
         }
 
-        private ProviderAndPackageInfo[] getValidWebViewPackagesAndInfos() {
+        private ProviderAndPackageInfo[] getValidWebViewPackagesAndInfos(boolean onlyInstalled) {
             WebViewProviderInfo[] allProviders = mSystemInterface.getWebViewPackages();
             List<ProviderAndPackageInfo> providers = new ArrayList<>();
             for(int n = 0; n < allProviders.length; n++) {
                 try {
                     PackageInfo packageInfo =
                         mSystemInterface.getPackageInfoForProvider(allProviders[n]);
-                    if (isValidProvider(allProviders[n], packageInfo)) {
+                    if ((!onlyInstalled || isInstalledPackage(packageInfo))
+                            && isValidProvider(allProviders[n], packageInfo)) {
                         providers.add(new ProviderAndPackageInfo(allProviders[n], packageInfo));
                     }
                 } catch (NameNotFoundException e) {
@@ -393,8 +404,9 @@
         /**
          * Fetch only the currently valid WebView packages.
          **/
-        public WebViewProviderInfo[] getValidWebViewPackages() {
-            ProviderAndPackageInfo[] providersAndPackageInfos = getValidWebViewPackagesAndInfos();
+        public WebViewProviderInfo[] getValidAndInstalledWebViewPackages() {
+            ProviderAndPackageInfo[] providersAndPackageInfos =
+                getValidWebViewPackagesAndInfos(true /* only fetch installed packages */);
             WebViewProviderInfo[] providers =
                 new WebViewProviderInfo[providersAndPackageInfos.length];
             for(int n = 0; n < providersAndPackageInfos.length; n++) {
@@ -421,29 +433,33 @@
          *
          */
         private PackageInfo findPreferredWebViewPackage() {
-            ProviderAndPackageInfo[] providers = getValidWebViewPackagesAndInfos();
+            ProviderAndPackageInfo[] providers =
+                getValidWebViewPackagesAndInfos(false /* onlyInstalled */);
 
             String userChosenProvider = mSystemInterface.getUserChosenWebViewProvider(mContext);
 
             // If the user has chosen provider, use that
             for (ProviderAndPackageInfo providerAndPackage : providers) {
                 if (providerAndPackage.provider.packageName.equals(userChosenProvider)
+                        && isInstalledPackage(providerAndPackage.packageInfo)
                         && isEnabledPackage(providerAndPackage.packageInfo)) {
                     return providerAndPackage.packageInfo;
                 }
             }
 
             // User did not choose, or the choice failed; use the most stable provider that is
-            // enabled and available by default (not through user choice).
+            // installed and enabled for the device owner, and available by default (not through
+            // user choice).
             for (ProviderAndPackageInfo providerAndPackage : providers) {
                 if (providerAndPackage.provider.availableByDefault
+                        && isInstalledPackage(providerAndPackage.packageInfo)
                         && isEnabledPackage(providerAndPackage.packageInfo)) {
                     return providerAndPackage.packageInfo;
                 }
             }
 
-            // Could not find any enabled package either, use the most stable and default-available
-            // provider.
+            // Could not find any installed and enabled package either, use the most stable and
+            // default-available provider.
             for (ProviderAndPackageInfo providerAndPackage : providers) {
                 if (providerAndPackage.provider.availableByDefault) {
                     return providerAndPackage.packageInfo;
@@ -642,4 +658,13 @@
         return packageInfo.applicationInfo.enabled;
     }
 
+    /**
+     * Return true if the package is installed and not hidden
+     */
+    private static boolean isInstalledPackage(PackageInfo packageInfo) {
+        return (((packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED) != 0)
+            && ((packageInfo.applicationInfo.privateFlags
+                        & ApplicationInfo.PRIVATE_FLAG_HIDDEN) == 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
index c03324a..b737033 100644
--- a/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/webkit/WebViewUpdateServiceTest.java
@@ -86,7 +86,7 @@
     private void setEnabledAndValidPackageInfos(WebViewProviderInfo[] providers) {
         for(WebViewProviderInfo wpi : providers) {
             mTestSystemImpl.setPackageInfo(createPackageInfo(wpi.packageName, true /* enabled */,
-                        true /* valid */));
+                        true /* valid */, true /* installed */));
         }
     }
 
@@ -137,12 +137,17 @@
     }
 
     private static PackageInfo createPackageInfo(
-            String packageName, boolean enabled, boolean valid) {
+            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");
@@ -150,10 +155,23 @@
         return p;
     }
 
-    private static PackageInfo createPackageInfo(
-            String packageName, boolean enabled, boolean valid, Signature[] signatures) {
-        PackageInfo p = createPackageInfo(packageName, enabled, valid);
+    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;
     }
 
@@ -223,9 +241,11 @@
         setupWithPackages(packages, true /* fallback logic enabled */, 1 /* numRelros */,
                 false /* isDebuggable */);
         mTestSystemImpl.setPackageInfo(createPackageInfo(invalidPackage, true /* enabled */,
-                    true /* valid */, new Signature[]{invalidPackageSignature}));
+                    true /* valid */, true /* installed */, new Signature[]{invalidPackageSignature}
+                    , 0 /* updateTime */));
         mTestSystemImpl.setPackageInfo(createPackageInfo(validPackage, true /* enabled */,
-                    true /* valid */, new Signature[]{validSignature}));
+                    true /* valid */, true /* installed */, new Signature[]{validSignature}
+                    , 0 /* updateTime */));
 
         mWebViewUpdateServiceImpl.prepareWebViewInSystemServer();
 
@@ -273,7 +293,8 @@
         WebViewProviderInfo[] packages = new WebViewProviderInfo[] {wpi};
         setupWithPackages(packages);
         mTestSystemImpl.setPackageInfo(
-                createPackageInfo(wpi.packageName, true /* enabled */, false /* valid */));
+                createPackageInfo(wpi.packageName, true /* enabled */, false /* valid */,
+                    true /* installed */));
 
         mWebViewUpdateServiceImpl.prepareWebViewInSystemServer();
 
@@ -285,9 +306,10 @@
 
         // Verify that we can recover from failing to list webview packages.
         mTestSystemImpl.setPackageInfo(
-                createPackageInfo(wpi.packageName, true /* enabled */, true /* valid */));
+                createPackageInfo(wpi.packageName, true /* enabled */, true /* valid */,
+                    true /* installed */));
         mWebViewUpdateServiceImpl.packageStateChanged(wpi.packageName,
-                WebViewUpdateService.PACKAGE_ADDED_REPLACED);
+                WebViewUpdateService.PACKAGE_ADDED_REPLACED, 0);
 
         checkPreparationPhasesForPackage(wpi.packageName, 1);
     }
@@ -345,7 +367,7 @@
             // 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 */));
+                            false /* enabled */, true /* valid */, true /* installed */));
             }
         }
 
@@ -371,7 +393,7 @@
             }
         }).start();
         try {
-            Thread.sleep(1000); // Let the new thread run / be blocked
+            Thread.sleep(500); // Let the new thread run / be blocked
         } catch (InterruptedException e) {
         }
 
@@ -380,9 +402,9 @@
         } else {
             // Switch provider by enabling the second one
             mTestSystemImpl.setPackageInfo(createPackageInfo(secondPackage, true /* enabled */,
-                        true /* valid */));
+                        true /* valid */, true /* installed */));
             mWebViewUpdateServiceImpl.packageStateChanged(
-                    secondPackage, WebViewUpdateService.PACKAGE_CHANGED);
+                    secondPackage, WebViewUpdateService.PACKAGE_CHANGED, 0);
         }
         mWebViewUpdateServiceImpl.notifyRelroCreationCompleted();
         // first package done, should start on second
@@ -432,9 +454,9 @@
 
         // Enable fallback package
         mTestSystemImpl.setPackageInfo(createPackageInfo(fallbackPackage, true /* enabled */,
-                        true /* valid */));
+                        true /* valid */, true /* installed */));
         mWebViewUpdateServiceImpl.packageStateChanged(
-                fallbackPackage, WebViewUpdateService.PACKAGE_CHANGED);
+                fallbackPackage, WebViewUpdateService.PACKAGE_CHANGED, 0);
 
         if (fallbackLogicEnabled) {
             // Check that we have now disabled the fallback package twice
@@ -463,7 +485,8 @@
                     fallbackPackage, "", true /* default available */, true /* fallback */, null)};
         setupWithPackages(packages, true /* isFallbackLogicEnabled */);
         mTestSystemImpl.setPackageInfo(
-                createPackageInfo(fallbackPackage, true /* enabled */ , true /* valid */));
+                createPackageInfo(fallbackPackage, true /* enabled */ , true /* valid */,
+                    true /* installed */));
 
         mWebViewUpdateServiceImpl.prepareWebViewInSystemServer();
         Mockito.verify(mTestSystemImpl, Mockito.never()).uninstallAndDisablePackageForAllUsers(
@@ -474,9 +497,10 @@
 
         // Install primary package
         mTestSystemImpl.setPackageInfo(
-                createPackageInfo(primaryPackage, true /* enabled */ , true /* valid */));
+                createPackageInfo(primaryPackage, true /* enabled */ , true /* valid */,
+                    true /* installed */));
         mWebViewUpdateServiceImpl.packageStateChanged(primaryPackage,
-                WebViewUpdateService.PACKAGE_ADDED_REPLACED);
+                WebViewUpdateService.PACKAGE_ADDED_REPLACED, 0);
 
         // Verify fallback disabled, primary package used as provider, and fallback package killed
         Mockito.verify(mTestSystemImpl).uninstallAndDisablePackageForAllUsers(
@@ -507,9 +531,10 @@
 
         // Disable primary package and ensure fallback becomes enabled and used
         mTestSystemImpl.setPackageInfo(
-                createPackageInfo(primaryPackage, false /* enabled */, true /* valid */));
+                createPackageInfo(primaryPackage, false /* enabled */, true /* valid */,
+                    true /* installed */));
         mWebViewUpdateServiceImpl.packageStateChanged(primaryPackage,
-                WebViewUpdateService.PACKAGE_CHANGED);
+                WebViewUpdateService.PACKAGE_CHANGED, 0);
 
         Mockito.verify(mTestSystemImpl).enablePackageForUser(
                 Mockito.eq(fallbackPackage), Mockito.eq(true) /* enable */,
@@ -520,9 +545,10 @@
 
         // Again enable primary package and verify primary is used and fallback becomes disabled
         mTestSystemImpl.setPackageInfo(
-                createPackageInfo(primaryPackage, true /* enabled */, true /* valid */));
+                createPackageInfo(primaryPackage, true /* enabled */, true /* valid */,
+                    true /* installed */));
         mWebViewUpdateServiceImpl.packageStateChanged(primaryPackage,
-                WebViewUpdateService.PACKAGE_CHANGED);
+                WebViewUpdateService.PACKAGE_CHANGED, 0);
 
         // Verify fallback is disabled a second time when primary package becomes enabled
         Mockito.verify(mTestSystemImpl, Mockito.times(2)).enablePackageForUser(
@@ -593,9 +619,10 @@
 
         // Make packages invalid to cause exception to be thrown
         mTestSystemImpl.setPackageInfo(createPackageInfo(firstPackage, true /* enabled */,
-                    false /* valid */));
+                    false /* valid */, true /* installed */, null /* signatures */,
+                    0 /* updateTime */));
         mTestSystemImpl.setPackageInfo(createPackageInfo(secondPackage, true /* enabled */,
-                    false /* valid */));
+                    false /* valid */, true /* installed */));
 
         // This shouldn't throw an exception!
         mWebViewUpdateServiceImpl.notifyRelroCreationCompleted();
@@ -605,10 +632,11 @@
 
         // Now make a package valid again and verify that we can switch back to that
         mTestSystemImpl.setPackageInfo(createPackageInfo(firstPackage, true /* enabled */,
-                    true /* valid */));
+                    true /* valid */, true /* installed */, null /* signatures */,
+                    1 /* updateTime */ ));
 
         mWebViewUpdateServiceImpl.packageStateChanged(firstPackage,
-                WebViewUpdateService.PACKAGE_ADDED_REPLACED);
+                WebViewUpdateService.PACKAGE_ADDED_REPLACED, 0);
 
         // Ensure we use firstPackage
         checkPreparationPhasesForPackage(firstPackage, 2 /* second preparation for this package */);
@@ -634,16 +662,16 @@
 
         // Remove second package (invalidate it) and verify that first package is used
         mTestSystemImpl.setPackageInfo(createPackageInfo(secondPackage, true /* enabled */,
-                    false /* valid */));
+                    false /* valid */, true /* installed */));
         mWebViewUpdateServiceImpl.packageStateChanged(secondPackage,
-                WebViewUpdateService.PACKAGE_ADDED);
+                WebViewUpdateService.PACKAGE_ADDED, 0);
         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 /* valid */, true /* installed */));
         mWebViewUpdateServiceImpl.packageStateChanged(secondPackage,
-                WebViewUpdateService.PACKAGE_ADDED);
+                WebViewUpdateService.PACKAGE_ADDED, 0);
         checkPreparationPhasesForPackage(secondPackage, 2 /* second time for this package */);
     }
 
@@ -663,7 +691,7 @@
         setupWithPackages(packages);
         // Only 'install' nonChosenPackage
         mTestSystemImpl.setPackageInfo(
-                createPackageInfo(nonChosenPackage, true /* enabled */, true /* valid */));
+                createPackageInfo(nonChosenPackage, true /* enabled */, true /* valid */, true /* installed */));
 
         // Set user-chosen package
         mTestSystemImpl.updateUserSetting(null, chosenPackage);
@@ -702,16 +730,16 @@
 
         // Make both packages invalid so that we fail listing WebView packages
         mTestSystemImpl.setPackageInfo(createPackageInfo(firstPackage, true /* enabled */,
-                    false /* valid */));
+                    false /* valid */, true /* installed */));
         mTestSystemImpl.setPackageInfo(createPackageInfo(secondPackage, true /* enabled */,
-                    false /* valid */));
+                    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);
+                    WebViewUpdateService.PACKAGE_ADDED_REPLACED, 0);
         }
 
         WebViewProviderResponse response = mWebViewUpdateServiceImpl.waitForAndGetProvider();
@@ -719,10 +747,10 @@
 
         // Make second package valid and verify that we can load it again
         mTestSystemImpl.setPackageInfo(createPackageInfo(secondPackage, true /* enabled */,
-                    true /* valid */));
+                    true /* valid */, true /* installed */));
 
         mWebViewUpdateServiceImpl.packageStateChanged(secondPackage,
-                WebViewUpdateService.PACKAGE_ADDED_REPLACED);
+                WebViewUpdateService.PACKAGE_ADDED_REPLACED, 0);
 
 
         checkPreparationPhasesForPackage(secondPackage, 1);
@@ -749,13 +777,14 @@
         // Replace or remove the current webview package
         if (replaced) {
             mTestSystemImpl.setPackageInfo(
-                    createPackageInfo(firstPackage, true /* enabled */, false /* valid */));
+                    createPackageInfo(firstPackage, true /* enabled */, false /* valid */,
+                        true /* installed */));
             mWebViewUpdateServiceImpl.packageStateChanged(firstPackage,
-                    WebViewUpdateService.PACKAGE_ADDED_REPLACED);
+                    WebViewUpdateService.PACKAGE_ADDED_REPLACED, 0);
         } else {
             mTestSystemImpl.removePackageInfo(firstPackage);
             mWebViewUpdateServiceImpl.packageStateChanged(firstPackage,
-                    WebViewUpdateService.PACKAGE_REMOVED);
+                    WebViewUpdateService.PACKAGE_REMOVED, 0);
         }
 
         checkPreparationPhasesForPackage(secondPackage, 1);
@@ -806,7 +835,7 @@
         checkPreparationPhasesForPackage(thirdPackage, 1);
 
         mTestSystemImpl.setPackageInfo(
-                createPackageInfo(secondPackage, true /* enabled */, false /* valid */));
+                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.
@@ -817,4 +846,229 @@
 
         Mockito.verify(mTestSystemImpl).killPackageDependents(Mockito.eq(thirdPackage));
     }
+
+    // Ensure that the update service uses an uninstalled package if that is the only package
+    // available.
+    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 */));
+
+        mWebViewUpdateServiceImpl.prepareWebViewInSystemServer();
+
+        checkPreparationPhasesForPackage(testPackageName, 1 /* first preparation phase */);
+    }
+
+    public void testNonhiddenPackageUserOverHidden() {
+        checkVisiblePackageUserOverNonVisible(false /* true == uninstalled, false == hidden */);
+    }
+
+    public void testInstalledPackageUsedOverUninstalled() {
+        checkVisiblePackageUserOverNonVisible(true /* true == uninstalled, false == hidden */);
+    }
+
+    private void checkVisiblePackageUserOverNonVisible(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 */);
+        mTestSystemImpl.setPackageInfo(createPackageInfo(installedPackage, true /* enabled */,
+                    true /* valid */, true /* installed */));
+        mTestSystemImpl.setPackageInfo(createPackageInfo(uninstalledPackage, true /* enabled */,
+                    true /* valid */, (testUninstalled ? false : true) /* installed */,
+                    null /* signatures */, 0 /* updateTime */, (testHidden ? true : false)));
+
+        mWebViewUpdateServiceImpl.prepareWebViewInSystemServer();
+
+        checkPreparationPhasesForPackage(installedPackage, 1 /* first preparation phase */);
+    }
+
+    public void testCantSwitchToHiddenPackage () {
+        checkCantSwitchToNonVisiblePackage(false /* true == uninstalled, false == hidden */);
+    }
+
+
+    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,
+     * and that an uninstalled (or hidden) package is not considered valid (in the
+     * getValidWebViewPackages() API).
+     */
+    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 */);
+        mTestSystemImpl.setPackageInfo(createPackageInfo(installedPackage, true /* enabled */,
+                    true /* valid */, true /* installed */));
+        mTestSystemImpl.setPackageInfo(createPackageInfo(uninstalledPackage, true /* enabled */,
+                    true /* valid */, (testUninstalled ? false : true) /* installed */,
+                    null /* signatures */, 0 /* updateTime */,
+                    (testHidden ? true : false) /* hidden */));
+
+        mWebViewUpdateServiceImpl.prepareWebViewInSystemServer();
+
+        checkPreparationPhasesForPackage(installedPackage, 1 /* first preparation phase */);
+
+        // Ensure that only the installed package is considered valid
+        WebViewProviderInfo[] validPackages = mWebViewUpdateServiceImpl.getValidWebViewPackages();
+        assertEquals(1, validPackages.length);
+        assertEquals(installedPackage, validPackages[0].packageName);
+
+        // ensure that we don't switch to the uninstalled package (it will be used if it becomes
+        // installed later)
+        assertEquals(installedPackage,
+                mWebViewUpdateServiceImpl.changeProviderAndSetting(uninstalledPackage));
+
+        // We should only have called onWebViewProviderChanged once (before calling
+        // changeProviderAndSetting
+        Mockito.verify(mTestSystemImpl, Mockito.times(1)).onWebViewProviderChanged(
+                Mockito.argThat(new IsPackageInfoWithName(installedPackage)));
+    }
+
+    public void testHiddenPackageNotPrioritizedEvenIfChosen() {
+        checkNonvisiblePackageNotPrioritizedEvenIfChosen(
+                false /* true == uninstalled, false == hidden */);
+    }
+
+    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 */);
+        mTestSystemImpl.setPackageInfo(createPackageInfo(installedPackage, true /* enabled */,
+                    true /* valid */, true /* installed */));
+        mTestSystemImpl.setPackageInfo(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);
+
+        mWebViewUpdateServiceImpl.prepareWebViewInSystemServer();
+
+        checkPreparationPhasesForPackage(installedPackage, 1 /* first preparation phase */);
+    }
+
+    /**
+     * Ensures that fallback becomes enabled if the primary package is uninstalled for the current
+     * user.
+     */
+    public void testFallbackEnabledIfPrimaryUninstalled() {
+        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 */, false /* installed */));
+        mTestSystemImpl.setPackageInfo(createPackageInfo(fallbackPackage, true /* enabled */,
+                    true /* valid */, true /* installed */));
+
+        mWebViewUpdateServiceImpl.prepareWebViewInSystemServer();
+        // 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 */);
+    }
+
+    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 */));
+
+        mWebViewUpdateServiceImpl.prepareWebViewInSystemServer();
+
+        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 */);
+    }
+
 }