Merge "Report error correctly for the implementation of StreamInterface::GetData()" into nyc-dev
diff --git a/core/java/android/os/IDeviceIdleController.aidl b/core/java/android/os/IDeviceIdleController.aidl
index 082194b..cc2af21 100644
--- a/core/java/android/os/IDeviceIdleController.aidl
+++ b/core/java/android/os/IDeviceIdleController.aidl
@@ -38,8 +38,6 @@
     long addPowerSaveTempWhitelistAppForMms(String name, int userId, String reason);
     long addPowerSaveTempWhitelistAppForSms(String name, int userId, String reason);
     void exitIdle(String reason);
-    void downloadServiceActive(IBinder token);
-    void downloadServiceInactive();
     boolean registerMaintenanceActivityListener(IMaintenanceActivityListener listener);
     void unregisterMaintenanceActivityListener(IMaintenanceActivityListener listener);
 }
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index 9245749..6bc3e4c 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -245,7 +245,7 @@
     <string name="permgrouplab_contacts" msgid="3657758145679177612">"Kontakte"</string>
     <string name="permgroupdesc_contacts" msgid="6951499528303668046">"auf deine Kontakte zugreifen"</string>
     <string name="permgrouplab_location" msgid="7275582855722310164">"Standort"</string>
-    <string name="permgroupdesc_location" msgid="1346617465127855033">"auf den Standort deines Geräts zugreifen"</string>
+    <string name="permgroupdesc_location" msgid="1346617465127855033">"auf den Standort deines Geräts zuzugreifen"</string>
     <string name="permgrouplab_calendar" msgid="5863508437783683902">"Kalender"</string>
     <string name="permgroupdesc_calendar" msgid="3889615280211184106">"auf deinen Kalender zugreifen"</string>
     <string name="permgrouplab_sms" msgid="228308803364967808">"SMS"</string>
diff --git a/packages/DocumentsUI/src/com/android/documentsui/services/CopyJob.java b/packages/DocumentsUI/src/com/android/documentsui/services/CopyJob.java
index f10af43..53fa3cc4 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/services/CopyJob.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/services/CopyJob.java
@@ -227,7 +227,7 @@
         try {
             mBatchSize = calculateSize(mSrcs);
         } catch (ResourceException e) {
-            Log.w(TAG, "Failed to calculate total size. Copying without progress.");
+            Log.w(TAG, "Failed to calculate total size. Copying without progress.", e);
             mBatchSize = -1;
         }
 
@@ -236,25 +236,19 @@
         for (int i = 0; i < mSrcs.size() && !isCanceled(); ++i) {
             srcInfo = mSrcs.get(i);
 
-            // Guard unsupported recursive operation.
-            try {
-                if (dstInfo.equals(srcInfo) || isDescendentOf(srcInfo, dstInfo)) {
-                    throw new ResourceException("Cannot copy to itself recursively.");
-                }
-            } catch (ResourceException e) {
-                Log.e(TAG, e.toString());
-                onFileFailed(srcInfo);
-                continue;
-            }
-
             if (DEBUG) Log.d(TAG,
                     "Copying " + srcInfo.displayName + " (" + srcInfo.derivedUri + ")"
                     + " to " + dstInfo.displayName + " (" + dstInfo.derivedUri + ")");
 
             try {
-                processDocument(srcInfo, null, dstInfo);
+                if (dstInfo.equals(srcInfo) || isDescendentOf(srcInfo, dstInfo)) {
+                    Log.e(TAG, "Skipping recursive copy of " + srcInfo.derivedUri);
+                    onFileFailed(srcInfo);
+                } else {
+                    processDocument(srcInfo, null, dstInfo);
+                }
             } catch (ResourceException e) {
-                Log.e(TAG, e.toString());
+                Log.e(TAG, "Failed to copy " + srcInfo.derivedUri, e);
                 onFileFailed(srcInfo);
             }
         }
@@ -306,7 +300,7 @@
                     }
                 } catch (RemoteException | RuntimeException e) {
                     Log.e(TAG, "Provider side copy failed for: " + src.derivedUri
-                            + " due to an exception: " + e);
+                            + " due to an exception.", e);
                 }
                 // If optimized copy fails, then fallback to byte-by-byte copy.
                 if (DEBUG) Log.d(TAG, "Fallback to byte-by-byte copy for: " + src.derivedUri);
diff --git a/packages/DocumentsUI/src/com/android/documentsui/services/DeleteJob.java b/packages/DocumentsUI/src/com/android/documentsui/services/DeleteJob.java
index 8f45162..36a0f32 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/services/DeleteJob.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/services/DeleteJob.java
@@ -86,7 +86,7 @@
             try {
                 deleteDocument(doc, mSrcParent);
             } catch (ResourceException e) {
-                Log.e(TAG, "Failed to delete document @ " + doc.derivedUri);
+                Log.e(TAG, "Failed to delete document @ " + doc.derivedUri, e);
                 onFileFailed(doc);
             }
         }
diff --git a/packages/DocumentsUI/src/com/android/documentsui/services/MoveJob.java b/packages/DocumentsUI/src/com/android/documentsui/services/MoveJob.java
index aaa7596..1118171 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/services/MoveJob.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/services/MoveJob.java
@@ -98,7 +98,7 @@
                     }
                 } catch (RemoteException | RuntimeException e) {
                     Log.e(TAG, "Provider side move failed for: " + src.derivedUri
-                            + " due to an exception: " + e);
+                            + " due to an exception: ", e);
                 }
                 // If optimized move fails, then fallback to byte-by-byte copy.
                 if (DEBUG) Log.d(TAG, "Fallback to byte-by-byte move for: " + src.derivedUri);
diff --git a/packages/SettingsLib/res/values-uz-rUZ/strings.xml b/packages/SettingsLib/res/values-uz-rUZ/strings.xml
index 13fdb05..bd0cc8c 100644
--- a/packages/SettingsLib/res/values-uz-rUZ/strings.xml
+++ b/packages/SettingsLib/res/values-uz-rUZ/strings.xml
@@ -85,7 +85,7 @@
     <string name="accessibility_wifi_signal_full" msgid="7061045677694702">"Wi-Fi: signal to‘liq"</string>
     <string name="process_kernel_label" msgid="3916858646836739323">"Android OS"</string>
     <string name="data_usage_uninstalled_apps" msgid="614263770923231598">"O‘chirilgan ilovalar"</string>
-    <string name="data_usage_uninstalled_apps_users" msgid="7986294489899813194">"O‘chirib yuborilgan ilovalar va foydalanuvchilar"</string>
+    <string name="data_usage_uninstalled_apps_users" msgid="7986294489899813194">"O‘chirib tashlangan ilova va foydalanuvchilar"</string>
     <string name="tether_settings_title_usb" msgid="6688416425801386511">"USB modem"</string>
     <string name="tether_settings_title_wifi" msgid="3277144155960302049">"Ixcham hotspot"</string>
     <string name="tether_settings_title_bluetooth" msgid="355855408317564420">"Bluetooth modem"</string>
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 2c874e5..effefb0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -72,6 +72,8 @@
     private QSCustomizer mCustomizePanel;
     private Record mDetailRecord;
 
+    private BrightnessMirrorController mBrightnessMirrorController;
+
     public QSPanel(Context context) {
         this(context, null);
     }
@@ -159,7 +161,7 @@
     }
 
     public void setBrightnessMirror(BrightnessMirrorController c) {
-        super.onFinishInflate();
+        mBrightnessMirrorController = c;
         ToggleSlider brightnessSlider = (ToggleSlider) findViewById(R.id.brightness_slider);
         ToggleSlider mirror = (ToggleSlider) c.getMirror().findViewById(R.id.brightness_slider);
         brightnessSlider.setMirror(mirror);
@@ -205,6 +207,11 @@
     protected void onConfigurationChanged(Configuration newConfig) {
         super.onConfigurationChanged(newConfig);
         mFooter.onConfigurationChanged();
+
+        if (mBrightnessMirrorController != null) {
+            // Reload the mirror in case it got reinflated but we didn't.
+            setBrightnessMirror(mBrightnessMirrorController);
+        }
     }
 
     public void onCollapse() {
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index 41d8b4f..130fb7c 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -3417,10 +3417,6 @@
                 Slog.w(TAG, "User " + userId + " has no Vpn configuration");
                 return false;
             }
-            // If the current VPN package is the same as the new one, this is a no-op
-            if (TextUtils.equals(packageName, vpn.getAlwaysOnPackage())) {
-                return true;
-            }
             if (!vpn.setAlwaysOnPackage(packageName, lockdown)) {
                 return false;
             }
diff --git a/services/core/java/com/android/server/DeviceIdleController.java b/services/core/java/com/android/server/DeviceIdleController.java
index bb966f7..afed5ef 100644
--- a/services/core/java/com/android/server/DeviceIdleController.java
+++ b/services/core/java/com/android/server/DeviceIdleController.java
@@ -212,7 +212,6 @@
 
     private int mActiveIdleOpCount;
     private PowerManager.WakeLock mActiveIdleWakeLock;
-    private IBinder mDownloadServiceActive;
     private boolean mJobsActive;
     private boolean mAlarmsActive;
     private boolean mReportedMaintenanceActivity;
@@ -607,7 +606,7 @@
          * This is the minimum amount of time that we will stay in maintenance mode after
          * a light doze.  We have this minimum to allow various things to respond to switching
          * in to maintenance mode and scheduling their work -- otherwise we may
-         * see there is nothing to do (no jobs or downloads pending) and go out of maintenance
+         * see there is nothing to do (no jobs pending) and go out of maintenance
          * mode immediately.
          * @see Settings.Global#DEVICE_IDLE_CONSTANTS
          * @see #KEY_MIN_LIGHT_MAINTENANCE_TIME
@@ -618,7 +617,7 @@
          * This is the minimum amount of time that we will stay in maintenance mode after
          * a full doze.  We have this minimum to allow various things to respond to switching
          * in to maintenance mode and scheduling their work -- otherwise we may
-         * see there is nothing to do (no jobs or downloads pending) and go out of maintenance
+         * see there is nothing to do (no jobs pending) and go out of maintenance
          * mode immediately.
          * @see Settings.Global#DEVICE_IDLE_CONSTANTS
          * @see #KEY_MIN_DEEP_MAINTENANCE_TIME
@@ -1220,28 +1219,6 @@
             }
         }
 
-        @Override public void downloadServiceActive(IBinder token) {
-            getContext().enforceCallingOrSelfPermission(
-                    "android.permission.SEND_DOWNLOAD_COMPLETED_INTENTS", null);
-            long ident = Binder.clearCallingIdentity();
-            try {
-                DeviceIdleController.this.downloadServiceActive(token);
-            } finally {
-                Binder.restoreCallingIdentity(ident);
-            }
-        }
-
-        @Override public void downloadServiceInactive() {
-            getContext().enforceCallingOrSelfPermission(
-                    "android.permission.SEND_DOWNLOAD_COMPLETED_INTENTS", null);
-            long ident = Binder.clearCallingIdentity();
-            try {
-                DeviceIdleController.this.downloadServiceInactive();
-            } finally {
-                Binder.restoreCallingIdentity(ident);
-            }
-        }
-
         @Override public boolean registerMaintenanceActivityListener(
                 IMaintenanceActivityListener listener) {
             return DeviceIdleController.this.registerMaintenanceActivityListener(listener);
@@ -2086,30 +2063,6 @@
         }
     }
 
-    void downloadServiceActive(IBinder token) {
-        synchronized (this) {
-            mDownloadServiceActive = token;
-            reportMaintenanceActivityIfNeededLocked();
-            try {
-                token.linkToDeath(new IBinder.DeathRecipient() {
-                    @Override public void binderDied() {
-                        downloadServiceInactive();
-                    }
-                }, 0);
-            } catch (RemoteException e) {
-                mDownloadServiceActive = null;
-            }
-        }
-    }
-
-    void downloadServiceInactive() {
-        synchronized (this) {
-            mDownloadServiceActive = null;
-            reportMaintenanceActivityIfNeededLocked();
-            exitMaintenanceEarlyIfNeededLocked();
-        }
-    }
-
     void setJobsActive(boolean active) {
         synchronized (this) {
             mJobsActive = active;
@@ -2143,7 +2096,7 @@
     }
 
     void reportMaintenanceActivityIfNeededLocked() {
-        boolean active = mJobsActive | (mDownloadServiceActive != null);
+        boolean active = mJobsActive;
         if (active == mReportedMaintenanceActivity) {
             return;
         }
@@ -2154,8 +2107,7 @@
     }
 
     boolean isOpsInactiveLocked() {
-        return mActiveIdleOpCount <= 0 && mDownloadServiceActive == null
-                && !mJobsActive && !mAlarmsActive;
+        return mActiveIdleOpCount <= 0 && !mJobsActive && !mAlarmsActive;
     }
 
     void exitMaintenanceEarlyIfNeededLocked() {
@@ -3053,9 +3005,6 @@
             if (mAlarmsActive) {
                 pw.print("  mAlarmsActive="); pw.println(mAlarmsActive);
             }
-            if (mDownloadServiceActive != null) {
-                pw.print("  mDownloadServiceActive="); pw.println(mDownloadServiceActive);
-            }
         }
     }
 
diff --git a/services/core/java/com/android/server/LockSettingsService.java b/services/core/java/com/android/server/LockSettingsService.java
index 1e715f9..ef7b787 100644
--- a/services/core/java/com/android/server/LockSettingsService.java
+++ b/services/core/java/com/android/server/LockSettingsService.java
@@ -617,6 +617,7 @@
 
     @Override
     public boolean getSeparateProfileChallengeEnabled(int userId) throws RemoteException {
+        checkReadPermission(SEPARATE_PROFILE_CHALLENGE_KEY, userId);
         synchronized (mSeparateChallengeLock) {
             return getBoolean(SEPARATE_PROFILE_CHALLENGE_KEY, false, userId);
         }
@@ -625,6 +626,7 @@
     @Override
     public void setSeparateProfileChallengeEnabled(int userId, boolean enabled,
             String managedUserPassword) throws RemoteException {
+        checkWritePermission(userId);
         synchronized (mSeparateChallengeLock) {
             setBoolean(SEPARATE_PROFILE_CHALLENGE_KEY, enabled, userId);
             if (enabled) {
@@ -672,7 +674,6 @@
     @Override
     public long getLong(String key, long defaultValue, int userId) throws RemoteException {
         checkReadPermission(key, userId);
-
         String value = getStringUnchecked(key, null, userId);
         return TextUtils.isEmpty(value) ? defaultValue : Long.parseLong(value);
     }
@@ -680,7 +681,6 @@
     @Override
     public String getString(String key, String defaultValue, int userId) throws RemoteException {
         checkReadPermission(key, userId);
-
         return getStringUnchecked(key, defaultValue, userId);
     }
 
@@ -899,7 +899,7 @@
         }
     }
 
-    public void setLockPatternInternal(String pattern, String savedCredential, int userId)
+    private void setLockPatternInternal(String pattern, String savedCredential, int userId)
             throws RemoteException {
         byte[] currentHandle = getCurrentHandle(userId);
 
@@ -962,7 +962,7 @@
         }
     }
 
-    public void setLockPasswordInternal(String password, String savedCredential, int userId)
+    private void setLockPasswordInternal(String password, String savedCredential, int userId)
             throws RemoteException {
         byte[] currentHandle = getCurrentHandle(userId);
         if (password == null) {
@@ -1156,6 +1156,7 @@
 
     @Override
     public void resetKeyStore(int userId) throws RemoteException {
+        checkWritePermission(userId);
         if (DEBUG) Slog.v(TAG, "Reset keystore for user: " + userId);
         int managedUserId = -1;
         String managedUserDecryptedPassword = null;
@@ -1558,6 +1559,7 @@
             LockPatternUtils.LOCK_PASSWORD_SALT_KEY,
             LockPatternUtils.PASSWORD_HISTORY_KEY,
             LockPatternUtils.PASSWORD_TYPE_KEY,
+            SEPARATE_PROFILE_CHALLENGE_KEY
     };
 
     private static final String[] SETTINGS_TO_BACKUP = new String[] {
diff --git a/services/core/java/com/android/server/am/ActivityStartInterceptor.java b/services/core/java/com/android/server/am/ActivityStartInterceptor.java
index a2c2040..f26e47e 100644
--- a/services/core/java/com/android/server/am/ActivityStartInterceptor.java
+++ b/services/core/java/com/android/server/am/ActivityStartInterceptor.java
@@ -186,9 +186,13 @@
         if (mActivityOptions == null) {
             mActivityOptions = ActivityOptions.makeBasic();
         }
-        // Showing credential confirmation activity in home task to avoid stopping multi-windowed
-        // mode after showing the full-screen credential confirmation activity.
-        mActivityOptions.setLaunchTaskId(mSupervisor.getHomeActivity().task.taskId);
+
+        ActivityRecord homeActivityRecord = mSupervisor.getHomeActivity();
+        if (homeActivityRecord != null && homeActivityRecord.task != null) {
+            // Showing credential confirmation activity in home task to avoid stopping multi-windowed
+            // mode after showing the full-screen credential confirmation activity.
+            mActivityOptions.setLaunchTaskId(homeActivityRecord.task.taskId);
+        }
 
         final UserInfo parent = mUserManager.getProfileParent(mUserId);
         mRInfo = mSupervisor.resolveIntent(mIntent, mResolvedType, parent.id);
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index ebacc71..ede3bda 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -259,28 +259,39 @@
      *
      * @param packageName the package to designate as always-on VPN supplier.
      * @param lockdown whether to prevent traffic outside of a VPN, for example while connecting.
+     * @return {@code true} if the package has been set as always-on, {@code false} otherwise.
      */
     public synchronized boolean setAlwaysOnPackage(String packageName, boolean lockdown) {
         enforceControlPermissionOrInternalCaller();
+        if (VpnConfig.LEGACY_VPN.equals(packageName)) {
+            Log.w(TAG, "Not setting legacy VPN \"" + packageName + "\" as always-on.");
+            return false;
+        }
 
-        // Disconnect current VPN.
-        prepareInternal(VpnConfig.LEGACY_VPN);
-
-        // Pre-authorize new always-on VPN package.
         if (packageName != null) {
+            // Pre-authorize new always-on VPN package.
             if (!setPackageAuthorization(packageName, true)) {
                 return false;
             }
-            prepareInternal(packageName);
+            mAlwaysOn = true;
+        } else {
+            packageName = VpnConfig.LEGACY_VPN;
+            mAlwaysOn = false;
         }
 
-        mAlwaysOn = (packageName != null);
         mLockdown = (mAlwaysOn && lockdown);
+        if (!isCurrentPreparedPackage(packageName)) {
+            prepareInternal(packageName);
+        }
         maybeRegisterPackageChangeReceiverLocked(packageName);
         setVpnForcedLocked(mLockdown);
         return true;
     }
 
+    private static boolean isNullOrLegacyVpn(String packageName) {
+        return packageName == null || VpnConfig.LEGACY_VPN.equals(packageName);
+    }
+
     private void unregisterPackageChangeReceiverLocked() {
         // register previous intent filter
         if (mIsPackageIntentReceiverRegistered) {
@@ -293,7 +304,7 @@
         // Unregister IntentFilter listening for previous always-on package change
         unregisterPackageChangeReceiverLocked();
 
-        if (packageName != null) {
+        if (!isNullOrLegacyVpn(packageName)) {
             mIsPackageIntentReceiverRegistered = true;
 
             IntentFilter intentFilter = new IntentFilter();
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index 27b3aa2..8589de1 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -692,8 +692,13 @@
         boolean active = mPendingJobs.size() > 0;
         if (mPendingJobs.size() <= 0) {
             for (int i=0; i<mActiveServices.size(); i++) {
-                JobServiceContext jsc = mActiveServices.get(i);
-                if (jsc.getRunningJob() != null) {
+                final JobServiceContext jsc = mActiveServices.get(i);
+                final JobStatus job = jsc.getRunningJob();
+                if (job != null
+                        && (job.getJob().getFlags() & JobInfo.FLAG_WILL_BE_FOREGROUND) == 0
+                        && !job.dozeWhitelisted) {
+                    // We will report active if we have a job running and it is not an exception
+                    // due to being in the foreground or whitelisted.
                     active = true;
                     break;
                 }
diff --git a/services/core/java/com/android/server/job/controllers/AppIdleController.java b/services/core/java/com/android/server/job/controllers/AppIdleController.java
index a23af35..2dbecbd 100644
--- a/services/core/java/com/android/server/job/controllers/AppIdleController.java
+++ b/services/core/java/com/android/server/job/controllers/AppIdleController.java
@@ -142,8 +142,11 @@
                 UserHandle.formatUid(pw, jobStatus.getSourceUid());
                 pw.print(": ");
                 pw.print(jobStatus.getSourcePackageName());
-                pw.print(", runnable=");
-                pw.println((jobStatus.satisfiedConstraints&JobStatus.CONSTRAINT_APP_NOT_IDLE) != 0);
+                if ((jobStatus.satisfiedConstraints&JobStatus.CONSTRAINT_APP_NOT_IDLE) != 0) {
+                    pw.println(" RUNNABLE");
+                } else {
+                    pw.println(" WAITING");
+                }
             }
         });
     }
diff --git a/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java b/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java
index bf1297f..f7706d7 100644
--- a/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java
+++ b/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java
@@ -157,8 +157,9 @@
     }
 
     private void updateTaskStateLocked(JobStatus task) {
-        boolean enableTask = !mDeviceIdleMode || isWhitelistedLocked(task);
-        task.setDeviceNotDozingConstraintSatisfied(enableTask);
+        final boolean whitelisted = isWhitelistedLocked(task);
+        final boolean enableTask = !mDeviceIdleMode || whitelisted;
+        task.setDeviceNotDozingConstraintSatisfied(enableTask, whitelisted);
     }
 
     @Override
@@ -186,9 +187,13 @@
                 UserHandle.formatUid(pw, jobStatus.getSourceUid());
                 pw.print(": ");
                 pw.print(jobStatus.getSourcePackageName());
-                pw.print(", runnable=");
-                pw.println((jobStatus.satisfiedConstraints
-                        & JobStatus.CONSTRAINT_DEVICE_NOT_DOZING) != 0);
+                pw.print((jobStatus.satisfiedConstraints
+                        & JobStatus.CONSTRAINT_DEVICE_NOT_DOZING) != 0
+                                ? " RUNNABLE" : " WAITING");
+                if (jobStatus.dozeWhitelisted) {
+                    pw.print(" WHITELISTED");
+                }
+                pw.println();
             }
         });
     }
diff --git a/services/core/java/com/android/server/job/controllers/JobStatus.java b/services/core/java/com/android/server/job/controllers/JobStatus.java
index ded7a2f..552c990 100644
--- a/services/core/java/com/android/server/job/controllers/JobStatus.java
+++ b/services/core/java/com/android/server/job/controllers/JobStatus.java
@@ -103,6 +103,9 @@
     final int requiredConstraints;
     int satisfiedConstraints = 0;
 
+    // Set to true if doze constraint was satisfied due to app being whitelisted.
+    public boolean dozeWhitelisted;
+
     // These are filled in by controllers when preparing for execution.
     public ArraySet<Uri> changedUris;
     public ArraySet<String> changedAuthorities;
@@ -403,7 +406,8 @@
         return setConstraintSatisfied(CONSTRAINT_CONTENT_TRIGGER, state);
     }
 
-    boolean setDeviceNotDozingConstraintSatisfied(boolean state) {
+    boolean setDeviceNotDozingConstraintSatisfied(boolean state, boolean whitelisted) {
+        dozeWhitelisted = whitelisted;
         return setConstraintSatisfied(CONSTRAINT_DEVICE_NOT_DOZING, state);
     }
 
@@ -651,6 +655,9 @@
             pw.print(prefix); pw.print("Unsatisfied constraints:");
             dumpConstraints(pw, (requiredConstraints & ~satisfiedConstraints));
             pw.println();
+            if (dozeWhitelisted) {
+                pw.print(prefix); pw.println("Doze whitelisted: true");
+            }
         }
         if (changedAuthorities != null) {
             pw.print(prefix); pw.println("Changed authorities:");
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 0af0c73..d4ee02e 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -2614,17 +2614,18 @@
     private static void setPendingIntentWhitelistDuration(ActivityManagerInternal am, long duration,
             Bundle extras) {
         for (String key : extras.keySet()) {
-            setPendingIntentWhitelistDuration(am, duration, extras.getParcelable(key));
-            final Parcelable[] parcelableArray = extras.getParcelableArray(key);
-            if (parcelableArray != null) {
-                for (Parcelable parcelable: parcelableArray) {
+            final Object value = extras.get(key);
+            if (value instanceof Parcelable) {
+                setPendingIntentWhitelistDuration(am, duration, (Parcelable) value);
+            } else if (value instanceof Parcelable[]) {
+                for (Parcelable parcelable : (Parcelable[]) value) {
                     setPendingIntentWhitelistDuration(am, duration, parcelable);
                 }
-            }
-            final ArrayList<Parcelable> parcelableList = extras.getParcelableArrayList(key);
-            if (parcelableList != null) {
-                for (Parcelable parcelable: parcelableList) {
-                    setPendingIntentWhitelistDuration(am, duration, parcelable);
+            } else if (value instanceof List) {
+                for (Object element : (List <?>) value) {
+                    if (element instanceof Parcelable) {
+                        setPendingIntentWhitelistDuration(am, duration, (Parcelable) element);
+                    }
                 }
             }
         }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index a560c83..ada0a6b 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -4753,10 +4753,8 @@
                 final SharedUserSetting sus = (SharedUserSetting) obj;
                 final int N = sus.packages.size();
                 final String[] res = new String[N];
-                final Iterator<PackageSetting> it = sus.packages.iterator();
-                int i = 0;
-                while (it.hasNext()) {
-                    res[i++] = it.next().name;
+                for (int i = 0; i < N; i++) {
+                    res[i] = sus.packages.valueAt(i).name;
                 }
                 return res;
             } else if (obj instanceof PackageSetting) {
@@ -14478,7 +14476,9 @@
         // Remove existing system package
         removePackageLI(deletedPackage, true);
 
-        disabledSystem = disableSystemPackageLPw(deletedPackage, pkg);
+        synchronized (mPackages) {
+            disabledSystem = disableSystemPackageLPw(deletedPackage, pkg);
+        }
         if (!disabledSystem) {
             // We didn't need to disable the .apk as a current system package,
             // which means we are replacing another update that is already
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 */);
+    }
+
 }