Merge \"Support using uninstalled WebView packages as WebView implementation.\" into nyc-dev
am: e25c8532b6

Change-Id: Ie9f405765c146d21f1399037dd236d440902f0f9
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 */);
+    }
+
 }