Import Android SDK Platform P [4386628]

/google/data/ro/projects/android/fetch_artifact \
    --bid 4386628 \
    --target sdk_phone_armv7-win_sdk \
    sdk-repo-linux-sources-4386628.zip

AndroidVersion.ApiLevel has been modified to appear as 28

Change-Id: I9b8400ac92116cae4f033d173f7a5682b26ccba9
diff --git a/com/android/commands/pm/Pm.java b/com/android/commands/pm/Pm.java
index ad989de..c5c38f5 100644
--- a/com/android/commands/pm/Pm.java
+++ b/com/android/commands/pm/Pm.java
@@ -416,7 +416,7 @@
                     PackageLite pkgLite = new PackageLite(null, baseApk, null, null, null, null,
                             null, null);
                     params.sessionParams.setSize(
-                            PackageHelper.calculateInstalledSize(pkgLite, false,
+                            PackageHelper.calculateInstalledSize(pkgLite,
                             params.sessionParams.abiOverride));
                 } catch (PackageParserException | IOException e) {
                     System.err.println("Error: Failed to parse APK file: " + e);
@@ -636,7 +636,7 @@
             out = session.openWrite(splitName, 0, sizeBytes);
 
             int total = 0;
-            byte[] buffer = new byte[65536];
+            byte[] buffer = new byte[1024 * 1024];
             int c;
             while ((c = in.read(buffer)) != -1) {
                 total += c;
diff --git a/com/android/defcontainer/DefaultContainerService.java b/com/android/defcontainer/DefaultContainerService.java
index 3800e6f..4a771eb 100644
--- a/com/android/defcontainer/DefaultContainerService.java
+++ b/com/android/defcontainer/DefaultContainerService.java
@@ -16,8 +16,6 @@
 
 package com.android.defcontainer;
 
-import static com.android.internal.content.NativeLibraryHelper.LIB_DIR_NAME;
-
 import android.app.IntentService;
 import android.content.Context;
 import android.content.Intent;
@@ -31,21 +29,15 @@
 import android.content.res.ObbInfo;
 import android.content.res.ObbScanner;
 import android.os.Binder;
-import android.os.Environment;
 import android.os.Environment.UserEnvironment;
-import android.os.FileUtils;
 import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.system.ErrnoException;
-import android.system.Os;
-import android.system.StructStatVfs;
 import android.util.Slog;
 
 import com.android.internal.app.IMediaContainerService;
-import com.android.internal.content.NativeLibraryHelper;
 import com.android.internal.content.PackageHelper;
 import com.android.internal.os.IParcelFileDescriptorFactory;
 import com.android.internal.util.ArrayUtils;
@@ -72,51 +64,6 @@
 
     private IMediaContainerService.Stub mBinder = new IMediaContainerService.Stub() {
         /**
-         * Creates a new container and copies package there.
-         *
-         * @param packagePath absolute path to the package to be copied. Can be
-         *            a single monolithic APK file or a cluster directory
-         *            containing one or more APKs.
-         * @param containerId the id of the secure container that should be used
-         *            for creating a secure container into which the resource
-         *            will be copied.
-         * @param key Refers to key used for encrypting the secure container
-         * @return Returns the new cache path where the resource has been copied
-         *         into
-         */
-        @Override
-        public String copyPackageToContainer(String packagePath, String containerId, String key,
-                boolean isExternal, boolean isForwardLocked, String abiOverride) {
-            if (packagePath == null || containerId == null) {
-                return null;
-            }
-
-            if (isExternal) {
-                // Make sure the sdcard is mounted.
-                String status = Environment.getExternalStorageState();
-                if (!status.equals(Environment.MEDIA_MOUNTED)) {
-                    Slog.w(TAG, "Make sure sdcard is mounted.");
-                    return null;
-                }
-            }
-
-            PackageLite pkg = null;
-            NativeLibraryHelper.Handle handle = null;
-            try {
-                final File packageFile = new File(packagePath);
-                pkg = PackageParser.parsePackageLite(packageFile, 0);
-                handle = NativeLibraryHelper.Handle.create(pkg);
-                return copyPackageToContainerInner(pkg, handle, containerId, key, isExternal,
-                        isForwardLocked, abiOverride);
-            } catch (PackageParserException | IOException e) {
-                Slog.w(TAG, "Failed to copy package at " + packagePath, e);
-                return null;
-            } finally {
-                IoUtils.closeQuietly(handle);
-            }
-        }
-
-        /**
          * Copy package to the target location.
          *
          * @param packagePath absolute path to the package to be copied. Can be
@@ -153,7 +100,6 @@
         public PackageInfoLite getMinimalPackageInfo(String packagePath, int flags,
                 String abiOverride) {
             final Context context = DefaultContainerService.this;
-            final boolean isForwardLocked = (flags & PackageManager.INSTALL_FORWARD_LOCK) != 0;
 
             PackageInfoLite ret = new PackageInfoLite();
             if (packagePath == null) {
@@ -167,7 +113,7 @@
             final long sizeBytes;
             try {
                 pkg = PackageParser.parsePackageLite(packageFile, 0);
-                sizeBytes = PackageHelper.calculateInstalledSize(pkg, isForwardLocked, abiOverride);
+                sizeBytes = PackageHelper.calculateInstalledSize(pkg, abiOverride);
             } catch (PackageParserException | IOException e) {
                 Slog.w(TAG, "Failed to parse package at " + packagePath + ": " + e);
 
@@ -230,13 +176,13 @@
          *            containing one or more APKs.
          */
         @Override
-        public long calculateInstalledSize(String packagePath, boolean isForwardLocked,
-                String abiOverride) throws RemoteException {
+        public long calculateInstalledSize(String packagePath, String abiOverride)
+                throws RemoteException {
             final File packageFile = new File(packagePath);
             final PackageParser.PackageLite pkg;
             try {
                 pkg = PackageParser.parsePackageLite(packageFile, 0);
-                return PackageHelper.calculateInstalledSize(pkg, isForwardLocked, abiOverride);
+                return PackageHelper.calculateInstalledSize(pkg, abiOverride);
             } catch (PackageParserException | IOException e) {
                 Slog.w(TAG, "Failed to calculate installed size: " + e);
                 return Long.MAX_VALUE;
@@ -292,60 +238,6 @@
         return mBinder;
     }
 
-    private String copyPackageToContainerInner(PackageLite pkg, NativeLibraryHelper.Handle handle,
-            String newCid, String key, boolean isExternal, boolean isForwardLocked,
-            String abiOverride) throws IOException {
-
-        // Calculate container size, rounding up to nearest MB and adding an
-        // extra MB for filesystem overhead
-        final long sizeBytes = PackageHelper.calculateInstalledSize(pkg, handle,
-                isForwardLocked, abiOverride);
-
-        // Create new container
-        final String newMountPath = PackageHelper.createSdDir(sizeBytes, newCid, key,
-                Process.myUid(), isExternal);
-        if (newMountPath == null) {
-            throw new IOException("Failed to create container " + newCid);
-        }
-        final File targetDir = new File(newMountPath);
-
-        try {
-            // Copy all APKs
-            copyFile(pkg.baseCodePath, targetDir, "base.apk", isForwardLocked);
-            if (!ArrayUtils.isEmpty(pkg.splitNames)) {
-                for (int i = 0; i < pkg.splitNames.length; i++) {
-                    copyFile(pkg.splitCodePaths[i], targetDir,
-                            "split_" + pkg.splitNames[i] + ".apk", isForwardLocked);
-                }
-            }
-
-            // Extract native code
-            final File libraryRoot = new File(targetDir, LIB_DIR_NAME);
-            final int res = NativeLibraryHelper.copyNativeBinariesWithOverride(handle, libraryRoot,
-                    abiOverride);
-            if (res != PackageManager.INSTALL_SUCCEEDED) {
-                throw new IOException("Failed to extract native code, res=" + res);
-            }
-
-            if (!PackageHelper.finalizeSdDir(newCid)) {
-                throw new IOException("Failed to finalize " + newCid);
-            }
-
-            if (PackageHelper.isContainerMounted(newCid)) {
-                PackageHelper.unMountSdDir(newCid);
-            }
-
-        } catch (ErrnoException e) {
-            PackageHelper.destroySdDir(newCid);
-            throw e.rethrowAsIOException();
-        } catch (IOException e) {
-            PackageHelper.destroySdDir(newCid);
-            throw e;
-        }
-
-        return newMountPath;
-    }
-
     private int copyPackageInner(PackageLite pkg, IParcelFileDescriptorFactory target)
             throws IOException, RemoteException {
         copyFile(pkg.baseCodePath, target, "base.apk");
@@ -373,28 +265,4 @@
             IoUtils.closeQuietly(in);
         }
     }
-
-    private void copyFile(String sourcePath, File targetDir, String targetName,
-            boolean isForwardLocked) throws IOException, ErrnoException {
-        final File sourceFile = new File(sourcePath);
-        final File targetFile = new File(targetDir, targetName);
-
-        Slog.d(TAG, "Copying " + sourceFile + " to " + targetFile);
-        if (!FileUtils.copyFile(sourceFile, targetFile)) {
-            throw new IOException("Failed to copy " + sourceFile + " to " + targetFile);
-        }
-
-        if (isForwardLocked) {
-            final String publicTargetName = PackageHelper.replaceEnd(targetName,
-                    ".apk", ".zip");
-            final File publicTargetFile = new File(targetDir, publicTargetName);
-
-            PackageHelper.extractPublicFiles(sourceFile, publicTargetFile);
-
-            Os.chmod(targetFile.getAbsolutePath(), 0640);
-            Os.chmod(publicTargetFile.getAbsolutePath(), 0644);
-        } else {
-            Os.chmod(targetFile.getAbsolutePath(), 0644);
-        }
-    }
 }
diff --git a/com/android/ims/ImsCallProfile.java b/com/android/ims/ImsCallProfile.java
index 36abfc9..489c208 100644
--- a/com/android/ims/ImsCallProfile.java
+++ b/com/android/ims/ImsCallProfile.java
@@ -19,7 +19,9 @@
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.PersistableBundle;
 import android.telecom.VideoProfile;
+import android.util.Log;
 
 import com.android.internal.telephony.PhoneConstants;
 
@@ -216,6 +218,29 @@
     public int mServiceType;
     public int mCallType;
     public int mRestrictCause = CALL_RESTRICT_CAUSE_NONE;
+
+    /**
+     * Extras associated with this {@link ImsCallProfile}.
+     * <p>
+     * Valid data types include:
+     * <ul>
+     *     <li>{@link Integer} (and int)</li>
+     *     <li>{@link Long} (and long)</li>
+     *     <li>{@link Double} (and double)</li>
+     *     <li>{@link String}</li>
+     *     <li>{@code int[]}</li>
+     *     <li>{@code long[]}</li>
+     *     <li>{@code double[]}</li>
+     *     <li>{@code String[]}</li>
+     *     <li>{@link PersistableBundle}</li>
+     *     <li>{@link Boolean} (and boolean)</li>
+     *     <li>{@code boolean[]}</li>
+     *     <li>Other {@link Parcelable} classes in the {@code android.*} namespace.</li>
+     * </ul>
+     * <p>
+     * Invalid types will be removed when the {@link ImsCallProfile} is parceled for transmit across
+     * a {@link android.os.Binder}.
+     */
     public Bundle mCallExtras;
     public ImsStreamMediaProfile mMediaProfile;
 
@@ -315,16 +340,17 @@
 
     @Override
     public void writeToParcel(Parcel out, int flags) {
+        Bundle filteredExtras = maybeCleanseExtras(mCallExtras);
         out.writeInt(mServiceType);
         out.writeInt(mCallType);
-        out.writeParcelable(mCallExtras, 0);
+        out.writeBundle(filteredExtras);
         out.writeParcelable(mMediaProfile, 0);
     }
 
     private void readFromParcel(Parcel in) {
         mServiceType = in.readInt();
         mCallType = in.readInt();
-        mCallExtras = in.readParcelable(null);
+        mCallExtras = in.readBundle();
         mMediaProfile = in.readParcelable(null);
     }
 
@@ -465,6 +491,31 @@
     }
 
     /**
+     * Cleanses a {@link Bundle} to ensure that it contains only data of type:
+     * 1. Primitive data types (e.g. int, bool, and other values determined by
+     * {@link android.os.PersistableBundle#isValidType(Object)}).
+     * 2. Other Bundles.
+     * 3. {@link Parcelable} objects in the {@code android.*} namespace.
+     * @param extras the source {@link Bundle}
+     * @return where all elements are valid types the source {@link Bundle} is returned unmodified,
+     *      otherwise a copy of the {@link Bundle} with the invalid elements is returned.
+     */
+    private Bundle maybeCleanseExtras(Bundle extras) {
+        if (extras == null) {
+            return null;
+        }
+
+        int startSize = extras.size();
+        Bundle filtered = extras.filterValues();
+        int endSize = filtered.size();
+        if (startSize != endSize) {
+            Log.i(TAG, "maybeCleanseExtras: " + (startSize - endSize) + " extra values were "
+                    + "removed - only primitive types and system parcelables are permitted.");
+        }
+        return filtered;
+    }
+
+    /**
      * Determines if a video state is set in a video state bit-mask.
      *
      * @param videoState The video state bit mask.
diff --git a/com/android/internal/alsa/AlsaDevicesParser.java b/com/android/internal/alsa/AlsaDevicesParser.java
index 7cdd897..6e3d596 100644
--- a/com/android/internal/alsa/AlsaDevicesParser.java
+++ b/com/android/internal/alsa/AlsaDevicesParser.java
@@ -258,7 +258,7 @@
         return line.charAt(kIndex_CardDeviceField) == '[';
     }
 
-    public void scan() {
+    public boolean scan() {
         mDeviceRecords.clear();
 
         File devicesFile = new File(kDevicesFilePath);
@@ -274,11 +274,13 @@
                 }
             }
             reader.close();
+            return true;
         } catch (FileNotFoundException e) {
             e.printStackTrace();
         } catch (IOException e) {
             e.printStackTrace();
         }
+        return false;
     }
 
     //
diff --git a/com/android/internal/app/ChooserActivity.java b/com/android/internal/app/ChooserActivity.java
index 2cab009..6e0ba34 100644
--- a/com/android/internal/app/ChooserActivity.java
+++ b/com/android/internal/app/ChooserActivity.java
@@ -100,7 +100,7 @@
     private static final boolean DEBUG = false;
 
     private static final int QUERY_TARGET_SERVICE_LIMIT = 5;
-    private static final int WATCHDOG_TIMEOUT_MILLIS = 5000;
+    private static final int WATCHDOG_TIMEOUT_MILLIS = 2000;
 
     private Bundle mReplacementExtras;
     private IntentSender mChosenComponentSender;
@@ -1450,11 +1450,16 @@
                         getFirstRowPosition(rowPosition + 1));
                 int serviceSpacing = holder.row.getContext().getResources()
                         .getDimensionPixelSize(R.dimen.chooser_service_spacing);
-                int top = rowPosition == 0 ? serviceSpacing : 0;
-                if (nextStartType != ChooserListAdapter.TARGET_SERVICE) {
-                    setVertPadding(holder, top, serviceSpacing);
+                if (rowPosition == 0 && nextStartType != ChooserListAdapter.TARGET_SERVICE) {
+                    // if the row is the only row for target service
+                    setVertPadding(holder, 0, 0);
                 } else {
-                    setVertPadding(holder, top, 0);
+                    int top = rowPosition == 0 ? serviceSpacing : 0;
+                    if (nextStartType != ChooserListAdapter.TARGET_SERVICE) {
+                        setVertPadding(holder, top, serviceSpacing);
+                    } else {
+                        setVertPadding(holder, top, 0);
+                    }
                 }
             } else {
                 holder.row.setBackgroundColor(Color.TRANSPARENT);
@@ -1580,8 +1585,8 @@
                 } catch (RemoteException e) {
                     Log.e(TAG, "Querying ChooserTargetService " + name + " failed.", e);
                     mChooserActivity.unbindService(this);
-                    destroy();
                     mChooserActivity.mServiceConnections.remove(this);
+                    destroy();
                 }
             }
         }
@@ -1597,7 +1602,6 @@
                 }
 
                 mChooserActivity.unbindService(this);
-                destroy();
                 mChooserActivity.mServiceConnections.remove(this);
                 if (mChooserActivity.mServiceConnections.isEmpty()) {
                     mChooserActivity.mChooserHandler.removeMessages(
@@ -1605,6 +1609,7 @@
                     mChooserActivity.sendVoiceChoicesIfNeeded();
                 }
                 mConnectedComponent = null;
+                destroy();
             }
         }
 
diff --git a/com/android/internal/app/NightDisplayController.java b/com/android/internal/app/NightDisplayController.java
index 860c5c4..7a1383c 100644
--- a/com/android/internal/app/NightDisplayController.java
+++ b/com/android/internal/app/NightDisplayController.java
@@ -32,8 +32,12 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.util.Calendar;
-import java.util.Locale;
+import java.time.DateTimeException;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeParseException;
 
 /**
  * Controller for managing Night display settings.
@@ -116,8 +120,9 @@
      */
     public boolean setActivated(boolean activated) {
         if (isActivated() != activated) {
-            Secure.putLongForUser(mContext.getContentResolver(),
-                    Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME, System.currentTimeMillis(),
+            Secure.putStringForUser(mContext.getContentResolver(),
+                    Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME,
+                    LocalDateTime.now().toString(),
                     mUserId);
         }
         return Secure.putIntForUser(mContext.getContentResolver(),
@@ -128,17 +133,22 @@
      * Returns the time when Night display's activation state last changed, or {@code null} if it
      * has never been changed.
      */
-    public Calendar getLastActivatedTime() {
+    public LocalDateTime getLastActivatedTime() {
         final ContentResolver cr = mContext.getContentResolver();
-        final long lastActivatedTimeMillis = Secure.getLongForUser(
-                cr, Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME, -1, mUserId);
-        if (lastActivatedTimeMillis < 0) {
-            return null;
+        final String lastActivatedTime = Secure.getStringForUser(
+                cr, Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME, mUserId);
+        if (lastActivatedTime != null) {
+            try {
+                return LocalDateTime.parse(lastActivatedTime);
+            } catch (DateTimeParseException ignored) {}
+            // Uses the old epoch time.
+            try {
+                return LocalDateTime.ofInstant(
+                    Instant.ofEpochMilli(Long.parseLong(lastActivatedTime)),
+                    ZoneId.systemDefault());
+            } catch (DateTimeException|NumberFormatException ignored) {}
         }
-
-        final Calendar lastActivatedTime = Calendar.getInstance();
-        lastActivatedTime.setTimeInMillis(lastActivatedTimeMillis);
-        return lastActivatedTime;
+        return null;
     }
 
     /**
@@ -183,8 +193,10 @@
         }
 
         if (getAutoMode() != autoMode) {
-            Secure.putLongForUser(mContext.getContentResolver(),
-                    Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME, -1L, mUserId);
+            Secure.putStringForUser(mContext.getContentResolver(),
+                    Secure.NIGHT_DISPLAY_LAST_ACTIVATED_TIME,
+                    null,
+                    mUserId);
         }
         return Secure.putIntForUser(mContext.getContentResolver(),
                 Secure.NIGHT_DISPLAY_AUTO_MODE, autoMode, mUserId);
@@ -206,7 +218,7 @@
                     R.integer.config_defaultNightDisplayCustomStartTime);
         }
 
-        return LocalTime.valueOf(startTimeValue);
+        return LocalTime.ofSecondOfDay(startTimeValue / 1000);
     }
 
     /**
@@ -221,7 +233,7 @@
             throw new IllegalArgumentException("startTime cannot be null");
         }
         return Secure.putIntForUser(mContext.getContentResolver(),
-                Secure.NIGHT_DISPLAY_CUSTOM_START_TIME, startTime.toMillis(), mUserId);
+                Secure.NIGHT_DISPLAY_CUSTOM_START_TIME, startTime.toSecondOfDay() * 1000, mUserId);
     }
 
     /**
@@ -240,7 +252,7 @@
                     R.integer.config_defaultNightDisplayCustomEndTime);
         }
 
-        return LocalTime.valueOf(endTimeValue);
+        return LocalTime.ofSecondOfDay(endTimeValue / 1000);
     }
 
     /**
@@ -255,7 +267,7 @@
             throw new IllegalArgumentException("endTime cannot be null");
         }
         return Secure.putIntForUser(mContext.getContentResolver(),
-                Secure.NIGHT_DISPLAY_CUSTOM_END_TIME, endTime.toMillis(), mUserId);
+                Secure.NIGHT_DISPLAY_CUSTOM_END_TIME, endTime.toSecondOfDay() * 1000, mUserId);
     }
 
     /**
@@ -379,106 +391,6 @@
     }
 
     /**
-     * A time without a time-zone or date.
-     */
-    public static class LocalTime {
-
-        /**
-         * The hour of the day from 0 - 23.
-         */
-        public final int hourOfDay;
-        /**
-         * The minute within the hour from 0 - 59.
-         */
-        public final int minute;
-
-        public LocalTime(int hourOfDay, int minute) {
-            if (hourOfDay < 0 || hourOfDay > 23) {
-                throw new IllegalArgumentException("Invalid hourOfDay: " + hourOfDay);
-            } else if (minute < 0 || minute > 59) {
-                throw new IllegalArgumentException("Invalid minute: " + minute);
-            }
-
-            this.hourOfDay = hourOfDay;
-            this.minute = minute;
-        }
-
-        /**
-         * Returns the first date time corresponding to this local time that occurs before the
-         * provided date time.
-         *
-         * @param time the date time to compare against
-         * @return the prior date time corresponding to this local time
-         */
-        public Calendar getDateTimeBefore(Calendar time) {
-            final Calendar c = Calendar.getInstance();
-            c.set(Calendar.YEAR, time.get(Calendar.YEAR));
-            c.set(Calendar.DAY_OF_YEAR, time.get(Calendar.DAY_OF_YEAR));
-
-            c.set(Calendar.HOUR_OF_DAY, hourOfDay);
-            c.set(Calendar.MINUTE, minute);
-            c.set(Calendar.SECOND, 0);
-            c.set(Calendar.MILLISECOND, 0);
-
-            // Check if the local time has past, if so return the same time tomorrow.
-            if (c.after(time)) {
-                c.add(Calendar.DATE, -1);
-            }
-
-            return c;
-        }
-
-        /**
-         * Returns the first date time corresponding to this local time that occurs after the
-         * provided date time.
-         *
-         * @param time the date time to compare against
-         * @return the next date time corresponding to this local time
-         */
-        public Calendar getDateTimeAfter(Calendar time) {
-            final Calendar c = Calendar.getInstance();
-            c.set(Calendar.YEAR, time.get(Calendar.YEAR));
-            c.set(Calendar.DAY_OF_YEAR, time.get(Calendar.DAY_OF_YEAR));
-
-            c.set(Calendar.HOUR_OF_DAY, hourOfDay);
-            c.set(Calendar.MINUTE, minute);
-            c.set(Calendar.SECOND, 0);
-            c.set(Calendar.MILLISECOND, 0);
-
-            // Check if the local time has past, if so return the same time tomorrow.
-            if (c.before(time)) {
-                c.add(Calendar.DATE, 1);
-            }
-
-            return c;
-        }
-
-        /**
-         * Returns a local time corresponding the given number of milliseconds from midnight.
-         *
-         * @param millis the number of milliseconds from midnight
-         * @return the corresponding local time
-         */
-        private static LocalTime valueOf(int millis) {
-            final int hourOfDay = (millis / 3600000) % 24;
-            final int minutes = (millis / 60000) % 60;
-            return new LocalTime(hourOfDay, minutes);
-        }
-
-        /**
-         * Returns the local time represented as milliseconds from midnight.
-         */
-        private int toMillis() {
-            return hourOfDay * 3600000 + minute * 60000;
-        }
-
-        @Override
-        public String toString() {
-            return String.format(Locale.US, "%02d:%02d", hourOfDay, minute);
-        }
-    }
-
-    /**
      * Callback invoked whenever the Night display settings are changed.
      */
     public interface Callback {
diff --git a/com/android/internal/app/ShutdownActivity.java b/com/android/internal/app/ShutdownActivity.java
index 745d28f..f81e838 100644
--- a/com/android/internal/app/ShutdownActivity.java
+++ b/com/android/internal/app/ShutdownActivity.java
@@ -41,6 +41,9 @@
         mReboot = Intent.ACTION_REBOOT.equals(intent.getAction());
         mConfirm = intent.getBooleanExtra(Intent.EXTRA_KEY_CONFIRM, false);
         mUserRequested = intent.getBooleanExtra(Intent.EXTRA_USER_REQUESTED_SHUTDOWN, false);
+        final String reason = mUserRequested
+                ? PowerManager.SHUTDOWN_USER_REQUESTED
+                : intent.getStringExtra(Intent.EXTRA_REASON);
         Slog.i(TAG, "onCreate(): confirm=" + mConfirm);
 
         Thread thr = new Thread("ShutdownActivity") {
@@ -52,9 +55,7 @@
                     if (mReboot) {
                         pm.reboot(mConfirm, null, false);
                     } else {
-                        pm.shutdown(mConfirm,
-                                    mUserRequested ? PowerManager.SHUTDOWN_USER_REQUESTED : null,
-                                    false);
+                        pm.shutdown(mConfirm, reason, false);
                     }
                 } catch (RemoteException e) {
                 }
diff --git a/com/android/internal/app/procstats/DumpUtils.java b/com/android/internal/app/procstats/DumpUtils.java
index ebedc89..0bc8c48 100644
--- a/com/android/internal/app/procstats/DumpUtils.java
+++ b/com/android/internal/app/procstats/DumpUtils.java
@@ -29,6 +29,7 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
 
 import static com.android.internal.app.procstats.ProcessStats.*;
 
@@ -66,6 +67,8 @@
             "cch-activity", "cch-aclient", "cch-empty"
     };
 
+    // State enum is defined in frameworks/base/core/proto/android/service/procstats.proto
+    // Update states must sync enum definition as well, the ordering must not be changed.
     static final String[] ADJ_SCREEN_TAGS = new String[] {
             "0", "1"
     };
@@ -177,6 +180,13 @@
         printArrayEntry(pw, STATE_TAGS,  state, 1);
     }
 
+    public static void printProcStateTagProto(ProtoOutputStream proto, long screenId, long memId,
+            long stateId, int state) {
+        state = printProto(proto, screenId, ADJ_SCREEN_TAGS, state, ADJ_SCREEN_MOD * STATE_COUNT);
+        state = printProto(proto, memId, ADJ_MEM_TAGS, state, STATE_COUNT);
+        printProto(proto, stateId, STATE_TAGS, state, 1);
+    }
+
     public static void printAdjTag(PrintWriter pw, int state) {
         state = printArrayEntry(pw, ADJ_SCREEN_TAGS,  state, ADJ_SCREEN_MOD);
         printArrayEntry(pw, ADJ_MEM_TAGS, state, 1);
@@ -352,6 +362,15 @@
         return value - index*mod;
     }
 
+    public static int printProto(ProtoOutputStream proto, long fieldId, String[] array, int value, int mod) {
+        int index = value/mod;
+        if (index >= 0 && index < array.length) {
+            // Valid state enum number starts at 1, 0 stands for unknown.
+            proto.write(fieldId, index + 1);
+        } // else enum default is always zero in proto3
+        return value - index*mod;
+    }
+
     public static String collapseString(String pkgName, String itemName) {
         if (itemName.startsWith(pkgName)) {
             final int ITEMLEN = itemName.length();
diff --git a/com/android/internal/app/procstats/ProcessState.java b/com/android/internal/app/procstats/ProcessState.java
index e0a4053..7519fce 100644
--- a/com/android/internal/app/procstats/ProcessState.java
+++ b/com/android/internal/app/procstats/ProcessState.java
@@ -21,6 +21,8 @@
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.service.pm.PackageProto;
+import android.service.procstats.ProcessStatsProto;
 import android.text.format.DateFormat;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -29,6 +31,8 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
+import android.util.proto.ProtoUtils;
 
 import com.android.internal.app.procstats.ProcessStats;
 import com.android.internal.app.procstats.ProcessStats.PackageState;
@@ -69,6 +73,9 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 
 public final class ProcessState {
@@ -1157,6 +1164,7 @@
         }
     }
 
+    @Override
     public String toString() {
         StringBuilder sb = new StringBuilder(128);
         sb.append("ProcessState{").append(Integer.toHexString(System.identityHashCode(this)))
@@ -1167,4 +1175,76 @@
         sb.append("}");
         return sb.toString();
     }
+
+    public void toProto(ProtoOutputStream proto, String procName, int uid, long now) {
+        proto.write(ProcessStatsProto.PROCESS, procName);
+        proto.write(ProcessStatsProto.UID, uid);
+        if (mNumExcessiveCpu > 0 || mNumCachedKill > 0 ) {
+            final long killToken = proto.start(ProcessStatsProto.KILL);
+            proto.write(ProcessStatsProto.Kill.CPU, mNumExcessiveCpu);
+            proto.write(ProcessStatsProto.Kill.CACHED, mNumCachedKill);
+            ProtoUtils.toAggStatsProto(proto, ProcessStatsProto.Kill.CACHED_PSS,
+                    mMinCachedKillPss, mAvgCachedKillPss, mMaxCachedKillPss);
+            proto.end(killToken);
+        }
+
+        // Group proc stats by type (screen state + mem state + process state)
+        Map<Integer, Long> durationByState = new HashMap<>();
+        boolean didCurState = false;
+        for (int i=0; i<mDurations.getKeyCount(); i++) {
+            final int key = mDurations.getKeyAt(i);
+            final int type = SparseMappingTable.getIdFromKey(key);
+            long time = mDurations.getValue(key);
+            if (mCurState == type) {
+                didCurState = true;
+                time += now - mStartTime;
+            }
+            durationByState.put(type, time);
+        }
+        if (!didCurState && mCurState != STATE_NOTHING) {
+            durationByState.put(mCurState, now - mStartTime);
+        }
+
+        for (int i=0; i<mPssTable.getKeyCount(); i++) {
+            final int key = mPssTable.getKeyAt(i);
+            final int type = SparseMappingTable.getIdFromKey(key);
+            if (!durationByState.containsKey(type)) {
+                // state without duration should not have stats!
+                continue;
+            }
+            final long stateToken = proto.start(ProcessStatsProto.STATES);
+            DumpUtils.printProcStateTagProto(proto,
+                    ProcessStatsProto.State.SCREEN_STATE,
+                    ProcessStatsProto.State.MEMORY_STATE,
+                    ProcessStatsProto.State.PROCESS_STATE,
+                    type);
+
+            long duration = durationByState.get(type);
+            durationByState.remove(type); // remove the key since it is already being dumped.
+            proto.write(ProcessStatsProto.State.DURATION_MS, duration);
+
+            proto.write(ProcessStatsProto.State.SAMPLE_SIZE, mPssTable.getValue(key, PSS_SAMPLE_COUNT));
+            ProtoUtils.toAggStatsProto(proto, ProcessStatsProto.State.PSS,
+                    mPssTable.getValue(key, PSS_MINIMUM),
+                    mPssTable.getValue(key, PSS_AVERAGE),
+                    mPssTable.getValue(key, PSS_MAXIMUM));
+            ProtoUtils.toAggStatsProto(proto, ProcessStatsProto.State.USS,
+                    mPssTable.getValue(key, PSS_USS_MINIMUM),
+                    mPssTable.getValue(key, PSS_USS_AVERAGE),
+                    mPssTable.getValue(key, PSS_USS_MAXIMUM));
+
+            proto.end(stateToken);
+        }
+
+        for (Map.Entry<Integer, Long> entry : durationByState.entrySet()) {
+            final long stateToken = proto.start(ProcessStatsProto.STATES);
+            DumpUtils.printProcStateTagProto(proto,
+                    ProcessStatsProto.State.SCREEN_STATE,
+                    ProcessStatsProto.State.MEMORY_STATE,
+                    ProcessStatsProto.State.PROCESS_STATE,
+                    entry.getKey());
+            proto.write(ProcessStatsProto.State.DURATION_MS, entry.getValue());
+            proto.end(stateToken);
+        }
+    }
 }
diff --git a/com/android/internal/app/procstats/ProcessStats.java b/com/android/internal/app/procstats/ProcessStats.java
index 35b53c2..14f5e5b 100644
--- a/com/android/internal/app/procstats/ProcessStats.java
+++ b/com/android/internal/app/procstats/ProcessStats.java
@@ -22,6 +22,7 @@
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.service.procstats.ProcessStatsSectionProto;
 import android.text.format.DateFormat;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -30,6 +31,7 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.app.ProcessMap;
 import com.android.internal.app.procstats.DurationsTable;
@@ -1706,6 +1708,46 @@
         }
     }
 
+    public void toProto(ProtoOutputStream proto, long now) {
+        final ArrayMap<String, SparseArray<SparseArray<PackageState>>> pkgMap = mPackages.getMap();
+
+        proto.write(ProcessStatsSectionProto.START_REALTIME_MS, mTimePeriodStartRealtime);
+        proto.write(ProcessStatsSectionProto.END_REALTIME_MS,
+                mRunning ? SystemClock.elapsedRealtime() : mTimePeriodEndRealtime);
+        proto.write(ProcessStatsSectionProto.START_UPTIME_MS, mTimePeriodStartUptime);
+        proto.write(ProcessStatsSectionProto.END_UPTIME_MS, mTimePeriodEndUptime);
+        proto.write(ProcessStatsSectionProto.RUNTIME, mRuntime);
+        proto.write(ProcessStatsSectionProto.HAS_SWAPPED_PSS, mHasSwappedOutPss);
+        boolean partial = true;
+        if ((mFlags&FLAG_SHUTDOWN) != 0) {
+            proto.write(ProcessStatsSectionProto.STATUS, ProcessStatsSectionProto.STATUS_SHUTDOWN);
+            partial = false;
+        }
+        if ((mFlags&FLAG_SYSPROPS) != 0) {
+            proto.write(ProcessStatsSectionProto.STATUS, ProcessStatsSectionProto.STATUS_SYSPROPS);
+            partial = false;
+        }
+        if ((mFlags&FLAG_COMPLETE) != 0) {
+            proto.write(ProcessStatsSectionProto.STATUS, ProcessStatsSectionProto.STATUS_COMPLETE);
+            partial = false;
+        }
+        if (partial) {
+            proto.write(ProcessStatsSectionProto.STATUS, ProcessStatsSectionProto.STATUS_PARTIAL);
+        }
+
+        ArrayMap<String, SparseArray<ProcessState>> procMap = mProcesses.getMap();
+        for (int ip=0; ip<procMap.size(); ip++) {
+            String procName = procMap.keyAt(ip);
+            SparseArray<ProcessState> uids = procMap.valueAt(ip);
+            for (int iu=0; iu<uids.size(); iu++) {
+                final int uid = uids.keyAt(iu);
+                final ProcessState procState = uids.valueAt(iu);
+                final long processStateToken = proto.start(ProcessStatsSectionProto.PROCESS_STATS);
+                procState.toProto(proto, procName, uid, now);
+                proto.end(processStateToken);
+            }
+        }
+    }
 
     final public static class ProcessStateHolder {
         public final int appVersion;
diff --git a/com/android/internal/content/PackageHelper.java b/com/android/internal/content/PackageHelper.java
index e923223..59a7995 100644
--- a/com/android/internal/content/PackageHelper.java
+++ b/com/android/internal/content/PackageHelper.java
@@ -16,7 +16,6 @@
 
 package com.android.internal.content;
 
-import static android.net.TrafficStats.MB_IN_BYTES;
 import static android.os.storage.VolumeInfo.ID_PRIVATE_INTERNAL;
 
 import android.content.Context;
@@ -27,13 +26,11 @@
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.PackageParser.PackageLite;
 import android.os.Environment;
-import android.os.FileUtils;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.storage.IStorageManager;
 import android.os.storage.StorageManager;
-import android.os.storage.StorageResultCode;
 import android.os.storage.StorageVolume;
 import android.os.storage.VolumeInfo;
 import android.provider.Settings;
@@ -45,15 +42,9 @@
 import libcore.io.IoUtils;
 
 import java.io.File;
-import java.io.FileOutputStream;
 import java.io.IOException;
-import java.io.InputStream;
-import java.util.Collections;
 import java.util.Objects;
 import java.util.UUID;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipFile;
-import java.util.zip.ZipOutputStream;
 
 /**
  * Constants used internally between the PackageManager
@@ -72,7 +63,6 @@
     public static final int RECOMMEND_FAILED_INVALID_URI = -6;
     public static final int RECOMMEND_FAILED_VERSION_DOWNGRADE = -7;
 
-    private static final boolean localLOGV = false;
     private static final String TAG = "PackageHelper";
     // App installation location settings values
     public static final int APP_INSTALL_AUTO = 0;
@@ -91,259 +81,6 @@
         }
     }
 
-    public static String createSdDir(long sizeBytes, String cid, String sdEncKey, int uid,
-            boolean isExternal) {
-        // Round up to nearest MB, plus another MB for filesystem overhead
-        final int sizeMb = (int) ((sizeBytes + MB_IN_BYTES) / MB_IN_BYTES) + 1;
-        try {
-            IStorageManager storageManager = getStorageManager();
-
-            if (localLOGV)
-                Log.i(TAG, "Size of container " + sizeMb + " MB");
-
-            int rc = storageManager.createSecureContainer(cid, sizeMb, "ext4", sdEncKey, uid,
-                    isExternal);
-            if (rc != StorageResultCode.OperationSucceeded) {
-                Log.e(TAG, "Failed to create secure container " + cid);
-                return null;
-            }
-            String cachePath = storageManager.getSecureContainerPath(cid);
-            if (localLOGV) Log.i(TAG, "Created secure container " + cid +
-                    " at " + cachePath);
-                return cachePath;
-        } catch (RemoteException e) {
-            Log.e(TAG, "StorageManagerService running?");
-        }
-        return null;
-    }
-
-    public static boolean resizeSdDir(long sizeBytes, String cid, String sdEncKey) {
-        // Round up to nearest MB, plus another MB for filesystem overhead
-        final int sizeMb = (int) ((sizeBytes + MB_IN_BYTES) / MB_IN_BYTES) + 1;
-        try {
-            IStorageManager storageManager = getStorageManager();
-            int rc = storageManager.resizeSecureContainer(cid, sizeMb, sdEncKey);
-            if (rc == StorageResultCode.OperationSucceeded) {
-                return true;
-            }
-        } catch (RemoteException e) {
-            Log.e(TAG, "StorageManagerService running?");
-        }
-        Log.e(TAG, "Failed to create secure container " + cid);
-        return false;
-    }
-
-    public static String mountSdDir(String cid, String key, int ownerUid) {
-        return mountSdDir(cid, key, ownerUid, true);
-    }
-
-    public static String mountSdDir(String cid, String key, int ownerUid, boolean readOnly) {
-        try {
-            int rc = getStorageManager().mountSecureContainer(cid, key, ownerUid, readOnly);
-            if (rc != StorageResultCode.OperationSucceeded) {
-                Log.i(TAG, "Failed to mount container " + cid + " rc : " + rc);
-                return null;
-            }
-            return getStorageManager().getSecureContainerPath(cid);
-        } catch (RemoteException e) {
-            Log.e(TAG, "StorageManagerService running?");
-        }
-        return null;
-    }
-
-   public static boolean unMountSdDir(String cid) {
-    try {
-        int rc = getStorageManager().unmountSecureContainer(cid, true);
-        if (rc != StorageResultCode.OperationSucceeded) {
-            Log.e(TAG, "Failed to unmount " + cid + " with rc " + rc);
-            return false;
-        }
-        return true;
-    } catch (RemoteException e) {
-        Log.e(TAG, "StorageManagerService running?");
-    }
-        return false;
-   }
-
-   public static boolean renameSdDir(String oldId, String newId) {
-       try {
-           int rc = getStorageManager().renameSecureContainer(oldId, newId);
-           if (rc != StorageResultCode.OperationSucceeded) {
-               Log.e(TAG, "Failed to rename " + oldId + " to " +
-                       newId + "with rc " + rc);
-               return false;
-           }
-           return true;
-       } catch (RemoteException e) {
-           Log.i(TAG, "Failed ot rename  " + oldId + " to " + newId +
-                   " with exception : " + e);
-       }
-       return false;
-   }
-
-   public static String getSdDir(String cid) {
-       try {
-            return getStorageManager().getSecureContainerPath(cid);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Failed to get container path for " + cid +
-                " with exception " + e);
-        }
-        return null;
-   }
-
-   public static String getSdFilesystem(String cid) {
-       try {
-            return getStorageManager().getSecureContainerFilesystemPath(cid);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Failed to get container path for " + cid +
-                " with exception " + e);
-        }
-        return null;
-   }
-
-    public static boolean finalizeSdDir(String cid) {
-        try {
-            int rc = getStorageManager().finalizeSecureContainer(cid);
-            if (rc != StorageResultCode.OperationSucceeded) {
-                Log.i(TAG, "Failed to finalize container " + cid);
-                return false;
-            }
-            return true;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Failed to finalize container " + cid +
-                    " with exception " + e);
-        }
-        return false;
-    }
-
-    public static boolean destroySdDir(String cid) {
-        try {
-            if (localLOGV) Log.i(TAG, "Forcibly destroying container " + cid);
-            int rc = getStorageManager().destroySecureContainer(cid, true);
-            if (rc != StorageResultCode.OperationSucceeded) {
-                Log.i(TAG, "Failed to destroy container " + cid);
-                return false;
-            }
-            return true;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Failed to destroy container " + cid +
-                    " with exception " + e);
-        }
-        return false;
-    }
-
-    public static String[] getSecureContainerList() {
-        try {
-            return getStorageManager().getSecureContainerList();
-        } catch (RemoteException e) {
-            Log.e(TAG, "Failed to get secure container list with exception" +
-                    e);
-        }
-        return null;
-    }
-
-   public static boolean isContainerMounted(String cid) {
-       try {
-           return getStorageManager().isSecureContainerMounted(cid);
-       } catch (RemoteException e) {
-           Log.e(TAG, "Failed to find out if container " + cid + " mounted");
-       }
-       return false;
-   }
-
-    /**
-     * Extract public files for the single given APK.
-     */
-    public static long extractPublicFiles(File apkFile, File publicZipFile)
-            throws IOException {
-        final FileOutputStream fstr;
-        final ZipOutputStream publicZipOutStream;
-
-        if (publicZipFile == null) {
-            fstr = null;
-            publicZipOutStream = null;
-        } else {
-            fstr = new FileOutputStream(publicZipFile);
-            publicZipOutStream = new ZipOutputStream(fstr);
-            Log.d(TAG, "Extracting " + apkFile + " to " + publicZipFile);
-        }
-
-        long size = 0L;
-
-        try {
-            final ZipFile privateZip = new ZipFile(apkFile.getAbsolutePath());
-            try {
-                // Copy manifest, resources.arsc and res directory to public zip
-                for (final ZipEntry zipEntry : Collections.list(privateZip.entries())) {
-                    final String zipEntryName = zipEntry.getName();
-                    if ("AndroidManifest.xml".equals(zipEntryName)
-                            || "resources.arsc".equals(zipEntryName)
-                            || zipEntryName.startsWith("res/")) {
-                        size += zipEntry.getSize();
-                        if (publicZipFile != null) {
-                            copyZipEntry(zipEntry, privateZip, publicZipOutStream);
-                        }
-                    }
-                }
-            } finally {
-                try { privateZip.close(); } catch (IOException e) {}
-            }
-
-            if (publicZipFile != null) {
-                publicZipOutStream.finish();
-                publicZipOutStream.flush();
-                FileUtils.sync(fstr);
-                publicZipOutStream.close();
-                FileUtils.setPermissions(publicZipFile.getAbsolutePath(), FileUtils.S_IRUSR
-                        | FileUtils.S_IWUSR | FileUtils.S_IRGRP | FileUtils.S_IROTH, -1, -1);
-            }
-        } finally {
-            IoUtils.closeQuietly(publicZipOutStream);
-        }
-
-        return size;
-    }
-
-    private static void copyZipEntry(ZipEntry zipEntry, ZipFile inZipFile,
-            ZipOutputStream outZipStream) throws IOException {
-        byte[] buffer = new byte[4096];
-        int num;
-
-        ZipEntry newEntry;
-        if (zipEntry.getMethod() == ZipEntry.STORED) {
-            // Preserve the STORED method of the input entry.
-            newEntry = new ZipEntry(zipEntry);
-        } else {
-            // Create a new entry so that the compressed len is recomputed.
-            newEntry = new ZipEntry(zipEntry.getName());
-        }
-        outZipStream.putNextEntry(newEntry);
-
-        final InputStream data = inZipFile.getInputStream(zipEntry);
-        try {
-            while ((num = data.read(buffer)) > 0) {
-                outZipStream.write(buffer, 0, num);
-            }
-            outZipStream.flush();
-        } finally {
-            IoUtils.closeQuietly(data);
-        }
-    }
-
-    public static boolean fixSdPermissions(String cid, int gid, String filename) {
-        try {
-            int rc = getStorageManager().fixPermissionsSecureContainer(cid, gid, filename);
-            if (rc != StorageResultCode.OperationSucceeded) {
-                Log.i(TAG, "Failed to fixperms container " + cid);
-                return false;
-            }
-            return true;
-        } catch (RemoteException e) {
-            Log.e(TAG, "Failed to fixperms container " + cid + " with exception " + e);
-        }
-        return false;
-    }
-
     /**
      * A group of external dependencies used in
      * {@link #resolveInstallVolume(Context, String, int, long)}. It can be backed by real values
@@ -638,29 +375,37 @@
         return PackageHelper.RECOMMEND_FAILED_INSUFFICIENT_STORAGE;
     }
 
+    @Deprecated
     public static long calculateInstalledSize(PackageLite pkg, boolean isForwardLocked,
             String abiOverride) throws IOException {
+        return calculateInstalledSize(pkg, abiOverride);
+    }
+
+    public static long calculateInstalledSize(PackageLite pkg, String abiOverride)
+            throws IOException {
         NativeLibraryHelper.Handle handle = null;
         try {
             handle = NativeLibraryHelper.Handle.create(pkg);
-            return calculateInstalledSize(pkg, handle, isForwardLocked, abiOverride);
+            return calculateInstalledSize(pkg, handle, abiOverride);
         } finally {
             IoUtils.closeQuietly(handle);
         }
     }
 
+    @Deprecated
+    public static long calculateInstalledSize(PackageLite pkg, boolean isForwardLocked,
+            NativeLibraryHelper.Handle handle, String abiOverride) throws IOException {
+        return calculateInstalledSize(pkg, handle, abiOverride);
+    }
+
     public static long calculateInstalledSize(PackageLite pkg, NativeLibraryHelper.Handle handle,
-            boolean isForwardLocked, String abiOverride) throws IOException {
+            String abiOverride) throws IOException {
         long sizeBytes = 0;
 
         // Include raw APKs, and possibly unpacked resources
         for (String codePath : pkg.getAllCodePaths()) {
             final File codeFile = new File(codePath);
             sizeBytes += codeFile.length();
-
-            if (isForwardLocked) {
-                sizeBytes += PackageHelper.extractPublicFiles(codeFile, null);
-            }
         }
 
         // Include all relevant native code
diff --git a/com/android/internal/os/BatteryStatsHelper.java b/com/android/internal/os/BatteryStatsHelper.java
index f085e29..15dc6f5 100644
--- a/com/android/internal/os/BatteryStatsHelper.java
+++ b/com/android/internal/os/BatteryStatsHelper.java
@@ -143,6 +143,9 @@
     public static boolean checkWifiOnly(Context context) {
         ConnectivityManager cm = (ConnectivityManager) context.getSystemService(
                 Context.CONNECTIVITY_SERVICE);
+        if (cm == null) {
+            return false;
+        }
         return !cm.isNetworkSupported(ConnectivityManager.TYPE_MOBILE);
     }
 
diff --git a/com/android/internal/os/BatteryStatsImpl.java b/com/android/internal/os/BatteryStatsImpl.java
index 0bd2981..36fd991 100644
--- a/com/android/internal/os/BatteryStatsImpl.java
+++ b/com/android/internal/os/BatteryStatsImpl.java
@@ -119,7 +119,7 @@
     private static final int MAGIC = 0xBA757475; // 'BATSTATS'
 
     // Current on-disk Parcel version
-    private static final int VERSION = 166 + (USE_OLD_HISTORY ? 1000 : 0);
+    private static final int VERSION = 167 + (USE_OLD_HISTORY ? 1000 : 0);
 
     // Maximum number of items we will record in the history.
     private static final int MAX_HISTORY_ITEMS;
@@ -341,8 +341,8 @@
     protected final TimeBase mOnBatteryTimeBase = new TimeBase();
 
     // These are the objects that will want to do something when the device
-    // is unplugged from power *and* the screen is off.
-    final TimeBase mOnBatteryScreenOffTimeBase = new TimeBase();
+    // is unplugged from power *and* the screen is off or doze.
+    protected final TimeBase mOnBatteryScreenOffTimeBase = new TimeBase();
 
     // Set to true when we want to distribute CPU across wakelocks for the next
     // CPU update, even if we aren't currently running wake locks.
@@ -436,8 +436,12 @@
     public boolean mRecordAllHistory;
     boolean mNoAutoReset;
 
-    int mScreenState = Display.STATE_UNKNOWN;
-    StopwatchTimer mScreenOnTimer;
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    protected int mScreenState = Display.STATE_UNKNOWN;
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    protected StopwatchTimer mScreenOnTimer;
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    protected StopwatchTimer mScreenDozeTimer;
 
     int mScreenBrightnessBin = -1;
     final StopwatchTimer[] mScreenBrightnessTimer = new StopwatchTimer[NUM_SCREEN_BRIGHTNESS_BINS];
@@ -583,12 +587,16 @@
     int mHighDischargeAmountSinceCharge;
     int mDischargeScreenOnUnplugLevel;
     int mDischargeScreenOffUnplugLevel;
+    int mDischargeScreenDozeUnplugLevel;
     int mDischargeAmountScreenOn;
     int mDischargeAmountScreenOnSinceCharge;
     int mDischargeAmountScreenOff;
     int mDischargeAmountScreenOffSinceCharge;
+    int mDischargeAmountScreenDoze;
+    int mDischargeAmountScreenDozeSinceCharge;
 
     private LongSamplingCounter mDischargeScreenOffCounter;
+    private LongSamplingCounter mDischargeScreenDozeCounter;
     private LongSamplingCounter mDischargeCounter;
 
     static final int MAX_LEVEL_STEPS = 200;
@@ -673,13 +681,18 @@
     }
 
     @Override
-    public LongCounter getDischargeScreenOffCoulombCounter() {
-        return mDischargeScreenOffCounter;
+    public long getMahDischarge(int which) {
+        return mDischargeCounter.getCountLocked(which);
     }
 
     @Override
-    public LongCounter getDischargeCoulombCounter() {
-        return mDischargeCounter;
+    public long getMahDischargeScreenOff(int which) {
+        return mDischargeScreenOffCounter.getCountLocked(which);
+    }
+
+    @Override
+    public long getMahDischargeScreenDoze(int which) {
+        return mDischargeScreenDozeCounter.getCountLocked(which);
     }
 
     @Override
@@ -3573,8 +3586,9 @@
         mActiveHistoryStates2 = 0xffffffff;
     }
 
-    public void updateTimeBasesLocked(boolean unplugged, boolean screenOff, long uptime,
+    public void updateTimeBasesLocked(boolean unplugged, int screenState, long uptime,
             long realtime) {
+        final boolean screenOff = isScreenOff(screenState) || isScreenDoze(screenState);
         final boolean updateOnBatteryTimeBase = unplugged != mOnBatteryTimeBase.isRunning();
         final boolean updateOnBatteryScreenOffTimeBase =
                 (unplugged && screenOff) != mOnBatteryScreenOffTimeBase.isRunning();
@@ -3591,20 +3605,22 @@
                 updateRpmStatsLocked(); // if either OnBattery or OnBatteryScreenOff timebase changes.
             }
             if (DEBUG_ENERGY_CPU) {
-                Slog.d(TAG, "Updating cpu time because screen is now " + (screenOff ? "off" : "on")
+                Slog.d(TAG, "Updating cpu time because screen is now "
+                        + Display.stateToString(screenState)
                         + " and battery is " + (unplugged ? "on" : "off"));
             }
             updateCpuTimeLocked();
 
             mOnBatteryTimeBase.setRunning(unplugged, uptime, realtime);
-            mOnBatteryScreenOffTimeBase.setRunning(unplugged && screenOff, uptime, realtime);
-            for (int i = mUidStats.size() - 1; i >= 0; --i) {
-                final Uid u = mUidStats.valueAt(i);
-                if (updateOnBatteryTimeBase) {
-                    u.updateOnBatteryBgTimeBase(uptime, realtime);
+            if (updateOnBatteryTimeBase) {
+                for (int i = mUidStats.size() - 1; i >= 0; --i) {
+                    mUidStats.valueAt(i).updateOnBatteryBgTimeBase(uptime, realtime);
                 }
-                if (updateOnBatteryScreenOffTimeBase) {
-                    u.updateOnBatteryScreenOffBgTimeBase(uptime, realtime);
+            }
+            if (updateOnBatteryScreenOffTimeBase) {
+                mOnBatteryScreenOffTimeBase.setRunning(unplugged && screenOff, uptime, realtime);
+                for (int i = mUidStats.size() - 1; i >= 0; --i) {
+                    mUidStats.valueAt(i).updateOnBatteryScreenOffBgTimeBase(uptime, realtime);
                 }
             }
         }
@@ -3864,8 +3880,10 @@
     }
 
     public void setPretendScreenOff(boolean pretendScreenOff) {
-        mPretendScreenOff = pretendScreenOff;
-        noteScreenStateLocked(pretendScreenOff ? Display.STATE_OFF : Display.STATE_ON);
+        if (mPretendScreenOff != pretendScreenOff) {
+            mPretendScreenOff = pretendScreenOff;
+            noteScreenStateLocked(pretendScreenOff ? Display.STATE_OFF : Display.STATE_ON);
+        }
     }
 
     private String mInitialAcquireWakeName;
@@ -4195,54 +4213,58 @@
                 }
             }
 
-            if (state == Display.STATE_ON) {
-                // Screen turning on.
-                final long elapsedRealtime = mClocks.elapsedRealtime();
-                final long uptime = mClocks.uptimeMillis();
+            final long elapsedRealtime = mClocks.elapsedRealtime();
+            final long uptime = mClocks.uptimeMillis();
+
+            boolean updateHistory = false;
+            if (isScreenDoze(state)) {
+                mHistoryCur.states |= HistoryItem.STATE_SCREEN_DOZE_FLAG;
+                mScreenDozeTimer.startRunningLocked(elapsedRealtime);
+                updateHistory = true;
+            } else if (isScreenDoze(oldState)) {
+                mHistoryCur.states &= ~HistoryItem.STATE_SCREEN_DOZE_FLAG;
+                mScreenDozeTimer.stopRunningLocked(elapsedRealtime);
+                updateHistory = true;
+            }
+            if (isScreenOn(state)) {
                 mHistoryCur.states |= HistoryItem.STATE_SCREEN_ON_FLAG;
                 if (DEBUG_HISTORY) Slog.v(TAG, "Screen on to: "
                         + Integer.toHexString(mHistoryCur.states));
-                addHistoryRecordLocked(elapsedRealtime, uptime);
                 mScreenOnTimer.startRunningLocked(elapsedRealtime);
                 if (mScreenBrightnessBin >= 0) {
                     mScreenBrightnessTimer[mScreenBrightnessBin].startRunningLocked(elapsedRealtime);
                 }
-
-                updateTimeBasesLocked(mOnBatteryTimeBase.isRunning(), false,
-                        mClocks.uptimeMillis() * 1000, elapsedRealtime * 1000);
-
-                // Fake a wake lock, so we consider the device waked as long
-                // as the screen is on.
-                noteStartWakeLocked(-1, -1, "screen", null, WAKE_TYPE_PARTIAL, false,
-                        elapsedRealtime, uptime);
-
-                // Update discharge amounts.
-                if (mOnBatteryInternal) {
-                    updateDischargeScreenLevelsLocked(false, true);
-                }
-            } else if (oldState == Display.STATE_ON) {
-                // Screen turning off or dozing.
-                final long elapsedRealtime = mClocks.elapsedRealtime();
-                final long uptime = mClocks.uptimeMillis();
+                updateHistory = true;
+            } else if (isScreenOn(oldState)) {
                 mHistoryCur.states &= ~HistoryItem.STATE_SCREEN_ON_FLAG;
                 if (DEBUG_HISTORY) Slog.v(TAG, "Screen off to: "
                         + Integer.toHexString(mHistoryCur.states));
-                addHistoryRecordLocked(elapsedRealtime, uptime);
                 mScreenOnTimer.stopRunningLocked(elapsedRealtime);
                 if (mScreenBrightnessBin >= 0) {
                     mScreenBrightnessTimer[mScreenBrightnessBin].stopRunningLocked(elapsedRealtime);
                 }
-
+                updateHistory = true;
+            }
+            if (updateHistory) {
+                if (DEBUG_HISTORY) Slog.v(TAG, "Screen state to: "
+                        + Display.stateToString(state));
+                addHistoryRecordLocked(elapsedRealtime, uptime);
+            }
+            if (isScreenOn(state)) {
+                updateTimeBasesLocked(mOnBatteryTimeBase.isRunning(), state,
+                        mClocks.uptimeMillis() * 1000, elapsedRealtime * 1000);
+                // Fake a wake lock, so we consider the device waked as long as the screen is on.
+                noteStartWakeLocked(-1, -1, "screen", null, WAKE_TYPE_PARTIAL, false,
+                        elapsedRealtime, uptime);
+            } else if (isScreenOn(oldState)) {
                 noteStopWakeLocked(-1, -1, "screen", "screen", WAKE_TYPE_PARTIAL,
                         elapsedRealtime, uptime);
-
-                updateTimeBasesLocked(mOnBatteryTimeBase.isRunning(), true,
+                updateTimeBasesLocked(mOnBatteryTimeBase.isRunning(), state,
                         mClocks.uptimeMillis() * 1000, elapsedRealtime * 1000);
-
-                // Update discharge amounts.
-                if (mOnBatteryInternal) {
-                    updateDischargeScreenLevelsLocked(true, false);
-                }
+            }
+            // Update discharge amounts.
+            if (mOnBatteryInternal) {
+                updateDischargeScreenLevelsLocked(oldState, state);
             }
         }
     }
@@ -5391,6 +5413,14 @@
         return mScreenOnTimer.getCountLocked(which);
     }
 
+    @Override public long getScreenDozeTime(long elapsedRealtimeUs, int which) {
+        return mScreenDozeTimer.getTotalTimeLocked(elapsedRealtimeUs, which);
+    }
+
+    @Override public int getScreenDozeCount(int which) {
+        return mScreenDozeTimer.getCountLocked(which);
+    }
+
     @Override public long getScreenBrightnessTime(int brightnessBin,
             long elapsedRealtimeUs, int which) {
         return mScreenBrightnessTimer[brightnessBin].getTotalTimeLocked(
@@ -8829,6 +8859,7 @@
         mHandler = new MyHandler(handler.getLooper());
         mStartCount++;
         mScreenOnTimer = new StopwatchTimer(mClocks, null, -1, null, mOnBatteryTimeBase);
+        mScreenDozeTimer = new StopwatchTimer(mClocks, null, -1, null, mOnBatteryTimeBase);
         for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
             mScreenBrightnessTimer[i] = new StopwatchTimer(mClocks, null, -100-i, null,
                     mOnBatteryTimeBase);
@@ -8887,6 +8918,7 @@
         mCameraOnTimer = new StopwatchTimer(mClocks, null, -13, null, mOnBatteryTimeBase);
         mBluetoothScanTimer = new StopwatchTimer(mClocks, null, -14, null, mOnBatteryTimeBase);
         mDischargeScreenOffCounter = new LongSamplingCounter(mOnBatteryScreenOffTimeBase);
+        mDischargeScreenDozeCounter = new LongSamplingCounter(mOnBatteryTimeBase);
         mDischargeCounter = new LongSamplingCounter(mOnBatteryTimeBase);
         mOnBattery = mOnBatteryInternal = false;
         long uptime = mClocks.uptimeMillis() * 1000;
@@ -9430,8 +9462,16 @@
         return mCharging;
     }
 
-    public boolean isScreenOn() {
-        return mScreenState == Display.STATE_ON;
+    public boolean isScreenOn(int state) {
+        return state == Display.STATE_ON;
+    }
+
+    public boolean isScreenOff(int state) {
+        return state == Display.STATE_OFF;
+    }
+
+    public boolean isScreenDoze(int state) {
+        return state == Display.STATE_DOZE || state == Display.STATE_DOZE_SUSPEND;
     }
 
     void initTimes(long uptime, long realtime) {
@@ -9451,9 +9491,12 @@
         mDischargeAmountScreenOnSinceCharge = 0;
         mDischargeAmountScreenOff = 0;
         mDischargeAmountScreenOffSinceCharge = 0;
+        mDischargeAmountScreenDoze = 0;
+        mDischargeAmountScreenDozeSinceCharge = 0;
         mDischargeStepTracker.init();
         mChargeStepTracker.init();
         mDischargeScreenOffCounter.reset(false);
+        mDischargeScreenDozeCounter.reset(false);
         mDischargeCounter.reset(false);
     }
 
@@ -9471,15 +9514,22 @@
         mOnBatteryTimeBase.reset(uptime, realtime);
         mOnBatteryScreenOffTimeBase.reset(uptime, realtime);
         if ((mHistoryCur.states&HistoryItem.STATE_BATTERY_PLUGGED_FLAG) == 0) {
-            if (mScreenState == Display.STATE_ON) {
+            if (isScreenOn(mScreenState)) {
                 mDischargeScreenOnUnplugLevel = mHistoryCur.batteryLevel;
+                mDischargeScreenDozeUnplugLevel = 0;
+                mDischargeScreenOffUnplugLevel = 0;
+            } else if (isScreenDoze(mScreenState)) {
+                mDischargeScreenOnUnplugLevel = 0;
+                mDischargeScreenDozeUnplugLevel = mHistoryCur.batteryLevel;
                 mDischargeScreenOffUnplugLevel = 0;
             } else {
                 mDischargeScreenOnUnplugLevel = 0;
+                mDischargeScreenDozeUnplugLevel = 0;
                 mDischargeScreenOffUnplugLevel = mHistoryCur.batteryLevel;
             }
             mDischargeAmountScreenOn = 0;
             mDischargeAmountScreenOff = 0;
+            mDischargeAmountScreenDoze = 0;
         }
         initActiveHistoryEventsLocked(mSecRealtime, mSecUptime);
     }
@@ -9490,6 +9540,7 @@
         mStartCount = 0;
         initTimes(uptimeMillis * 1000, elapsedRealtimeMillis * 1000);
         mScreenOnTimer.reset(false);
+        mScreenDozeTimer.reset(false);
         for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
             mScreenBrightnessTimer[i].reset(false);
         }
@@ -9626,33 +9677,52 @@
         }
     }
 
-    void updateDischargeScreenLevelsLocked(boolean oldScreenOn, boolean newScreenOn) {
-        if (oldScreenOn) {
+    void updateDischargeScreenLevelsLocked(int oldState, int newState) {
+        updateOldDischargeScreenLevelLocked(oldState);
+        updateNewDischargeScreenLevelLocked(newState);
+    }
+
+    private void updateOldDischargeScreenLevelLocked(int state) {
+        if (isScreenOn(state)) {
             int diff = mDischargeScreenOnUnplugLevel - mDischargeCurrentLevel;
             if (diff > 0) {
                 mDischargeAmountScreenOn += diff;
                 mDischargeAmountScreenOnSinceCharge += diff;
             }
-        } else {
+        } else if (isScreenDoze(state)) {
+            int diff = mDischargeScreenDozeUnplugLevel - mDischargeCurrentLevel;
+            if (diff > 0) {
+                mDischargeAmountScreenDoze += diff;
+                mDischargeAmountScreenDozeSinceCharge += diff;
+            }
+        } else if (isScreenOff(state)){
             int diff = mDischargeScreenOffUnplugLevel - mDischargeCurrentLevel;
             if (diff > 0) {
                 mDischargeAmountScreenOff += diff;
                 mDischargeAmountScreenOffSinceCharge += diff;
             }
         }
-        if (newScreenOn) {
+    }
+
+    private void updateNewDischargeScreenLevelLocked(int state) {
+        if (isScreenOn(state)) {
             mDischargeScreenOnUnplugLevel = mDischargeCurrentLevel;
             mDischargeScreenOffUnplugLevel = 0;
-        } else {
+            mDischargeScreenDozeUnplugLevel = 0;
+        } else if (isScreenDoze(state)){
             mDischargeScreenOnUnplugLevel = 0;
+            mDischargeScreenDozeUnplugLevel = mDischargeCurrentLevel;
+            mDischargeScreenOffUnplugLevel = 0;
+        } else if (isScreenOff(state)) {
+            mDischargeScreenOnUnplugLevel = 0;
+            mDischargeScreenDozeUnplugLevel = 0;
             mDischargeScreenOffUnplugLevel = mDischargeCurrentLevel;
         }
     }
 
     public void pullPendingStateUpdatesLocked() {
         if (mOnBatteryInternal) {
-            final boolean screenOn = mScreenState == Display.STATE_ON;
-            updateDischargeScreenLevelsLocked(screenOn, screenOn);
+            updateDischargeScreenLevelsLocked(mScreenState, mScreenState);
         }
     }
 
@@ -10785,8 +10855,8 @@
         return false;
     }
 
-    void setOnBatteryLocked(final long mSecRealtime, final long mSecUptime, final boolean onBattery,
-            final int oldStatus, final int level, final int chargeUAh) {
+    protected void setOnBatteryLocked(final long mSecRealtime, final long mSecUptime,
+            final boolean onBattery, final int oldStatus, final int level, final int chargeUAh) {
         boolean doWrite = false;
         Message m = mHandler.obtainMessage(MSG_REPORT_POWER_CHANGE);
         m.arg1 = onBattery ? 1 : 0;
@@ -10794,7 +10864,7 @@
 
         final long uptime = mSecUptime * 1000;
         final long realtime = mSecRealtime * 1000;
-        final boolean screenOn = mScreenState == Display.STATE_ON;
+        final int screenState = mScreenState;
         if (onBattery) {
             // We will reset our status if we are unplugging after the
             // battery was last full, or the level is at 100, or
@@ -10870,16 +10940,23 @@
             }
             addHistoryRecordLocked(mSecRealtime, mSecUptime);
             mDischargeCurrentLevel = mDischargeUnplugLevel = level;
-            if (screenOn) {
+            if (isScreenOn(screenState)) {
                 mDischargeScreenOnUnplugLevel = level;
+                mDischargeScreenDozeUnplugLevel = 0;
+                mDischargeScreenOffUnplugLevel = 0;
+            } else if (isScreenDoze(screenState)) {
+                mDischargeScreenOnUnplugLevel = 0;
+                mDischargeScreenDozeUnplugLevel = level;
                 mDischargeScreenOffUnplugLevel = 0;
             } else {
                 mDischargeScreenOnUnplugLevel = 0;
+                mDischargeScreenDozeUnplugLevel = 0;
                 mDischargeScreenOffUnplugLevel = level;
             }
             mDischargeAmountScreenOn = 0;
+            mDischargeAmountScreenDoze = 0;
             mDischargeAmountScreenOff = 0;
-            updateTimeBasesLocked(true, !screenOn, uptime, realtime);
+            updateTimeBasesLocked(true, screenState, uptime, realtime);
         } else {
             mLastChargingStateLevel = level;
             mOnBattery = mOnBatteryInternal = false;
@@ -10894,8 +10971,8 @@
                 mLowDischargeAmountSinceCharge += mDischargeUnplugLevel-level-1;
                 mHighDischargeAmountSinceCharge += mDischargeUnplugLevel-level;
             }
-            updateDischargeScreenLevelsLocked(screenOn, screenOn);
-            updateTimeBasesLocked(false, !screenOn, uptime, realtime);
+            updateDischargeScreenLevelsLocked(screenState, screenState);
+            updateTimeBasesLocked(false, screenState, uptime, realtime);
             mChargeStepTracker.init();
             mLastChargeStepLevel = level;
             mMaxChargeStepLevel = level;
@@ -11012,6 +11089,9 @@
                 final long chargeDiff = mHistoryCur.batteryChargeUAh - chargeUAh;
                 mDischargeCounter.addCountLocked(chargeDiff);
                 mDischargeScreenOffCounter.addCountLocked(chargeDiff);
+                if (isScreenDoze(mScreenState)) {
+                    mDischargeScreenDozeCounter.addCountLocked(chargeDiff);
+                }
             }
             mHistoryCur.batteryChargeUAh = chargeUAh;
             setOnBatteryLocked(elapsedRealtime, uptime, onBattery, oldStatus, level, chargeUAh);
@@ -11054,6 +11134,9 @@
                     final long chargeDiff = mHistoryCur.batteryChargeUAh - chargeUAh;
                     mDischargeCounter.addCountLocked(chargeDiff);
                     mDischargeScreenOffCounter.addCountLocked(chargeDiff);
+                    if (isScreenDoze(mScreenState)) {
+                        mDischargeScreenDozeCounter.addCountLocked(chargeDiff);
+                    }
                 }
                 mHistoryCur.batteryChargeUAh = chargeUAh;
                 changed = true;
@@ -11362,10 +11445,11 @@
         return dischargeAmount;
     }
 
+    @Override
     public int getDischargeAmountScreenOn() {
         synchronized(this) {
             int val = mDischargeAmountScreenOn;
-            if (mOnBattery && mScreenState == Display.STATE_ON
+            if (mOnBattery && isScreenOn(mScreenState)
                     && mDischargeCurrentLevel < mDischargeScreenOnUnplugLevel) {
                 val += mDischargeScreenOnUnplugLevel-mDischargeCurrentLevel;
             }
@@ -11373,10 +11457,11 @@
         }
     }
 
+    @Override
     public int getDischargeAmountScreenOnSinceCharge() {
         synchronized(this) {
             int val = mDischargeAmountScreenOnSinceCharge;
-            if (mOnBattery && mScreenState == Display.STATE_ON
+            if (mOnBattery && isScreenOn(mScreenState)
                     && mDischargeCurrentLevel < mDischargeScreenOnUnplugLevel) {
                 val += mDischargeScreenOnUnplugLevel-mDischargeCurrentLevel;
             }
@@ -11384,23 +11469,51 @@
         }
     }
 
+    @Override
     public int getDischargeAmountScreenOff() {
         synchronized(this) {
             int val = mDischargeAmountScreenOff;
-            if (mOnBattery && mScreenState != Display.STATE_ON
+            if (mOnBattery && isScreenOff(mScreenState)
                     && mDischargeCurrentLevel < mDischargeScreenOffUnplugLevel) {
                 val += mDischargeScreenOffUnplugLevel-mDischargeCurrentLevel;
             }
+            // For backward compatibility, doze discharge is counted into screen off.
+            return val + getDischargeAmountScreenDoze();
+        }
+    }
+
+    @Override
+    public int getDischargeAmountScreenOffSinceCharge() {
+        synchronized(this) {
+            int val = mDischargeAmountScreenOffSinceCharge;
+            if (mOnBattery && isScreenOff(mScreenState)
+                    && mDischargeCurrentLevel < mDischargeScreenOffUnplugLevel) {
+                val += mDischargeScreenOffUnplugLevel-mDischargeCurrentLevel;
+            }
+            // For backward compatibility, doze discharge is counted into screen off.
+            return val + getDischargeAmountScreenDozeSinceCharge();
+        }
+    }
+
+    @Override
+    public int getDischargeAmountScreenDoze() {
+        synchronized(this) {
+            int val = mDischargeAmountScreenDoze;
+            if (mOnBattery && isScreenDoze(mScreenState)
+                    && mDischargeCurrentLevel < mDischargeScreenDozeUnplugLevel) {
+                val += mDischargeScreenDozeUnplugLevel-mDischargeCurrentLevel;
+            }
             return val;
         }
     }
 
-    public int getDischargeAmountScreenOffSinceCharge() {
+    @Override
+    public int getDischargeAmountScreenDozeSinceCharge() {
         synchronized(this) {
-            int val = mDischargeAmountScreenOffSinceCharge;
-            if (mOnBattery && mScreenState != Display.STATE_ON
-                    && mDischargeCurrentLevel < mDischargeScreenOffUnplugLevel) {
-                val += mDischargeScreenOffUnplugLevel-mDischargeCurrentLevel;
+            int val = mDischargeAmountScreenDozeSinceCharge;
+            if (mOnBattery && isScreenDoze(mScreenState)
+                    && mDischargeCurrentLevel < mDischargeScreenDozeUnplugLevel) {
+                val += mDischargeScreenDozeUnplugLevel-mDischargeCurrentLevel;
             }
             return val;
         }
@@ -11759,12 +11872,14 @@
         mHighDischargeAmountSinceCharge = in.readInt();
         mDischargeAmountScreenOnSinceCharge = in.readInt();
         mDischargeAmountScreenOffSinceCharge = in.readInt();
+        mDischargeAmountScreenDozeSinceCharge = in.readInt();
         mDischargeStepTracker.readFromParcel(in);
         mChargeStepTracker.readFromParcel(in);
         mDailyDischargeStepTracker.readFromParcel(in);
         mDailyChargeStepTracker.readFromParcel(in);
         mDischargeCounter.readSummaryFromParcelLocked(in);
         mDischargeScreenOffCounter.readSummaryFromParcelLocked(in);
+        mDischargeScreenDozeCounter.readSummaryFromParcelLocked(in);
         int NPKG = in.readInt();
         if (NPKG > 0) {
             mDailyPackageChanges = new ArrayList<>(NPKG);
@@ -11787,6 +11902,7 @@
 
         mScreenState = Display.STATE_UNKNOWN;
         mScreenOnTimer.readSummaryFromParcelLocked(in);
+        mScreenDozeTimer.readSummaryFromParcelLocked(in);
         for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
             mScreenBrightnessTimer[i].readSummaryFromParcelLocked(in);
         }
@@ -12180,12 +12296,14 @@
         out.writeInt(getHighDischargeAmountSinceCharge());
         out.writeInt(getDischargeAmountScreenOnSinceCharge());
         out.writeInt(getDischargeAmountScreenOffSinceCharge());
+        out.writeInt(getDischargeAmountScreenDozeSinceCharge());
         mDischargeStepTracker.writeToParcel(out);
         mChargeStepTracker.writeToParcel(out);
         mDailyDischargeStepTracker.writeToParcel(out);
         mDailyChargeStepTracker.writeToParcel(out);
         mDischargeCounter.writeSummaryFromParcelLocked(out);
         mDischargeScreenOffCounter.writeSummaryFromParcelLocked(out);
+        mDischargeScreenDozeCounter.writeSummaryFromParcelLocked(out);
         if (mDailyPackageChanges != null) {
             final int NPKG = mDailyPackageChanges.size();
             out.writeInt(NPKG);
@@ -12203,6 +12321,7 @@
         out.writeLong(mNextMaxDailyDeadline);
 
         mScreenOnTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
+        mScreenDozeTimer.writeSummaryFromParcelLocked(out, NOWREAL_SYS);
         for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
             mScreenBrightnessTimer[i].writeSummaryFromParcelLocked(out, NOWREAL_SYS);
         }
@@ -12635,6 +12754,7 @@
 
         mScreenState = Display.STATE_UNKNOWN;
         mScreenOnTimer = new StopwatchTimer(mClocks, null, -1, null, mOnBatteryTimeBase, in);
+        mScreenDozeTimer = new StopwatchTimer(mClocks, null, -1, null, mOnBatteryTimeBase, in);
         for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
             mScreenBrightnessTimer[i] = new StopwatchTimer(mClocks, null, -100-i, null,
                     mOnBatteryTimeBase, in);
@@ -12728,10 +12848,13 @@
         mDischargeAmountScreenOnSinceCharge = in.readInt();
         mDischargeAmountScreenOff = in.readInt();
         mDischargeAmountScreenOffSinceCharge = in.readInt();
+        mDischargeAmountScreenDoze = in.readInt();
+        mDischargeAmountScreenDozeSinceCharge = in.readInt();
         mDischargeStepTracker.readFromParcel(in);
         mChargeStepTracker.readFromParcel(in);
         mDischargeCounter = new LongSamplingCounter(mOnBatteryTimeBase, in);
-        mDischargeScreenOffCounter = new LongSamplingCounter(mOnBatteryTimeBase, in);
+        mDischargeScreenOffCounter = new LongSamplingCounter(mOnBatteryScreenOffTimeBase, in);
+        mDischargeScreenDozeCounter = new LongSamplingCounter(mOnBatteryTimeBase, in);
         mLastWriteTime = in.readLong();
 
         mRpmStats.clear();
@@ -12848,6 +12971,7 @@
         mOnBatteryScreenOffTimeBase.writeToParcel(out, uSecUptime, uSecRealtime);
 
         mScreenOnTimer.writeToParcel(out, uSecRealtime);
+        mScreenDozeTimer.writeToParcel(out, uSecRealtime);
         for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
             mScreenBrightnessTimer[i].writeToParcel(out, uSecRealtime);
         }
@@ -12910,10 +13034,13 @@
         out.writeInt(mDischargeAmountScreenOnSinceCharge);
         out.writeInt(mDischargeAmountScreenOff);
         out.writeInt(mDischargeAmountScreenOffSinceCharge);
+        out.writeInt(mDischargeAmountScreenDoze);
+        out.writeInt(mDischargeAmountScreenDozeSinceCharge);
         mDischargeStepTracker.writeToParcel(out);
         mChargeStepTracker.writeToParcel(out);
         mDischargeCounter.writeToParcel(out);
         mDischargeScreenOffCounter.writeToParcel(out);
+        mDischargeScreenDozeCounter.writeToParcel(out);
         out.writeLong(mLastWriteTime);
 
         out.writeInt(mRpmStats.size());
@@ -13020,8 +13147,10 @@
             pw.println("mOnBatteryScreenOffTimeBase:");
             mOnBatteryScreenOffTimeBase.dump(pw, "  ");
             Printer pr = new PrintWriterPrinter(pw);
-            pr.println("*** Screen timer:");
+            pr.println("*** Screen on timer:");
             mScreenOnTimer.logState(pr, "  ");
+            pr.println("*** Screen doze timer:");
+            mScreenDozeTimer.logState(pr, "  ");
             for (int i=0; i<NUM_SCREEN_BRIGHTNESS_BINS; i++) {
                 pr.println("*** Screen brightness #" + i + ":");
                 mScreenBrightnessTimer[i].logState(pr, "  ");
diff --git a/com/android/internal/os/Zygote.java b/com/android/internal/os/Zygote.java
index 5ee0918..cbc63cf 100644
--- a/com/android/internal/os/Zygote.java
+++ b/com/android/internal/os/Zygote.java
@@ -49,6 +49,11 @@
     /** Make the code Java debuggable by turning off some optimizations. */
     public static final int DEBUG_JAVA_DEBUGGABLE = 1 << 8;
 
+    /** Turn off the verifier. */
+    public static final int DISABLE_VERIFIER = 1 << 9;
+    /** Only use oat files located in /system. Otherwise use dex/jar/apk . */
+    public static final int ONLY_USE_SYSTEM_OAT_FILES = 1 << 10;
+
     /** No external storage should be mounted. */
     public static final int MOUNT_EXTERNAL_NONE = IVold.REMOUNT_MODE_NONE;
     /** Default external storage should be mounted. */
diff --git a/com/android/internal/telephony/CallForwardInfo.java b/com/android/internal/telephony/CallForwardInfo.java
index dccf306..e40028f 100644
--- a/com/android/internal/telephony/CallForwardInfo.java
+++ b/com/android/internal/telephony/CallForwardInfo.java
@@ -16,12 +16,16 @@
 
 package com.android.internal.telephony;
 
+import android.telecom.Log;
+
 /**
  * See also RIL_CallForwardInfo in include/telephony/ril.h
  *
  * {@hide}
  */
 public class CallForwardInfo {
+    private static final String TAG = "CallForwardInfo";
+
     public int             status;      /*1 = active, 0 = not active */
     public int             reason;      /* from TS 27.007 7.11 "reason" */
     public int             serviceClass; /* Saum of CommandsInterface.SERVICE_CLASS */
@@ -31,9 +35,9 @@
 
     @Override
     public String toString() {
-        return super.toString() + (status == 0 ? " not active " : " active ")
-            + " reason: " + reason
-            + " serviceClass: " + serviceClass + " " + timeSeconds + " seconds";
-
+        return "[CallForwardInfo: status=" + (status == 0 ? " not active " : " active ")
+                + ", reason= " + reason
+                + ", serviceClass= " + serviceClass + ", timeSec= " + timeSeconds + " seconds"
+                + ", number=" + Log.pii(number) + "]";
     }
 }
diff --git a/com/android/internal/telephony/CarrierKeyDownloadManager.java b/com/android/internal/telephony/CarrierKeyDownloadManager.java
index bca337d..606f7ff 100644
--- a/com/android/internal/telephony/CarrierKeyDownloadManager.java
+++ b/com/android/internal/telephony/CarrierKeyDownloadManager.java
@@ -16,8 +16,6 @@
 
 package com.android.internal.telephony;
 
-import static android.preference.PreferenceManager.getDefaultSharedPreferences;
-
 import android.app.AlarmManager;
 import android.app.DownloadManager;
 import android.app.PendingIntent;
@@ -34,22 +32,30 @@
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
-import android.util.Base64;
 import android.util.Log;
+import android.util.Pair;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.org.bouncycastle.util.io.pem.PemReader;
 
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 
 import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
 import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
+import java.io.Reader;
+import java.security.PublicKey;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
 import java.util.Date;
 
+import static android.preference.PreferenceManager.getDefaultSharedPreferences;
+
 /**
  * This class contains logic to get Certificates and keep them current.
  * The class will be instantiated by various Phone implementations.
@@ -68,16 +74,19 @@
     private static final String INTENT_KEY_RENEWAL_ALARM_PREFIX =
             "com.android.internal.telephony.carrier_key_download_alarm";
 
-    private int mKeyAvailability = 0;
+    @VisibleForTesting
+    public int mKeyAvailability = 0;
 
     public static final String MNC = "MNC";
     public static final String MCC = "MCC";
     private static final String SEPARATOR = ":";
 
-    private static final String JSON_KEY = "key";
-    private static final String JSON_TYPE = "type";
-    private static final String JSON_IDENTIFIER = "identifier";
-    private static final String JSON_EXPIRATION_DATE = "expiration-date";
+    private static final String JSON_CERTIFICATE = "certificate";
+    // This is a hack to accomodate Verizon. Verizon insists on using the public-key
+    // field to store the certificate. We'll just use which-ever is not null.
+    private static final String JSON_CERTIFICATE_ALTERNATE = "public-key";
+    private static final String JSON_TYPE = "key-type";
+    private static final String JSON_IDENTIFIER = "key-identifier";
     private static final String JSON_CARRIER_KEYS = "carrier-keys";
     private static final String JSON_TYPE_VALUE_WLAN = "WLAN";
     private static final String JSON_TYPE_VALUE_EPDG = "EPDG";
@@ -89,7 +98,7 @@
 
     private final Phone mPhone;
     private final Context mContext;
-    private final DownloadManager mDownloadManager;
+    public final DownloadManager mDownloadManager;
     private String mURL;
 
     public CarrierKeyDownloadManager(Phone phone) {
@@ -173,14 +182,11 @@
     }
 
     /**
-     * this method resets the alarm. Starts by cleaning up the existing alarms.
-     * We look at the earliest expiration date, and setup an alarms X days prior.
-     * If the expiration date is in the past, we'll setup an alarm to run the next day. This
-     * could happen if the download has failed.
+     * this method returns the date to be used to decide on when to start downloading the key.
+     * from the carrier.
      **/
-    private void resetRenewalAlarm() {
-        cleanupRenewalAlarms();
-        int slotId = mPhone.getPhoneId();
+    @VisibleForTesting
+    public long getExpirationDate()  {
         long minExpirationDate = Long.MAX_VALUE;
         for (int key_type : CARRIER_KEY_TYPES) {
             if (!isKeyEnabled(key_type)) {
@@ -204,6 +210,20 @@
         } else {
             minExpirationDate = minExpirationDate - DEFAULT_RENEWAL_WINDOW_DAYS * DAY_IN_MILLIS;
         }
+        return minExpirationDate;
+    }
+
+    /**
+     * this method resets the alarm. Starts by cleaning up the existing alarms.
+     * We look at the earliest expiration date, and setup an alarms X days prior.
+     * If the expiration date is in the past, we'll setup an alarm to run the next day. This
+     * could happen if the download has failed.
+     **/
+    @VisibleForTesting
+    public void resetRenewalAlarm() {
+        cleanupRenewalAlarms();
+        int slotId = mPhone.getPhoneId();
+        long minExpirationDate = getExpirationDate();
         Log.d(LOG_TAG, "minExpirationDate: " + new Date(minExpirationDate));
         final AlarmManager alarmManager = (AlarmManager) mContext.getSystemService(
                 Context.ALARM_SERVICE);
@@ -225,21 +245,30 @@
     }
 
     /**
+     * Returns the sim operator.
+     **/
+    @VisibleForTesting
+    public String getSimOperator() {
+        final TelephonyManager telephonyManager =
+                (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+        return telephonyManager.getSimOperator(mPhone.getSubId());
+    }
+
+    /**
      *  checks if the download was sent by this particular instance. We do this by including the
      *  slot id in the key. If no value is found, we know that the download was not for this
      *  instance of the phone.
      **/
-    private boolean isValidDownload(String mccMnc) {
+    @VisibleForTesting
+    public boolean isValidDownload(String mccMnc) {
         String mccCurrent = "";
         String mncCurrent = "";
         String mccSource = "";
         String mncSource = "";
-        final TelephonyManager telephonyManager =
-                (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
-        String networkOperator = telephonyManager.getNetworkOperator(mPhone.getSubId());
 
-        if (TextUtils.isEmpty(networkOperator) || TextUtils.isEmpty(mccMnc)) {
-            Log.e(LOG_TAG, "networkOperator or mcc/mnc is empty");
+        String simOperator = getSimOperator();
+        if (TextUtils.isEmpty(simOperator) || TextUtils.isEmpty(mccMnc)) {
+            Log.e(LOG_TAG, "simOperator or mcc/mnc is empty");
             return false;
         }
 
@@ -248,8 +277,8 @@
         mncSource = splitValue[1];
         Log.d(LOG_TAG, "values from sharedPrefs mcc, mnc: " + mccSource + "," + mncSource);
 
-        mccCurrent = networkOperator.substring(0, 3);
-        mncCurrent = networkOperator.substring(3);
+        mccCurrent = simOperator.substring(0, 3);
+        mncCurrent = simOperator.substring(3);
         Log.d(LOG_TAG, "using values for mcc, mnc: " + mccCurrent + "," + mncCurrent);
 
         if (TextUtils.equals(mncSource, mncCurrent) &&  TextUtils.equals(mccSource, mccCurrent)) {
@@ -348,19 +377,20 @@
      * Converts the string into a json object to retreive the nodes. The Json should have 3 nodes,
      * including the Carrier public key, the key type and the key identifier. Once the nodes have
      * been extracted, they get persisted to the database. Sample:
-     *      "carrier-keys": [ { "key": "",
-     *                         "type": WLAN,
-     *                         "identifier": "",
-     *                         "expiration-date": 1502577746000
+     *      "carrier-keys": [ { "certificate": "",
+     *                         "key-type": "WLAN",
+     *                         "key-identifier": ""
      *                        } ]
      * @param jsonStr the json string.
-     * @param mccMnc contains the mcc, mnc
+     * @param mccMnc contains the mcc, mnc.
      */
-    private void parseJsonAndPersistKey(String jsonStr, String mccMnc) {
+    @VisibleForTesting
+    public void parseJsonAndPersistKey(String jsonStr, String mccMnc) {
         if (TextUtils.isEmpty(jsonStr) || TextUtils.isEmpty(mccMnc)) {
             Log.e(LOG_TAG, "jsonStr or mcc, mnc: is empty");
             return;
         }
+        PemReader reader = null;
         try {
             String mcc = "";
             String mnc = "";
@@ -369,10 +399,16 @@
             mnc = splitValue[1];
             JSONObject jsonObj = new JSONObject(jsonStr);
             JSONArray keys = jsonObj.getJSONArray(JSON_CARRIER_KEYS);
-
             for (int i = 0; i < keys.length(); i++) {
                 JSONObject key = keys.getJSONObject(i);
-                String carrierKey = key.getString(JSON_KEY);
+                // This is a hack to accomodate Verizon. Verizon insists on using the public-key
+                // field to store the certificate. We'll just use which-ever is not null.
+                String cert = null;
+                if (key.has(JSON_CERTIFICATE)) {
+                    cert = key.getString(JSON_CERTIFICATE);
+                } else {
+                    cert = key.getString(JSON_CERTIFICATE_ALTERNATE);
+                }
                 String typeString = key.getString(JSON_TYPE);
                 int type = UNINITIALIZED_KEY_TYPE;
                 if (typeString.equals(JSON_TYPE_VALUE_WLAN)) {
@@ -380,13 +416,27 @@
                 } else if (typeString.equals(JSON_TYPE_VALUE_EPDG)) {
                     type = TelephonyManager.KEY_TYPE_EPDG;
                 }
-                long expiration_date = key.getLong(JSON_EXPIRATION_DATE);
                 String identifier = key.getString(JSON_IDENTIFIER);
-                savePublicKey(carrierKey, type, identifier, expiration_date,
-                        mcc, mnc);
+                ByteArrayInputStream inStream = new ByteArrayInputStream(cert.getBytes());
+                Reader fReader = new BufferedReader(new InputStreamReader(inStream));
+                reader = new PemReader(fReader);
+                Pair<PublicKey, Long> keyInfo =
+                        getKeyInformation(reader.readPemObject().getContent());
+                reader.close();
+                savePublicKey(keyInfo.first, type, identifier, keyInfo.second, mcc, mnc);
             }
         } catch (final JSONException e) {
             Log.e(LOG_TAG, "Json parsing error: " + e.getMessage());
+        } catch (final Exception e) {
+            Log.e(LOG_TAG, "Exception getting certificate: " + e);
+        } finally {
+            try {
+                if (reader != null) {
+                    reader.close();
+                }
+            } catch (final Exception e) {
+                Log.e(LOG_TAG, "Exception getting certificate: " + e);
+            }
         }
     }
 
@@ -394,8 +444,8 @@
      * introspects the mKeyAvailability bitmask
      * @return true if the digit at position k is 1, else false.
      */
-
-    private boolean isKeyEnabled(int keyType) {
+    @VisibleForTesting
+    public boolean isKeyEnabled(int keyType) {
         //since keytype has values of 1, 2.... we need to subtract 1 from the keytype.
         int returnValue = (mKeyAvailability >> (keyType - 1)) & 1;
         return (returnValue == 1) ? true : false;
@@ -427,15 +477,13 @@
 
     private boolean downloadKey() {
         Log.d(LOG_TAG, "starting download from: " + mURL);
-        final TelephonyManager telephonyManager =
-                (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
         String mcc = "";
         String mnc = "";
-        String networkOperator = telephonyManager.getNetworkOperator(mPhone.getSubId());
+        String simOperator = getSimOperator();
 
-        if (!TextUtils.isEmpty(networkOperator)) {
-            mcc = networkOperator.substring(0, 3);
-            mnc = networkOperator.substring(3);
+        if (!TextUtils.isEmpty(simOperator)) {
+            mcc = simOperator.substring(0, 3);
+            mnc = simOperator.substring(3);
             Log.d(LOG_TAG, "using values for mcc, mnc: " + mcc + "," + mnc);
         } else {
             Log.e(LOG_TAG, "mcc, mnc: is empty");
@@ -461,11 +509,35 @@
         return true;
     }
 
-    private void savePublicKey(String key, int type, String identifier, long expirationDate,
+    /**
+     * Save the public key
+     * @param certificate certificate that contains the public key.
+     * @return Pair containing the Public Key and the expiration date.
+     **/
+    @VisibleForTesting
+    public static Pair<PublicKey, Long> getKeyInformation(byte[] certificate) throws Exception {
+        InputStream inStream = new ByteArrayInputStream(certificate);
+        CertificateFactory cf = CertificateFactory.getInstance("X.509");
+        X509Certificate cert = (X509Certificate) cf.generateCertificate(inStream);
+        Pair<PublicKey, Long> keyInformation =
+                new Pair(cert.getPublicKey(), cert.getNotAfter().getTime());
+        return keyInformation;
+    }
+
+    /**
+     * Save the public key
+     * @param publicKey public key.
+     * @param type key-type.
+     * @param identifier which is an opaque string.
+     * @param expirationDate expiration date of the key.
+     * @param mcc
+     * @param mnc
+     **/
+    @VisibleForTesting
+    public void savePublicKey(PublicKey publicKey, int type, String identifier, long expirationDate,
                                String mcc, String mnc) {
-        byte[] keyBytes = Base64.decode(key.getBytes(), Base64.DEFAULT);
         ImsiEncryptionInfo imsiEncryptionInfo = new ImsiEncryptionInfo(mcc, mnc, type, identifier,
-                keyBytes, new Date(expirationDate));
+                publicKey, new Date(expirationDate));
         mPhone.setCarrierInfoForImsiEncryption(imsiEncryptionInfo);
     }
 }
diff --git a/com/android/internal/telephony/CarrierServiceStateTracker.java b/com/android/internal/telephony/CarrierServiceStateTracker.java
index 8df201e..77a39eb 100644
--- a/com/android/internal/telephony/CarrierServiceStateTracker.java
+++ b/com/android/internal/telephony/CarrierServiceStateTracker.java
@@ -79,13 +79,8 @@
         switch (msg.what) {
             case CARRIER_EVENT_VOICE_REGISTRATION:
             case CARRIER_EVENT_DATA_REGISTRATION:
-                handleConfigChanges();
-                break;
             case CARRIER_EVENT_VOICE_DEREGISTRATION:
             case CARRIER_EVENT_DATA_DEREGISTRATION:
-                if (isRadioOffOrAirplaneMode()) {
-                    break;
-                }
                 handleConfigChanges();
                 break;
             case NOTIFICATION_EMERGENCY_NETWORK:
@@ -317,8 +312,8 @@
             Rlog.i(LOG_TAG, "PrefNetworkNotification: sendMessage() w/values: "
                     + "," + isPhoneStillRegistered() + "," + mDelay + "," + isGlobalMode()
                     + "," + mSST.isRadioOn());
-            if (mDelay == UNINITIALIZED_DELAY_VALUE ||  isPhoneStillRegistered()
-                    || isGlobalMode()) {
+            if (mDelay == UNINITIALIZED_DELAY_VALUE ||  isPhoneStillRegistered() || isGlobalMode()
+                    || isRadioOffOrAirplaneMode()) {
                 return false;
             }
             return true;
diff --git a/com/android/internal/telephony/ClientWakelockTracker.java b/com/android/internal/telephony/ClientWakelockTracker.java
index 5bec60b..fa71e76 100644
--- a/com/android/internal/telephony/ClientWakelockTracker.java
+++ b/com/android/internal/telephony/ClientWakelockTracker.java
@@ -18,10 +18,10 @@
 
 import android.os.SystemClock;
 import android.telephony.ClientRequestStats;
-import android.telephony.Rlog;
 
 import com.android.internal.annotations.VisibleForTesting;
 
+import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -119,13 +119,13 @@
         return false;
     }
 
-    void dumpClientRequestTracker() {
-        Rlog.d(RIL.RILJ_LOG_TAG, "-------mClients---------------");
+    void dumpClientRequestTracker(PrintWriter pw) {
+        pw.println("-------mClients---------------");
         synchronized (mClients) {
             for (String key : mClients.keySet()) {
-                Rlog.d(RIL.RILJ_LOG_TAG, "Client : " + key);
-                Rlog.d(RIL.RILJ_LOG_TAG, mClients.get(key).toString());
+                pw.println("Client : " + key);
+                pw.println(mClients.get(key).toString());
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/com/android/internal/telephony/Connection.java b/com/android/internal/telephony/Connection.java
index 245f76c..8c54a31 100644
--- a/com/android/internal/telephony/Connection.java
+++ b/com/android/internal/telephony/Connection.java
@@ -34,6 +34,7 @@
  * {@hide}
  */
 public abstract class Connection {
+    private static final String TAG = "Connection";
 
     public interface PostDialListener {
         void onPostDialWait();
@@ -836,6 +837,16 @@
     public void setConnectionExtras(Bundle extras) {
         if (extras != null) {
             mExtras = new Bundle(extras);
+
+            int previousCount = mExtras.size();
+            // Prevent vendors from passing in extras other than primitive types and android API
+            // parcelables.
+            mExtras = mExtras.filterValues();
+            int filteredCount = mExtras.size();
+            if (filteredCount != previousCount) {
+                Rlog.i(TAG, "setConnectionExtras: filtering " + (previousCount - filteredCount)
+                        + " invalid extras.");
+            }
         } else {
             mExtras = null;
         }
diff --git a/com/android/internal/telephony/DefaultPhoneNotifier.java b/com/android/internal/telephony/DefaultPhoneNotifier.java
index c13e540..98c0a32 100644
--- a/com/android/internal/telephony/DefaultPhoneNotifier.java
+++ b/com/android/internal/telephony/DefaultPhoneNotifier.java
@@ -125,6 +125,9 @@
         int subId = sender.getSubId();
         try {
             if (mRegistry != null) {
+                Rlog.d(LOG_TAG, "notifyCallForwardingChanged: subId=" + subId + ", isCFActive="
+                        + sender.getCallForwardingIndicator());
+
                 mRegistry.notifyCallForwardingChangedForSubscriber(subId,
                         sender.getCallForwardingIndicator());
             }
diff --git a/com/android/internal/telephony/GsmCdmaPhone.java b/com/android/internal/telephony/GsmCdmaPhone.java
index d95d018..ad078d6 100644
--- a/com/android/internal/telephony/GsmCdmaPhone.java
+++ b/com/android/internal/telephony/GsmCdmaPhone.java
@@ -286,7 +286,7 @@
             tm.setPhoneType(getPhoneId(), PhoneConstants.PHONE_TYPE_GSM);
             mIccCardProxy.setVoiceRadioTech(ServiceState.RIL_RADIO_TECHNOLOGY_UMTS);
         } else {
-            mCdmaSubscriptionSource = CdmaSubscriptionSourceManager.SUBSCRIPTION_SOURCE_UNKNOWN;
+            mCdmaSubscriptionSource = mCdmaSSM.getCdmaSubscriptionSource();
             // This is needed to handle phone process crashes
             mIsPhoneInEcmState = getInEcmMode();
             if (mIsPhoneInEcmState) {
@@ -505,7 +505,7 @@
 
             ret = PhoneConstants.DataState.DISCONNECTED;
         } else if (mSST.getCurrentDataConnectionState() != ServiceState.STATE_IN_SERVICE
-                && (isPhoneTypeCdma() ||
+                && (isPhoneTypeCdma() || isPhoneTypeCdmaLte() ||
                 (isPhoneTypeGsm() && !apnType.equals(PhoneConstants.APN_TYPE_EMERGENCY)))) {
             // If we're out of service, open TCP sockets may still work
             // but no data will flow
@@ -1063,7 +1063,7 @@
         boolean alwaysTryImsForEmergencyCarrierConfig = configManager.getConfigForSubId(getSubId())
                 .getBoolean(CarrierConfigManager.KEY_CARRIER_USE_IMS_FIRST_FOR_EMERGENCY_BOOL);
 
-        boolean imsUseEnabled = isImsUseEnabled()
+        boolean useImsForCall = isImsUseEnabled()
                  && imsPhone != null
                  && (imsPhone.isVolteEnabled() || imsPhone.isWifiCallingEnabled() ||
                  (imsPhone.isVideoEnabled() && VideoProfile.isVideo(videoState)))
@@ -1083,7 +1083,7 @@
         boolean useImsForUt = imsPhone != null && imsPhone.isUtEnabled();
 
         if (DBG) {
-            logd("imsUseEnabled=" + imsUseEnabled
+            logd("useImsForCall=" + useImsForCall
                     + ", useImsForEmergency=" + useImsForEmergency
                     + ", useImsForUt=" + useImsForUt
                     + ", isUt=" + isUt
@@ -1100,13 +1100,13 @@
 
         Phone.checkWfcWifiOnlyModeBeforeDial(mImsPhone, mContext);
 
-        if ((imsUseEnabled && (!isUt || useImsForUt)) || useImsForEmergency) {
+        if ((useImsForCall && !isUt) || (isUt && useImsForUt) || useImsForEmergency) {
             try {
                 if (DBG) logd("Trying IMS PS call");
                 return imsPhone.dial(dialString, uusInfo, videoState, intentExtras);
             } catch (CallStateException e) {
                 if (DBG) logd("IMS PS call exception " + e +
-                        "imsUseEnabled =" + imsUseEnabled + ", imsPhone =" + imsPhone);
+                        "useImsForCall =" + useImsForCall + ", imsPhone =" + imsPhone);
                 // Do not throw a CallStateException and instead fall back to Circuit switch
                 // for emergency calls and MMI codes.
                 if (Phone.CS_FALLBACK.equals(e.getMessage()) || isEmergency) {
@@ -2549,6 +2549,7 @@
     private void processIccRecordEvents(int eventCode) {
         switch (eventCode) {
             case IccRecords.EVENT_CFI:
+                logi("processIccRecordEvents: EVENT_CFI");
                 notifyCallForwardingIndicator();
                 break;
         }
diff --git a/com/android/internal/telephony/InboundSmsHandler.java b/com/android/internal/telephony/InboundSmsHandler.java
index 59195f8..391de50 100644
--- a/com/android/internal/telephony/InboundSmsHandler.java
+++ b/com/android/internal/telephony/InboundSmsHandler.java
@@ -158,6 +158,17 @@
     /** New SMS received as an AsyncResult. */
     public static final int EVENT_INJECT_SMS = 8;
 
+    /** Update tracker object; used only in waiting state */
+    private static final int EVENT_UPDATE_TRACKER = 9;
+
+    /** Timeout in case state machine is stuck in a state for too long; used only in waiting
+     * state */
+    private static final int EVENT_STATE_TIMEOUT = 10;
+
+    /** Timeout duration for EVENT_STATE_TIMEOUT */
+    @VisibleForTesting
+    public static final int STATE_TIMEOUT = 30000;
+
     /** Wakelock release delay when returning to idle state. */
     private static final int WAKELOCK_TIMEOUT = 3000;
 
@@ -450,6 +461,7 @@
                     // if any broadcasts were sent, transition to waiting state
                     InboundSmsTracker inboundSmsTracker = (InboundSmsTracker) msg.obj;
                     if (processMessagePart(inboundSmsTracker)) {
+                        sendMessage(EVENT_UPDATE_TRACKER, inboundSmsTracker);
                         transitionTo(mWaitingState);
                     } else {
                         // if event is sent from SmsBroadcastUndelivered.broadcastSms(), and
@@ -493,18 +505,41 @@
      * {@link IdleState} after any deferred {@link #EVENT_BROADCAST_SMS} messages are handled.
      */
     private class WaitingState extends State {
+        private InboundSmsTracker mTracker;
+
+        @Override
+        public void enter() {
+            if (DBG) log("entering Waiting state");
+            mTracker = null;
+            sendMessageDelayed(EVENT_STATE_TIMEOUT, STATE_TIMEOUT);
+        }
+
         @Override
         public void exit() {
             if (DBG) log("exiting Waiting state");
             // Before moving to idle state, set wakelock timeout to WAKE_LOCK_TIMEOUT milliseconds
             // to give any receivers time to take their own wake locks
             setWakeLockTimeout(WAKELOCK_TIMEOUT);
+            if (VDBG) {
+                if (hasMessages(EVENT_STATE_TIMEOUT)) {
+                    log("exiting Waiting state: removing EVENT_STATE_TIMEOUT from message queue");
+                }
+                if (hasMessages(EVENT_UPDATE_TRACKER)) {
+                    log("exiting Waiting state: removing EVENT_UPDATE_TRACKER from message queue");
+                }
+            }
+            removeMessages(EVENT_STATE_TIMEOUT);
+            removeMessages(EVENT_UPDATE_TRACKER);
         }
 
         @Override
         public boolean processMessage(Message msg) {
             log("WaitingState.processMessage:" + msg.what);
             switch (msg.what) {
+                case EVENT_UPDATE_TRACKER:
+                    mTracker = (InboundSmsTracker) msg.obj;
+                    return HANDLED;
+
                 case EVENT_BROADCAST_SMS:
                     // defer until the current broadcast completes
                     deferMessage(msg);
@@ -520,6 +555,18 @@
                     // not ready to return to idle; ignore
                     return HANDLED;
 
+                case EVENT_STATE_TIMEOUT:
+                    // stuck in WaitingState for too long; drop the message and exit this state
+                    if (mTracker != null) {
+                        log("WaitingState.processMessage: EVENT_STATE_TIMEOUT; dropping message");
+                        dropSms(new SmsBroadcastReceiver(mTracker));
+                    } else {
+                        log("WaitingState.processMessage: EVENT_STATE_TIMEOUT; mTracker is null "
+                                + "- sending EVENT_BROADCAST_COMPLETE");
+                        sendMessage(EVENT_BROADCAST_COMPLETE);
+                    }
+                    return HANDLED;
+
                 default:
                     // parent state handles the other message types
                     return NOT_HANDLED;
diff --git a/com/android/internal/telephony/Phone.java b/com/android/internal/telephony/Phone.java
index 28e4556..6acc874 100644
--- a/com/android/internal/telephony/Phone.java
+++ b/com/android/internal/telephony/Phone.java
@@ -56,6 +56,7 @@
 import android.telephony.SubscriptionManager;
 import android.telephony.VoLteServiceState;
 import android.text.TextUtils;
+import android.util.Log;
 
 import com.android.ims.ImsCall;
 import com.android.ims.ImsConfig;
@@ -226,6 +227,9 @@
     // Key used to read/write "disable DNS server check" pref (used for testing)
     private static final String DNS_SERVER_CHECK_DISABLED_KEY = "dns_server_check_disabled_key";
 
+    // Integer used to let the calling application know that the we are ignoring auto mode switch.
+    private static final int ALREADY_IN_AUTO_SELECTION = 1;
+
     /**
      * This method is invoked when the Phone exits Emergency Callback Mode.
      */
@@ -1205,6 +1209,11 @@
             mCi.setNetworkSelectionModeAutomatic(msg);
         } else {
             Rlog.d(LOG_TAG, "setNetworkSelectionModeAutomatic - already auto, ignoring");
+            // let the calling application know that the we are ignoring automatic mode switch.
+            if (nsm.message != null) {
+                nsm.message.arg1 = ALREADY_IN_AUTO_SELECTION;
+            }
+
             ar.userObj = nsm;
             handleSetSelectNetwork(ar);
         }
@@ -1789,7 +1798,7 @@
         int status = enable ? IccRecords.CALL_FORWARDING_STATUS_ENABLED :
                 IccRecords.CALL_FORWARDING_STATUS_DISABLED;
         int subId = getSubId();
-        Rlog.d(LOG_TAG, "setCallForwardingIndicatorInSharedPref: Storing status = " + status +
+        Rlog.i(LOG_TAG, "setCallForwardingIndicatorInSharedPref: Storing status = " + status +
                 " in pref " + CF_STATUS + subId);
 
         SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
@@ -1831,6 +1840,9 @@
         if (callForwardingIndicator == IccRecords.CALL_FORWARDING_STATUS_UNKNOWN) {
             callForwardingIndicator = getCallForwardingIndicatorFromSharedPref();
         }
+        Rlog.v(LOG_TAG, "getCallForwardingIndicator: iccForwardingFlag=" + (r != null
+                    ? r.getVoiceCallForwardingFlag() : "null") + ", sharedPrefFlag="
+                    + getCallForwardingIndicatorFromSharedPref());
         return (callForwardingIndicator == IccRecords.CALL_FORWARDING_STATUS_ENABLED);
     }
 
diff --git a/com/android/internal/telephony/RIL.java b/com/android/internal/telephony/RIL.java
index 8bb2125..84c2b65 100644
--- a/com/android/internal/telephony/RIL.java
+++ b/com/android/internal/telephony/RIL.java
@@ -4774,7 +4774,7 @@
         }
         pw.println(" mLastNITZTimeInfo=" + Arrays.toString(mLastNITZTimeInfo));
         pw.println(" mTestingEmergencyCall=" + mTestingEmergencyCall.get());
-        mClientWakelockTracker.dumpClientRequestTracker();
+        mClientWakelockTracker.dumpClientRequestTracker(pw);
     }
 
     public List<ClientRequestStats> getClientRequestStats() {
diff --git a/com/android/internal/telephony/ServiceStateTracker.java b/com/android/internal/telephony/ServiceStateTracker.java
index b379440..c34cbb2 100644
--- a/com/android/internal/telephony/ServiceStateTracker.java
+++ b/com/android/internal/telephony/ServiceStateTracker.java
@@ -210,6 +210,7 @@
     protected static final int EVENT_ALL_DATA_DISCONNECTED             = 49;
     protected static final int EVENT_PHONE_TYPE_SWITCHED               = 50;
     protected static final int EVENT_RADIO_POWER_FROM_CARRIER          = 51;
+    protected static final int EVENT_SIM_NOT_INSERTED                  = 52;
 
     protected static final String TIMEZONE_PROPERTY = "persist.sys.timezone";
 
@@ -354,6 +355,14 @@
                 }
                 // update voicemail count and notify message waiting changed
                 mPhone.updateVoiceMail();
+
+                // cancel notifications if we see SIM_NOT_INSERTED (This happens on bootup before
+                // the SIM is first detected and then subsequently on SIM removals)
+                if (mSubscriptionController.getSlotIndex(subId)
+                        == SubscriptionManager.SIM_NOT_INSERTED) {
+                    // this is handled on the main thread to mitigate racing with setNotification().
+                    sendMessage(obtainMessage(EVENT_SIM_NOT_INSERTED));
+                }
             }
         }
     };
@@ -446,12 +455,15 @@
     public static final int CS_NORMAL_ENABLED = 1005;     // Access Control blocks normal voice/sms service
     public static final int CS_EMERGENCY_ENABLED = 1006;  // Access Control blocks emergency call service
     public static final int CS_REJECT_CAUSE_ENABLED = 2001;     // Notify MM rejection cause
-    public static final int CS_REJECT_CAUSE_DISABLED = 2002;    // Cancel MM rejection cause
     /** Notification id. */
     public static final int PS_NOTIFICATION = 888;  // Id to update and cancel PS restricted
     public static final int CS_NOTIFICATION = 999;  // Id to update and cancel CS restricted
     public static final int CS_REJECT_CAUSE_NOTIFICATION = 111; // Id to update and cancel MM
                                                                 // rejection cause
+
+    /** To identify whether EVENT_SIM_READY is received or not */
+    private boolean mIsSimReady = false;
+
     private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -1064,6 +1076,11 @@
 
             case EVENT_ICC_CHANGED:
                 onUpdateIccAvailability();
+                if (mUiccApplcation != null
+                        && mUiccApplcation.getState() != AppState.APPSTATE_READY) {
+                    mIsSimReady = false;
+                    updateSpnDisplay();
+                }
                 break;
 
             case EVENT_GET_CELL_INFO_LIST: {
@@ -1121,6 +1138,7 @@
                 // Reset the mPreviousSubId so we treat a SIM power bounce
                 // as a first boot.  See b/19194287
                 mOnSubscriptionsChangedListener.mPreviousSubId.set(-1);
+                mIsSimReady = true;
                 pollState();
                 // Signal strength polling stops when radio is off
                 queueNextSignalStrengthPoll();
@@ -1298,6 +1316,14 @@
                 }
                 break;
 
+            case EVENT_SIM_NOT_INSERTED:
+                if (DBG) log("EVENT_SIM_NOT_INSERTED");
+                cancelAllNotifications();
+                mMdn = null;
+                mMin = null;
+                mIsMinInfoReady = false;
+                break;
+
             case EVENT_ALL_DATA_DISCONNECTED:
                 int dds = SubscriptionManager.getDefaultDataSubscriptionId();
                 ProxyController.getInstance().unregisterForAllDataDisconnected(dds, this);
@@ -2222,7 +2248,12 @@
             if (combinedRegState == ServiceState.STATE_OUT_OF_SERVICE
                     || combinedRegState == ServiceState.STATE_EMERGENCY_ONLY) {
                 showPlmn = true;
-                if (mEmergencyOnly) {
+
+                // Force display no service
+                final boolean forceDisplayNoService = mPhone.getContext().getResources().getBoolean(
+                        com.android.internal.R.bool.config_display_no_service_when_sim_unready)
+                                && !mIsSimReady;
+                if (mEmergencyOnly && !forceDisplayNoService) {
                     // No service but emergency call allowed
                     plmn = Resources.getSystem().
                             getText(com.android.internal.R.string.emergency_calls_only).toString();
@@ -2825,7 +2856,7 @@
         }
 
         if (hasRejectCauseChanged) {
-            setNotification(mRejectCode == 0 ? CS_REJECT_CAUSE_DISABLED : CS_REJECT_CAUSE_ENABLED);
+            setNotification(CS_REJECT_CAUSE_ENABLED);
         }
 
         if (hasChanged) {
@@ -3833,6 +3864,18 @@
     }
 
     /**
+     * Cancels all notifications posted to NotificationManager. These notifications for restricted
+     * state and rejection cause for cs registration are no longer valid after the SIM has been
+     * removed.
+     */
+    private void cancelAllNotifications() {
+        if (DBG) log("setNotification: cancelAllNotifications");
+        NotificationManager notificationManager = (NotificationManager)
+                mPhone.getContext().getSystemService(Context.NOTIFICATION_SERVICE);
+        notificationManager.cancelAll();
+    }
+
+    /**
      * Post a notification to NotificationManager for restricted state and
      * rejection cause for cs registration
      *
@@ -3907,17 +3950,14 @@
                 notificationId = CS_REJECT_CAUSE_NOTIFICATION;
                 int resId = selectResourceForRejectCode(mRejectCode);
                 if (0 == resId) {
-                    // cancel notification because current reject code is not handled.
-                    notifyType = CS_REJECT_CAUSE_DISABLED;
+                    loge("setNotification: mRejectCode=" + mRejectCode + " is not handled.");
+                    return;
                 } else {
                     icon = com.android.internal.R.drawable.stat_notify_mmcc_indication_icn;
                     title = Resources.getSystem().getString(resId);
                     details = null;
                 }
                 break;
-            case CS_REJECT_CAUSE_DISABLED:
-                notificationId = CS_REJECT_CAUSE_NOTIFICATION;
-                break;
         }
 
         if (DBG) {
@@ -3941,8 +3981,7 @@
         NotificationManager notificationManager = (NotificationManager)
                 context.getSystemService(Context.NOTIFICATION_SERVICE);
 
-        if (notifyType == PS_DISABLED || notifyType == CS_DISABLED
-                || notifyType == CS_REJECT_CAUSE_DISABLED) {
+        if (notifyType == PS_DISABLED || notifyType == CS_DISABLED) {
             // cancel previous post notification
             notificationManager.cancel(notificationId);
         } else {
diff --git a/com/android/internal/telephony/cat/AppInterface.java b/com/android/internal/telephony/cat/AppInterface.java
index c78b7f8..1f2d3a0 100644
--- a/com/android/internal/telephony/cat/AppInterface.java
+++ b/com/android/internal/telephony/cat/AppInterface.java
@@ -84,6 +84,7 @@
         SET_UP_MENU(0x25),
         SET_UP_CALL(0x10),
         PROVIDE_LOCAL_INFORMATION(0x26),
+        LANGUAGE_NOTIFICATION(0x35),
         OPEN_CHANNEL(0x40),
         CLOSE_CHANNEL(0x41),
         RECEIVE_DATA(0x42),
diff --git a/com/android/internal/telephony/cat/CatService.java b/com/android/internal/telephony/cat/CatService.java
index a242de4..cd7a756 100644
--- a/com/android/internal/telephony/cat/CatService.java
+++ b/com/android/internal/telephony/cat/CatService.java
@@ -16,15 +16,21 @@
 
 package com.android.internal.telephony.cat;
 
+import android.app.ActivityManagerNative;
+import android.app.IActivityManager;
+import android.app.backup.BackupManager;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
+import android.content.res.Configuration;
 import android.content.res.Resources.NotFoundException;
 import android.os.AsyncResult;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.os.LocaleList;
 import android.os.Message;
+import android.os.RemoteException;
 import android.os.SystemProperties;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
@@ -450,6 +456,18 @@
                     ((CallSetupParams) cmdParams).mConfirmMsg.text = message.toString();
                 }
                 break;
+            case LANGUAGE_NOTIFICATION:
+                String language = ((LanguageParams) cmdParams).mLanguage;
+                ResultCode result = ResultCode.OK;
+                if (language != null && language.length() > 0) {
+                    try {
+                        changeLanguage(language);
+                    } catch (RemoteException e) {
+                        result = ResultCode.TERMINAL_CRNTLY_UNABLE_TO_PROCESS;
+                    }
+                }
+                sendTerminalResponse(cmdParams.mCmdDet, result, false, 0, null);
+                return;
             case OPEN_CHANNEL:
             case CLOSE_CHANNEL:
             case RECEIVE_DATA:
@@ -881,8 +899,9 @@
         // This sends an intent with CARD_ABSENT (0 - false) /CARD_PRESENT (1 - true).
         intent.putExtra(AppInterface.CARD_STATUS, cardPresent);
         intent.setComponent(AppInterface.getDefaultSTKApplication());
+        intent.putExtra("SLOT_ID", mSlotId);
         CatLog.d(this, "Sending Card Status: "
-                + cardState + " " + "cardPresent: " + cardPresent);
+                + cardState + " " + "cardPresent: " + cardPresent +  "SLOT_ID: " +  mSlotId);
         mContext.sendBroadcast(intent, AppInterface.STK_PERMISSION);
     }
 
@@ -1006,6 +1025,13 @@
                 }
                 break;
             case LAUNCH_BROWSER:
+                if (resMsg.mResCode == ResultCode.LAUNCH_BROWSER_ERROR) {
+                    // Additional info for Default URL unavailable.
+                    resMsg.setAdditionalInfo(0x04);
+                } else {
+                    resMsg.mIncludeAdditionalInfo = false;
+                    resMsg.mAdditionalInfo = 0;
+                }
                 break;
             // 3GPP TS.102.223: Open Channel alpha confirmation should not send TR
             case OPEN_CHANNEL:
@@ -1121,4 +1147,13 @@
             mCmdIf.reportStkServiceIsRunning(null);
         }
     }
+
+    private void changeLanguage(String language) throws RemoteException {
+        IActivityManager am = ActivityManagerNative.getDefault();
+        Configuration config = am.getConfiguration();
+        config.setLocales(new LocaleList(new Locale(language), LocaleList.getDefault()));
+        config.userSetLocale = true;
+        am.updatePersistentConfiguration(config);
+        BackupManager.dataChanged("com.android.providers.settings");
+    }
 }
diff --git a/com/android/internal/telephony/cat/CommandParams.java b/com/android/internal/telephony/cat/CommandParams.java
index 7dfedab..59cd414 100644
--- a/com/android/internal/telephony/cat/CommandParams.java
+++ b/com/android/internal/telephony/cat/CommandParams.java
@@ -150,6 +150,15 @@
     }
 }
 
+class LanguageParams extends CommandParams {
+    String mLanguage;
+
+    LanguageParams(CommandDetails cmdDet, String lang) {
+        super(cmdDet);
+        mLanguage = lang;
+    }
+}
+
 class SelectItemParams extends CommandParams {
     Menu mMenu = null;
     boolean mLoadTitleIcon = false;
diff --git a/com/android/internal/telephony/cat/CommandParamsFactory.java b/com/android/internal/telephony/cat/CommandParamsFactory.java
index 3dd5337..eb92888 100644
--- a/com/android/internal/telephony/cat/CommandParamsFactory.java
+++ b/com/android/internal/telephony/cat/CommandParamsFactory.java
@@ -19,12 +19,15 @@
 import android.graphics.Bitmap;
 import android.os.Handler;
 import android.os.Message;
+import android.text.TextUtils;
 
 import com.android.internal.telephony.GsmAlphabet;
 import com.android.internal.telephony.uicc.IccFileHandler;
 
 import java.util.Iterator;
 import java.util.List;
+import java.util.Locale;
+
 import static com.android.internal.telephony.cat.CatCmdMessage.
                    SetupEventListConstants.USER_ACTIVITY_EVENT;
 import static com.android.internal.telephony.cat.CatCmdMessage.
@@ -47,6 +50,8 @@
     private int mIconLoadState = LOAD_NO_ICON;
     private RilMessageDecoder mCaller = null;
     private boolean mloadIcon = false;
+    private String mSavedLanguage;
+    private String mRequestedLanguage;
 
     // constants
     static final int MSG_ID_LOAD_ICON_DONE = 1;
@@ -66,6 +71,10 @@
     static final int DTTZ_SETTING                           = 0x03;
     static final int LANGUAGE_SETTING                       = 0x04;
 
+    // Command Qualifier value for language notification command
+    static final int NON_SPECIFIC_LANGUAGE                  = 0x00;
+    static final int SPECIFIC_LANGUAGE                      = 0x01;
+
     // As per TS 102.223 Annex C, Structure of CAT communications,
     // the APDU length can be max 255 bytes. This leaves only 239 bytes for user
     // input string. CMD details TLV + Device IDs TLV + Result TLV + Other
@@ -203,6 +212,9 @@
              case PROVIDE_LOCAL_INFORMATION:
                 cmdPending = processProvideLocalInfo(cmdDet, ctlvs);
                 break;
+             case LANGUAGE_NOTIFICATION:
+                 cmdPending = processLanguageNotification(cmdDet, ctlvs);
+                 break;
              case OPEN_CHANNEL:
              case CLOSE_CHANNEL:
              case RECEIVE_DATA:
@@ -1014,6 +1026,67 @@
         return false;
     }
 
+    /**
+     * Processes LANGUAGE_NOTIFICATION proactive command from the SIM card.
+     *
+     * The SPECIFIC_LANGUAGE notification sets the specified language.
+     * The NON_SPECIFIC_LANGUAGE notification restores the last specifically set language.
+     *
+     * @param cmdDet Command Details object retrieved from the proactive command object
+     * @param ctlvs List of ComprehensionTlv objects following Command Details
+     *        object and Device Identities object within the proactive command
+     * @return false. This function always returns false meaning that the command
+     *         processing is  not pending and additional asynchronous processing
+     *         is not required.
+     */
+    private boolean processLanguageNotification(CommandDetails cmdDet, List<ComprehensionTlv> ctlvs)
+            throws ResultException {
+        CatLog.d(this, "process Language Notification");
+
+        String desiredLanguage = null;
+        String currentLanguage = Locale.getDefault().getLanguage();
+        switch (cmdDet.commandQualifier) {
+            case NON_SPECIFIC_LANGUAGE:
+                if (!TextUtils.isEmpty(mSavedLanguage) && (!TextUtils.isEmpty(mRequestedLanguage)
+                        && mRequestedLanguage.equals(currentLanguage))) {
+                    CatLog.d(this, "Non-specific language notification changes the language "
+                            + "setting back to " + mSavedLanguage);
+                    desiredLanguage = mSavedLanguage;
+                }
+
+                mSavedLanguage = null;
+                mRequestedLanguage = null;
+                break;
+            case SPECIFIC_LANGUAGE:
+                ComprehensionTlv ctlv = searchForTag(ComprehensionTlvTag.LANGUAGE, ctlvs);
+                if (ctlv != null) {
+                    int valueLen = ctlv.getLength();
+                    if (valueLen != 2) {
+                        throw new ResultException(ResultCode.CMD_DATA_NOT_UNDERSTOOD);
+                    }
+
+                    byte[] rawValue = ctlv.getRawValue();
+                    int valueIndex = ctlv.getValueIndex();
+                    desiredLanguage = GsmAlphabet.gsm8BitUnpackedToString(rawValue, valueIndex, 2);
+
+                    if (TextUtils.isEmpty(mSavedLanguage) || (!TextUtils.isEmpty(mRequestedLanguage)
+                            && !mRequestedLanguage.equals(currentLanguage))) {
+                        mSavedLanguage = currentLanguage;
+                    }
+                    mRequestedLanguage = desiredLanguage;
+                    CatLog.d(this, "Specific language notification changes the language setting to "
+                            + mRequestedLanguage);
+                }
+                break;
+            default:
+                CatLog.d(this, "LN[" + cmdDet.commandQualifier + "] Command Not Supported");
+                break;
+        }
+
+        mCmdParams = new LanguageParams(cmdDet, desiredLanguage);
+        return false;
+    }
+
     private boolean processBIPClient(CommandDetails cmdDet,
                                      List<ComprehensionTlv> ctlvs) throws ResultException {
         AppInterface.CommandType commandType =
diff --git a/com/android/internal/telephony/cdma/SmsMessage.java b/com/android/internal/telephony/cdma/SmsMessage.java
index 629173d..7a53ef6 100644
--- a/com/android/internal/telephony/cdma/SmsMessage.java
+++ b/com/android/internal/telephony/cdma/SmsMessage.java
@@ -161,7 +161,7 @@
 
             // Second byte is the MSG_LEN, length of the message
             // See 3GPP2 C.S0023 3.4.27
-            int size = data[1];
+            int size = data[1] & 0xFF;
 
             // Note: Data may include trailing FF's.  That's OK; message
             // should still parse correctly.
diff --git a/com/android/internal/telephony/dataconnection/DcTracker.java b/com/android/internal/telephony/dataconnection/DcTracker.java
index f9b0017..fb756cd 100644
--- a/com/android/internal/telephony/dataconnection/DcTracker.java
+++ b/com/android/internal/telephony/dataconnection/DcTracker.java
@@ -149,8 +149,6 @@
     private static final boolean DATA_STALL_SUSPECTED = true;
     private static final boolean DATA_STALL_NOT_SUSPECTED = false;
 
-    private String RADIO_RESET_PROPERTY = "gsm.radioreset";
-
     private static final String INTENT_RECONNECT_ALARM =
             "com.android.internal.telephony.data-reconnect";
     private static final String INTENT_RECONNECT_ALARM_EXTRA_TYPE = "reconnect_alarm_extra_type";
@@ -2246,7 +2244,7 @@
         intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
 
         // Get current sub id.
-        int subId = SubscriptionManager.getDefaultDataSubscriptionId();
+        int subId = mPhone.getSubId();
         intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId);
 
         if (DBG) {
@@ -4546,13 +4544,11 @@
         public static final int CLEANUP                 = 1;
         public static final int REREGISTER              = 2;
         public static final int RADIO_RESTART           = 3;
-        public static final int RADIO_RESTART_WITH_PROP = 4;
 
         private static boolean isAggressiveRecovery(int value) {
             return ((value == RecoveryAction.CLEANUP) ||
                     (value == RecoveryAction.REREGISTER) ||
-                    (value == RecoveryAction.RADIO_RESTART) ||
-                    (value == RecoveryAction.RADIO_RESTART_WITH_PROP));
+                    (value == RecoveryAction.RADIO_RESTART));
         }
     }
 
@@ -4598,23 +4594,6 @@
                 EventLog.writeEvent(EventLogTags.DATA_STALL_RECOVERY_RADIO_RESTART,
                         mSentSinceLastRecv);
                 if (DBG) log("restarting radio");
-                putRecoveryAction(RecoveryAction.RADIO_RESTART_WITH_PROP);
-                restartRadio();
-                break;
-            case RecoveryAction.RADIO_RESTART_WITH_PROP:
-                // This is in case radio restart has not recovered the data.
-                // It will set an additional "gsm.radioreset" property to tell
-                // RIL or system to take further action.
-                // The implementation of hard reset recovery action is up to OEM product.
-                // Once RADIO_RESET property is consumed, it is expected to set back
-                // to false by RIL.
-                EventLog.writeEvent(EventLogTags.DATA_STALL_RECOVERY_RADIO_RESTART_WITH_PROP, -1);
-                if (DBG) log("restarting radio with gsm.radioreset to true");
-                SystemProperties.set(RADIO_RESET_PROPERTY, "true");
-                // give 1 sec so property change can be notified.
-                try {
-                    Thread.sleep(1000);
-                } catch (InterruptedException e) {}
                 restartRadio();
                 putRecoveryAction(RecoveryAction.GET_DATA_CALL_LIST);
                 break;
diff --git a/com/android/internal/telephony/gsm/GsmSmsAddress.java b/com/android/internal/telephony/gsm/GsmSmsAddress.java
index 2fbf7ed..bd8c83e 100644
--- a/com/android/internal/telephony/gsm/GsmSmsAddress.java
+++ b/com/android/internal/telephony/gsm/GsmSmsAddress.java
@@ -17,6 +17,7 @@
 package com.android.internal.telephony.gsm;
 
 import android.telephony.PhoneNumberUtils;
+
 import java.text.ParseException;
 import com.android.internal.telephony.GsmAlphabet;
 import com.android.internal.telephony.SmsAddress;
@@ -71,8 +72,11 @@
                 // Make sure the final unused BCD digit is 0xf
                 origBytes[length - 1] |= 0xf0;
             }
-            address = PhoneNumberUtils.calledPartyBCDToString(origBytes,
-                    OFFSET_TOA, length - OFFSET_TOA);
+            address = PhoneNumberUtils.calledPartyBCDToString(
+                    origBytes,
+                    OFFSET_TOA,
+                    length - OFFSET_TOA,
+                    PhoneNumberUtils.BCD_EXTENDED_TYPE_CALLED_PARTY);
 
             // And restore origBytes
             origBytes[length - 1] = lastByte;
diff --git a/com/android/internal/telephony/gsm/SmsMessage.java b/com/android/internal/telephony/gsm/SmsMessage.java
index d4098d9..1ca19e0 100644
--- a/com/android/internal/telephony/gsm/SmsMessage.java
+++ b/com/android/internal/telephony/gsm/SmsMessage.java
@@ -535,8 +535,8 @@
             } else {
                 // SC address
                 try {
-                    ret = PhoneNumberUtils
-                            .calledPartyBCDToString(mPdu, mCur, len);
+                    ret = PhoneNumberUtils.calledPartyBCDToString(
+                            mPdu, mCur, len, PhoneNumberUtils.BCD_EXTENDED_TYPE_CALLED_PARTY);
                 } catch (RuntimeException tr) {
                     Rlog.d(LOG_TAG, "invalid SC address: ", tr);
                     ret = null;
diff --git a/com/android/internal/telephony/imsphone/ImsPhone.java b/com/android/internal/telephony/imsphone/ImsPhone.java
index 45dc0b2..03d83df 100644
--- a/com/android/internal/telephony/imsphone/ImsPhone.java
+++ b/com/android/internal/telephony/imsphone/ImsPhone.java
@@ -1038,6 +1038,9 @@
                 break;
             case ImsReasonInfo.CODE_UT_SERVICE_UNAVAILABLE:
                 error = CommandException.Error.RADIO_NOT_AVAILABLE;
+                break;
+            case ImsReasonInfo.CODE_FDN_BLOCKED:
+                error = CommandException.Error.FDN_CHECK_FAILURE;
             default:
                 break;
         }
diff --git a/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java b/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
index ab30878..f837b56 100644
--- a/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
+++ b/com/android/internal/telephony/imsphone/ImsPhoneCallTracker.java
@@ -2566,7 +2566,7 @@
                                 && targetAccessTech != ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN
                                 && targetAccessTech != ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN;
                 if (isHandoverFromWifi && imsCall.isVideoCall()) {
-                    if (mNotifyHandoverVideoFromWifiToLTE) {
+                    if (mNotifyHandoverVideoFromWifiToLTE && mIsDataEnabled) {
                         log("onCallHandover :: notifying of WIFI to LTE handover.");
                         conn.onConnectionEvent(
                                 TelephonyManager.EVENT_HANDOVER_VIDEO_FROM_WIFI_TO_LTE, null);
@@ -2575,7 +2575,7 @@
                     if (!mIsDataEnabled && mIsViLteDataMetered) {
                         // Call was downgraded from WIFI to LTE and data is metered; downgrade the
                         // call now.
-                        downgradeVideoCall(ImsReasonInfo.CODE_DATA_DISABLED, conn);
+                        downgradeVideoCall(ImsReasonInfo.CODE_WIFI_LOST, conn);
                     }
                 }
             } else {
@@ -3535,8 +3535,9 @@
                 // If the carrier supports downgrading to voice, then we can simply issue a
                 // downgrade to voice instead of terminating the call.
                 modifyVideoCall(imsCall, VideoProfile.STATE_AUDIO_ONLY);
-            } else if (mSupportPauseVideo) {
-                // The carrier supports video pause signalling, so pause the video.
+            } else if (mSupportPauseVideo && reasonCode != ImsReasonInfo.CODE_WIFI_LOST) {
+                // The carrier supports video pause signalling, so pause the video if we didn't just
+                // lose wifi; in that case just disconnect.
                 mShouldUpdateImsConfigOnDisconnect = true;
                 conn.pauseVideo(VideoPauseTracker.SOURCE_DATA_ENABLED);
             } else {
diff --git a/com/android/internal/telephony/imsphone/ImsPhoneMmiCode.java b/com/android/internal/telephony/imsphone/ImsPhoneMmiCode.java
index 4e3957e..9c99055 100644
--- a/com/android/internal/telephony/imsphone/ImsPhoneMmiCode.java
+++ b/com/android/internal/telephony/imsphone/ImsPhoneMmiCode.java
@@ -40,6 +40,7 @@
 import android.text.TextUtils;
 
 import com.android.ims.ImsException;
+import com.android.ims.ImsReasonInfo;
 import com.android.ims.ImsSsInfo;
 import com.android.ims.ImsUtInterface;
 import com.android.internal.telephony.CallForwardInfo;
@@ -1172,6 +1173,14 @@
     }
 
     private CharSequence getErrorMessage(AsyncResult ar) {
+        if (ar.exception instanceof CommandException) {
+            CommandException.Error err = ((CommandException) (ar.exception)).getCommandError();
+            if (err == CommandException.Error.FDN_CHECK_FAILURE) {
+                Rlog.i(LOG_TAG, "FDN_CHECK_FAILURE");
+                return mContext.getText(com.android.internal.R.string.mmiFdnError);
+            }
+        }
+
         return mContext.getText(com.android.internal.R.string.mmiError);
     }
 
@@ -1216,18 +1225,15 @@
                 if (err.getCommandError() == CommandException.Error.PASSWORD_INCORRECT) {
                     sb.append(mContext.getText(
                             com.android.internal.R.string.passwordIncorrect));
+                } else if (err.getCommandError() == CommandException.Error.FDN_CHECK_FAILURE) {
+                    sb.append(mContext.getText(com.android.internal.R.string.mmiFdnError));
                 } else if (err.getMessage() != null) {
                     sb.append(err.getMessage());
                 } else {
                     sb.append(mContext.getText(com.android.internal.R.string.mmiError));
                 }
-            } else {
-                ImsException error = (ImsException) ar.exception;
-                if (error.getMessage() != null) {
-                    sb.append(error.getMessage());
-                } else {
-                    sb.append(getErrorMessage(ar));
-                }
+            } else if (ar.exception instanceof ImsException) {
+                sb.append(getImsErrorMessage(ar));
             }
         } else if (isActivate()) {
             mState = State.COMPLETE;
@@ -1360,12 +1366,7 @@
             mState = State.FAILED;
 
             if (ar.exception instanceof ImsException) {
-                ImsException error = (ImsException) ar.exception;
-                if (error.getMessage() != null) {
-                    sb.append(error.getMessage());
-                } else {
-                    sb.append(getErrorMessage(ar));
-                }
+                sb.append(getImsErrorMessage(ar));
             }
             else {
                 sb.append(getErrorMessage(ar));
@@ -1421,21 +1422,14 @@
         StringBuilder sb = new StringBuilder(getScString());
         sb.append("\n");
 
+        mState = State.FAILED;
         if (ar.exception != null) {
-            mState = State.FAILED;
-
             if (ar.exception instanceof ImsException) {
-                ImsException error = (ImsException) ar.exception;
-                if (error.getMessage() != null) {
-                    sb.append(error.getMessage());
-                } else {
-                    sb.append(getErrorMessage(ar));
-                }
+                sb.append(getImsErrorMessage(ar));
             } else {
                 sb.append(getErrorMessage(ar));
             }
         } else {
-            mState = State.FAILED;
             ImsSsInfo ssInfo = null;
             if (ar.result instanceof Bundle) {
                 Rlog.d(LOG_TAG, "onSuppSvcQueryComplete: Received CLIP/COLP/COLR Response.");
@@ -1486,12 +1480,7 @@
             mState = State.FAILED;
 
             if (ar.exception instanceof ImsException) {
-                ImsException error = (ImsException) ar.exception;
-                if (error.getMessage() != null) {
-                    sb.append(error.getMessage());
-                } else {
-                    sb.append(getErrorMessage(ar));
-                }
+                sb.append(getImsErrorMessage(ar));
             } else {
                 sb.append(getErrorMessage(ar));
             }
@@ -1525,14 +1514,8 @@
         mState = State.FAILED;
 
         if (ar.exception != null) {
-
             if (ar.exception instanceof ImsException) {
-                ImsException error = (ImsException) ar.exception;
-                if (error.getMessage() != null) {
-                    sb.append(error.getMessage());
-                } else {
-                    sb.append(getErrorMessage(ar));
-                }
+                sb.append(getImsErrorMessage(ar));
             }
         } else {
             Bundle ssInfo = (Bundle) ar.result;
@@ -1623,12 +1606,7 @@
             mState = State.FAILED;
 
             if (ar.exception instanceof ImsException) {
-                ImsException error = (ImsException) ar.exception;
-                if (error.getMessage() != null) {
-                    sb.append(error.getMessage());
-                } else {
-                    sb.append(getErrorMessage(ar));
-                }
+                sb.append(getImsErrorMessage(ar));
             } else {
                 sb.append(getErrorMessage(ar));
             }
@@ -1676,6 +1654,17 @@
         return sb;
     }
 
+    private CharSequence getImsErrorMessage(AsyncResult ar) {
+        ImsException error = (ImsException) ar.exception;
+        if (error.getCode() == ImsReasonInfo.CODE_FDN_BLOCKED) {
+            return mContext.getText(com.android.internal.R.string.mmiFdnError);
+        } else if (error.getMessage() != null) {
+            return error.getMessage();
+        } else {
+            return getErrorMessage(ar);
+        }
+    }
+
     @Override
     public ResultReceiver getUssdCallbackReceiver() {
         return this.mCallbackReceiver;
diff --git a/com/android/internal/telephony/uicc/AdnRecord.java b/com/android/internal/telephony/uicc/AdnRecord.java
index 203236c..4414caf 100644
--- a/com/android/internal/telephony/uicc/AdnRecord.java
+++ b/com/android/internal/telephony/uicc/AdnRecord.java
@@ -19,8 +19,8 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.telephony.PhoneNumberUtils;
-import android.text.TextUtils;
 import android.telephony.Rlog;
+import android.text.TextUtils;
 
 import com.android.internal.telephony.GsmAlphabet;
 
@@ -248,7 +248,8 @@
             Rlog.w(LOG_TAG, "[buildAdnString] Max length of tag is " + footerOffset);
             return null;
         } else {
-            bcdNumber = PhoneNumberUtils.numberToCalledPartyBCD(mNumber);
+            bcdNumber = PhoneNumberUtils.numberToCalledPartyBCD(
+                    mNumber, PhoneNumberUtils.BCD_EXTENDED_TYPE_EF_ADN);
 
             System.arraycopy(bcdNumber, 0, adnString,
                     footerOffset + ADN_TON_AND_NPI, bcdNumber.length);
@@ -289,7 +290,10 @@
             }
 
             mNumber += PhoneNumberUtils.calledPartyBCDFragmentToString(
-                                        extRecord, 2, 0xff & extRecord[1]);
+                    extRecord,
+                    2,
+                    0xff & extRecord[1],
+                    PhoneNumberUtils.BCD_EXTENDED_TYPE_EF_ADN);
 
             // We don't support ext record chaining.
 
@@ -327,7 +331,10 @@
             // the ME (see note 2)."
 
             mNumber = PhoneNumberUtils.calledPartyBCDToString(
-                            record, footerOffset + 1, numberLength);
+                    record,
+                    footerOffset + 1,
+                    numberLength,
+                    PhoneNumberUtils.BCD_EXTENDED_TYPE_EF_ADN);
 
 
             mExtRecord = 0xff & record[record.length - 1];
diff --git a/com/android/internal/telephony/uicc/SIMRecords.java b/com/android/internal/telephony/uicc/SIMRecords.java
index 724b478..dad1ee2 100644
--- a/com/android/internal/telephony/uicc/SIMRecords.java
+++ b/com/android/internal/telephony/uicc/SIMRecords.java
@@ -563,7 +563,8 @@
                 // Spec reference for EF_CFIS contents, TS 51.011 section 10.3.46.
                 if (enable && !TextUtils.isEmpty(dialNumber)) {
                     logv("EF_CFIS: updating cf number, " + Rlog.pii(LOG_TAG, dialNumber));
-                    byte[] bcdNumber = PhoneNumberUtils.numberToCalledPartyBCD(dialNumber);
+                    byte[] bcdNumber = PhoneNumberUtils.numberToCalledPartyBCD(
+                            dialNumber, PhoneNumberUtils.BCD_EXTENDED_TYPE_EF_ADN);
 
                     System.arraycopy(bcdNumber, 0, mEfCfis, CFIS_TON_NPI_OFFSET, bcdNumber.length);
 
diff --git a/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRules.java b/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRules.java
index e50f40c..bfa458b 100644
--- a/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRules.java
+++ b/com/android/internal/telephony/uicc/UiccCarrierPrivilegeRules.java
@@ -54,7 +54,10 @@
     private static final String LOG_TAG = "UiccCarrierPrivilegeRules";
     private static final boolean DBG = false;
 
-    private static final String AID = "A00000015141434C00";
+    private static final String ARAM_AID = "A00000015141434C00";
+    private static final String ARAD_AID = "A00000015144414300";
+    private static final int ARAM = 1;
+    private static final int ARAD = 0;
     private static final int CLA = 0x80;
     private static final int COMMAND = 0xCA;
     private static final int P1 = 0xFF;
@@ -184,18 +187,21 @@
     private String mStatusMessage;  // Only used for debugging.
     private int mChannelId; // Channel Id for communicating with UICC.
     private int mRetryCount;  // Number of retries for open logical channel.
+    private boolean mCheckedRules = false;  // Flag that used to mark whether get rules from ARA-D.
+    private int mAIDInUse;  // Message component to identify which AID is currently in-use.
     private final Runnable mRetryRunnable = new Runnable() {
         @Override
         public void run() {
-            openChannel();
+            openChannel(mAIDInUse);
         }
     };
 
-    private void openChannel() {
+    private void openChannel(int aidId) {
         // Send open logical channel request.
+        String aid = (aidId == ARAD) ? ARAD_AID : ARAM_AID;
         int p2 = 0x00;
-        mUiccCard.iccOpenLogicalChannel(AID, p2, /* supported p2 value */
-            obtainMessage(EVENT_OPEN_LOGICAL_CHANNEL_DONE, null));
+        mUiccCard.iccOpenLogicalChannel(aid, p2, /* supported p2 value */
+                obtainMessage(EVENT_OPEN_LOGICAL_CHANNEL_DONE, 0, aidId, null));
     }
 
     public UiccCarrierPrivilegeRules(UiccCard uiccCard, Message loadedCallback) {
@@ -207,7 +213,9 @@
         mRules = "";
         mAccessRules = new ArrayList<>();
 
-        openChannel();
+        // Open logical channel with ARA_D.
+        mAIDInUse = ARAD;
+        openChannel(mAIDInUse);
     }
 
     /**
@@ -391,6 +399,7 @@
     @Override
     public void handleMessage(Message msg) {
         AsyncResult ar;
+        mAIDInUse = msg.arg2;  // 0 means ARA-D and 1 means ARA-M.
 
         switch (msg.what) {
 
@@ -400,23 +409,34 @@
                 if (ar.exception == null && ar.result != null) {
                     mChannelId = ((int[]) ar.result)[0];
                     mUiccCard.iccTransmitApduLogicalChannel(mChannelId, CLA, COMMAND, P1, P2, P3,
-                            DATA, obtainMessage(EVENT_TRANSMIT_LOGICAL_CHANNEL_DONE,
-                                    mChannelId));
+                            DATA, obtainMessage(EVENT_TRANSMIT_LOGICAL_CHANNEL_DONE, mChannelId,
+                                    mAIDInUse));
                 } else {
                     // MISSING_RESOURCE could be due to logical channels temporarily unavailable,
                     // so we retry up to MAX_RETRY times, with an interval of RETRY_INTERVAL_MS.
                     if (ar.exception instanceof CommandException && mRetryCount < MAX_RETRY
                             && ((CommandException) (ar.exception)).getCommandError()
-                                    == CommandException.Error.MISSING_RESOURCE) {
+                            == CommandException.Error.MISSING_RESOURCE) {
                         mRetryCount++;
                         removeCallbacks(mRetryRunnable);
                         postDelayed(mRetryRunnable, RETRY_INTERVAL_MS);
                     } else {
-                        // if rules cannot be read from ARA applet,
-                        // fallback to PKCS15-based ARF.
-                        log("No ARA, try ARF next.");
-                        mUiccPkcs15 = new UiccPkcs15(mUiccCard,
-                                obtainMessage(EVENT_PKCS15_READ_DONE));
+                        if (mAIDInUse == ARAD) {
+                            // Open logical channel with ARA_M.
+                            mRules = "";
+                            openChannel(1);
+                        }
+                        if (mAIDInUse == ARAM) {
+                            if (mCheckedRules) {
+                                updateState(STATE_LOADED, "Success!");
+                            } else {
+                                // if rules cannot be read from both ARA_D and ARA_M applet,
+                                // fallback to PKCS15-based ARF.
+                                log("No ARA, try ARF next.");
+                                mUiccPkcs15 = new UiccPkcs15(mUiccCard,
+                                        obtainMessage(EVENT_PKCS15_READ_DONE));
+                            }
+                        }
                     }
                 }
                 break;
@@ -432,34 +452,49 @@
                             mRules += IccUtils.bytesToHexString(response.payload)
                                     .toUpperCase(Locale.US);
                             if (isDataComplete()) {
-                                mAccessRules = parseRules(mRules);
-                                updateState(STATE_LOADED, "Success!");
+                                mAccessRules.addAll(parseRules(mRules));
+                                if (mAIDInUse == ARAD) {
+                                    mCheckedRules = true;
+                                } else {
+                                    updateState(STATE_LOADED, "Success!");
+                                }
                             } else {
                                 mUiccCard.iccTransmitApduLogicalChannel(mChannelId, CLA, COMMAND,
                                         P1, P2_EXTENDED_DATA, P3, DATA,
                                         obtainMessage(EVENT_TRANSMIT_LOGICAL_CHANNEL_DONE,
-                                                mChannelId));
+                                                mChannelId, mAIDInUse));
                                 break;
                             }
                         } catch (IllegalArgumentException | IndexOutOfBoundsException ex) {
-                            updateState(STATE_ERROR, "Error parsing rules: " + ex);
+                            if (mAIDInUse == ARAM) {
+                                updateState(STATE_ERROR, "Error parsing rules: " + ex);
+                            }
                         }
                     } else {
-                        String errorMsg = "Invalid response: payload=" + response.payload
-                                + " sw1=" + response.sw1 + " sw2=" + response.sw2;
-                        updateState(STATE_ERROR, errorMsg);
+                        if (mAIDInUse == ARAM) {
+                            String errorMsg = "Invalid response: payload=" + response.payload
+                                    + " sw1=" + response.sw1 + " sw2=" + response.sw2;
+                            updateState(STATE_ERROR, errorMsg);
+                        }
                     }
                 } else {
-                    updateState(STATE_ERROR, "Error reading value from SIM.");
+                    if (mAIDInUse == ARAM) {
+                        updateState(STATE_ERROR, "Error reading value from SIM.");
+                    }
                 }
 
                 mUiccCard.iccCloseLogicalChannel(mChannelId, obtainMessage(
-                        EVENT_CLOSE_LOGICAL_CHANNEL_DONE));
+                        EVENT_CLOSE_LOGICAL_CHANNEL_DONE, 0, mAIDInUse));
                 mChannelId = -1;
                 break;
 
             case EVENT_CLOSE_LOGICAL_CHANNEL_DONE:
                 log("EVENT_CLOSE_LOGICAL_CHANNEL_DONE");
+                if (mAIDInUse == ARAD) {
+                    // Close logical channel with ARA_D and then open logical channel with ARA_M.
+                    mRules = "";
+                    openChannel(1);
+                }
                 break;
 
             case EVENT_PKCS15_READ_DONE:
@@ -492,7 +527,7 @@
             String lengthBytes = allRules.parseLength(mRules);
             log("isDataComplete lengthBytes: " + lengthBytes);
             if (mRules.length() == TAG_ALL_REF_AR_DO.length() + lengthBytes.length() +
-                                   allRules.length) {
+                    allRules.length) {
                 log("isDataComplete yes");
                 return true;
             } else {
@@ -522,7 +557,7 @@
             if (accessRule != null) {
                 accessRules.add(accessRule);
             } else {
-              Rlog.e(LOG_TAG, "Skip unrecognized rule." + refArDo.value);
+                Rlog.e(LOG_TAG, "Skip unrecognized rule." + refArDo.value);
             }
         }
         return accessRules;
@@ -644,15 +679,15 @@
      * Converts state into human readable format.
      */
     private String getStateString(int state) {
-      switch (state) {
-        case STATE_LOADING:
-            return "STATE_LOADING";
-        case STATE_LOADED:
-            return "STATE_LOADED";
-        case STATE_ERROR:
-            return "STATE_ERROR";
-        default:
-            return "UNKNOWN";
-      }
+        switch (state) {
+            case STATE_LOADING:
+                return "STATE_LOADING";
+            case STATE_LOADED:
+                return "STATE_LOADED";
+            case STATE_ERROR:
+                return "STATE_ERROR";
+            default:
+                return "UNKNOWN";
+        }
     }
-}
+}
\ No newline at end of file
diff --git a/com/android/internal/util/BitUtils.java b/com/android/internal/util/BitUtils.java
index 28f12eb..ba80aea 100644
--- a/com/android/internal/util/BitUtils.java
+++ b/com/android/internal/util/BitUtils.java
@@ -93,6 +93,10 @@
         return s & 0xffff;
     }
 
+    public static int uint16(byte hi, byte lo) {
+        return ((hi & 0xff) << 8) | (lo & 0xff);
+    }
+
     public static long uint32(int i) {
         return i & 0xffffffffL;
     }
diff --git a/com/android/internal/util/RingBuffer.java b/com/android/internal/util/RingBuffer.java
new file mode 100644
index 0000000..ad84353
--- /dev/null
+++ b/com/android/internal/util/RingBuffer.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.util;
+
+import static com.android.internal.util.Preconditions.checkArgumentPositive;
+
+import java.lang.reflect.Array;
+import java.util.Arrays;
+
+/**
+ * A simple ring buffer structure with bounded capacity backed by an array.
+ * Events can always be added at the logical end of the buffer. If the buffer is
+ * full, oldest events are dropped when new events are added.
+ * {@hide}
+ */
+public class RingBuffer<T> {
+
+    // Array for storing events.
+    private final T[] mBuffer;
+    // Cursor keeping track of the logical end of the array. This cursor never
+    // wraps and instead keeps track of the total number of append() operations.
+    private long mCursor = 0;
+
+    public RingBuffer(Class<T> c, int capacity) {
+        checkArgumentPositive(capacity, "A RingBuffer cannot have 0 capacity");
+        // Java cannot create generic arrays without a runtime hint.
+        mBuffer = (T[]) Array.newInstance(c, capacity);
+    }
+
+    public int size() {
+        return (int) Math.min(mBuffer.length, (long) mCursor);
+    }
+
+    public void append(T t) {
+        mBuffer[indexOf(mCursor++)] = t;
+    }
+
+    public T[] toArray() {
+        // Only generic way to create a T[] from another T[]
+        T[] out = Arrays.copyOf(mBuffer, size(), (Class<T[]>) mBuffer.getClass());
+        // Reverse iteration from youngest event to oldest event.
+        long inCursor = mCursor - 1;
+        int outIdx = out.length - 1;
+        while (outIdx >= 0) {
+            out[outIdx--] = (T) mBuffer[indexOf(inCursor--)];
+        }
+        return out;
+    }
+
+    private int indexOf(long cursor) {
+        return (int) Math.abs(cursor % mBuffer.length);
+    }
+}
diff --git a/com/android/internal/widget/LinearLayoutManager.java b/com/android/internal/widget/LinearLayoutManager.java
index d82c746..0000a74 100644
--- a/com/android/internal/widget/LinearLayoutManager.java
+++ b/com/android/internal/widget/LinearLayoutManager.java
@@ -168,10 +168,6 @@
     /**
      * Constructor used when layout manager is set in XML by RecyclerView attribute
      * "layoutManager". Defaults to vertical orientation.
-     *
-     * @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_android_orientation
-     * @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_reverseLayout
-     * @attr ref android.support.v7.recyclerview.R.styleable#RecyclerView_stackFromEnd
      */
     public LinearLayoutManager(Context context, AttributeSet attrs, int defStyleAttr,
             int defStyleRes) {
diff --git a/com/android/internal/widget/LockPatternUtils.java b/com/android/internal/widget/LockPatternUtils.java
index b8ef82b..4be6b28 100644
--- a/com/android/internal/widget/LockPatternUtils.java
+++ b/com/android/internal/widget/LockPatternUtils.java
@@ -303,7 +303,7 @@
     }
 
     public void reportFailedPasswordAttempt(int userId) {
-        if (userId == USER_FRP && frpCredentialEnabled()) {
+        if (userId == USER_FRP && frpCredentialEnabled(mContext)) {
             return;
         }
         getDevicePolicyManager().reportFailedPasswordAttempt(userId);
@@ -311,7 +311,7 @@
     }
 
     public void reportSuccessfulPasswordAttempt(int userId) {
-        if (userId == USER_FRP && frpCredentialEnabled()) {
+        if (userId == USER_FRP && frpCredentialEnabled(mContext)) {
             return;
         }
         getDevicePolicyManager().reportSuccessfulPasswordAttempt(userId);
@@ -319,21 +319,21 @@
     }
 
     public void reportPasswordLockout(int timeoutMs, int userId) {
-        if (userId == USER_FRP && frpCredentialEnabled()) {
+        if (userId == USER_FRP && frpCredentialEnabled(mContext)) {
             return;
         }
         getTrustManager().reportUnlockLockout(timeoutMs, userId);
     }
 
     public int getCurrentFailedPasswordAttempts(int userId) {
-        if (userId == USER_FRP && frpCredentialEnabled()) {
+        if (userId == USER_FRP && frpCredentialEnabled(mContext)) {
             return 0;
         }
         return getDevicePolicyManager().getCurrentFailedPasswordAttempts(userId);
     }
 
     public int getMaximumFailedPasswordsForWipe(int userId) {
-        if (userId == USER_FRP && frpCredentialEnabled()) {
+        if (userId == USER_FRP && frpCredentialEnabled(mContext)) {
             return 0;
         }
         return getDevicePolicyManager().getMaximumFailedPasswordsForWipe(
@@ -1774,11 +1774,12 @@
         return getLong(SYNTHETIC_PASSWORD_ENABLED_KEY, 0, UserHandle.USER_SYSTEM) != 0;
     }
 
-    public static boolean userOwnsFrpCredential(UserInfo info) {
-        return info != null && info.isPrimary() && info.isAdmin() && frpCredentialEnabled();
+    public static boolean userOwnsFrpCredential(Context context, UserInfo info) {
+        return info != null && info.isPrimary() && info.isAdmin() && frpCredentialEnabled(context);
     }
 
-    public static boolean frpCredentialEnabled() {
-        return FRP_CREDENTIAL_ENABLED;
+    public static boolean frpCredentialEnabled(Context context) {
+        return FRP_CREDENTIAL_ENABLED && context.getResources().getBoolean(
+                com.android.internal.R.bool.config_enableCredentialFactoryResetProtection);
     }
 }
diff --git a/com/android/internal/widget/Magnifier.java b/com/android/internal/widget/Magnifier.java
new file mode 100644
index 0000000..86e7b38
--- /dev/null
+++ b/com/android/internal/widget/Magnifier.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.internal.widget;
+
+import android.annotation.FloatRange;
+import android.annotation.NonNull;
+import android.annotation.UiThread;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.LayoutInflater;
+import android.view.PixelCopy;
+import android.view.View;
+import android.view.ViewRootImpl;
+import android.widget.ImageView;
+import android.widget.PopupWindow;
+
+import com.android.internal.R;
+import com.android.internal.util.Preconditions;
+
+/**
+ * Android magnifier widget. Can be used by any view which is attached to window.
+ */
+public final class Magnifier {
+    private static final String LOG_TAG = "magnifier";
+    // The view for which this magnifier is attached.
+    private final View mView;
+    // The window containing the magnifier.
+    private final PopupWindow mWindow;
+    // The center coordinates of the window containing the magnifier.
+    private final Point mWindowCoords = new Point();
+    // The width of the window containing the magnifier.
+    private final int mWindowWidth;
+    // The height of the window containing the magnifier.
+    private final int mWindowHeight;
+    // The bitmap used to display the contents of the magnifier.
+    private final Bitmap mBitmap;
+    // The center coordinates of the content that is to be magnified.
+    private final Point mCenterZoomCoords = new Point();
+    // The callback of the pixel copy request will be invoked on this Handler when
+    // the copy is finished.
+    private final Handler mPixelCopyHandler = Handler.getMain();
+
+    /**
+     * Initializes a magnifier.
+     *
+     * @param view the view for which this magnifier is attached
+     */
+    @UiThread
+    public Magnifier(@NonNull View view) {
+        mView = Preconditions.checkNotNull(view);
+        final Context context = mView.getContext();
+        final View content = LayoutInflater.from(context).inflate(R.layout.magnifier, null);
+        mWindowWidth = context.getResources().getDimensionPixelSize(R.dimen.magnifier_width);
+        mWindowHeight = context.getResources().getDimensionPixelSize(R.dimen.magnifier_height);
+        final float elevation = context.getResources().getDimension(R.dimen.magnifier_elevation);
+
+        mWindow = new PopupWindow(context);
+        mWindow.setContentView(content);
+        mWindow.setWidth(mWindowWidth);
+        mWindow.setHeight(mWindowHeight);
+        mWindow.setElevation(elevation);
+        mWindow.setTouchable(false);
+        mWindow.setBackgroundDrawable(null);
+
+        mBitmap = Bitmap.createBitmap(mWindowWidth, mWindowHeight, Bitmap.Config.ARGB_8888);
+        getImageView().setImageBitmap(mBitmap);
+    }
+
+    /**
+     * Shows the magnifier on the screen.
+     *
+     * @param centerXOnScreen horizontal coordinate of the center point of the magnifier source
+     * @param centerYOnScreen vertical coordinate of the center point of the magnifier source
+     * @param scale the scale at which the magnifier zooms on the source content
+     */
+    public void show(@FloatRange(from=0) float centerXOnScreen,
+            @FloatRange(from=0) float centerYOnScreen,
+            @FloatRange(from=1, to=10) float scale) {
+        maybeResizeBitmap(scale);
+        configureCoordinates(centerXOnScreen, centerYOnScreen);
+        performPixelCopy();
+
+        if (mWindow.isShowing()) {
+            mWindow.update(mWindowCoords.x, mWindowCoords.y, mWindow.getWidth(),
+                    mWindow.getHeight());
+        } else {
+            mWindow.showAtLocation(mView.getRootView(), Gravity.NO_GRAVITY,
+                    mWindowCoords.x, mWindowCoords.y);
+        }
+    }
+
+    /**
+     * Dismisses the magnifier from the screen.
+     */
+    public void dismiss() {
+        mWindow.dismiss();
+    }
+
+    /**
+     * @return the height of the magnifier window.
+     */
+    public int getHeight() {
+        return mWindowHeight;
+    }
+
+    /**
+     * @return the width of the magnifier window.
+     */
+    public int getWidth() {
+        return mWindowWidth;
+    }
+
+    private void maybeResizeBitmap(float scale) {
+        final int bitmapWidth = (int) (mWindowWidth / scale);
+        final int bitmapHeight = (int) (mWindowHeight / scale);
+        if (mBitmap.getWidth() != bitmapWidth || mBitmap.getHeight() != bitmapHeight) {
+            mBitmap.reconfigure(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888);
+            getImageView().setImageBitmap(mBitmap);
+        }
+    }
+
+    private void configureCoordinates(float posXOnScreen, float posYOnScreen) {
+        mCenterZoomCoords.x = (int) posXOnScreen;
+        mCenterZoomCoords.y = (int) posYOnScreen;
+
+        final int verticalMagnifierOffset = mView.getContext().getResources().getDimensionPixelSize(
+                R.dimen.magnifier_offset);
+        final int availableTopSpace = (mCenterZoomCoords.y - mWindowHeight / 2)
+                - verticalMagnifierOffset - (mBitmap.getHeight() / 2);
+
+        mWindowCoords.x = mCenterZoomCoords.x - mWindowWidth / 2;
+        mWindowCoords.y = mCenterZoomCoords.y - mWindowHeight / 2
+                + verticalMagnifierOffset * (availableTopSpace > 0 ? -1 : 1);
+    }
+
+    private void performPixelCopy() {
+        int startX = mCenterZoomCoords.x - mBitmap.getWidth() / 2;
+        // Clamp startX value to avoid distorting the rendering of the magnifier content.
+        if (startX < 0) {
+            startX = 0;
+        } else if (startX + mBitmap.getWidth() > mView.getWidth()) {
+            startX = mView.getWidth() - mBitmap.getWidth();
+        }
+
+        final int startY = mCenterZoomCoords.y - mBitmap.getHeight() / 2;
+        final ViewRootImpl viewRootImpl = mView.getViewRootImpl();
+
+        if (viewRootImpl != null && viewRootImpl.mSurface != null
+                && viewRootImpl.mSurface.isValid()) {
+            PixelCopy.request(
+                    viewRootImpl.mSurface,
+                    new Rect(startX, startY, startX + mBitmap.getWidth(),
+                            startY + mBitmap.getHeight()),
+                    mBitmap,
+                    result -> getImageView().invalidate(),
+                    mPixelCopyHandler);
+        } else {
+            Log.d(LOG_TAG, "Could not perform PixelCopy request");
+        }
+    }
+
+    private ImageView getImageView() {
+        return mWindow.getContentView().findViewById(R.id.magnifier_image);
+    }
+}
diff --git a/com/android/keyguard/KeyguardDisplayManager.java b/com/android/keyguard/KeyguardDisplayManager.java
index 8de1d31..2bc0e45 100644
--- a/com/android/keyguard/KeyguardDisplayManager.java
+++ b/com/android/keyguard/KeyguardDisplayManager.java
@@ -15,6 +15,8 @@
  */
 package com.android.keyguard;
 
+import static android.view.Display.INVALID_DISPLAY;
+
 import android.app.Presentation;
 import android.content.Context;
 import android.content.DialogInterface;
@@ -28,16 +30,21 @@
 import android.view.View;
 import android.view.WindowManager;
 
+// TODO(multi-display): Support multiple external displays
 public class KeyguardDisplayManager {
     protected static final String TAG = "KeyguardDisplayManager";
     private static boolean DEBUG = KeyguardConstants.DEBUG;
+
+    private final ViewMediatorCallback mCallback;
+    private final MediaRouter mMediaRouter;
+    private final Context mContext;
+
     Presentation mPresentation;
-    private MediaRouter mMediaRouter;
-    private Context mContext;
     private boolean mShowing;
 
-    public KeyguardDisplayManager(Context context) {
+    public KeyguardDisplayManager(Context context, ViewMediatorCallback callback) {
         mContext = context;
+        mCallback = callback;
         mMediaRouter = (MediaRouter) mContext.getSystemService(Context.MEDIA_ROUTER_SERVICE);
     }
 
@@ -90,6 +97,7 @@
     };
 
     protected void updateDisplays(boolean showing) {
+        Presentation originalPresentation = mPresentation;
         if (showing) {
             MediaRouter.RouteInfo route = mMediaRouter.getSelectedRoute(
                     MediaRouter.ROUTE_TYPE_REMOTE_DISPLAY);
@@ -121,6 +129,13 @@
                 mPresentation = null;
             }
         }
+
+        // mPresentation is only updated when the display changes
+        if (mPresentation != originalPresentation) {
+            final int displayId = mPresentation != null
+                    ? mPresentation.getDisplay().getDisplayId() : INVALID_DISPLAY;
+            mCallback.onSecondaryDisplayShowingChanged(displayId);
+        }
     }
 
     private final static class KeyguardPresentation extends Presentation {
diff --git a/com/android/keyguard/KeyguardSimPinView.java b/com/android/keyguard/KeyguardSimPinView.java
index 7225ba9..432b406 100644
--- a/com/android/keyguard/KeyguardSimPinView.java
+++ b/com/android/keyguard/KeyguardSimPinView.java
@@ -66,7 +66,11 @@
                 // again when the PUK locked SIM is re-entered.
                 case ABSENT: {
                     KeyguardUpdateMonitor.getInstance(getContext()).reportSimUnlocked(mSubId);
-                    mCallback.dismiss(true, KeyguardUpdateMonitor.getCurrentUser());
+                    // onSimStateChanged callback can fire when the SIM PIN lock is not currently
+                    // active and mCallback is null.
+                    if (mCallback != null) {
+                        mCallback.dismiss(true, KeyguardUpdateMonitor.getCurrentUser());
+                    }
                     break;
                 }
                 default:
diff --git a/com/android/keyguard/KeyguardSimPukView.java b/com/android/keyguard/KeyguardSimPukView.java
index 171cf23..7f79008 100644
--- a/com/android/keyguard/KeyguardSimPukView.java
+++ b/com/android/keyguard/KeyguardSimPukView.java
@@ -72,7 +72,11 @@
                 // move into the READY state and the PUK lock keyguard should be removed.
                 case READY: {
                     KeyguardUpdateMonitor.getInstance(getContext()).reportSimUnlocked(mSubId);
-                    mCallback.dismiss(true, KeyguardUpdateMonitor.getCurrentUser());
+                    // mCallback can be null if onSimStateChanged callback is called when keyguard
+                    // isn't active.
+                    if (mCallback != null) {
+                        mCallback.dismiss(true, KeyguardUpdateMonitor.getCurrentUser());
+                    }
                     break;
                 }
                 default:
diff --git a/com/android/keyguard/KeyguardUpdateMonitor.java b/com/android/keyguard/KeyguardUpdateMonitor.java
index d95402c..d83a6c6 100644
--- a/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -16,6 +16,8 @@
 
 package com.android.keyguard;
 
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.content.Intent.ACTION_USER_UNLOCKED;
 import static android.os.BatteryManager.BATTERY_HEALTH_UNKNOWN;
 import static android.os.BatteryManager.BATTERY_STATUS_FULL;
@@ -452,6 +454,7 @@
      */
     public void setKeyguardGoingAway(boolean goingAway) {
         mKeyguardGoingAway = goingAway;
+        updateFingerprintListeningState();
     }
 
     /**
@@ -1069,6 +1072,7 @@
                 cb.onDreamingStateChanged(mIsDreaming);
             }
         }
+        updateFingerprintListeningState();
     }
 
     /**
@@ -1772,7 +1776,7 @@
         public void onTaskStackChangedBackground() {
             try {
                 ActivityManager.StackInfo info = ActivityManager.getService().getStackInfo(
-                        ActivityManager.StackId.ASSISTANT_STACK_ID);
+                        WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_ASSISTANT);
                 if (info == null) {
                     return;
                 }
diff --git a/com/android/keyguard/ViewMediatorCallback.java b/com/android/keyguard/ViewMediatorCallback.java
index 327d218..b194de4 100644
--- a/com/android/keyguard/ViewMediatorCallback.java
+++ b/com/android/keyguard/ViewMediatorCallback.java
@@ -88,4 +88,9 @@
      *         {@link KeyguardSecurityView#PROMPT_REASON_TIMEOUT}.
      */
     int getBouncerPromptReason();
+
+    /**
+     * Invoked when the secondary display showing a keyguard window changes.
+     */
+    void onSecondaryDisplayShowingChanged(int displayId);
 }
diff --git a/com/android/layoutlib/bridge/Bridge.java b/com/android/layoutlib/bridge/Bridge.java
index 5dca8e7..0cfc181 100644
--- a/com/android/layoutlib/bridge/Bridge.java
+++ b/com/android/layoutlib/bridge/Bridge.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (C) 2016 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,659 +14,62 @@
  * limitations under the License.
  */
 
-package com.android.layoutlib.bridge;
-
-import com.android.ide.common.rendering.api.Capability;
-import com.android.ide.common.rendering.api.DrawableParams;
-import com.android.ide.common.rendering.api.Features;
-import com.android.ide.common.rendering.api.LayoutLog;
-import com.android.ide.common.rendering.api.RenderSession;
+package com.android.layoutlib.bridge;import com.android.ide.common.rendering.api.RenderSession;
 import com.android.ide.common.rendering.api.Result;
 import com.android.ide.common.rendering.api.Result.Status;
 import com.android.ide.common.rendering.api.SessionParams;
-import com.android.layoutlib.bridge.android.RenderParamsFlags;
-import com.android.layoutlib.bridge.impl.RenderDrawable;
-import com.android.layoutlib.bridge.impl.RenderSessionImpl;
-import com.android.layoutlib.bridge.util.DynamicIdMap;
-import com.android.ninepatch.NinePatchChunk;
-import com.android.resources.ResourceType;
-import com.android.tools.layoutlib.create.MethodAdapter;
-import com.android.tools.layoutlib.create.OverrideMethod;
-import com.android.util.Pair;
 
-import android.annotation.NonNull;
-import android.content.res.BridgeAssetManager;
-import android.graphics.Bitmap;
-import android.graphics.FontFamily_Delegate;
-import android.graphics.Typeface;
-import android.graphics.Typeface_Delegate;
-import android.icu.util.ULocale;
-import android.os.Looper;
-import android.os.Looper_Accessor;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewParent;
-
-import java.io.File;
-import java.lang.ref.SoftReference;
-import java.lang.reflect.Field;
-import java.lang.reflect.Modifier;
-import java.util.Arrays;
-import java.util.EnumMap;
-import java.util.EnumSet;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.WeakHashMap;
-import java.util.concurrent.locks.ReentrantLock;
-
-import libcore.io.MemoryMappedFile_Delegate;
-
-import static com.android.ide.common.rendering.api.Result.Status.ERROR_UNKNOWN;
+import java.awt.Graphics2D;
+import java.awt.image.BufferedImage;
 
 /**
- * Main entry point of the LayoutLib Bridge.
- * <p/>To use this bridge, simply instantiate an object of type {@link Bridge} and call
- * {@link #createSession(SessionParams)}
+ * Legacy Bridge used in the SDK version of layoutlib
  */
 public final class Bridge extends com.android.ide.common.rendering.api.Bridge {
+    private static final String SDK_NOT_SUPPORTED = "The SDK layoutlib version is not supported";
+    private static final Result NOT_SUPPORTED_RESULT =
+            Status.NOT_IMPLEMENTED.createResult(SDK_NOT_SUPPORTED);
+    private static BufferedImage sImage;
 
-    private static final String ICU_LOCALE_DIRECTION_RTL = "right-to-left";
+    private static class BridgeRenderSession extends RenderSession {
 
-    public static class StaticMethodNotImplementedException extends RuntimeException {
-        private static final long serialVersionUID = 1L;
+        @Override
+        public synchronized BufferedImage getImage() {
+            if (sImage == null) {
+                sImage = new BufferedImage(500, 500, BufferedImage.TYPE_INT_ARGB);
+                Graphics2D g = sImage.createGraphics();
+                g.clearRect(0, 0, 500, 500);
+                g.drawString(SDK_NOT_SUPPORTED, 20, 20);
+                g.dispose();
+            }
 
-        public StaticMethodNotImplementedException(String msg) {
-            super(msg);
+            return sImage;
+        }
+
+        @Override
+        public Result render(long timeout, boolean forceMeasure) {
+            return NOT_SUPPORTED_RESULT;
+        }
+
+        @Override
+        public Result measure(long timeout) {
+            return NOT_SUPPORTED_RESULT;
+        }
+
+        @Override
+        public Result getResult() {
+            return NOT_SUPPORTED_RESULT;
         }
     }
 
-    /**
-     * Lock to ensure only one rendering/inflating happens at a time.
-     * This is due to some singleton in the Android framework.
-     */
-    private final static ReentrantLock sLock = new ReentrantLock();
 
-    /**
-     * Maps from id to resource type/name. This is for com.android.internal.R
-     */
-    @SuppressWarnings("deprecation")
-    private final static Map<Integer, Pair<ResourceType, String>> sRMap = new HashMap<>();
-
-    /**
-     * Reverse map compared to sRMap, resource type -> (resource name -> id).
-     * This is for com.android.internal.R.
-     */
-    private final static Map<ResourceType, Map<String, Integer>> sRevRMap = new EnumMap<>(ResourceType.class);
-
-    // framework resources are defined as 0x01XX#### where XX is the resource type (layout,
-    // drawable, etc...). Using FF as the type allows for 255 resource types before we get a
-    // collision which should be fine.
-    private final static int DYNAMIC_ID_SEED_START = 0x01ff0000;
-    private final static DynamicIdMap sDynamicIds = new DynamicIdMap(DYNAMIC_ID_SEED_START);
-
-    private final static Map<Object, Map<String, SoftReference<Bitmap>>> sProjectBitmapCache =
-            new WeakHashMap<>();
-    private final static Map<Object, Map<String, SoftReference<NinePatchChunk>>> sProject9PatchCache =
-            new WeakHashMap<>();
-
-    private final static Map<String, SoftReference<Bitmap>> sFrameworkBitmapCache = new HashMap<>();
-    private final static Map<String, SoftReference<NinePatchChunk>> sFramework9PatchCache =
-            new HashMap<>();
-
-    private static Map<String, Map<String, Integer>> sEnumValueMap;
-    private static Map<String, String> sPlatformProperties;
-
-    /**
-     * A default log than prints to stdout/stderr.
-     */
-    private final static LayoutLog sDefaultLog = new LayoutLog() {
-        @Override
-        public void error(String tag, String message, Object data) {
-            System.err.println(message);
-        }
-
-        @Override
-        public void error(String tag, String message, Throwable throwable, Object data) {
-            System.err.println(message);
-        }
-
-        @Override
-        public void warning(String tag, String message, Object data) {
-            System.out.println(message);
-        }
-    };
-
-    /**
-     * Current log.
-     */
-    private static LayoutLog sCurrentLog = sDefaultLog;
-
-    private static final int LAST_SUPPORTED_FEATURE = Features.THEME_PREVIEW_NAVIGATION_BAR;
+    @Override
+    public RenderSession createSession(SessionParams params) {
+        return new BridgeRenderSession();
+    }
 
     @Override
     public int getApiLevel() {
-        return com.android.ide.common.rendering.api.Bridge.API_CURRENT;
-    }
-
-    @SuppressWarnings("deprecation")
-    @Override
-    @Deprecated
-    public EnumSet<Capability> getCapabilities() {
-        // The Capability class is deprecated and frozen. All Capabilities enumerated there are
-        // supported by this version of LayoutLibrary. So, it's safe to use EnumSet.allOf()
-        return EnumSet.allOf(Capability.class);
-    }
-
-    @Override
-    public boolean supports(int feature) {
-        return feature <= LAST_SUPPORTED_FEATURE;
-    }
-
-    @Override
-    public boolean init(Map<String,String> platformProperties,
-            File fontLocation,
-            Map<String, Map<String, Integer>> enumValueMap,
-            LayoutLog log) {
-        sPlatformProperties = platformProperties;
-        sEnumValueMap = enumValueMap;
-
-        BridgeAssetManager.initSystem();
-
-        // When DEBUG_LAYOUT is set and is not 0 or false, setup a default listener
-        // on static (native) methods which prints the signature on the console and
-        // throws an exception.
-        // This is useful when testing the rendering in ADT to identify static native
-        // methods that are ignored -- layoutlib_create makes them returns 0/false/null
-        // which is generally OK yet might be a problem, so this is how you'd find out.
-        //
-        // Currently layoutlib_create only overrides static native method.
-        // Static non-natives are not overridden and thus do not get here.
-        final String debug = System.getenv("DEBUG_LAYOUT");
-        if (debug != null && !debug.equals("0") && !debug.equals("false")) {
-
-            OverrideMethod.setDefaultListener(new MethodAdapter() {
-                @Override
-                public void onInvokeV(String signature, boolean isNative, Object caller) {
-                    sDefaultLog.error(null, "Missing Stub: " + signature +
-                            (isNative ? " (native)" : ""), null /*data*/);
-
-                    if (debug.equalsIgnoreCase("throw")) {
-                        // Throwing this exception doesn't seem that useful. It breaks
-                        // the layout editor yet doesn't display anything meaningful to the
-                        // user. Having the error in the console is just as useful. We'll
-                        // throw it only if the environment variable is "throw" or "THROW".
-                        throw new StaticMethodNotImplementedException(signature);
-                    }
-                }
-            });
-        }
-
-        // load the fonts.
-        FontFamily_Delegate.setFontLocation(fontLocation.getAbsolutePath());
-        MemoryMappedFile_Delegate.setDataDir(fontLocation.getAbsoluteFile().getParentFile());
-
-        // now parse com.android.internal.R (and only this one as android.R is a subset of
-        // the internal version), and put the content in the maps.
-        try {
-            Class<?> r = com.android.internal.R.class;
-            // Parse the styleable class first, since it may contribute to attr values.
-            parseStyleable();
-
-            for (Class<?> inner : r.getDeclaredClasses()) {
-                if (inner == com.android.internal.R.styleable.class) {
-                    // Already handled the styleable case. Not skipping attr, as there may be attrs
-                    // that are not referenced from styleables.
-                    continue;
-                }
-                String resTypeName = inner.getSimpleName();
-                ResourceType resType = ResourceType.getEnum(resTypeName);
-                if (resType != null) {
-                    Map<String, Integer> fullMap = null;
-                    switch (resType) {
-                        case ATTR:
-                            fullMap = sRevRMap.get(ResourceType.ATTR);
-                            break;
-                        case STRING:
-                        case STYLE:
-                            // Slightly less than thousand entries in each.
-                            fullMap = new HashMap<>(1280);
-                            // no break.
-                        default:
-                            if (fullMap == null) {
-                                fullMap = new HashMap<>();
-                            }
-                            sRevRMap.put(resType, fullMap);
-                    }
-
-                    for (Field f : inner.getDeclaredFields()) {
-                        // only process static final fields. Since the final attribute may have
-                        // been altered by layoutlib_create, we only check static
-                        if (!isValidRField(f)) {
-                            continue;
-                        }
-                        Class<?> type = f.getType();
-                        if (!type.isArray()) {
-                            Integer value = (Integer) f.get(null);
-                            //noinspection deprecation
-                            sRMap.put(value, Pair.of(resType, f.getName()));
-                            fullMap.put(f.getName(), value);
-                        }
-                    }
-                }
-            }
-        } catch (Exception throwable) {
-            if (log != null) {
-                log.error(LayoutLog.TAG_BROKEN,
-                        "Failed to load com.android.internal.R from the layout library jar",
-                        throwable, null);
-            }
-            return false;
-        }
-
-        return true;
-    }
-
-    /**
-     * Tests if the field is pubic, static and one of int or int[].
-     */
-    private static boolean isValidRField(Field field) {
-        int modifiers = field.getModifiers();
-        boolean isAcceptable = Modifier.isPublic(modifiers) && Modifier.isStatic(modifiers);
-        Class<?> type = field.getType();
-        return isAcceptable && type == int.class ||
-                (type.isArray() && type.getComponentType() == int.class);
-
-    }
-
-    private static void parseStyleable() throws Exception {
-        // R.attr doesn't contain all the needed values. There are too many resources in the
-        // framework for all to be in the R class. Only the ones specified manually in
-        // res/values/symbols.xml are put in R class. Since, we need to create a map of all attr
-        // values, we try and find them from the styleables.
-
-        // There were 1500 elements in this map at M timeframe.
-        Map<String, Integer> revRAttrMap = new HashMap<>(2048);
-        sRevRMap.put(ResourceType.ATTR, revRAttrMap);
-        // There were 2000 elements in this map at M timeframe.
-        Map<String, Integer> revRStyleableMap = new HashMap<>(3072);
-        sRevRMap.put(ResourceType.STYLEABLE, revRStyleableMap);
-        Class<?> c = com.android.internal.R.styleable.class;
-        Field[] fields = c.getDeclaredFields();
-        // Sort the fields to bring all arrays to the beginning, so that indices into the array are
-        // able to refer back to the arrays (i.e. no forward references).
-        Arrays.sort(fields, (o1, o2) -> {
-            if (o1 == o2) {
-                return 0;
-            }
-            Class<?> t1 = o1.getType();
-            Class<?> t2 = o2.getType();
-            if (t1.isArray() && !t2.isArray()) {
-                return -1;
-            } else if (t2.isArray() && !t1.isArray()) {
-                return 1;
-            }
-            return o1.getName().compareTo(o2.getName());
-        });
-        Map<String, int[]> styleables = new HashMap<>();
-        for (Field field : fields) {
-            if (!isValidRField(field)) {
-                // Only consider public static fields that are int or int[].
-                // Don't check the final flag as it may have been modified by layoutlib_create.
-                continue;
-            }
-            String name = field.getName();
-            if (field.getType().isArray()) {
-                int[] styleableValue = (int[]) field.get(null);
-                styleables.put(name, styleableValue);
-                continue;
-            }
-            // Not an array.
-            String arrayName = name;
-            int[] arrayValue = null;
-            int index;
-            while ((index = arrayName.lastIndexOf('_')) >= 0) {
-                // Find the name of the corresponding styleable.
-                // Search in reverse order so that attrs like LinearLayout_Layout_layout_gravity
-                // are mapped to LinearLayout_Layout and not to LinearLayout.
-                arrayName = arrayName.substring(0, index);
-                arrayValue = styleables.get(arrayName);
-                if (arrayValue != null) {
-                    break;
-                }
-            }
-            index = (Integer) field.get(null);
-            if (arrayValue != null) {
-                String attrName = name.substring(arrayName.length() + 1);
-                int attrValue = arrayValue[index];
-                //noinspection deprecation
-                sRMap.put(attrValue, Pair.of(ResourceType.ATTR, attrName));
-                revRAttrMap.put(attrName, attrValue);
-            }
-            //noinspection deprecation
-            sRMap.put(index, Pair.of(ResourceType.STYLEABLE, name));
-            revRStyleableMap.put(name, index);
-        }
-    }
-
-    @Override
-    public boolean dispose() {
-        BridgeAssetManager.clearSystem();
-
-        // dispose of the default typeface.
-        Typeface_Delegate.resetDefaults();
-        Typeface.sDynamicTypefaceCache.evictAll();
-        sProject9PatchCache.clear();
-        sProjectBitmapCache.clear();
-
-        return true;
-    }
-
-    /**
-     * Starts a layout session by inflating and rendering it. The method returns a
-     * {@link RenderSession} on which further actions can be taken.
-     * <p/>
-     * If {@link SessionParams} includes the {@link RenderParamsFlags#FLAG_DO_NOT_RENDER_ON_CREATE},
-     * this method will only inflate the layout but will NOT render it.
-     * @param params the {@link SessionParams} object with all the information necessary to create
-     *           the scene.
-     * @return a new {@link RenderSession} object that contains the result of the layout.
-     * @since 5
-     */
-    @Override
-    public RenderSession createSession(SessionParams params) {
-        try {
-            Result lastResult;
-            RenderSessionImpl scene = new RenderSessionImpl(params);
-            try {
-                prepareThread();
-                lastResult = scene.init(params.getTimeout());
-                if (lastResult.isSuccess()) {
-                    lastResult = scene.inflate();
-
-                    boolean doNotRenderOnCreate = Boolean.TRUE.equals(
-                            params.getFlag(RenderParamsFlags.FLAG_DO_NOT_RENDER_ON_CREATE));
-                    if (lastResult.isSuccess() && !doNotRenderOnCreate) {
-                        lastResult = scene.render(true /*freshRender*/);
-                    }
-                }
-            } finally {
-                scene.release();
-                cleanupThread();
-            }
-
-            return new BridgeRenderSession(scene, lastResult);
-        } catch (Throwable t) {
-            // get the real cause of the exception.
-            Throwable t2 = t;
-            while (t2.getCause() != null) {
-                t2 = t2.getCause();
-            }
-            return new BridgeRenderSession(null,
-                    ERROR_UNKNOWN.createResult(t2.getMessage(), t));
-        }
-    }
-
-    @Override
-    public Result renderDrawable(DrawableParams params) {
-        try {
-            Result lastResult;
-            RenderDrawable action = new RenderDrawable(params);
-            try {
-                prepareThread();
-                lastResult = action.init(params.getTimeout());
-                if (lastResult.isSuccess()) {
-                    lastResult = action.render();
-                }
-            } finally {
-                action.release();
-                cleanupThread();
-            }
-
-            return lastResult;
-        } catch (Throwable t) {
-            // get the real cause of the exception.
-            Throwable t2 = t;
-            while (t2.getCause() != null) {
-                t2 = t.getCause();
-            }
-            return ERROR_UNKNOWN.createResult(t2.getMessage(), t);
-        }
-    }
-
-    @Override
-    public void clearCaches(Object projectKey) {
-        if (projectKey != null) {
-            sProjectBitmapCache.remove(projectKey);
-            sProject9PatchCache.remove(projectKey);
-        }
-    }
-
-    @Override
-    public Result getViewParent(Object viewObject) {
-        if (viewObject instanceof View) {
-            return Status.SUCCESS.createResult(((View)viewObject).getParent());
-        }
-
-        throw new IllegalArgumentException("viewObject is not a View");
-    }
-
-    @Override
-    public Result getViewIndex(Object viewObject) {
-        if (viewObject instanceof View) {
-            View view = (View) viewObject;
-            ViewParent parentView = view.getParent();
-
-            if (parentView instanceof ViewGroup) {
-                Status.SUCCESS.createResult(((ViewGroup) parentView).indexOfChild(view));
-            }
-
-            return Status.SUCCESS.createResult();
-        }
-
-        throw new IllegalArgumentException("viewObject is not a View");
-    }
-
-    @Override
-    public boolean isRtl(String locale) {
-        return isLocaleRtl(locale);
-    }
-
-    public static boolean isLocaleRtl(String locale) {
-        if (locale == null) {
-            locale = "";
-        }
-        ULocale uLocale = new ULocale(locale);
-        return uLocale.getCharacterOrientation().equals(ICU_LOCALE_DIRECTION_RTL);
-    }
-
-    /**
-     * Returns the lock for the bridge
-     */
-    public static ReentrantLock getLock() {
-        return sLock;
-    }
-
-    /**
-     * Prepares the current thread for rendering.
-     *
-     * Note that while this can be called several time, the first call to {@link #cleanupThread()}
-     * will do the clean-up, and make the thread unable to do further scene actions.
-     */
-    public synchronized static void prepareThread() {
-        // we need to make sure the Looper has been initialized for this thread.
-        // this is required for View that creates Handler objects.
-        if (Looper.myLooper() == null) {
-            Looper.prepareMainLooper();
-        }
-    }
-
-    /**
-     * Cleans up thread-specific data. After this, the thread cannot be used for scene actions.
-     * <p>
-     * Note that it doesn't matter how many times {@link #prepareThread()} was called, a single
-     * call to this will prevent the thread from doing further scene actions
-     */
-    public synchronized static void cleanupThread() {
-        // clean up the looper
-        Looper_Accessor.cleanupThread();
-    }
-
-    public static LayoutLog getLog() {
-        return sCurrentLog;
-    }
-
-    public static void setLog(LayoutLog log) {
-        // check only the thread currently owning the lock can do this.
-        if (!sLock.isHeldByCurrentThread()) {
-            throw new IllegalStateException("scene must be acquired first. see #acquire(long)");
-        }
-
-        if (log != null) {
-            sCurrentLog = log;
-        } else {
-            sCurrentLog = sDefaultLog;
-        }
-    }
-
-    /**
-     * Returns details of a framework resource from its integer value.
-     * @param value the integer value
-     * @return a Pair containing the resource type and name, or null if the id
-     *     does not match any resource.
-     */
-    @SuppressWarnings("deprecation")
-    public static Pair<ResourceType, String> resolveResourceId(int value) {
-        Pair<ResourceType, String> pair = sRMap.get(value);
-        if (pair == null) {
-            pair = sDynamicIds.resolveId(value);
-        }
-        return pair;
-    }
-
-    /**
-     * Returns the integer id of a framework resource, from a given resource type and resource name.
-     * <p/>
-     * If no resource is found, it creates a dynamic id for the resource.
-     *
-     * @param type the type of the resource
-     * @param name the name of the resource.
-     *
-     * @return an {@link Integer} containing the resource id.
-     */
-    @NonNull
-    public static Integer getResourceId(ResourceType type, String name) {
-        Map<String, Integer> map = sRevRMap.get(type);
-        Integer value = null;
-        if (map != null) {
-            value = map.get(name);
-        }
-
-        return value == null ? sDynamicIds.getId(type, name) : value;
-
-    }
-
-    /**
-     * Returns the list of possible enums for a given attribute name.
-     */
-    public static Map<String, Integer> getEnumValues(String attributeName) {
-        if (sEnumValueMap != null) {
-            return sEnumValueMap.get(attributeName);
-        }
-
-        return null;
-    }
-
-    /**
-     * Returns the platform build properties.
-     */
-    public static Map<String, String> getPlatformProperties() {
-        return sPlatformProperties;
-    }
-
-    /**
-     * Returns the bitmap for a specific path, from a specific project cache, or from the
-     * framework cache.
-     * @param value the path of the bitmap
-     * @param projectKey the key of the project, or null to query the framework cache.
-     * @return the cached Bitmap or null if not found.
-     */
-    public static Bitmap getCachedBitmap(String value, Object projectKey) {
-        if (projectKey != null) {
-            Map<String, SoftReference<Bitmap>> map = sProjectBitmapCache.get(projectKey);
-            if (map != null) {
-                SoftReference<Bitmap> ref = map.get(value);
-                if (ref != null) {
-                    return ref.get();
-                }
-            }
-        } else {
-            SoftReference<Bitmap> ref = sFrameworkBitmapCache.get(value);
-            if (ref != null) {
-                return ref.get();
-            }
-        }
-
-        return null;
-    }
-
-    /**
-     * Sets a bitmap in a project cache or in the framework cache.
-     * @param value the path of the bitmap
-     * @param bmp the Bitmap object
-     * @param projectKey the key of the project, or null to put the bitmap in the framework cache.
-     */
-    public static void setCachedBitmap(String value, Bitmap bmp, Object projectKey) {
-        if (projectKey != null) {
-            Map<String, SoftReference<Bitmap>> map =
-                    sProjectBitmapCache.computeIfAbsent(projectKey, k -> new HashMap<>());
-
-            map.put(value, new SoftReference<>(bmp));
-        } else {
-            sFrameworkBitmapCache.put(value, new SoftReference<>(bmp));
-        }
-    }
-
-    /**
-     * Returns the 9 patch chunk for a specific path, from a specific project cache, or from the
-     * framework cache.
-     * @param value the path of the 9 patch
-     * @param projectKey the key of the project, or null to query the framework cache.
-     * @return the cached 9 patch or null if not found.
-     */
-    public static NinePatchChunk getCached9Patch(String value, Object projectKey) {
-        if (projectKey != null) {
-            Map<String, SoftReference<NinePatchChunk>> map = sProject9PatchCache.get(projectKey);
-
-            if (map != null) {
-                SoftReference<NinePatchChunk> ref = map.get(value);
-                if (ref != null) {
-                    return ref.get();
-                }
-            }
-        } else {
-            SoftReference<NinePatchChunk> ref = sFramework9PatchCache.get(value);
-            if (ref != null) {
-                return ref.get();
-            }
-        }
-
-        return null;
-    }
-
-    /**
-     * Sets a 9 patch chunk in a project cache or in the framework cache.
-     * @param value the path of the 9 patch
-     * @param ninePatch the 9 patch object
-     * @param projectKey the key of the project, or null to put the bitmap in the framework cache.
-     */
-    public static void setCached9Patch(String value, NinePatchChunk ninePatch, Object projectKey) {
-        if (projectKey != null) {
-            Map<String, SoftReference<NinePatchChunk>> map =
-                    sProject9PatchCache.computeIfAbsent(projectKey, k -> new HashMap<>());
-
-            map.put(value, new SoftReference<>(ninePatch));
-        } else {
-            sFramework9PatchCache.put(value, new SoftReference<>(ninePatch));
-        }
+        return 0;
     }
 }
diff --git a/com/android/layoutlib/bridge/android/BridgeContext.java b/com/android/layoutlib/bridge/android/BridgeContext.java
index 4c6c9d4..4a75be9 100644
--- a/com/android/layoutlib/bridge/android/BridgeContext.java
+++ b/com/android/layoutlib/bridge/android/BridgeContext.java
@@ -40,7 +40,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.Notification;
 import android.app.SystemServiceRegistry_Accessor;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -86,7 +85,6 @@
 import android.view.BridgeInflater;
 import android.view.Display;
 import android.view.DisplayAdjustments;
-import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.WindowManager;
@@ -610,45 +608,35 @@
 
     @Override
     public Object getSystemService(String service) {
-        if (LAYOUT_INFLATER_SERVICE.equals(service)) {
-            return mBridgeInflater;
+        switch (service) {
+            case LAYOUT_INFLATER_SERVICE:
+                return mBridgeInflater;
+
+            case TEXT_SERVICES_MANAGER_SERVICE:
+                // we need to return a valid service to avoid NPE
+                return TextServicesManager.getInstance();
+
+            case WINDOW_SERVICE:
+                return mWindowManager;
+
+            case POWER_SERVICE:
+                return new PowerManager(this, new BridgePowerManager(), new Handler());
+
+            case DISPLAY_SERVICE:
+                return mDisplayManager;
+
+            case ACCESSIBILITY_SERVICE:
+                return AccessibilityManager.getInstance(this);
+
+            case INPUT_METHOD_SERVICE:  // needed by SearchView
+            case AUTOFILL_MANAGER_SERVICE:
+            case AUDIO_SERVICE:
+            case TEXT_CLASSIFICATION_SERVICE:
+                return null;
+            default:
+                assert false : "Unsupported Service: " + service;
         }
 
-        if (TEXT_SERVICES_MANAGER_SERVICE.equals(service)) {
-            // we need to return a valid service to avoid NPE
-            return TextServicesManager.getInstance();
-        }
-
-        if (WINDOW_SERVICE.equals(service)) {
-            return mWindowManager;
-        }
-
-        // needed by SearchView
-        if (INPUT_METHOD_SERVICE.equals(service)) {
-            return null;
-        }
-
-        if (POWER_SERVICE.equals(service)) {
-            return new PowerManager(this, new BridgePowerManager(), new Handler());
-        }
-
-        if (DISPLAY_SERVICE.equals(service)) {
-            return mDisplayManager;
-        }
-
-        if (ACCESSIBILITY_SERVICE.equals(service)) {
-            return AccessibilityManager.getInstance(this);
-        }
-
-        if (AUTOFILL_MANAGER_SERVICE.equals(service)) {
-            return null;
-        }
-
-        if (AUDIO_SERVICE.equals(service)) {
-            return null;
-        }
-
-        assert false : "Unsupported Service: " + service;
         return null;
     }
 
@@ -657,13 +645,13 @@
         return SystemServiceRegistry_Accessor.getSystemServiceName(serviceClass);
     }
 
-    @Override
-    public final BridgeTypedArray obtainStyledAttributes(int[] attrs) {
-        return obtainStyledAttributes(0, attrs);
-    }
 
-    @Override
-    public final BridgeTypedArray obtainStyledAttributes(int resId, int[] attrs)
+    /**
+     * Same as Context#obtainStyledAttributes. We do not override the base method to give the
+     * original Context the chance to override the theme when needed.
+     */
+    @Nullable
+    public final BridgeTypedArray internalObtainStyledAttributes(int resId, int[] attrs)
             throws Resources.NotFoundException {
         StyleResourceValue style = null;
         // get the StyleResourceValue based on the resId;
@@ -715,13 +703,12 @@
         return typeArrayAndPropertiesPair.getFirst();
     }
 
-    @Override
-    public final BridgeTypedArray obtainStyledAttributes(AttributeSet set, int[] attrs) {
-        return obtainStyledAttributes(set, attrs, 0, 0);
-    }
-
-    @Override
-    public BridgeTypedArray obtainStyledAttributes(AttributeSet set, int[] attrs,
+    /**
+     * Same as Context#obtainStyledAttributes. We do not override the base method to give the
+     * original Context the chance to override the theme when needed.
+     */
+    @Nullable
+    public BridgeTypedArray internalObtainStyledAttributes(@Nullable AttributeSet set, int[] attrs,
             int defStyleAttr, int defStyleRes) {
 
         PropertiesMap defaultPropMap = null;
diff --git a/com/android/layoutlib/bridge/bars/AppCompatActionBar.java b/com/android/layoutlib/bridge/bars/AppCompatActionBar.java
index cdcf0ea..bc77685 100644
--- a/com/android/layoutlib/bridge/bars/AppCompatActionBar.java
+++ b/com/android/layoutlib/bridge/bars/AppCompatActionBar.java
@@ -26,6 +26,7 @@
 import com.android.layoutlib.bridge.android.BridgeContext;
 import com.android.layoutlib.bridge.impl.ResourceHelper;
 import com.android.resources.ResourceType;
+import com.android.tools.layoutlib.annotations.NotNull;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -33,11 +34,18 @@
 import android.graphics.drawable.Drawable;
 import android.view.ContextThemeWrapper;
 import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
 import android.view.View;
 import android.widget.FrameLayout;
 
+import java.lang.reflect.Field;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
+import java.util.List;
+
+import static com.android.SdkConstants.ANDROID_NS_NAME_PREFIX;
+import static com.android.resources.ResourceType.MENU;
 
 
 /**
@@ -50,6 +58,7 @@
     private static final String WINDOW_ACTION_BAR_CLASS = "android.support.v7.internal.app.WindowDecorActionBar";
     // This is used on v23.1.1 and later.
     private static final String WINDOW_ACTION_BAR_CLASS_NEW = "android.support.v7.app.WindowDecorActionBar";
+
     private Class<?> mWindowActionBarClass;
 
     /**
@@ -90,6 +99,7 @@
                     constructorParams, constructorArgs);
             mWindowActionBarClass = mWindowDecorActionBar == null ? null :
                     mWindowDecorActionBar.getClass();
+            inflateMenus();
             setupActionBar();
         } catch (Exception e) {
             Bridge.getLog().warning(LayoutLog.TAG_BROKEN,
@@ -165,6 +175,51 @@
         }
     }
 
+    private void inflateMenus() {
+        List<String> menuNames = getCallBack().getMenuIdNames();
+        if (menuNames.isEmpty()) {
+            return;
+        }
+
+        if (menuNames.size() > 1) {
+            // Supporting multiple menus means that we would need to instantiate our own supportlib
+            // MenuInflater instances using reflection
+            Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
+                    "Support Toolbar does not currently support multiple menus in the preview.",
+                    null, null, null);
+        }
+
+        String name = menuNames.get(0);
+        int id;
+        if (name.startsWith(ANDROID_NS_NAME_PREFIX)) {
+            // Framework menu.
+            name = name.substring(ANDROID_NS_NAME_PREFIX.length());
+            id = mBridgeContext.getFrameworkResourceValue(MENU, name, -1);
+        } else {
+            // Project menu.
+            id = mBridgeContext.getProjectResourceValue(MENU, name, -1);
+        }
+        if (id < 1) {
+            return;
+        }
+        // Get toolbar decorator
+        Object mDecorToolbar = getFieldValue(mWindowDecorActionBar, "mDecorToolbar");
+        if (mDecorToolbar == null) {
+            return;
+        }
+
+        Class<?> mDecorToolbarClass = mDecorToolbar.getClass();
+        Context themedContext = (Context)invoke(
+                getMethod(mWindowActionBarClass, "getThemedContext"),
+                mWindowDecorActionBar);
+        MenuInflater inflater = new MenuInflater(themedContext);
+        Menu menuBuilder = (Menu)invoke(getMethod(mDecorToolbarClass, "getMenu"), mDecorToolbar);
+        inflater.inflate(id, menuBuilder);
+
+        // Set the actual menu
+        invoke(findMethod(mDecorToolbarClass, "setMenu"), mDecorToolbar, menuBuilder, null);
+    }
+
     @Override
     public void createMenuPopup() {
         // it's hard to add menus to appcompat's actionbar, since it'll use a lot of reflection.
@@ -181,13 +236,53 @@
         return null;
     }
 
+    /**
+     * Same as getMethod but doesn't require the parameterTypes. This allows us to call methods
+     * without having to get all the types for the parameters when we do not need them
+     */
     @Nullable
-    private static Object invoke(Method method, Object owner, Object... args) {
+    private static Method findMethod(@Nullable Class<?> owner, @NotNull String name) {
+        if (owner == null) {
+            return null;
+        }
+        for (Method method : owner.getMethods()) {
+            if (name.equals(method.getName())) {
+                return method;
+            }
+        }
+
+        return null;
+    }
+
+    @Nullable
+    private static Object getFieldValue(@Nullable Object instance, @NotNull String name) {
+        if (instance == null) {
+            return null;
+        }
+
+        Class<?> instanceClass = instance.getClass();
+        try {
+            Field field = instanceClass.getDeclaredField(name);
+            boolean accesible = field.isAccessible();
+            if (!accesible) {
+                field.setAccessible(true);
+            }
+            try {
+                return field.get(instance);
+            } finally {
+                field.setAccessible(accesible);
+            }
+        } catch (NoSuchFieldException | IllegalAccessException e) {
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    @Nullable
+    private static Object invoke(@Nullable Method method, Object owner, Object... args) {
         try {
             return method == null ? null : method.invoke(owner, args);
-        } catch (InvocationTargetException e) {
-            e.printStackTrace();
-        } catch (IllegalAccessException e) {
+        } catch (InvocationTargetException | IllegalAccessException e) {
             e.printStackTrace();
         }
         return null;
diff --git a/com/android/layoutlib/bridge/impl/GcSnapshot.java b/com/android/layoutlib/bridge/impl/GcSnapshot.java
index 3ad859c..7526e09 100644
--- a/com/android/layoutlib/bridge/impl/GcSnapshot.java
+++ b/com/android/layoutlib/bridge/impl/GcSnapshot.java
@@ -19,7 +19,6 @@
 import com.android.ide.common.rendering.api.LayoutLog;
 import com.android.layoutlib.bridge.Bridge;
 
-import android.annotation.NonNull;
 import android.graphics.Bitmap_Delegate;
 import android.graphics.Canvas;
 import android.graphics.ColorFilter_Delegate;
@@ -40,13 +39,11 @@
 import java.awt.Rectangle;
 import java.awt.RenderingHints;
 import java.awt.Shape;
-import java.awt.Transparency;
 import java.awt.geom.AffineTransform;
 import java.awt.geom.Area;
 import java.awt.geom.NoninvertibleTransformException;
 import java.awt.geom.Rectangle2D;
 import java.awt.image.BufferedImage;
-import java.lang.ref.SoftReference;
 import java.util.ArrayList;
 
 /**
@@ -69,7 +66,7 @@
     private final int mFlags;
 
     /** list of layers. The first item in the list is always the  */
-    private final ArrayList<Layer> mLayers = new ArrayList<>();
+    private final ArrayList<Layer> mLayers = new ArrayList<Layer>();
 
     /** temp transform in case transformation are set before a Graphics2D exists */
     private AffineTransform mTransform = null;
@@ -85,13 +82,6 @@
     private final Layer mLocalLayer;
     private final Paint_Delegate mLocalLayerPaint;
     private final Rect mLayerBounds;
-    /**
-     * Cached buffer to be used for tinting operations. This buffer is usually used many times
-     * and there is no need to creating it every time.
-     */
-    private SoftReference<BufferedImage> mCachedLayerBuffer = new SoftReference<>(null);
-    private Rectangle2D.Float mCachedClipRect = new Rectangle2D.Float();
-
 
     public interface Drawable {
         void draw(Graphics2D graphics, Paint_Delegate paint);
@@ -310,11 +300,12 @@
             Layer baseLayer = mLayers.get(0);
 
             // create the image for the layer
-            BufferedImage layerImage =
-                    baseLayer.getGraphics().getDeviceConfiguration().createCompatibleImage(
-                            baseLayer.getImage().getWidth(), baseLayer.getImage().getHeight(),
-                            (mFlags & Canvas.HAS_ALPHA_LAYER_SAVE_FLAG) != 0 ?
-                                    Transparency.TRANSLUCENT : Transparency.OPAQUE);
+            BufferedImage layerImage = new BufferedImage(
+                    baseLayer.getImage().getWidth(),
+                    baseLayer.getImage().getHeight(),
+                    (mFlags & Canvas.HAS_ALPHA_LAYER_SAVE_FLAG) != 0 ?
+                            BufferedImage.TYPE_INT_ARGB :
+                                BufferedImage.TYPE_INT_RGB);
 
             // create a graphics for it so that drawing can be done.
             Graphics2D layerGraphics = layerImage.createGraphics();
@@ -361,8 +352,6 @@
     }
 
     public void dispose() {
-        mCachedLayerBuffer.clear();
-
         for (Layer layer : mLayers) {
             layer.getGraphics().dispose();
         }
@@ -538,12 +527,7 @@
     }
 
     public boolean clipRect(float left, float top, float right, float bottom, int regionOp) {
-        if (mCachedClipRect == null) {
-            mCachedClipRect = new Rectangle2D.Float(left, top, right - left, bottom - top);
-        } else {
-            mCachedClipRect.setRect(left, top, right - left, bottom - top);
-        }
-        return clip(mCachedClipRect, regionOp);
+        return clip(new Rectangle2D.Float(left, top, right - left, bottom - top), regionOp);
     }
 
     /**
@@ -656,16 +640,17 @@
                 height = layer.getImage().getHeight();
             }
 
+            // Create a temporary image to which the color filter will be applied.
+            BufferedImage image = new BufferedImage(width, height,
+                    BufferedImage.TYPE_INT_ARGB);
+            Graphics2D imageBaseGraphics = (Graphics2D) image.getGraphics();
+            // Configure the Graphics2D object with drawing parameters and shader.
+            Graphics2D imageGraphics = createCustomGraphics(
+                    imageBaseGraphics, paint, compositeOnly,
+                    AlphaComposite.SRC_OVER);
             // get a Graphics2D object configured with the drawing parameters, but no shader.
             Graphics2D configuredGraphics = createCustomGraphics(originalGraphics, paint,
                     true /*compositeOnly*/, forceMode);
-
-            // Create or re-use a temporary image to which the color filter will be applied.
-            BufferedImage image = getTemporaryBuffer(configuredGraphics, width, height);
-            Graphics2D imageBaseGraphics = (Graphics2D) image.getGraphics();
-            // Configure the Graphics2D object with drawing parameters and shader.
-            Graphics2D imageGraphics = createCustomGraphics(imageBaseGraphics, paint, compositeOnly,
-                    AlphaComposite.SRC_OVER);
             try {
                 // The main draw operation.
                 // We translate the operation to take into account that the rendering does not
@@ -692,28 +677,6 @@
         }
     }
 
-    /**
-     * Returns a temporary buffer sized width * height and configured with the given
-     * {@link Graphics2D} device configuration.
-     */
-    @NonNull
-    private BufferedImage getTemporaryBuffer(@NonNull Graphics2D configuredGraphics,
-            int width, int height) {
-        BufferedImage cachedImage = mCachedLayerBuffer.get();
-        if (cachedImage == null ||
-                width > cachedImage.getWidth() || height > cachedImage.getHeight() ||
-                !configuredGraphics.getDeviceConfiguration().getColorModel()
-                        .isCompatibleSampleModel(cachedImage.getSampleModel())) {
-            // The current cached image is not valid or does not exist
-            cachedImage = configuredGraphics.getDeviceConfiguration()
-                    .createCompatibleImage(width, height);
-            mCachedLayerBuffer = new SoftReference<>(cachedImage);
-        } else {
-            cachedImage = cachedImage.getSubimage(0, 0, width, height);
-        }
-        return cachedImage;
-    }
-
     private void drawOnGraphics(Graphics2D g, Drawable drawable, Paint_Delegate paint,
             Layer layer) {
         try {
diff --git a/com/android/providers/settings/SettingsProtoDumpUtil.java b/com/android/providers/settings/SettingsProtoDumpUtil.java
index ec6f831..67fb4d9 100644
--- a/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -720,9 +720,6 @@
                 Settings.Global.DEVICE_IDLE_CONSTANTS,
                 GlobalSettingsProto.DEVICE_IDLE_CONSTANTS);
         dumpSetting(s, p,
-                Settings.Global.DEVICE_IDLE_CONSTANTS_WATCH,
-                GlobalSettingsProto.DEVICE_IDLE_CONSTANTS_WATCH);
-        dumpSetting(s, p,
                 Settings.Global.APP_IDLE_CONSTANTS,
                 GlobalSettingsProto.APP_IDLE_CONSTANTS);
         dumpSetting(s, p,
diff --git a/com/android/providers/settings/SettingsProvider.java b/com/android/providers/settings/SettingsProvider.java
index a463db6..36f9b84 100644
--- a/com/android/providers/settings/SettingsProvider.java
+++ b/com/android/providers/settings/SettingsProvider.java
@@ -2896,7 +2896,7 @@
         }
 
         private final class UpgradeController {
-            private static final int SETTINGS_VERSION = 149;
+            private static final int SETTINGS_VERSION = 150;
 
             private final int mUserId;
 
@@ -3470,9 +3470,25 @@
                                     true, SettingsState.SYSTEM_PACKAGE_NAME);
                         }
                     }
-
                     currentVersion = 149;
                 }
+
+                if (currentVersion == 149) {
+                    // Version 150: Set a default value for mobile data always on
+                    final SettingsState globalSettings = getGlobalSettingsLocked();
+                    final Setting currentSetting = globalSettings.getSettingLocked(
+                            Settings.Global.MOBILE_DATA_ALWAYS_ON);
+                    if (currentSetting.isNull()) {
+                        globalSettings.insertSettingLocked(
+                                Settings.Global.MOBILE_DATA_ALWAYS_ON,
+                                getContext().getResources().getBoolean(
+                                        R.bool.def_mobile_data_always_on) ? "1" : "0",
+                                null, true, SettingsState.SYSTEM_PACKAGE_NAME);
+                    }
+
+                    currentVersion = 150;
+                }
+
                 // vXXX: Add new settings above this point.
 
                 if (currentVersion != newVersion) {
diff --git a/com/android/server/BatteryService.java b/com/android/server/BatteryService.java
index 83bd9eb..5106c8d 100644
--- a/com/android/server/BatteryService.java
+++ b/com/android/server/BatteryService.java
@@ -20,6 +20,7 @@
 import android.database.ContentObserver;
 import android.os.BatteryStats;
 
+import android.os.PowerManager;
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
 import android.os.ShellCommand;
@@ -291,6 +292,8 @@
                     if (mActivityManagerInternal.isSystemReady()) {
                         Intent intent = new Intent(Intent.ACTION_REQUEST_SHUTDOWN);
                         intent.putExtra(Intent.EXTRA_KEY_CONFIRM, false);
+                        intent.putExtra(Intent.EXTRA_REASON,
+                                PowerManager.SHUTDOWN_LOW_BATTERY);
                         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                         mContext.startActivityAsUser(intent, UserHandle.CURRENT);
                     }
@@ -310,6 +313,8 @@
                     if (mActivityManagerInternal.isSystemReady()) {
                         Intent intent = new Intent(Intent.ACTION_REQUEST_SHUTDOWN);
                         intent.putExtra(Intent.EXTRA_KEY_CONFIRM, false);
+                        intent.putExtra(Intent.EXTRA_REASON,
+                                PowerManager.SHUTDOWN_BATTERY_THERMAL_STATE);
                         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                         mContext.startActivityAsUser(intent, UserHandle.CURRENT);
                     }
diff --git a/com/android/server/ConnectivityService.java b/com/android/server/ConnectivityService.java
index bfe5040..348c799 100644
--- a/com/android/server/ConnectivityService.java
+++ b/com/android/server/ConnectivityService.java
@@ -19,6 +19,7 @@
 import static android.Manifest.permission.RECEIVE_DATA_ACTIVITY_CHANGE;
 import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
 import static android.net.ConnectivityManager.NETID_UNSET;
+import static android.net.ConnectivityManager.TYPE_ETHERNET;
 import static android.net.ConnectivityManager.TYPE_NONE;
 import static android.net.ConnectivityManager.TYPE_VPN;
 import static android.net.ConnectivityManager.getNetworkTypeName;
@@ -90,6 +91,7 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
+import android.os.ServiceManager;
 import android.os.ServiceSpecificException;
 import android.os.SystemClock;
 import android.os.UserHandle;
@@ -128,7 +130,6 @@
 import com.android.server.connectivity.KeepaliveTracker;
 import com.android.server.connectivity.LingerMonitor;
 import com.android.server.connectivity.MockableSystemProperties;
-import com.android.server.connectivity.Nat464Xlat;
 import com.android.server.connectivity.NetworkAgentInfo;
 import com.android.server.connectivity.NetworkDiagnostics;
 import com.android.server.connectivity.NetworkMonitor;
@@ -781,6 +782,13 @@
             mNetworksDefined++;  // used only in the log() statement below.
         }
 
+        // Do the same for Ethernet, since it's often not specified in the configs, although many
+        // devices can use it via USB host adapters.
+        if (mNetConfigs[TYPE_ETHERNET] == null && hasService(Context.ETHERNET_SERVICE)) {
+            mLegacyTypeTracker.addSupportedType(TYPE_ETHERNET);
+            mNetworksDefined++;
+        }
+
         if (VDBG) log("mNetworksDefined=" + mNetworksDefined);
 
         mProtectedNetworks = new ArrayList<Integer>();
@@ -2205,7 +2213,7 @@
                 // A network factory has connected.  Send it all current NetworkRequests.
                 for (NetworkRequestInfo nri : mNetworkRequests.values()) {
                     if (nri.request.isListen()) continue;
-                    NetworkAgentInfo nai = mNetworkForRequestId.get(nri.request.requestId);
+                    NetworkAgentInfo nai = getNetworkForRequest(nri.request.requestId);
                     ac.sendMessage(android.net.NetworkFactory.CMD_REQUEST_NETWORK,
                             (nai != null ? nai.getCurrentScore() : 0), 0, nri.request);
                 }
@@ -2282,9 +2290,9 @@
             // Remove all previously satisfied requests.
             for (int i = 0; i < nai.numNetworkRequests(); i++) {
                 NetworkRequest request = nai.requestAt(i);
-                NetworkAgentInfo currentNetwork = mNetworkForRequestId.get(request.requestId);
+                NetworkAgentInfo currentNetwork = getNetworkForRequest(request.requestId);
                 if (currentNetwork != null && currentNetwork.network.netId == nai.network.netId) {
-                    mNetworkForRequestId.remove(request.requestId);
+                    clearNetworkForRequest(request.requestId);
                     sendUpdatedScoreToFactories(request, 0);
                 }
             }
@@ -2360,7 +2368,7 @@
             }
         }
         rematchAllNetworksAndRequests(null, 0);
-        if (nri.request.isRequest() && mNetworkForRequestId.get(nri.request.requestId) == null) {
+        if (nri.request.isRequest() && getNetworkForRequest(nri.request.requestId) == null) {
             sendUpdatedScoreToFactories(nri.request, 0);
         }
     }
@@ -2415,7 +2423,7 @@
                     // 2. Unvalidated WiFi will not be reaped when validated cellular
                     //    is currently satisfying the request.  This is desirable when
                     //    WiFi ends up validating and out scoring cellular.
-                    mNetworkForRequestId.get(nri.request.requestId).getCurrentScore() <
+                    getNetworkForRequest(nri.request.requestId).getCurrentScore() <
                             nai.getCurrentScoreAsValidated())) {
                 return false;
             }
@@ -2442,7 +2450,7 @@
         if (mNetworkRequests.get(nri.request) == null) {
             return;
         }
-        if (mNetworkForRequestId.get(nri.request.requestId) != null) {
+        if (getNetworkForRequest(nri.request.requestId) != null) {
             return;
         }
         if (VDBG || (DBG && nri.request.isRequest())) {
@@ -2482,7 +2490,7 @@
         mNetworkRequestInfoLogs.log("RELEASE " + nri);
         if (nri.request.isRequest()) {
             boolean wasKept = false;
-            NetworkAgentInfo nai = mNetworkForRequestId.get(nri.request.requestId);
+            NetworkAgentInfo nai = getNetworkForRequest(nri.request.requestId);
             if (nai != null) {
                 boolean wasBackgroundNetwork = nai.isBackgroundNetwork();
                 nai.removeRequest(nri.request.requestId);
@@ -2499,7 +2507,7 @@
                 } else {
                     wasKept = true;
                 }
-                mNetworkForRequestId.remove(nri.request.requestId);
+                clearNetworkForRequest(nri.request.requestId);
                 if (!wasBackgroundNetwork && nai.isBackgroundNetwork()) {
                     // Went from foreground to background.
                     updateCapabilities(nai.getCurrentScore(), nai, nai.networkCapabilities);
@@ -4296,7 +4304,8 @@
      * and the are the highest scored network available.
      * the are keyed off the Requests requestId.
      */
-    // TODO: Yikes, this is accessed on multiple threads: add synchronization.
+    // NOTE: Accessed on multiple threads, must be synchronized on itself.
+    @GuardedBy("mNetworkForRequestId")
     private final SparseArray<NetworkAgentInfo> mNetworkForRequestId =
             new SparseArray<NetworkAgentInfo>();
 
@@ -4326,8 +4335,26 @@
     // priority networks like Wi-Fi are active.
     private final NetworkRequest mDefaultMobileDataRequest;
 
+    private NetworkAgentInfo getNetworkForRequest(int requestId) {
+        synchronized (mNetworkForRequestId) {
+            return mNetworkForRequestId.get(requestId);
+        }
+    }
+
+    private void clearNetworkForRequest(int requestId) {
+        synchronized (mNetworkForRequestId) {
+            mNetworkForRequestId.remove(requestId);
+        }
+    }
+
+    private void setNetworkForRequest(int requestId, NetworkAgentInfo nai) {
+        synchronized (mNetworkForRequestId) {
+            mNetworkForRequestId.put(requestId, nai);
+        }
+    }
+
     private NetworkAgentInfo getDefaultNetwork() {
-        return mNetworkForRequestId.get(mDefaultRequest.requestId);
+        return getNetworkForRequest(mDefaultRequest.requestId);
     }
 
     private boolean isDefaultNetwork(NetworkAgentInfo nai) {
@@ -4881,7 +4908,7 @@
             // requests or not, and doesn't affect the network's score.
             if (nri.request.isListen()) continue;
 
-            final NetworkAgentInfo currentNetwork = mNetworkForRequestId.get(nri.request.requestId);
+            final NetworkAgentInfo currentNetwork = getNetworkForRequest(nri.request.requestId);
             final boolean satisfies = newNetwork.satisfies(nri.request);
             if (newNetwork == currentNetwork && satisfies) {
                 if (VDBG) {
@@ -4913,7 +4940,7 @@
                         if (VDBG) log("   accepting network in place of null");
                     }
                     newNetwork.unlingerRequest(nri.request);
-                    mNetworkForRequestId.put(nri.request.requestId, newNetwork);
+                    setNetworkForRequest(nri.request.requestId, newNetwork);
                     if (!newNetwork.addRequest(nri.request)) {
                         Slog.wtf(TAG, "BUG: " + newNetwork.name() + " already has " + nri.request);
                     }
@@ -4947,7 +4974,7 @@
                 }
                 newNetwork.removeRequest(nri.request.requestId);
                 if (currentNetwork == newNetwork) {
-                    mNetworkForRequestId.remove(nri.request.requestId);
+                    clearNetworkForRequest(nri.request.requestId);
                     sendUpdatedScoreToFactories(nri.request, 0);
                 } else {
                     Slog.wtf(TAG, "BUG: Removing request " + nri.request.requestId + " from " +
@@ -5522,6 +5549,11 @@
         return new WakeupMessage(c, h, s, cmd, 0, 0, obj);
     }
 
+    @VisibleForTesting
+    public boolean hasService(String name) {
+        return ServiceManager.checkService(name) != null;
+    }
+
     private void logDefaultNetworkEvent(NetworkAgentInfo newNai, NetworkAgentInfo prevNai) {
         int newNetid = NETID_UNSET;
         int prevNetid = NETID_UNSET;
diff --git a/com/android/server/DeviceIdleController.java b/com/android/server/DeviceIdleController.java
index abbc89e..2d9baf6 100644
--- a/com/android/server/DeviceIdleController.java
+++ b/com/android/server/DeviceIdleController.java
@@ -307,6 +307,12 @@
      */
     private int[] mTempWhitelistAppIdArray = new int[0];
 
+    /**
+     * Apps in the system whitelist that have been taken out (probably because the user wanted to).
+     * They can be restored back by calling restoreAppToSystemWhitelist(String).
+     */
+    private ArrayMap<String, Integer> mRemovedFromSystemWhitelistApps = new ArrayMap<>();
+
     private static final int EVENT_NULL = 0;
     private static final int EVENT_NORMAL = 1;
     private static final int EVENT_LIGHT_IDLE = 2;
@@ -760,17 +766,15 @@
         public long NOTIFICATION_WHITELIST_DURATION;
 
         private final ContentResolver mResolver;
-        private final boolean mHasWatch;
+        private final boolean mSmallBatteryDevice;
         private final KeyValueListParser mParser = new KeyValueListParser(',');
 
         public Constants(Handler handler, ContentResolver resolver) {
             super(handler);
             mResolver = resolver;
-            mHasWatch = getContext().getPackageManager().hasSystemFeature(
-                    PackageManager.FEATURE_WATCH);
-            mResolver.registerContentObserver(Settings.Global.getUriFor(
-                    mHasWatch ? Settings.Global.DEVICE_IDLE_CONSTANTS_WATCH
-                              : Settings.Global.DEVICE_IDLE_CONSTANTS),
+            mSmallBatteryDevice = ActivityManager.isSmallBatteryDevice();
+            mResolver.registerContentObserver(
+                    Settings.Global.getUriFor(Settings.Global.DEVICE_IDLE_CONSTANTS),
                     false, this);
             updateConstants();
         }
@@ -784,8 +788,7 @@
             synchronized (DeviceIdleController.this) {
                 try {
                     mParser.setString(Settings.Global.getString(mResolver,
-                            mHasWatch ? Settings.Global.DEVICE_IDLE_CONSTANTS_WATCH
-                                      : Settings.Global.DEVICE_IDLE_CONSTANTS));
+                            Settings.Global.DEVICE_IDLE_CONSTANTS));
                 } catch (IllegalArgumentException e) {
                     // Failed to parse the settings string, log this and move on
                     // with defaults.
@@ -815,7 +818,7 @@
                 MIN_DEEP_MAINTENANCE_TIME = mParser.getLong(
                         KEY_MIN_DEEP_MAINTENANCE_TIME,
                         !COMPRESS_TIME ? 30 * 1000L : 5 * 1000L);
-                long inactiveTimeoutDefault = (mHasWatch ? 15 : 30) * 60 * 1000L;
+                long inactiveTimeoutDefault = (mSmallBatteryDevice ? 15 : 30) * 60 * 1000L;
                 INACTIVE_TIMEOUT = mParser.getLong(KEY_INACTIVE_TIMEOUT,
                         !COMPRESS_TIME ? inactiveTimeoutDefault : (inactiveTimeoutDefault / 10));
                 SENSING_TIMEOUT = mParser.getLong(KEY_SENSING_TIMEOUT,
@@ -825,7 +828,7 @@
                 LOCATION_ACCURACY = mParser.getFloat(KEY_LOCATION_ACCURACY, 20);
                 MOTION_INACTIVE_TIMEOUT = mParser.getLong(KEY_MOTION_INACTIVE_TIMEOUT,
                         !COMPRESS_TIME ? 10 * 60 * 1000L : 60 * 1000L);
-                long idleAfterInactiveTimeout = (mHasWatch ? 15 : 30) * 60 * 1000L;
+                long idleAfterInactiveTimeout = (mSmallBatteryDevice ? 15 : 30) * 60 * 1000L;
                 IDLE_AFTER_INACTIVE_TIMEOUT = mParser.getLong(KEY_IDLE_AFTER_INACTIVE_TIMEOUT,
                         !COMPRESS_TIME ? idleAfterInactiveTimeout
                                        : (idleAfterInactiveTimeout / 10));
@@ -1162,6 +1165,38 @@
             }
         }
 
+        @Override public void removeSystemPowerWhitelistApp(String name) {
+            if (DEBUG) {
+                Slog.d(TAG, "removeAppFromSystemWhitelist(name = " + name + ")");
+            }
+            getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER,
+                    null);
+            long ident = Binder.clearCallingIdentity();
+            try {
+                removeSystemPowerWhitelistAppInternal(name);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+
+        @Override public void restoreSystemPowerWhitelistApp(String name) {
+            if (DEBUG) {
+                Slog.d(TAG, "restoreAppToSystemWhitelist(name = " + name + ")");
+            }
+            getContext().enforceCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER,
+                    null);
+            long ident = Binder.clearCallingIdentity();
+            try {
+                restoreSystemPowerWhitelistAppInternal(name);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+
+        public String[] getRemovedSystemPowerWhitelistApps() {
+            return getRemovedSystemPowerWhitelistAppsInternal();
+        }
+
         @Override public String[] getSystemPowerWhitelistExceptIdle() {
             return getSystemPowerWhitelistExceptIdleInternal();
         }
@@ -1504,6 +1539,42 @@
         }
     }
 
+    void resetSystemPowerWhitelistInternal() {
+        synchronized (this) {
+            mPowerSaveWhitelistApps.putAll(mRemovedFromSystemWhitelistApps);
+            mRemovedFromSystemWhitelistApps.clear();
+            reportPowerSaveWhitelistChangedLocked();
+            updateWhitelistAppIdsLocked();
+            writeConfigFileLocked();
+        }
+    }
+
+    public boolean restoreSystemPowerWhitelistAppInternal(String name) {
+        synchronized (this) {
+            if (!mRemovedFromSystemWhitelistApps.containsKey(name)) {
+                return false;
+            }
+            mPowerSaveWhitelistApps.put(name, mRemovedFromSystemWhitelistApps.remove(name));
+            reportPowerSaveWhitelistChangedLocked();
+            updateWhitelistAppIdsLocked();
+            writeConfigFileLocked();
+            return true;
+        }
+    }
+
+    public boolean removeSystemPowerWhitelistAppInternal(String name) {
+        synchronized (this) {
+            if (!mPowerSaveWhitelistApps.containsKey(name)) {
+                return false;
+            }
+            mRemovedFromSystemWhitelistApps.put(name, mPowerSaveWhitelistApps.remove(name));
+            reportPowerSaveWhitelistChangedLocked();
+            updateWhitelistAppIdsLocked();
+            writeConfigFileLocked();
+            return true;
+        }
+    }
+
     public boolean addPowerSaveWhitelistExceptIdleInternal(String name) {
         synchronized (this) {
             try {
@@ -1565,6 +1636,17 @@
         }
     }
 
+    public String[] getRemovedSystemPowerWhitelistAppsInternal() {
+        synchronized (this) {
+            int size = mRemovedFromSystemWhitelistApps.size();
+            final String[] apps = new String[size];
+            for (int i = 0; i < size; i++) {
+                apps[i] = mRemovedFromSystemWhitelistApps.keyAt(i);
+            }
+            return apps;
+        }
+    }
+
     public String[] getUserPowerWhitelistInternal() {
         synchronized (this) {
             int size = mPowerSaveWhitelistUserApps.size();
@@ -2481,21 +2563,31 @@
                 }
 
                 String tagName = parser.getName();
-                if (tagName.equals("wl")) {
-                    String name = parser.getAttributeValue(null, "n");
-                    if (name != null) {
-                        try {
-                            ApplicationInfo ai = pm.getApplicationInfo(name,
-                                    PackageManager.MATCH_ANY_USER);
-                            mPowerSaveWhitelistUserApps.put(ai.packageName,
-                                    UserHandle.getAppId(ai.uid));
-                        } catch (PackageManager.NameNotFoundException e) {
+                switch (tagName) {
+                    case "wl":
+                        String name = parser.getAttributeValue(null, "n");
+                        if (name != null) {
+                            try {
+                                ApplicationInfo ai = pm.getApplicationInfo(name,
+                                        PackageManager.MATCH_ANY_USER);
+                                mPowerSaveWhitelistUserApps.put(ai.packageName,
+                                        UserHandle.getAppId(ai.uid));
+                            } catch (PackageManager.NameNotFoundException e) {
+                            }
                         }
-                    }
-                } else {
-                    Slog.w(TAG, "Unknown element under <config>: "
-                            + parser.getName());
-                    XmlUtils.skipCurrentTag(parser);
+                        break;
+                    case "un-wl":
+                        final String packageName = parser.getAttributeValue(null, "n");
+                        if (mPowerSaveWhitelistApps.containsKey(packageName)) {
+                            mRemovedFromSystemWhitelistApps.put(packageName,
+                                    mPowerSaveWhitelistApps.remove(packageName));
+                        }
+                        break;
+                    default:
+                        Slog.w(TAG, "Unknown element under <config>: "
+                                + parser.getName());
+                        XmlUtils.skipCurrentTag(parser);
+                        break;
                 }
             }
 
@@ -2556,6 +2648,11 @@
             out.attribute(null, "n", name);
             out.endTag(null, "wl");
         }
+        for (int i = 0; i < mRemovedFromSystemWhitelistApps.size(); i++) {
+            out.startTag(null, "un-wl");
+            out.attribute(null, "n", mRemovedFromSystemWhitelistApps.keyAt(i));
+            out.endTag(null, "un-wl");
+        }
         out.endTag(null, "config");
         out.endDocument();
     }
@@ -2584,6 +2681,13 @@
         pw.println("    Print currently whitelisted apps.");
         pw.println("  whitelist [package ...]");
         pw.println("    Add (prefix with +) or remove (prefix with -) packages.");
+        pw.println("  sys-whitelist [package ...|reset]");
+        pw.println("    Prefix the package with '-' to remove it from the system whitelist or '+'"
+                + " to put it back in the system whitelist.");
+        pw.println("    Note that only packages that were"
+                + " earlier removed from the system whitelist can be added back.");
+        pw.println("    reset will reset the whitelist to the original state");
+        pw.println("    Prints the system whitelist if no arguments are specified");
         pw.println("  except-idle-whitelist [package ...|reset]");
         pw.println("    Prefix the package with '+' to add it to whitelist or "
                 + "'=' to check if it is already whitelisted");
@@ -2944,6 +3048,50 @@
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
+        } else if ("sys-whitelist".equals(cmd)) {
+            String arg = shell.getNextArg();
+            if (arg != null) {
+                getContext().enforceCallingOrSelfPermission(
+                        android.Manifest.permission.DEVICE_POWER, null);
+                final long token = Binder.clearCallingIdentity();
+                try {
+                    if ("reset".equals(arg)) {
+                        resetSystemPowerWhitelistInternal();
+                    } else {
+                        do {
+                            if (arg.length() < 1
+                                    || (arg.charAt(0) != '-' && arg.charAt(0) != '+')) {
+                                pw.println("Package must be prefixed with + or - " + arg);
+                                return -1;
+                            }
+                            final char op = arg.charAt(0);
+                            final String pkg = arg.substring(1);
+                            switch (op) {
+                                case '+':
+                                    if (restoreSystemPowerWhitelistAppInternal(pkg)) {
+                                        pw.println("Restored " + pkg);
+                                    }
+                                    break;
+                                case '-':
+                                    if (removeSystemPowerWhitelistAppInternal(pkg)) {
+                                        pw.println("Removed " + pkg);
+                                    }
+                                    break;
+                            }
+                        } while ((arg = shell.getNextArg()) != null);
+                    }
+                } finally {
+                    Binder.restoreCallingIdentity(token);
+                }
+            } else {
+                synchronized (this) {
+                    for (int j=0; j<mPowerSaveWhitelistApps.size(); j++) {
+                        pw.print(mPowerSaveWhitelistApps.keyAt(j));
+                        pw.print(",");
+                        pw.println(mPowerSaveWhitelistApps.valueAt(j));
+                    }
+                }
+            }
         } else {
             return shell.handleDefaultCommands(cmd);
         }
@@ -3027,6 +3175,14 @@
                     pw.println(mPowerSaveWhitelistApps.keyAt(i));
                 }
             }
+            size = mRemovedFromSystemWhitelistApps.size();
+            if (size > 0) {
+                pw.println("  Removed from whitelist system apps:");
+                for (int i = 0; i < size; i++) {
+                    pw.print("    ");
+                    pw.println(mRemovedFromSystemWhitelistApps.keyAt(i));
+                }
+            }
             size = mPowerSaveWhitelistUserApps.size();
             if (size > 0) {
                 pw.println("  Whitelist user apps:");
diff --git a/com/android/server/DiskStatsService.java b/com/android/server/DiskStatsService.java
index 800081e..2d2c6b0 100644
--- a/com/android/server/DiskStatsService.java
+++ b/com/android/server/DiskStatsService.java
@@ -202,6 +202,8 @@
             JSONObject json = new JSONObject(jsonString);
             pw.print("App Size: ");
             pw.println(json.getLong(DiskStatsFileLogger.APP_SIZE_AGG_KEY));
+            pw.print("App Data Size: ");
+            pw.println(json.getLong(DiskStatsFileLogger.APP_DATA_SIZE_AGG_KEY));
             pw.print("App Cache Size: ");
             pw.println(json.getLong(DiskStatsFileLogger.APP_CACHE_AGG_KEY));
             pw.print("Photos Size: ");
@@ -220,6 +222,8 @@
             pw.println(json.getJSONArray(DiskStatsFileLogger.PACKAGE_NAMES_KEY));
             pw.print("App Sizes: ");
             pw.println(json.getJSONArray(DiskStatsFileLogger.APP_SIZES_KEY));
+            pw.print("App Data Sizes: ");
+            pw.println(json.getJSONArray(DiskStatsFileLogger.APP_DATA_KEY));
             pw.print("Cache Sizes: ");
             pw.println(json.getJSONArray(DiskStatsFileLogger.APP_CACHES_KEY));
         } catch (IOException | JSONException e) {
@@ -235,6 +239,8 @@
 
             proto.write(DiskStatsCachedValuesProto.AGG_APPS_SIZE,
                     json.getLong(DiskStatsFileLogger.APP_SIZE_AGG_KEY));
+            proto.write(DiskStatsCachedValuesProto.AGG_APPS_DATA_SIZE,
+                    json.getLong(DiskStatsFileLogger.APP_DATA_SIZE_AGG_KEY));
             proto.write(DiskStatsCachedValuesProto.AGG_APPS_CACHE_SIZE,
                     json.getLong(DiskStatsFileLogger.APP_CACHE_AGG_KEY));
             proto.write(DiskStatsCachedValuesProto.PHOTOS_SIZE,
@@ -252,22 +258,26 @@
 
             JSONArray packageNamesArray = json.getJSONArray(DiskStatsFileLogger.PACKAGE_NAMES_KEY);
             JSONArray appSizesArray = json.getJSONArray(DiskStatsFileLogger.APP_SIZES_KEY);
+            JSONArray appDataSizesArray = json.getJSONArray(DiskStatsFileLogger.APP_DATA_KEY);
             JSONArray cacheSizesArray = json.getJSONArray(DiskStatsFileLogger.APP_CACHES_KEY);
             final int len = packageNamesArray.length();
-            if (len == appSizesArray.length() && len == cacheSizesArray.length()) {
+            if (len == appSizesArray.length()
+                    && len == appDataSizesArray.length()
+                    && len == cacheSizesArray.length()) {
                 for (int i = 0; i < len; i++) {
                     long packageToken = proto.start(DiskStatsCachedValuesProto.APP_SIZES);
 
                     proto.write(DiskStatsAppSizesProto.PACKAGE_NAME,
                             packageNamesArray.getString(i));
                     proto.write(DiskStatsAppSizesProto.APP_SIZE, appSizesArray.getLong(i));
+                    proto.write(DiskStatsAppSizesProto.APP_DATA_SIZE, appDataSizesArray.getLong(i));
                     proto.write(DiskStatsAppSizesProto.CACHE_SIZE, cacheSizesArray.getLong(i));
 
                     proto.end(packageToken);
                 }
             } else {
-                Slog.wtf(TAG, "Sizes of packageNamesArray, appSizesArray and cacheSizesArray "
-                        + "are not the same");
+                Slog.wtf(TAG, "Sizes of packageNamesArray, appSizesArray, appDataSizesArray "
+                        + " and cacheSizesArray are not the same");
             }
 
             proto.end(cachedValuesToken);
diff --git a/com/android/server/IpSecService.java b/com/android/server/IpSecService.java
index 3056831..2e1f142 100644
--- a/com/android/server/IpSecService.java
+++ b/com/android/server/IpSecService.java
@@ -33,6 +33,7 @@
 import android.net.IpSecTransform;
 import android.net.IpSecTransformResponse;
 import android.net.IpSecUdpEncapResponse;
+import android.net.NetworkUtils;
 import android.net.util.NetdService;
 import android.os.Binder;
 import android.os.IBinder;
@@ -42,11 +43,14 @@
 import android.system.ErrnoException;
 import android.system.Os;
 import android.system.OsConstants;
+import android.text.TextUtils;
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
+
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.PrintWriter;
@@ -54,6 +58,7 @@
 import java.net.InetSocketAddress;
 import java.net.UnknownHostException;
 import java.util.concurrent.atomic.AtomicInteger;
+
 import libcore.io.IoUtils;
 
 /** @hide */
@@ -252,7 +257,11 @@
             return (mReferenceCount.get() > 0);
         }
 
-        public void checkOwnerOrSystemAndThrow() {
+        /**
+         * Ensures that the caller is either the owner of this resource or has the system UID and
+         * throws a SecurityException otherwise.
+         */
+        public void checkOwnerOrSystem() {
             if (uid != Binder.getCallingUid()
                     && android.os.Process.SYSTEM_UID != Binder.getCallingUid()) {
                 throw new SecurityException("Only the owner may access managed resources!");
@@ -335,12 +344,12 @@
     private class ManagedResourceArray<T extends ManagedResource> {
         SparseArray<T> mArray = new SparseArray<>();
 
-        T get(int key) {
+        T getAndCheckOwner(int key) {
             T val = mArray.get(key);
             // The value should never be null unless the resource doesn't exist
             // (since we do not allow null resources to be added).
             if (val != null) {
-                val.checkOwnerOrSystemAndThrow();
+                val.checkOwnerOrSystem();
             }
             return val;
         }
@@ -405,12 +414,8 @@
                             .ipSecDeleteSecurityAssociation(
                                     mResourceId,
                                     direction,
-                                    (mConfig.getLocalAddress() != null)
-                                            ? mConfig.getLocalAddress().getHostAddress()
-                                            : "",
-                                    (mConfig.getRemoteAddress() != null)
-                                            ? mConfig.getRemoteAddress().getHostAddress()
-                                            : "",
+                                    mConfig.getLocalAddress(),
+                                    mConfig.getRemoteAddress(),
                                     spi);
                 } catch (ServiceSpecificException e) {
                     // FIXME: get the error code and throw is at an IOException from Errno Exception
@@ -638,11 +643,45 @@
         }
     }
 
+    /**
+     * Checks that the provided InetAddress is valid for use in an IPsec SA. The address must not be
+     * a wildcard address and must be in a numeric form such as 1.2.3.4 or 2001::1.
+     */
+    private static void checkInetAddress(String inetAddress) {
+        if (TextUtils.isEmpty(inetAddress)) {
+            throw new IllegalArgumentException("Unspecified address");
+        }
+
+        InetAddress checkAddr = NetworkUtils.numericToInetAddress(inetAddress);
+
+        if (checkAddr.isAnyLocalAddress()) {
+            throw new IllegalArgumentException("Inappropriate wildcard address: " + inetAddress);
+        }
+    }
+
+    /**
+     * Checks the user-provided direction field and throws an IllegalArgumentException if it is not
+     * DIRECTION_IN or DIRECTION_OUT
+     */
+    private static void checkDirection(int direction) {
+        switch (direction) {
+            case IpSecTransform.DIRECTION_OUT:
+            case IpSecTransform.DIRECTION_IN:
+                return;
+        }
+        throw new IllegalArgumentException("Invalid Direction: " + direction);
+    }
+
     @Override
     /** Get a new SPI and maintain the reservation in the system server */
     public synchronized IpSecSpiResponse reserveSecurityParameterIndex(
             int direction, String remoteAddress, int requestedSpi, IBinder binder)
             throws RemoteException {
+        checkDirection(direction);
+        checkInetAddress(remoteAddress);
+        /* requestedSpi can be anything in the int range, so no check is needed. */
+        checkNotNull(binder, "Null Binder passed to reserveSecurityParameterIndex");
+
         int resourceId = mNextResourceId.getAndIncrement();
 
         int spi = IpSecManager.INVALID_SECURITY_PARAMETER_INDEX;
@@ -651,9 +690,7 @@
         try {
             if (!mUserQuotaTracker.getUserRecord(Binder.getCallingUid()).spi.isAvailable()) {
                 return new IpSecSpiResponse(
-                        IpSecManager.Status.RESOURCE_UNAVAILABLE,
-                        INVALID_RESOURCE_ID,
-                        spi);
+                        IpSecManager.Status.RESOURCE_UNAVAILABLE, INVALID_RESOURCE_ID, spi);
             }
             spi =
                     mSrvConfig
@@ -686,7 +723,7 @@
             throws RemoteException {
         // We want to non-destructively get so that we can check credentials before removing
         // this from the records.
-        T record = resArray.get(resourceId);
+        T record = resArray.getAndCheckOwner(resourceId);
 
         if (record == null) {
             throw new IllegalArgumentException(
@@ -751,6 +788,8 @@
             throw new IllegalArgumentException(
                     "Specified port number must be a valid non-reserved UDP port");
         }
+        checkNotNull(binder, "Null Binder passed to openUdpEncapsulationSocket");
+
         int resourceId = mNextResourceId.getAndIncrement();
         FileDescriptor sockFd = null;
         try {
@@ -792,6 +831,68 @@
     }
 
     /**
+     * Checks an IpSecConfig parcel to ensure that the contents are sane and throws an
+     * IllegalArgumentException if they are not.
+     */
+    private void checkIpSecConfig(IpSecConfig config) {
+        if (config.getLocalAddress() == null) {
+            throw new IllegalArgumentException("Invalid null Local InetAddress");
+        }
+
+        if (config.getRemoteAddress() == null) {
+            throw new IllegalArgumentException("Invalid null Remote InetAddress");
+        }
+
+        switch (config.getMode()) {
+            case IpSecTransform.MODE_TRANSPORT:
+                if (!config.getLocalAddress().isEmpty()) {
+                    throw new IllegalArgumentException("Non-empty Local Address");
+                }
+                // Must be valid, and not a wildcard
+                checkInetAddress(config.getRemoteAddress());
+                break;
+            case IpSecTransform.MODE_TUNNEL:
+                break;
+            default:
+                throw new IllegalArgumentException(
+                        "Invalid IpSecTransform.mode: " + config.getMode());
+        }
+
+        switch (config.getEncapType()) {
+            case IpSecTransform.ENCAP_NONE:
+                break;
+            case IpSecTransform.ENCAP_ESPINUDP:
+            case IpSecTransform.ENCAP_ESPINUDP_NON_IKE:
+                if (mUdpSocketRecords.getAndCheckOwner(
+                            config.getEncapSocketResourceId()) == null) {
+                    throw new IllegalStateException(
+                            "No Encapsulation socket for Resource Id: "
+                                    + config.getEncapSocketResourceId());
+                }
+
+                int port = config.getEncapRemotePort();
+                if (port <= 0 || port > 0xFFFF) {
+                    throw new IllegalArgumentException("Invalid remote UDP port: " + port);
+                }
+                break;
+            default:
+                throw new IllegalArgumentException("Invalid Encap Type: " + config.getEncapType());
+        }
+
+        for (int direction : DIRECTIONS) {
+            IpSecAlgorithm crypt = config.getEncryption(direction);
+            IpSecAlgorithm auth = config.getAuthentication(direction);
+            if (crypt == null && auth == null) {
+                throw new IllegalArgumentException("Encryption and Authentication are both null");
+            }
+
+            if (mSpiRecords.getAndCheckOwner(config.getSpiResourceId(direction)) == null) {
+                throw new IllegalStateException("No SPI for specified Resource Id");
+            }
+        }
+    }
+
+    /**
      * Create a transport mode transform, which represent two security associations (one in each
      * direction) in the kernel. The transform will be cached by the system server and must be freed
      * when no longer needed. It is possible to free one, deleting the SA from underneath sockets
@@ -801,17 +902,19 @@
     @Override
     public synchronized IpSecTransformResponse createTransportModeTransform(
             IpSecConfig c, IBinder binder) throws RemoteException {
+        checkIpSecConfig(c);
+        checkNotNull(binder, "Null Binder passed to createTransportModeTransform");
         int resourceId = mNextResourceId.getAndIncrement();
         if (!mUserQuotaTracker.getUserRecord(Binder.getCallingUid()).transform.isAvailable()) {
             return new IpSecTransformResponse(IpSecManager.Status.RESOURCE_UNAVAILABLE);
         }
         SpiRecord[] spis = new SpiRecord[DIRECTIONS.length];
-        // TODO: Basic input validation here since it's coming over the Binder
+
         int encapType, encapLocalPort = 0, encapRemotePort = 0;
         UdpSocketRecord socketRecord = null;
         encapType = c.getEncapType();
         if (encapType != IpSecTransform.ENCAP_NONE) {
-            socketRecord = mUdpSocketRecords.get(c.getEncapLocalResourceId());
+            socketRecord = mUdpSocketRecords.getAndCheckOwner(c.getEncapSocketResourceId());
             encapLocalPort = socketRecord.getPort();
             encapRemotePort = c.getEncapRemotePort();
         }
@@ -820,23 +923,18 @@
             IpSecAlgorithm auth = c.getAuthentication(direction);
             IpSecAlgorithm crypt = c.getEncryption(direction);
 
-            spis[direction] = mSpiRecords.get(c.getSpiResourceId(direction));
+            spis[direction] = mSpiRecords.getAndCheckOwner(c.getSpiResourceId(direction));
             int spi = spis[direction].getSpi();
             try {
-                mSrvConfig.getNetdInstance()
+                mSrvConfig
+                        .getNetdInstance()
                         .ipSecAddSecurityAssociation(
                                 resourceId,
                                 c.getMode(),
                                 direction,
-                                (c.getLocalAddress() != null)
-                                        ? c.getLocalAddress().getHostAddress()
-                                        : "",
-                                (c.getRemoteAddress() != null)
-                                        ? c.getRemoteAddress().getHostAddress()
-                                        : "",
-                                (c.getNetwork() != null)
-                                        ? c.getNetwork().getNetworkHandle()
-                                        : 0,
+                                c.getLocalAddress(),
+                                c.getRemoteAddress(),
+                                (c.getNetwork() != null) ? c.getNetwork().getNetworkHandle() : 0,
                                 spi,
                                 (auth != null) ? auth.getName() : "",
                                 (auth != null) ? auth.getKey() : null,
@@ -879,7 +977,7 @@
         // Synchronize liberally here because we are using ManagedResources in this block
         TransformRecord info;
         // FIXME: this code should be factored out into a security check + getter
-        info = mTransformRecords.get(resourceId);
+        info = mTransformRecords.getAndCheckOwner(resourceId);
 
         if (info == null) {
             throw new IllegalArgumentException("Transform " + resourceId + " is not active");
@@ -899,12 +997,8 @@
                                 socket.getFileDescriptor(),
                                 resourceId,
                                 direction,
-                                (c.getLocalAddress() != null)
-                                        ? c.getLocalAddress().getHostAddress()
-                                        : "",
-                                (c.getRemoteAddress() != null)
-                                        ? c.getRemoteAddress().getHostAddress()
-                                        : "",
+                                c.getLocalAddress(),
+                                c.getRemoteAddress(),
                                 info.getSpiRecord(direction).getSpi());
             }
         } catch (ServiceSpecificException e) {
diff --git a/com/android/server/LocationManagerService.java b/com/android/server/LocationManagerService.java
index 340d672..0fd59ea 100644
--- a/com/android/server/LocationManagerService.java
+++ b/com/android/server/LocationManagerService.java
@@ -18,7 +18,6 @@
 
 import android.app.ActivityManager;
 import android.annotation.NonNull;
-import android.content.pm.PackageManagerInternal;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import com.android.internal.content.PackageMonitor;
@@ -56,6 +55,7 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ResolveInfo;
 import android.content.pm.Signature;
diff --git a/com/android/server/NetworkManagementService.java b/com/android/server/NetworkManagementService.java
index 2f95aa2..ba3afc3 100644
--- a/com/android/server/NetworkManagementService.java
+++ b/com/android/server/NetworkManagementService.java
@@ -1140,17 +1140,6 @@
     }
 
     @Override
-    public void setInterfaceIpv6NdOffload(String iface, boolean enable) {
-        mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG);
-        try {
-            mConnector.execute(
-                    "interface", "ipv6ndoffload", iface, (enable ? "enable" : "disable"));
-        } catch (NativeDaemonConnectorException e) {
-            throw e.rethrowAsParcelableException();
-        }
-    }
-
-    @Override
     public void addRoute(int netId, RouteInfo route) {
         modifyRoute("add", "" + netId, route);
     }
@@ -1991,8 +1980,12 @@
 
         final String[] domainStrs = domains == null ? new String[0] : domains.split(" ");
         final int[] params = { sampleValidity, successThreshold, minSamples, maxSamples };
+        final boolean useTls = false;
+        final String tlsHostname = "";
+        final String[] tlsFingerprints = new String[0];
         try {
-            mNetdService.setResolverConfiguration(netId, servers, domainStrs, params);
+            mNetdService.setResolverConfiguration(netId, servers, domainStrs, params,
+                    useTls, tlsHostname, tlsFingerprints);
         } catch (RemoteException e) {
             throw new RuntimeException(e);
         }
diff --git a/com/android/server/StorageManagerService.java b/com/android/server/StorageManagerService.java
index c0fcfd0..55391b3 100644
--- a/com/android/server/StorageManagerService.java
+++ b/com/android/server/StorageManagerService.java
@@ -111,8 +111,6 @@
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
 import com.android.internal.widget.LockPatternUtils;
-import com.android.server.NativeDaemonConnector.Command;
-import com.android.server.NativeDaemonConnector.SensitiveArg;
 import com.android.server.pm.PackageManagerService;
 import com.android.server.storage.AppFuseBridge;
 
@@ -138,7 +136,6 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
@@ -161,8 +158,7 @@
  * watch for and manage dynamically added storage, such as SD cards and USB mass
  * storage. Also decides how storage should be presented to users on the device.
  */
-class StorageManagerService extends IStorageManager.Stub
-        implements INativeDaemonConnectorCallbacks, Watchdog.Monitor {
+class StorageManagerService extends IStorageManager.Stub implements Watchdog.Monitor {
 
     // Static direct instance pointer for the tightly-coupled idle service to use
     static StorageManagerService sSelf = null;
@@ -206,18 +202,12 @@
         }
     }
 
-    /** Flag to enable binder-based interface to vold */
-    private static final boolean ENABLE_BINDER = true;
-
     private static final boolean DEBUG_EVENTS = false;
     private static final boolean DEBUG_OBB = false;
 
     // Disable this since it messes up long-running cryptfs operations.
     private static final boolean WATCHDOG_ENABLE = false;
 
-    /** Flag to enable ASECs */
-    private static final boolean ASEC_ENABLE = false;
-
     /**
      * Our goal is for all Android devices to be usable as development devices,
      * which includes the new Direct Boot mode added in N. For devices that
@@ -232,66 +222,9 @@
     private static final String TAG_STORAGE_BENCHMARK = "storage_benchmark";
     private static final String TAG_STORAGE_TRIM = "storage_trim";
 
-    private static final String VOLD_TAG = "VoldConnector";
-    private static final String CRYPTD_TAG = "CryptdConnector";
-
-    /** Maximum number of ASEC containers allowed to be mounted. */
-    private static final int MAX_CONTAINERS = 250;
-
     /** Magic value sent by MoveTask.cpp */
     private static final int MOVE_STATUS_COPY_FINISHED = 82;
 
-    /*
-     * Internal vold response code constants
-     */
-    class VoldResponseCode {
-        /*
-         * 100 series - Requestion action was initiated; expect another reply
-         *              before proceeding with a new command.
-         */
-        public static final int VolumeListResult               = 110;
-        public static final int AsecListResult                 = 111;
-        public static final int StorageUsersListResult         = 112;
-        public static final int CryptfsGetfieldResult          = 113;
-
-        /*
-         * 200 series - Requestion action has been successfully completed.
-         */
-        public static final int ShareStatusResult              = 210;
-        public static final int AsecPathResult                 = 211;
-        public static final int ShareEnabledResult             = 212;
-
-        /*
-         * 400 series - Command was accepted, but the requested action
-         *              did not take place.
-         */
-        public static final int OpFailedNoMedia                = 401;
-        public static final int OpFailedMediaBlank             = 402;
-        public static final int OpFailedMediaCorrupt           = 403;
-        public static final int OpFailedVolNotMounted          = 404;
-        public static final int OpFailedStorageBusy            = 405;
-        public static final int OpFailedStorageNotFound        = 406;
-
-        /*
-         * 600 series - Unsolicited broadcasts.
-         */
-        public static final int DISK_CREATED = 640;
-        public static final int DISK_SIZE_CHANGED = 641;
-        public static final int DISK_LABEL_CHANGED = 642;
-        public static final int DISK_SCANNED = 643;
-        public static final int DISK_SYS_PATH_CHANGED = 644;
-        public static final int DISK_DESTROYED = 649;
-
-        public static final int VOLUME_CREATED = 650;
-        public static final int VOLUME_STATE_CHANGED = 651;
-        public static final int VOLUME_FS_TYPE_CHANGED = 652;
-        public static final int VOLUME_FS_UUID_CHANGED = 653;
-        public static final int VOLUME_FS_LABEL_CHANGED = 654;
-        public static final int VOLUME_PATH_CHANGED = 655;
-        public static final int VOLUME_INTERNAL_PATH_CHANGED = 656;
-        public static final int VOLUME_DESTROYED = 659;
-    }
-
     private static final int VERSION_INIT = 1;
     private static final int VERSION_ADD_PRIMARY = 2;
     private static final int VERSION_FIX_PRIMARY = 3;
@@ -453,17 +386,6 @@
         }
     }
 
-    private static String escapeNull(String arg) {
-        if (TextUtils.isEmpty(arg)) {
-            return "!";
-        } else {
-            if (arg.indexOf('\0') != -1 || arg.indexOf(' ') != -1) {
-                throw new IllegalArgumentException(arg);
-            }
-            return arg;
-        }
-    }
-
     /** List of crypto types.
       * These must match CRYPT_TYPE_XXX in cryptfs.h AND their
       * corresponding commands in CommandListener.cpp */
@@ -472,12 +394,6 @@
 
     private final Context mContext;
 
-    private final NativeDaemonConnector mConnector;
-    private final NativeDaemonConnector mCryptConnector;
-
-    private final Thread mConnectorThread;
-    private final Thread mCryptConnectorThread;
-
     private volatile IVold mVold;
 
     private volatile boolean mSystemReady = false;
@@ -489,20 +405,6 @@
     private final Callbacks mCallbacks;
     private final LockPatternUtils mLockPatternUtils;
 
-    // Two connectors - mConnector & mCryptConnector
-    private final CountDownLatch mConnectedSignal = new CountDownLatch(2);
-    private final CountDownLatch mAsecsScanned = new CountDownLatch(1);
-
-    private final Object mUnmountLock = new Object();
-    @GuardedBy("mUnmountLock")
-    private CountDownLatch mUnmountSignal;
-
-    /**
-     * Private hash of currently mounted secure containers.
-     * Used as a lock in methods to manipulate secure containers.
-     */
-    final private HashSet<String> mAsecMountSet = new HashSet<String>();
-
     /**
      * The size of the crypto algorithm key in bits for OBB files. Currently
      * Twofish is used which takes 128-bit keys.
@@ -616,7 +518,7 @@
             if (DEBUG_OBB)
                 Slog.i(TAG, "onServiceDisconnected");
         }
-    };
+    }
 
     // Used in the ObbActionHandler
     private IMediaContainerService mContainerService = null;
@@ -655,13 +557,6 @@
                     break;
                 }
                 case H_FSTRIM: {
-                    if (!isReady()) {
-                        Slog.i(TAG, "fstrim requested, but no daemon connection yet; trying again");
-                        sendMessageDelayed(obtainMessage(H_FSTRIM, msg.obj),
-                                DateUtils.SECOND_IN_MILLIS);
-                        break;
-                    }
-
                     Slog.i(TAG, "Running fstrim idle maintenance");
 
                     // Remember when we kicked it off
@@ -687,12 +582,8 @@
                     final IStorageShutdownObserver obs = (IStorageShutdownObserver) msg.obj;
                     boolean success = false;
                     try {
-                        if (ENABLE_BINDER) {
-                            mVold.shutdown();
-                            success = true;
-                        } else {
-                            success = mConnector.execute("volume", "shutdown").isClassOk();
-                        }
+                        mVold.shutdown();
+                        success = true;
                     } catch (Exception e) {
                         Slog.wtf(TAG, e);
                     }
@@ -711,12 +602,7 @@
                         break;
                     }
                     try {
-                        if (ENABLE_BINDER) {
-                            mVold.mount(vol.id, vol.mountFlags, vol.mountUserId);
-                        } else {
-                            mConnector.execute("volume", "mount", vol.id, vol.mountFlags,
-                                    vol.mountUserId);
-                        }
+                        mVold.mount(vol.id, vol.mountFlags, vol.mountUserId);
                     } catch (Exception e) {
                         Slog.wtf(TAG, e);
                     }
@@ -778,11 +664,7 @@
                 if (Intent.ACTION_USER_ADDED.equals(action)) {
                     final UserManager um = mContext.getSystemService(UserManager.class);
                     final int userSerialNumber = um.getUserSerialNumber(userId);
-                    if (ENABLE_BINDER) {
-                        mVold.onUserAdded(userId, userSerialNumber);
-                    } else {
-                        mConnector.execute("volume", "user_added", userId, userSerialNumber);
-                    }
+                    mVold.onUserAdded(userId, userSerialNumber);
                 } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
                     synchronized (mVolumes) {
                         final int size = mVolumes.size();
@@ -794,11 +676,7 @@
                             }
                         }
                     }
-                    if (ENABLE_BINDER) {
-                        mVold.onUserRemoved(userId);
-                    } else {
-                        mConnector.execute("volume", "user_removed", userId);
-                    }
+                    mVold.onUserRemoved(userId);
                 }
             } catch (Exception e) {
                 Slog.wtf(TAG, e);
@@ -806,22 +684,6 @@
         }
     };
 
-    @Override
-    public void waitForAsecScan() {
-        waitForLatch(mAsecsScanned, "mAsecsScanned");
-    }
-
-    private void waitForReady() {
-        waitForLatch(mConnectedSignal, "mConnectedSignal");
-    }
-
-    private void waitForLatch(CountDownLatch latch, String condition) {
-        try {
-            waitForLatch(latch, condition, -1);
-        } catch (TimeoutException ignored) {
-        }
-    }
-
     private void waitForLatch(CountDownLatch latch, String condition, long timeoutMillis)
             throws TimeoutException {
         final long startMillis = SystemClock.elapsedRealtime();
@@ -843,14 +705,6 @@
         }
     }
 
-    private boolean isReady() {
-        try {
-            return mConnectedSignal.await(0, TimeUnit.MILLISECONDS);
-        } catch (InterruptedException e) {
-            return false;
-        }
-    }
-
     private void handleSystemReady() {
         initIfReadyAndConnected();
         resetIfReadyAndConnected();
@@ -917,19 +771,10 @@
             for (UserInfo user : users) {
                 try {
                     if (initLocked) {
-                        if (ENABLE_BINDER) {
-                            mVold.lockUserKey(user.id);
-                        } else {
-                            mCryptConnector.execute("cryptfs", "lock_user_key", user.id);
-                        }
+                        mVold.lockUserKey(user.id);
                     } else {
-                        if (ENABLE_BINDER) {
-                            mVold.unlockUserKey(user.id, user.serialNumber, encodeBytes(null),
-                                    encodeBytes(null));
-                        } else {
-                            mCryptConnector.execute("cryptfs", "unlock_user_key", user.id,
-                                    user.serialNumber, "!", "!");
-                        }
+                        mVold.unlockUserKey(user.id, user.serialNumber, encodeBytes(null),
+                                encodeBytes(null));
                     }
                 } catch (Exception e) {
                     Slog.wtf(TAG, e);
@@ -956,26 +801,14 @@
             }
 
             try {
-                if (ENABLE_BINDER) {
-                    mVold.reset();
-                } else {
-                    mConnector.execute("volume", "reset");
-                }
+                mVold.reset();
 
                 // Tell vold about all existing and started users
                 for (UserInfo user : users) {
-                    if (ENABLE_BINDER) {
-                        mVold.onUserAdded(user.id, user.serialNumber);
-                    } else {
-                        mConnector.execute("volume", "user_added", user.id, user.serialNumber);
-                    }
+                    mVold.onUserAdded(user.id, user.serialNumber);
                 }
                 for (int userId : systemUnlockedUsers) {
-                    if (ENABLE_BINDER) {
-                        mVold.onUserStarted(userId);
-                    } else {
-                        mConnector.execute("volume", "user_started", userId);
-                    }
+                    mVold.onUserStarted(userId);
                 }
             } catch (Exception e) {
                 Slog.wtf(TAG, e);
@@ -990,11 +823,7 @@
         // staging area is ready so it's ready for zygote-forked apps to
         // bind mount against.
         try {
-            if (ENABLE_BINDER) {
-                mVold.onUserStarted(userId);
-            } else {
-                mConnector.execute("volume", "user_started", userId);
-            }
+            mVold.onUserStarted(userId);
         } catch (Exception e) {
             Slog.wtf(TAG, e);
         }
@@ -1020,11 +849,7 @@
         Slog.d(TAG, "onCleanupUser " + userId);
 
         try {
-            if (ENABLE_BINDER) {
-                mVold.onUserStopped(userId);
-            } else {
-                mConnector.execute("volume", "user_stopped", userId);
-            }
+            mVold.onUserStopped(userId);
         } catch (Exception e) {
             Slog.wtf(TAG, e);
         }
@@ -1050,10 +875,6 @@
         return mLastMaintenance;
     }
 
-    /**
-     * Callback from NativeDaemonConnector
-     */
-    @Override
     public void onDaemonConnected() {
         mDaemonConnected = true;
         mHandler.obtainMessage(H_DAEMON_CONNECTED).sendToTarget();
@@ -1063,29 +884,11 @@
         initIfReadyAndConnected();
         resetIfReadyAndConnected();
 
-        /*
-         * Now that we've done our initialization, release
-         * the hounds!
-         */
-        mConnectedSignal.countDown();
-        if (mConnectedSignal.getCount() != 0) {
-            // More daemons need to connect
-            return;
-        }
-
         // On an encrypted device we can't see system properties yet, so pull
         // the system locale out of the mount service.
         if ("".equals(SystemProperties.get("vold.encrypt_progress"))) {
             copyLocaleFromMountService();
         }
-
-        // Let package manager load internal ASECs.
-        if (ASEC_ENABLE) {
-            mPms.scanAvailableAsecs();
-        }
-
-        // Notify people waiting for ASECs to be scanned that it's done.
-        mAsecsScanned.countDown();
     }
 
     private void copyLocaleFromMountService() {
@@ -1114,148 +917,6 @@
         SystemProperties.set("persist.sys.locale", locale.toLanguageTag());
     }
 
-    /**
-     * Callback from NativeDaemonConnector
-     */
-    @Override
-    public boolean onCheckHoldWakeLock(int code) {
-        return false;
-    }
-
-    /**
-     * Callback from NativeDaemonConnector
-     */
-    @Override
-    public boolean onEvent(int code, String raw, String[] cooked) {
-        synchronized (mLock) {
-            try {
-                return onEventLocked(code, raw, cooked);
-            } catch (RemoteException e) {
-                throw e.rethrowAsRuntimeException();
-            }
-        }
-    }
-
-    private boolean onEventLocked(int code, String raw, String[] cooked) throws RemoteException {
-        switch (code) {
-            case VoldResponseCode.DISK_CREATED: {
-                if (cooked.length != 3) break;
-                final String diskId = cooked[1];
-                final int flags = Integer.parseInt(cooked[2]);
-                mListener.onDiskCreated(diskId, flags);
-                break;
-            }
-            case VoldResponseCode.DISK_SIZE_CHANGED: {
-                if (cooked.length != 3) break;
-                final DiskInfo disk = mDisks.get(cooked[1]);
-                if (disk != null) {
-                    disk.size = Long.parseLong(cooked[2]);
-                }
-                break;
-            }
-            case VoldResponseCode.DISK_LABEL_CHANGED: {
-                final DiskInfo disk = mDisks.get(cooked[1]);
-                if (disk != null) {
-                    final StringBuilder builder = new StringBuilder();
-                    for (int i = 2; i < cooked.length; i++) {
-                        builder.append(cooked[i]).append(' ');
-                    }
-                    disk.label = builder.toString().trim();
-                }
-                break;
-            }
-            case VoldResponseCode.DISK_SCANNED: {
-                if (cooked.length != 2) break;
-                final String diskId = cooked[1];
-                mListener.onDiskScanned(diskId);
-                break;
-            }
-            case VoldResponseCode.DISK_SYS_PATH_CHANGED: {
-                if (cooked.length != 3) break;
-                final DiskInfo disk = mDisks.get(cooked[1]);
-                if (disk != null) {
-                    disk.sysPath = cooked[2];
-                }
-                break;
-            }
-            case VoldResponseCode.DISK_DESTROYED: {
-                if (cooked.length != 2) break;
-                final String diskId = cooked[1];
-                mListener.onDiskDestroyed(diskId);
-                break;
-            }
-
-            case VoldResponseCode.VOLUME_CREATED: {
-                final String volId = cooked[1];
-                final int type = Integer.parseInt(cooked[2]);
-                final String diskId = TextUtils.nullIfEmpty(cooked[3]);
-                final String partGuid = TextUtils.nullIfEmpty(cooked[4]);
-                mListener.onVolumeCreated(volId, type, diskId, partGuid);
-                break;
-            }
-            case VoldResponseCode.VOLUME_STATE_CHANGED: {
-                if (cooked.length != 3) break;
-                final String volId = cooked[1];
-                final int state = Integer.parseInt(cooked[2]);
-                mListener.onVolumeStateChanged(volId, state);
-                break;
-            }
-            case VoldResponseCode.VOLUME_FS_TYPE_CHANGED: {
-                if (cooked.length != 3) break;
-                final VolumeInfo vol = mVolumes.get(cooked[1]);
-                if (vol != null) {
-                    vol.fsType = cooked[2];
-                }
-                break;
-            }
-            case VoldResponseCode.VOLUME_FS_UUID_CHANGED: {
-                if (cooked.length != 3) break;
-                final VolumeInfo vol = mVolumes.get(cooked[1]);
-                if (vol != null) {
-                    vol.fsUuid = cooked[2];
-                }
-                break;
-            }
-            case VoldResponseCode.VOLUME_FS_LABEL_CHANGED: {
-                final VolumeInfo vol = mVolumes.get(cooked[1]);
-                if (vol != null) {
-                    final StringBuilder builder = new StringBuilder();
-                    for (int i = 2; i < cooked.length; i++) {
-                        builder.append(cooked[i]).append(' ');
-                    }
-                    vol.fsLabel = builder.toString().trim();
-                }
-                // TODO: notify listeners that label changed
-                break;
-            }
-            case VoldResponseCode.VOLUME_PATH_CHANGED: {
-                if (cooked.length != 3) break;
-                final String volId = cooked[1];
-                final String path = cooked[2];
-                mListener.onVolumePathChanged(volId, path);
-                break;
-            }
-            case VoldResponseCode.VOLUME_INTERNAL_PATH_CHANGED: {
-                if (cooked.length != 3) break;
-                final String volId = cooked[1];
-                final String internalPath = cooked[2];
-                mListener.onVolumeInternalPathChanged(volId, internalPath);
-                break;
-            }
-            case VoldResponseCode.VOLUME_DESTROYED: {
-                if (cooked.length != 2) break;
-                final String volId = cooked[1];
-                mListener.onVolumeDestroyed(volId);
-                break;
-            }
-            default: {
-                Slog.d(TAG, "Unhandled vold event " + code);
-            }
-        }
-
-        return true;
-    }
-
     private final IVoldListener mListener = new IVoldListener.Stub() {
         @Override
         public void onDiskCreated(String diskId, int flags) {
@@ -1654,24 +1315,6 @@
 
         LocalServices.addService(StorageManagerInternal.class, mStorageManagerInternal);
 
-        /*
-         * Create the connection to vold with a maximum queue of twice the
-         * amount of containers we'd ever expect to have. This keeps an
-         * "asec list" from blocking a thread repeatedly.
-         */
-
-        mConnector = new NativeDaemonConnector(this, "vold", MAX_CONTAINERS * 2, VOLD_TAG, 25,
-                null);
-        mConnector.setDebug(true);
-        mConnector.setWarnIfHeld(mLock);
-        mConnectorThread = new Thread(mConnector, VOLD_TAG);
-
-        // Reuse parameters from first connector since they are tested and safe
-        mCryptConnector = new NativeDaemonConnector(this, "cryptd",
-                MAX_CONTAINERS * 2, CRYPTD_TAG, 25, null);
-        mCryptConnector.setDebug(true);
-        mCryptConnectorThread = new Thread(mCryptConnector, CRYPTD_TAG);
-
         final IntentFilter userFilter = new IntentFilter();
         userFilter.addAction(Intent.ACTION_USER_ADDED);
         userFilter.addAction(Intent.ACTION_USER_REMOVED);
@@ -1689,8 +1332,6 @@
 
     private void start() {
         connect();
-        mConnectorThread.start();
-        mCryptConnectorThread.start();
     }
 
     private void connect() {
@@ -1713,6 +1354,7 @@
             mVold = IVold.Stub.asInterface(binder);
             try {
                 mVold.setListener(mListener);
+                onDaemonConnected();
                 return;
             } catch (RemoteException e) {
                 Slog.w(TAG, "vold listener rejected; trying again", e);
@@ -1864,62 +1506,15 @@
     }
 
     @Override
-    public boolean isUsbMassStorageConnected() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public void setUsbMassStorageEnabled(boolean enable) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public boolean isUsbMassStorageEnabled() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public String getVolumeState(String mountPoint) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public boolean isExternalStorageEmulated() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public int mountVolume(String path) {
-        mount(findVolumeIdForPathOrThrow(path));
-        return 0;
-    }
-
-    @Override
-    public void unmountVolume(String path, boolean force, boolean removeEncryption) {
-        unmount(findVolumeIdForPathOrThrow(path));
-    }
-
-    @Override
-    public int formatVolume(String path) {
-        format(findVolumeIdForPathOrThrow(path));
-        return 0;
-    }
-
-    @Override
     public void mount(String volId) {
         enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
-        waitForReady();
 
         final VolumeInfo vol = findVolumeByIdOrThrow(volId);
         if (isMountDisallowed(vol)) {
             throw new SecurityException("Mounting " + volId + " restricted by policy");
         }
         try {
-            if (ENABLE_BINDER) {
-                mVold.mount(vol.id, vol.mountFlags, vol.mountUserId);
-            } else {
-                mConnector.execute("volume", "mount", vol.id, vol.mountFlags, vol.mountUserId);
-            }
+            mVold.mount(vol.id, vol.mountFlags, vol.mountUserId);
         } catch (Exception e) {
             Slog.wtf(TAG, e);
         }
@@ -1928,31 +1523,10 @@
     @Override
     public void unmount(String volId) {
         enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
-        waitForReady();
 
         final VolumeInfo vol = findVolumeByIdOrThrow(volId);
-
-        // TODO: expand PMS to know about multiple volumes
-        if (vol.isPrimaryPhysical()) {
-            final long ident = Binder.clearCallingIdentity();
-            try {
-                synchronized (mUnmountLock) {
-                    mUnmountSignal = new CountDownLatch(1);
-                    mPms.updateExternalMediaStatus(false, true);
-                    waitForLatch(mUnmountSignal, "mUnmountSignal");
-                    mUnmountSignal = null;
-                }
-            } finally {
-                Binder.restoreCallingIdentity(ident);
-            }
-        }
-
         try {
-            if (ENABLE_BINDER) {
-                mVold.unmount(vol.id);
-            } else {
-                mConnector.execute("volume", "unmount", vol.id);
-            }
+            mVold.unmount(vol.id);
         } catch (Exception e) {
             Slog.wtf(TAG, e);
         }
@@ -1961,15 +1535,10 @@
     @Override
     public void format(String volId) {
         enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
-        waitForReady();
 
         final VolumeInfo vol = findVolumeByIdOrThrow(volId);
         try {
-            if (ENABLE_BINDER) {
-                mVold.format(vol.id, "auto");
-            } else {
-                mConnector.execute("volume", "format", vol.id, "auto");
-            }
+            mVold.format(vol.id, "auto");
         } catch (Exception e) {
             Slog.wtf(TAG, e);
         }
@@ -1978,7 +1547,6 @@
     @Override
     public long benchmark(String volId) {
         enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
-        waitForReady();
 
         // TODO: refactor for callers to provide a listener
         try {
@@ -2022,15 +1590,10 @@
     @Override
     public void partitionPublic(String diskId) {
         enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
-        waitForReady();
 
         final CountDownLatch latch = findOrCreateDiskScanLatch(diskId);
         try {
-            if (ENABLE_BINDER) {
-                mVold.partition(diskId, IVold.PARTITION_TYPE_PUBLIC, -1);
-            } else {
-                mConnector.execute("volume", "partition", diskId, "public");
-            }
+            mVold.partition(diskId, IVold.PARTITION_TYPE_PUBLIC, -1);
             waitForLatch(latch, "partitionPublic", 3 * DateUtils.MINUTE_IN_MILLIS);
         } catch (Exception e) {
             Slog.wtf(TAG, e);
@@ -2041,15 +1604,10 @@
     public void partitionPrivate(String diskId) {
         enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
         enforceAdminUser();
-        waitForReady();
 
         final CountDownLatch latch = findOrCreateDiskScanLatch(diskId);
         try {
-            if (ENABLE_BINDER) {
-                mVold.partition(diskId, IVold.PARTITION_TYPE_PRIVATE, -1);
-            } else {
-                mConnector.execute("volume", "partition", diskId, "private");
-            }
+            mVold.partition(diskId, IVold.PARTITION_TYPE_PRIVATE, -1);
             waitForLatch(latch, "partitionPrivate", 3 * DateUtils.MINUTE_IN_MILLIS);
         } catch (Exception e) {
             Slog.wtf(TAG, e);
@@ -2060,15 +1618,10 @@
     public void partitionMixed(String diskId, int ratio) {
         enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
         enforceAdminUser();
-        waitForReady();
 
         final CountDownLatch latch = findOrCreateDiskScanLatch(diskId);
         try {
-            if (ENABLE_BINDER) {
-                mVold.partition(diskId, IVold.PARTITION_TYPE_MIXED, ratio);
-            } else {
-                mConnector.execute("volume", "partition", diskId, "mixed", ratio);
-            }
+            mVold.partition(diskId, IVold.PARTITION_TYPE_MIXED, ratio);
             waitForLatch(latch, "partitionMixed", 3 * DateUtils.MINUTE_IN_MILLIS);
         } catch (Exception e) {
             Slog.wtf(TAG, e);
@@ -2078,7 +1631,6 @@
     @Override
     public void setVolumeNickname(String fsUuid, String nickname) {
         enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
-        waitForReady();
 
         Preconditions.checkNotNull(fsUuid);
         synchronized (mLock) {
@@ -2092,7 +1644,6 @@
     @Override
     public void setVolumeUserFlags(String fsUuid, int flags, int mask) {
         enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
-        waitForReady();
 
         Preconditions.checkNotNull(fsUuid);
         synchronized (mLock) {
@@ -2106,7 +1657,6 @@
     @Override
     public void forgetVolume(String fsUuid) {
         enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
-        waitForReady();
 
         Preconditions.checkNotNull(fsUuid);
 
@@ -2131,7 +1681,6 @@
     @Override
     public void forgetAllVolumes() {
         enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
-        waitForReady();
 
         synchronized (mLock) {
             for (int i = 0; i < mRecords.size(); i++) {
@@ -2155,11 +1704,7 @@
 
     private void forgetPartition(String partGuid) {
         try {
-            if (ENABLE_BINDER) {
-                mVold.forgetPartition(partGuid);
-            } else {
-                mConnector.execute("volume", "forget_partition", partGuid);
-            }
+            mVold.forgetPartition(partGuid);
         } catch (Exception e) {
             Slog.wtf(TAG, e);
         }
@@ -2168,14 +1713,6 @@
     @Override
     public void fstrim(int flags) {
         enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
-        waitForReady();
-
-        String cmd;
-        if ((flags & StorageManager.FSTRIM_FLAG_DEEP) != 0) {
-            cmd = "dodtrim";
-        } else {
-            cmd = "dotrim";
-        }
 
         try {
             mVold.fstrim(flags, new IVoldTaskListener.Stub() {
@@ -2212,27 +1749,8 @@
     }
 
     private void remountUidExternalStorage(int uid, int mode) {
-        waitForReady();
-
-        String modeName = "none";
-        switch (mode) {
-            case Zygote.MOUNT_EXTERNAL_DEFAULT: {
-                modeName = "default";
-            } break;
-            case Zygote.MOUNT_EXTERNAL_READ: {
-                modeName = "read";
-            } break;
-            case Zygote.MOUNT_EXTERNAL_WRITE: {
-                modeName = "write";
-            } break;
-        }
-
         try {
-            if (ENABLE_BINDER) {
-                mVold.remountUid(uid, mode);
-            } else {
-                mConnector.execute("volume", "remount_uid", uid, modeName);
-            }
+            mVold.remountUid(uid, mode);
         } catch (Exception e) {
             Slog.wtf(TAG, e);
         }
@@ -2241,7 +1759,6 @@
     @Override
     public void setDebugFlags(int flags, int mask) {
         enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
-        waitForReady();
 
         if ((mask & StorageManager.DEBUG_EMULATE_FBE) != 0) {
             if (!EMULATE_FBE_SUPPORTED) {
@@ -2331,7 +1848,6 @@
     @Override
     public void setPrimaryStorageUuid(String volumeUuid, IPackageMoveObserver callback) {
         enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
-        waitForReady();
 
         final VolumeInfo from;
         final VolumeInfo to;
@@ -2403,33 +1919,6 @@
         }
     }
 
-    @Override
-    public int[] getStorageUsers(String path) {
-        enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
-        waitForReady();
-        try {
-            final String[] r = NativeDaemonEvent.filterMessageList(
-                    mConnector.executeForList("storage", "users", path),
-                    VoldResponseCode.StorageUsersListResult);
-
-            // FMT: <pid> <process name>
-            int[] data = new int[r.length];
-            for (int i = 0; i < r.length; i++) {
-                String[] tok = r[i].split(" ");
-                try {
-                    data[i] = Integer.parseInt(tok[0]);
-                } catch (NumberFormatException nfe) {
-                    Slog.e(TAG, String.format("Error parsing pid %s", tok[0]));
-                    return new int[0];
-                }
-            }
-            return data;
-        } catch (NativeDaemonConnectorException e) {
-            Slog.e(TAG, "Failed to retrieve storage users list", e);
-            return new int[0];
-        }
-    }
-
     private void warnOnNotMounted() {
         synchronized (mLock) {
             for (int i = 0; i < mVolumes.size(); i++) {
@@ -2444,304 +1933,6 @@
         Slog.w(TAG, "No primary storage mounted!");
     }
 
-    public String[] getSecureContainerList() {
-        if (!ASEC_ENABLE) throw new UnsupportedOperationException();
-        enforcePermission(android.Manifest.permission.ASEC_ACCESS);
-        waitForReady();
-        warnOnNotMounted();
-
-        try {
-            return NativeDaemonEvent.filterMessageList(
-                    mConnector.executeForList("asec", "list"), VoldResponseCode.AsecListResult);
-        } catch (NativeDaemonConnectorException e) {
-            return new String[0];
-        }
-    }
-
-    public int createSecureContainer(String id, int sizeMb, String fstype, String key,
-            int ownerUid, boolean external) {
-        if (!ASEC_ENABLE) throw new UnsupportedOperationException();
-        enforcePermission(android.Manifest.permission.ASEC_CREATE);
-        waitForReady();
-        warnOnNotMounted();
-
-        int rc = StorageResultCode.OperationSucceeded;
-        try {
-            mConnector.execute("asec", "create", id, sizeMb, fstype, new SensitiveArg(key),
-                    ownerUid, external ? "1" : "0");
-        } catch (NativeDaemonConnectorException e) {
-            rc = StorageResultCode.OperationFailedInternalError;
-        }
-
-        if (rc == StorageResultCode.OperationSucceeded) {
-            synchronized (mAsecMountSet) {
-                mAsecMountSet.add(id);
-            }
-        }
-        return rc;
-    }
-
-    @Override
-    public int resizeSecureContainer(String id, int sizeMb, String key) {
-        if (!ASEC_ENABLE) throw new UnsupportedOperationException();
-        enforcePermission(android.Manifest.permission.ASEC_CREATE);
-        waitForReady();
-        warnOnNotMounted();
-
-        int rc = StorageResultCode.OperationSucceeded;
-        try {
-            mConnector.execute("asec", "resize", id, sizeMb, new SensitiveArg(key));
-        } catch (NativeDaemonConnectorException e) {
-            rc = StorageResultCode.OperationFailedInternalError;
-        }
-        return rc;
-    }
-
-    public int finalizeSecureContainer(String id) {
-        if (!ASEC_ENABLE) throw new UnsupportedOperationException();
-        enforcePermission(android.Manifest.permission.ASEC_CREATE);
-        warnOnNotMounted();
-
-        int rc = StorageResultCode.OperationSucceeded;
-        try {
-            mConnector.execute("asec", "finalize", id);
-            /*
-             * Finalization does a remount, so no need
-             * to update mAsecMountSet
-             */
-        } catch (NativeDaemonConnectorException e) {
-            rc = StorageResultCode.OperationFailedInternalError;
-        }
-        return rc;
-    }
-
-    public int fixPermissionsSecureContainer(String id, int gid, String filename) {
-        if (!ASEC_ENABLE) throw new UnsupportedOperationException();
-        enforcePermission(android.Manifest.permission.ASEC_CREATE);
-        warnOnNotMounted();
-
-        int rc = StorageResultCode.OperationSucceeded;
-        try {
-            mConnector.execute("asec", "fixperms", id, gid, filename);
-            /*
-             * Fix permissions does a remount, so no need to update
-             * mAsecMountSet
-             */
-        } catch (NativeDaemonConnectorException e) {
-            rc = StorageResultCode.OperationFailedInternalError;
-        }
-        return rc;
-    }
-
-    public int destroySecureContainer(String id, boolean force) {
-        if (!ASEC_ENABLE) throw new UnsupportedOperationException();
-        enforcePermission(android.Manifest.permission.ASEC_DESTROY);
-        waitForReady();
-        warnOnNotMounted();
-
-        /*
-         * Force a GC to make sure AssetManagers in other threads of the
-         * system_server are cleaned up. We have to do this since AssetManager
-         * instances are kept as a WeakReference and it's possible we have files
-         * open on the external storage.
-         */
-        Runtime.getRuntime().gc();
-
-        int rc = StorageResultCode.OperationSucceeded;
-        try {
-            final Command cmd = new Command("asec", "destroy", id);
-            if (force) {
-                cmd.appendArg("force");
-            }
-            mConnector.execute(cmd);
-        } catch (NativeDaemonConnectorException e) {
-            int code = e.getCode();
-            if (code == VoldResponseCode.OpFailedStorageBusy) {
-                rc = StorageResultCode.OperationFailedStorageBusy;
-            } else {
-                rc = StorageResultCode.OperationFailedInternalError;
-            }
-        }
-
-        if (rc == StorageResultCode.OperationSucceeded) {
-            synchronized (mAsecMountSet) {
-                if (mAsecMountSet.contains(id)) {
-                    mAsecMountSet.remove(id);
-                }
-            }
-        }
-
-        return rc;
-    }
-
-    public int mountSecureContainer(String id, String key, int ownerUid, boolean readOnly) {
-        if (!ASEC_ENABLE) throw new UnsupportedOperationException();
-        enforcePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT);
-        waitForReady();
-        warnOnNotMounted();
-
-        synchronized (mAsecMountSet) {
-            if (mAsecMountSet.contains(id)) {
-                return StorageResultCode.OperationFailedStorageMounted;
-            }
-        }
-
-        int rc = StorageResultCode.OperationSucceeded;
-        try {
-            mConnector.execute("asec", "mount", id, new SensitiveArg(key), ownerUid,
-                    readOnly ? "ro" : "rw");
-        } catch (NativeDaemonConnectorException e) {
-            int code = e.getCode();
-            if (code != VoldResponseCode.OpFailedStorageBusy) {
-                rc = StorageResultCode.OperationFailedInternalError;
-            }
-        }
-
-        if (rc == StorageResultCode.OperationSucceeded) {
-            synchronized (mAsecMountSet) {
-                mAsecMountSet.add(id);
-            }
-        }
-        return rc;
-    }
-
-    public int unmountSecureContainer(String id, boolean force) {
-        if (!ASEC_ENABLE) throw new UnsupportedOperationException();
-        enforcePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT);
-        waitForReady();
-        warnOnNotMounted();
-
-        synchronized (mAsecMountSet) {
-            if (!mAsecMountSet.contains(id)) {
-                return StorageResultCode.OperationFailedStorageNotMounted;
-            }
-         }
-
-        /*
-         * Force a GC to make sure AssetManagers in other threads of the
-         * system_server are cleaned up. We have to do this since AssetManager
-         * instances are kept as a WeakReference and it's possible we have files
-         * open on the external storage.
-         */
-        Runtime.getRuntime().gc();
-
-        int rc = StorageResultCode.OperationSucceeded;
-        try {
-            final Command cmd = new Command("asec", "unmount", id);
-            if (force) {
-                cmd.appendArg("force");
-            }
-            mConnector.execute(cmd);
-        } catch (NativeDaemonConnectorException e) {
-            int code = e.getCode();
-            if (code == VoldResponseCode.OpFailedStorageBusy) {
-                rc = StorageResultCode.OperationFailedStorageBusy;
-            } else {
-                rc = StorageResultCode.OperationFailedInternalError;
-            }
-        }
-
-        if (rc == StorageResultCode.OperationSucceeded) {
-            synchronized (mAsecMountSet) {
-                mAsecMountSet.remove(id);
-            }
-        }
-        return rc;
-    }
-
-    public boolean isSecureContainerMounted(String id) {
-        if (!ASEC_ENABLE) throw new UnsupportedOperationException();
-        enforcePermission(android.Manifest.permission.ASEC_ACCESS);
-        waitForReady();
-        warnOnNotMounted();
-
-        synchronized (mAsecMountSet) {
-            return mAsecMountSet.contains(id);
-        }
-    }
-
-    public int renameSecureContainer(String oldId, String newId) {
-        if (!ASEC_ENABLE) throw new UnsupportedOperationException();
-        enforcePermission(android.Manifest.permission.ASEC_RENAME);
-        waitForReady();
-        warnOnNotMounted();
-
-        synchronized (mAsecMountSet) {
-            /*
-             * Because a mounted container has active internal state which cannot be
-             * changed while active, we must ensure both ids are not currently mounted.
-             */
-            if (mAsecMountSet.contains(oldId) || mAsecMountSet.contains(newId)) {
-                return StorageResultCode.OperationFailedStorageMounted;
-            }
-        }
-
-        int rc = StorageResultCode.OperationSucceeded;
-        try {
-            mConnector.execute("asec", "rename", oldId, newId);
-        } catch (NativeDaemonConnectorException e) {
-            rc = StorageResultCode.OperationFailedInternalError;
-        }
-
-        return rc;
-    }
-
-    public String getSecureContainerPath(String id) {
-        if (!ASEC_ENABLE) throw new UnsupportedOperationException();
-        enforcePermission(android.Manifest.permission.ASEC_ACCESS);
-        waitForReady();
-        warnOnNotMounted();
-
-        final NativeDaemonEvent event;
-        try {
-            event = mConnector.execute("asec", "path", id);
-            event.checkCode(VoldResponseCode.AsecPathResult);
-            return event.getMessage();
-        } catch (NativeDaemonConnectorException e) {
-            int code = e.getCode();
-            if (code == VoldResponseCode.OpFailedStorageNotFound) {
-                Slog.i(TAG, String.format("Container '%s' not found", id));
-                return null;
-            } else {
-                throw new IllegalStateException(String.format("Unexpected response code %d", code));
-            }
-        }
-    }
-
-    public String getSecureContainerFilesystemPath(String id) {
-        if (!ASEC_ENABLE) throw new UnsupportedOperationException();
-        enforcePermission(android.Manifest.permission.ASEC_ACCESS);
-        waitForReady();
-        warnOnNotMounted();
-
-        final NativeDaemonEvent event;
-        try {
-            event = mConnector.execute("asec", "fspath", id);
-            event.checkCode(VoldResponseCode.AsecPathResult);
-            return event.getMessage();
-        } catch (NativeDaemonConnectorException e) {
-            int code = e.getCode();
-            if (code == VoldResponseCode.OpFailedStorageNotFound) {
-                Slog.i(TAG, String.format("Container '%s' not found", id));
-                return null;
-            } else {
-                throw new IllegalStateException(String.format("Unexpected response code %d", code));
-            }
-        }
-    }
-
-    @Override
-    public void finishMediaUpdate() {
-        if (Binder.getCallingUid() != Process.SYSTEM_UID) {
-            throw new SecurityException("no permission to call finishMediaUpdate()");
-        }
-        if (mUnmountSignal != null) {
-            mUnmountSignal.countDown();
-        } else {
-            Slog.w(TAG, "Odd, nobody asked to unmount?");
-        }
-    }
-
     private boolean isUidOwnerOfPackageOrSystem(String packageName, int callerUid) {
         if (callerUid == android.os.Process.SYSTEM_UID) {
             return true;
@@ -2762,10 +1953,10 @@
         return callerUid == packageUid;
     }
 
+    @Override
     public String getMountedObbPath(String rawPath) {
         Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
 
-        waitForReady();
         warnOnNotMounted();
 
         final ObbState state;
@@ -2777,23 +1968,7 @@
             return null;
         }
 
-        if (ENABLE_BINDER) {
-            return findVolumeByIdOrThrow(state.volId).getPath().getAbsolutePath();
-        }
-
-        final NativeDaemonEvent event;
-        try {
-            event = mConnector.execute("obb", "path", state.canonicalPath);
-            event.checkCode(VoldResponseCode.AsecPathResult);
-            return event.getMessage();
-        } catch (NativeDaemonConnectorException e) {
-            int code = e.getCode();
-            if (code == VoldResponseCode.OpFailedStorageNotFound) {
-                return null;
-            } else {
-                throw new IllegalStateException(String.format("Unexpected response code %d", code));
-            }
-        }
+        return findVolumeByIdOrThrow(state.volId).getPath().getAbsolutePath();
     }
 
     @Override
@@ -2850,28 +2025,10 @@
         mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
                 "no permission to access the crypt keeper");
 
-        waitForReady();
-
-        if (ENABLE_BINDER) {
-            try {
-                return mVold.fdeComplete();
-            } catch (Exception e) {
-                Slog.wtf(TAG, e);
-                return StorageManager.ENCRYPTION_STATE_ERROR_UNKNOWN;
-            }
-        }
-
-        final NativeDaemonEvent event;
         try {
-            event = mCryptConnector.execute("cryptfs", "cryptocomplete");
-            return Integer.parseInt(event.getMessage());
-        } catch (NumberFormatException e) {
-            // Bad result - unexpected.
-            Slog.w(TAG, "Unable to parse result from cryptfs cryptocomplete");
-            return StorageManager.ENCRYPTION_STATE_ERROR_UNKNOWN;
-        } catch (NativeDaemonConnectorException e) {
-            // Something bad happened.
-            Slog.w(TAG, "Error in communicating with cryptfs in validating");
+            return mVold.fdeComplete();
+        } catch (Exception e) {
+            Slog.wtf(TAG, e);
             return StorageManager.ENCRYPTION_STATE_ERROR_UNKNOWN;
         }
     }
@@ -2881,8 +2038,6 @@
         mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
                 "no permission to access the crypt keeper");
 
-        waitForReady();
-
         if (TextUtils.isEmpty(password)) {
             throw new IllegalArgumentException("password cannot be empty");
         }
@@ -2891,55 +2046,27 @@
             Slog.i(TAG, "decrypting storage...");
         }
 
-        if (ENABLE_BINDER) {
-            try {
-                mVold.fdeCheckPassword(password);
-                mHandler.postDelayed(() -> {
-                    try {
-                        mVold.fdeRestart();
-                    } catch (Exception e) {
-                        Slog.wtf(TAG, e);
-                    }
-                }, DateUtils.SECOND_IN_MILLIS);
-                return 0;
-            } catch (Exception e) {
-                Slog.wtf(TAG, e);
-                return StorageManager.ENCRYPTION_STATE_ERROR_UNKNOWN;
-            }
-        }
-
-        final NativeDaemonEvent event;
         try {
-            event = mCryptConnector.execute("cryptfs", "checkpw", new SensitiveArg(password));
-
-            final int code = Integer.parseInt(event.getMessage());
-            if (code == 0) {
-                // Decrypt was successful. Post a delayed message before restarting in order
-                // to let the UI to clear itself
-                mHandler.postDelayed(new Runnable() {
-                    public void run() {
-                        try {
-                            mCryptConnector.execute("cryptfs", "restart");
-                        } catch (NativeDaemonConnectorException e) {
-                            Slog.e(TAG, "problem executing in background", e);
-                        }
-                    }
-                }, 1000); // 1 second
-            }
-
-            return code;
-        } catch (NativeDaemonConnectorException e) {
-            // Decryption failed
-            return e.getCode();
+            mVold.fdeCheckPassword(password);
+            mHandler.postDelayed(() -> {
+                try {
+                    mVold.fdeRestart();
+                } catch (Exception e) {
+                    Slog.wtf(TAG, e);
+                }
+            }, DateUtils.SECOND_IN_MILLIS);
+            return 0;
+        } catch (Exception e) {
+            Slog.wtf(TAG, e);
+            return StorageManager.ENCRYPTION_STATE_ERROR_UNKNOWN;
         }
     }
 
+    @Override
     public int encryptStorage(int type, String password) {
         mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
             "no permission to access the crypt keeper");
 
-        waitForReady();
-
         if (type == StorageManager.CRYPT_TYPE_DEFAULT) {
             password = "";
         } else if (TextUtils.isEmpty(password)) {
@@ -2951,17 +2078,7 @@
         }
 
         try {
-            if (ENABLE_BINDER) {
-                mVold.fdeEnable(type, password, IVold.ENCRYPTION_FLAG_IN_PLACE);
-            } else {
-                if (type == StorageManager.CRYPT_TYPE_DEFAULT) {
-                    mCryptConnector.execute("cryptfs", "enablecrypto", "inplace",
-                            CRYPTO_TYPES[type]);
-                } else {
-                    mCryptConnector.execute("cryptfs", "enablecrypto", "inplace",
-                            CRYPTO_TYPES[type], new SensitiveArg(password));
-                }
-            }
+            mVold.fdeEnable(type, password, IVold.ENCRYPTION_FLAG_IN_PLACE);
         } catch (Exception e) {
             Slog.wtf(TAG, e);
             return -1;
@@ -2974,12 +2091,11 @@
      *  @param type One of the CRYPTO_TYPE_XXX consts defined in StorageManager.
      *  @param password The password to set.
      */
+    @Override
     public int changeEncryptionPassword(int type, String password) {
         mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
             "no permission to access the crypt keeper");
 
-        waitForReady();
-
         if (type == StorageManager.CRYPT_TYPE_DEFAULT) {
             password = "";
         } else if (TextUtils.isEmpty(password)) {
@@ -2990,23 +2106,12 @@
             Slog.i(TAG, "changing encryption password...");
         }
 
-        if (ENABLE_BINDER) {
-            try {
-                mVold.fdeChangePassword(type, password);
-                return 0;
-            } catch (Exception e) {
-                Slog.wtf(TAG, e);
-                return -1;
-            }
-        }
-
         try {
-            NativeDaemonEvent event = mCryptConnector.execute("cryptfs", "changepw", CRYPTO_TYPES[type],
-                        new SensitiveArg(password));
-            return Integer.parseInt(event.getMessage());
-        } catch (NativeDaemonConnectorException e) {
-            // Encryption failed
-            return e.getCode();
+            mVold.fdeChangePassword(type, password);
+            return 0;
+        } catch (Exception e) {
+            Slog.wtf(TAG, e);
+            return -1;
         }
     }
 
@@ -3027,30 +2132,16 @@
             throw new IllegalArgumentException("password cannot be empty");
         }
 
-        waitForReady();
-
         if (DEBUG_EVENTS) {
             Slog.i(TAG, "validating encryption password...");
         }
 
-        if (ENABLE_BINDER) {
-            try {
-                mVold.fdeVerifyPassword(password);
-                return 0;
-            } catch (Exception e) {
-                Slog.wtf(TAG, e);
-                return -1;
-            }
-        }
-
-        final NativeDaemonEvent event;
         try {
-            event = mCryptConnector.execute("cryptfs", "verifypw", new SensitiveArg(password));
-            Slog.i(TAG, "cryptfs verifypw => " + event.getMessage());
-            return Integer.parseInt(event.getMessage());
-        } catch (NativeDaemonConnectorException e) {
-            // Encryption failed
-            return e.getCode();
+            mVold.fdeVerifyPassword(password);
+            return 0;
+        } catch (Exception e) {
+            Slog.wtf(TAG, e);
+            return -1;
         }
     }
 
@@ -3063,28 +2154,11 @@
         mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
             "no permission to access the crypt keeper");
 
-        waitForReady();
-
-        if (ENABLE_BINDER) {
-            try {
-                return mVold.fdeGetPasswordType();
-            } catch (Exception e) {
-                Slog.wtf(TAG, e);
-                return -1;
-            }
-        }
-
-        final NativeDaemonEvent event;
         try {
-            event = mCryptConnector.execute("cryptfs", "getpwtype");
-            for (int i = 0; i < CRYPTO_TYPES.length; ++i) {
-                if (CRYPTO_TYPES[i].equals(event.getMessage()))
-                    return i;
-            }
-
-            throw new IllegalStateException("unexpected return from cryptfs");
-        } catch (NativeDaemonConnectorException e) {
-            throw e.rethrowAsParcelableException();
+            return mVold.fdeGetPasswordType();
+        } catch (Exception e) {
+            Slog.wtf(TAG, e);
+            return -1;
         }
     }
 
@@ -3098,23 +2172,12 @@
         mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
             "no permission to access the crypt keeper");
 
-        waitForReady();
-
-        if (ENABLE_BINDER) {
-            try {
-                mVold.fdeSetField(field, contents);
-                return;
-            } catch (Exception e) {
-                Slog.wtf(TAG, e);
-                return;
-            }
-        }
-
-        final NativeDaemonEvent event;
         try {
-            event = mCryptConnector.execute("cryptfs", "setfield", field, contents);
-        } catch (NativeDaemonConnectorException e) {
-            throw e.rethrowAsParcelableException();
+            mVold.fdeSetField(field, contents);
+            return;
+        } catch (Exception e) {
+            Slog.wtf(TAG, e);
+            return;
         }
     }
 
@@ -3128,29 +2191,11 @@
         mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
             "no permission to access the crypt keeper");
 
-        waitForReady();
-
-        if (ENABLE_BINDER) {
-            try {
-                return mVold.fdeGetField(field);
-            } catch (Exception e) {
-                Slog.wtf(TAG, e);
-                return null;
-            }
-        }
-
-        final NativeDaemonEvent event;
         try {
-            final String[] contents = NativeDaemonEvent.filterMessageList(
-                    mCryptConnector.executeForList("cryptfs", "getfield", field),
-                    VoldResponseCode.CryptfsGetfieldResult);
-            String result = new String();
-            for (String content : contents) {
-                result += content;
-            }
-            return result;
-        } catch (NativeDaemonConnectorException e) {
-            throw e.rethrowAsParcelableException();
+            return mVold.fdeGetField(field);
+        } catch (Exception e) {
+            Slog.wtf(TAG, e);
+            return null;
         }
     }
 
@@ -3163,23 +2208,11 @@
         mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
             "no permission to access the crypt keeper");
 
-        waitForReady();
-
-        if (ENABLE_BINDER) {
-            try {
-                return mVold.isConvertibleToFbe();
-            } catch (Exception e) {
-                Slog.wtf(TAG, e);
-                return false;
-            }
-        }
-
-        final NativeDaemonEvent event;
         try {
-            event = mCryptConnector.execute("cryptfs", "isConvertibleToFBE");
-            return Integer.parseInt(event.getMessage()) != 0;
-        } catch (NativeDaemonConnectorException e) {
-            throw e.rethrowAsParcelableException();
+            return mVold.isConvertibleToFbe();
+        } catch (Exception e) {
+            Slog.wtf(TAG, e);
+            return false;
         }
     }
 
@@ -3188,31 +2221,10 @@
         mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
                 "only keyguard can retrieve password");
 
-        if (!isReady()) {
-            return new String();
-        }
-
-        if (ENABLE_BINDER) {
-            try {
-                return mVold.fdeGetPassword();
-            } catch (Exception e) {
-                Slog.wtf(TAG, e);
-                return null;
-            }
-        }
-
-        final NativeDaemonEvent event;
         try {
-            event = mCryptConnector.execute("cryptfs", "getpw");
-            if ("-1".equals(event.getMessage())) {
-                // -1 equals no password
-                return null;
-            }
-            return event.getMessage();
-        } catch (NativeDaemonConnectorException e) {
-            throw e.rethrowAsParcelableException();
-        } catch (IllegalArgumentException e) {
-            Slog.e(TAG, "Invalid response to getPassword");
+            return mVold.fdeGetPassword();
+        } catch (Exception e) {
+            Slog.wtf(TAG, e);
             return null;
         }
     }
@@ -3222,40 +2234,21 @@
         mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
                 "only keyguard can clear password");
 
-        if (!isReady()) {
-            return;
-        }
-
-        if (ENABLE_BINDER) {
-            try {
-                mVold.fdeClearPassword();
-                return;
-            } catch (Exception e) {
-                Slog.wtf(TAG, e);
-                return;
-            }
-        }
-
-        final NativeDaemonEvent event;
         try {
-            event = mCryptConnector.execute("cryptfs", "clearpw");
-        } catch (NativeDaemonConnectorException e) {
-            throw e.rethrowAsParcelableException();
+            mVold.fdeClearPassword();
+            return;
+        } catch (Exception e) {
+            Slog.wtf(TAG, e);
+            return;
         }
     }
 
     @Override
     public void createUserKey(int userId, int serialNumber, boolean ephemeral) {
         enforcePermission(android.Manifest.permission.STORAGE_INTERNAL);
-        waitForReady();
 
         try {
-            if (ENABLE_BINDER) {
-                mVold.createUserKey(userId, serialNumber, ephemeral);
-            } else {
-                mCryptConnector.execute("cryptfs", "create_user_key", userId, serialNumber,
-                        ephemeral ? 1 : 0);
-            }
+            mVold.createUserKey(userId, serialNumber, ephemeral);
         } catch (Exception e) {
             Slog.wtf(TAG, e);
         }
@@ -3264,14 +2257,9 @@
     @Override
     public void destroyUserKey(int userId) {
         enforcePermission(android.Manifest.permission.STORAGE_INTERNAL);
-        waitForReady();
 
         try {
-            if (ENABLE_BINDER) {
-                mVold.destroyUserKey(userId);
-            } else {
-                mCryptConnector.execute("cryptfs", "destroy_user_key", userId);
-            }
+            mVold.destroyUserKey(userId);
         } catch (Exception e) {
             Slog.wtf(TAG, e);
         }
@@ -3295,16 +2283,9 @@
     @Override
     public void addUserKeyAuth(int userId, int serialNumber, byte[] token, byte[] secret) {
         enforcePermission(android.Manifest.permission.STORAGE_INTERNAL);
-        waitForReady();
 
         try {
-            if (ENABLE_BINDER) {
-                mVold.addUserKeyAuth(userId, serialNumber, encodeBytes(token), encodeBytes(secret));
-            } else {
-                mCryptConnector.execute("cryptfs", "add_user_key_auth", userId, serialNumber,
-                        new SensitiveArg(encodeBytes(token)),
-                        new SensitiveArg(encodeBytes(secret)));
-            }
+            mVold.addUserKeyAuth(userId, serialNumber, encodeBytes(token), encodeBytes(secret));
         } catch (Exception e) {
             Slog.wtf(TAG, e);
         }
@@ -3316,14 +2297,9 @@
     @Override
     public void fixateNewestUserKeyAuth(int userId) {
         enforcePermission(android.Manifest.permission.STORAGE_INTERNAL);
-        waitForReady();
 
         try {
-            if (ENABLE_BINDER) {
-                mVold.fixateNewestUserKeyAuth(userId);
-            } else {
-                mCryptConnector.execute("cryptfs", "fixate_newest_user_key_auth", userId);
-            }
+            mVold.fixateNewestUserKeyAuth(userId);
         } catch (Exception e) {
             Slog.wtf(TAG, e);
         }
@@ -3332,7 +2308,6 @@
     @Override
     public void unlockUserKey(int userId, int serialNumber, byte[] token, byte[] secret) {
         enforcePermission(android.Manifest.permission.STORAGE_INTERNAL);
-        waitForReady();
 
         if (StorageManager.isFileEncryptedNativeOrEmulated()) {
             // When a user has secure lock screen, require secret to actually unlock.
@@ -3342,14 +2317,8 @@
             }
 
             try {
-                if (ENABLE_BINDER) {
-                    mVold.unlockUserKey(userId, serialNumber, encodeBytes(token),
-                            encodeBytes(secret));
-                } else {
-                    mCryptConnector.execute("cryptfs", "unlock_user_key", userId, serialNumber,
-                            new SensitiveArg(encodeBytes(token)),
-                            new SensitiveArg(encodeBytes(secret)));
-                }
+                mVold.unlockUserKey(userId, serialNumber, encodeBytes(token),
+                        encodeBytes(secret));
             } catch (Exception e) {
                 Slog.wtf(TAG, e);
                 return;
@@ -3369,14 +2338,9 @@
     @Override
     public void lockUserKey(int userId) {
         enforcePermission(android.Manifest.permission.STORAGE_INTERNAL);
-        waitForReady();
 
         try {
-            if (ENABLE_BINDER) {
-                mVold.lockUserKey(userId);
-            } else {
-                mCryptConnector.execute("cryptfs", "lock_user_key", userId);
-            }
+            mVold.lockUserKey(userId);
         } catch (Exception e) {
             Slog.wtf(TAG, e);
             return;
@@ -3397,15 +2361,9 @@
     @Override
     public void prepareUserStorage(String volumeUuid, int userId, int serialNumber, int flags) {
         enforcePermission(android.Manifest.permission.STORAGE_INTERNAL);
-        waitForReady();
 
         try {
-            if (ENABLE_BINDER) {
-                mVold.prepareUserStorage(volumeUuid, userId, serialNumber, flags);
-            } else {
-                mCryptConnector.execute("cryptfs", "prepare_user_storage", escapeNull(volumeUuid),
-                        userId, serialNumber, flags);
-            }
+            mVold.prepareUserStorage(volumeUuid, userId, serialNumber, flags);
         } catch (Exception e) {
             Slog.wtf(TAG, e);
         }
@@ -3414,15 +2372,9 @@
     @Override
     public void destroyUserStorage(String volumeUuid, int userId, int flags) {
         enforcePermission(android.Manifest.permission.STORAGE_INTERNAL);
-        waitForReady();
 
         try {
-            if (ENABLE_BINDER) {
-                mVold.destroyUserStorage(volumeUuid, userId, flags);
-            } else {
-                mCryptConnector.execute("cryptfs", "destroy_user_storage", escapeNull(volumeUuid),
-                        userId, flags);
-            }
+            mVold.destroyUserStorage(volumeUuid, userId, flags);
         } catch (Exception e) {
             Slog.wtf(TAG, e);
         }
@@ -3431,14 +2383,9 @@
     @Override
     public void secdiscard(String path) {
         enforcePermission(android.Manifest.permission.STORAGE_INTERNAL);
-        waitForReady();
 
         try {
-            if (ENABLE_BINDER) {
-                mVold.secdiscard(path);
-            } else {
-                mCryptConnector.execute("cryptfs", "secdiscard", escapeNull(path));
-            }
+            mVold.secdiscard(path);
         } catch (Exception e) {
             Slog.wtf(TAG, e);
         }
@@ -3453,33 +2400,18 @@
 
         @Override
         public ParcelFileDescriptor open() throws NativeDaemonConnectorException {
-            if (ENABLE_BINDER) {
-                try {
-                    return new ParcelFileDescriptor(
-                            mVold.mountAppFuse(uid, Process.myPid(), mountId));
-                } catch (Exception e) {
-                    throw new NativeDaemonConnectorException("Failed to mount", e);
-                }
-            } else {
-                final NativeDaemonEvent event = mConnector.execute(
-                        "appfuse", "mount", uid, Process.myPid(), mountId);
-                opened = true;
-                if (event.getFileDescriptors() == null ||
-                    event.getFileDescriptors().length == 0) {
-                    throw new NativeDaemonConnectorException("Cannot obtain device FD");
-                }
-                return new ParcelFileDescriptor(event.getFileDescriptors()[0]);
+            try {
+                return new ParcelFileDescriptor(
+                        mVold.mountAppFuse(uid, Process.myPid(), mountId));
+            } catch (Exception e) {
+                throw new NativeDaemonConnectorException("Failed to mount", e);
             }
         }
 
         @Override
         public void close() throws Exception {
             if (opened) {
-                if (ENABLE_BINDER) {
-                    mVold.unmountAppFuse(uid, Process.myPid(), mountId);
-                } else {
-                    mConnector.execute("appfuse", "unmount", uid, Process.myPid(), mountId);
-                }
+                mVold.unmountAppFuse(uid, Process.myPid(), mountId);
                 opened = false;
             }
         }
@@ -3568,11 +2500,7 @@
             }
 
             try {
-                if (ENABLE_BINDER) {
-                    mVold.mkdirs(appPath);
-                } else {
-                    mConnector.execute("volume", "mkdirs", appPath);
-                }
+                mVold.mkdirs(appPath);
                 return 0;
             } catch (Exception e) {
                 Slog.wtf(TAG, e);
@@ -4124,7 +3052,6 @@
 
         @Override
         public void handleExecute() throws IOException, RemoteException {
-            waitForReady();
             warnOnNotMounted();
 
             final ObbInfo obbInfo = getObbInfo();
@@ -4174,19 +3101,9 @@
 
             int rc = StorageResultCode.OperationSucceeded;
             try {
-                if (ENABLE_BINDER) {
-                    mObbState.volId = mVold.createObb(mObbState.canonicalPath, binderKey,
-                            mObbState.ownerGid);
-                    mVold.mount(mObbState.volId, 0, -1);
-                } else {
-                    mConnector.execute("obb", "mount", mObbState.canonicalPath,
-                            new SensitiveArg(hashedKey), mObbState.ownerGid);
-                }
-            } catch (NativeDaemonConnectorException e) {
-                int code = e.getCode();
-                if (code != VoldResponseCode.OpFailedStorageBusy) {
-                    rc = StorageResultCode.OperationFailedInternalError;
-                }
+                mObbState.volId = mVold.createObb(mObbState.canonicalPath, binderKey,
+                        mObbState.ownerGid);
+                mVold.mount(mObbState.volId, 0, -1);
             } catch (Exception e) {
                 Slog.w(TAG, e);
                 rc = StorageResultCode.OperationFailedInternalError;
@@ -4233,7 +3150,6 @@
 
         @Override
         public void handleExecute() throws IOException {
-            waitForReady();
             warnOnNotMounted();
 
             final ObbState existingState;
@@ -4255,27 +3171,9 @@
 
             int rc = StorageResultCode.OperationSucceeded;
             try {
-                if (ENABLE_BINDER) {
-                    mVold.unmount(mObbState.volId);
-                    mVold.destroyObb(mObbState.volId);
-                    mObbState.volId = null;
-                } else {
-                    final Command cmd = new Command("obb", "unmount", mObbState.canonicalPath);
-                    if (mForceUnmount) {
-                        cmd.appendArg("force");
-                    }
-                    mConnector.execute(cmd);
-                }
-            } catch (NativeDaemonConnectorException e) {
-                int code = e.getCode();
-                if (code == VoldResponseCode.OpFailedStorageBusy) {
-                    rc = StorageResultCode.OperationFailedStorageBusy;
-                } else if (code == VoldResponseCode.OpFailedStorageNotFound) {
-                    // If it's not mounted then we've already won.
-                    rc = StorageResultCode.OperationSucceeded;
-                } else {
-                    rc = StorageResultCode.OperationFailedInternalError;
-                }
+                mVold.unmount(mObbState.volId);
+                mVold.destroyObb(mObbState.volId);
+                mObbState.volId = null;
             } catch (Exception e) {
                 Slog.w(TAG, e);
                 rc = StorageResultCode.OperationFailedInternalError;
@@ -4506,18 +3404,6 @@
         }
 
         pw.println();
-        pw.println("mConnector:");
-        pw.increaseIndent();
-        mConnector.dump(fd, pw, args);
-        pw.decreaseIndent();
-
-        pw.println();
-        pw.println("mCryptConnector:");
-        pw.increaseIndent();
-        mCryptConnector.dump(fd, pw, args);
-        pw.decreaseIndent();
-
-        pw.println();
         pw.print("Last maintenance: ");
         pw.println(TimeUtils.formatForLogging(mLastMaintenance));
     }
@@ -4525,11 +3411,10 @@
     /** {@inheritDoc} */
     @Override
     public void monitor() {
-        if (mConnector != null) {
-            mConnector.monitor();
-        }
-        if (mCryptConnector != null) {
-            mCryptConnector.monitor();
+        try {
+            mVold.monitor();
+        } catch (Exception e) {
+            Slog.wtf(TAG, e);
         }
     }
 
diff --git a/com/android/server/SystemServer.java b/com/android/server/SystemServer.java
index 57271fa..92cbd3d 100644
--- a/com/android/server/SystemServer.java
+++ b/com/android/server/SystemServer.java
@@ -103,6 +103,7 @@
 import com.android.server.security.KeyAttestationApplicationIdProviderService;
 import com.android.server.security.KeyChainSystemService;
 import com.android.server.soundtrigger.SoundTriggerService;
+import com.android.server.stats.StatsCompanionService;
 import com.android.server.statusbar.StatusBarManagerService;
 import com.android.server.storage.DeviceStorageMonitorService;
 import com.android.server.telecom.TelecomLoaderService;
@@ -151,7 +152,7 @@
      * them from the build system somehow.
      */
     private static final String BACKUP_MANAGER_SERVICE_CLASS =
-            "com.android.server.backup.BackupManagerService$Lifecycle";
+            "com.android.server.backup.RefactoredBackupManagerService$Lifecycle";
     private static final String APPWIDGET_SERVICE_CLASS =
             "com.android.server.appwidget.AppWidgetService";
     private static final String VOICE_RECOGNITION_MANAGER_SERVICE_CLASS =
@@ -667,9 +668,11 @@
         traceEnd();
 
         // Tracks whether the updatable WebView is in a ready state and watches for update installs.
-        traceBeginAndSlog("StartWebViewUpdateService");
-        mWebViewUpdateService = mSystemServiceManager.startService(WebViewUpdateService.class);
-        traceEnd();
+        if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_WEBVIEW)) {
+            traceBeginAndSlog("StartWebViewUpdateService");
+            mWebViewUpdateService = mSystemServiceManager.startService(WebViewUpdateService.class);
+            traceEnd();
+        }
     }
 
     /**
@@ -681,6 +684,7 @@
         VibratorService vibrator = null;
         IStorageManager storageManager = null;
         NetworkManagementService networkManagement = null;
+        IpSecService ipSecService = null;
         NetworkStatsService networkStats = null;
         NetworkPolicyManagerService networkPolicy = null;
         ConnectivityService connectivity = null;
@@ -1030,6 +1034,15 @@
                     reportWtf("starting NetworkManagement Service", e);
                 }
                 traceEnd();
+
+                traceBeginAndSlog("StartIpSecService");
+                try {
+                    ipSecService = IpSecService.create(context);
+                    ServiceManager.addService(Context.IPSEC_SERVICE, ipSecService);
+                } catch (Throwable e) {
+                    reportWtf("starting IpSec Service", e);
+                }
+                traceEnd();
             }
 
             if (!disableNonCoreServices && !disableTextServices) {
@@ -1080,6 +1093,14 @@
                     traceBeginAndSlog("StartWifiRtt");
                     mSystemServiceManager.startService("com.android.server.wifi.RttService");
                     traceEnd();
+
+                    if (context.getPackageManager().hasSystemFeature(
+                            PackageManager.FEATURE_WIFI_RTT)) {
+                        traceBeginAndSlog("StartRttService");
+                        mSystemServiceManager.startService(
+                                "com.android.server.wifi.rtt.RttService");
+                        traceEnd();
+                    }
                 }
 
                 if (context.getPackageManager().hasSystemFeature(
@@ -1087,8 +1108,6 @@
                     traceBeginAndSlog("StartWifiAware");
                     mSystemServiceManager.startService(WIFI_AWARE_SERVICE_CLASS);
                     traceEnd();
-                } else {
-                    Slog.i(TAG, "No Wi-Fi Aware Service (Aware support Not Present)");
                 }
 
                 if (context.getPackageManager().hasSystemFeature(
@@ -1146,20 +1165,6 @@
                 traceEnd();
             }
 
-            /*
-             * StorageManagerService has a few dependencies: Notification Manager and
-             * AppWidget Provider. Make sure StorageManagerService is completely started
-             * first before continuing.
-             */
-            if (storageManager != null && !mOnlyCore) {
-                traceBeginAndSlog("WaitForAsecScan");
-                try {
-                    storageManager.waitForAsecScan();
-                } catch (RemoteException ignored) {
-                }
-                traceEnd();
-            }
-
             traceBeginAndSlog("StartNotificationManager");
             mSystemServiceManager.startService(NotificationManagerService.class);
             SystemNotificationChannels.createAll(context);
@@ -1209,18 +1214,6 @@
                 traceEnd();
             }
 
-            // timezone.RulesManagerService will prevent a device starting up if the chain of trust
-            // required for safe time zone updates might be broken. RuleManagerService cannot do
-            // this check when mOnlyCore == true, so we don't enable the service in this case.
-            final boolean startRulesManagerService =
-                    !mOnlyCore && context.getResources().getBoolean(
-                            R.bool.config_enableUpdateableTimeZoneRules);
-            if (startRulesManagerService) {
-                traceBeginAndSlog("StartTimeZoneRulesManagerService");
-                mSystemServiceManager.startService(TIME_ZONE_RULES_MANAGER_SERVICE_CLASS);
-                traceEnd();
-            }
-
             traceBeginAndSlog("StartAudioService");
             mSystemServiceManager.startService(AudioService.Lifecycle.class);
             traceEnd();
@@ -1361,6 +1354,19 @@
             }
             traceEnd();
 
+            // timezone.RulesManagerService will prevent a device starting up if the chain of trust
+            // required for safe time zone updates might be broken. RuleManagerService cannot do
+            // this check when mOnlyCore == true, so we don't enable the service in this case.
+            // This service requires that JobSchedulerService is already started when it starts.
+            final boolean startRulesManagerService =
+                    !mOnlyCore && context.getResources().getBoolean(
+                            R.bool.config_enableUpdateableTimeZoneRules);
+            if (startRulesManagerService) {
+                traceBeginAndSlog("StartTimeZoneRulesManagerService");
+                mSystemServiceManager.startService(TIME_ZONE_RULES_MANAGER_SERVICE_CLASS);
+                traceEnd();
+            }
+
             if (!disableNetwork && !disableNetworkTime) {
                 traceBeginAndSlog("StartNetworkTimeUpdateService");
                 try {
@@ -1536,6 +1542,11 @@
             traceEnd();
         }
 
+        // Statsd helper
+        traceBeginAndSlog("StartStatsCompanionService");
+        mSystemServiceManager.startService(StatsCompanionService.Lifecycle.class);
+        traceEnd();
+
         // Before things start rolling, be sure we have decided whether
         // we are in safe mode.
         final boolean safeMode = wm.detectSafeMode();
@@ -1661,6 +1672,7 @@
         final TelephonyRegistry telephonyRegistryF = telephonyRegistry;
         final MediaRouterService mediaRouterF = mediaRouter;
         final MmsServiceBroker mmsServiceF = mmsService;
+        final IpSecService ipSecServiceF = ipSecService;
         final WindowManagerService windowManagerF = wm;
 
         // We now tell the activity manager it is okay to run third party
@@ -1683,10 +1695,10 @@
             traceEnd();
 
             // No dependency on Webview preparation in system server. But this should
-            // be completed before allowring 3rd party
+            // be completed before allowing 3rd party
             final String WEBVIEW_PREPARATION = "WebViewFactoryPreparation";
             Future<?> webviewPrep = null;
-            if (!mOnlyCore) {
+            if (!mOnlyCore && mWebViewUpdateService != null) {
                 webviewPrep = SystemServerInitThreadPool.get().submit(() -> {
                     Slog.i(TAG, WEBVIEW_PREPARATION);
                     TimingsTraceLog traceLog = new TimingsTraceLog(
@@ -1731,6 +1743,13 @@
                         .networkScoreAndNetworkManagementServiceReady();
             }
             traceEnd();
+            traceBeginAndSlog("MakeIpSecServiceReady");
+            try {
+                if (ipSecServiceF != null) ipSecServiceF.systemReady();
+            } catch (Throwable e) {
+                reportWtf("making IpSec Service ready", e);
+            }
+            traceEnd();
             traceBeginAndSlog("MakeNetworkStatsServiceReady");
             try {
                 if (networkStatsF != null) networkStatsF.systemReady();
diff --git a/com/android/server/accounts/AccountManagerService.java b/com/android/server/accounts/AccountManagerService.java
index 8ae592f..4e15e5d 100644
--- a/com/android/server/accounts/AccountManagerService.java
+++ b/com/android/server/accounts/AccountManagerService.java
@@ -2969,9 +2969,13 @@
                              * have users launching arbitrary activities by tricking users to
                              * interact with malicious notifications.
                              */
-                            checkKeyIntent(
+                            if (!checkKeyIntent(
                                     Binder.getCallingUid(),
-                                    intent);
+                                    intent)) {
+                                onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,
+                                        "invalid intent in bundle returned");
+                                return;
+                            }
                             doNotification(
                                     mAccounts,
                                     account,
@@ -3366,9 +3370,13 @@
             Intent intent = null;
             if (result != null
                     && (intent = result.getParcelable(AccountManager.KEY_INTENT)) != null) {
-                checkKeyIntent(
+                if (!checkKeyIntent(
                         Binder.getCallingUid(),
-                        intent);
+                        intent)) {
+                    onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,
+                            "invalid intent in bundle returned");
+                    return;
+                }
             }
             IAccountManagerResponse response;
             if (mExpectActivityLaunch && result != null
@@ -4716,9 +4724,7 @@
          * into launching arbitrary intents on the device via by tricking to click authenticator
          * supplied entries in the system Settings app.
          */
-        protected void checkKeyIntent(
-                int authUid,
-                Intent intent) throws SecurityException {
+         protected boolean checkKeyIntent(int authUid, Intent intent) {
             intent.setFlags(intent.getFlags() & ~(Intent.FLAG_GRANT_READ_URI_PERMISSION
                     | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
                     | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
@@ -4727,6 +4733,9 @@
             try {
                 PackageManager pm = mContext.getPackageManager();
                 ResolveInfo resolveInfo = pm.resolveActivityAsUser(intent, 0, mAccounts.userId);
+                if (resolveInfo == null) {
+                    return false;
+                }
                 ActivityInfo targetActivityInfo = resolveInfo.activityInfo;
                 int targetUid = targetActivityInfo.applicationInfo.uid;
                 if (!isExportedSystemActivity(targetActivityInfo)
@@ -4736,9 +4745,10 @@
                     String activityName = targetActivityInfo.name;
                     String tmpl = "KEY_INTENT resolved to an Activity (%s) in a package (%s) that "
                             + "does not share a signature with the supplying authenticator (%s).";
-                    throw new SecurityException(
-                            String.format(tmpl, activityName, pkgName, mAccountType));
+                    Log.e(TAG, String.format(tmpl, activityName, pkgName, mAccountType));
+                    return false;
                 }
+                return true;
             } finally {
                 Binder.restoreCallingIdentity(bid);
             }
@@ -4888,9 +4898,13 @@
             }
             if (result != null
                     && (intent = result.getParcelable(AccountManager.KEY_INTENT)) != null) {
-                checkKeyIntent(
+                if (!checkKeyIntent(
                         Binder.getCallingUid(),
-                        intent);
+                        intent)) {
+                    onError(AccountManager.ERROR_CODE_INVALID_RESPONSE,
+                            "invalid intent in bundle returned");
+                    return;
+                }
             }
             if (result != null
                     && !TextUtils.isEmpty(result.getString(AccountManager.KEY_AUTHTOKEN))) {
@@ -5285,7 +5299,7 @@
                         == PackageManager.PERMISSION_GRANTED) {
                     // Checks runtime permission revocation.
                     final int opCode = AppOpsManager.permissionToOpCode(perm);
-                    if (opCode == AppOpsManager.OP_NONE || mAppOpsManager.noteOp(
+                    if (opCode == AppOpsManager.OP_NONE || mAppOpsManager.noteOpNoThrow(
                             opCode, uid, packageName) == AppOpsManager.MODE_ALLOWED) {
                         return true;
                     }
@@ -5306,7 +5320,7 @@
                     Log.v(TAG, "  caller uid " + callingUid + " has " + perm);
                 }
                 final int opCode = AppOpsManager.permissionToOpCode(perm);
-                if (opCode == AppOpsManager.OP_NONE || mAppOpsManager.noteOp(
+                if (opCode == AppOpsManager.OP_NONE || mAppOpsManager.noteOpNoThrow(
                         opCode, callingUid, opPackageName) == AppOpsManager.MODE_ALLOWED) {
                     return true;
                 }
diff --git a/com/android/server/am/ActiveServices.java b/com/android/server/am/ActiveServices.java
index e0cde72..2131731 100644
--- a/com/android/server/am/ActiveServices.java
+++ b/com/android/server/am/ActiveServices.java
@@ -2162,6 +2162,15 @@
             }
         }
 
+        if (r.fgRequired) {
+            if (DEBUG_FOREGROUND_SERVICE) {
+                Slog.v(TAG, "Whitelisting " + UserHandle.formatUid(r.appInfo.uid)
+                        + " for fg-service launch");
+            }
+            mAm.tempWhitelistUidLocked(r.appInfo.uid,
+                    SERVICE_START_FOREGROUND_TIMEOUT, "fg-service-launch");
+        }
+
         if (!mPendingServices.contains(r)) {
             mPendingServices.add(r);
         }
@@ -3141,7 +3150,7 @@
                         sr.userId, sr.crashCount, sr.shortName, app.pid);
                 bringDownServiceLocked(sr);
             } else if (!allowRestart
-                    || !mAm.mUserController.isUserRunningLocked(sr.userId, 0)) {
+                    || !mAm.mUserController.isUserRunning(sr.userId, 0)) {
                 bringDownServiceLocked(sr);
             } else {
                 boolean canceled = scheduleServiceRestartLocked(sr, true);
diff --git a/com/android/server/am/ActivityDisplay.java b/com/android/server/am/ActivityDisplay.java
new file mode 100644
index 0000000..8bcbfbe
--- /dev/null
+++ b/com/android/server/am/ActivityDisplay.java
@@ -0,0 +1,414 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.am;
+
+import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+import static android.app.ActivityManager.StackId.getStackIdForWindowingMode;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.FLAG_PRIVATE;
+import static android.view.Display.REMOVE_MODE_DESTROY_CONTENT;
+import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_STACK;
+import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_STACK;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
+import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.am.proto.ActivityDisplayProto.CONFIGURATION_CONTAINER;
+import static com.android.server.am.proto.ActivityDisplayProto.STACKS;
+import static com.android.server.am.proto.ActivityDisplayProto.ID;
+
+import android.app.ActivityManagerInternal;
+import android.app.WindowConfiguration;
+import android.util.IntArray;
+import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
+import android.view.Display;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.wm.ConfigurationContainer;
+
+import java.util.ArrayList;
+
+/**
+ * Exactly one of these classes per Display in the system. Capable of holding zero or more
+ * attached {@link ActivityStack}s.
+ */
+class ActivityDisplay extends ConfigurationContainer {
+    private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityDisplay" : TAG_AM;
+    private static final String TAG_STACK = TAG + POSTFIX_STACK;
+
+    static final int POSITION_TOP = Integer.MAX_VALUE;
+    static final int POSITION_BOTTOM = Integer.MIN_VALUE;
+
+    private ActivityStackSupervisor mSupervisor;
+    /** Actual Display this object tracks. */
+    int mDisplayId;
+    Display mDisplay;
+
+    /** All of the stacks on this display. Order matters, topmost stack is in front of all other
+     * stacks, bottommost behind. Accessed directly by ActivityManager package classes */
+    final ArrayList<ActivityStack> mStacks = new ArrayList<>();
+
+    /** Array of all UIDs that are present on the display. */
+    private IntArray mDisplayAccessUIDs = new IntArray();
+
+    /** All tokens used to put activities on this stack to sleep (including mOffToken) */
+    final ArrayList<ActivityManagerInternal.SleepToken> mAllSleepTokens = new ArrayList<>();
+    /** The token acquired by ActivityStackSupervisor to put stacks on the display to sleep */
+    ActivityManagerInternal.SleepToken mOffToken;
+
+    private boolean mSleeping;
+
+    ActivityDisplay(ActivityStackSupervisor supervisor, int displayId) {
+        mSupervisor = supervisor;
+        mDisplayId = displayId;
+        final Display display = supervisor.mDisplayManager.getDisplay(displayId);
+        if (display == null) {
+            throw new IllegalStateException("Display does not exist displayId=" + displayId);
+        }
+        mDisplay = display;
+    }
+
+    void addChild(ActivityStack stack, int position) {
+        if (position == POSITION_BOTTOM) {
+            position = 0;
+        } else if (position == POSITION_TOP) {
+            position = mStacks.size();
+        }
+        if (DEBUG_STACK) Slog.v(TAG_STACK, "addChild: attaching " + stack
+                + " to displayId=" + mDisplayId + " position=" + position);
+        positionChildAt(stack, position);
+        mSupervisor.mService.updateSleepIfNeededLocked();
+    }
+
+    void removeChild(ActivityStack stack) {
+        if (DEBUG_STACK) Slog.v(TAG_STACK, "removeChild: detaching " + stack
+                + " from displayId=" + mDisplayId);
+        mStacks.remove(stack);
+        mSupervisor.mService.updateSleepIfNeededLocked();
+    }
+
+    void positionChildAtTop(ActivityStack stack) {
+        positionChildAt(stack, mStacks.size());
+    }
+
+    void positionChildAtBottom(ActivityStack stack) {
+        positionChildAt(stack, 0);
+    }
+
+    private void positionChildAt(ActivityStack stack, int position) {
+        mStacks.remove(stack);
+        mStacks.add(getTopInsertPosition(stack, position), stack);
+    }
+
+    private int getTopInsertPosition(ActivityStack stack, int candidatePosition) {
+        int position = mStacks.size();
+        if (position > 0) {
+            final ActivityStack topStack = mStacks.get(position - 1);
+            if (topStack.getWindowConfiguration().isAlwaysOnTop() && topStack != stack) {
+                // If the top stack is always on top, we move this stack just below it.
+                position--;
+            }
+        }
+        return Math.min(position, candidatePosition);
+    }
+
+    <T extends ActivityStack> T getStack(int stackId) {
+        for (int i = mStacks.size() - 1; i >= 0; --i) {
+            final ActivityStack stack = mStacks.get(i);
+            if (stack.mStackId == stackId) {
+                return (T) stack;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * @return the topmost stack on the display that is compatible with the input windowing mode and
+     * activity type. {@code null} means no compatible stack on the display.
+     * @see ConfigurationContainer#isCompatible(int, int)
+     */
+    <T extends ActivityStack> T getStack(int windowingMode, int activityType) {
+        for (int i = mStacks.size() - 1; i >= 0; --i) {
+            final ActivityStack stack = mStacks.get(i);
+            // TODO: Should undefined windowing and activity type be compatible with standard type?
+            if (stack.isCompatible(windowingMode, activityType)) {
+                return (T) stack;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * @see #getStack(int, int)
+     * @see #createStack(int, int, boolean)
+     */
+    <T extends ActivityStack> T getOrCreateStack(int windowingMode, int activityType,
+            boolean onTop) {
+        T stack = getStack(windowingMode, activityType);
+        if (stack != null) {
+            return stack;
+        }
+        return createStack(windowingMode, activityType, onTop);
+    }
+
+    /**
+     * Creates a stack matching the input windowing mode and activity type on this display.
+     * @param windowingMode The windowing mode the stack should be created in. If
+     *                      {@link WindowConfiguration#WINDOWING_MODE_UNDEFINED} then the stack will
+     *                      be created in {@link WindowConfiguration#WINDOWING_MODE_FULLSCREEN}.
+     * @param activityType The activityType the stack should be created in. If
+     *                     {@link WindowConfiguration#ACTIVITY_TYPE_UNDEFINED} then the stack will
+     *                     be created in {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD}.
+     * @param onTop If true the stack will be created at the top of the display, else at the bottom.
+     * @return The newly created stack.
+     */
+    <T extends ActivityStack> T createStack(int windowingMode, int activityType, boolean onTop) {
+
+        if (activityType == ACTIVITY_TYPE_UNDEFINED) {
+            // Can't have an undefined stack type yet...so re-map to standard. Anyone that wants
+            // anything else should be passing it in anyways...
+            activityType = ACTIVITY_TYPE_STANDARD;
+        }
+
+        if (activityType != ACTIVITY_TYPE_STANDARD) {
+            // For now there can be only one stack of a particular non-standard activity type on a
+            // display. So, get that ignoring whatever windowing mode it is currently in.
+            T stack = getStack(WINDOWING_MODE_UNDEFINED, activityType);
+            if (stack != null) {
+                throw new IllegalArgumentException("Stack=" + stack + " of activityType="
+                        + activityType + " already on display=" + this + ". Can't have multiple.");
+            }
+        }
+
+        final ActivityManagerService service = mSupervisor.mService;
+        if (!mSupervisor.isWindowingModeSupported(windowingMode, service.mSupportsMultiWindow,
+                service.mSupportsSplitScreenMultiWindow, service.mSupportsFreeformWindowManagement,
+                service.mSupportsPictureInPicture, activityType)) {
+            throw new IllegalArgumentException("Can't create stack for unsupported windowingMode="
+                    + windowingMode);
+        }
+
+        if (windowingMode == WINDOWING_MODE_UNDEFINED) {
+            // TODO: Should be okay to have stacks with with undefined windowing mode long term, but
+            // have to set them to something for now due to logic that depending on them.
+            windowingMode = WINDOWING_MODE_FULLSCREEN;
+        }
+
+        final boolean inSplitScreenMode = hasSplitScreenStack();
+        if (!inSplitScreenMode
+                && windowingMode == WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY) {
+            // Switch to fullscreen windowing mode if we are not in split-screen mode and we are
+            // trying to launch in split-screen secondary.
+            windowingMode = WINDOWING_MODE_FULLSCREEN;
+        } else if (inSplitScreenMode && windowingMode == WINDOWING_MODE_FULLSCREEN
+                && WindowConfiguration.supportSplitScreenWindowingMode(
+                        windowingMode, activityType)) {
+            windowingMode = WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+        }
+
+        int stackId = INVALID_STACK_ID;
+        if (mDisplayId == DEFAULT_DISPLAY && (activityType == ACTIVITY_TYPE_STANDARD
+                || activityType == ACTIVITY_TYPE_UNDEFINED)) {
+            // TODO: Will be removed once we are no longer using static stack ids.
+            stackId = getStackIdForWindowingMode(windowingMode);
+            if (stackId == INVALID_STACK_ID) {
+                // Whatever...put in fullscreen stack for now.
+                stackId = FULLSCREEN_WORKSPACE_STACK_ID;
+            }
+            final T stack = getStack(stackId);
+            if (stack != null) {
+                return stack;
+            }
+        }
+
+        if (stackId == INVALID_STACK_ID) {
+            stackId = mSupervisor.getNextStackId();
+        }
+
+        final T stack = createStackUnchecked(windowingMode, activityType, stackId, onTop);
+
+        if (mDisplayId == DEFAULT_DISPLAY && windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
+            // Make sure recents stack exist when creating a dock stack as it normally need to be on
+            // the other side of the docked stack and we make visibility decisions based on that.
+            // TODO: Not sure if this is needed after we change to calculate visibility based on
+            // stack z-order vs. id.
+            getOrCreateStack(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_RECENTS, onTop);
+        }
+
+        return stack;
+    }
+
+    @VisibleForTesting
+    <T extends ActivityStack> T createStackUnchecked(int windowingMode, int activityType,
+            int stackId, boolean onTop) {
+        switch (windowingMode) {
+            case WINDOWING_MODE_PINNED:
+                return (T) new PinnedActivityStack(this, stackId, mSupervisor, onTop);
+            default:
+                return (T) new ActivityStack(
+                        this, stackId, mSupervisor, windowingMode, activityType, onTop);
+        }
+    }
+
+    /**
+     * Removes stacks in the input windowing modes from the system if they are of activity type
+     * ACTIVITY_TYPE_STANDARD or ACTIVITY_TYPE_UNDEFINED
+     */
+    void removeStacksInWindowingModes(int... windowingModes) {
+        if (windowingModes == null || windowingModes.length == 0) {
+            return;
+        }
+
+        for (int j = windowingModes.length - 1 ; j >= 0; --j) {
+            final int windowingMode = windowingModes[j];
+            for (int i = mStacks.size() - 1; i >= 0; --i) {
+                final ActivityStack stack = mStacks.get(i);
+                if (!stack.isActivityTypeStandardOrUndefined()) {
+                    continue;
+                }
+                if (stack.getWindowingMode() != windowingMode) {
+                    continue;
+                }
+                mSupervisor.removeStackLocked(stack.mStackId);
+            }
+        }
+    }
+
+    void removeStacksWithActivityTypes(int... activityTypes) {
+        if (activityTypes == null || activityTypes.length == 0) {
+            return;
+        }
+
+        for (int j = activityTypes.length - 1 ; j >= 0; --j) {
+            final int activityType = activityTypes[j];
+            for (int i = mStacks.size() - 1; i >= 0; --i) {
+                final ActivityStack stack = mStacks.get(i);
+                if (stack.getActivityType() == activityType) {
+                    mSupervisor.removeStackLocked(stack.mStackId);
+                }
+            }
+        }
+    }
+
+    /** Returns the top visible stack activity type that isn't in the exclude windowing mode. */
+    int getTopVisibleStackActivityType(int excludeWindowingMode) {
+        for (int i = mStacks.size() - 1; i >= 0; --i) {
+            final ActivityStack stack = mStacks.get(i);
+            if (stack.getWindowingMode() == excludeWindowingMode) {
+                continue;
+            }
+            if (stack.shouldBeVisible(null /* starting */)) {
+                return stack.getActivityType();
+            }
+        }
+        return ACTIVITY_TYPE_UNDEFINED;
+    }
+
+    ActivityStack getSplitScreenStack() {
+        return getStack(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_UNDEFINED);
+    }
+
+    boolean hasSplitScreenStack() {
+        return getSplitScreenStack() != null;
+    }
+
+    PinnedActivityStack getPinnedStack() {
+        return getStack(WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
+    }
+
+    boolean hasPinnedStack() {
+        return getPinnedStack() != null;
+    }
+
+    @Override
+    public String toString() {
+        return "ActivityDisplay={" + mDisplayId + " numStacks=" + mStacks.size() + "}";
+    }
+
+    @Override
+    protected int getChildCount() {
+        return mStacks.size();
+    }
+
+    @Override
+    protected ConfigurationContainer getChildAt(int index) {
+        return mStacks.get(index);
+    }
+
+    @Override
+    protected ConfigurationContainer getParent() {
+        return mSupervisor;
+    }
+
+    boolean isPrivate() {
+        return (mDisplay.getFlags() & FLAG_PRIVATE) != 0;
+    }
+
+    boolean isUidPresent(int uid) {
+        for (ActivityStack stack : mStacks) {
+            if (stack.isUidPresent(uid)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /** Update and get all UIDs that are present on the display and have access to it. */
+    IntArray getPresentUIDs() {
+        mDisplayAccessUIDs.clear();
+        for (ActivityStack stack : mStacks) {
+            stack.getPresentUIDs(mDisplayAccessUIDs);
+        }
+        return mDisplayAccessUIDs;
+    }
+
+    boolean shouldDestroyContentOnRemove() {
+        return mDisplay.getRemoveMode() == REMOVE_MODE_DESTROY_CONTENT;
+    }
+
+    boolean shouldSleep() {
+        return (mStacks.isEmpty() || !mAllSleepTokens.isEmpty())
+                && (mSupervisor.mService.mRunningVoice == null);
+    }
+
+    boolean isSleeping() {
+        return mSleeping;
+    }
+
+    void setIsSleeping(boolean asleep) {
+        mSleeping = asleep;
+    }
+
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+        super.writeToProto(proto, CONFIGURATION_CONTAINER);
+        proto.write(ID, mDisplayId);
+        for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) {
+            final ActivityStack stack = mStacks.get(stackNdx);
+            stack.writeToProto(proto, STACKS);
+        }
+        proto.end(token);
+    }
+}
diff --git a/com/android/server/am/ActivityManagerService.java b/com/android/server/am/ActivityManagerService.java
index 02eb3b4..e6fe620 100644
--- a/com/android/server/am/ActivityManagerService.java
+++ b/com/android/server/am/ActivityManagerService.java
@@ -33,6 +33,14 @@
 import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
 import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
 import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.app.ActivityManager.StackId.getWindowingModeForStackId;
+import static android.app.ActivityManager.StackId.isStaticStack;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.content.pm.PackageManager.FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS;
 import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT;
 import static android.content.pm.PackageManager.FEATURE_LEANBACK_ONLY;
@@ -145,7 +153,6 @@
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_CONFIGURATION;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_FOCUS;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_IMMERSIVE;
-import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_LOCKSCREEN;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_LOCKTASK;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_LRU;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_MU;
@@ -163,10 +170,8 @@
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_UID_OBSERVERS;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_URI_PERMISSION;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_VISIBILITY;
-import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_VISIBLE_BEHIND;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.am.ActivityStackSupervisor.CREATE_IF_NEEDED;
 import static com.android.server.am.ActivityStackSupervisor.DEFER_RESUME;
 import static com.android.server.am.ActivityStackSupervisor.MATCH_TASK_IN_STACKS_ONLY;
 import static com.android.server.am.ActivityStackSupervisor.MATCH_TASK_IN_STACKS_OR_RECENT_TASKS;
@@ -177,8 +182,8 @@
 import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_DONT_LOCK;
 import static com.android.server.am.TaskRecord.REPARENT_KEEP_STACK_AT_FRONT;
 import static com.android.server.am.TaskRecord.REPARENT_LEAVE_STACK_IN_PLACE;
+import static com.android.server.am.proto.ActivityManagerServiceProto.ACTIVITIES;
 import static com.android.server.wm.AppTransition.TRANSIT_ACTIVITY_OPEN;
-import static com.android.server.wm.AppTransition.TRANSIT_ACTIVITY_RELAUNCH;
 import static com.android.server.wm.AppTransition.TRANSIT_NONE;
 import static com.android.server.wm.AppTransition.TRANSIT_TASK_IN_PLACE;
 import static com.android.server.wm.AppTransition.TRANSIT_TASK_OPEN;
@@ -259,8 +264,8 @@
 import android.content.pm.InstrumentationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.PackageManagerInternal;
+import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.PathPermission;
 import android.content.pm.PermissionInfo;
@@ -349,6 +354,7 @@
 import android.util.SparseIntArray;
 import android.util.TimeUtils;
 import android.util.Xml;
+import android.util.proto.ProtoOutputStream;
 import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -402,6 +408,7 @@
 import com.android.server.job.JobSchedulerInternal;
 import com.android.server.pm.Installer;
 import com.android.server.pm.Installer.InstallerException;
+import com.android.server.utils.PriorityDump;
 import com.android.server.vr.VrManagerInternal;
 import com.android.server.wm.PinnedStackWindowController;
 import com.android.server.wm.WindowManagerService;
@@ -682,11 +689,6 @@
     ActivityInfo mLastAddedTaskActivity;
 
     /**
-     * List of packages whitelisted by DevicePolicyManager for locktask. Indexed by userId.
-     */
-    SparseArray<String[]> mLockTaskPackages = new SparseArray<>();
-
-    /**
      * The package name of the DeviceOwner. This package is not permitted to have its data cleared.
      */
     String mDeviceOwnerName;
@@ -712,9 +714,45 @@
     @VisibleForTesting
     long mWaitForNetworkTimeoutMs;
 
+    /**
+     * Helper class which parses out priority arguments and dumps sections according to their
+     * priority. If priority arguments are omitted, function calls the legacy dump command.
+     */
+    private final PriorityDump.PriorityDumper mPriorityDumper = new PriorityDump.PriorityDumper() {
+        @Override
+        public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args) {
+            doDump(fd, pw, new String[] {"activities"});
+        }
+
+        @Override
+        public void dumpNormal(FileDescriptor fd, PrintWriter pw, String[] args) {
+            doDump(fd, pw, new String[] {"settings"});
+            doDump(fd, pw, new String[] {"intents"});
+            doDump(fd, pw, new String[] {"broadcasts"});
+            doDump(fd, pw, new String[] {"providers"});
+            doDump(fd, pw, new String[] {"permissions"});
+            doDump(fd, pw, new String[] {"services"});
+            doDump(fd, pw, new String[] {"recents"});
+            doDump(fd, pw, new String[] {"lastanr"});
+            doDump(fd, pw, new String[] {"starter"});
+            if (mAssociations.size() > 0) {
+                doDump(fd, pw, new String[] {"associations"});
+            }
+            doDump(fd, pw, new String[] {"processes"});
+            doDump(fd, pw, new String[] {"-v", "all"});
+            doDump(fd, pw, new String[] {"service", "all"});
+            doDump(fd, pw, new String[] {"provider", "all"});
+        }
+
+        @Override
+        public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+            doDump(fd, pw, args);
+        }
+    };
+
     public boolean canShowErrorDialogs() {
         return mShowDialogs && !mSleeping && !mShuttingDown
-                && !mKeyguardController.isKeyguardShowing()
+                && !mKeyguardController.isKeyguardShowing(DEFAULT_DISPLAY)
                 && !(UserManager.isDeviceInDemoMode(mContext)
                         && mUserController.getCurrentUser().isDemo());
     }
@@ -1653,45 +1691,34 @@
     static final int DISPATCH_PROCESSES_CHANGED_UI_MSG = 31;
     static final int DISPATCH_PROCESS_DIED_UI_MSG = 32;
     static final int REPORT_MEM_USAGE_MSG = 33;
-    static final int REPORT_USER_SWITCH_MSG = 34;
-    static final int CONTINUE_USER_SWITCH_MSG = 35;
-    static final int USER_SWITCH_TIMEOUT_MSG = 36;
     static final int IMMERSIVE_MODE_LOCK_MSG = 37;
     static final int PERSIST_URI_GRANTS_MSG = 38;
     static final int REQUEST_ALL_PSS_MSG = 39;
-    static final int START_PROFILES_MSG = 40;
     static final int UPDATE_TIME_PREFERENCE_MSG = 41;
-    static final int SYSTEM_USER_START_MSG = 42;
-    static final int SYSTEM_USER_CURRENT_MSG = 43;
     static final int ENTER_ANIMATION_COMPLETE_MSG = 44;
     static final int FINISH_BOOTING_MSG = 45;
-    static final int START_USER_SWITCH_UI_MSG = 46;
     static final int SEND_LOCALE_TO_MOUNT_DAEMON_MSG = 47;
     static final int DISMISS_DIALOG_UI_MSG = 48;
     static final int NOTIFY_CLEARTEXT_NETWORK_MSG = 49;
     static final int POST_DUMP_HEAP_NOTIFICATION_MSG = 50;
     static final int DELETE_DUMPHEAP_MSG = 51;
-    static final int FOREGROUND_PROFILE_CHANGED_MSG = 52;
     static final int DISPATCH_UIDS_CHANGED_UI_MSG = 53;
     static final int REPORT_TIME_TRACKER_MSG = 54;
-    static final int REPORT_USER_SWITCH_COMPLETE_MSG = 55;
     static final int SHUTDOWN_UI_AUTOMATION_CONNECTION_MSG = 56;
     static final int CONTENT_PROVIDER_PUBLISH_TIMEOUT_MSG = 57;
     static final int IDLE_UIDS_MSG = 58;
-    static final int SYSTEM_USER_UNLOCK_MSG = 59;
     static final int LOG_STACK_STATE = 60;
     static final int VR_MODE_CHANGE_MSG = 61;
     static final int SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG_MSG = 62;
     static final int HANDLE_TRUST_STORAGE_UPDATE_MSG = 63;
-    static final int REPORT_LOCKED_BOOT_COMPLETE_MSG = 64;
     static final int NOTIFY_VR_SLEEPING_MSG = 65;
     static final int SERVICE_FOREGROUND_TIMEOUT_MSG = 66;
     static final int DISPATCH_PENDING_INTENT_CANCEL_MSG = 67;
     static final int PUSH_TEMP_WHITELIST_UI_MSG = 68;
     static final int SERVICE_FOREGROUND_CRASH_MSG = 69;
     static final int DISPATCH_OOM_ADJ_OBSERVER_MSG = 70;
-    static final int USER_SWITCH_CALLBACKS_TIMEOUT_MSG = 71;
-    static final int START_USER_SWITCH_FG_MSG = 712;
+    static final int TOP_APP_KILLED_BY_LMK_MSG = 73;
+    static final int NOTIFY_VR_KEYGUARD_MSG = 74;
 
     static final int FIRST_ACTIVITY_STACK_MSG = 100;
     static final int FIRST_BROADCAST_QUEUE_MSG = 200;
@@ -1904,10 +1931,6 @@
                 }
                 break;
             }
-            case START_USER_SWITCH_UI_MSG: {
-                mUserController.showUserSwitchDialog((Pair<UserInfo, UserInfo>) msg.obj);
-                break;
-            }
             case DISMISS_DIALOG_UI_MSG: {
                 final Dialog d = (Dialog) msg.obj;
                 d.dismiss();
@@ -1923,6 +1946,17 @@
                 dispatchProcessDied(pid, uid);
                 break;
             }
+            case TOP_APP_KILLED_BY_LMK_MSG: {
+                final String appName = (String) msg.obj;
+                final AlertDialog d = new BaseErrorDialog(mUiContext);
+                d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR);
+                d.setTitle(mUiContext.getText(R.string.top_app_killed_title));
+                d.setMessage(mUiContext.getString(R.string.top_app_killed_message, appName));
+                d.setButton(DialogInterface.BUTTON_POSITIVE, mUiContext.getText(R.string.close),
+                        obtainMessage(DISMISS_DIALOG_UI_MSG, d));
+                d.show();
+                break;
+            }
             case DISPATCH_UIDS_CHANGED_UI_MSG: {
                 dispatchUidsChanged();
             } break;
@@ -2136,26 +2170,6 @@
                 thread.start();
                 break;
             }
-            case START_USER_SWITCH_FG_MSG: {
-                mUserController.startUserInForeground(msg.arg1);
-                break;
-            }
-            case REPORT_USER_SWITCH_MSG: {
-                mUserController.dispatchUserSwitch((UserState) msg.obj, msg.arg1, msg.arg2);
-                break;
-            }
-            case CONTINUE_USER_SWITCH_MSG: {
-                mUserController.continueUserSwitch((UserState) msg.obj, msg.arg1, msg.arg2);
-                break;
-            }
-            case USER_SWITCH_TIMEOUT_MSG: {
-                mUserController.timeoutUserSwitch((UserState) msg.obj, msg.arg1, msg.arg2);
-                break;
-            }
-            case USER_SWITCH_CALLBACKS_TIMEOUT_MSG: {
-                mUserController.timeoutUserSwitchCallbacks(msg.arg1, msg.arg2);
-                break;
-            }
             case IMMERSIVE_MODE_LOCK_MSG: {
                 final boolean nextState = (msg.arg1 != 0);
                 if (mUpdateLock.isHeld() != nextState) {
@@ -2180,12 +2194,6 @@
                 }
                 break;
             }
-            case START_PROFILES_MSG: {
-                synchronized (ActivityManagerService.this) {
-                    mUserController.startProfilesLocked();
-                }
-                break;
-            }
             case UPDATE_TIME_PREFERENCE_MSG: {
                 // The user's time format preference might have changed.
                 // For convenience we re-use the Intent extra values.
@@ -2204,35 +2212,6 @@
                 }
                 break;
             }
-            case SYSTEM_USER_START_MSG: {
-                mBatteryStatsService.noteEvent(BatteryStats.HistoryItem.EVENT_USER_RUNNING_START,
-                        Integer.toString(msg.arg1), msg.arg1);
-                mSystemServiceManager.startUser(msg.arg1);
-                break;
-            }
-            case SYSTEM_USER_UNLOCK_MSG: {
-                final int userId = msg.arg1;
-                mSystemServiceManager.unlockUser(userId);
-                synchronized (ActivityManagerService.this) {
-                    mRecentTasks.loadUserRecentsLocked(userId);
-                }
-                if (userId == UserHandle.USER_SYSTEM) {
-                    startPersistentApps(PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
-                }
-                installEncryptionUnawareProviders(userId);
-                mUserController.finishUserUnlocked((UserState) msg.obj);
-                break;
-            }
-            case SYSTEM_USER_CURRENT_MSG: {
-                mBatteryStatsService.noteEvent(
-                        BatteryStats.HistoryItem.EVENT_USER_FOREGROUND_FINISH,
-                        Integer.toString(msg.arg2), msg.arg2);
-                mBatteryStatsService.noteEvent(
-                        BatteryStats.HistoryItem.EVENT_USER_FOREGROUND_START,
-                        Integer.toString(msg.arg1), msg.arg1);
-                mSystemServiceManager.switchUser(msg.arg1);
-                break;
-            }
             case ENTER_ANIMATION_COMPLETE_MSG: {
                 synchronized (ActivityManagerService.this) {
                     ActivityRecord r = ActivityRecord.forTokenLocked((IBinder) msg.obj);
@@ -2372,19 +2351,10 @@
                     mMemWatchDumpUid = -1;
                 }
             } break;
-            case FOREGROUND_PROFILE_CHANGED_MSG: {
-                mUserController.dispatchForegroundProfileChanged(msg.arg1);
-            } break;
             case REPORT_TIME_TRACKER_MSG: {
                 AppTimeTracker tracker = (AppTimeTracker)msg.obj;
                 tracker.deliverResult(mContext);
             } break;
-            case REPORT_USER_SWITCH_COMPLETE_MSG: {
-                mUserController.dispatchUserSwitchComplete(msg.arg1);
-            } break;
-            case REPORT_LOCKED_BOOT_COMPLETE_MSG: {
-                mUserController.dispatchLockedBootComplete(msg.arg1);
-            } break;
             case SHUTDOWN_UI_AUTOMATION_CONNECTION_MSG: {
                 IUiAutomationConnection connection = (IUiAutomationConnection) msg.obj;
                 try {
@@ -2409,17 +2379,16 @@
                     if (disableNonVrUi) {
                         // If we are in a VR mode where Picture-in-Picture mode is unsupported,
                         // then remove the pinned stack.
-                        final PinnedActivityStack pinnedStack = mStackSupervisor.getStack(
-                                PINNED_STACK_ID);
-                        if (pinnedStack != null) {
-                            mStackSupervisor.removeStackLocked(PINNED_STACK_ID);
-                        }
+                        mStackSupervisor.removeStacksInWindowingModes(WINDOWING_MODE_PINNED);
                     }
                 }
             } break;
             case NOTIFY_VR_SLEEPING_MSG: {
                 notifyVrManagerOfSleepState(msg.arg1 != 0);
             } break;
+            case NOTIFY_VR_KEYGUARD_MSG: {
+                notifyVrManagerOfKeyguardState(msg.arg1 != 0);
+            } break;
             case HANDLE_TRUST_STORAGE_UPDATE_MSG: {
                 synchronized (ActivityManagerService.this) {
                     for (int i = mLruProcesses.size() - 1 ; i >= 0 ; i--) {
@@ -2568,10 +2537,12 @@
     }
 
     public void setWindowManager(WindowManagerService wm) {
-        mWindowManager = wm;
-        mStackSupervisor.setWindowManager(wm);
-        mActivityStarter.setWindowManager(wm);
-        mLockTaskController.setWindowManager(wm);
+        synchronized (this) {
+            mWindowManager = wm;
+            mStackSupervisor.setWindowManager(wm);
+            mActivityStarter.setWindowManager(wm);
+            mLockTaskController.setWindowManager(wm);
+        }
     }
 
     public void setUsageStatsManager(UsageStatsManagerInternal usageStatsManager) {
@@ -2589,6 +2560,14 @@
 
     static class MemBinder extends Binder {
         ActivityManagerService mActivityManagerService;
+        private final PriorityDump.PriorityDumper mPriorityDumper =
+                new PriorityDump.PriorityDumper() {
+            @Override
+            public void dumpNormal(FileDescriptor fd, PrintWriter pw, String[] args) {
+                mActivityManagerService.dumpApplicationMemoryUsage(fd, pw, "  ", args, false, null);
+            }
+        };
+
         MemBinder(ActivityManagerService activityManagerService) {
             mActivityManagerService = activityManagerService;
         }
@@ -2597,7 +2576,7 @@
         protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
             if (!DumpUtils.checkDumpAndUsageStatsPermission(mActivityManagerService.mContext,
                     "meminfo", pw)) return;
-            mActivityManagerService.dumpApplicationMemoryUsage(fd, pw, "  ", args, false, null);
+            PriorityDump.dump(mPriorityDumper, fd, pw, args);
         }
     }
 
@@ -2631,19 +2610,27 @@
 
     static class CpuBinder extends Binder {
         ActivityManagerService mActivityManagerService;
+        private final PriorityDump.PriorityDumper mPriorityDumper =
+                new PriorityDump.PriorityDumper() {
+            @Override
+            public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args) {
+                if (!DumpUtils.checkDumpAndUsageStatsPermission(mActivityManagerService.mContext,
+                        "cpuinfo", pw)) return;
+                synchronized (mActivityManagerService.mProcessCpuTracker) {
+                    pw.print(mActivityManagerService.mProcessCpuTracker.printCurrentLoad());
+                    pw.print(mActivityManagerService.mProcessCpuTracker.printCurrentState(
+                            SystemClock.uptimeMillis()));
+                }
+            }
+        };
+
         CpuBinder(ActivityManagerService activityManagerService) {
             mActivityManagerService = activityManagerService;
         }
 
         @Override
         protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-            if (!DumpUtils.checkDumpAndUsageStatsPermission(mActivityManagerService.mContext,
-                    "cpuinfo", pw)) return;
-            synchronized (mActivityManagerService.mProcessCpuTracker) {
-                pw.print(mActivityManagerService.mProcessCpuTracker.printCurrentLoad());
-                pw.print(mActivityManagerService.mProcessCpuTracker.printCurrentState(
-                        SystemClock.uptimeMillis()));
-            }
+            PriorityDump.dump(mPriorityDumper, fd, pw, args);
         }
     }
 
@@ -3152,9 +3139,7 @@
         }
 
         if (mLastResumedActivity != null && r.userId != mLastResumedActivity.userId) {
-            mHandler.removeMessages(FOREGROUND_PROFILE_CHANGED_MSG);
-            mHandler.obtainMessage(
-                    FOREGROUND_PROFILE_CHANGED_MSG, r.userId, 0).sendToTarget();
+            mUserController.sendForegroundProfileChanged(r.userId);
         }
         mLastResumedActivity = r;
 
@@ -3259,7 +3244,8 @@
         if (r.requestedVrComponent != null && r.getStackId() >= FIRST_DYNAMIC_STACK_ID) {
             Slog.i(TAG, "Moving " + r.shortComponentName + " from stack " + r.getStackId()
                     + " to main stack for VR");
-            moveTaskToStack(r.getTask().taskId, FULLSCREEN_WORKSPACE_STACK_ID, true /* toTop */);
+            setTaskWindowingMode(r.getTask().taskId,
+                    WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY, true /* toTop */);
         }
         mHandler.sendMessage(
                 mHandler.obtainMessage(VR_MODE_CHANGE_MSG, 0, 0, r));
@@ -3278,6 +3264,19 @@
         vrService.onSleepStateChanged(isSleeping);
     }
 
+    private void sendNotifyVrManagerOfKeyguardState(boolean isShowing) {
+        mHandler.sendMessage(
+                mHandler.obtainMessage(NOTIFY_VR_KEYGUARD_MSG, isShowing ? 1 : 0, 0));
+    }
+
+    private void notifyVrManagerOfKeyguardState(boolean isShowing) {
+        final VrManagerInternal vrService = LocalServices.getService(VrManagerInternal.class);
+        if (vrService == null) {
+            return;
+        }
+        vrService.onKeyguardStateChanged(isShowing);
+    }
+
     final void showAskCompatModeDialogLocked(ActivityRecord r) {
         Message msg = Message.obtain();
         msg.what = SHOW_COMPAT_MODE_DIALOG_UI_MSG;
@@ -3874,6 +3873,12 @@
                 mNativeDebuggingApp = null;
             }
 
+            if (app.info.isPrivilegedApp() &&
+                    !SystemProperties.getBoolean("pm.dexopt.priv-apps", true)) {
+                runtimeFlags |= Zygote.DISABLE_VERIFIER;
+                runtimeFlags |= Zygote.ONLY_USE_SYSTEM_OAT_FILES;
+            }
+
             String invokeWith = null;
             if ((app.info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
                 // Debuggable apps may include a wrapper script with their library directory.
@@ -4156,15 +4161,6 @@
         }
     }
 
-    void enforceShellRestriction(String restriction, int userHandle) {
-        if (Binder.getCallingUid() == SHELL_UID) {
-            if (userHandle < 0 || mUserController.hasUserRestriction(restriction, userHandle)) {
-                throw new SecurityException("Shell does not have permission to access user "
-                        + userHandle);
-            }
-        }
-    }
-
     @Override
     public int getFrontActivityScreenCompatMode() {
         enforceNotIsolatedCaller("getFrontActivityScreenCompatMode");
@@ -5432,6 +5428,7 @@
             boolean doLowMem = app.instr == null;
             boolean doOomAdj = doLowMem;
             if (!app.killedByAm) {
+                maybeNotifyTopAppKilled(app);
                 Slog.i(TAG, "Process " + app.processName + " (pid " + pid + ") has died: "
                         + ProcessList.makeOomAdjString(app.setAdj)
                         + ProcessList.makeProcStateString(app.setProcState));
@@ -5465,6 +5462,23 @@
         }
     }
 
+    /** Show system error dialog when a top app is killed by LMK */
+    void maybeNotifyTopAppKilled(ProcessRecord app) {
+        if (!shouldNotifyTopAppKilled(app)) {
+            return;
+        }
+
+        Message msg = mHandler.obtainMessage(TOP_APP_KILLED_BY_LMK_MSG);
+        msg.obj = mContext.getPackageManager().getApplicationLabel(app.info);
+        mUiHandler.sendMessage(msg);
+    }
+
+    /** Only show notification when the top app is killed on low ram devices */
+    private boolean shouldNotifyTopAppKilled(ProcessRecord app) {
+        return app.curSchedGroup == ProcessList.SCHED_GROUP_TOP_APP &&
+            ActivityManager.isLowRamDeviceStatic();
+    }
+
     /**
      * If a stack trace dump file is configured, dump process stack traces.
      * @param clearTraces causes the dump file to be erased prior to the new
@@ -6188,7 +6202,7 @@
                         Slog.w(TAG, "Failed trying to unstop package "
                                 + packageName + ": " + e);
                     }
-                    if (mUserController.isUserRunningLocked(user, 0)) {
+                    if (mUserController.isUserRunning(user, 0)) {
                         forceStopPackageLocked(packageName, pkgUid, "from pid " + callingPid);
                         finishForceStopPackageLocked(packageName, pkgUid);
                     }
@@ -7346,33 +7360,32 @@
                     startProcessLocked(procs.get(ip), "on-hold", null);
                 }
             }
-
-            if (mFactoryTest != FactoryTest.FACTORY_TEST_LOW_LEVEL) {
-                // Start looking for apps that are abusing wake locks.
-                Message nmsg = mHandler.obtainMessage(CHECK_EXCESSIVE_POWER_USE_MSG);
-                mHandler.sendMessageDelayed(nmsg, mConstants.POWER_CHECK_INTERVAL);
-                // Tell anyone interested that we are done booting!
-                SystemProperties.set("sys.boot_completed", "1");
-
-                // And trigger dev.bootcomplete if we are not showing encryption progress
-                if (!"trigger_restart_min_framework".equals(SystemProperties.get("vold.decrypt"))
-                    || "".equals(SystemProperties.get("vold.encrypt_progress"))) {
-                    SystemProperties.set("dev.bootcomplete", "1");
-                }
-                mUserController.sendBootCompletedLocked(
-                        new IIntentReceiver.Stub() {
-                            @Override
-                            public void performReceive(Intent intent, int resultCode,
-                                    String data, Bundle extras, boolean ordered,
-                                    boolean sticky, int sendingUser) {
-                                synchronized (ActivityManagerService.this) {
-                                    requestPssAllProcsLocked(SystemClock.uptimeMillis(),
-                                            true, false);
-                                }
-                            }
-                        });
-                scheduleStartProfilesLocked();
+            if (mFactoryTest == FactoryTest.FACTORY_TEST_LOW_LEVEL) {
+                return;
             }
+            // Start looking for apps that are abusing wake locks.
+            Message nmsg = mHandler.obtainMessage(CHECK_EXCESSIVE_POWER_USE_MSG);
+            mHandler.sendMessageDelayed(nmsg, mConstants.POWER_CHECK_INTERVAL);
+            // Tell anyone interested that we are done booting!
+            SystemProperties.set("sys.boot_completed", "1");
+
+            // And trigger dev.bootcomplete if we are not showing encryption progress
+            if (!"trigger_restart_min_framework".equals(SystemProperties.get("vold.decrypt"))
+                    || "".equals(SystemProperties.get("vold.encrypt_progress"))) {
+                SystemProperties.set("dev.bootcomplete", "1");
+            }
+            mUserController.sendBootCompleted(
+                    new IIntentReceiver.Stub() {
+                        @Override
+                        public void performReceive(Intent intent, int resultCode,
+                                String data, Bundle extras, boolean ordered,
+                                boolean sticky, int sendingUser) {
+                            synchronized (ActivityManagerService.this) {
+                                requestPssAllProcsLocked(SystemClock.uptimeMillis(), true, false);
+                            }
+                        }
+                    });
+            mUserController.scheduleStartProfiles();
         }
     }
 
@@ -8112,7 +8125,7 @@
                     final Rect sourceBounds = new Rect(r.pictureInPictureArgs.getSourceRectHint());
                     mStackSupervisor.moveActivityToPinnedStackLocked(r, sourceBounds, aspectRatio,
                             true /* moveHomeStackToFront */, "enterPictureInPictureMode");
-                    final PinnedActivityStack stack = mStackSupervisor.getStack(PINNED_STACK_ID);
+                    final PinnedActivityStack stack = r.getStack();
                     stack.setPictureInPictureAspectRatio(aspectRatio);
                     stack.setPictureInPictureActions(actions);
 
@@ -8227,11 +8240,6 @@
                     + ": Current activity does not support picture-in-picture.");
         }
 
-        if (!StackId.isAllowedToEnterPictureInPicture(r.getStack().getStackId())) {
-            throw new IllegalStateException(caller
-                    + ": Activities on the home, assistant, or recents stack not supported");
-        }
-
         if (params.hasSetAspectRatio()
                 && !mWindowManager.isValidPictureInPictureAspectRatio(r.getStack().mDisplayId,
                         params.getAspectRatio())) {
@@ -9869,8 +9877,9 @@
         if (tr.mBounds != null) {
             rti.bounds = new Rect(tr.mBounds);
         }
-        rti.supportsSplitScreenMultiWindow = tr.supportsSplitScreen();
+        rti.supportsSplitScreenMultiWindow = tr.supportsSplitScreenWindowingMode();
         rti.resizeMode = tr.mResizeMode;
+        rti.configuration.setTo(tr.getConfiguration());
 
         ActivityRecord base = null;
         ActivityRecord top = null;
@@ -10048,7 +10057,7 @@
             enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS,
                     "getTaskDescription()");
             final TaskRecord tr = mStackSupervisor.anyTaskForIdLocked(id,
-                    MATCH_TASK_IN_STACKS_OR_RECENT_TASKS, INVALID_STACK_ID);
+                    MATCH_TASK_IN_STACKS_OR_RECENT_TASKS);
             if (tr != null) {
                 return tr.lastTaskDescription;
             }
@@ -10161,7 +10170,7 @@
     public void setTaskResizeable(int taskId, int resizeableMode) {
         synchronized (this) {
             final TaskRecord task = mStackSupervisor.anyTaskForIdLocked(
-                    taskId, MATCH_TASK_IN_STACKS_OR_RECENT_TASKS, INVALID_STACK_ID);
+                    taskId, MATCH_TASK_IN_STACKS_OR_RECENT_TASKS);
             if (task == null) {
                 Slog.w(TAG, "setTaskResizeable: taskId=" + taskId + " not found");
                 return;
@@ -10188,21 +10197,23 @@
                 // - a non-null bounds on a non-freeform (fullscreen OR docked) task moves
                 //   that task to freeform
                 // - otherwise the task is not moved
-                int stackId = task.getStackId();
+                ActivityStack stack = task.getStack();
                 if (!task.getWindowConfiguration().canResizeTask()) {
                     throw new IllegalArgumentException("resizeTask not allowed on task=" + task);
                 }
-                if (bounds == null && stackId == FREEFORM_WORKSPACE_STACK_ID) {
-                    stackId = FULLSCREEN_WORKSPACE_STACK_ID;
-                } else if (bounds != null && stackId != FREEFORM_WORKSPACE_STACK_ID ) {
-                    stackId = FREEFORM_WORKSPACE_STACK_ID;
+                if (bounds == null && stack.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
+                    stack = stack.getDisplay().getOrCreateStack(
+                            WINDOWING_MODE_FULLSCREEN, stack.getActivityType(), ON_TOP);
+                } else if (bounds != null && stack.getWindowingMode() != WINDOWING_MODE_FREEFORM) {
+                    stack = stack.getDisplay().getOrCreateStack(
+                            WINDOWING_MODE_FREEFORM, stack.getActivityType(), ON_TOP);
                 }
 
                 // Reparent the task to the right stack if necessary
                 boolean preserveWindow = (resizeMode & RESIZE_MODE_PRESERVE_WINDOW) != 0;
-                if (stackId != task.getStackId()) {
+                if (stack != task.getStack()) {
                     // Defer resume until the task is resized below
-                    task.reparent(stackId, ON_TOP, REPARENT_KEEP_STACK_AT_FRONT, ANIMATE,
+                    task.reparent(stack, ON_TOP, REPARENT_KEEP_STACK_AT_FRONT, ANIMATE,
                             DEFER_RESUME, "resizeTask");
                     preserveWindow = false;
                 }
@@ -10224,7 +10235,7 @@
         try {
             synchronized (this) {
                 final TaskRecord task = mStackSupervisor.anyTaskForIdLocked(taskId,
-                        MATCH_TASK_IN_STACKS_OR_RECENT_TASKS, INVALID_STACK_ID);
+                        MATCH_TASK_IN_STACKS_OR_RECENT_TASKS);
                 if (task == null) {
                     Slog.w(TAG, "getTaskBounds: taskId=" + taskId + " not found");
                     return rect;
@@ -10256,7 +10267,7 @@
         try {
             synchronized (this) {
                 final TaskRecord task = mStackSupervisor.anyTaskForIdLocked(taskId,
-                        MATCH_TASK_IN_STACKS_ONLY, INVALID_STACK_ID);
+                        MATCH_TASK_IN_STACKS_ONLY);
                 if (task == null) {
                     Slog.w(TAG, "cancelTaskWindowTransition: taskId=" + taskId + " not found");
                     return;
@@ -10275,7 +10286,7 @@
         try {
             synchronized (this) {
                 final TaskRecord task = mStackSupervisor.anyTaskForIdLocked(taskId,
-                        MATCH_TASK_IN_STACKS_ONLY, INVALID_STACK_ID);
+                        MATCH_TASK_IN_STACKS_ONLY);
                 if (task == null) {
                     Slog.w(TAG, "cancelTaskThumbnailTransition: taskId=" + taskId + " not found");
                     return;
@@ -10295,7 +10306,7 @@
             final TaskRecord task;
             synchronized (this) {
                 task = mStackSupervisor.anyTaskForIdLocked(taskId,
-                        MATCH_TASK_IN_STACKS_OR_RECENT_TASKS, INVALID_STACK_ID);
+                        MATCH_TASK_IN_STACKS_OR_RECENT_TASKS);
                 if (task == null) {
                     Slog.w(TAG, "getTaskSnapshot: taskId=" + taskId + " not found");
                     return null;
@@ -10375,13 +10386,14 @@
     @Override
     public void removeStack(int stackId) {
         enforceCallingPermission(Manifest.permission.MANAGE_ACTIVITY_STACKS, "removeStack()");
-        if (StackId.isHomeOrRecentsStack(stackId)) {
-            throw new IllegalArgumentException("Removing home or recents stack is not allowed.");
-        }
-
         synchronized (this) {
             final long ident = Binder.clearCallingIdentity();
             try {
+                final ActivityStack stack = mStackSupervisor.getStack(stackId);
+                if (stack != null && !stack.isActivityTypeStandardOrUndefined()) {
+                    throw new IllegalArgumentException(
+                            "Removing non-standard stack is not allowed.");
+                }
                 mStackSupervisor.removeStackLocked(stackId);
             } finally {
                 Binder.restoreCallingIdentity(ident);
@@ -10389,6 +10401,36 @@
         }
     }
 
+    /**
+     * Removes stacks in the input windowing modes from the system if they are of activity type
+     * ACTIVITY_TYPE_STANDARD or ACTIVITY_TYPE_UNDEFINED
+     */
+    @Override
+    public void removeStacksInWindowingModes(int[] windowingModes) {
+        enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "removeStacksInWindowingModes()");
+        synchronized (this) {
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                mStackSupervisor.removeStacksInWindowingModes(windowingModes);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+    }
+
+    @Override
+    public void removeStacksWithActivityTypes(int[] activityTypes) {
+        enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "removeStacksWithActivityTypes()");
+        synchronized (this) {
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                mStackSupervisor.removeStacksWithActivityTypes(activityTypes);
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+    }
+
     @Override
     public void moveStackToDisplay(int stackId, int displayId) {
         enforceCallingPermission(INTERNAL_SYSTEM_WINDOW, "moveStackToDisplay()");
@@ -10532,13 +10574,15 @@
     public int createStackOnDisplay(int displayId) throws RemoteException {
         enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "createStackOnDisplay()");
         synchronized (this) {
-            final int stackId = mStackSupervisor.getNextStackId();
-            final ActivityStack stack =
-                    mStackSupervisor.createStackOnDisplay(stackId, displayId, true /*onTop*/);
-            if (stack == null) {
+            final ActivityDisplay display =
+                    mStackSupervisor.getActivityDisplayOrCreateLocked(displayId);
+            if (display == null) {
                 return INVALID_STACK_ID;
             }
-            return stack.mStackId;
+            // TODO(multi-display): Have the caller pass in the windowing mode and activity type.
+            final ActivityStack stack = display.createStack(
+                    WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, true /*onTop*/);
+            return (stack != null) ? stack.mStackId : INVALID_STACK_ID;
         }
     }
 
@@ -10570,8 +10614,12 @@
                             "exitFreeformMode: You can only go fullscreen from freeform.");
                 }
 
+                final ActivityStack fullscreenStack = stack.getDisplay().getOrCreateStack(
+                        WINDOWING_MODE_FULLSCREEN, stack.getActivityType(), ON_TOP);
+
                 if (DEBUG_STACK) Slog.d(TAG_STACK, "exitFreeformMode: " + r);
-                r.getTask().reparent(FULLSCREEN_WORKSPACE_STACK_ID, ON_TOP,
+                // TODO: Should just change windowing mode vs. re-parenting...
+                r.getTask().reparent(fullscreenStack, ON_TOP,
                         REPARENT_KEEP_STACK_AT_FRONT, ANIMATE, !DEFER_RESUME, "exitFreeformMode");
             } finally {
                 Binder.restoreCallingIdentity(ident);
@@ -10580,12 +10628,44 @@
     }
 
     @Override
+    public void setTaskWindowingMode(int taskId, int windowingMode, boolean toTop) {
+        enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "setTaskWindowingMode()");
+        synchronized (this) {
+            final long ident = Binder.clearCallingIdentity();
+            try {
+                final TaskRecord task = mStackSupervisor.anyTaskForIdLocked(taskId);
+                if (task == null) {
+                    Slog.w(TAG, "setTaskWindowingMode: No task for id=" + taskId);
+                    return;
+                }
+
+                if (DEBUG_STACK) Slog.d(TAG_STACK, "setTaskWindowingMode: moving task=" + taskId
+                        + " to windowingMode=" + windowingMode + " toTop=" + toTop);
+                if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
+                    mWindowManager.setDockedStackCreateState(DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT,
+                            null /* initialBounds */);
+                }
+
+                if (!task.isActivityTypeStandardOrUndefined()) {
+                    throw new IllegalArgumentException("setTaskWindowingMode: Attempt to move task "
+                            + taskId + " to non-standard windowin mode=" + windowingMode);
+                }
+                final ActivityDisplay display = task.getStack().getDisplay();
+                final ActivityStack stack = display.getOrCreateStack(windowingMode,
+                        task.getStack().getActivityType(), toTop);
+                // TODO: We should just change the windowing mode for the task vs. creating and
+                // moving it to a stack.
+                task.reparent(stack, toTop, REPARENT_KEEP_STACK_AT_FRONT, ANIMATE, !DEFER_RESUME,
+                        "moveTaskToStack");
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        }
+    }
+
+    @Override
     public void moveTaskToStack(int taskId, int stackId, boolean toTop) {
         enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "moveTaskToStack()");
-        if (StackId.isHomeOrRecentsStack(stackId)) {
-            throw new IllegalArgumentException(
-                    "moveTaskToStack: Attempt to move task " + taskId + " to stack " + stackId);
-        }
         synchronized (this) {
             long ident = Binder.clearCallingIdentity();
             try {
@@ -10601,58 +10681,25 @@
                     mWindowManager.setDockedStackCreateState(DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT,
                             null /* initialBounds */);
                 }
-                task.reparent(stackId, toTop,
-                        REPARENT_KEEP_STACK_AT_FRONT, ANIMATE, !DEFER_RESUME, "moveTaskToStack");
-            } finally {
-                Binder.restoreCallingIdentity(ident);
-            }
-        }
-    }
 
-    @Override
-    public void swapDockedAndFullscreenStack() throws RemoteException {
-        enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "swapDockedAndFullscreenStack()");
-        synchronized (this) {
-            long ident = Binder.clearCallingIdentity();
-            try {
-                final ActivityStack fullscreenStack = mStackSupervisor.getStack(
-                        FULLSCREEN_WORKSPACE_STACK_ID);
-                final TaskRecord topTask = fullscreenStack != null ? fullscreenStack.topTask()
-                        : null;
-                final ActivityStack dockedStack = mStackSupervisor.getStack(DOCKED_STACK_ID);
-                final ArrayList<TaskRecord> tasks = dockedStack != null ? dockedStack.getAllTasks()
-                        : null;
-                if (topTask == null || tasks == null || tasks.size() == 0) {
-                    Slog.w(TAG,
-                            "Unable to swap tasks, either docked or fullscreen stack is empty.");
-                    return;
-                }
-
-                // TODO: App transition
-                mWindowManager.prepareAppTransition(TRANSIT_ACTIVITY_RELAUNCH, false);
-
-                // Defer the resume until we move all the docked tasks to the fullscreen stack below
-                topTask.reparent(DOCKED_STACK_ID, ON_TOP, REPARENT_KEEP_STACK_AT_FRONT, ANIMATE,
-                        DEFER_RESUME, "swapDockedAndFullscreenStack - DOCKED_STACK");
-                final int size = tasks.size();
-                for (int i = 0; i < size; i++) {
-                    final int id = tasks.get(i).taskId;
-                    if (id == topTask.taskId) {
-                        continue;
+                ActivityStack stack = mStackSupervisor.getStack(stackId);
+                if (stack == null) {
+                    if (!isStaticStack(stackId)) {
+                        throw new IllegalStateException(
+                                "moveTaskToStack: No stack for stackId=" + stackId);
                     }
-
-                    // Defer the resume until after all the tasks have been moved
-                    tasks.get(i).reparent(FULLSCREEN_WORKSPACE_STACK_ID, ON_TOP,
-                            REPARENT_KEEP_STACK_AT_FRONT, ANIMATE, DEFER_RESUME,
-                            "swapDockedAndFullscreenStack - FULLSCREEN_STACK");
+                    final ActivityDisplay display = task.getStack().getDisplay();
+                    final int windowingMode =
+                            getWindowingModeForStackId(stackId, display.hasSplitScreenStack());
+                    stack = display.getOrCreateStack(windowingMode,
+                            task.getStack().getActivityType(), toTop);
                 }
-
-                // Because we deferred the resume to avoid conflicts with stack switches while
-                // resuming, we need to do it after all the tasks are moved.
-                mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
-                mStackSupervisor.resumeFocusedStackTopActivityLocked();
-
-                mWindowManager.executeAppTransition();
+                if (!stack.isActivityTypeStandardOrUndefined()) {
+                    throw new IllegalArgumentException("moveTaskToStack: Attempt to move task "
+                            + taskId + " to stack " + stackId);
+                }
+                task.reparent(stack, toTop, REPARENT_KEEP_STACK_AT_FRONT, ANIMATE, !DEFER_RESUME,
+                        "moveTaskToStack");
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -10685,13 +10732,18 @@
                     Slog.w(TAG, "moveTaskToDockedStack: No task for id=" + taskId);
                     return false;
                 }
-
                 if (DEBUG_STACK) Slog.d(TAG_STACK, "moveTaskToDockedStack: moving task=" + taskId
                         + " to createMode=" + createMode + " toTop=" + toTop);
                 mWindowManager.setDockedStackCreateState(createMode, initialBounds);
 
+                final ActivityDisplay display = task.getStack().getDisplay();
+                final ActivityStack stack = display.getOrCreateStack(
+                        WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, task.getStack().getActivityType(),
+                        toTop);
+
                 // Defer resuming until we move the home stack to the front below
-                final boolean moved = task.reparent(DOCKED_STACK_ID, toTop,
+                // TODO: Should just change windowing mode vs. re-parenting...
+                final boolean moved = task.reparent(stack, toTop,
                         REPARENT_KEEP_STACK_AT_FRONT, animate, !DEFER_RESUME,
                         "moveTaskToDockedStack");
                 if (moved) {
@@ -10705,6 +10757,66 @@
     }
 
     /**
+     * Dismisses split-screen multi-window mode.
+     * @param toTop If true the current primary split-screen stack will be placed or left on top.
+     */
+    @Override
+    public void dismissSplitScreenMode(boolean toTop) {
+        enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "dismissSplitScreenMode()");
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            synchronized (this) {
+                final ActivityStack stack =
+                        mStackSupervisor.getDefaultDisplay().getSplitScreenStack();
+                if (toTop) {
+                    mStackSupervisor.resizeStackLocked(stack.mStackId, null /* destBounds */,
+                            null /* tempTaskBounds */, null /* tempTaskInsetBounds */,
+                            true /* preserveWindows */, true /* allowResizeInDockedMode */,
+                            !DEFER_RESUME);
+                } else {
+                    mStackSupervisor.moveTasksToFullscreenStackLocked(stack, false /* onTop */);
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    /**
+     * Dismisses Pip
+     * @param animate True if the dismissal should be animated.
+     * @param animationDuration The duration of the resize animation in milliseconds or -1 if the
+     *                          default animation duration should be used.
+     */
+    @Override
+    public void dismissPip(boolean animate, int animationDuration) {
+        enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "dismissPip()");
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            synchronized (this) {
+                final PinnedActivityStack stack =
+                        mStackSupervisor.getDefaultDisplay().getPinnedStack();
+
+                if (stack == null) {
+                    return;
+                }
+                if (stack.getWindowingMode() != WINDOWING_MODE_PINNED) {
+                    throw new IllegalArgumentException("Stack: " + stack
+                            + " doesn't support animated resize.");
+                }
+                if (animate) {
+                    stack.animateResizePinnedStack(null /* sourceHintBounds */,
+                            null /* destBounds */, animationDuration, false /* fromFullscreen */);
+                } else {
+                    mStackSupervisor.moveTasksToFullscreenStackLocked(stack, true /* onTop */);
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    /**
      * Moves the top activity in the input stackId to the pinned stack.
      *
      * @param stackId Id of stack to move the top activity to pinned stack.
@@ -10739,17 +10851,16 @@
         try {
             synchronized (this) {
                 if (animate) {
-                    if (stackId == PINNED_STACK_ID) {
-                        final PinnedActivityStack pinnedStack =
-                                mStackSupervisor.getStack(PINNED_STACK_ID);
-                        if (pinnedStack != null) {
-                            pinnedStack.animateResizePinnedStack(null /* sourceHintBounds */,
-                                    destBounds, animationDuration, false /* fromFullscreen */);
-                        }
-                    } else {
+                    final PinnedActivityStack stack = mStackSupervisor.getStack(stackId);
+                    if (stack == null) {
+                        return;
+                    }
+                    if (stack.getWindowingMode() != WINDOWING_MODE_PINNED) {
                         throw new IllegalArgumentException("Stack: " + stackId
                                 + " doesn't support animated resize.");
                     }
+                    stack.animateResizePinnedStack(null /* sourceHintBounds */, destBounds,
+                            animationDuration, false /* fromFullscreen */);
                 } else {
                     mStackSupervisor.resizeStackLocked(stackId, destBounds, null /* tempTaskBounds */,
                             null /* tempTaskInsetBounds */, preserveWindows,
@@ -10801,11 +10912,6 @@
     @Override
     public void positionTaskInStack(int taskId, int stackId, int position) {
         enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "positionTaskInStack()");
-        if (StackId.isHomeOrRecentsStack(stackId)) {
-            throw new IllegalArgumentException(
-                    "positionTaskInStack: Attempt to change the position of task "
-                    + taskId + " in/to home/recents stack");
-        }
         synchronized (this) {
             long ident = Binder.clearCallingIdentity();
             try {
@@ -10817,8 +10923,16 @@
                             + taskId);
                 }
 
-                final ActivityStack stack = mStackSupervisor.getStack(stackId, CREATE_IF_NEEDED,
-                        !ON_TOP);
+                final ActivityStack stack = mStackSupervisor.getStack(stackId);
+
+                if (stack == null) {
+                    throw new IllegalArgumentException("positionTaskInStack: no stack for id="
+                            + stackId);
+                }
+                if (!stack.isActivityTypeStandardOrUndefined()) {
+                    throw new IllegalArgumentException("positionTaskInStack: Attempt to change"
+                            + " the position of task " + taskId + " in/to non-standard stack");
+                }
 
                 // TODO: Have the callers of this API call a separate reparent method if that is
                 // what they intended to do vs. having this method also do reparenting.
@@ -10827,8 +10941,8 @@
                     stack.positionChildAt(task, position);
                 } else {
                     // Reparent to new stack.
-                    task.reparent(stackId, position, REPARENT_LEAVE_STACK_IN_PLACE,
-                            !ANIMATE, !DEFER_RESUME, "positionTaskInStack");
+                    task.reparent(stack, position, REPARENT_LEAVE_STACK_IN_PLACE, !ANIMATE,
+                            !DEFER_RESUME, "positionTaskInStack");
                 }
             } finally {
                 Binder.restoreCallingIdentity(ident);
@@ -10850,12 +10964,12 @@
     }
 
     @Override
-    public StackInfo getStackInfo(int stackId) {
+    public StackInfo getStackInfo(int windowingMode, int activityType) {
         enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "getStackInfo()");
         long ident = Binder.clearCallingIdentity();
         try {
             synchronized (this) {
-                return mStackSupervisor.getStackInfoLocked(stackId);
+                return mStackSupervisor.getStackInfo(windowingMode, activityType);
             }
         } finally {
             Binder.restoreCallingIdentity(ident);
@@ -10882,7 +10996,6 @@
 
     @Override
     public void updateLockTaskPackages(int userId, String[] packages) {
-        // TODO: move this into LockTaskController
         final int callingUid = Binder.getCallingUid();
         if (callingUid != 0 && callingUid != SYSTEM_UID) {
             enforceCallingPermission(android.Manifest.permission.UPDATE_LOCK_TASK_PACKAGES,
@@ -10891,8 +11004,7 @@
         synchronized (this) {
             if (DEBUG_LOCKTASK) Slog.w(TAG_LOCKTASK, "Whitelisting " + userId + ":" +
                     Arrays.toString(packages));
-            mLockTaskPackages.put(userId, packages);
-            mLockTaskController.onLockTaskPackagesUpdated();
+            mLockTaskController.updateLockTaskPackages(userId, packages);
         }
     }
 
@@ -10908,11 +11020,7 @@
         }
 
         // When a task is locked, dismiss the pinned stack if it exists
-        final PinnedActivityStack pinnedStack = mStackSupervisor.getStack(
-                PINNED_STACK_ID);
-        if (pinnedStack != null) {
-            mStackSupervisor.removeStackLocked(PINNED_STACK_ID);
-        }
+        mStackSupervisor.removeStacksInWindowingModes(WINDOWING_MODE_PINNED);
 
         // isAppPinning is used to distinguish between locked and pinned mode, as pinned mode
         // is initiated by system after the pinning request was shown and locked mode is initiated
@@ -11139,7 +11247,7 @@
         boolean checkedGrants = false;
         if (checkUser) {
             // Looking for cross-user grants before enforcing the typical cross-users permissions
-            int tmpTargetUserId = mUserController.unsafeConvertIncomingUserLocked(userId);
+            int tmpTargetUserId = mUserController.unsafeConvertIncomingUser(userId);
             if (tmpTargetUserId != UserHandle.getUserId(callingUid)) {
                 if (checkAuthorityGrants(callingUid, cpi, tmpTargetUserId, checkUser)) {
                     return null;
@@ -11527,7 +11635,7 @@
 
                 // Make sure that the user who owns this provider is running.  If not,
                 // we don't want to allow it to run.
-                if (!mUserController.isUserRunningLocked(userId, 0)) {
+                if (!mUserController.isUserRunning(userId, 0)) {
                     Slog.w(TAG, "Unable to launch app "
                             + cpi.applicationInfo.packageName + "/"
                             + cpi.applicationInfo.uid + " for provider "
@@ -12073,7 +12181,7 @@
         //mUsageStatsService.monitorPackages();
     }
 
-    private void startPersistentApps(int matchFlags) {
+    void startPersistentApps(int matchFlags) {
         if (mFactoryTest == FactoryTest.FACTORY_TEST_LOW_LEVEL) return;
 
         synchronized (this) {
@@ -12094,7 +12202,7 @@
      * When a user is unlocked, we need to install encryption-unaware providers
      * belonging to any running apps.
      */
-    private void installEncryptionUnawareProviders(int userId) {
+    void installEncryptionUnawareProviders(int userId) {
         // We're only interested in providers that are encryption unaware, and
         // we don't care about uninstalled apps, since there's no way they're
         // running at this point.
@@ -12157,9 +12265,7 @@
         int callingPid = Binder.getCallingPid();
         long ident = 0;
         boolean clearedIdentity = false;
-        synchronized (this) {
-            userId = mUserController.unsafeConvertIncomingUserLocked(userId);
-        }
+        userId = mUserController.unsafeConvertIncomingUser(userId);
         if (canClearIdentity(callingPid, callingUid, userId)) {
             clearedIdentity = true;
             ident = Binder.clearCallingIdentity();
@@ -12399,19 +12505,14 @@
 
     void onWakefulnessChanged(int wakefulness) {
         synchronized(this) {
+            boolean wasAwake = mWakefulness == PowerManagerInternal.WAKEFULNESS_AWAKE;
+            boolean isAwake = wakefulness == PowerManagerInternal.WAKEFULNESS_AWAKE;
             mWakefulness = wakefulness;
 
-            // Also update state in a special way for running foreground services UI.
-            switch (mWakefulness) {
-                case PowerManagerInternal.WAKEFULNESS_ASLEEP:
-                case PowerManagerInternal.WAKEFULNESS_DREAMING:
-                case PowerManagerInternal.WAKEFULNESS_DOZING:
-                    mServices.updateScreenStateLocked(false /* screenOn */);
-                    break;
-                case PowerManagerInternal.WAKEFULNESS_AWAKE:
-                default:
-                    mServices.updateScreenStateLocked(true /* screenOn */);
-                    break;
+            if (wasAwake != isAwake) {
+                // Also update state in a special way for running foreground services UI.
+                mServices.updateScreenStateLocked(isAwake);
+                sendNotifyVrManagerOfSleepState(!isAwake);
             }
         }
     }
@@ -12447,7 +12548,6 @@
             }
             mStackSupervisor.applySleepTokensLocked(true /* applyToStacks */);
             if (wasSleeping) {
-                sendNotifyVrManagerOfSleepState(false);
                 updateOomAdjLocked();
             }
         } else if (!mSleeping && shouldSleep) {
@@ -12457,7 +12557,6 @@
             }
             mTopProcessState = ActivityManager.PROCESS_STATE_TOP_SLEEPING;
             mStackSupervisor.goingToSleepLocked();
-            sendNotifyVrManagerOfSleepState(true);
             updateOomAdjLocked();
         }
     }
@@ -12551,7 +12650,7 @@
     }
 
     @Override
-    public void setLockScreenShown(boolean showing) {
+    public void setLockScreenShown(boolean showing, int secondaryDisplayShowing) {
         if (checkCallingPermission(android.Manifest.permission.DEVICE_POWER)
                 != PackageManager.PERMISSION_GRANTED) {
             throw new SecurityException("Requires permission "
@@ -12561,11 +12660,12 @@
         synchronized(this) {
             long ident = Binder.clearCallingIdentity();
             try {
-                mKeyguardController.setKeyguardShown(showing);
+                mKeyguardController.setKeyguardShown(showing, secondaryDisplayShowing);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
         }
+        sendNotifyVrManagerOfKeyguardState(showing);
     }
 
     @Override
@@ -12584,7 +12684,7 @@
                 if (mUserController.shouldConfirmCredentials(userId)) {
                     if (mKeyguardController.isKeyguardLocked()) {
                         // Showing launcher to avoid user entering credential twice.
-                        final int currentUserId = mUserController.getCurrentUserIdLocked();
+                        final int currentUserId = mUserController.getCurrentUserId();
                         startHomeActivityLocked(currentUserId, "notifyLockedProfile");
                     }
                     mStackSupervisor.lockAllProfileTasks(userId);
@@ -13982,10 +14082,10 @@
                 mContext.getPackageManager().hasSystemFeature(FEATURE_FREEFORM_WINDOW_MANAGEMENT)
                         || Settings.Global.getInt(
                                 resolver, DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT, 0) != 0;
-        final boolean supportsPictureInPicture =
-                mContext.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE);
 
         final boolean supportsMultiWindow = ActivityManager.supportsMultiWindow(mContext);
+        final boolean supportsPictureInPicture = supportsMultiWindow &&
+                mContext.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE);
         final boolean supportsSplitScreenMultiWindow =
                 ActivityManager.supportsSplitScreenMultiWindow(mContext);
         final boolean supportsMultiDisplay = mContext.getPackageManager()
@@ -14164,9 +14264,8 @@
         }
 
         retrieveSettings();
-        final int currentUserId;
+        final int currentUserId = mUserController.getCurrentUserId();
         synchronized (this) {
-            currentUserId = mUserController.getCurrentUserIdLocked();
             readGrantedUriPermissionsLocked();
         }
 
@@ -14245,7 +14344,7 @@
                 Binder.restoreCallingIdentity(ident);
             }
             mStackSupervisor.resumeFocusedStackTopActivityLocked();
-            mUserController.sendUserSwitchBroadcastsLocked(-1, currentUserId);
+            mUserController.sendUserSwitchBroadcasts(-1, currentUserId);
             traceLog.traceEnd(); // ActivityManagerStartApps
             traceLog.traceEnd(); // PhaseActivityManagerReady
         }
@@ -14597,6 +14696,9 @@
                 }
                 sb.append("\n");
             }
+            if (process.info.isInstantApp()) {
+                sb.append("Instant-App: true\n");
+            }
         }
     }
 
@@ -14943,6 +15045,13 @@
 
     @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        PriorityDump.dump(mPriorityDumper, fd, pw, args);
+    }
+
+    /**
+     * Wrapper function to print out debug data filtered by specified arguments.
+    */
+    private void doDump(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, pw)) return;
 
         boolean dumpAll = false;
@@ -14951,6 +15060,7 @@
         boolean dumpCheckinFormat = false;
         boolean dumpVisibleStacksOnly = false;
         boolean dumpFocusedStackOnly = false;
+        boolean useProto = false;
         String dumpPackage = null;
 
         int opti = 0;
@@ -14984,12 +15094,26 @@
             } else if ("-h".equals(opt)) {
                 ActivityManagerShellCommand.dumpHelp(pw, true);
                 return;
+            } else if ("--proto".equals(opt)) {
+                useProto = true;
             } else {
                 pw.println("Unknown argument: " + opt + "; use -h for help");
             }
         }
 
         long origId = Binder.clearCallingIdentity();
+
+        if (useProto) {
+            //TODO: Options when dumping proto
+            final ProtoOutputStream proto = new ProtoOutputStream(fd);
+            synchronized (this) {
+                writeActivitiesToProtoLocked(proto);
+            }
+            proto.flush();
+            Binder.restoreCallingIdentity(origId);
+            return;
+        }
+
         boolean more = false;
         // Is the caller requesting to dump a particular piece of data?
         if (opti < args.length) {
@@ -15333,6 +15457,10 @@
         Binder.restoreCallingIdentity(origId);
     }
 
+    private void writeActivitiesToProtoLocked(ProtoOutputStream proto) {
+        mStackSupervisor.writeToProto(proto, ACTIVITIES);
+    }
+
     private void dumpLastANRLocked(PrintWriter pw) {
         pw.println("ACTIVITY MANAGER LAST ANR (dumpsys activity lastanr)");
         if (mLastANRState == null) {
@@ -19047,7 +19175,7 @@
         // If not, we will just skip it. Make an exception for shutdown broadcasts
         // and upgrade steps.
 
-        if (userId != UserHandle.USER_ALL && !mUserController.isUserRunningLocked(userId, 0)) {
+        if (userId != UserHandle.USER_ALL && !mUserController.isUserRunning(userId, 0)) {
             if ((callingUid != SYSTEM_UID
                     || (intent.getFlags() & Intent.FLAG_RECEIVER_BOOT_UPGRADE) == 0)
                     && !Intent.ACTION_SHUTDOWN.equals(intent.getAction())) {
@@ -19466,7 +19594,7 @@
         int[] users;
         if (userId == UserHandle.USER_ALL) {
             // Caller wants broadcast to go to all started users.
-            users = mUserController.getStartedUserArrayLocked();
+            users = mUserController.getStartedUserArray();
         } else {
             // Caller wants broadcast to go to one specific user.
             users = new int[] {userId};
@@ -20092,12 +20220,20 @@
     }
 
     @Override
-    public int getFocusedStackId() throws RemoteException {
-        ActivityStack focusedStack = getFocusedStack();
-        if (focusedStack != null) {
-            return focusedStack.getStackId();
+    public StackInfo getFocusedStackInfo() throws RemoteException {
+        enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "getStackInfo()");
+        long ident = Binder.clearCallingIdentity();
+        try {
+            synchronized (this) {
+                ActivityStack focusedStack = getFocusedStack();
+                if (focusedStack != null) {
+                    return mStackSupervisor.getStackInfo(focusedStack.mStackId);
+                }
+                return null;
+            }
+        } finally {
+            Binder.restoreCallingIdentity(ident);
         }
-        return -1;
     }
 
     public Configuration getConfiguration() {
@@ -20123,15 +20259,20 @@
      *       activity and clearing the task at the same time.
      */
     @Override
+    // TODO: API should just be about changing windowing modes...
     public void moveTasksToFullscreenStack(int fromStackId, boolean onTop) {
         enforceCallingPermission(MANAGE_ACTIVITY_STACKS, "moveTasksToFullscreenStack()");
-        if (StackId.isHomeOrRecentsStack(fromStackId)) {
-            throw new IllegalArgumentException("You can't move tasks from the home/recents stack.");
-        }
         synchronized (this) {
             final long origId = Binder.clearCallingIdentity();
             try {
-                mStackSupervisor.moveTasksToFullscreenStackLocked(fromStackId, onTop);
+                final ActivityStack stack = mStackSupervisor.getStack(fromStackId);
+                if (stack != null){
+                    if (!stack.isActivityTypeStandardOrUndefined()) {
+                        throw new IllegalArgumentException(
+                                "You can't move tasks from non-standard stacks.");
+                    }
+                    mStackSupervisor.moveTasksToFullscreenStackLocked(stack, onTop);
+                }
             } finally {
                 Binder.restoreCallingIdentity(origId);
             }
@@ -20229,7 +20370,7 @@
 
     void updateUserConfigurationLocked() {
         final Configuration configuration = new Configuration(getGlobalConfiguration());
-        final int currentUserId = mUserController.getCurrentUserIdLocked();
+        final int currentUserId = mUserController.getCurrentUserId();
         Settings.System.adjustConfigurationForUser(mContext.getContentResolver(), configuration,
                 currentUserId, Settings.System.canWrite(mContext));
         updateConfigurationLocked(configuration, null /* starting */, false /* initLocale */,
@@ -20340,7 +20481,7 @@
         Slog.i(TAG, "Config changes=" + Integer.toHexString(changes) + " " + mTempConfig);
         // TODO(multi-display): Update UsageEvents#Event to include displayId.
         mUsageStatsService.reportConfigurationChange(mTempConfig,
-                mUserController.getCurrentUserIdLocked());
+                mUserController.getCurrentUserId());
 
         // TODO: If our config changes, should we auto dismiss any currently showing dialogs?
         mShowDialogs = shouldShowDialogs(mTempConfig);
@@ -22271,7 +22412,7 @@
             String authority) {
         if (app == null) return;
         if (app.curProcState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
-            UserState userState = mUserController.getStartedUserStateLocked(app.userId);
+            UserState userState = mUserController.getStartedUserState(app.userId);
             if (userState == null) return;
             final long now = SystemClock.elapsedRealtime();
             Long lastReported = userState.mProviderLastReportedFg.get(authority);
@@ -23566,54 +23707,7 @@
 
     @Override
     public boolean switchUser(final int targetUserId) {
-        enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, targetUserId);
-        int currentUserId;
-        UserInfo targetUserInfo;
-        synchronized (this) {
-            currentUserId = mUserController.getCurrentUserIdLocked();
-            targetUserInfo = mUserController.getUserInfo(targetUserId);
-            if (targetUserId == currentUserId) {
-                Slog.i(TAG, "user #" + targetUserId + " is already the current user");
-                return true;
-            }
-            if (targetUserInfo == null) {
-                Slog.w(TAG, "No user info for user #" + targetUserId);
-                return false;
-            }
-            if (!targetUserInfo.isDemo() && UserManager.isDeviceInDemoMode(mContext)) {
-                Slog.w(TAG, "Cannot switch to non-demo user #" + targetUserId
-                        + " when device is in demo mode");
-                return false;
-            }
-            if (!targetUserInfo.supportsSwitchTo()) {
-                Slog.w(TAG, "Cannot switch to User #" + targetUserId + ": not supported");
-                return false;
-            }
-            if (targetUserInfo.isManagedProfile()) {
-                Slog.w(TAG, "Cannot switch to User #" + targetUserId + ": not a full user");
-                return false;
-            }
-            mUserController.setTargetUserIdLocked(targetUserId);
-        }
-        if (mUserController.mUserSwitchUiEnabled) {
-            UserInfo currentUserInfo = mUserController.getUserInfo(currentUserId);
-            Pair<UserInfo, UserInfo> userNames = new Pair<>(currentUserInfo, targetUserInfo);
-            mUiHandler.removeMessages(START_USER_SWITCH_UI_MSG);
-            mUiHandler.sendMessage(mHandler.obtainMessage(
-                    START_USER_SWITCH_UI_MSG, userNames));
-        } else {
-            mHandler.removeMessages(START_USER_SWITCH_FG_MSG);
-            mHandler.sendMessage(mHandler.obtainMessage(
-                    START_USER_SWITCH_FG_MSG, targetUserId, 0));
-        }
-        return true;
-    }
-
-    void scheduleStartProfilesLocked() {
-        if (!mHandler.hasMessages(START_PROFILES_MSG)) {
-            mHandler.sendMessageDelayed(mHandler.obtainMessage(START_PROFILES_MSG),
-                    DateUtils.SECOND_IN_MILLIS);
-        }
+        return mUserController.switchUser(targetUserId);
     }
 
     @Override
@@ -23627,10 +23721,8 @@
     }
 
     String getStartedUserState(int userId) {
-        synchronized (this) {
-            final UserState userState = mUserController.getStartedUserStateLocked(userId);
-            return UserState.stateToString(userState.state);
-        }
+        final UserState userState = mUserController.getStartedUserState(userId);
+        return UserState.stateToString(userState.state);
     }
 
     @Override
@@ -23645,9 +23737,7 @@
             Slog.w(TAG, msg);
             throw new SecurityException(msg);
         }
-        synchronized (this) {
-            return mUserController.isUserRunningLocked(userId, flags);
-        }
+        return mUserController.isUserRunning(userId, flags);
     }
 
     @Override
@@ -23661,9 +23751,7 @@
             Slog.w(TAG, msg);
             throw new SecurityException(msg);
         }
-        synchronized (this) {
-            return mUserController.getStartedUserArrayLocked();
-        }
+        return mUserController.getStartedUserArray();
     }
 
     @Override
@@ -23684,9 +23772,7 @@
     }
 
     public boolean isUserStopped(int userId) {
-        synchronized (this) {
-            return mUserController.getStartedUserStateLocked(userId) == null;
-        }
+        return mUserController.getStartedUserState(userId) == null;
     }
 
     ActivityInfo getActivityInfoForUser(ActivityInfo aInfo, int userId) {
@@ -24025,7 +24111,7 @@
         @Override
         public void notifyKeyguardTrustedChanged() {
             synchronized (ActivityManagerService.this) {
-                if (mKeyguardController.isKeyguardShowing()) {
+                if (mKeyguardController.isKeyguardShowing(DEFAULT_DISPLAY)) {
                     mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
                 }
             }
@@ -24274,7 +24360,7 @@
                 permission.INTERACT_ACROSS_USERS_FULL, "getLastResumedActivityUserId()");
         synchronized (this) {
             if (mLastResumedActivity == null) {
-                return mUserController.getCurrentUserIdLocked();
+                return mUserController.getCurrentUserId();
             }
             return mLastResumedActivity.userId;
         }
@@ -24481,7 +24567,7 @@
                 if (updateFrameworkRes || packagesToUpdate.contains(packageName)) {
                     try {
                         final ApplicationInfo ai = AppGlobals.getPackageManager()
-                                .getApplicationInfo(packageName, 0 /*flags*/, app.userId);
+                                .getApplicationInfo(packageName, STOCK_PM_FLAGS, app.userId);
                         if (ai != null) {
                             app.thread.scheduleApplicationInfoChanged(ai);
                         }
diff --git a/com/android/server/am/ActivityManagerShellCommand.java b/com/android/server/am/ActivityManagerShellCommand.java
index 6901d2d..4c93423 100644
--- a/com/android/server/am/ActivityManagerShellCommand.java
+++ b/com/android/server/am/ActivityManagerShellCommand.java
@@ -75,6 +75,9 @@
 import static android.app.ActivityManager.RESIZE_MODE_USER;
 import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
 import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.view.Display.INVALID_DISPLAY;
 
 import static com.android.server.am.TaskRecord.INVALID_TASK_ID;
@@ -115,7 +118,8 @@
     private boolean mStreaming;   // Streaming the profiling output to a file.
     private String mAgent;  // Agent to attach on startup.
     private int mDisplayId;
-    private int mStackId;
+    private int mWindowingMode;
+    private int mActivityType;
     private int mTaskId;
     private boolean mIsTaskOverlay;
 
@@ -271,7 +275,8 @@
         mStreaming = false;
         mUserId = defUser;
         mDisplayId = INVALID_DISPLAY;
-        mStackId = INVALID_STACK_ID;
+        mWindowingMode = WINDOWING_MODE_UNDEFINED;
+        mActivityType = ACTIVITY_TYPE_UNDEFINED;
         mTaskId = INVALID_TASK_ID;
         mIsTaskOverlay = false;
 
@@ -308,8 +313,10 @@
                     mReceiverPermission = getNextArgRequired();
                 } else if (opt.equals("--display")) {
                     mDisplayId = Integer.parseInt(getNextArgRequired());
-                } else if (opt.equals("--stack")) {
-                    mStackId = Integer.parseInt(getNextArgRequired());
+                } else if (opt.equals("--windowingMode")) {
+                    mWindowingMode = Integer.parseInt(getNextArgRequired());
+                } else if (opt.equals("--activityType")) {
+                    mActivityType = Integer.parseInt(getNextArgRequired());
                 } else if (opt.equals("--task")) {
                     mTaskId = Integer.parseInt(getNextArgRequired());
                 } else if (opt.equals("--task-overlay")) {
@@ -396,9 +403,17 @@
                 options = ActivityOptions.makeBasic();
                 options.setLaunchDisplayId(mDisplayId);
             }
-            if (mStackId != INVALID_STACK_ID) {
-                options = ActivityOptions.makeBasic();
-                options.setLaunchStackId(mStackId);
+            if (mWindowingMode != WINDOWING_MODE_UNDEFINED) {
+                if (options == null) {
+                    options = ActivityOptions.makeBasic();
+                }
+                options.setLaunchWindowingMode(mWindowingMode);
+            }
+            if (mActivityType != ACTIVITY_TYPE_UNDEFINED) {
+                if (options == null) {
+                    options = ActivityOptions.makeBasic();
+                }
+                options.setLaunchActivityType(mActivityType);
             }
             if (mTaskId != INVALID_TASK_ID) {
                 options = ActivityOptions.makeBasic();
@@ -2099,9 +2114,9 @@
     }
 
     int runStackInfo(PrintWriter pw) throws RemoteException {
-        String stackIdStr = getNextArgRequired();
-        int stackId = Integer.parseInt(stackIdStr);
-        ActivityManager.StackInfo info = mInterface.getStackInfo(stackId);
+        int windowingMode = Integer.parseInt(getNextArgRequired());
+        int activityType = Integer.parseInt(getNextArgRequired());
+        ActivityManager.StackInfo info = mInterface.getStackInfo(windowingMode, activityType);
         pw.println(info);
         return 0;
     }
@@ -2135,7 +2150,8 @@
         final String delayStr = getNextArg();
         final int delayMs = (delayStr != null) ? Integer.parseInt(delayStr) : 0;
 
-        ActivityManager.StackInfo info = mInterface.getStackInfo(DOCKED_STACK_ID);
+        ActivityManager.StackInfo info = mInterface.getStackInfo(
+                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_UNDEFINED);
         if (info == null) {
             err.println("Docked stack doesn't exist");
             return -1;
@@ -2238,10 +2254,6 @@
             return runTaskResizeable(pw);
         } else if (op.equals("resize")) {
             return runTaskResize(pw);
-        } else if (op.equals("drag-task-test")) {
-            return runTaskDragTaskTest(pw);
-        } else if (op.equals("size-task-test")) {
-            return runTaskSizeTaskTest(pw);
         } else if (op.equals("focus")) {
             return runTaskFocus(pw);
         } else {
@@ -2294,58 +2306,6 @@
         }
     }
 
-    int runTaskDragTaskTest(PrintWriter pw) throws RemoteException {
-        final int taskId = Integer.parseInt(getNextArgRequired());
-        final int stepSize = Integer.parseInt(getNextArgRequired());
-        final String delayStr = getNextArg();
-        final int delay_ms = (delayStr != null) ? Integer.parseInt(delayStr) : 0;
-        final ActivityManager.StackInfo stackInfo;
-        Rect taskBounds;
-        stackInfo = mInterface.getStackInfo(mInterface.getFocusedStackId());
-        taskBounds = mInterface.getTaskBounds(taskId);
-        final Rect stackBounds = stackInfo.bounds;
-        int travelRight = stackBounds.width() - taskBounds.width();
-        int travelLeft = -travelRight;
-        int travelDown = stackBounds.height() - taskBounds.height();
-        int travelUp = -travelDown;
-        int passes = 0;
-
-        // We do 2 passes to get back to the original location of the task.
-        while (passes < 2) {
-            // Move right
-            pw.println("Moving right...");
-            pw.flush();
-            travelRight = moveTask(taskId, taskBounds, stackBounds, stepSize,
-                    travelRight, MOVING_FORWARD, MOVING_HORIZONTALLY, delay_ms);
-            pw.println("Still need to travel right by " + travelRight);
-
-            // Move down
-            pw.println("Moving down...");
-            pw.flush();
-            travelDown = moveTask(taskId, taskBounds, stackBounds, stepSize,
-                    travelDown, MOVING_FORWARD, !MOVING_HORIZONTALLY, delay_ms);
-            pw.println("Still need to travel down by " + travelDown);
-
-            // Move left
-            pw.println("Moving left...");
-            pw.flush();
-            travelLeft = moveTask(taskId, taskBounds, stackBounds, stepSize,
-                    travelLeft, !MOVING_FORWARD, MOVING_HORIZONTALLY, delay_ms);
-            pw.println("Still need to travel left by " + travelLeft);
-
-            // Move up
-            pw.println("Moving up...");
-            pw.flush();
-            travelUp = moveTask(taskId, taskBounds, stackBounds, stepSize,
-                    travelUp, !MOVING_FORWARD, !MOVING_HORIZONTALLY, delay_ms);
-            pw.println("Still need to travel up by " + travelUp);
-
-            taskBounds = mInterface.getTaskBounds(taskId);
-            passes++;
-        }
-        return 0;
-    }
-
     int moveTask(int taskId, Rect taskRect, Rect stackRect, int stepSize,
             int maxToTravel, boolean movingForward, boolean horizontal, int delay_ms)
             throws RemoteException {
@@ -2408,133 +2368,6 @@
         return stepSize;
     }
 
-    int runTaskSizeTaskTest(PrintWriter pw) throws RemoteException {
-        final int taskId = Integer.parseInt(getNextArgRequired());
-        final int stepSize = Integer.parseInt(getNextArgRequired());
-        final String delayStr = getNextArg();
-        final int delay_ms = (delayStr != null) ? Integer.parseInt(delayStr) : 0;
-        final ActivityManager.StackInfo stackInfo;
-        final Rect initialTaskBounds;
-        stackInfo = mInterface.getStackInfo(mInterface.getFocusedStackId());
-        initialTaskBounds = mInterface.getTaskBounds(taskId);
-        final Rect stackBounds = stackInfo.bounds;
-        stackBounds.inset(STACK_BOUNDS_INSET, STACK_BOUNDS_INSET);
-        final Rect currentTaskBounds = new Rect(initialTaskBounds);
-
-        // Size by top-left
-        pw.println("Growing top-left");
-        pw.flush();
-        do {
-            currentTaskBounds.top -= getStepSize(
-                    currentTaskBounds.top, stackBounds.top, stepSize, GREATER_THAN_TARGET);
-
-            currentTaskBounds.left -= getStepSize(
-                    currentTaskBounds.left, stackBounds.left, stepSize, GREATER_THAN_TARGET);
-
-            taskResize(taskId, currentTaskBounds, delay_ms, true);
-        } while (stackBounds.top < currentTaskBounds.top
-                || stackBounds.left < currentTaskBounds.left);
-
-        // Back to original size
-        pw.println("Shrinking top-left");
-        pw.flush();
-        do {
-            currentTaskBounds.top += getStepSize(
-                    currentTaskBounds.top, initialTaskBounds.top, stepSize, !GREATER_THAN_TARGET);
-
-            currentTaskBounds.left += getStepSize(
-                    currentTaskBounds.left, initialTaskBounds.left, stepSize, !GREATER_THAN_TARGET);
-
-            taskResize(taskId, currentTaskBounds, delay_ms, true);
-        } while (initialTaskBounds.top > currentTaskBounds.top
-                || initialTaskBounds.left > currentTaskBounds.left);
-
-        // Size by top-right
-        pw.println("Growing top-right");
-        pw.flush();
-        do {
-            currentTaskBounds.top -= getStepSize(
-                    currentTaskBounds.top, stackBounds.top, stepSize, GREATER_THAN_TARGET);
-
-            currentTaskBounds.right += getStepSize(
-                    currentTaskBounds.right, stackBounds.right, stepSize, !GREATER_THAN_TARGET);
-
-            taskResize(taskId, currentTaskBounds, delay_ms, true);
-        } while (stackBounds.top < currentTaskBounds.top
-                || stackBounds.right > currentTaskBounds.right);
-
-        // Back to original size
-        pw.println("Shrinking top-right");
-        pw.flush();
-        do {
-            currentTaskBounds.top += getStepSize(
-                    currentTaskBounds.top, initialTaskBounds.top, stepSize, !GREATER_THAN_TARGET);
-
-            currentTaskBounds.right -= getStepSize(currentTaskBounds.right, initialTaskBounds.right,
-                    stepSize, GREATER_THAN_TARGET);
-
-            taskResize(taskId, currentTaskBounds, delay_ms, true);
-        } while (initialTaskBounds.top > currentTaskBounds.top
-                || initialTaskBounds.right < currentTaskBounds.right);
-
-        // Size by bottom-left
-        pw.println("Growing bottom-left");
-        pw.flush();
-        do {
-            currentTaskBounds.bottom += getStepSize(
-                    currentTaskBounds.bottom, stackBounds.bottom, stepSize, !GREATER_THAN_TARGET);
-
-            currentTaskBounds.left -= getStepSize(
-                    currentTaskBounds.left, stackBounds.left, stepSize, GREATER_THAN_TARGET);
-
-            taskResize(taskId, currentTaskBounds, delay_ms, true);
-        } while (stackBounds.bottom > currentTaskBounds.bottom
-                || stackBounds.left < currentTaskBounds.left);
-
-        // Back to original size
-        pw.println("Shrinking bottom-left");
-        pw.flush();
-        do {
-            currentTaskBounds.bottom -= getStepSize(currentTaskBounds.bottom,
-                    initialTaskBounds.bottom, stepSize, GREATER_THAN_TARGET);
-
-            currentTaskBounds.left += getStepSize(
-                    currentTaskBounds.left, initialTaskBounds.left, stepSize, !GREATER_THAN_TARGET);
-
-            taskResize(taskId, currentTaskBounds, delay_ms, true);
-        } while (initialTaskBounds.bottom < currentTaskBounds.bottom
-                || initialTaskBounds.left > currentTaskBounds.left);
-
-        // Size by bottom-right
-        pw.println("Growing bottom-right");
-        pw.flush();
-        do {
-            currentTaskBounds.bottom += getStepSize(
-                    currentTaskBounds.bottom, stackBounds.bottom, stepSize, !GREATER_THAN_TARGET);
-
-            currentTaskBounds.right += getStepSize(
-                    currentTaskBounds.right, stackBounds.right, stepSize, !GREATER_THAN_TARGET);
-
-            taskResize(taskId, currentTaskBounds, delay_ms, true);
-        } while (stackBounds.bottom > currentTaskBounds.bottom
-                || stackBounds.right > currentTaskBounds.right);
-
-        // Back to original size
-        pw.println("Shrinking bottom-right");
-        pw.flush();
-        do {
-            currentTaskBounds.bottom -= getStepSize(currentTaskBounds.bottom,
-                    initialTaskBounds.bottom, stepSize, GREATER_THAN_TARGET);
-
-            currentTaskBounds.right -= getStepSize(currentTaskBounds.right, initialTaskBounds.right,
-                    stepSize, GREATER_THAN_TARGET);
-
-            taskResize(taskId, currentTaskBounds, delay_ms, true);
-        } while (initialTaskBounds.bottom < currentTaskBounds.bottom
-                || initialTaskBounds.right < currentTaskBounds.right);
-        return 0;
-    }
-
     int runTaskFocus(PrintWriter pw) throws RemoteException {
         final int taskId = Integer.parseInt(getNextArgRequired());
         pw.println("Setting focus to task " + taskId);
@@ -2661,6 +2494,7 @@
             pw.println("  -p: limit output to given package.");
             pw.println("  --checkin: output checkin format, resetting data.");
             pw.println("  --C: output checkin format, not resetting data.");
+            pw.println("  --proto: output dump in protocol buffer format.");
         } else {
             pw.println("Activity manager (activity) commands:");
             pw.println("  help");
@@ -2685,7 +2519,8 @@
             pw.println("      --track-allocation: enable tracking of object allocations");
             pw.println("      --user <USER_ID> | current: Specify which user to run as; if not");
             pw.println("          specified then run as the current user.");
-            pw.println("      --stack <STACK_ID>: Specify into which stack should the activity be put.");
+            pw.println("      --windowingMode <WINDOWING_MODE>: The windowing mode to launch the activity into.");
+            pw.println("      --activityType <ACTIVITY_TYPE>: The activity type to launch the activity as.");
             pw.println("  start-service [--user <USER_ID> | current] <INTENT>");
             pw.println("      Start a Service.  Options are:");
             pw.println("      --user <USER_ID> | current: Specify which user to run as; if not");
@@ -2864,8 +2699,8 @@
             pw.println("           Place <TASK_ID> in <STACK_ID> at <POSITION>");
             pw.println("       list");
             pw.println("           List all of the activity stacks and their sizes.");
-            pw.println("       info <STACK_ID>");
-            pw.println("           Display the information about activity stack <STACK_ID>.");
+            pw.println("       info <WINDOWING_MODE> <ACTIVITY_TYPE>");
+            pw.println("           Display the information about activity stack in <WINDOWING_MODE> and <ACTIVITY_TYPE>.");
             pw.println("       remove <STACK_ID>");
             pw.println("           Remove stack <STACK_ID>.");
             pw.println("  task [COMMAND] [...]: sub-commands for operating on activity tasks.");
@@ -2883,14 +2718,6 @@
             pw.println("           Makes sure <TASK_ID> is in a stack with the specified bounds.");
             pw.println("           Forces the task to be resizeable and creates a stack if no existing stack");
             pw.println("           has the specified bounds.");
-            pw.println("       drag-task-test <TASK_ID> <STEP_SIZE> [DELAY_MS]");
-            pw.println("           Test command for dragging/moving <TASK_ID> by");
-            pw.println("           <STEP_SIZE> increments around the screen applying the optional [DELAY_MS]");
-            pw.println("           between each step.");
-            pw.println("       size-task-test <TASK_ID> <STEP_SIZE> [DELAY_MS]");
-            pw.println("           Test command for sizing <TASK_ID> by <STEP_SIZE>");
-            pw.println("           increments within the screen applying the optional [DELAY_MS] between");
-            pw.println("           each step.");
             pw.println("  update-appinfo <USER_ID> <PACKAGE_NAME> [<PACKAGE_NAME>...]");
             pw.println("      Update the ApplicationInfo objects of the listed packages for <USER_ID>");
             pw.println("      without restarting any processes.");
diff --git a/com/android/server/am/ActivityMetricsLogger.java b/com/android/server/am/ActivityMetricsLogger.java
index 0c8321d..fdcb8c6 100644
--- a/com/android/server/am/ActivityMetricsLogger.java
+++ b/com/android/server/am/ActivityMetricsLogger.java
@@ -2,12 +2,9 @@
 
 import static android.app.ActivityManager.START_SUCCESS;
 import static android.app.ActivityManager.START_TASK_TO_FRONT;
-import static android.app.ActivityManager.StackId.ASSISTANT_STACK_ID;
-import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
 import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
 import static android.app.ActivityManagerInternal.APP_TRANSITION_TIMEOUT;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
@@ -32,13 +29,10 @@
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_TRANSITION_WARM_LAUNCH;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.am.ActivityStack.STACK_INVISIBLE;
 
-import android.app.ActivityManager.StackId;
 import android.content.Context;
 import android.metrics.LogMaker;
 import android.os.SystemClock;
-import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
 
diff --git a/com/android/server/am/ActivityRecord.java b/com/android/server/am/ActivityRecord.java
index 0ccb45f..7b0b942 100644
--- a/com/android/server/am/ActivityRecord.java
+++ b/com/android/server/am/ActivityRecord.java
@@ -17,8 +17,6 @@
 package com.android.server.am;
 
 import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
-import static android.app.ActivityManager.StackId.ASSISTANT_STACK_ID;
-import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
 import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
 import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
 import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
@@ -36,7 +34,6 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.activityTypeToString;
 import static android.content.Intent.ACTION_MAIN;
@@ -89,10 +86,8 @@
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_VISIBILITY;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_CONFIGURATION;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_SAVED_STATE;
-import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_SCREENSHOTS;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_STATES;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_SWITCH;
-import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_THUMBNAILS;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_VISIBILITY;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -107,7 +102,6 @@
 import static com.android.server.am.ActivityStack.LAUNCH_TICK;
 import static com.android.server.am.ActivityStack.LAUNCH_TICK_MSG;
 import static com.android.server.am.ActivityStack.PAUSE_TIMEOUT_MSG;
-import static com.android.server.am.ActivityStack.STACK_INVISIBLE;
 import static com.android.server.am.ActivityStack.STOP_TIMEOUT_MSG;
 import static com.android.server.am.EventLogTags.AM_ACTIVITY_FULLY_DRAWN_TIME;
 import static com.android.server.am.EventLogTags.AM_ACTIVITY_LAUNCH_TIME;
@@ -116,6 +110,16 @@
 import static com.android.server.am.TaskPersister.DEBUG;
 import static com.android.server.am.TaskPersister.IMAGE_EXTENSION;
 import static com.android.server.am.TaskRecord.INVALID_TASK_ID;
+import static com.android.server.am.proto.ActivityRecordProto.CONFIGURATION_CONTAINER;
+import static com.android.server.am.proto.ActivityRecordProto.FRONT_OF_TASK;
+import static com.android.server.am.proto.ActivityRecordProto.IDENTIFIER;
+import static com.android.server.am.proto.ActivityRecordProto.PROC_ID;
+import static com.android.server.am.proto.ActivityRecordProto.STATE;
+import static com.android.server.am.proto.ActivityRecordProto.VISIBLE;
+import static com.android.server.wm.proto.IdentifierProto.HASH_CODE;
+import static com.android.server.wm.proto.IdentifierProto.TITLE;
+import static com.android.server.wm.proto.IdentifierProto.USER_ID;
+
 import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
 import static org.xmlpull.v1.XmlPullParser.END_TAG;
 import static org.xmlpull.v1.XmlPullParser.START_TAG;
@@ -153,6 +157,7 @@
 import android.util.MergedConfiguration;
 import android.util.Slog;
 import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
 import android.view.AppTransitionAnimationSpec;
 import android.view.IAppTransitionAnimationSpecsFuture;
 import android.view.IApplicationToken;
@@ -1038,7 +1043,7 @@
             }
         } else if (realActivity.getClassName().contains(RECENTS_PACKAGE_NAME)) {
             activityType = ACTIVITY_TYPE_RECENTS;
-        } else if (options != null && options.getLaunchStackId() == ASSISTANT_STACK_ID
+        } else if (options != null && options.getLaunchActivityType() == ACTIVITY_TYPE_ASSISTANT
                 && canLaunchAssistActivity(launchedFromPackage)) {
             activityType = ACTIVITY_TYPE_ASSISTANT;
         }
@@ -1062,6 +1067,11 @@
         return getStack() != null ? getStack().mStackId : INVALID_STACK_ID;
     }
 
+    ActivityDisplay getDisplay() {
+        final ActivityStack stack = getStack();
+        return stack != null ? stack.getDisplay() : null;
+    }
+
     boolean changeWindowTranslucency(boolean toOpaque) {
         if (fullscreen == toOpaque) {
             return false;
@@ -1127,10 +1137,12 @@
      * @return whether this activity supports split-screen multi-window and can be put in the docked
      *         stack.
      */
-    boolean supportsSplitScreen() {
+    @Override
+    public boolean supportsSplitScreenWindowingMode() {
         // An activity can not be docked even if it is considered resizeable because it only
         // supports picture-in-picture mode but has a non-resizeable resizeMode
-        return service.mSupportsSplitScreenMultiWindow && supportsResizeableMultiWindow();
+        return super.supportsSplitScreenWindowingMode()
+                && service.mSupportsSplitScreenMultiWindow && supportsResizeableMultiWindow();
     }
 
     /**
@@ -1157,8 +1169,15 @@
      *         can be put a secondary screen.
      */
     boolean canBeLaunchedOnDisplay(int displayId) {
+        final TaskRecord task = getTask();
+
+        // The resizeability of an Activity's parent task takes precendence over the ActivityInfo.
+        // This allows for a non resizable activity to be launched into a resizeable task.
+        final boolean resizeable =
+                task != null ? task.isResizeable() : supportsResizeableMultiWindow();
+
         return service.mStackSupervisor.canPlaceEntityOnDisplay(displayId,
-                supportsResizeableMultiWindow(), launchedFromPid, launchedFromUid, info);
+                resizeable, launchedFromPid, launchedFromUid, info);
     }
 
     /**
@@ -1184,7 +1203,8 @@
 
         boolean isKeyguardLocked = service.isKeyguardLocked();
         boolean isCurrentAppLocked = service.getLockTaskModeState() != LOCK_TASK_MODE_NONE;
-        boolean hasPinnedStack = mStackSupervisor.getStack(PINNED_STACK_ID) != null;
+        final ActivityDisplay display = getDisplay();
+        boolean hasPinnedStack = display != null && display.hasPinnedStack();
         // Don't return early if !isNotLocked, since we want to throw an exception if the activity
         // is in an incorrect state
         boolean isNotLockedOrOnKeyguard = !isKeyguardLocked && !isCurrentAppLocked;
@@ -1530,8 +1550,9 @@
         if (service.mSupportsLeanbackOnly && isVisible && isActivityTypeRecents()) {
             // On devices that support leanback only (Android TV), Recents activity can only be
             // visible if the home stack is the focused stack or we are in split-screen mode.
-            isVisible = mStackSupervisor.getStack(DOCKED_STACK_ID) != null
-                    || mStackSupervisor.isFocusedStack(getStack());
+            final ActivityDisplay display = getDisplay();
+            boolean hasSplitScreenStack = display != null && display.hasSplitScreenStack();
+            isVisible = hasSplitScreenStack || mStackSupervisor.isFocusedStack(getStack());
         }
 
         return isVisible;
@@ -1934,7 +1955,7 @@
 
         return (info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0
                 || (mStackSupervisor.isCurrentProfileLocked(userId)
-                && service.mUserController.isUserRunningLocked(userId, 0 /* flags */));
+                && service.mUserController.isUserRunning(userId, 0 /* flags */));
     }
 
     /**
@@ -2298,7 +2319,7 @@
         // be visible based on the stack, task, and lockscreen state and use that here instead. The
         // method should be based on the logic in ActivityStack.ensureActivitiesVisibleLocked().
         // Skip updating configuration for activity is a stack that shouldn't be visible.
-        if (stack.shouldBeVisible(null /* starting */) == STACK_INVISIBLE) {
+        if (!stack.shouldBeVisible(null /* starting */)) {
             if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION,
                     "Skipping config check invisible stack: " + this);
             return true;
@@ -2770,4 +2791,25 @@
         stringName = sb.toString();
         return toString();
     }
+
+    void writeIdentifierToProto(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+        proto.write(HASH_CODE, System.identityHashCode(this));
+        proto.write(USER_ID, userId);
+        proto.write(TITLE, intent.getComponent().flattenToShortString());
+        proto.end(token);
+    }
+
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+        super.writeToProto(proto, CONFIGURATION_CONTAINER);
+        writeIdentifierToProto(proto, IDENTIFIER);
+        proto.write(STATE, state.toString());
+        proto.write(VISIBLE, visible);
+        proto.write(FRONT_OF_TASK, frontOfTask);
+        if (app != null) {
+            proto.write(PROC_ID, app.pid);
+        }
+        proto.end(token);
+    }
 }
diff --git a/com/android/server/am/ActivityStack.java b/com/android/server/am/ActivityStack.java
index a6a702f..1940ca2 100644
--- a/com/android/server/am/ActivityStack.java
+++ b/com/android/server/am/ActivityStack.java
@@ -16,19 +16,19 @@
 
 package com.android.server.am;
 
-import static android.app.ActivityManager.StackId.ASSISTANT_STACK_ID;
 import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
 import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
 import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.HOME_STACK_ID;
 import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
 import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
-import static android.app.ActivityManager.StackId.getActivityTypeForStackId;
-import static android.app.ActivityManager.StackId.getWindowingModeForStackId;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.content.pm.ActivityInfo.CONFIG_SCREEN_LAYOUT;
 import static android.content.pm.ActivityInfo.FLAG_RESUME_WHILE_PAUSING;
@@ -37,6 +37,8 @@
 import static android.view.Display.FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD;
 
 import static android.view.Display.INVALID_DISPLAY;
+import static com.android.server.am.ActivityDisplay.POSITION_BOTTOM;
+import static com.android.server.am.ActivityDisplay.POSITION_TOP;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ADD_REMOVE;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALL;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_APP;
@@ -74,10 +76,16 @@
 import static com.android.server.am.ActivityStack.ActivityState.STOPPED;
 import static com.android.server.am.ActivityStack.ActivityState.STOPPING;
 import static com.android.server.am.ActivityStackSupervisor.FindTaskResult;
-import static com.android.server.am.ActivityStackSupervisor.ON_TOP;
 import static com.android.server.am.ActivityStackSupervisor.PAUSE_IMMEDIATELY;
 import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS;
 import static com.android.server.am.ActivityStackSupervisor.REMOVE_FROM_RECENTS;
+import static com.android.server.am.proto.ActivityStackProto.BOUNDS;
+import static com.android.server.am.proto.ActivityStackProto.CONFIGURATION_CONTAINER;
+import static com.android.server.am.proto.ActivityStackProto.DISPLAY_ID;
+import static com.android.server.am.proto.ActivityStackProto.FULLSCREEN;
+import static com.android.server.am.proto.ActivityStackProto.ID;
+import static com.android.server.am.proto.ActivityStackProto.RESUMED_ACTIVITY;
+import static com.android.server.am.proto.ActivityStackProto.TASKS;
 import static com.android.server.wm.AppTransition.TRANSIT_ACTIVITY_CLOSE;
 import static com.android.server.wm.AppTransition.TRANSIT_ACTIVITY_OPEN;
 import static com.android.server.wm.AppTransition.TRANSIT_NONE;
@@ -86,6 +94,7 @@
 import static com.android.server.wm.AppTransition.TRANSIT_TASK_OPEN_BEHIND;
 import static com.android.server.wm.AppTransition.TRANSIT_TASK_TO_BACK;
 import static com.android.server.wm.AppTransition.TRANSIT_TASK_TO_FRONT;
+
 import static java.lang.Integer.MAX_VALUE;
 
 import android.app.Activity;
@@ -101,7 +110,6 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.res.Configuration;
-import android.graphics.Point;
 import android.graphics.Rect;
 import android.net.Uri;
 import android.os.Binder;
@@ -122,6 +130,7 @@
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.proto.ProtoOutputStream;
 import android.view.Display;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -231,11 +240,6 @@
         DESTROYED
     }
 
-    // Stack is not considered visible.
-    static final int STACK_INVISIBLE = 0;
-    // Stack is considered visible
-    static final int STACK_VISIBLE = 1;
-
     @VisibleForTesting
     /* The various modes for the method {@link #removeTask}. */
     // Task is being completely removed from all stacks in the system.
@@ -258,7 +262,6 @@
     final ActivityManagerService mService;
     private final WindowManagerService mWindowManager;
     T mWindowContainerController;
-    private final RecentTasks mRecentTasks;
 
     /**
      * The back history of all previous (and possibly still
@@ -341,9 +344,6 @@
     int mCurrentUser;
 
     final int mStackId;
-    /** The other stacks, in order, on the attached display. Updated at attach/detach time. */
-    // TODO: This list doesn't belong here...
-    ArrayList<ActivityStack> mStacks;
     /** The attached Display's unique identifier, or -1 if detached */
     int mDisplayId;
 
@@ -452,49 +452,35 @@
         return count;
     }
 
-    ActivityStack(ActivityStackSupervisor.ActivityDisplay display, int stackId,
-            ActivityStackSupervisor supervisor, RecentTasks recentTasks, boolean onTop) {
+    ActivityStack(ActivityDisplay display, int stackId, ActivityStackSupervisor supervisor,
+            int windowingMode, int activityType, boolean onTop) {
         mStackSupervisor = supervisor;
         mService = supervisor.mService;
         mHandler = new ActivityStackHandler(mService.mHandler.getLooper());
         mWindowManager = mService.mWindowManager;
         mStackId = stackId;
-        mCurrentUser = mService.mUserController.getCurrentUserIdLocked();
-        mRecentTasks = recentTasks;
+        mCurrentUser = mService.mUserController.getCurrentUserId();
         mTaskPositioner = mStackId == FREEFORM_WORKSPACE_STACK_ID
                 ? new LaunchingTaskPositioner() : null;
         mTmpRect2.setEmpty();
-        updateOverrideConfiguration();
+        setWindowingMode(windowingMode);
+        setActivityType(activityType);
         mWindowContainerController = createStackWindowController(display.mDisplayId, onTop,
                 mTmpRect2);
-        mStackSupervisor.mStacks.put(mStackId, this);
         postAddToDisplay(display, mTmpRect2.isEmpty() ? null : mTmpRect2, onTop);
     }
 
     T createStackWindowController(int displayId, boolean onTop, Rect outBounds) {
-        return (T) new StackWindowController(mStackId, this, displayId, onTop, outBounds);
+        return (T) new StackWindowController(mStackId, this, displayId, onTop, outBounds,
+                mStackSupervisor.mWindowManager);
     }
 
     T getWindowContainerController() {
         return mWindowContainerController;
     }
 
-    // TODO: Not needed once we are no longer using stack ids as the override config. can be passed
-    // in.
-    private void updateOverrideConfiguration() {
-        final int windowingMode = getWindowingModeForStackId(
-                mStackId, mStackSupervisor.getStack(DOCKED_STACK_ID) != null);
-        if (windowingMode != WINDOWING_MODE_UNDEFINED) {
-            setWindowingMode(windowingMode);
-        }
-        final int activityType = getActivityTypeForStackId(mStackId);
-        if (activityType != ACTIVITY_TYPE_UNDEFINED) {
-            setActivityType(activityType);
-        }
-    }
-
     /** Adds the stack to specified display and calls WindowManager to do the same. */
-    void reparent(ActivityStackSupervisor.ActivityDisplay activityDisplay, boolean onTop) {
+    void reparent(ActivityDisplay activityDisplay, boolean onTop) {
         removeFromDisplay();
         mTmpRect2.setEmpty();
         postAddToDisplay(activityDisplay, mTmpRect2.isEmpty() ? null : mTmpRect2, onTop);
@@ -512,10 +498,8 @@
      * @param activityDisplay New display to which this stack was attached.
      * @param bounds Updated bounds.
      */
-    private void postAddToDisplay(ActivityStackSupervisor.ActivityDisplay activityDisplay,
-            Rect bounds, boolean onTop) {
+    private void postAddToDisplay(ActivityDisplay activityDisplay, Rect bounds, boolean onTop) {
         mDisplayId = activityDisplay.mDisplayId;
-        mStacks = activityDisplay.mStacks;
         mBounds = bounds != null ? new Rect(bounds) : null;
         mFullscreen = mBounds == null;
         if (mTaskPositioner != null) {
@@ -524,7 +508,7 @@
         }
         onParentChanged();
 
-        activityDisplay.attachStack(this, findStackInsertIndex(onTop));
+        activityDisplay.addChild(this, onTop ? POSITION_TOP : POSITION_BOTTOM);
         if (mStackId == DOCKED_STACK_ID) {
             // If we created a docked stack we want to resize it so it resizes all other stacks
             // in the system.
@@ -538,42 +522,36 @@
      * either destroyed completely or re-parented.
      */
     private void removeFromDisplay() {
-        final ActivityStackSupervisor.ActivityDisplay display = getDisplay();
-        if (display != null) {
-            display.detachStack(this);
-        }
-        mDisplayId = INVALID_DISPLAY;
-        mStacks = null;
-        if (mTaskPositioner != null) {
-            mTaskPositioner.reset();
-        }
-        if (mStackId == DOCKED_STACK_ID) {
+        if (getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
             // If we removed a docked stack we want to resize it so it resizes all other stacks
             // in the system to fullscreen.
             mStackSupervisor.resizeDockedStackLocked(
                     null, null, null, null, null, PRESERVE_WINDOWS);
         }
+        final ActivityDisplay display = getDisplay();
+        if (display != null) {
+            display.removeChild(this);
+        }
+        mDisplayId = INVALID_DISPLAY;
+        if (mTaskPositioner != null) {
+            mTaskPositioner.reset();
+        }
     }
 
     /** Removes the stack completely. Also calls WindowManager to do the same on its side. */
     void remove() {
         removeFromDisplay();
-        mStackSupervisor.mStacks.remove(mStackId);
         mWindowContainerController.removeContainer();
         mWindowContainerController = null;
         onParentChanged();
     }
 
-    ActivityStackSupervisor.ActivityDisplay getDisplay() {
+    ActivityDisplay getDisplay() {
         return mStackSupervisor.getActivityDisplay(mDisplayId);
     }
 
-    void getDisplaySize(Point out) {
-        getDisplay().mDisplay.getSize(out);
-    }
-
     /**
-     * @see ActivityStack.getStackDockedModeBounds(Rect, Rect, Rect, boolean)
+     * @see #getStackDockedModeBounds(Rect, Rect, Rect, boolean)
      */
     void getStackDockedModeBounds(Rect currentTempTaskBounds, Rect outStackBounds,
             Rect outTempTaskBounds, boolean ignoreVisibility) {
@@ -837,7 +815,7 @@
     }
 
     final boolean isHomeOrRecentsStack() {
-        return StackId.isHomeOrRecentsStack(mStackId);
+        return isActivityTypeHome() || isActivityTypeRecents();
     }
 
     final boolean isDockedStack() {
@@ -849,7 +827,7 @@
     }
 
     final boolean isOnHomeDisplay() {
-        return isAttached() && mDisplayId == DEFAULT_DISPLAY;
+        return mDisplayId == DEFAULT_DISPLAY;
     }
 
     void moveToFront(String reason) {
@@ -865,8 +843,7 @@
             return;
         }
 
-        mStacks.remove(this);
-        mStacks.add(findStackInsertIndex(ON_TOP), this);
+        getDisplay().positionChildAtTop(this);
         mStackSupervisor.setFocusStackUnchecked(reason, this);
         if (task != null) {
             insertTaskAtTop(task, null);
@@ -880,45 +857,6 @@
         }
     }
 
-    /**
-     * @param task If non-null, the task will be moved to the back of the stack.
-     * */
-    private void moveToBack(TaskRecord task) {
-        if (!isAttached()) {
-            return;
-        }
-
-        mStacks.remove(this);
-        mStacks.add(0, this);
-
-        if (task != null) {
-            mTaskHistory.remove(task);
-            mTaskHistory.add(0, task);
-            updateTaskMovement(task, false);
-            mWindowContainerController.positionChildAtBottom(task.getWindowContainerController());
-        }
-    }
-
-    /**
-     * @return the index to insert a new stack into, taking the always-on-top stacks into account.
-     */
-    private int findStackInsertIndex(boolean onTop) {
-        if (onTop) {
-            int addIndex = mStacks.size();
-            if (addIndex > 0) {
-                final ActivityStack topStack = mStacks.get(addIndex - 1);
-                if (topStack.getWindowConfiguration().isAlwaysOnTop()
-                        && topStack != this) {
-                    // If the top stack is always on top, we move this stack just below it.
-                    addIndex--;
-                }
-            }
-            return addIndex;
-        } else {
-            return 0;
-        }
-    }
-
     boolean isFocusable() {
         if (getWindowConfiguration().canReceiveKeys()) {
             return true;
@@ -930,7 +868,7 @@
     }
 
     final boolean isAttached() {
-        return mStacks != null;
+        return getParent() != null;
     }
 
     /**
@@ -1105,14 +1043,6 @@
                 "Launch completed; removing icicle of " + r.icicle);
     }
 
-    void addRecentActivityLocked(ActivityRecord r) {
-        if (r != null) {
-            final TaskRecord task = r.getTask();
-            mRecentTasks.addLocked(task);
-            task.touchActiveTime();
-        }
-    }
-
     private void startLaunchTraces(String packageName) {
         if (mFullyDrawnStartTime != 0)  {
             Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER, "drawing", 0);
@@ -1527,7 +1457,7 @@
         // focus). Also if there is an active pinned stack - we always want to notify it about
         // task stack changes, because its positioning may depend on it.
         if (mStackSupervisor.mAppVisibilitiesChangedSinceLastPause
-                || mService.mStackSupervisor.getStack(PINNED_STACK_ID) != null) {
+                || getDisplay().hasPinnedStack()) {
             mService.mTaskChangeNotificationController.notifyTaskStackChanged();
             mStackSupervisor.mAppVisibilitiesChangedSinceLastPause = false;
         }
@@ -1559,54 +1489,6 @@
         }
     }
 
-    // Find the first visible activity above the passed activity and if it is translucent return it
-    // otherwise return null;
-    ActivityRecord findNextTranslucentActivity(ActivityRecord r) {
-        TaskRecord task = r.getTask();
-        if (task == null) {
-            return null;
-        }
-
-        final ActivityStack stack = task.getStack();
-        if (stack == null) {
-            return null;
-        }
-
-        int stackNdx = mStacks.indexOf(stack);
-
-        ArrayList<TaskRecord> tasks = stack.mTaskHistory;
-        int taskNdx = tasks.indexOf(task);
-
-        ArrayList<ActivityRecord> activities = task.mActivities;
-        int activityNdx = activities.indexOf(r) + 1;
-
-        final int numStacks = mStacks.size();
-        while (stackNdx < numStacks) {
-            final ActivityStack historyStack = mStacks.get(stackNdx);
-            tasks = historyStack.mTaskHistory;
-            final int numTasks = tasks.size();
-            while (taskNdx < numTasks) {
-                final TaskRecord currentTask = tasks.get(taskNdx);
-                activities = currentTask.mActivities;
-                final int numActivities = activities.size();
-                while (activityNdx < numActivities) {
-                    final ActivityRecord activity = activities.get(activityNdx);
-                    if (!activity.finishing) {
-                        return historyStack.mFullscreen
-                                && currentTask.mFullscreen && activity.fullscreen ? null : activity;
-                    }
-                    ++activityNdx;
-                }
-                activityNdx = 0;
-                ++taskNdx;
-            }
-            taskNdx = 0;
-            ++stackNdx;
-        }
-
-        return null;
-    }
-
     /** Returns true if the stack contains a fullscreen task. */
     private boolean hasFullscreenTask() {
         for (int i = mTaskHistory.size() - 1; i >= 0; --i) {
@@ -1650,9 +1532,11 @@
                     return false;
                 }
 
+                final ActivityStack stackBehind = mStackSupervisor.getStack(stackBehindId);
+                final boolean stackBehindHomeOrRecent = stackBehind != null
+                        && stackBehind.isHomeOrRecentsStack();
                 if (!isHomeOrRecentsStack() && r.frontOfTask && task.isOverHomeStack()
-                        && !StackId.isHomeOrRecentsStack(stackBehindId)
-                        && !isActivityTypeAssistant()) {
+                        && !stackBehindHomeOrRecent && !isActivityTypeAssistant()) {
                     // Stack isn't translucent if it's top activity should have the home stack
                     // behind it and the stack currently behind it isn't the home or recents stack
                     // or the assistant stack.
@@ -1670,119 +1554,146 @@
     }
 
     /**
-     * Returns what the stack visibility should be: {@link #STACK_INVISIBLE} or
-     * {@link #STACK_VISIBLE}.
+     * Returns true if the stack should be visible.
      *
      * @param starting The currently starting activity or null if there is none.
      */
-    int shouldBeVisible(ActivityRecord starting) {
+    boolean shouldBeVisible(ActivityRecord starting) {
         if (!isAttached() || mForceHidden) {
-            return STACK_INVISIBLE;
+            return false;
         }
 
         if (mStackSupervisor.isFrontStackOnDisplay(this) || mStackSupervisor.isFocusedStack(this)) {
-            return STACK_VISIBLE;
+            return true;
         }
 
-        final int stackIndex = mStacks.indexOf(this);
+        final ActivityDisplay display = getDisplay();
+        final ArrayList<ActivityStack> displayStacks = display.mStacks;
+        final int stackIndex = displayStacks.indexOf(this);
 
-        if (stackIndex == mStacks.size() - 1) {
+        if (stackIndex == displayStacks.size() - 1) {
             Slog.wtf(TAG,
                     "Stack=" + this + " isn't front stack but is at the top of the stack list");
-            return STACK_INVISIBLE;
+            return false;
         }
 
         // Check position and visibility of this stack relative to the front stack on its display.
         final ActivityStack topStack = getTopStackOnDisplay();
         final int topStackId = topStack.mStackId;
+        final int windowingMode = getWindowingMode();
+        final int activityType = getActivityType();
 
-        if (mStackId == DOCKED_STACK_ID) {
+        if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
             // If the assistant stack is focused and translucent, then the docked stack is always
             // visible
             if (topStack.isActivityTypeAssistant()) {
-                return (topStack.isStackTranslucent(starting, DOCKED_STACK_ID)) ? STACK_VISIBLE
-                        : STACK_INVISIBLE;
+                return topStack.isStackTranslucent(starting, DOCKED_STACK_ID);
             }
-            return STACK_VISIBLE;
+            return true;
         }
 
         // Set home stack to invisible when it is below but not immediately below the docked stack
         // A case would be if recents stack exists but has no tasks and is below the docked stack
         // and home stack is below recents
-        if (mStackId == HOME_STACK_ID) {
-            int dockedStackIndex = mStacks.indexOf(mStackSupervisor.getStack(DOCKED_STACK_ID));
+        if (activityType == ACTIVITY_TYPE_HOME) {
+            final ActivityStack splitScreenStack = display.getStack(
+                    WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_UNDEFINED);
+            int dockedStackIndex = displayStacks.indexOf(splitScreenStack);
             if (dockedStackIndex > stackIndex && stackIndex != dockedStackIndex - 1) {
-                return STACK_INVISIBLE;
+                return false;
             }
         }
 
         // Find the first stack behind front stack that actually got something visible.
-        int stackBehindTopIndex = mStacks.indexOf(topStack) - 1;
+        int stackBehindTopIndex = displayStacks.indexOf(topStack) - 1;
         while (stackBehindTopIndex >= 0 &&
-                mStacks.get(stackBehindTopIndex).topRunningActivityLocked() == null) {
+                displayStacks.get(stackBehindTopIndex).topRunningActivityLocked() == null) {
             stackBehindTopIndex--;
         }
-        final int stackBehindTopId = (stackBehindTopIndex >= 0)
-                ? mStacks.get(stackBehindTopIndex).mStackId : INVALID_STACK_ID;
+        final ActivityStack stackBehindTop = (stackBehindTopIndex >= 0)
+                ? displayStacks.get(stackBehindTopIndex) : null;
+        int stackBehindTopId = INVALID_STACK_ID;
+        int stackBehindTopWindowingMode = WINDOWING_MODE_UNDEFINED;
+        int stackBehindTopActivityType = ACTIVITY_TYPE_UNDEFINED;
+        if (stackBehindTop != null) {
+            stackBehindTopId = stackBehindTop.mStackId;
+            stackBehindTopWindowingMode = stackBehindTop.getWindowingMode();
+            stackBehindTopActivityType = stackBehindTop.getActivityType();
+        }
+
         final boolean alwaysOnTop = topStack.getWindowConfiguration().isAlwaysOnTop();
-        if (topStackId == DOCKED_STACK_ID || alwaysOnTop) {
+        if (topStack.getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY || alwaysOnTop) {
             if (stackIndex == stackBehindTopIndex) {
                 // Stacks directly behind the docked or pinned stack are always visible.
-                return STACK_VISIBLE;
+                return true;
             } else if (alwaysOnTop && stackIndex == stackBehindTopIndex - 1) {
                 // Otherwise, this stack can also be visible if it is directly behind a docked stack
                 // or translucent assistant stack behind an always-on-top top-most stack
-                if (stackBehindTopId == DOCKED_STACK_ID) {
-                    return STACK_VISIBLE;
-                } else if (stackBehindTopId == ASSISTANT_STACK_ID) {
-                    return mStacks.get(stackBehindTopIndex).isStackTranslucent(starting, mStackId)
-                            ? STACK_VISIBLE : STACK_INVISIBLE;
+                if (stackBehindTopWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
+                    return true;
+                } else if (stackBehindTopActivityType == ACTIVITY_TYPE_ASSISTANT) {
+                    return displayStacks.get(stackBehindTopIndex).isStackTranslucent(
+                            starting, mStackId);
                 }
             }
         }
 
-        if (StackId.isBackdropToTranslucentActivity(topStackId)
+        if (topStack.isBackdropToTranslucentActivity()
                 && topStack.isStackTranslucent(starting, stackBehindTopId)) {
             // Stacks behind the fullscreen or assistant stack with a translucent activity are
             // always visible so they can act as a backdrop to the translucent activity.
             // For example, dialog activities
             if (stackIndex == stackBehindTopIndex) {
-                return STACK_VISIBLE;
+                return true;
             }
             if (stackBehindTopIndex >= 0) {
-                if ((stackBehindTopId == DOCKED_STACK_ID
-                        || stackBehindTopId == PINNED_STACK_ID)
+                if ((stackBehindTopWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
+                        || stackBehindTopWindowingMode == WINDOWING_MODE_PINNED)
                         && stackIndex == (stackBehindTopIndex - 1)) {
                     // The stack behind the docked or pinned stack is also visible so we can have a
                     // complete backdrop to the translucent activity when the docked stack is up.
-                    return STACK_VISIBLE;
+                    return true;
                 }
             }
         }
 
-        if (StackId.isStaticStack(mStackId)) {
+        if (StackId.isStaticStack(mStackId)
+                || isHomeOrRecentsStack() || isActivityTypeAssistant()) {
             // Visibility of any static stack should have been determined by the conditions above.
-            return STACK_INVISIBLE;
+            return false;
         }
 
-        for (int i = stackIndex + 1; i < mStacks.size(); i++) {
-            final ActivityStack stack = mStacks.get(i);
+        for (int i = stackIndex + 1; i < displayStacks.size(); i++) {
+            final ActivityStack stack = displayStacks.get(i);
 
             if (!stack.mFullscreen && !stack.hasFullscreenTask()) {
                 continue;
             }
 
-            if (!StackId.isDynamicStacksVisibleBehindAllowed(stack.mStackId)) {
+            if (!stack.isDynamicStacksVisibleBehindAllowed()) {
                 // These stacks can't have any dynamic stacks visible behind them.
-                return STACK_INVISIBLE;
+                return false;
             }
 
             if (!stack.isStackTranslucent(starting, INVALID_STACK_ID)) {
-                return STACK_INVISIBLE;
+                return false;
             }
         }
 
-        return STACK_VISIBLE;
+        return true;
+    }
+
+    private boolean isBackdropToTranslucentActivity() {
+        if (isActivityTypeAssistant()) {
+            return true;
+        }
+        final int windowingMode = getWindowingMode();
+        return windowingMode == WINDOWING_MODE_FULLSCREEN
+                || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+    }
+
+    private boolean isDynamicStacksVisibleBehindAllowed() {
+        return isActivityTypeAssistant() || getWindowingMode() == WINDOWING_MODE_PINNED;
     }
 
     final int rankTaskLayers(int baseLayer) {
@@ -1819,12 +1730,10 @@
             // If the top activity is not fullscreen, then we need to
             // make sure any activities under it are now visible.
             boolean aboveTop = top != null;
-            final int stackVisibility = shouldBeVisible(starting);
-            final boolean stackInvisible = stackVisibility != STACK_VISIBLE;
-            boolean behindFullscreenActivity = stackInvisible;
+            final boolean stackShouldBeVisible = shouldBeVisible(starting);
+            boolean behindFullscreenActivity = !stackShouldBeVisible;
             boolean resumeNextActivity = mStackSupervisor.isFocusedStack(this)
                     && (isInStackLocked(starting) == null);
-            boolean behindTranslucentActivity = false;
             for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
                 final TaskRecord task = mTaskHistory.get(taskNdx);
                 final ArrayList<ActivityRecord> activities = task.mActivities;
@@ -1848,11 +1757,8 @@
                     final boolean reallyVisible = checkKeyguardVisibility(r,
                             visibleIgnoringKeyguard, isTop);
                     if (visibleIgnoringKeyguard) {
-                        behindFullscreenActivity = updateBehindFullscreen(stackInvisible,
+                        behindFullscreenActivity = updateBehindFullscreen(!stackShouldBeVisible,
                                 behindFullscreenActivity, task, r);
-                        if (behindFullscreenActivity && !r.fullscreen) {
-                            behindTranslucentActivity = true;
-                        }
                     }
                     if (reallyVisible) {
                         if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Make visible? " + r
@@ -1888,10 +1794,10 @@
                         configChanges |= r.configChangeFlags;
                     } else {
                         if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Make invisible? " + r
-                                + " finishing=" + r.finishing + " state=" + r.state + " stackInvisible="
-                                + stackInvisible + " behindFullscreenActivity="
-                                + behindFullscreenActivity + " mLaunchTaskBehind="
-                                + r.mLaunchTaskBehind);
+                                + " finishing=" + r.finishing + " state=" + r.state
+                                + " stackShouldBeVisible=" + stackShouldBeVisible
+                                + " behindFullscreenActivity=" + behindFullscreenActivity
+                                + " mLaunchTaskBehind=" + r.mLaunchTaskBehind);
                         makeInvisible(r);
                     }
                 }
@@ -1899,10 +1805,10 @@
                     // The visibility of tasks and the activities they contain in freeform stack are
                     // determined individually unlike other stacks where the visibility or fullscreen
                     // status of an activity in a previous task affects other.
-                    behindFullscreenActivity = stackVisibility == STACK_INVISIBLE;
-                } else if (mStackId == HOME_STACK_ID) {
+                    behindFullscreenActivity = !stackShouldBeVisible;
+                } else if (isActivityTypeHome()) {
                     if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Home task: at " + task
-                            + " stackInvisible=" + stackInvisible
+                            + " stackShouldBeVisible=" + stackShouldBeVisible
                             + " behindFullscreenActivity=" + behindFullscreenActivity);
                     // No other task in the home stack should be visible behind the home activity.
                     // Home activities is usually a translucent activity with the wallpaper behind
@@ -1962,7 +1868,8 @@
     boolean checkKeyguardVisibility(ActivityRecord r, boolean shouldBeVisible,
             boolean isTop) {
         final boolean isInPinnedStack = r.getStack().getStackId() == PINNED_STACK_ID;
-        final boolean keyguardShowing = mStackSupervisor.mKeyguardController.isKeyguardShowing();
+        final boolean keyguardShowing = mStackSupervisor.mKeyguardController.isKeyguardShowing(
+                mDisplayId != INVALID_DISPLAY ? mDisplayId : DEFAULT_DISPLAY);
         final boolean keyguardLocked = mStackSupervisor.mKeyguardController.isKeyguardLocked();
         final boolean showWhenLocked = r.canShowWhenLocked() && !isInPinnedStack;
         final boolean dismissKeyguard = r.hasDismissKeyguardWindows();
@@ -2002,7 +1909,7 @@
      * {@link Display#FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD} applied.
      */
     private boolean canShowWithInsecureKeyguard() {
-        final ActivityStackSupervisor.ActivityDisplay activityDisplay = getDisplay();
+        final ActivityDisplay activityDisplay = getDisplay();
         if (activityDisplay == null) {
             throw new IllegalStateException("Stack is not attached to any display, stackId="
                     + mStackId);
@@ -2182,7 +2089,7 @@
         // activities as we need to display their starting window until they are done initializing.
         boolean behindFullscreenActivity = false;
 
-        if (shouldBeVisible(null) == STACK_INVISIBLE) {
+        if (!shouldBeVisible(null)) {
             // The stack is not visible, so no activity in it should be displaying a starting
             // window. Mark all activities below top and behind fullscreen.
             aboveTop = false;
@@ -2258,9 +2165,7 @@
         mResumedActivity = r;
         r.state = ActivityState.RESUMED;
         mService.setResumedActivityUncheckLocked(r, reason);
-        final TaskRecord task = r.getTask();
-        task.touchActiveTime();
-        mRecentTasks.addLocked(task);
+        mStackSupervisor.addRecentActivity(r);
     }
 
     private boolean resumeTopActivityInnerLocked(ActivityRecord prev, ActivityOptions options) {
@@ -2543,128 +2448,139 @@
                     || (lastStack.mLastPausedActivity != null
                     && !lastStack.mLastPausedActivity.fullscreen));
 
-            // This activity is now becoming visible.
-            if (!next.visible || next.stopped || lastActivityTranslucent) {
-                next.setVisibility(true);
-            }
-
-            // schedule launch ticks to collect information about slow apps.
-            next.startLaunchTickingLocked();
-
-            ActivityRecord lastResumedActivity =
-                    lastStack == null ? null :lastStack.mResumedActivity;
-            ActivityState lastState = next.state;
-
-            mService.updateCpuStats();
-
-            if (DEBUG_STATES) Slog.v(TAG_STATES, "Moving to RESUMED: " + next + " (in existing)");
-
-            setResumedActivityLocked(next, "resumeTopActivityInnerLocked");
-
-            mService.updateLruProcessLocked(next.app, true, null);
-            updateLRUListLocked(next);
-            mService.updateOomAdjLocked();
-
-            // Have the window manager re-evaluate the orientation of
-            // the screen based on the new activity order.
-            boolean notUpdated = true;
-            if (mStackSupervisor.isFocusedStack(this)) {
-
-                // We have special rotation behavior when Keyguard is locked. Make sure all activity
-                // visibilities are set correctly as well as the transition is updated if needed to
-                // get the correct rotation behavior.
-                // TODO: Remove this once visibilities are set correctly immediately when starting
-                // an activity.
-                if (mStackSupervisor.mKeyguardController.isKeyguardLocked()) {
-                    mStackSupervisor.ensureActivitiesVisibleLocked(null /* starting */,
-                            0 /* configChanges */, false /* preserveWindows */);
-                }
-                final Configuration config = mWindowManager.updateOrientationFromAppTokens(
-                        mStackSupervisor.getDisplayOverrideConfiguration(mDisplayId),
-                        next.mayFreezeScreenLocked(next.app) ? next.appToken : null, mDisplayId);
-                if (config != null) {
-                    next.frozenBeforeDestroy = true;
-                }
-                notUpdated = !mService.updateDisplayOverrideConfigurationLocked(config, next,
-                        false /* deferResume */, mDisplayId);
-            }
-
-            if (notUpdated) {
-                // The configuration update wasn't able to keep the existing
-                // instance of the activity, and instead started a new one.
-                // We should be all done, but let's just make sure our activity
-                // is still at the top and schedule another run if something
-                // weird happened.
-                ActivityRecord nextNext = topRunningActivityLocked();
-                if (DEBUG_SWITCH || DEBUG_STATES) Slog.i(TAG_STATES,
-                        "Activity config changed during resume: " + next
-                        + ", new next: " + nextNext);
-                if (nextNext != next) {
-                    // Do over!
-                    mStackSupervisor.scheduleResumeTopActivities();
-                }
-                if (!next.visible || next.stopped) {
+            // The contained logic must be synchronized, since we are both changing the visibility
+            // and updating the {@link Configuration}. {@link ActivityRecord#setVisibility} will
+            // ultimately cause the client code to schedule a layout. Since layouts retrieve the
+            // current {@link Configuration}, we must ensure that the below code updates it before
+            // the layout can occur.
+            synchronized(mWindowManager.getWindowManagerLock()) {
+                // This activity is now becoming visible.
+                if (!next.visible || next.stopped || lastActivityTranslucent) {
                     next.setVisibility(true);
                 }
-                next.completeResumeLocked();
-                if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked();
-                return true;
-            }
 
-            try {
-                // Deliver all pending results.
-                ArrayList<ResultInfo> a = next.results;
-                if (a != null) {
-                    final int N = a.size();
-                    if (!next.finishing && N > 0) {
-                        if (DEBUG_RESULTS) Slog.v(TAG_RESULTS,
-                                "Delivering results to " + next + ": " + a);
-                        next.app.thread.scheduleSendResult(next.appToken, a);
+                // schedule launch ticks to collect information about slow apps.
+                next.startLaunchTickingLocked();
+
+                ActivityRecord lastResumedActivity =
+                        lastStack == null ? null :lastStack.mResumedActivity;
+                ActivityState lastState = next.state;
+
+                mService.updateCpuStats();
+
+                if (DEBUG_STATES) Slog.v(TAG_STATES, "Moving to RESUMED: " + next
+                        + " (in existing)");
+
+                setResumedActivityLocked(next, "resumeTopActivityInnerLocked");
+
+                mService.updateLruProcessLocked(next.app, true, null);
+                updateLRUListLocked(next);
+                mService.updateOomAdjLocked();
+
+                // Have the window manager re-evaluate the orientation of
+                // the screen based on the new activity order.
+                boolean notUpdated = true;
+
+                if (mStackSupervisor.isFocusedStack(this)) {
+
+                    // We have special rotation behavior when Keyguard is locked. Make sure all
+                    // activity visibilities are set correctly as well as the transition is updated
+                    // if needed to get the correct rotation behavior.
+                    // TODO: Remove this once visibilities are set correctly immediately when
+                    // starting an activity.
+                    if (mStackSupervisor.mKeyguardController.isKeyguardLocked()) {
+                        mStackSupervisor.ensureActivitiesVisibleLocked(null /* starting */,
+                                0 /* configChanges */, false /* preserveWindows */);
                     }
+                    final Configuration config = mWindowManager.updateOrientationFromAppTokens(
+                            mStackSupervisor.getDisplayOverrideConfiguration(mDisplayId),
+                            next.mayFreezeScreenLocked(next.app) ? next.appToken : null,
+                                    mDisplayId);
+                    if (config != null) {
+                        next.frozenBeforeDestroy = true;
+                    }
+                    notUpdated = !mService.updateDisplayOverrideConfigurationLocked(config, next,
+                            false /* deferResume */, mDisplayId);
                 }
 
-                if (next.newIntents != null) {
-                    next.app.thread.scheduleNewIntent(
-                            next.newIntents, next.appToken, false /* andPause */);
+                if (notUpdated) {
+                    // The configuration update wasn't able to keep the existing
+                    // instance of the activity, and instead started a new one.
+                    // We should be all done, but let's just make sure our activity
+                    // is still at the top and schedule another run if something
+                    // weird happened.
+                    ActivityRecord nextNext = topRunningActivityLocked();
+                    if (DEBUG_SWITCH || DEBUG_STATES) Slog.i(TAG_STATES,
+                            "Activity config changed during resume: " + next
+                                    + ", new next: " + nextNext);
+                    if (nextNext != next) {
+                        // Do over!
+                        mStackSupervisor.scheduleResumeTopActivities();
+                    }
+                    if (!next.visible || next.stopped) {
+                        next.setVisibility(true);
+                    }
+                    next.completeResumeLocked();
+                    if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked();
+                    return true;
                 }
 
-                // Well the app will no longer be stopped.
-                // Clear app token stopped state in window manager if needed.
-                next.notifyAppResumed(next.stopped);
+                try {
+                    // Deliver all pending results.
+                    ArrayList<ResultInfo> a = next.results;
+                    if (a != null) {
+                        final int N = a.size();
+                        if (!next.finishing && N > 0) {
+                            if (DEBUG_RESULTS) Slog.v(TAG_RESULTS,
+                                    "Delivering results to " + next + ": " + a);
+                            next.app.thread.scheduleSendResult(next.appToken, a);
+                        }
+                    }
 
-                EventLog.writeEvent(EventLogTags.AM_RESUME_ACTIVITY, next.userId,
-                        System.identityHashCode(next), next.getTask().taskId,
-                        next.shortComponentName);
+                    if (next.newIntents != null) {
+                        next.app.thread.scheduleNewIntent(
+                                next.newIntents, next.appToken, false /* andPause */);
+                    }
 
-                next.sleeping = false;
-                mService.showUnsupportedZoomDialogIfNeededLocked(next);
-                mService.showAskCompatModeDialogLocked(next);
-                next.app.pendingUiClean = true;
-                next.app.forceProcessStateUpTo(mService.mTopProcessState);
-                next.clearOptionsLocked();
-                next.app.thread.scheduleResumeActivity(next.appToken, next.app.repProcState,
-                        mService.isNextTransitionForward(), resumeAnimOptions);
+                    // Well the app will no longer be stopped.
+                    // Clear app token stopped state in window manager if needed.
+                    next.notifyAppResumed(next.stopped);
 
-                if (DEBUG_STATES) Slog.d(TAG_STATES, "resumeTopActivityLocked: Resumed " + next);
-            } catch (Exception e) {
-                // Whoops, need to restart this activity!
-                if (DEBUG_STATES) Slog.v(TAG_STATES, "Resume failed; resetting state to "
-                        + lastState + ": " + next);
-                next.state = lastState;
-                if (lastStack != null) {
-                    lastStack.mResumedActivity = lastResumedActivity;
+                    EventLog.writeEvent(EventLogTags.AM_RESUME_ACTIVITY, next.userId,
+                            System.identityHashCode(next), next.getTask().taskId,
+                            next.shortComponentName);
+
+                    next.sleeping = false;
+                    mService.showUnsupportedZoomDialogIfNeededLocked(next);
+                    mService.showAskCompatModeDialogLocked(next);
+                    next.app.pendingUiClean = true;
+                    next.app.forceProcessStateUpTo(mService.mTopProcessState);
+                    next.clearOptionsLocked();
+                    next.app.thread.scheduleResumeActivity(next.appToken, next.app.repProcState,
+                            mService.isNextTransitionForward(), resumeAnimOptions);
+
+                    if (DEBUG_STATES) Slog.d(TAG_STATES, "resumeTopActivityLocked: Resumed "
+                            + next);
+                } catch (Exception e) {
+                    // Whoops, need to restart this activity!
+                    if (DEBUG_STATES) Slog.v(TAG_STATES, "Resume failed; resetting state to "
+                            + lastState + ": " + next);
+                    next.state = lastState;
+                    if (lastStack != null) {
+                        lastStack.mResumedActivity = lastResumedActivity;
+                    }
+                    Slog.i(TAG, "Restarting because process died: " + next);
+                    if (!next.hasBeenLaunched) {
+                        next.hasBeenLaunched = true;
+                    } else  if (SHOW_APP_STARTING_PREVIEW && lastStack != null &&
+                            mStackSupervisor.isFrontStackOnDisplay(lastStack)) {
+                        next.showStartingWindow(null /* prev */, false /* newTask */,
+                                false /* taskSwitch */);
+                    }
+                    mStackSupervisor.startSpecificActivityLocked(next, true, false);
+                    if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked();
+                    return true;
                 }
-                Slog.i(TAG, "Restarting because process died: " + next);
-                if (!next.hasBeenLaunched) {
-                    next.hasBeenLaunched = true;
-                } else  if (SHOW_APP_STARTING_PREVIEW && lastStack != null &&
-                        mStackSupervisor.isFrontStackOnDisplay(lastStack)) {
-                    next.showStartingWindow(null /* prev */, false /* newTask */,
-                            false /* taskSwitch */);
-                }
-                mStackSupervisor.startSpecificActivityLocked(next, true, false);
-                if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked();
-                return true;
             }
 
             // From this point on, if something goes wrong there is no way
@@ -2989,9 +2905,9 @@
             // Ensure that we do not trigger entering PiP an activity on the pinned stack
             return false;
         }
-        final int targetStackId = toFrontTask != null ? toFrontTask.getStackId()
-                : toFrontActivity.getStackId();
-        if (targetStackId == ASSISTANT_STACK_ID) {
+        final ActivityStack targetStack = toFrontTask != null
+                ? toFrontTask.getStack() : toFrontActivity.getStack();
+        if (targetStack != null && targetStack.isActivityTypeAssistant()) {
             // Ensure the task/activity being brought forward is not the assistant
             return false;
         }
@@ -4548,7 +4464,7 @@
         // Don't refocus if invisible to current user
         final ActivityRecord top = tr.getTopActivity();
         if (top == null || !top.okToShowLocked()) {
-            addRecentActivityLocked(top);
+            mStackSupervisor.addRecentActivity(top);
             ActivityOptions.abort(options);
             return;
         }
@@ -4601,7 +4517,7 @@
         Slog.i(TAG, "moveTaskToBack: " + tr);
 
         // If the task is locked, then show the lock task toast
-        if (!mService.mLockTaskController.checkLockedTask(tr)) {
+        if (mService.mLockTaskController.checkLockedTask(tr)) {
             return false;
         }
 
@@ -4982,8 +4898,9 @@
             }
             ci.numActivities = numActivities;
             ci.numRunning = numRunning;
-            ci.supportsSplitScreenMultiWindow = task.supportsSplitScreen();
+            ci.supportsSplitScreenMultiWindow = task.supportsSplitScreenWindowingMode();
             ci.resizeMode = task.mResizeMode;
+            ci.configuration.setTo(task.getConfiguration());
             list.add(ci);
         }
     }
@@ -5152,8 +5069,7 @@
             if (task.autoRemoveFromRecents() || isVoiceSession) {
                 // Task creator asked to remove this when done, or this task was a voice
                 // interaction, so it should not remain on the recent tasks list.
-                mRecentTasks.remove(task);
-                task.removedFromRecents();
+                mStackSupervisor.removeTaskFromRecents(task);
             }
 
             task.removeWindowContainer();
@@ -5170,11 +5086,10 @@
                     mStackSupervisor.moveHomeStackToFront(myReason);
                 }
             }
-            if (mStacks != null) {
-                mStacks.remove(this);
-                mStacks.add(0, this);
+            if (isAttached()) {
+                getDisplay().positionChildAtBottom(this);
             }
-            if (!isHomeOrRecentsStack()) {
+            if (!isActivityTypeHome()) {
                 remove();
             }
         }
@@ -5194,8 +5109,8 @@
                 voiceInteractor);
         // add the task to stack first, mTaskPositioner might need the stack association
         addTask(task, toTop, "createTaskRecord");
-        final boolean isLockscreenShown =
-                mService.mStackSupervisor.mKeyguardController.isKeyguardShowing();
+        final boolean isLockscreenShown = mService.mStackSupervisor.mKeyguardController
+                .isKeyguardShowing(mDisplayId != INVALID_DISPLAY ? mDisplayId : DEFAULT_DISPLAY);
         if (!layoutTaskInStack(task, info.windowLayout) && mBounds != null && task.isResizeable()
                 && !isLockscreenShown) {
             task.updateOverrideConfiguration(mBounds);
@@ -5349,11 +5264,30 @@
     }
 
     boolean shouldSleepActivities() {
-        final ActivityStackSupervisor.ActivityDisplay display = getDisplay();
+        final ActivityDisplay display = getDisplay();
         return display != null ? display.isSleeping() : mService.isSleepingLocked();
     }
 
     boolean shouldSleepOrShutDownActivities() {
         return shouldSleepActivities() || mService.isShuttingDownLocked();
     }
+
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+        super.writeToProto(proto, CONFIGURATION_CONTAINER);
+        proto.write(ID, mStackId);
+        for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
+            final TaskRecord task = mTaskHistory.get(taskNdx);
+            task.writeToProto(proto, TASKS);
+        }
+        if (mResumedActivity != null) {
+            mResumedActivity.writeIdentifierToProto(proto, RESUMED_ACTIVITY);
+        }
+        proto.write(DISPLAY_ID, mDisplayId);
+        if (mBounds != null) {
+            mBounds.writeToProto(proto, BOUNDS);
+        }
+        proto.write(FULLSCREEN, mFullscreen);
+        proto.end(token);
+    }
 }
diff --git a/com/android/server/am/ActivityStackSupervisor.java b/com/android/server/am/ActivityStackSupervisor.java
index fe28956..da2827a 100644
--- a/com/android/server/am/ActivityStackSupervisor.java
+++ b/com/android/server/am/ActivityStackSupervisor.java
@@ -23,30 +23,29 @@
 import static android.app.ActivityManager.START_TASK_TO_FRONT;
 import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
 import static android.app.ActivityManager.StackId.FIRST_DYNAMIC_STACK_ID;
-import static android.app.ActivityManager.StackId.FIRST_STATIC_STACK_ID;
-import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
 import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.HOME_STACK_ID;
 import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
-import static android.app.ActivityManager.StackId.LAST_STATIC_STACK_ID;
 import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
-import static android.app.ActivityManager.StackId.RECENTS_STACK_ID;
 import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SECONDARY_DISPLAY;
 import static android.app.ITaskStackListener.FORCED_RESIZEABLE_REASON_SPLIT_SCREEN;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.content.pm.PackageManager.PERMISSION_DENIED;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.os.PowerManager.PARTIAL_WAKE_LOCK;
 import static android.os.Process.SYSTEM_UID;
 import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
 import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.Display.FLAG_PRIVATE;
 import static android.view.Display.INVALID_DISPLAY;
-import static android.view.Display.REMOVE_MODE_DESTROY_CONTENT;
 import static android.view.Display.TYPE_VIRTUAL;
 import static com.android.internal.logging.nano.MetricsProto.MetricsEvent.ACTION_PICTURE_IN_PICTURE_EXPANDED_TO_FULLSCREEN;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_ALL;
@@ -81,19 +80,24 @@
 import static com.android.server.am.ActivityStack.ActivityState.STOPPED;
 import static com.android.server.am.ActivityStack.ActivityState.STOPPING;
 import static com.android.server.am.ActivityStack.REMOVE_TASK_MODE_MOVING;
-import static com.android.server.am.ActivityStack.STACK_INVISIBLE;
-import static com.android.server.am.ActivityStack.STACK_VISIBLE;
+import static com.android.server.am.TaskRecord.INVALID_TASK_ID;
 import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_LAUNCHABLE;
 import static com.android.server.am.TaskRecord.LOCK_TASK_AUTH_LAUNCHABLE_PRIV;
 import static com.android.server.am.TaskRecord.REPARENT_KEEP_STACK_AT_FRONT;
 import static com.android.server.am.TaskRecord.REPARENT_LEAVE_STACK_IN_PLACE;
 import static com.android.server.am.TaskRecord.REPARENT_MOVE_STACK_TO_FRONT;
+import static com.android.server.am.proto.ActivityStackSupervisorProto.DISPLAYS;
+import static com.android.server.am.proto.ActivityStackSupervisorProto.FOCUSED_STACK_ID;
+import static com.android.server.am.proto.ActivityStackSupervisorProto.KEYGUARD_CONTROLLER;
+import static com.android.server.am.proto.ActivityStackSupervisorProto.RESUMED_ACTIVITY;
+import static com.android.server.am.proto.ActivityStackSupervisorProto.CONFIGURATION_CONTAINER;
 import static com.android.server.wm.AppTransition.TRANSIT_DOCK_TASK_FROM_RECENTS;
 import static java.lang.Integer.MAX_VALUE;
 
 import android.Manifest;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.app.Activity;
 import android.app.ActivityManager;
@@ -106,6 +110,7 @@
 import android.app.ProfilerInfo;
 import android.app.ResultInfo;
 import android.app.WaitResult;
+import android.app.WindowConfiguration;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -147,6 +152,7 @@
 import android.util.SparseArray;
 import android.util.SparseIntArray;
 import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
 import android.view.Display;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -166,7 +172,6 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
@@ -375,15 +380,11 @@
      * They are used by components that may hide and block interaction with underlying
      * activities.
      */
-    final ArrayList<SleepToken> mSleepTokens = new ArrayList<SleepToken>();
+    final ArrayList<SleepToken> mSleepTokens = new ArrayList<>();
 
     /** Stack id of the front stack when user switched, indexed by userId. */
     SparseIntArray mUserStackInFront = new SparseIntArray(2);
 
-    // TODO: Add listener for removal of references.
-    /** Mapping from (ActivityStack/TaskStack).mStackId to their current state */
-    SparseArray<ActivityStack> mStacks = new SparseArray<>();
-
     // TODO: There should be an ActivityDisplayController coordinating am/wm interaction.
     /** Mapping from displayId to display current state */
     private final SparseArray<ActivityDisplay> mActivityDisplays = new SparseArray<>();
@@ -602,16 +603,13 @@
             Display[] displays = mDisplayManager.getDisplays();
             for (int displayNdx = displays.length - 1; displayNdx >= 0; --displayNdx) {
                 final int displayId = displays[displayNdx].getDisplayId();
-                ActivityDisplay activityDisplay = new ActivityDisplay(displayId);
-                if (activityDisplay.mDisplay == null) {
-                    throw new IllegalStateException("Default Display does not exist");
-                }
+                ActivityDisplay activityDisplay = new ActivityDisplay(this, displayId);
                 mActivityDisplays.put(displayId, activityDisplay);
                 calculateDefaultMinimalSizeOfResizeableTasks(activityDisplay);
             }
 
-            mHomeStack = mFocusedStack = mLastFocusedStack =
-                    getStack(HOME_STACK_ID, CREATE_IF_NEEDED, ON_TOP);
+            mHomeStack = mFocusedStack = mLastFocusedStack = getDefaultDisplay().getOrCreateStack(
+                    WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_HOME, ON_TOP);
 
             mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
         }
@@ -667,7 +665,8 @@
     }
 
     void moveRecentsStackToFront(String reason) {
-        final ActivityStack recentsStack = getStack(RECENTS_STACK_ID);
+        final ActivityStack recentsStack = getDefaultDisplay().getStack(
+                WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_RECENTS);
         if (recentsStack != null) {
             recentsStack.moveToFront(reason);
         }
@@ -708,24 +707,26 @@
     }
 
     TaskRecord anyTaskForIdLocked(int id) {
-        return anyTaskForIdLocked(id, MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE,
-                INVALID_STACK_ID);
+        return anyTaskForIdLocked(id, MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE);
+    }
+
+    TaskRecord anyTaskForIdLocked(int id, @AnyTaskForIdMatchTaskMode int matchMode) {
+        return anyTaskForIdLocked(id, matchMode, null);
     }
 
     /**
      * Returns a {@link TaskRecord} for the input id if available. {@code null} otherwise.
      * @param id Id of the task we would like returned.
      * @param matchMode The mode to match the given task id in.
-     * @param stackId The stack to restore the task to (default launch stack will be used if
-     *                stackId is {@link android.app.ActivityManager.StackId#INVALID_STACK_ID}). Only
-     *                valid if the matchMode is
-     *                {@link #MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE}.
+     * @param aOptions The activity options to use for restoration. Can be null.
      */
-    TaskRecord anyTaskForIdLocked(int id, @AnyTaskForIdMatchTaskMode int matchMode, int stackId) {
+    TaskRecord anyTaskForIdLocked(int id, @AnyTaskForIdMatchTaskMode int matchMode,
+            @Nullable ActivityOptions aOptions) {
         // If there is a stack id set, ensure that we are attempting to actually restore a task
-        if (matchMode != MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE &&
-                stackId != INVALID_STACK_ID) {
-            throw new IllegalArgumentException("Should not specify stackId for non-restore lookup");
+        // TODO: Don't really know if this is needed...
+        if (matchMode != MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE && aOptions != null) {
+            throw new IllegalArgumentException("Should not specify activity options for non-restore"
+                    + " lookup");
         }
 
         int numDisplays = mActivityDisplays.size();
@@ -763,7 +764,7 @@
         }
 
         // Implicitly, this case is MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE
-        if (!restoreRecentTaskLocked(task, stackId)) {
+        if (!restoreRecentTaskLocked(task, aOptions)) {
             if (DEBUG_RECENTS) Slog.w(TAG_RECENTS,
                     "Couldn't restore task id=" + id + " found in recents");
             return null;
@@ -858,8 +859,8 @@
         // was 10, user 0 could only have taskIds 0 to 9, user 1: 10 to 19, user 2: 20 to 29, so on.
         int candidateTaskId = nextTaskIdForUser(currentTaskId, userId);
         while (mRecentTasks.taskIdTakenForUserLocked(candidateTaskId, userId)
-                || anyTaskForIdLocked(candidateTaskId, MATCH_TASK_IN_STACKS_OR_RECENT_TASKS,
-                        INVALID_STACK_ID) != null) {
+                || anyTaskForIdLocked(
+                        candidateTaskId, MATCH_TASK_IN_STACKS_OR_RECENT_TASKS) != null) {
             candidateTaskId = nextTaskIdForUser(candidateTaskId, userId);
             if (candidateTaskId == currentTaskId) {
                 // Something wrong!
@@ -1152,8 +1153,7 @@
 
     void getTasksLocked(int maxNum, List<RunningTaskInfo> list, int callingUid, boolean allowed) {
         // Gather all of the running tasks for each stack into runningTaskLists.
-        ArrayList<ArrayList<RunningTaskInfo>> runningTaskLists =
-                new ArrayList<ArrayList<RunningTaskInfo>>();
+        ArrayList<ArrayList<RunningTaskInfo>> runningTaskLists = new ArrayList<>();
         final int numDisplays = mActivityDisplays.size();
         for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
             ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
@@ -1235,7 +1235,7 @@
         synchronized (mService) {
             return mService.getPackageManagerInternalLocked().resolveIntent(intent, resolvedType,
                     PackageManager.MATCH_INSTANT | PackageManager.MATCH_DEFAULT_ONLY | flags
-                    | ActivityManagerService.STOCK_PM_FLAGS, userId);
+                    | ActivityManagerService.STOCK_PM_FLAGS, userId, true);
         }
     }
 
@@ -1639,6 +1639,9 @@
             return true;
         }
 
+        // Check if caller is already present on display
+        final boolean uidPresentOnDisplay = activityDisplay.isUidPresent(callingUid);
+
         final int displayOwnerUid = activityDisplay.mDisplay.getOwnerUid();
         if (activityDisplay.mDisplay.getType() == TYPE_VIRTUAL && displayOwnerUid != SYSTEM_UID
                 && displayOwnerUid != aInfo.applicationInfo.uid) {
@@ -1651,7 +1654,7 @@
             }
             // Check if the caller is allowed to embed activities from other apps.
             if (mService.checkPermission(ACTIVITY_EMBEDDING, callingPid, callingUid)
-                    == PERMISSION_DENIED) {
+                    == PERMISSION_DENIED && !uidPresentOnDisplay) {
                 if (DEBUG_TASKS) Slog.d(TAG, "Launch on display check:"
                         + " disallow activity embedding without permission.");
                 return false;
@@ -1672,8 +1675,7 @@
             return true;
         }
 
-        // Check if caller is present on display
-        if (activityDisplay.isUidPresent(callingUid)) {
+        if (uidPresentOnDisplay) {
             if (DEBUG_TASKS) Slog.d(TAG, "Launch on display check:"
                     + " allow launch for caller present on the display");
             return true;
@@ -1953,7 +1955,7 @@
      */
     void updateUserStackLocked(int userId, ActivityStack stack) {
         if (userId != mCurrentUser) {
-            mUserStackInFront.put(userId, stack != null ? stack.getStackId() : HOME_STACK_ID);
+            mUserStackInFront.put(userId, stack != null ? stack.getStackId() : mHomeStack.mStackId);
         }
     }
 
@@ -2083,38 +2085,35 @@
             // we'll just indicate that this task returns to the home task.
             task.setTaskToReturnTo(ACTIVITY_TYPE_HOME);
         }
-        ActivityStack currentStack = task.getStack();
+        final ActivityStack currentStack = task.getStack();
         if (currentStack == null) {
             Slog.e(TAG, "findTaskToMoveToFrontLocked: can't move task="
                     + task + " to front. Stack is null");
             return;
         }
 
-        if (task.isResizeable() && options != null) {
-            int stackId = options.getLaunchStackId();
-            if (canUseActivityOptionsLaunchBounds(options, stackId)) {
-                final Rect bounds = TaskRecord.validateBounds(options.getLaunchBounds());
-                task.updateOverrideConfiguration(bounds);
-                if (stackId == INVALID_STACK_ID) {
-                    stackId = task.getLaunchStackId();
-                }
-                if (stackId != currentStack.mStackId) {
-                    task.reparent(stackId, ON_TOP, REPARENT_KEEP_STACK_AT_FRONT, !ANIMATE,
-                            DEFER_RESUME, "findTaskToMoveToFrontLocked");
-                    stackId = currentStack.mStackId;
-                    // moveTaskToStackUncheckedLocked() should already placed the task on top,
-                    // still need moveTaskToFrontLocked() below for any transition settings.
-                }
-                if (StackId.resizeStackWithLaunchBounds(stackId)) {
-                    resizeStackLocked(stackId, bounds,
-                            null /* tempTaskBounds */, null /* tempTaskInsetBounds */,
-                            !PRESERVE_WINDOWS, true /* allowResizeInDockedMode */, !DEFER_RESUME);
-                } else {
-                    // WM resizeTask must be done after the task is moved to the correct stack,
-                    // because Task's setBounds() also updates dim layer's bounds, but that has
-                    // dependency on the stack.
-                    task.resizeWindowContainer();
-                }
+        if (task.isResizeable() && canUseActivityOptionsLaunchBounds(options)) {
+            final Rect bounds = TaskRecord.validateBounds(options.getLaunchBounds());
+            task.updateOverrideConfiguration(bounds);
+
+            ActivityStack stack = getLaunchStack(null, options, task, ON_TOP);
+
+            if (stack != currentStack) {
+                task.reparent(stack, ON_TOP, REPARENT_KEEP_STACK_AT_FRONT, !ANIMATE, DEFER_RESUME,
+                        "findTaskToMoveToFrontLocked");
+                stack = currentStack;
+                // moveTaskToStackUncheckedLocked() should already placed the task on top,
+                // still need moveTaskToFrontLocked() below for any transition settings.
+            }
+            if (StackId.resizeStackWithLaunchBounds(stack.mStackId)) {
+                resizeStackLocked(stack.mStackId, bounds, null /* tempTaskBounds */,
+                        null /* tempTaskInsetBounds */, !PRESERVE_WINDOWS,
+                        true /* allowResizeInDockedMode */, !DEFER_RESUME);
+            } else {
+                // WM resizeTask must be done after the task is moved to the correct stack,
+                // because Task's setBounds() also updates dim layer's bounds, but that has
+                // dependency on the stack.
+                task.resizeWindowContainer();
             }
         }
 
@@ -2125,39 +2124,246 @@
         if (DEBUG_STACK) Slog.d(TAG_STACK,
                 "findTaskToMoveToFront: moved to front of stack=" + currentStack);
 
-        handleNonResizableTaskIfNeeded(task, INVALID_STACK_ID, DEFAULT_DISPLAY,
+        handleNonResizableTaskIfNeeded(task, WINDOWING_MODE_UNDEFINED, DEFAULT_DISPLAY,
                 currentStack.mStackId, forceNonResizeable);
     }
 
-    boolean canUseActivityOptionsLaunchBounds(ActivityOptions options, int launchStackId) {
+    boolean canUseActivityOptionsLaunchBounds(ActivityOptions options) {
         // We use the launch bounds in the activity options is the device supports freeform
         // window management or is launching into the pinned stack.
-        if (options.getLaunchBounds() == null) {
+        if (options == null || options.getLaunchBounds() == null) {
             return false;
         }
-        return (mService.mSupportsPictureInPicture && launchStackId == PINNED_STACK_ID)
+        return (mService.mSupportsPictureInPicture
+                && options.getLaunchWindowingMode() == WINDOWING_MODE_PINNED)
                 || mService.mSupportsFreeformWindowManagement;
     }
 
     protected <T extends ActivityStack> T getStack(int stackId) {
-        return getStack(stackId, !CREATE_IF_NEEDED, !ON_TOP);
+        for (int i = mActivityDisplays.size() - 1; i >= 0; --i) {
+            final T stack = mActivityDisplays.valueAt(i).getStack(stackId);
+            if (stack != null) {
+                return stack;
+            }
+        }
+        return null;
     }
 
-    protected <T extends ActivityStack> T getStack(int stackId, boolean createStaticStackIfNeeded,
-            boolean createOnTop) {
-        final ActivityStack stack = mStacks.get(stackId);
+    /** @see ActivityDisplay#getStack(int, int) */
+    private <T extends ActivityStack> T getStack(int windowingMode, int activityType) {
+        for (int i = mActivityDisplays.size() - 1; i >= 0; --i) {
+            final T stack = mActivityDisplays.valueAt(i).getStack(windowingMode, activityType);
+            if (stack != null) {
+                return stack;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns true if the {@param windowingMode} is supported based on other parameters passed in.
+     * @param windowingMode The windowing mode we are checking support for.
+     * @param supportsMultiWindow If we should consider support for multi-window mode in general.
+     * @param supportsSplitScreen If we should consider support for split-screen multi-window.
+     * @param supportsFreeform If we should consider support for freeform multi-window.
+     * @param supportsPip If we should consider support for picture-in-picture mutli-window.
+     * @param activityType The activity type under consideration.
+     * @return true if the windowing mode is supported.
+     */
+    boolean isWindowingModeSupported(int windowingMode, boolean supportsMultiWindow,
+            boolean supportsSplitScreen, boolean supportsFreeform, boolean supportsPip,
+            int activityType) {
+
+        if (windowingMode == WINDOWING_MODE_UNDEFINED
+                || windowingMode == WINDOWING_MODE_FULLSCREEN) {
+            return true;
+        }
+        if (!supportsMultiWindow) {
+            return false;
+        }
+
+        if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
+                || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
+            return supportsSplitScreen && WindowConfiguration.supportSplitScreenWindowingMode(
+                    windowingMode, activityType);
+        }
+
+        if (!supportsFreeform && windowingMode == WINDOWING_MODE_FREEFORM) {
+            return false;
+        }
+
+        if (!supportsPip && windowingMode == WINDOWING_MODE_PINNED) {
+            return false;
+        }
+        return true;
+    }
+
+    private int resolveWindowingMode(@Nullable ActivityRecord r, @Nullable ActivityOptions options,
+            @Nullable TaskRecord task, int activityType) {
+
+        // First preference if the windowing mode in the activity options if set.
+        int windowingMode = (options != null)
+                ? options.getLaunchWindowingMode() : WINDOWING_MODE_UNDEFINED;
+
+        // If windowing mode is unset, then next preference is the candidate task, then the
+        // activity record.
+        if (windowingMode == WINDOWING_MODE_UNDEFINED) {
+            if (task != null) {
+                windowingMode = task.getWindowingMode();
+            }
+            if (windowingMode == WINDOWING_MODE_UNDEFINED && r != null) {
+                windowingMode = r.getWindowingMode();
+            }
+        }
+
+        // Make sure the windowing mode we are trying to use makes sense for what is supported.
+        boolean supportsMultiWindow = mService.mSupportsMultiWindow;
+        boolean supportsSplitScreen = mService.mSupportsSplitScreenMultiWindow;
+        boolean supportsFreeform = mService.mSupportsFreeformWindowManagement;
+        boolean supportsPip = mService.mSupportsPictureInPicture;
+        if (supportsMultiWindow) {
+            if (task != null) {
+                supportsMultiWindow = task.isResizeable();
+                supportsSplitScreen = task.supportsSplitScreenWindowingMode();
+                // TODO: Do we need to check for freeform and Pip support here?
+            } else if (r != null) {
+                supportsMultiWindow = r.isResizeable();
+                supportsSplitScreen = r.supportsSplitScreenWindowingMode();
+                supportsFreeform = r.supportsFreeform();
+                supportsPip = r.supportsPictureInPicture();
+            }
+        }
+
+        if (isWindowingModeSupported(windowingMode, supportsMultiWindow, supportsSplitScreen,
+                supportsFreeform, supportsPip, activityType)) {
+            return windowingMode;
+        }
+        return WINDOWING_MODE_FULLSCREEN;
+    }
+
+    int resolveActivityType(@Nullable ActivityRecord r, @Nullable ActivityOptions options,
+            @Nullable TaskRecord task) {
+        // Preference is given to the activity type for the activity then the task since the type
+        // once set shouldn't change.
+        int activityType = r != null ? r.getActivityType() : ACTIVITY_TYPE_UNDEFINED;
+        if (activityType == ACTIVITY_TYPE_UNDEFINED && task != null) {
+            activityType = task.getActivityType();
+        }
+        if (activityType != ACTIVITY_TYPE_UNDEFINED) {
+            return activityType;
+        }
+        return options != null ? options.getLaunchActivityType() : ACTIVITY_TYPE_UNDEFINED;
+    }
+
+    <T extends ActivityStack> T getLaunchStack(@Nullable ActivityRecord r,
+            @Nullable ActivityOptions options, @Nullable TaskRecord candidateTask, boolean onTop) {
+        return getLaunchStack(r, options, candidateTask, onTop, INVALID_DISPLAY);
+    }
+
+    /**
+     * Returns the right stack to use for launching factoring in all the input parameters.
+     *
+     * @param r The activity we are trying to launch. Can be null.
+     * @param options The activity options used to the launch. Can be null.
+     * @param candidateTask The possible task the activity might be launched in. Can be null.
+     *
+     * @return The stack to use for the launch or INVALID_STACK_ID.
+     */
+    <T extends ActivityStack> T getLaunchStack(@Nullable ActivityRecord r,
+            @Nullable ActivityOptions options, @Nullable TaskRecord candidateTask, boolean onTop,
+            int candidateDisplayId) {
+        int taskId = INVALID_TASK_ID;
+        int displayId = INVALID_DISPLAY;
+        //Rect bounds = null;
+
+        // We give preference to the launch preference in activity options.
+        if (options != null) {
+            taskId = options.getLaunchTaskId();
+            displayId = options.getLaunchDisplayId();
+            // TODO: Need to work this into the equation...
+            //bounds = options.getLaunchBounds();
+        }
+
+        // First preference for stack goes to the task Id set in the activity options. Use the stack
+        // associated with that if possible.
+        if (taskId != INVALID_TASK_ID) {
+            // Temporarily set the task id to invalid in case in re-entry.
+            options.setLaunchTaskId(INVALID_TASK_ID);
+            final TaskRecord task = anyTaskForIdLocked(taskId,
+                    MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE, options);
+            options.setLaunchTaskId(taskId);
+            if (task != null) {
+                return task.getStack();
+            }
+        }
+
+        final int activityType = resolveActivityType(r, options, candidateTask);
+        int windowingMode = resolveWindowingMode(r, options, candidateTask, activityType);
+        T stack = null;
+
+        // Next preference for stack goes to the display Id set in the activity options or the
+        // candidate display.
+        if (displayId == INVALID_DISPLAY) {
+            displayId = candidateDisplayId;
+        }
+        if (displayId != INVALID_DISPLAY) {
+            if (r != null) {
+                // TODO: This should also take in the windowing mode and activity type into account.
+                stack = (T) getValidLaunchStackOnDisplay(displayId, r);
+                if (stack != null) {
+                    return stack;
+                }
+            }
+            final ActivityDisplay display = getActivityDisplayOrCreateLocked(displayId);
+            if (display != null) {
+                for (int i = display.mStacks.size() - 1; i >= 0; --i) {
+                    stack = (T) display.mStacks.get(i);
+                    if (stack.isCompatible(windowingMode, activityType)) {
+                        return stack;
+                    }
+                }
+                // TODO: We should create the stack we want on the display at this point.
+            }
+        }
+
+        // Give preference to the stack and display of the input task and activity if they match the
+        // mode we want to launch into.
+        stack = null;
+        ActivityDisplay display = null;
+        if (candidateTask != null) {
+            stack = candidateTask.getStack();
+        }
+        if (stack == null && r != null) {
+            stack = r.getStack();
+        }
         if (stack != null) {
-            return (T) stack;
+            if (stack.isCompatible(windowingMode, activityType)) {
+                return stack;
+            }
+            display = stack.getDisplay();
         }
-        if (!createStaticStackIfNeeded || !StackId.isStaticStack(stackId)) {
-            return null;
+
+        if (display == null
+                // TODO: Can be removed once we figure-out how non-standard types should launch
+                // outside the default display.
+                || (activityType != ACTIVITY_TYPE_STANDARD
+                && activityType != ACTIVITY_TYPE_UNDEFINED)) {
+            display = getDefaultDisplay();
         }
-        if (stackId == DOCKED_STACK_ID) {
-            // Make sure recents stack exist when creating a dock stack as it normally need to be on
-            // the other side of the docked stack and we make visibility decisions based on that.
-            getStack(RECENTS_STACK_ID, CREATE_IF_NEEDED, createOnTop);
+
+        stack = display.getOrCreateStack(windowingMode, activityType, onTop);
+        if (stack != null) {
+            return stack;
         }
-        return (T) createStackOnDisplay(stackId, DEFAULT_DISPLAY, createOnTop);
+
+        // Whatever...return some default for now.
+        if (candidateTask != null && candidateTask.mBounds != null
+                && mService.mSupportsFreeformWindowManagement) {
+            windowingMode = WINDOWING_MODE_FREEFORM;
+        } else {
+            windowingMode = WINDOWING_MODE_FULLSCREEN;
+        }
+        return display.getOrCreateStack(windowingMode, activityType, onTop);
     }
 
     /**
@@ -2174,26 +2380,50 @@
                     "Display with displayId=" + displayId + " not found.");
         }
 
+        if (!r.canBeLaunchedOnDisplay(displayId)) {
+            return null;
+        }
+
         // Return the topmost valid stack on the display.
         for (int i = activityDisplay.mStacks.size() - 1; i >= 0; --i) {
             final ActivityStack stack = activityDisplay.mStacks.get(i);
-            if (mService.mActivityStarter.isValidLaunchStackId(stack.mStackId, displayId, r)) {
+            if (isValidLaunchStack(stack, displayId, r)) {
                 return stack;
             }
         }
 
         // If there is no valid stack on the external display - check if new dynamic stack will do.
-        if (displayId != Display.DEFAULT_DISPLAY) {
-            final int newDynamicStackId = getNextStackId();
-            if (mService.mActivityStarter.isValidLaunchStackId(newDynamicStackId, displayId, r)) {
-                return createStackOnDisplay(newDynamicStackId, displayId, true /*onTop*/);
-            }
+        if (displayId != DEFAULT_DISPLAY) {
+            return activityDisplay.createStack(
+                    r.getWindowingMode(), r.getActivityType(), true /*onTop*/);
         }
 
         Slog.w(TAG, "getValidLaunchStackOnDisplay: can't launch on displayId " + displayId);
         return null;
     }
 
+    // TODO: Can probably be consolidated into getLaunchStack()...
+    private boolean isValidLaunchStack(ActivityStack stack, int displayId, ActivityRecord r) {
+        switch (stack.getActivityType()) {
+            case ACTIVITY_TYPE_HOME: return r.isActivityTypeHome();
+            case ACTIVITY_TYPE_RECENTS: return r.isActivityTypeRecents();
+            case ACTIVITY_TYPE_ASSISTANT: return r.isActivityTypeAssistant();
+        }
+        switch (stack.getWindowingMode()) {
+            case WINDOWING_MODE_FULLSCREEN: return true;
+            case WINDOWING_MODE_FREEFORM: return r.supportsFreeform();
+            case WINDOWING_MODE_PINNED: return r.supportsPictureInPicture();
+            case WINDOWING_MODE_SPLIT_SCREEN_PRIMARY: return r.supportsSplitScreenWindowingMode();
+            case WINDOWING_MODE_SPLIT_SCREEN_SECONDARY: return r.supportsSplitScreenWindowingMode();
+        }
+
+        if (StackId.isDynamicStack(stack.mStackId)) {
+            return r.canBeLaunchedOnDisplay(displayId);
+        }
+        Slog.e(TAG, "isValidLaunchStack: Unexpected stack=" + stack);
+        return false;
+    }
+
     ArrayList<ActivityStack> getStacks() {
         ArrayList<ActivityStack> allStacks = new ArrayList<>();
         for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
@@ -2225,7 +2455,7 @@
             for (int j = stacks.size() - 1; j >= 0; --j) {
                 final ActivityStack stack = stacks.get(j);
                 if (stack != currentFocus && stack.isFocusable()
-                        && stack.shouldBeVisible(null) != STACK_INVISIBLE) {
+                        && stack.shouldBeVisible(null)) {
                     return stack;
                 }
             }
@@ -2294,7 +2524,7 @@
             return;
         }
 
-        final boolean splitScreenActive = getStack(DOCKED_STACK_ID) != null;
+        final boolean splitScreenActive = getDefaultDisplay().hasSplitScreenStack();
         if (!allowResizeInDockedMode
                 && !stack.getWindowConfiguration().tasksAreFloating() && splitScreenActive) {
             // If the docked stack exists, don't resize non-floating stacks independently of the
@@ -2305,7 +2535,7 @@
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "am.resizeStack_" + stackId);
         mWindowManager.deferSurfaceLayout();
         try {
-            if (stack.supportSplitScreenWindowingMode()) {
+            if (stack.supportsSplitScreenWindowingMode()) {
                 if (bounds == null && stack.inSplitScreenWindowingMode()) {
                     // null bounds = fullscreen windowing mode...at least for now.
                     stack.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
@@ -2326,26 +2556,25 @@
         }
     }
 
-    private void deferUpdateBounds(int stackId) {
-        final ActivityStack stack = getStack(stackId);
+    private void deferUpdateBounds(int activityType) {
+        final ActivityStack stack = getStack(WINDOWING_MODE_UNDEFINED, activityType);
         if (stack != null) {
             stack.deferUpdateBounds();
         }
     }
 
-    private void continueUpdateBounds(int stackId) {
-        final ActivityStack stack = getStack(stackId);
+    private void continueUpdateBounds(int activityType) {
+        final ActivityStack stack = getStack(WINDOWING_MODE_UNDEFINED, activityType);
         if (stack != null) {
             stack.continueUpdateBounds();
         }
     }
 
     void notifyAppTransitionDone() {
-        continueUpdateBounds(RECENTS_STACK_ID);
+        continueUpdateBounds(ACTIVITY_TYPE_RECENTS);
         for (int i = mResizingTasksDuringAnimation.size() - 1; i >= 0; i--) {
             final int taskId = mResizingTasksDuringAnimation.valueAt(i);
-            final TaskRecord task =
-                    anyTaskForIdLocked(taskId, MATCH_TASK_IN_STACKS_ONLY, INVALID_STACK_ID);
+            final TaskRecord task = anyTaskForIdLocked(taskId, MATCH_TASK_IN_STACKS_ONLY);
             if (task != null) {
                 task.setTaskDockedResizing(false);
             }
@@ -2353,29 +2582,31 @@
         mResizingTasksDuringAnimation.clear();
     }
 
-    private void moveTasksToFullscreenStackInSurfaceTransaction(int fromStackId,
-            boolean onTop) {
-
-        final ActivityStack stack = getStack(fromStackId);
-        if (stack == null) {
-            return;
-        }
+    /**
+     * TODO: This should just change the windowing mode and resize vs. actually moving task around.
+     * Can do that once we are no longer using static stack ids. Specially when
+     * {@link ActivityManager.StackId#FULLSCREEN_WORKSPACE_STACK_ID} is removed.
+     */
+    private void moveTasksToFullscreenStackInSurfaceTransaction(ActivityStack fromStack,
+            int toDisplayId, boolean onTop) {
 
         mWindowManager.deferSurfaceLayout();
         try {
-            if (fromStackId == DOCKED_STACK_ID) {
+            final int windowingMode = fromStack.getWindowingMode();
+            final boolean inPinnedWindowingMode = windowingMode == WINDOWING_MODE_PINNED;
+            final ActivityDisplay toDisplay = getActivityDisplay(toDisplayId);
+
+            if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
                 // We are moving all tasks from the docked stack to the fullscreen stack,
                 // which is dismissing the docked stack, so resize all other stacks to
                 // fullscreen here already so we don't end up with resize trashing.
-                for (int i = FIRST_STATIC_STACK_ID; i <= LAST_STATIC_STACK_ID; i++) {
-                    final ActivityStack otherStack = getStack(i);
-                    if (otherStack == null) {
-                        continue;
-                    }
+                final ArrayList<ActivityStack> displayStacks = toDisplay.mStacks;
+                for (int i = displayStacks.size() - 1; i >= 0; --i) {
+                    final ActivityStack otherStack = displayStacks.get(i);
                     if (!otherStack.inSplitScreenSecondaryWindowingMode()) {
                         continue;
                     }
-                    resizeStackLocked(i, null, null, null, PRESERVE_WINDOWS,
+                    resizeStackLocked(otherStack.mStackId, null, null, null, PRESERVE_WINDOWS,
                             true /* allowResizeInDockedMode */, DEFER_RESUME);
                 }
 
@@ -2384,48 +2615,51 @@
                 // resize when we remove task from it below and it is detached from the
                 // display because it no longer contains any tasks.
                 mAllowDockedStackResize = false;
-            } else if (fromStackId == PINNED_STACK_ID) {
-                if (onTop) {
-                    // Log if we are expanding the PiP to fullscreen
-                    MetricsLogger.action(mService.mContext,
-                            ACTION_PICTURE_IN_PICTURE_EXPANDED_TO_FULLSCREEN);
-                }
+            } else if (inPinnedWindowingMode && onTop) {
+                // Log if we are expanding the PiP to fullscreen
+                MetricsLogger.action(mService.mContext,
+                        ACTION_PICTURE_IN_PICTURE_EXPANDED_TO_FULLSCREEN);
             }
-            ActivityStack fullscreenStack = getStack(FULLSCREEN_WORKSPACE_STACK_ID);
-            final boolean isFullscreenStackVisible = fullscreenStack != null &&
-                    fullscreenStack.shouldBeVisible(null) == STACK_VISIBLE;
+
             // If we are moving from the pinned stack, then the animation takes care of updating
             // the picture-in-picture mode.
-            final boolean schedulePictureInPictureModeChange = (fromStackId == PINNED_STACK_ID);
-            final ArrayList<TaskRecord> tasks = stack.getAllTasks();
-            final int size = tasks.size();
-            if (onTop) {
-                for (int i = 0; i < size; i++) {
-                    final TaskRecord task = tasks.get(i);
-                    final boolean isTopTask = i == (size - 1);
-                    if (fromStackId == PINNED_STACK_ID) {
-                        // Update the return-to to reflect where the pinned stack task was moved
-                        // from so that we retain the stack that was previously visible if the
-                        // pinned stack is recreated. See moveActivityToPinnedStackLocked().
-                        task.setTaskToReturnTo(isFullscreenStackVisible ?
-                                ACTIVITY_TYPE_STANDARD : ACTIVITY_TYPE_HOME);
+            final boolean schedulePictureInPictureModeChange = inPinnedWindowingMode;
+            final ArrayList<TaskRecord> tasks = fromStack.getAllTasks();
+
+            if (!tasks.isEmpty()) {
+                final int size = tasks.size();
+                final ActivityStack fullscreenStack = toDisplay.getOrCreateStack(
+                        WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, onTop);
+
+                if (onTop) {
+                    final int returnToType =
+                            toDisplay.getTopVisibleStackActivityType(WINDOWING_MODE_PINNED);
+                    for (int i = 0; i < size; i++) {
+                        final TaskRecord task = tasks.get(i);
+                        final boolean isTopTask = i == (size - 1);
+                        if (inPinnedWindowingMode) {
+                            // Update the return-to to reflect where the pinned stack task was moved
+                            // from so that we retain the stack that was previously visible if the
+                            // pinned stack is recreated. See moveActivityToPinnedStackLocked().
+                            task.setTaskToReturnTo(returnToType);
+                        }
+                        // Defer resume until all the tasks have been moved to the fullscreen stack
+                        task.reparent(fullscreenStack, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT,
+                                isTopTask /* animate */, DEFER_RESUME,
+                                schedulePictureInPictureModeChange,
+                                "moveTasksToFullscreenStack - onTop");
                     }
-                    // Defer resume until all the tasks have been moved to the fullscreen stack
-                    task.reparent(FULLSCREEN_WORKSPACE_STACK_ID, ON_TOP,
-                            REPARENT_MOVE_STACK_TO_FRONT, isTopTask /* animate */, DEFER_RESUME,
-                            schedulePictureInPictureModeChange,
-                            "moveTasksToFullscreenStack - onTop");
-                }
-            } else {
-                for (int i = 0; i < size; i++) {
-                    final TaskRecord task = tasks.get(i);
-                    // Position the tasks in the fullscreen stack in order at the bottom of the
-                    // stack. Also defer resume until all the tasks have been moved to the
-                    // fullscreen stack.
-                    task.reparent(FULLSCREEN_WORKSPACE_STACK_ID, i /* position */,
-                            REPARENT_LEAVE_STACK_IN_PLACE, !ANIMATE, DEFER_RESUME,
-                            schedulePictureInPictureModeChange,
-                            "moveTasksToFullscreenStack - NOT_onTop");
+                } else {
+                    for (int i = 0; i < size; i++) {
+                        final TaskRecord task = tasks.get(i);
+                        // Position the tasks in the fullscreen stack in order at the bottom of the
+                        // stack. Also defer resume until all the tasks have been moved to the
+                        // fullscreen stack.
+                        task.reparent(fullscreenStack, i /* position */,
+                                REPARENT_LEAVE_STACK_IN_PLACE, !ANIMATE, DEFER_RESUME,
+                                schedulePictureInPictureModeChange,
+                                "moveTasksToFullscreenStack - NOT_onTop");
+                    }
                 }
             }
 
@@ -2437,9 +2671,13 @@
         }
     }
 
-    void moveTasksToFullscreenStackLocked(int fromStackId, boolean onTop) {
-        mWindowManager.inSurfaceTransaction(
-                () -> moveTasksToFullscreenStackInSurfaceTransaction(fromStackId, onTop));
+    void moveTasksToFullscreenStackLocked(ActivityStack fromStack, boolean onTop) {
+        moveTasksToFullscreenStackLocked(fromStack, DEFAULT_DISPLAY, onTop);
+    }
+
+    void moveTasksToFullscreenStackLocked(ActivityStack fromStack, int toDisplayId, boolean onTop) {
+        mWindowManager.inSurfaceTransaction(() ->
+                moveTasksToFullscreenStackInSurfaceTransaction(fromStack, toDisplayId, onTop));
     }
 
     void resizeDockedStackLocked(Rect dockedBounds, Rect tempDockedTaskBounds,
@@ -2450,7 +2688,7 @@
                 false /* deferResume */);
     }
 
-    void resizeDockedStackLocked(Rect dockedBounds, Rect tempDockedTaskBounds,
+    private void resizeDockedStackLocked(Rect dockedBounds, Rect tempDockedTaskBounds,
             Rect tempDockedTaskInsetBounds, Rect tempOtherTaskBounds, Rect tempOtherTaskInsetBounds,
             boolean preserveWindows, boolean deferResume) {
 
@@ -2459,7 +2697,8 @@
             return;
         }
 
-        final ActivityStack stack = getStack(DOCKED_STACK_ID);
+        final ActivityStack stack = getDefaultDisplay().getStack(
+                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_UNDEFINED);
         if (stack == null) {
             Slog.w(TAG, "resizeDockedStackLocked: docked stack not found");
             return;
@@ -2479,7 +2718,7 @@
                 // The dock stack either was dismissed or went fullscreen, which is kinda the same.
                 // In this case we make all other static stacks fullscreen and move all
                 // docked stack tasks to the fullscreen stack.
-                moveTasksToFullscreenStackLocked(DOCKED_STACK_ID, ON_TOP);
+                moveTasksToFullscreenStackLocked(stack, ON_TOP);
 
                 // stack shouldn't contain anymore activities, so nothing to resume.
                 r = null;
@@ -2488,13 +2727,14 @@
                 // static stacks need to be adjusted so they don't overlap with the docked stack.
                 // We get the bounds to use from window manager which has been adjusted for any
                 // screen controls and is also the same for all stacks.
+                final ArrayList<ActivityStack> stacks = getStacksOnDefaultDisplay();
                 final Rect otherTaskRect = new Rect();
-                for (int i = FIRST_STATIC_STACK_ID; i <= LAST_STATIC_STACK_ID; i++) {
-                    if (i == DOCKED_STACK_ID) {
+                for (int i = stacks.size() - 1; i >= 0; --i) {
+                    final ActivityStack current = stacks.get(i);
+                    if (current.getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
                         continue;
                     }
-                    final ActivityStack current = getStack(i);
-                    if (current == null || !current.supportSplitScreenWindowingMode()) {
+                    if (!current.supportsSplitScreenWindowingMode()) {
                         continue;
                     }
                     // Need to set windowing mode here before we try to get the dock bounds.
@@ -2504,7 +2744,7 @@
                             tempRect /* outStackBounds */,
                             otherTaskRect /* outTempTaskBounds */, true /* ignoreVisibility */);
 
-                    resizeStackLocked(i, !tempRect.isEmpty() ? tempRect : null,
+                    resizeStackLocked(current.mStackId, !tempRect.isEmpty() ? tempRect : null,
                             !otherTaskRect.isEmpty() ? otherTaskRect : tempOtherTaskBounds,
                             tempOtherTaskInsetBounds, preserveWindows,
                             true /* allowResizeInDockedMode */, deferResume);
@@ -2521,7 +2761,9 @@
     }
 
     void resizePinnedStackLocked(Rect pinnedBounds, Rect tempPinnedTaskBounds) {
-        final PinnedActivityStack stack = getStack(PINNED_STACK_ID);
+        // TODO(multi-display): Pinned stack display should be passed in.
+        final PinnedActivityStack stack = getDefaultDisplay().getStack(
+                WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
         if (stack == null) {
             Slog.w(TAG, "resizePinnedStackLocked: pinned stack not found");
             return;
@@ -2559,32 +2801,14 @@
         }
     }
 
-    ActivityStack createStackOnDisplay(int stackId, int displayId, boolean onTop) {
-        final ActivityDisplay activityDisplay = getActivityDisplayOrCreateLocked(displayId);
-        if (activityDisplay == null) {
-            return null;
-        }
-        return createStack(stackId, activityDisplay, onTop);
-
-    }
-
-    ActivityStack createStack(int stackId, ActivityDisplay display, boolean onTop) {
-        switch (stackId) {
-            case PINNED_STACK_ID:
-                return new PinnedActivityStack(display, stackId, this, mRecentTasks, onTop);
-            default:
-                return new ActivityStack(display, stackId, this, mRecentTasks, onTop);
-        }
-    }
-
-    void removeStackInSurfaceTransaction(int stackId) {
+    private void removeStackInSurfaceTransaction(int stackId) {
         final ActivityStack stack = getStack(stackId);
         if (stack == null) {
             return;
         }
 
         final ArrayList<TaskRecord> tasks = stack.getAllTasks();
-        if (stack.getStackId() == PINNED_STACK_ID) {
+        if (stack.getWindowingMode() == WINDOWING_MODE_PINNED) {
             /**
              * Workaround: Force-stop all the activities in the pinned stack before we reparent them
              * to the fullscreen stack.  This is to guarantee that when we are removing a stack,
@@ -2602,7 +2826,7 @@
                     true /* processPausingActivites */, null /* configuration */);
 
             // Move all the tasks to the bottom of the fullscreen stack
-            moveTasksToFullscreenStackLocked(PINNED_STACK_ID, !ON_TOP);
+            moveTasksToFullscreenStackLocked(pinnedStack, !ON_TOP);
         } else {
             for (int i = tasks.size() - 1; i >= 0; i--) {
                 removeTaskByIdLocked(tasks.get(i).taskId, true /* killProcess */,
@@ -2617,8 +2841,23 @@
      * instead moved back onto the fullscreen stack.
      */
     void removeStackLocked(int stackId) {
-        mWindowManager.inSurfaceTransaction(
-                () -> removeStackInSurfaceTransaction(stackId));
+        mWindowManager.inSurfaceTransaction(() -> removeStackInSurfaceTransaction(stackId));
+    }
+
+    /**
+     * Removes stacks in the input windowing modes from the system if they are of activity type
+     * ACTIVITY_TYPE_STANDARD or ACTIVITY_TYPE_UNDEFINED
+     */
+    void removeStacksInWindowingModes(int... windowingModes) {
+        for (int i = mActivityDisplays.size() - 1; i >= 0; --i) {
+            mActivityDisplays.valueAt(i).removeStacksInWindowingModes(windowingModes);
+        }
+    }
+
+    void removeStacksWithActivityTypes(int... activityTypes) {
+        for (int i = mActivityDisplays.size() - 1; i >= 0; --i) {
+            mActivityDisplays.valueAt(i).removeStacksWithActivityTypes(activityTypes);
+        }
     }
 
     /**
@@ -2640,8 +2879,7 @@
      */
     boolean removeTaskByIdLocked(int taskId, boolean killProcess, boolean removeFromRecents,
             boolean pauseImmediately) {
-        final TaskRecord tr = anyTaskForIdLocked(taskId, MATCH_TASK_IN_STACKS_OR_RECENT_TASKS,
-                INVALID_STACK_ID);
+        final TaskRecord tr = anyTaskForIdLocked(taskId, MATCH_TASK_IN_STACKS_OR_RECENT_TASKS);
         if (tr != null) {
             tr.removeTaskActivitiesLocked(pauseImmediately);
             cleanUpRemovedTaskLocked(tr, killProcess, removeFromRecents);
@@ -2654,10 +2892,23 @@
         return false;
     }
 
+    void addRecentActivity(ActivityRecord r) {
+        if (r == null) {
+            return;
+        }
+        final TaskRecord task = r.getTask();
+        mRecentTasks.addLocked(task);
+        task.touchActiveTime();
+    }
+
+    void removeTaskFromRecents(TaskRecord task) {
+        mRecentTasks.remove(task);
+        task.removedFromRecents();
+    }
+
     void cleanUpRemovedTaskLocked(TaskRecord tr, boolean killProcess, boolean removeFromRecents) {
         if (removeFromRecents) {
-            mRecentTasks.remove(tr);
-            tr.removedFromRecents();
+            removeTaskFromRecents(tr);
         }
         ComponentName component = tr.getBaseIntent().getComponent();
         if (component == null) {
@@ -2740,27 +2991,15 @@
     /**
      * Restores a recent task to a stack
      * @param task The recent task to be restored.
-     * @param stackId The stack to restore the task to (default launch stack will be used
-     *                if stackId is {@link android.app.ActivityManager.StackId#INVALID_STACK_ID}
-     *                or is not a static stack).
+     * @param aOptions The activity options to use for restoration.
      * @return true if the task has been restored successfully.
      */
-    boolean restoreRecentTaskLocked(TaskRecord task, int stackId) {
-        if (!StackId.isStaticStack(stackId)) {
-            // If stack is not static (or stack id is invalid) - use the default one.
-            // This means that tasks that were on external displays will be restored on the
-            // primary display.
-            stackId = task.getLaunchStackId();
-        } else if (stackId == DOCKED_STACK_ID && !task.supportsSplitScreen()) {
-            // Preferred stack is the docked stack, but the task can't go in the docked stack.
-            // Put it in the fullscreen stack.
-            stackId = FULLSCREEN_WORKSPACE_STACK_ID;
-        }
-
+    boolean restoreRecentTaskLocked(TaskRecord task, ActivityOptions aOptions) {
+        final ActivityStack stack = getLaunchStack(null, aOptions, task, !ON_TOP);
         final ActivityStack currentStack = task.getStack();
         if (currentStack != null) {
             // Task has already been restored once. See if we need to do anything more
-            if (currentStack.mStackId == stackId) {
+            if (currentStack == stack) {
                 // Nothing else to do since it is already restored in the right stack.
                 return true;
             }
@@ -2769,19 +3008,9 @@
             currentStack.removeTask(task, "restoreRecentTaskLocked", REMOVE_TASK_MODE_MOVING);
         }
 
-        final ActivityStack stack =
-                getStack(stackId, CREATE_IF_NEEDED, !ON_TOP);
-
-        if (stack == null) {
-            // What does this mean??? Not sure how we would get here...
-            if (DEBUG_RECENTS) Slog.v(TAG_RECENTS,
-                    "Unable to find/create stack to restore recent task=" + task);
-            return false;
-        }
-
-        stack.addTask(task, false /* toTop */, "restoreRecentTask");
+        stack.addTask(task, !ON_TOP, "restoreRecentTask");
         // TODO: move call for creation here and other place into Stack.addTask()
-        task.createWindowContainer(false /* toTop */, true /* showForAllUsers */);
+        task.createWindowContainer(!ON_TOP, true /* showForAllUsers */);
         if (DEBUG_RECENTS) Slog.v(TAG_RECENTS,
                 "Added restored task=" + task + " to stack=" + stack);
         final ArrayList<ActivityRecord> activities = task.mActivities;
@@ -2803,7 +3032,7 @@
             throw new IllegalArgumentException("moveStackToDisplayLocked: Unknown displayId="
                     + displayId);
         }
-        final ActivityStack stack = mStacks.get(stackId);
+        final ActivityStack stack = getStack(stackId);
         if (stack == null) {
             throw new IllegalArgumentException("moveStackToDisplayLocked: Unknown stackId="
                     + stackId);
@@ -2828,8 +3057,10 @@
      * Returns the reparent target stack, creating the stack if necessary.  This call also enforces
      * the various checks on tasks that are going to be reparented from one stack to another.
      */
-    ActivityStack getReparentTargetStack(TaskRecord task, int stackId, boolean toTop) {
+    ActivityStack getReparentTargetStack(TaskRecord task, ActivityStack stack, boolean toTop) {
         final ActivityStack prevStack = task.getStack();
+        final int stackId = stack.mStackId;
+        final boolean inMultiWindowMode = stack.inMultiWindowMode();
 
         // Check that we aren't reparenting to the same stack that the task is already in
         if (prevStack != null && prevStack.mStackId == stackId) {
@@ -2840,22 +3071,22 @@
 
         // Ensure that we aren't trying to move into a multi-window stack without multi-window
         // support
-        if (StackId.isMultiWindowStack(stackId) && !mService.mSupportsMultiWindow) {
+        if (inMultiWindowMode && !mService.mSupportsMultiWindow) {
             throw new IllegalArgumentException("Device doesn't support multi-window, can not"
-                    + " reparent task=" + task + " to stackId=" + stackId);
+                    + " reparent task=" + task + " to stack=" + stack);
         }
 
         // Ensure that we're not moving a task to a dynamic stack if device doesn't support
         // multi-display.
-        // TODO(multi-display): Support non-dynamic stacks on secondary displays.
-        if (StackId.isDynamicStack(stackId) && !mService.mSupportsMultiDisplay) {
+        if (stack.mDisplayId != DEFAULT_DISPLAY && !mService.mSupportsMultiDisplay) {
             throw new IllegalArgumentException("Device doesn't support multi-display, can not"
                     + " reparent task=" + task + " to stackId=" + stackId);
         }
 
         // Ensure that we aren't trying to move into a freeform stack without freeform
         // support
-        if (stackId == FREEFORM_WORKSPACE_STACK_ID && !mService.mSupportsFreeformWindowManagement) {
+        if (stack.getWindowingMode() == WINDOWING_MODE_FREEFORM
+                && !mService.mSupportsFreeformWindowManagement) {
             throw new IllegalArgumentException("Device doesn't support freeform, can not reparent"
                     + " task=" + task);
         }
@@ -2864,25 +3095,25 @@
         // used for split-screen mode and will cause things like the docked divider to show up. We
         // instead leave the task in its current stack or move it to the fullscreen stack if it
         // isn't currently in a stack.
-        if (stackId == DOCKED_STACK_ID && !task.isResizeable()) {
-            stackId = (prevStack != null) ? prevStack.mStackId : FULLSCREEN_WORKSPACE_STACK_ID;
+        if (inMultiWindowMode && !task.isResizeable()) {
             Slog.w(TAG, "Can not move unresizeable task=" + task + " to docked stack."
                     + " Moving to stackId=" + stackId + " instead.");
+            // Temporarily disable resizeablility of the task as we don't want it to be resized if,
+            // for example, a docked stack is created which will lead to the stack we are moving
+            // from being resized and and its resizeable tasks being resized.
+            try {
+                task.mTemporarilyUnresizable = true;
+                stack = stack.getDisplay().createStack(
+                        WINDOWING_MODE_FULLSCREEN, stack.getActivityType(), toTop);
+            } finally {
+                task.mTemporarilyUnresizable = false;
+            }
         }
-
-        // Temporarily disable resizeablility of the task as we don't want it to be resized if, for
-        // example, a docked stack is created which will lead to the stack we are moving from being
-        // resized and and its resizeable tasks being resized.
-        try {
-            task.mTemporarilyUnresizable = true;
-            return getStack(stackId, CREATE_IF_NEEDED, toTop);
-        } finally {
-            task.mTemporarilyUnresizable = false;
-        }
+        return stack;
     }
 
     boolean moveTopStackActivityToPinnedStackLocked(int stackId, Rect destBounds) {
-        final ActivityStack stack = getStack(stackId, !CREATE_IF_NEEDED, !ON_TOP);
+        final ActivityStack stack = getStack(stackId);
         if (stack == null) {
             throw new IllegalArgumentException(
                     "moveTopStackActivityToPinnedStackLocked: Unknown stackId=" + stackId);
@@ -2912,12 +3143,17 @@
 
         mWindowManager.deferSurfaceLayout();
 
+        final ActivityDisplay display = r.getStack().getDisplay();
+        PinnedActivityStack stack = display.getPinnedStack();
+
         // This will clear the pinned stack by moving an existing task to the full screen stack,
         // ensuring only one task is present.
-        moveTasksToFullscreenStackLocked(PINNED_STACK_ID, !ON_TOP);
+        if (stack != null) {
+            moveTasksToFullscreenStackLocked(stack, !ON_TOP);
+        }
 
         // Need to make sure the pinned stack exist so we can resize it below...
-        final PinnedActivityStack stack = getStack(PINNED_STACK_ID, CREATE_IF_NEEDED, ON_TOP);
+        stack = display.getOrCreateStack(WINDOWING_MODE_PINNED, r.getActivityType(), ON_TOP);
 
         try {
             final TaskRecord task = r.getTask();
@@ -2941,8 +3177,8 @@
                     moveHomeStackToFront(reason);
                 }
                 // Defer resume until below, and do not schedule PiP changes until we animate below
-                task.reparent(PINNED_STACK_ID, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT, !ANIMATE,
-                        DEFER_RESUME, false /* schedulePictureInPictureModeChange */, reason);
+                task.reparent(stack, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT, !ANIMATE, DEFER_RESUME,
+                        false /* schedulePictureInPictureModeChange */, reason);
             } else {
                 // There are multiple activities in the task and moving the top activity should
                 // reveal/leave the other activities in their original task.
@@ -2957,7 +3193,7 @@
                 r.reparent(newTask, MAX_VALUE, "moveActivityToStack");
 
                 // Defer resume until below, and do not schedule PiP changes until we animate below
-                newTask.reparent(PINNED_STACK_ID, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT, !ANIMATE,
+                newTask.reparent(stack, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT, !ANIMATE,
                         DEFER_RESUME, false /* schedulePictureInPictureModeChange */, reason);
             }
 
@@ -2983,8 +3219,7 @@
         ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
         resumeFocusedStackTopActivityLocked();
 
-        mService.mTaskChangeNotificationController.notifyActivityPinned(r.packageName,
-                r.getTask().taskId);
+        mService.mTaskChangeNotificationController.notifyActivityPinned(r);
     }
 
     /** Move activity with its stack to front and make the stack focused. */
@@ -3044,6 +3279,9 @@
                         // tasks should always have lower priority than any affinity-matching tasks
                         // in the fullscreen stacks
                         affinityMatch = mTmpFindTaskResult.r;
+                    } else if (DEBUG_TASKS && mTmpFindTaskResult.matchedByRootAffinity) {
+                        Slog.d(TAG_TASKS, "Skipping match on different display "
+                                + mTmpFindTaskResult.r.getDisplayId() + " " + displayId);
                     }
                 }
             }
@@ -3408,14 +3646,17 @@
     boolean switchUserLocked(int userId, UserState uss) {
         final int focusStackId = mFocusedStack.getStackId();
         // We dismiss the docked stack whenever we switch users.
-        moveTasksToFullscreenStackLocked(DOCKED_STACK_ID, focusStackId == DOCKED_STACK_ID);
+        final ActivityStack dockedStack = getDefaultDisplay().getSplitScreenStack();
+        if (dockedStack != null) {
+            moveTasksToFullscreenStackLocked(dockedStack, mFocusedStack == dockedStack);
+        }
         // Also dismiss the pinned stack whenever we switch users. Removing the pinned stack will
         // also cause all tasks to be moved to the fullscreen stack at a position that is
         // appropriate.
         removeStackLocked(PINNED_STACK_ID);
 
         mUserStackInFront.put(mCurrentUser, focusStackId);
-        final int restoreStackId = mUserStackInFront.get(userId, HOME_STACK_ID);
+        final int restoreStackId = mUserStackInFront.get(userId, mHomeStack.mStackId);
         mCurrentUser = userId;
 
         mStartingUsers.add(uss);
@@ -3448,7 +3689,7 @@
     /** Checks whether the userid is a profile of the current user. */
     boolean isCurrentProfileLocked(int userId) {
         if (userId == mCurrentUser) return true;
-        return mService.mUserController.isCurrentProfileLocked(userId);
+        return mService.mUserController.isCurrentProfile(userId);
     }
 
     /**
@@ -3555,15 +3796,9 @@
         pw.print(prefix);
         pw.println("mCurTaskIdForUser=" + mCurTaskIdForUser);
         pw.print(prefix); pw.println("mUserStackInFront=" + mUserStackInFront);
-        pw.print(prefix); pw.println("mStacks=" + mStacks);
-        // TODO: move this to LockTaskController
-        final SparseArray<String[]> packages = mService.mLockTaskPackages;
-        if (packages.size() > 0) {
-            pw.print(prefix); pw.println("mLockTaskPackages (userId:packages)=");
-            for (int i = 0; i < packages.size(); ++i) {
-                pw.print(prefix); pw.print(prefix); pw.print(packages.keyAt(i));
-                pw.print(":"); pw.println(Arrays.toString(packages.valueAt(i)));
-            }
+        for (int i = mActivityDisplays.size() - 1; i >= 0; --i) {
+            final ActivityDisplay display = mActivityDisplays.valueAt(i);
+            pw.println(prefix + "displayId=" + display.mDisplayId + " mStacks=" + display.mStacks);
         }
         if (!mWaitingForActivityVisible.isEmpty()) {
             pw.print(prefix); pw.println("mWaitingForActivityVisible=");
@@ -3576,6 +3811,26 @@
         mService.mLockTaskController.dump(pw, prefix);
     }
 
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+        super.writeToProto(proto, CONFIGURATION_CONTAINER);
+        for (int displayNdx = 0; displayNdx < mActivityDisplays.size(); ++displayNdx) {
+            ActivityDisplay activityDisplay = mActivityDisplays.valueAt(displayNdx);
+            activityDisplay.writeToProto(proto, DISPLAYS);
+        }
+        mKeyguardController.writeToProto(proto, KEYGUARD_CONTROLLER);
+        if (mFocusedStack != null) {
+            proto.write(FOCUSED_STACK_ID, mFocusedStack.mStackId);
+            ActivityRecord focusedActivity = getResumedActivityLocked();
+            if (focusedActivity != null) {
+                focusedActivity.writeIdentifierToProto(proto, RESUMED_ACTIVITY);
+            }
+        } else {
+            proto.write(FOCUSED_STACK_ID, INVALID_STACK_ID);
+        }
+        proto.end(token);
+    }
+
     /**
      * Dump all connected displays' configurations.
      * @param prefix Prefix to apply to each line of the dump.
@@ -3605,8 +3860,7 @@
                 ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
                 for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
                     ActivityStack stack = stacks.get(stackNdx);
-                    if (!dumpVisibleStacksOnly ||
-                            stack.shouldBeVisible(null) == STACK_VISIBLE) {
+                    if (!dumpVisibleStacksOnly || stack.shouldBeVisible(null)) {
                         activities.addAll(stack.getDumpActivitiesLocked(name));
                     }
                 }
@@ -3832,15 +4086,22 @@
         return getActivityDisplayOrCreateLocked(displayId) != null;
     }
 
+    // TODO: Look into consolidating with getActivityDisplayOrCreateLocked()
     ActivityDisplay getActivityDisplay(int displayId) {
         return mActivityDisplays.get(displayId);
     }
 
+    // TODO(multi-display): Look at all callpoints to make sure they make sense in multi-display.
+    ActivityDisplay getDefaultDisplay() {
+        return mActivityDisplays.get(DEFAULT_DISPLAY);
+    }
+
     /**
      * Get an existing instance of {@link ActivityDisplay} or create new if there is a
      * corresponding record in display manager.
      */
-    private ActivityDisplay getActivityDisplayOrCreateLocked(int displayId) {
+    // TODO: Look into consolidating with getActivityDisplay()
+    ActivityDisplay getActivityDisplayOrCreateLocked(int displayId) {
         ActivityDisplay activityDisplay = mActivityDisplays.get(displayId);
         if (activityDisplay != null) {
             return activityDisplay;
@@ -3855,17 +4116,18 @@
             return null;
         }
         // The display hasn't been added to ActivityManager yet, create a new record now.
-        activityDisplay = new ActivityDisplay(displayId);
-        if (activityDisplay.mDisplay == null) {
-            Slog.w(TAG, "Display " + displayId + " gone before initialization complete");
-            return null;
-        }
-        mActivityDisplays.put(displayId, activityDisplay);
+        activityDisplay = new ActivityDisplay(this, displayId);
+        attachDisplay(activityDisplay);
         calculateDefaultMinimalSizeOfResizeableTasks(activityDisplay);
         mWindowManager.onDisplayAdded(displayId);
         return activityDisplay;
     }
 
+    @VisibleForTesting
+    void attachDisplay(ActivityDisplay display) {
+        mActivityDisplays.put(display.mDisplayId, display);
+    }
+
     private void calculateDefaultMinimalSizeOfResizeableTasks(ActivityDisplay display) {
         mDefaultMinSizeOfResizeableTask =
                 mService.mContext.getResources().getDimensionPixelSize(
@@ -3893,7 +4155,7 @@
                         // Moving all tasks to fullscreen stack, because it's guaranteed to be
                         // a valid launch stack for all activities. This way the task history from
                         // external display will be preserved on primary after move.
-                        moveTasksToFullscreenStackLocked(stack.getStackId(), true /* onTop */);
+                        moveTasksToFullscreenStackLocked(stack, true /* onTop */);
                     }
                 }
 
@@ -3955,7 +4217,7 @@
         if (display.mAllSleepTokens.isEmpty()) {
             return;
         }
-        for (SleepTokenImpl token : display.mAllSleepTokens) {
+        for (SleepToken token : display.mAllSleepTokens) {
             mSleepTokens.remove(token);
         }
         display.mAllSleepTokens.clear();
@@ -3963,7 +4225,7 @@
         mService.updateSleepIfNeededLocked();
     }
 
-    private StackInfo getStackInfoLocked(ActivityStack stack) {
+    private StackInfo getStackInfo(ActivityStack stack) {
         final int displayId = stack.mDisplayId;
         final ActivityDisplay display = mActivityDisplays.get(displayId);
         StackInfo info = new StackInfo();
@@ -3971,11 +4233,10 @@
         info.displayId = displayId;
         info.stackId = stack.mStackId;
         info.userId = stack.mCurrentUser;
-        info.visible = stack.shouldBeVisible(null) == STACK_VISIBLE;
+        info.visible = stack.shouldBeVisible(null);
         // A stack might be not attached to a display.
-        info.position = display != null
-                ? display.mStacks.indexOf(stack)
-                : 0;
+        info.position = display != null ? display.mStacks.indexOf(stack) : 0;
+        info.configuration.setTo(stack.getConfiguration());
 
         ArrayList<TaskRecord> tasks = stack.getAllTasks();
         final int numTasks = tasks.size();
@@ -4004,40 +4265,44 @@
         return info;
     }
 
-    StackInfo getStackInfoLocked(int stackId) {
+    StackInfo getStackInfo(int stackId) {
         ActivityStack stack = getStack(stackId);
         if (stack != null) {
-            return getStackInfoLocked(stack);
+            return getStackInfo(stack);
         }
         return null;
     }
 
+    StackInfo getStackInfo(int windowingMode, int activityType) {
+        final ActivityStack stack = getStack(windowingMode, activityType);
+        return (stack != null) ? getStackInfo(stack) : null;
+    }
+
     ArrayList<StackInfo> getAllStackInfosLocked() {
         ArrayList<StackInfo> list = new ArrayList<>();
         for (int displayNdx = 0; displayNdx < mActivityDisplays.size(); ++displayNdx) {
             ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
             for (int ndx = stacks.size() - 1; ndx >= 0; --ndx) {
-                list.add(getStackInfoLocked(stacks.get(ndx)));
+                list.add(getStackInfo(stacks.get(ndx)));
             }
         }
         return list;
     }
 
-    void handleNonResizableTaskIfNeeded(TaskRecord task, int preferredStackId,
+    void handleNonResizableTaskIfNeeded(TaskRecord task, int preferredWindowingMode,
             int preferredDisplayId, int actualStackId) {
-        handleNonResizableTaskIfNeeded(task, preferredStackId, preferredDisplayId, actualStackId,
-                false /* forceNonResizable */);
+        handleNonResizableTaskIfNeeded(task, preferredWindowingMode, preferredDisplayId,
+                actualStackId, false /* forceNonResizable */);
     }
 
-    void handleNonResizableTaskIfNeeded(TaskRecord task, int preferredStackId,
+    void handleNonResizableTaskIfNeeded(TaskRecord task, int preferredWindowingMode,
             int preferredDisplayId, int actualStackId, boolean forceNonResizable) {
         final boolean isSecondaryDisplayPreferred =
-                (preferredDisplayId != DEFAULT_DISPLAY && preferredDisplayId != INVALID_DISPLAY)
-                || StackId.isDynamicStack(preferredStackId);
+                (preferredDisplayId != DEFAULT_DISPLAY && preferredDisplayId != INVALID_DISPLAY);
         final ActivityStack actualStack = getStack(actualStackId);
         final boolean inSplitScreenMode = actualStack != null
                 && actualStack.inSplitScreenWindowingMode();
-        if (((!inSplitScreenMode && preferredStackId != DOCKED_STACK_ID)
+        if (((!inSplitScreenMode && preferredWindowingMode != WINDOWING_MODE_SPLIT_SCREEN_PRIMARY)
                 && !isSecondaryDisplayPreferred) || task.isActivityTypeHome()) {
             return;
         }
@@ -4065,7 +4330,8 @@
         }
 
         final ActivityRecord topActivity = task.getTopActivity();
-        if (launchOnSecondaryDisplayFailed || !task.supportsSplitScreen() || forceNonResizable) {
+        if (launchOnSecondaryDisplayFailed
+                || !task.supportsSplitScreenWindowingMode() || forceNonResizable) {
             if (launchOnSecondaryDisplayFailed) {
                 // Display a warning toast that we tried to put a non-resizeable task on a secondary
                 // display with config different from global config.
@@ -4079,7 +4345,12 @@
 
             // Dismiss docked stack. If task appeared to be in docked stack but is not resizable -
             // we need to move it to top of fullscreen stack, otherwise it will be covered.
-            moveTasksToFullscreenStackLocked(DOCKED_STACK_ID, actualStackId == DOCKED_STACK_ID);
+
+            final ActivityStack dockedStack = task.getStack().getDisplay().getSplitScreenStack();
+            if (dockedStack != null) {
+                moveTasksToFullscreenStackLocked(dockedStack,
+                        actualStackId == dockedStack.getStackId());
+            }
         } else if (topActivity != null && topActivity.isNonResizableOrForcedResizable()
                 && !topActivity.noDisplay) {
             final String packageName = topActivity.appInfo.packageName;
@@ -4284,122 +4555,6 @@
         }
     }
 
-    // TODO: Move to its own file.
-    /** Exactly one of these classes per Display in the system. Capable of holding zero or more
-     * attached {@link ActivityStack}s */
-    class ActivityDisplay extends ConfigurationContainer {
-        /** Actual Display this object tracks. */
-        int mDisplayId;
-        Display mDisplay;
-
-        /** All of the stacks on this display. Order matters, topmost stack is in front of all other
-         * stacks, bottommost behind. Accessed directly by ActivityManager package classes */
-        final ArrayList<ActivityStack> mStacks = new ArrayList<>();
-
-        /** Array of all UIDs that are present on the display. */
-        private IntArray mDisplayAccessUIDs = new IntArray();
-
-        /** All tokens used to put activities on this stack to sleep (including mOffToken) */
-        final ArrayList<SleepTokenImpl> mAllSleepTokens = new ArrayList<>();
-        /** The token acquired by ActivityStackSupervisor to put stacks on the display to sleep */
-        SleepToken mOffToken;
-
-        private boolean mSleeping;
-
-        @VisibleForTesting
-        ActivityDisplay() {
-            mActivityDisplays.put(mDisplayId, this);
-        }
-
-        // After instantiation, check that mDisplay is not null before using this. The alternative
-        // is for this to throw an exception if mDisplayManager.getDisplay() returns null.
-        ActivityDisplay(int displayId) {
-            final Display display = mDisplayManager.getDisplay(displayId);
-            if (display == null) {
-                return;
-            }
-            init(display);
-        }
-
-        void init(Display display) {
-            mDisplay = display;
-            mDisplayId = display.getDisplayId();
-        }
-
-        void attachStack(ActivityStack stack, int position) {
-            if (DEBUG_STACK) Slog.v(TAG_STACK, "attachStack: attaching " + stack
-                    + " to displayId=" + mDisplayId + " position=" + position);
-            mStacks.add(position, stack);
-            mService.updateSleepIfNeededLocked();
-        }
-
-        void detachStack(ActivityStack stack) {
-            if (DEBUG_STACK) Slog.v(TAG_STACK, "detachStack: detaching " + stack
-                    + " from displayId=" + mDisplayId);
-            mStacks.remove(stack);
-            mService.updateSleepIfNeededLocked();
-        }
-
-        @Override
-        public String toString() {
-            return "ActivityDisplay={" + mDisplayId + " numStacks=" + mStacks.size() + "}";
-        }
-
-        @Override
-        protected int getChildCount() {
-            return mStacks.size();
-        }
-
-        @Override
-        protected ConfigurationContainer getChildAt(int index) {
-            return mStacks.get(index);
-        }
-
-        @Override
-        protected ConfigurationContainer getParent() {
-            return ActivityStackSupervisor.this;
-        }
-
-        boolean isPrivate() {
-            return (mDisplay.getFlags() & FLAG_PRIVATE) != 0;
-        }
-
-        boolean isUidPresent(int uid) {
-            for (ActivityStack stack : mStacks) {
-                if (stack.isUidPresent(uid)) {
-                    return true;
-                }
-            }
-            return false;
-        }
-
-        /** Update and get all UIDs that are present on the display and have access to it. */
-        private IntArray getPresentUIDs() {
-            mDisplayAccessUIDs.clear();
-            for (ActivityStack stack : mStacks) {
-                stack.getPresentUIDs(mDisplayAccessUIDs);
-            }
-            return mDisplayAccessUIDs;
-        }
-
-        boolean shouldDestroyContentOnRemove() {
-            return mDisplay.getRemoveMode() == REMOVE_MODE_DESTROY_CONTENT;
-        }
-
-        boolean shouldSleep() {
-            return (mStacks.isEmpty() || !mAllSleepTokens.isEmpty())
-                    && (mService.mRunningVoice == null);
-        }
-
-        boolean isSleeping() {
-            return mSleeping;
-        }
-
-        void setIsSleeping(boolean asleep) {
-            mSleeping = asleep;
-        }
-    }
-
     ActivityStack findStackBehind(ActivityStack stack) {
         // TODO(multi-display): We are only looking for stacks on the default display.
         final ActivityDisplay display = mActivityDisplays.get(DEFAULT_DISPLAY);
@@ -4432,32 +4587,36 @@
         final String callingPackage;
         final Intent intent;
         final int userId;
+        int activityType = ACTIVITY_TYPE_UNDEFINED;
+        int windowingMode = WINDOWING_MODE_UNDEFINED;
         final ActivityOptions activityOptions = (bOptions != null)
                 ? new ActivityOptions(bOptions) : null;
-        final int launchStackId = (activityOptions != null)
-                ? activityOptions.getLaunchStackId() : INVALID_STACK_ID;
-        if (StackId.isHomeOrRecentsStack(launchStackId)) {
+        if (activityOptions != null) {
+            activityType = activityOptions.getLaunchActivityType();
+            windowingMode = activityOptions.getLaunchWindowingMode();
+        }
+        if (activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS) {
             throw new IllegalArgumentException("startActivityFromRecentsInner: Task "
                     + taskId + " can't be launch in the home/recents stack.");
         }
 
         mWindowManager.deferSurfaceLayout();
         try {
-            if (launchStackId == DOCKED_STACK_ID) {
+            if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
                 mWindowManager.setDockedStackCreateState(
                         activityOptions.getDockCreateMode(), null /* initialBounds */);
 
                 // Defer updating the stack in which recents is until the app transition is done, to
                 // not run into issues where we still need to draw the task in recents but the
                 // docked stack is already created.
-                deferUpdateBounds(RECENTS_STACK_ID);
+                deferUpdateBounds(ACTIVITY_TYPE_RECENTS);
                 mWindowManager.prepareAppTransition(TRANSIT_DOCK_TASK_FROM_RECENTS, false);
             }
 
             task = anyTaskForIdLocked(taskId, MATCH_TASK_IN_STACKS_OR_RECENT_TASKS_AND_RESTORE,
-                    launchStackId);
+                    activityOptions);
             if (task == null) {
-                continueUpdateBounds(RECENTS_STACK_ID);
+                continueUpdateBounds(ACTIVITY_TYPE_RECENTS);
                 mWindowManager.executeAppTransition();
                 throw new IllegalArgumentException(
                         "startActivityFromRecentsInner: Task " + taskId + " not found.");
@@ -4465,15 +4624,11 @@
 
             // Since we don't have an actual source record here, we assume that the currently
             // focused activity was the source.
-            final ActivityStack focusedStack = getFocusedStack();
-            final ActivityRecord sourceRecord =
-                    focusedStack != null ? focusedStack.topActivity() : null;
+            final ActivityStack stack = getLaunchStack(null, activityOptions, task, ON_TOP);
 
-            if (launchStackId != INVALID_STACK_ID) {
-                if (task.getStackId() != launchStackId) {
-                    task.reparent(launchStackId, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT, ANIMATE,
-                            DEFER_RESUME, "startActivityFromRecents");
-                }
+            if (stack != null && task.getStack() != stack) {
+                task.reparent(stack, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT, ANIMATE, DEFER_RESUME,
+                        "startActivityFromRecents");
             }
 
             // If the user must confirm credentials (e.g. when first launching a work app and the
@@ -4492,15 +4647,12 @@
                 // If we are launching the task in the docked stack, put it into resizing mode so
                 // the window renders full-screen with the background filling the void. Also only
                 // call this at the end to make sure that tasks exists on the window manager side.
-                if (launchStackId == DOCKED_STACK_ID) {
+                if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
                     setResizingDuringAnimation(task);
                 }
 
                 mService.mActivityStarter.postStartActivityProcessing(task.getTopActivity(),
-                        ActivityManager.START_TASK_TO_FRONT,
-                        sourceRecord != null
-                                ? sourceRecord.getTask().getStackId() : INVALID_STACK_ID,
-                        sourceRecord, task.getStack());
+                        ActivityManager.START_TASK_TO_FRONT, task.getStack());
                 return ActivityManager.START_TASK_TO_FRONT;
             }
             callingUid = task.mCallingUid;
@@ -4510,7 +4662,7 @@
             userId = task.userId;
             int result = mService.startActivityInPackage(callingUid, callingPackage, intent, null,
                     null, null, 0, 0, bOptions, userId, task, "startActivityFromRecents");
-            if (launchStackId == DOCKED_STACK_ID) {
+            if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
                 setResizingDuringAnimation(task);
             }
             return result;
@@ -4532,8 +4684,7 @@
             for (int j = display.mStacks.size() - 1; j >= 0; j--) {
                 final ActivityStack stack = display.mStacks.get(j);
                 // Get top activity from a visible stack and add it to the list.
-                if (stack.shouldBeVisible(null /* starting */)
-                        == ActivityStack.STACK_VISIBLE) {
+                if (stack.shouldBeVisible(null /* starting */)) {
                     final ActivityRecord top = stack.topActivity();
                     if (top != null) {
                         if (stack == mFocusedStack) {
diff --git a/com/android/server/am/ActivityStarter.java b/com/android/server/am/ActivityStarter.java
index 16abcfb..d444c66 100644
--- a/com/android/server/am/ActivityStarter.java
+++ b/com/android/server/am/ActivityStarter.java
@@ -27,18 +27,19 @@
 import static android.app.ActivityManager.START_SUCCESS;
 import static android.app.ActivityManager.START_TASK_TO_FRONT;
 import static android.app.ActivityManager.StackId;
-import static android.app.ActivityManager.StackId.ASSISTANT_STACK_ID;
 import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
-import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.HOME_STACK_ID;
 import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
 import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
-import static android.app.ActivityManager.StackId.RECENTS_STACK_ID;
 import static android.app.ActivityManager.StackId.isDynamicStack;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
 import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
@@ -76,8 +77,6 @@
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.am.ActivityManagerService.ANIMATE;
 import static com.android.server.am.ActivityStack.ActivityState.RESUMED;
-import static com.android.server.am.ActivityStack.STACK_INVISIBLE;
-import static com.android.server.am.ActivityStackSupervisor.CREATE_IF_NEEDED;
 import static com.android.server.am.ActivityStackSupervisor.DEFER_RESUME;
 import static com.android.server.am.ActivityStackSupervisor.ON_TOP;
 import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS;
@@ -167,7 +166,8 @@
     private boolean mDoResume;
     private int mStartFlags;
     private ActivityRecord mSourceRecord;
-    private int mSourceDisplayId;
+    // The display to launch the activity onto, barring any strong reason to do otherwise.
+    private int mPreferredDisplayId;
 
     private TaskRecord mInTask;
     private boolean mAddingToTask;
@@ -186,6 +186,12 @@
     private boolean mAvoidMoveToFront;
     private boolean mPowerHintSent;
 
+    // We must track when we deliver the new intent since multiple code paths invoke
+    // {@link #deliverNewIntent}. This is due to early returns in the code path. This flag is used
+    // inside {@link #deliverNewIntent} to suppress duplicate requests and ensure the intent is
+    // delivered at most once.
+    private boolean mIntentDelivered;
+
     private IVoiceInteractionSession mVoiceSession;
     private IVoiceInteractor mVoiceInteractor;
 
@@ -222,7 +228,7 @@
         mDoResume = false;
         mStartFlags = 0;
         mSourceRecord = null;
-        mSourceDisplayId = INVALID_DISPLAY;
+        mPreferredDisplayId = INVALID_DISPLAY;
 
         mInTask = null;
         mAddingToTask = false;
@@ -243,6 +249,8 @@
         mVoiceInteractor = null;
 
         mUsingVr2dDisplay = false;
+
+        mIntentDelivered = false;
     }
 
     ActivityStarter(ActivityManagerService service, ActivityStackSupervisor supervisor) {
@@ -589,9 +597,7 @@
                 auxiliaryResponse.token, auxiliaryResponse.needsPhaseTwo);
     }
 
-    void postStartActivityProcessing(
-            ActivityRecord r, int result, int prevFocusedStackId, ActivityRecord sourceRecord,
-            ActivityStack targetStack) {
+    void postStartActivityProcessing(ActivityRecord r, int result, ActivityStack targetStack) {
 
         if (ActivityManager.isStartResultFatalError(result)) {
             return;
@@ -613,7 +619,8 @@
         }
 
         if (startedActivityStackId == DOCKED_STACK_ID) {
-            final ActivityStack homeStack = mSupervisor.getStack(HOME_STACK_ID);
+            final ActivityStack homeStack = mSupervisor.getDefaultDisplay().getStack(
+                            WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME);
             final boolean homeStackVisible = homeStack != null && homeStack.isVisible();
             if (homeStackVisible) {
                 // We launch an activity while being in home stack, which means either launcher or
@@ -1001,8 +1008,7 @@
             mService.mWindowManager.continueSurfaceLayout();
         }
 
-        postStartActivityProcessing(r, result, mSupervisor.getLastStack().mStackId,  mSourceRecord,
-                mTargetStack);
+        postStartActivityProcessing(r, result, mTargetStack);
 
         return result;
     }
@@ -1024,10 +1030,12 @@
 
         ActivityRecord reusedActivity = getReusableIntentActivity();
 
-        final int preferredLaunchStackId =
-                (mOptions != null) ? mOptions.getLaunchStackId() : INVALID_STACK_ID;
-        final int preferredLaunchDisplayId =
-                (mOptions != null) ? mOptions.getLaunchDisplayId() : DEFAULT_DISPLAY;
+        int preferredWindowingMode = WINDOWING_MODE_UNDEFINED;
+        int preferredLaunchDisplayId = DEFAULT_DISPLAY;
+        if (mOptions != null) {
+            preferredWindowingMode = mOptions.getLaunchWindowingMode();
+            preferredLaunchDisplayId = mOptions.getLaunchDisplayId();
+        }
 
         if (reusedActivity != null) {
             // When the flags NEW_TASK and CLEAR_TASK are set, then the task gets reused but
@@ -1077,9 +1085,7 @@
                         // so make sure the task now has the identity of the new intent.
                         top.getTask().setIntent(mStartActivity);
                     }
-                    ActivityStack.logStartActivity(AM_NEW_INTENT, mStartActivity, top.getTask());
-                    top.deliverNewIntentLocked(mCallingUid, mStartActivity.intent,
-                            mStartActivity.launchedFromPackage);
+                    deliverNewIntent(top);
                 }
             }
 
@@ -1141,7 +1147,6 @@
                 && ((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0
                 || mLaunchSingleTop || mLaunchSingleTask);
         if (dontStart) {
-            ActivityStack.logStartActivity(AM_NEW_INTENT, top, top.getTask());
             // For paranoia, make sure we have correctly resumed the top activity.
             topStack.mLastPausedActivity = null;
             if (mDoResume) {
@@ -1153,12 +1158,12 @@
                 // anything if that is the case, so this is it!
                 return START_RETURN_INTENT_TO_CALLER;
             }
-            top.deliverNewIntentLocked(
-                    mCallingUid, mStartActivity.intent, mStartActivity.launchedFromPackage);
+
+            deliverNewIntent(top);
 
             // Don't use mStartActivity.task to show the toast. We're not starting a new activity
             // but reusing 'top'. Fields in mStartActivity may not be fully initialized.
-            mSupervisor.handleNonResizableTaskIfNeeded(top.getTask(), preferredLaunchStackId,
+            mSupervisor.handleNonResizableTaskIfNeeded(top.getTask(), preferredWindowingMode,
                     preferredLaunchDisplayId, topStack.mStackId);
 
             return START_DELIVERED_TO_TOP;
@@ -1173,8 +1178,7 @@
         if (mStartActivity.resultTo == null && mInTask == null && !mAddingToTask
                 && (mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0) {
             newTask = true;
-            result = setTaskFromReuseOrCreateNewTask(
-                    taskToAffiliate, preferredLaunchStackId, topStack);
+            result = setTaskFromReuseOrCreateNewTask(taskToAffiliate, topStack);
         } else if (mSourceRecord != null) {
             result = setTaskFromSourceRecord();
         } else if (mInTask != null) {
@@ -1237,11 +1241,11 @@
                         mOptions);
             }
         } else {
-            mTargetStack.addRecentActivityLocked(mStartActivity);
+            mSupervisor.addRecentActivity(mStartActivity);
         }
         mSupervisor.updateUserStackLocked(mStartActivity.userId, mTargetStack);
 
-        mSupervisor.handleNonResizableTaskIfNeeded(mStartActivity.getTask(), preferredLaunchStackId,
+        mSupervisor.handleNonResizableTaskIfNeeded(mStartActivity.getTask(), preferredWindowingMode,
                 preferredLaunchDisplayId, mTargetStack.mStackId);
 
         return START_SUCCESS;
@@ -1260,7 +1264,7 @@
         mVoiceSession = voiceSession;
         mVoiceInteractor = voiceInteractor;
 
-        mSourceDisplayId = getSourceDisplayId(mSourceRecord, mStartActivity);
+        mPreferredDisplayId = getPreferedDisplayId(mSourceRecord, mStartActivity, options);
 
         mLaunchBounds = getOverrideBounds(r, options, inTask);
 
@@ -1515,7 +1519,7 @@
                         !mLaunchSingleTask);
             } else {
                 // Otherwise find the best task to put the activity in.
-                intentActivity = mSupervisor.findTaskLocked(mStartActivity, mSourceDisplayId);
+                intentActivity = mSupervisor.findTaskLocked(mStartActivity, mPreferredDisplayId);
             }
         }
         return intentActivity;
@@ -1523,10 +1527,12 @@
 
     /**
      * Returns the ID of the display to use for a new activity. If the device is in VR mode,
-     * then return the Vr mode's virtual display ID. If not, if the source activity has
-     * a explicit display ID set, use that to launch the activity.
+     * then return the Vr mode's virtual display ID. If not,  if the activity was started with
+     * a launchDisplayId, use that. Otherwise, if the source activity has a explicit display ID
+     * set, use that to launch the activity.
      */
-    private int getSourceDisplayId(ActivityRecord sourceRecord, ActivityRecord startingActivity) {
+    private int getPreferedDisplayId(
+            ActivityRecord sourceRecord, ActivityRecord startingActivity, ActivityOptions options) {
         // Check if the Activity is a VR activity. If so, the activity should be launched in
         // main display.
         if (startingActivity != null && startingActivity.requestedVrComponent != null) {
@@ -1543,6 +1549,13 @@
             return displayId;
         }
 
+        // If the caller requested a display, prefer that display.
+        final int launchDisplayId =
+                (options != null) ? options.getLaunchDisplayId() : INVALID_DISPLAY;
+        if (launchDisplayId != INVALID_DISPLAY) {
+            return launchDisplayId;
+        }
+
         displayId = sourceRecord != null ? sourceRecord.getDisplayId() : INVALID_DISPLAY;
         // If the activity has a displayId set explicitly, launch it on the same displayId.
         if (displayId != INVALID_DISPLAY) {
@@ -1601,12 +1614,11 @@
                         mTargetStack.moveTaskToFrontLocked(intentTask, mNoAnimation, mOptions,
                                 mStartActivity.appTimeTracker, "bringingFoundTaskToFront");
                         mMovedToFront = true;
-                    } else if (launchStack.mStackId == DOCKED_STACK_ID
-                            || launchStack.mStackId == FULLSCREEN_WORKSPACE_STACK_ID) {
+                    } else if (launchStack.inSplitScreenWindowingMode()) {
                         if ((mLaunchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0) {
                             // If we want to launch adjacent and mTargetStack is not the computed
                             // launch stack - move task to top of computed stack.
-                            intentTask.reparent(launchStack.mStackId, ON_TOP,
+                            intentTask.reparent(launchStack, ON_TOP,
                                     REPARENT_MOVE_STACK_TO_FRONT, ANIMATE, DEFER_RESUME,
                                     "launchToSide");
                         } else {
@@ -1623,17 +1635,17 @@
                         // Target and computed stacks are on different displays and we've
                         // found a matching task - move the existing instance to that display and
                         // move it to front.
-                        intentActivity.getTask().reparent(launchStack.mStackId, ON_TOP,
+                        intentActivity.getTask().reparent(launchStack, ON_TOP,
                                 REPARENT_MOVE_STACK_TO_FRONT, ANIMATE, DEFER_RESUME,
                                 "reparentToDisplay");
                         mMovedToFront = true;
-                    } else if (launchStack.getStackId() == StackId.HOME_STACK_ID
-                        && mTargetStack.getStackId() != StackId.HOME_STACK_ID) {
+                    } else if (launchStack.isActivityTypeHome()
+                            && !mTargetStack.isActivityTypeHome()) {
                         // It is possible for the home activity to be in another stack initially.
                         // For example, the activity may have been initially started with an intent
                         // which placed it in the fullscreen stack. To ensure the proper handling of
                         // the activity based on home stack assumptions, we must move it over.
-                        intentActivity.getTask().reparent(launchStack.mStackId, ON_TOP,
+                        intentActivity.getTask().reparent(launchStack, ON_TOP,
                                 REPARENT_MOVE_STACK_TO_FRONT, ANIMATE, DEFER_RESUME,
                                 "reparentingHome");
                         mMovedToFront = true;
@@ -1654,8 +1666,8 @@
             mTargetStack.moveToFront("intentActivityFound");
         }
 
-        mSupervisor.handleNonResizableTaskIfNeeded(intentActivity.getTask(), INVALID_STACK_ID,
-                DEFAULT_DISPLAY, mTargetStack.mStackId);
+        mSupervisor.handleNonResizableTaskIfNeeded(intentActivity.getTask(),
+                WINDOWING_MODE_UNDEFINED, DEFAULT_DISPLAY, mTargetStack.mStackId);
 
         // If the caller has requested that the target task be reset, then do so.
         if ((mLaunchFlags & FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) {
@@ -1675,8 +1687,7 @@
             // Task will be launched over the home stack, so return home.
             task.setTaskToReturnTo(ACTIVITY_TYPE_HOME);
             return;
-        } else if (focusedStack != null && focusedStack != task.getStack() &&
-                focusedStack.isActivityTypeAssistant()) {
+        } else if (focusedStack != task.getStack() && focusedStack.isActivityTypeAssistant()) {
             // Task was launched over the assistant stack, so return there
             task.setTaskToReturnTo(ACTIVITY_TYPE_ASSISTANT);
             return;
@@ -1739,13 +1750,10 @@
             // desires.
             if (((mLaunchFlags & FLAG_ACTIVITY_SINGLE_TOP) != 0 || mLaunchSingleTop)
                     && intentActivity.realActivity.equals(mStartActivity.realActivity)) {
-                ActivityStack.logStartActivity(AM_NEW_INTENT, mStartActivity,
-                        intentActivity.getTask());
                 if (intentActivity.frontOfTask) {
                     intentActivity.getTask().setIntent(mStartActivity);
                 }
-                intentActivity.deliverNewIntentLocked(mCallingUid, mStartActivity.intent,
-                        mStartActivity.launchedFromPackage);
+                deliverNewIntent(intentActivity);
             } else if (!intentActivity.getTask().isSameIntentFilter(mStartActivity)) {
                 // In this case we are launching the root activity of the task, but with a
                 // different intent. We should start a new instance on top.
@@ -1779,7 +1787,7 @@
     }
 
     private int setTaskFromReuseOrCreateNewTask(
-            TaskRecord taskToAffiliate, int preferredLaunchStackId, ActivityStack topStack) {
+            TaskRecord taskToAffiliate, ActivityStack topStack) {
         mTargetStack = computeStackFocus(
                 mStartActivity, true, mLaunchBounds, mLaunchFlags, mOptions);
 
@@ -1793,15 +1801,8 @@
                     mNewTaskIntent != null ? mNewTaskIntent : mIntent, mVoiceSession,
                     mVoiceInteractor, !mLaunchTaskBehind /* toTop */);
             addOrReparentStartingActivity(task, "setTaskFromReuseOrCreateNewTask - mReuseTask");
-            if (mLaunchBounds != null) {
-                final int stackId = mTargetStack.mStackId;
-                if (StackId.resizeStackWithLaunchBounds(stackId)) {
-                    mService.resizeStack(
-                            stackId, mLaunchBounds, true, !PRESERVE_WINDOWS, ANIMATE, -1);
-                } else {
-                    mStartActivity.getTask().updateOverrideConfiguration(mLaunchBounds);
-                }
-            }
+            updateBounds(mStartActivity.getTask(), mLaunchBounds);
+
             if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Starting new activity " + mStartActivity
                     + " in new task " + mStartActivity.getTask());
         } else {
@@ -1821,8 +1822,10 @@
             // If stack id is specified in activity options, usually it means that activity is
             // launched not from currently focused stack (e.g. from SysUI or from shell) - in
             // that case we check the target stack.
+            // TODO: Not sure I understand the value or use of the commented out code and the
+            // comment above. See if this causes any issues and why...
             updateTaskReturnToType(mStartActivity.getTask(), mLaunchFlags,
-                    preferredLaunchStackId != INVALID_STACK_ID ? mTargetStack : topStack);
+                    /*preferredLaunchStackId != INVALID_STACK_ID ? mTargetStack : */topStack);
         }
         if (mDoResume) {
             mTargetStack.moveToFront("reuseOrNewTask");
@@ -1830,6 +1833,17 @@
         return START_SUCCESS;
     }
 
+    private void deliverNewIntent(ActivityRecord activity) {
+        if (mIntentDelivered) {
+            return;
+        }
+
+        ActivityStack.logStartActivity(AM_NEW_INTENT, activity, activity.getTask());
+        activity.deliverNewIntentLocked(mCallingUid, mStartActivity.intent,
+                mStartActivity.launchedFromPackage);
+        mIntentDelivered = true;
+    }
+
     private int setTaskFromSourceRecord() {
         if (mService.mLockTaskController.isLockTaskModeViolation(mSourceRecord.getTask())) {
             Slog.e(TAG, "Attempted Lock Task Mode violation mStartActivity=" + mStartActivity);
@@ -1867,8 +1881,8 @@
         if (mTargetStack == null) {
             mTargetStack = sourceStack;
         } else if (mTargetStack != sourceStack) {
-            sourceTask.reparent(mTargetStack.mStackId, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT,
-                    !ANIMATE, DEFER_RESUME, "launchToSide");
+            sourceTask.reparent(mTargetStack, ON_TOP, REPARENT_MOVE_STACK_TO_FRONT, !ANIMATE,
+                    DEFER_RESUME, "launchToSide");
         }
 
         final TaskRecord topTask = mTargetStack.topTask();
@@ -1886,7 +1900,7 @@
             mKeepCurTransition = true;
             if (top != null) {
                 ActivityStack.logStartActivity(AM_NEW_INTENT, mStartActivity, top.getTask());
-                top.deliverNewIntentLocked(mCallingUid, mStartActivity.intent, mStartActivity.launchedFromPackage);
+                deliverNewIntent(top);
                 // For paranoia, make sure we have correctly resumed the top activity.
                 mTargetStack.mLastPausedActivity = null;
                 if (mDoResume) {
@@ -1905,7 +1919,7 @@
                 task.moveActivityToFrontLocked(top);
                 top.updateOptionsLocked(mOptions);
                 ActivityStack.logStartActivity(AM_NEW_INTENT, mStartActivity, task);
-                top.deliverNewIntentLocked(mCallingUid, mStartActivity.intent, mStartActivity.launchedFromPackage);
+                deliverNewIntent(top);
                 mTargetStack.mLastPausedActivity = null;
                 if (mDoResume) {
                     mSupervisor.resumeFocusedStackTopActivityLocked();
@@ -1941,14 +1955,12 @@
                     || mLaunchSingleTop || mLaunchSingleTask) {
                 mTargetStack.moveTaskToFrontLocked(mInTask, mNoAnimation, mOptions,
                         mStartActivity.appTimeTracker, "inTaskToFront");
-                ActivityStack.logStartActivity(AM_NEW_INTENT, top, top.getTask());
                 if ((mStartFlags & START_FLAG_ONLY_IF_NEEDED) != 0) {
                     // We don't need to start a new activity, and the client said not to do
                     // anything if that is the case, so this is it!
                     return START_RETURN_INTENT_TO_CALLER;
                 }
-                top.deliverNewIntentLocked(mCallingUid, mStartActivity.intent,
-                        mStartActivity.launchedFromPackage);
+                deliverNewIntent(top);
                 return START_DELIVERED_TO_TOP;
             }
         }
@@ -1963,17 +1975,15 @@
         }
 
         if (mLaunchBounds != null) {
-            mInTask.updateOverrideConfiguration(mLaunchBounds);
-            int stackId = mInTask.getLaunchStackId();
-            if (stackId != mInTask.getStackId()) {
-                mInTask.reparent(stackId, ON_TOP, REPARENT_KEEP_STACK_AT_FRONT, !ANIMATE,
+            // TODO: Shouldn't we already know what stack to use by the time we get here?
+            ActivityStack stack = mSupervisor.getLaunchStack(null, null, mInTask, ON_TOP);
+            if (stack != mInTask.getStack()) {
+                mInTask.reparent(stack, ON_TOP, REPARENT_KEEP_STACK_AT_FRONT, !ANIMATE,
                         DEFER_RESUME, "inTaskToFront");
-                stackId = mInTask.getStackId();
                 mTargetStack = mInTask.getStack();
             }
-            if (StackId.resizeStackWithLaunchBounds(stackId)) {
-                mService.resizeStack(stackId, mLaunchBounds, true, !PRESERVE_WINDOWS, ANIMATE, -1);
-            }
+
+            updateBounds(mInTask, mLaunchBounds);
         }
 
         mTargetStack.moveTaskToFrontLocked(
@@ -1986,6 +1996,19 @@
         return START_SUCCESS;
     }
 
+    void updateBounds(TaskRecord task, Rect bounds) {
+        if (bounds == null) {
+            return;
+        }
+
+        final int stackId = task.getStackId();
+        if (StackId.resizeStackWithLaunchBounds(stackId)) {
+            mService.resizeStack(stackId, bounds, true, !PRESERVE_WINDOWS, ANIMATE, -1);
+        } else {
+            task.updateOverrideConfiguration(bounds);
+        }
+    }
+
     private void setTaskToCurrentTopOrCreateNewTask() {
         mTargetStack = computeStackFocus(mStartActivity, false, null /* bounds */, mLaunchFlags,
                 mOptions);
@@ -2079,20 +2102,21 @@
             return mSupervisor.mFocusedStack;
         }
 
-        if (mSourceDisplayId != DEFAULT_DISPLAY) {
+        if (mPreferredDisplayId != DEFAULT_DISPLAY) {
             // Try to put the activity in a stack on a secondary display.
-            stack = mSupervisor.getValidLaunchStackOnDisplay(mSourceDisplayId, r);
+            stack = mSupervisor.getValidLaunchStackOnDisplay(mPreferredDisplayId, r);
             if (stack == null) {
                 // If source display is not suitable - look for topmost valid stack in the system.
                 if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS,
-                        "computeStackFocus: Can't launch on mSourceDisplayId=" + mSourceDisplayId
-                                + ", looking on all displays.");
-                stack = mSupervisor.getNextValidLaunchStackLocked(r, mSourceDisplayId);
+                        "computeStackFocus: Can't launch on mPreferredDisplayId="
+                                + mPreferredDisplayId + ", looking on all displays.");
+                stack = mSupervisor.getNextValidLaunchStackLocked(r, mPreferredDisplayId);
             }
         }
         if (stack == null) {
             // We first try to put the task in the first dynamic stack on home display.
-            final ArrayList<ActivityStack> homeDisplayStacks = mSupervisor.mHomeStack.mStacks;
+            final ArrayList<ActivityStack> homeDisplayStacks =
+                    mSupervisor.getStacksOnDefaultDisplay();
             for (int stackNdx = homeDisplayStacks.size() - 1; stackNdx >= 0; --stackNdx) {
                 stack = homeDisplayStacks.get(stackNdx);
                 if (isDynamicStack(stack.mStackId)) {
@@ -2102,10 +2126,7 @@
                 }
             }
             // If there is no suitable dynamic stack then we figure out which static stack to use.
-            final int stackId = task != null ? task.getLaunchStackId() :
-                    bounds != null ? FREEFORM_WORKSPACE_STACK_ID :
-                            FULLSCREEN_WORKSPACE_STACK_ID;
-            stack = mSupervisor.getStack(stackId, CREATE_IF_NEEDED, ON_TOP);
+            stack = mSupervisor.getLaunchStack(r, aOptions, task, ON_TOP);
         }
         if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG_FOCUS, "computeStackFocus: New stack r="
                 + r + " stackId=" + stack.mStackId);
@@ -2113,37 +2134,40 @@
     }
 
     /** Check if provided activity record can launch in currently focused stack. */
+    // TODO: This method can probably be consolidated into getLaunchStack() below.
     private boolean canLaunchIntoFocusedStack(ActivityRecord r, boolean newTask) {
         final ActivityStack focusedStack = mSupervisor.mFocusedStack;
-        final int focusedStackId = mSupervisor.mFocusedStack.mStackId;
         final boolean canUseFocusedStack;
-        switch (focusedStackId) {
-            case FULLSCREEN_WORKSPACE_STACK_ID:
-                // The fullscreen stack can contain any task regardless of if the task is resizeable
-                // or not. So, we let the task go in the fullscreen task if it is the focus stack.
-                canUseFocusedStack = true;
-                break;
-            case ASSISTANT_STACK_ID:
-                canUseFocusedStack = r.isActivityTypeAssistant();
-                break;
-            case DOCKED_STACK_ID:
-                // Any activity which supports split screen can go in the docked stack.
-                canUseFocusedStack = r.supportsSplitScreen();
-                break;
-            case FREEFORM_WORKSPACE_STACK_ID:
-                // Any activity which supports freeform can go in the freeform stack.
-                canUseFocusedStack = r.supportsFreeform();
-                break;
-            default:
-                // Dynamic stacks behave similarly to the fullscreen stack and can contain any
-                // resizeable task.
-                canUseFocusedStack = isDynamicStack(focusedStackId)
-                        && r.canBeLaunchedOnDisplay(focusedStack.mDisplayId);
+        final int focusedStackId = mSupervisor.mFocusedStack.mStackId;
+        if (focusedStack.isActivityTypeAssistant()) {
+            canUseFocusedStack = r.isActivityTypeAssistant();
+        } else {
+            switch (focusedStack.getWindowingMode()) {
+                case WINDOWING_MODE_FULLSCREEN:
+                    // The fullscreen stack can contain any task regardless of if the task is
+                    // resizeable or not. So, we let the task go in the fullscreen task if it is the
+                    // focus stack.
+                    canUseFocusedStack = true;
+                    break;
+                case WINDOWING_MODE_SPLIT_SCREEN_PRIMARY:
+                case WINDOWING_MODE_SPLIT_SCREEN_SECONDARY:
+                    // Any activity which supports split screen can go in the docked stack.
+                    canUseFocusedStack = r.supportsSplitScreenWindowingMode();
+                    break;
+                case WINDOWING_MODE_FREEFORM:
+                    // Any activity which supports freeform can go in the freeform stack.
+                    canUseFocusedStack = r.supportsFreeform();
+                    break;
+                default:
+                    // Dynamic stacks behave similarly to the fullscreen stack and can contain any
+                    // resizeable task.
+                    canUseFocusedStack = isDynamicStack(focusedStackId)
+                            && r.canBeLaunchedOnDisplay(focusedStack.mDisplayId);
+            }
         }
-
         return canUseFocusedStack && !newTask
-                // We strongly prefer to launch activities on the same display as their source.
-                && (mSourceDisplayId == focusedStack.mDisplayId);
+                // Using the focus stack isn't important enough to override the preferred display.
+                && (mPreferredDisplayId == focusedStack.mDisplayId);
     }
 
     private ActivityStack getLaunchStack(ActivityRecord r, int launchFlags, TaskRecord task,
@@ -2153,54 +2177,16 @@
             return mReuseTask.getStack();
         }
 
-        // If the activity is of a specific type, return the associated stack, creating it if
-        // necessary
-        if (r.isActivityTypeHome()) {
-            return mSupervisor.mHomeStack;
-        }
-        if (r.isActivityTypeRecents()) {
-            return mSupervisor.getStack(RECENTS_STACK_ID, CREATE_IF_NEEDED, ON_TOP);
-        }
-        if (r.isActivityTypeAssistant()) {
-            return mSupervisor.getStack(ASSISTANT_STACK_ID, CREATE_IF_NEEDED, ON_TOP);
-        }
+        final int vrDisplayId = mUsingVr2dDisplay ? mPreferredDisplayId : INVALID_DISPLAY;
+        final ActivityStack launchStack = mSupervisor.getLaunchStack(r, aOptions, task, ON_TOP,
+                vrDisplayId);
 
-        final int launchDisplayId =
-                (aOptions != null) ? aOptions.getLaunchDisplayId() : INVALID_DISPLAY;
-
-        final int launchStackId =
-                (aOptions != null) ? aOptions.getLaunchStackId() : INVALID_STACK_ID;
-
-        if (launchStackId != INVALID_STACK_ID && launchDisplayId != INVALID_DISPLAY) {
-            throw new IllegalArgumentException(
-                    "Stack and display id can't be set at the same time.");
-        }
-
-        if (isValidLaunchStackId(launchStackId, launchDisplayId, r)) {
-            return mSupervisor.getStack(launchStackId, CREATE_IF_NEEDED, ON_TOP);
-        }
-        if (launchStackId == DOCKED_STACK_ID) {
-            // The preferred launch stack is the docked stack, but it isn't a valid launch stack
-            // for this activity, so we put the activity in the fullscreen stack.
-            return mSupervisor.getStack(FULLSCREEN_WORKSPACE_STACK_ID, CREATE_IF_NEEDED, ON_TOP);
-        }
-        if (launchDisplayId != INVALID_DISPLAY) {
-            // Stack id has higher priority than display id.
-            return mSupervisor.getValidLaunchStackOnDisplay(launchDisplayId, r);
-        }
-
-        // If we are using Vr2d display, find the virtual display stack.
-        if (mUsingVr2dDisplay) {
-            ActivityStack as = mSupervisor.getValidLaunchStackOnDisplay(mSourceDisplayId, r);
-            if (DEBUG_STACK) {
-                Slog.v(TAG, "Launch stack for app: " + r.toString() +
-                    ", on virtual display stack:" + as.toString());
-            }
-            return as;
+        if (launchStack != null) {
+            return launchStack;
         }
 
         if (((launchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) == 0)
-                 || mSourceDisplayId != DEFAULT_DISPLAY) {
+                 || mPreferredDisplayId != DEFAULT_DISPLAY) {
             return null;
         }
         // Otherwise handle adjacent launch.
@@ -2222,15 +2208,16 @@
             if (parentStack != null && parentStack.isDockedStack()) {
                 // If parent was in docked stack, the natural place to launch another activity
                 // will be fullscreen, so it can appear alongside the docked window.
-                return mSupervisor.getStack(FULLSCREEN_WORKSPACE_STACK_ID, CREATE_IF_NEEDED,
-                        ON_TOP);
+                final int activityType = mSupervisor.resolveActivityType(r, mOptions, task);
+                return parentStack.getDisplay().getOrCreateStack(
+                        WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, activityType, ON_TOP);
             } else {
                 // If the parent is not in the docked stack, we check if there is docked window
                 // and if yes, we will launch into that stack. If not, we just put the new
                 // activity into parent's stack, because we can't find a better place.
-                final ActivityStack dockedStack = mSupervisor.getStack(DOCKED_STACK_ID);
-                if (dockedStack != null
-                        && dockedStack.shouldBeVisible(r) == STACK_INVISIBLE) {
+                final ActivityStack dockedStack = mSupervisor.getDefaultDisplay().getStack(
+                                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_UNDEFINED);
+                if (dockedStack != null && !dockedStack.shouldBeVisible(r)) {
                     // There is a docked stack, but it isn't visible, so we can't launch into that.
                     return null;
                 } else {
@@ -2240,39 +2227,11 @@
         }
     }
 
-    boolean isValidLaunchStackId(int stackId, int displayId, ActivityRecord r) {
-        switch (stackId) {
-            case INVALID_STACK_ID:
-            case HOME_STACK_ID:
-                return false;
-            case FULLSCREEN_WORKSPACE_STACK_ID:
-                return true;
-            case FREEFORM_WORKSPACE_STACK_ID:
-                return r.supportsFreeform();
-            case DOCKED_STACK_ID:
-                return r.supportsSplitScreen();
-            case PINNED_STACK_ID:
-                return r.supportsPictureInPicture();
-            case RECENTS_STACK_ID:
-                return r.isActivityTypeRecents();
-            case ASSISTANT_STACK_ID:
-                return r.isActivityTypeAssistant();
-            default:
-                if (StackId.isDynamicStack(stackId)) {
-                    return r.canBeLaunchedOnDisplay(displayId);
-                }
-                Slog.e(TAG, "isValidLaunchStackId: Unexpected stackId=" + stackId);
-                return false;
-        }
-    }
-
-    Rect getOverrideBounds(ActivityRecord r, ActivityOptions options, TaskRecord inTask) {
+    private Rect getOverrideBounds(ActivityRecord r, ActivityOptions options, TaskRecord inTask) {
         Rect newBounds = null;
-        if (options != null && (r.isResizeable() || (inTask != null && inTask.isResizeable()))) {
-            if (mSupervisor.canUseActivityOptionsLaunchBounds(
-                    options, options.getLaunchStackId())) {
-                newBounds = TaskRecord.validateBounds(options.getLaunchBounds());
-            }
+        if (mSupervisor.canUseActivityOptionsLaunchBounds(options)
+                && (r.isResizeable() || (inTask != null && inTask.isResizeable()))) {
+            newBounds = TaskRecord.validateBounds(options.getLaunchBounds());
         }
         return newBounds;
     }
@@ -2281,15 +2240,6 @@
         mWindowManager = wm;
     }
 
-    void removePendingActivityLaunchesLocked(ActivityStack stack) {
-        for (int palNdx = mPendingActivityLaunches.size() - 1; palNdx >= 0; --palNdx) {
-            PendingActivityLaunch pal = mPendingActivityLaunches.get(palNdx);
-            if (pal.stack == stack) {
-                mPendingActivityLaunches.remove(palNdx);
-            }
-        }
-    }
-
     static boolean isDocumentLaunchesIntoExisting(int flags) {
         return (flags & Intent.FLAG_ACTIVITY_NEW_DOCUMENT) != 0 &&
                 (flags & Intent.FLAG_ACTIVITY_MULTIPLE_TASK) == 0;
diff --git a/com/android/server/am/AppErrors.java b/com/android/server/am/AppErrors.java
index 440b3d3..fe38097 100644
--- a/com/android/server/am/AppErrors.java
+++ b/com/android/server/am/AppErrors.java
@@ -507,7 +507,7 @@
         // launching the report UI under a different user.
         app.errorReportReceiver = null;
 
-        for (int userId : mService.mUserController.getCurrentProfileIdsLocked()) {
+        for (int userId : mService.mUserController.getCurrentProfileIds()) {
             if (app.userId == userId) {
                 app.errorReportReceiver = ApplicationErrorReport.getErrorReportReceiver(
                         mContext, app.info.packageName, app.info.flags);
@@ -728,7 +728,7 @@
             boolean isBackground = (UserHandle.getAppId(proc.uid)
                     >= Process.FIRST_APPLICATION_UID
                     && proc.pid != MY_PID);
-            for (int userId : mService.mUserController.getCurrentProfileIdsLocked()) {
+            for (int userId : mService.mUserController.getCurrentProfileIds()) {
                 isBackground &= (proc.userId != userId);
             }
             if (isBackground && !showBackground) {
diff --git a/com/android/server/am/BatteryStatsService.java b/com/android/server/am/BatteryStatsService.java
index 3105e37..7c9cd00 100644
--- a/com/android/server/am/BatteryStatsService.java
+++ b/com/android/server/am/BatteryStatsService.java
@@ -220,7 +220,7 @@
         // Shutdown the thread we made.
         mWorker.shutdown();
     }
-    
+
     public static IBatteryStats getService() {
         if (sService != null) {
             return sService;
@@ -300,10 +300,10 @@
 
             // TODO: remove this once we figure out properly where and how
             // PROCESS_EVENT = 1112
-            // EVENT SUBTYPE: START = 1
-            // KEY_NAME: 1
+            // KEY_STATE = 1
+            // KEY_PACKAGE_NAME: 1002
             // KEY_UID: 2
-            StatsLog.writeArray(1112, 1, 1, name, 2, uid);
+            StatsLog.writeArray(1112, 1, 1, 1002, name, 2, uid);
         }
     }
 
@@ -313,10 +313,10 @@
 
             // TODO: remove this once we figure out properly where and how
             // PROCESS_EVENT = 1112
-            // EVENT SUBTYPE: CRASH = 2
-            // KEY_NAME: 1
+            // KEY_STATE = 1
+            // KEY_PACKAGE_NAME: 1002
             // KEY_UID: 2
-            StatsLog.writeArray(1112, 2, 1, name, 2, uid);
+            StatsLog.writeArray(1112, 1, 2, 1002, name, 2, uid);
         }
     }
 
@@ -550,10 +550,10 @@
         synchronized (mStats) {
             mStats.noteScreenStateLocked(state);
             // TODO: remove this once we figure out properly where and how
-            // SCREEN_EVENT = 1003
-            // State key: 1
+            // SCREEN_EVENT = 2
+            // KEY_STATE: 1
             // State value: state. We can change this to our own def later.
-            StatsLog.writeArray(1003, 1, state);
+            StatsLog.writeArray(2, 1, state);
         }
         if (DBG) Slog.d(TAG, "end noteScreenState");
     }
@@ -564,14 +564,14 @@
             mStats.noteScreenBrightnessLocked(brightness);
         }
     }
-    
+
     public void noteUserActivity(int uid, int event) {
         enforceCallingPermission();
         synchronized (mStats) {
             mStats.noteUserActivityLocked(uid, event);
         }
     }
-    
+
     public void noteWakeUp(String reason, int reasonUid) {
         enforceCallingPermission();
         synchronized (mStats) {
@@ -611,21 +611,21 @@
             mStats.notePhoneOnLocked();
         }
     }
-    
+
     public void notePhoneOff() {
         enforceCallingPermission();
         synchronized (mStats) {
             mStats.notePhoneOffLocked();
         }
     }
-    
+
     public void notePhoneSignalStrength(SignalStrength signalStrength) {
         enforceCallingPermission();
         synchronized (mStats) {
             mStats.notePhoneSignalStrengthLocked(signalStrength);
         }
     }
-    
+
     public void notePhoneDataConnectionState(int dataType, boolean hasData) {
         enforceCallingPermission();
         synchronized (mStats) {
@@ -647,7 +647,7 @@
             mStats.noteWifiOnLocked();
         }
     }
-    
+
     public void noteWifiOff() {
         enforceCallingPermission();
         synchronized (mStats) {
@@ -806,7 +806,7 @@
             mStats.noteFullWifiLockAcquiredLocked(uid);
         }
     }
-    
+
     public void noteFullWifiLockReleased(int uid) {
         enforceCallingPermission();
         synchronized (mStats) {
@@ -1043,7 +1043,7 @@
             });
         });
     }
-    
+
     public long getAwakeTimeBattery() {
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.BATTERY_STATS, null);
@@ -1186,6 +1186,7 @@
 
         int flags = 0;
         boolean useCheckinFormat = false;
+        boolean toProto = false;
         boolean isRealCheckin = false;
         boolean noOutput = false;
         boolean writeData = false;
@@ -1212,6 +1213,8 @@
                 } else if ("-c".equals(arg)) {
                     useCheckinFormat = true;
                     flags |= BatteryStats.DUMP_INCLUDE_HISTORY;
+                } else if ("--proto".equals(arg)) {
+                    toProto = true;
                 } else if ("--charged".equals(arg)) {
                     flags |= BatteryStats.DUMP_CHARGED_ONLY;
                 } else if ("--daily".equals(arg)) {
@@ -1304,7 +1307,45 @@
             }
         }
 
-        if (useCheckinFormat) {
+        if (toProto) {
+            List<ApplicationInfo> apps = mContext.getPackageManager().getInstalledApplications(
+                    PackageManager.MATCH_ANY_USER | PackageManager.MATCH_ALL);
+            if (isRealCheckin) {
+                // For a real checkin, first we want to prefer to use the last complete checkin
+                // file if there is one.
+                synchronized (mStats.mCheckinFile) {
+                    if (mStats.mCheckinFile.exists()) {
+                        try {
+                            byte[] raw = mStats.mCheckinFile.readFully();
+                            if (raw != null) {
+                                Parcel in = Parcel.obtain();
+                                in.unmarshall(raw, 0, raw.length);
+                                in.setDataPosition(0);
+                                BatteryStatsImpl checkinStats = new BatteryStatsImpl(
+                                        null, mStats.mHandler, null, mUserManagerUserInfoProvider);
+                                checkinStats.readSummaryFromParcel(in);
+                                in.recycle();
+                                checkinStats.dumpProtoLocked(mContext, fd, apps, flags,
+                                        historyStart);
+                                mStats.mCheckinFile.delete();
+                                return;
+                            }
+                        } catch (IOException | ParcelFormatException e) {
+                            Slog.w(TAG, "Failure reading checkin file "
+                                    + mStats.mCheckinFile.getBaseFile(), e);
+                        }
+                    }
+                }
+            }
+            if (DBG) Slog.d(TAG, "begin dumpProtoLocked from UID " + Binder.getCallingUid());
+            synchronized (mStats) {
+                mStats.dumpProtoLocked(mContext, fd, apps, flags, historyStart);
+                if (writeData) {
+                    mStats.writeAsyncLocked();
+                }
+            }
+            if (DBG) Slog.d(TAG, "end dumpProtoLocked");
+        } else if (useCheckinFormat) {
             List<ApplicationInfo> apps = mContext.getPackageManager().getInstalledApplications(
                     PackageManager.MATCH_ANY_USER | PackageManager.MATCH_ALL);
             if (isRealCheckin) {
diff --git a/com/android/server/am/KeyguardController.java b/com/android/server/am/KeyguardController.java
index cea80c8..5c48f90 100644
--- a/com/android/server/am/KeyguardController.java
+++ b/com/android/server/am/KeyguardController.java
@@ -19,12 +19,15 @@
 import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
 import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
 import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.INVALID_DISPLAY;
 import static android.view.WindowManagerPolicy.KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS;
 import static android.view.WindowManagerPolicy.KEYGUARD_GOING_AWAY_FLAG_TO_SHADE;
 import static android.view.WindowManagerPolicy.KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS;
+import static com.android.server.am.proto.KeyguardControllerProto.KEYGUARD_OCCLUDED;
+import static com.android.server.am.proto.KeyguardControllerProto.KEYGUARD_SHOWING;
 import static com.android.server.wm.AppTransition.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION;
 import static com.android.server.wm.AppTransition.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_TO_SHADE;
 import static com.android.server.wm.AppTransition.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER;
@@ -38,6 +41,7 @@
 import android.os.RemoteException;
 import android.os.Trace;
 import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.policy.IKeyguardDismissCallback;
 import com.android.server.wm.WindowManagerService;
@@ -66,6 +70,7 @@
     private int mBeforeUnoccludeTransit;
     private int mVisibilityTransactionDepth;
     private SleepToken mSleepToken;
+    private int mSecondaryDisplayShowing = INVALID_DISPLAY;
 
     KeyguardController(ActivityManagerService service,
             ActivityStackSupervisor stackSupervisor) {
@@ -78,10 +83,12 @@
     }
 
     /**
-     * @return true if Keyguard is showing, not going away, and not being occluded, false otherwise
+     * @return true if Keyguard is showing, not going away, and not being occluded on the given
+     *         display, false otherwise
      */
-    boolean isKeyguardShowing() {
-        return mKeyguardShowing && !mKeyguardGoingAway && !mOccluded;
+    boolean isKeyguardShowing(int displayId) {
+        return mKeyguardShowing && !mKeyguardGoingAway &&
+                (displayId == DEFAULT_DISPLAY ? !mOccluded : displayId == mSecondaryDisplayShowing);
     }
 
     /**
@@ -94,15 +101,19 @@
     /**
      * Update the Keyguard showing state.
      */
-    void setKeyguardShown(boolean showing) {
-        if (showing == mKeyguardShowing) {
+    void setKeyguardShown(boolean showing, int secondaryDisplayShowing) {
+        boolean showingChanged = showing != mKeyguardShowing;
+        if (!showingChanged && secondaryDisplayShowing == mSecondaryDisplayShowing) {
             return;
         }
         mKeyguardShowing = showing;
-        dismissDockedStackIfNeeded();
-        if (showing) {
-            setKeyguardGoingAway(false);
-            mDismissalRequested = false;
+        mSecondaryDisplayShowing = secondaryDisplayShowing;
+        if (showingChanged) {
+            dismissDockedStackIfNeeded();
+            if (showing) {
+                setKeyguardGoingAway(false);
+                mDismissalRequested = false;
+            }
         }
         mStackSupervisor.ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
         updateKeyguardSleepToken();
@@ -331,15 +342,19 @@
             // show on top of the lock screen. In this can we want to dismiss the docked
             // stack since it will be complicated/risky to try to put the activity on top
             // of the lock screen in the right fullscreen configuration.
-            mStackSupervisor.moveTasksToFullscreenStackLocked(DOCKED_STACK_ID,
-                    mStackSupervisor.mFocusedStack.getStackId() == DOCKED_STACK_ID);
+            final ActivityStack stack = mStackSupervisor.getDefaultDisplay().getSplitScreenStack();
+            if (stack == null) {
+                return;
+            }
+            mStackSupervisor.moveTasksToFullscreenStackLocked(stack,
+                    mStackSupervisor.mFocusedStack == stack);
         }
     }
 
     private void updateKeyguardSleepToken() {
-        if (mSleepToken == null && isKeyguardShowing()) {
+        if (mSleepToken == null && isKeyguardShowing(DEFAULT_DISPLAY)) {
             mSleepToken = mService.acquireSleepToken("Keyguard", DEFAULT_DISPLAY);
-        } else if (mSleepToken != null && !isKeyguardShowing()) {
+        } else if (mSleepToken != null && !isKeyguardShowing(DEFAULT_DISPLAY)) {
             mSleepToken.release();
             mSleepToken = null;
         }
@@ -354,4 +369,11 @@
         pw.println(prefix + "  mDismissalRequested=" + mDismissalRequested);
         pw.println(prefix + "  mVisibilityTransactionDepth=" + mVisibilityTransactionDepth);
     }
+
+    void writeToProto(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+        proto.write(KEYGUARD_SHOWING, mKeyguardShowing);
+        proto.write(KEYGUARD_OCCLUDED, mOccluded);
+        proto.end(token);
+    }
 }
diff --git a/com/android/server/am/LockTaskController.java b/com/android/server/am/LockTaskController.java
index d8706bc..72b5de8 100644
--- a/com/android/server/am/LockTaskController.java
+++ b/com/android/server/am/LockTaskController.java
@@ -25,12 +25,14 @@
 import static android.app.StatusBarManager.DISABLE_MASK;
 import static android.app.StatusBarManager.DISABLE_NONE;
 import static android.app.StatusBarManager.DISABLE_RECENT;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.content.Context.DEVICE_POLICY_SERVICE;
 import static android.content.Context.STATUS_BAR_SERVICE;
 import static android.os.UserHandle.USER_ALL;
 import static android.os.UserHandle.USER_CURRENT;
 import static android.provider.Settings.Secure.LOCK_TO_APP_EXIT_LOCKED;
 import static android.view.Display.DEFAULT_DISPLAY;
+
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_LOCKTASK;
 import static com.android.server.am.ActivityManagerDebugConfig.POSTFIX_LOCKTASK;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
@@ -55,7 +57,9 @@
 import android.os.ServiceManager;
 import android.provider.Settings;
 import android.util.Slog;
+import android.util.SparseArray;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.widget.LockPatternUtils;
@@ -65,6 +69,7 @@
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
 
 /**
  * Helper class that deals with all things related to task locking. This includes the screen pinning
@@ -123,6 +128,11 @@
     private final ArrayList<TaskRecord> mLockTaskModeTasks = new ArrayList<>();
 
     /**
+     * Packages that are allowed to be launched into the lock task mode for each user.
+     */
+    private final SparseArray<String[]> mLockTaskPackages = new SparseArray<>();
+
+    /**
      * Store the current lock task mode. Possible values:
      * {@link ActivityManager#LOCK_TASK_MODE_NONE}, {@link ActivityManager#LOCK_TASK_MODE_LOCKED},
      * {@link ActivityManager#LOCK_TASK_MODE_PINNED}
@@ -159,8 +169,8 @@
     }
 
     /**
-     * @return whether the given task can be moved to the back of the stack. Locked tasks cannot be
-     * moved to the back of the stack.
+     * @return whether the given task is locked at the moment. Locked tasks cannot be moved to the
+     * back of the stack.
      */
     boolean checkLockedTask(TaskRecord task) {
         if (mLockTaskModeTasks.contains(task)) {
@@ -422,8 +432,8 @@
             mSupervisor.resumeFocusedStackTopActivityLocked();
             mWindowManager.executeAppTransition();
         } else if (lockTaskModeState != LOCK_TASK_MODE_NONE) {
-            mSupervisor.handleNonResizableTaskIfNeeded(task, INVALID_STACK_ID, DEFAULT_DISPLAY,
-                    task.getStackId(), true /* forceNonResizable */);
+            mSupervisor.handleNonResizableTaskIfNeeded(task, WINDOWING_MODE_UNDEFINED,
+                    DEFAULT_DISPLAY, task.getStackId(), true /* forceNonResizable */);
         }
     }
 
@@ -452,29 +462,35 @@
     }
 
     /**
-     * Called when the list of packages whitelisted for lock task mode is changed. Any currently
-     * locked tasks that got removed from the whitelist will be finished.
+     * Update packages that are allowed to be launched in lock task mode.
+     * @param userId Which user this whitelist is associated with
+     * @param packages The whitelist of packages allowed in lock task mode
+     * @see #mLockTaskPackages
      */
-    // TODO: Missing unit tests
-    void onLockTaskPackagesUpdated() {
-        boolean didSomething = false;
+    void updateLockTaskPackages(int userId, String[] packages) {
+        mLockTaskPackages.put(userId, packages);
+
+        boolean taskChanged = false;
         for (int taskNdx = mLockTaskModeTasks.size() - 1; taskNdx >= 0; --taskNdx) {
             final TaskRecord lockedTask = mLockTaskModeTasks.get(taskNdx);
-            final boolean wasWhitelisted =
-                    (lockedTask.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE) ||
-                            (lockedTask.mLockTaskAuth == LOCK_TASK_AUTH_WHITELISTED);
+            final boolean wasWhitelisted = lockedTask.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE
+                    || lockedTask.mLockTaskAuth == LOCK_TASK_AUTH_WHITELISTED;
             lockedTask.setLockTaskAuth();
-            final boolean isWhitelisted =
-                    (lockedTask.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE) ||
-                            (lockedTask.mLockTaskAuth == LOCK_TASK_AUTH_WHITELISTED);
-            if (wasWhitelisted && !isWhitelisted) {
-                // Lost whitelisting authorization. End it now.
-                if (DEBUG_LOCKTASK) Slog.d(TAG_LOCKTASK, "onLockTaskPackagesUpdated: removing " +
-                        lockedTask + " mLockTaskAuth()=" + lockedTask.lockTaskAuthToString());
-                removeLockedTask(lockedTask);
-                lockedTask.performClearTaskLocked();
-                didSomething = true;
+            final boolean isWhitelisted = lockedTask.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE
+                    || lockedTask.mLockTaskAuth == LOCK_TASK_AUTH_WHITELISTED;
+
+            if (mLockTaskModeState != LOCK_TASK_MODE_LOCKED
+                    || lockedTask.userId != userId
+                    || !wasWhitelisted || isWhitelisted) {
+                continue;
             }
+
+            // Terminate locked tasks that have recently lost whitelist authorization.
+            if (DEBUG_LOCKTASK) Slog.d(TAG_LOCKTASK, "onLockTaskPackagesUpdated: removing " +
+                    lockedTask + " mLockTaskAuth()=" + lockedTask.lockTaskAuthToString());
+            removeLockedTask(lockedTask);
+            lockedTask.performClearTaskLocked();
+            taskChanged = true;
         }
 
         for (int displayNdx = mSupervisor.getChildCount() - 1; displayNdx >= 0; --displayNdx) {
@@ -484,22 +500,40 @@
                 stack.onLockTaskPackagesUpdatedLocked();
             }
         }
+
         final ActivityRecord r = mSupervisor.topRunningActivityLocked();
-        final TaskRecord task = r != null ? r.getTask() : null;
-        if (mLockTaskModeTasks.isEmpty() && task != null
+        final TaskRecord task = (r != null) ? r.getTask() : null;
+        if (mLockTaskModeTasks.isEmpty() && task!= null
                 && task.mLockTaskAuth == LOCK_TASK_AUTH_LAUNCHABLE) {
             // This task must have just been authorized.
             if (DEBUG_LOCKTASK) Slog.d(TAG_LOCKTASK,
                     "onLockTaskPackagesUpdated: starting new locktask task=" + task);
-            setLockTaskMode(task, LOCK_TASK_MODE_LOCKED, "package updated",
-                    false);
-            didSomething = true;
+            setLockTaskMode(task, LOCK_TASK_MODE_LOCKED, "package updated", false);
+            taskChanged = true;
         }
-        if (didSomething) {
+
+        if (taskChanged) {
             mSupervisor.resumeFocusedStackTopActivityLocked();
         }
     }
 
+    boolean isPackageWhitelisted(int userId, String pkg) {
+        if (pkg == null) {
+            return false;
+        }
+        String[] whitelist;
+        whitelist = mLockTaskPackages.get(userId);
+        if (whitelist == null) {
+            return false;
+        }
+        for (String whitelistedPkg : whitelist) {
+            if (pkg.equals(whitelistedPkg)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     /**
      * @return the topmost locked task
      */
@@ -556,8 +590,18 @@
     }
 
     public void dump(PrintWriter pw, String prefix) {
-        pw.print(prefix); pw.print("mLockTaskModeState=" + lockTaskModeToString());
-        pw.println(" mLockTaskModeTasks" + mLockTaskModeTasks);
+        pw.println(prefix + "LockTaskController");
+        prefix = prefix + "  ";
+        pw.println(prefix + "mLockTaskModeState=" + lockTaskModeToString());
+        pw.println(prefix + "mLockTaskModeTasks=");
+        for (int i = 0; i < mLockTaskModeTasks.size(); ++i) {
+            pw.println(prefix + "  #" + i + " " + mLockTaskModeTasks.get(i));
+        }
+        pw.println(prefix + "mLockTaskPackages (userId:packages)=");
+        for (int i = 0; i < mLockTaskPackages.size(); ++i) {
+            pw.println(prefix + "  u" + mLockTaskPackages.keyAt(i)
+                    + ":" + Arrays.toString(mLockTaskPackages.valueAt(i)));
+        }
     }
 
     private String lockTaskModeToString() {
diff --git a/com/android/server/am/PendingIntentRecord.java b/com/android/server/am/PendingIntentRecord.java
index ee59386..7930f53 100644
--- a/com/android/server/am/PendingIntentRecord.java
+++ b/com/android/server/am/PendingIntentRecord.java
@@ -308,7 +308,7 @@
                 boolean sendFinish = finishedReceiver != null;
                 int userId = key.userId;
                 if (userId == UserHandle.USER_CURRENT) {
-                    userId = owner.mUserController.getCurrentOrTargetUserIdLocked();
+                    userId = owner.mUserController.getCurrentOrTargetUserId();
                 }
                 int res = 0;
                 switch (key.type) {
diff --git a/com/android/server/am/PinnedActivityStack.java b/com/android/server/am/PinnedActivityStack.java
index a601ee1..2726979 100644
--- a/com/android/server/am/PinnedActivityStack.java
+++ b/com/android/server/am/PinnedActivityStack.java
@@ -16,6 +16,9 @@
 
 package com.android.server.am;
 
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+
 import android.app.RemoteAction;
 import android.content.res.Configuration;
 import android.graphics.Rect;
@@ -32,15 +35,16 @@
 class PinnedActivityStack extends ActivityStack<PinnedStackWindowController>
         implements PinnedStackWindowListener {
 
-    PinnedActivityStack(ActivityStackSupervisor.ActivityDisplay display, int stackId,
-            ActivityStackSupervisor supervisor, RecentTasks recentTasks, boolean onTop) {
-        super(display, stackId, supervisor, recentTasks, onTop);
+    PinnedActivityStack(ActivityDisplay display, int stackId, ActivityStackSupervisor supervisor,
+            boolean onTop) {
+        super(display, stackId, supervisor, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, onTop);
     }
 
     @Override
     PinnedStackWindowController createStackWindowController(int displayId, boolean onTop,
             Rect outBounds) {
-        return new PinnedStackWindowController(mStackId, this, displayId, onTop, outBounds);
+        return new PinnedStackWindowController(mStackId, this, displayId, onTop, outBounds,
+                mStackSupervisor.mWindowManager);
     }
 
     Rect getDefaultPictureInPictureBounds(float aspectRatio) {
diff --git a/com/android/server/am/ProcessStatsService.java b/com/android/server/am/ProcessStatsService.java
index 39aed7c..effb86c 100644
--- a/com/android/server/am/ProcessStatsService.java
+++ b/com/android/server/am/ProcessStatsService.java
@@ -23,11 +23,14 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.SystemProperties;
+import android.service.procstats.ProcessStatsProto;
+import android.service.procstats.ProcessStatsServiceDumpProto;
 import android.util.ArrayMap;
 import android.util.AtomicFile;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.app.procstats.DumpUtils;
@@ -622,13 +625,17 @@
 
         long ident = Binder.clearCallingIdentity();
         try {
-            dumpInner(fd, pw, args);
+            if (args.length > 0 && "--proto".equals(args[0])) {
+                dumpProto(fd);
+            } else {
+                dumpInner(pw, args);
+            }
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
     }
 
-    private void dumpInner(FileDescriptor fd, PrintWriter pw, String[] args) {
+    private void dumpInner(PrintWriter pw, String[] args) {
         final long now = SystemClock.uptimeMillis();
 
         boolean isCheckin = false;
@@ -1038,4 +1045,44 @@
             }
         }
     }
+
+    private void dumpAggregatedStats(ProtoOutputStream proto, int aggregateHours, long now) {
+        ParcelFileDescriptor pfd = getStatsOverTime(aggregateHours*60*60*1000
+                - (ProcessStats.COMMIT_PERIOD/2));
+        if (pfd == null) {
+            return;
+        }
+        ProcessStats stats = new ProcessStats(false);
+        InputStream stream = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
+        stats.read(stream);
+        if (stats.mReadError != null) {
+            return;
+        }
+        stats.toProto(proto, now);
+    }
+
+    private void dumpProto(FileDescriptor fd) {
+        final ProtoOutputStream proto = new ProtoOutputStream(fd);
+
+        // dump current procstats
+        long nowToken = proto.start(ProcessStatsServiceDumpProto.PROCSTATS_NOW);
+        long now;
+        synchronized (mAm) {
+            now = SystemClock.uptimeMillis();
+            mProcessStats.toProto(proto, now);
+        }
+        proto.end(nowToken);
+
+        // aggregated over last 3 hours procstats
+        long tokenOf3Hrs = proto.start(ProcessStatsServiceDumpProto.PROCSTATS_OVER_3HRS);
+        dumpAggregatedStats(proto, 3, now);
+        proto.end(tokenOf3Hrs);
+
+        // aggregated over last 24 hours procstats
+        long tokenOf24Hrs = proto.start(ProcessStatsServiceDumpProto.PROCSTATS_OVER_24HRS);
+        dumpAggregatedStats(proto, 24, now);
+        proto.end(tokenOf24Hrs);
+
+        proto.flush();
+    }
 }
diff --git a/com/android/server/am/ServiceRecord.java b/com/android/server/am/ServiceRecord.java
index 027dc08..ac85e6b 100644
--- a/com/android/server/am/ServiceRecord.java
+++ b/com/android/server/am/ServiceRecord.java
@@ -517,11 +517,14 @@
                             } catch (PackageManager.NameNotFoundException e) {
                             }
                         }
-                        if (localForegroundNoti.getSmallIcon() == null) {
+                        if (localForegroundNoti.getSmallIcon() == null
+                                || nm.getNotificationChannel(localPackageName, appUid,
+                                localForegroundNoti.getChannelId()) == null) {
                             // Notifications whose icon is 0 are defined to not show
                             // a notification, silently ignoring it.  We don't want to
                             // just ignore it, we want to prevent the service from
                             // being foreground.
+                            // Also every notification needs a channel.
                             throw new RuntimeException("invalid service notification: "
                                     + foregroundNoti);
                         }
diff --git a/com/android/server/am/TaskChangeNotificationController.java b/com/android/server/am/TaskChangeNotificationController.java
index 8297169..5a7e7ce 100644
--- a/com/android/server/am/TaskChangeNotificationController.java
+++ b/com/android/server/am/TaskChangeNotificationController.java
@@ -95,7 +95,8 @@
     };
 
     private final TaskStackConsumer mNotifyActivityPinned = (l, m) -> {
-        l.onActivityPinned((String) m.obj, m.arg1);
+        final ActivityRecord r = (ActivityRecord) m.obj;
+        l.onActivityPinned(r.packageName, r.userId, r.getTask().taskId, r.getStackId());
     };
 
     private final TaskStackConsumer mNotifyActivityUnpinned = (l, m) -> {
@@ -278,10 +279,9 @@
     }
 
     /** Notifies all listeners when an Activity is pinned. */
-    void notifyActivityPinned(String packageName, int taskId) {
+    void notifyActivityPinned(ActivityRecord r) {
         mHandler.removeMessages(NOTIFY_ACTIVITY_PINNED_LISTENERS_MSG);
-        final Message msg = mHandler.obtainMessage(NOTIFY_ACTIVITY_PINNED_LISTENERS_MSG,
-                taskId, 0, packageName);
+        final Message msg = mHandler.obtainMessage(NOTIFY_ACTIVITY_PINNED_LISTENERS_MSG, r);
         forAllLocalListeners(mNotifyActivityPinned, msg);
         msg.sendToTarget();
     }
diff --git a/com/android/server/am/TaskPersister.java b/com/android/server/am/TaskPersister.java
index 74c4826..61994b5 100644
--- a/com/android/server/am/TaskPersister.java
+++ b/com/android/server/am/TaskPersister.java
@@ -54,9 +54,6 @@
 import java.util.Comparator;
 import java.util.List;
 
-import static android.app.ActivityManager.StackId.HOME_STACK_ID;
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
-
 import static com.android.server.am.ActivityStackSupervisor.MATCH_TASK_IN_STACKS_OR_RECENT_TASKS;
 
 public class TaskPersister {
@@ -472,8 +469,7 @@
 
                                 final int taskId = task.taskId;
                                 if (mStackSupervisor.anyTaskForIdLocked(taskId,
-                                        MATCH_TASK_IN_STACKS_OR_RECENT_TASKS,
-                                        INVALID_STACK_ID) != null) {
+                                        MATCH_TASK_IN_STACKS_OR_RECENT_TASKS) != null) {
                                     // Should not happen.
                                     Slog.wtf(TAG, "Existing task with taskId " + taskId + "found");
                                 } else if (userId != task.userId) {
diff --git a/com/android/server/am/TaskRecord.java b/com/android/server/am/TaskRecord.java
index 0e65184..0d8df79 100644
--- a/com/android/server/am/TaskRecord.java
+++ b/com/android/server/am/TaskRecord.java
@@ -18,19 +18,19 @@
 
 import static android.app.ActivityManager.RESIZE_MODE_FORCED;
 import static android.app.ActivityManager.RESIZE_MODE_SYSTEM;
-import static android.app.ActivityManager.StackId.ASSISTANT_STACK_ID;
-import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
 import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
 import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.HOME_STACK_ID;
 import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
 import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
-import static android.app.ActivityManager.StackId.RECENTS_STACK_ID;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
 import static android.content.Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS;
 import static android.content.pm.ActivityInfo.FLAG_RELINQUISH_TASK_IDENTITY;
@@ -65,6 +65,20 @@
 import static com.android.server.am.ActivityStack.REMOVE_TASK_MODE_MOVING_TO_TOP;
 import static com.android.server.am.ActivityStackSupervisor.PAUSE_IMMEDIATELY;
 import static com.android.server.am.ActivityStackSupervisor.PRESERVE_WINDOWS;
+import static com.android.server.am.proto.TaskRecordProto.ACTIVITIES;
+import static com.android.server.am.proto.TaskRecordProto.BOUNDS;
+import static com.android.server.am.proto.TaskRecordProto.CONFIGURATION_CONTAINER;
+import static com.android.server.am.proto.TaskRecordProto.FULLSCREEN;
+import static com.android.server.am.proto.TaskRecordProto.ID;
+import static com.android.server.am.proto.TaskRecordProto.LAST_NON_FULLSCREEN_BOUNDS;
+import static com.android.server.am.proto.TaskRecordProto.MIN_HEIGHT;
+import static com.android.server.am.proto.TaskRecordProto.MIN_WIDTH;
+import static com.android.server.am.proto.TaskRecordProto.ORIG_ACTIVITY;
+import static com.android.server.am.proto.TaskRecordProto.REAL_ACTIVITY;
+import static com.android.server.am.proto.TaskRecordProto.RESIZE_MODE;
+import static com.android.server.am.proto.TaskRecordProto.RETURN_TO_TYPE;
+import static com.android.server.am.proto.TaskRecordProto.STACK_ID;
+import static com.android.server.am.proto.TaskRecordProto.ACTIVITY_TYPE;
 
 import static java.lang.Integer.MAX_VALUE;
 
@@ -78,7 +92,6 @@
 import android.app.ActivityOptions;
 import android.app.AppGlobals;
 import android.app.IActivityManager;
-import android.app.WindowConfiguration;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
@@ -95,6 +108,7 @@
 import android.service.voice.IVoiceInteractionSession;
 import android.util.DisplayMetrics;
 import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IVoiceInteractor;
@@ -508,8 +522,7 @@
             updateOverrideConfiguration(bounds);
             if (getStackId() != FREEFORM_WORKSPACE_STACK_ID) {
                 // re-restore the task so it can have the proper stack association.
-                mService.mStackSupervisor.restoreRecentTaskLocked(this,
-                        FREEFORM_WORKSPACE_STACK_ID);
+                mService.mStackSupervisor.restoreRecentTaskLocked(this, null);
             }
             return true;
         }
@@ -559,36 +572,36 @@
     /**
      * Convenience method to reparent a task to the top or bottom position of the stack.
      */
-    boolean reparent(int preferredStackId, boolean toTop, @ReparentMoveStackMode int moveStackMode,
-            boolean animate, boolean deferResume, String reason) {
-        return reparent(preferredStackId, toTop ? MAX_VALUE : 0, moveStackMode, animate,
-                deferResume, true /* schedulePictureInPictureModeChange */, reason);
+    boolean reparent(ActivityStack preferredStack, boolean toTop,
+            @ReparentMoveStackMode int moveStackMode, boolean animate, boolean deferResume,
+            String reason) {
+        return reparent(preferredStack, toTop ? MAX_VALUE : 0, moveStackMode, animate, deferResume,
+                true /* schedulePictureInPictureModeChange */, reason);
     }
 
     /**
      * Convenience method to reparent a task to the top or bottom position of the stack, with
      * an option to skip scheduling the picture-in-picture mode change.
      */
-    boolean reparent(int preferredStackId, boolean toTop, @ReparentMoveStackMode int moveStackMode,
-            boolean animate, boolean deferResume, boolean schedulePictureInPictureModeChange,
-            String reason) {
-        return reparent(preferredStackId, toTop ? MAX_VALUE : 0, moveStackMode, animate,
+    boolean reparent(ActivityStack preferredStack, boolean toTop,
+            @ReparentMoveStackMode int moveStackMode, boolean animate, boolean deferResume,
+            boolean schedulePictureInPictureModeChange, String reason) {
+        return reparent(preferredStack, toTop ? MAX_VALUE : 0, moveStackMode, animate,
                 deferResume, schedulePictureInPictureModeChange, reason);
     }
 
-    /**
-     * Convenience method to reparent a task to a specific position of the stack.
-     */
-    boolean reparent(int preferredStackId, int position, @ReparentMoveStackMode int moveStackMode,
-            boolean animate, boolean deferResume, String reason) {
-        return reparent(preferredStackId, position, moveStackMode, animate, deferResume,
+    /** Convenience method to reparent a task to a specific position of the stack. */
+    boolean reparent(ActivityStack preferredStack, int position,
+            @ReparentMoveStackMode int moveStackMode, boolean animate, boolean deferResume,
+            String reason) {
+        return reparent(preferredStack, position, moveStackMode, animate, deferResume,
                 true /* schedulePictureInPictureModeChange */, reason);
     }
 
     /**
      * Reparents the task into a preferred stack, creating it if necessary.
      *
-     * @param preferredStackId the stack id of the target stack to move this task
+     * @param preferredStack the target stack to move this task
      * @param position the position to place this task in the new stack
      * @param animate whether or not we should wait for the new window created as a part of the
      *            reparenting to be drawn and animated in
@@ -602,13 +615,16 @@
      * @param reason the caller of this reparenting
      * @return whether the task was reparented
      */
-    boolean reparent(int preferredStackId, int position, @ReparentMoveStackMode int moveStackMode,
-            boolean animate, boolean deferResume, boolean schedulePictureInPictureModeChange,
-            String reason) {
+    // TODO: Inspect all call sites and change to just changing windowing mode of the stack vs.
+    // re-parenting the task. Can only be done when we are no longer using static stack Ids like
+    /** {@link ActivityManager.StackId#FULLSCREEN_WORKSPACE_STACK_ID} */
+    boolean reparent(ActivityStack preferredStack, int position,
+            @ReparentMoveStackMode int moveStackMode, boolean animate, boolean deferResume,
+            boolean schedulePictureInPictureModeChange, String reason) {
         final ActivityStackSupervisor supervisor = mService.mStackSupervisor;
         final WindowManagerService windowManager = mService.mWindowManager;
         final ActivityStack sourceStack = getStack();
-        final ActivityStack toStack = supervisor.getReparentTargetStack(this, preferredStackId,
+        final ActivityStack toStack = supervisor.getReparentTargetStack(this, preferredStack,
                 position == MAX_VALUE);
         if (toStack == sourceStack) {
             return false;
@@ -691,19 +707,22 @@
             toStack.prepareFreezingTaskBounds();
 
             // Make sure the task has the appropriate bounds/size for the stack it is in.
+            final int toStackWindowingMode = toStack.getWindowingMode();
+            final boolean toStackSplitScreenPrimary =
+                    toStackWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
             if (stackId == FULLSCREEN_WORKSPACE_STACK_ID
                     && !Objects.equals(mBounds, toStack.mBounds)) {
                 kept = resize(toStack.mBounds, RESIZE_MODE_SYSTEM, !mightReplaceWindow,
                         deferResume);
-            } else if (stackId == FREEFORM_WORKSPACE_STACK_ID) {
+            } else if (toStackWindowingMode == WINDOWING_MODE_FREEFORM) {
                 Rect bounds = getLaunchBounds();
                 if (bounds == null) {
                     toStack.layoutTaskInStack(this, null);
                     bounds = mBounds;
                 }
                 kept = resize(bounds, RESIZE_MODE_FORCED, !mightReplaceWindow, deferResume);
-            } else if (stackId == DOCKED_STACK_ID || stackId == PINNED_STACK_ID) {
-                if (stackId == DOCKED_STACK_ID && moveStackMode == REPARENT_KEEP_STACK_AT_FRONT) {
+            } else if (toStackSplitScreenPrimary || toStackWindowingMode == WINDOWING_MODE_PINNED) {
+                if (toStackSplitScreenPrimary && moveStackMode == REPARENT_KEEP_STACK_AT_FRONT) {
                     // Move recents to front so it is not behind home stack when going into docked
                     // mode
                     mService.mStackSupervisor.moveRecentsStackToFront(reason);
@@ -730,10 +749,12 @@
         }
 
         // TODO: Handle incorrect request to move before the actual move, not after.
-        supervisor.handleNonResizableTaskIfNeeded(this, preferredStackId, DEFAULT_DISPLAY, stackId);
+        final boolean inSplitScreenMode = supervisor.getDefaultDisplay().hasSplitScreenStack();
+        supervisor.handleNonResizableTaskIfNeeded(this, preferredStack.getWindowingMode(),
+                DEFAULT_DISPLAY, stackId);
 
-        boolean successful = (preferredStackId == stackId);
-        if (successful && stackId == DOCKED_STACK_ID) {
+        boolean successful = (preferredStack == toStack);
+        if (successful && toStack.getWindowingMode() == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
             // If task moved to docked stack - show recents if needed.
             mService.mWindowManager.showRecentApps(false /* fromHome */);
         }
@@ -857,8 +878,13 @@
         }
         mResizeMode = info.resizeMode;
         mSupportsPictureInPicture = info.supportsPictureInPicture();
-        mLockTaskMode = info.lockTaskLaunchMode;
         mPrivileged = (info.applicationInfo.privateFlags & PRIVATE_FLAG_PRIVILEGED) != 0;
+        mLockTaskMode = info.lockTaskLaunchMode;
+        if (!mPrivileged && (mLockTaskMode == LOCK_TASK_LAUNCH_MODE_ALWAYS
+                || mLockTaskMode == LOCK_TASK_LAUNCH_MODE_NEVER)) {
+            // Non-priv apps are not allowed to use always or never, fall back to default
+            mLockTaskMode = LOCK_TASK_LAUNCH_MODE_DEFAULT;
+        }
         setLockTaskAuth();
     }
 
@@ -921,8 +947,8 @@
         mNextAffiliateTaskId = nextAffiliate == null ? INVALID_TASK_ID : nextAffiliate.taskId;
     }
 
-    ActivityStack getStack() {
-        return mStack;
+    <T extends ActivityStack> T getStack() {
+        return (T) mStack;
     }
 
     /**
@@ -1396,16 +1422,11 @@
     }
 
     void setLockTaskAuth() {
-        if (!mPrivileged &&
-                (mLockTaskMode == LOCK_TASK_LAUNCH_MODE_ALWAYS ||
-                        mLockTaskMode == LOCK_TASK_LAUNCH_MODE_NEVER)) {
-            // Non-priv apps are not allowed to use always or never, fall back to default
-            mLockTaskMode = LOCK_TASK_LAUNCH_MODE_DEFAULT;
-        }
+        final String pkg = (realActivity != null) ? realActivity.getPackageName() : null;
         switch (mLockTaskMode) {
             case LOCK_TASK_LAUNCH_MODE_DEFAULT:
-                mLockTaskAuth = isLockTaskWhitelistedLocked() ?
-                    LOCK_TASK_AUTH_WHITELISTED : LOCK_TASK_AUTH_PINNABLE;
+                mLockTaskAuth = mService.mLockTaskController.isPackageWhitelisted(userId, pkg)
+                        ? LOCK_TASK_AUTH_WHITELISTED : LOCK_TASK_AUTH_PINNABLE;
                 break;
 
             case LOCK_TASK_LAUNCH_MODE_NEVER:
@@ -1417,31 +1438,14 @@
                 break;
 
             case LOCK_TASK_LAUNCH_MODE_IF_WHITELISTED:
-                mLockTaskAuth = isLockTaskWhitelistedLocked() ?
-                        LOCK_TASK_AUTH_LAUNCHABLE : LOCK_TASK_AUTH_PINNABLE;
+                mLockTaskAuth = mService.mLockTaskController.isPackageWhitelisted(userId, pkg)
+                        ? LOCK_TASK_AUTH_LAUNCHABLE : LOCK_TASK_AUTH_PINNABLE;
                 break;
         }
         if (DEBUG_LOCKTASK) Slog.d(TAG_LOCKTASK, "setLockTaskAuth: task=" + this +
                 " mLockTaskAuth=" + lockTaskAuthToString());
     }
 
-    private boolean isLockTaskWhitelistedLocked() {
-        String pkg = (realActivity != null) ? realActivity.getPackageName() : null;
-        if (pkg == null) {
-            return false;
-        }
-        String[] packages = mService.mLockTaskPackages.get(userId);
-        if (packages == null) {
-            return false;
-        }
-        for (int i = packages.length - 1; i >= 0; --i) {
-            if (pkg.equals(packages[i])) {
-                return true;
-            }
-        }
-        return false;
-    }
-
     boolean isOverHomeStack() {
         return mTaskToReturnTo == ACTIVITY_TYPE_HOME;
     }
@@ -1459,10 +1463,12 @@
         return isResizeable(true /* checkSupportsPip */);
     }
 
-    boolean supportsSplitScreen() {
+    @Override
+    public boolean supportsSplitScreenWindowingMode() {
         // A task can not be docked even if it is considered resizeable because it only supports
         // picture-in-picture mode but has a non-resizeable resizeMode
-        return mService.mSupportsSplitScreenMultiWindow
+        return super.supportsSplitScreenWindowingMode()
+                && mService.mSupportsSplitScreenMultiWindow
                 && (mService.mForceResizableActivities
                         || (isResizeable(false /* checkSupportsPip */)
                                 && !ActivityInfo.isPreserveOrientationMode(mResizeMode)));
@@ -2097,39 +2103,16 @@
         }
     }
 
-    /**
-     * Returns the correct stack to use based on task type and currently set bounds,
-     * regardless of the focused stack and current stack association of the task.
-     * The task will be moved (and stack focus changed) later if necessary.
-     */
-    int getLaunchStackId() {
-        if (isActivityTypeRecents()) {
-            return RECENTS_STACK_ID;
-        }
-        if (isActivityTypeHome()) {
-            return HOME_STACK_ID;
-        }
-        if (isActivityTypeAssistant()) {
-            return ASSISTANT_STACK_ID;
-        }
-        if (mBounds != null) {
-            return FREEFORM_WORKSPACE_STACK_ID;
-        }
-        return FULLSCREEN_WORKSPACE_STACK_ID;
-    }
-
     /** Returns the bounds that should be used to launch this task. */
     private Rect getLaunchBounds() {
         if (mStack == null) {
             return null;
         }
 
-        final int stackId = mStack.mStackId;
-        if (stackId == HOME_STACK_ID
-                || stackId == RECENTS_STACK_ID
-                || stackId == ASSISTANT_STACK_ID
-                || stackId == FULLSCREEN_WORKSPACE_STACK_ID
-                || (stackId == DOCKED_STACK_ID && !isResizeable())) {
+        final int windowingMode = getWindowingMode();
+        if (!isActivityTypeStandardOrUndefined()
+                || windowingMode == WINDOWING_MODE_FULLSCREEN
+                || (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY && !isResizeable())) {
             return isResizeable() ? mStack.mBounds : null;
         } else if (!getWindowConfiguration().persistTaskBounds()) {
             return mStack.mBounds;
@@ -2275,4 +2258,34 @@
         stringName = sb.toString();
         return toString();
     }
+
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+        super.writeToProto(proto, CONFIGURATION_CONTAINER);
+        proto.write(ID, taskId);
+        for (int i = mActivities.size() - 1; i >= 0; i--) {
+            ActivityRecord activity = mActivities.get(i);
+            activity.writeToProto(proto, ACTIVITIES);
+        }
+        proto.write(STACK_ID, mStack.mStackId);
+        if (mLastNonFullscreenBounds != null) {
+            mLastNonFullscreenBounds.writeToProto(proto, LAST_NON_FULLSCREEN_BOUNDS);
+        }
+        if (realActivity != null) {
+            proto.write(REAL_ACTIVITY, realActivity.flattenToShortString());
+        }
+        if (origActivity != null) {
+            proto.write(ORIG_ACTIVITY, origActivity.flattenToShortString());
+        }
+        proto.write(ACTIVITY_TYPE, getActivityType());
+        proto.write(RETURN_TO_TYPE, mTaskToReturnTo);
+        proto.write(RESIZE_MODE, mResizeMode);
+        proto.write(FULLSCREEN, mFullscreen);
+        if (mBounds != null) {
+            mBounds.writeToProto(proto, BOUNDS);
+        }
+        proto.write(MIN_WIDTH, mMinWidth);
+        proto.write(MIN_HEIGHT, mMinHeight);
+        proto.end(token);
+    }
 }
diff --git a/com/android/server/am/UserController.java b/com/android/server/am/UserController.java
index db6bb7d..5a29594 100644
--- a/com/android/server/am/UserController.java
+++ b/com/android/server/am/UserController.java
@@ -22,6 +22,7 @@
 import static android.app.ActivityManager.USER_OP_ERROR_RELATED_USERS_CANNOT_STOP;
 import static android.app.ActivityManager.USER_OP_IS_CURRENT;
 import static android.app.ActivityManager.USER_OP_SUCCESS;
+import static android.os.Process.SHELL_UID;
 import static android.os.Process.SYSTEM_UID;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_MU;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
@@ -30,14 +31,6 @@
 import static com.android.server.am.ActivityManagerService.ALLOW_NON_FULL;
 import static com.android.server.am.ActivityManagerService.ALLOW_NON_FULL_IN_PROFILE;
 import static com.android.server.am.ActivityManagerService.MY_PID;
-import static com.android.server.am.ActivityManagerService.REPORT_LOCKED_BOOT_COMPLETE_MSG;
-import static com.android.server.am.ActivityManagerService.REPORT_USER_SWITCH_COMPLETE_MSG;
-import static com.android.server.am.ActivityManagerService.REPORT_USER_SWITCH_MSG;
-import static com.android.server.am.ActivityManagerService.SYSTEM_USER_CURRENT_MSG;
-import static com.android.server.am.ActivityManagerService.SYSTEM_USER_START_MSG;
-import static com.android.server.am.ActivityManagerService.SYSTEM_USER_UNLOCK_MSG;
-import static com.android.server.am.ActivityManagerService.USER_SWITCH_CALLBACKS_TIMEOUT_MSG;
-import static com.android.server.am.ActivityManagerService.USER_SWITCH_TIMEOUT_MSG;
 import static com.android.server.am.UserState.STATE_BOOTING;
 import static com.android.server.am.UserState.STATE_RUNNING_LOCKED;
 import static com.android.server.am.UserState.STATE_RUNNING_UNLOCKED;
@@ -68,6 +61,7 @@
 import android.os.IProgressListener;
 import android.os.IRemoteCallback;
 import android.os.IUserManager;
+import android.os.Message;
 import android.os.Process;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
@@ -78,6 +72,7 @@
 import android.os.UserManagerInternal;
 import android.os.storage.IStorageManager;
 import android.os.storage.StorageManager;
+import android.text.format.DateUtils;
 import android.util.ArraySet;
 import android.util.IntArray;
 import android.util.Pair;
@@ -93,6 +88,7 @@
 import com.android.internal.util.Preconditions;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.server.LocalServices;
+import com.android.server.SystemServiceManager;
 import com.android.server.pm.UserManagerService;
 import com.android.server.wm.WindowManagerService;
 
@@ -107,25 +103,64 @@
 
 /**
  * Helper class for {@link ActivityManagerService} responsible for multi-user functionality.
+ *
+ * <p>This class use {@link #mLock} to synchronize access to internal state. Methods that require
+ * {@link #mLock} to be held should have "LU" suffix in the name.
+ *
+ * <p><strong>Important:</strong> Synchronized code, i.e. one executed inside a synchronized(mLock)
+ * block or inside LU method, should only access internal state of this class or make calls to
+ * other LU methods. Non-LU method calls or calls to external classes are discouraged as they
+ * may cause lock inversion.
  */
-class UserController {
+class UserController implements Handler.Callback {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "UserController" : TAG_AM;
 
-    // Maximum number of users we allow to be running at a time.
+    /**
+     * Maximum number of users we allow to be running at a time, including the system user and
+     * its profiles.
+     * Note changing this to 2 is not recommended, since that would mean, if the user uses
+     * work profile and then switch to a secondary user, then the work profile user would be killed,
+     * which should work fine, but aggressively killing the work profile user that has just been
+     * running could cause data loss.  (Even without work profile, witching from secondary user A
+     * to secondary user B would cause similar issues on user B.)
+     *
+     * TODO: Consider adding or replacing with "MAX_RUNNING_*SECONDARY*_USERS", which is the max
+     * number of running *secondary, switchable* users.
+     */
     static final int MAX_RUNNING_USERS = 3;
 
     // Amount of time we wait for observers to handle a user switch before
     // giving up on them and unfreezing the screen.
     static final int USER_SWITCH_TIMEOUT_MS = 3 * 1000;
 
+    // ActivityManager thread message constants
+    static final int REPORT_USER_SWITCH_MSG = 10;
+    static final int CONTINUE_USER_SWITCH_MSG = 20;
+    static final int USER_SWITCH_TIMEOUT_MSG = 30;
+    static final int START_PROFILES_MSG = 40;
+    static final int SYSTEM_USER_START_MSG = 50;
+    static final int SYSTEM_USER_CURRENT_MSG = 60;
+    static final int FOREGROUND_PROFILE_CHANGED_MSG = 70;
+    static final int REPORT_USER_SWITCH_COMPLETE_MSG = 80;
+    static final int USER_SWITCH_CALLBACKS_TIMEOUT_MSG = 90;
+    static final int SYSTEM_USER_UNLOCK_MSG = 100;
+    static final int REPORT_LOCKED_BOOT_COMPLETE_MSG = 110;
+    static final int START_USER_SWITCH_FG_MSG = 120;
+
+    // UI thread message constants
+    static final int START_USER_SWITCH_UI_MSG = 1000;
+
     // If a callback wasn't called within USER_SWITCH_CALLBACKS_TIMEOUT_MS after
     // USER_SWITCH_TIMEOUT_MS, an error is reported. Usually it indicates a problem in the observer
     // when it never calls back.
     private static final int USER_SWITCH_CALLBACKS_TIMEOUT_MS = 5 * 1000;
 
-    private final Object mLock;
+    // Lock for internal state.
+    private final Object mLock = new Object();
+
     private final Injector mInjector;
     private final Handler mHandler;
+    private final Handler mUiHandler;
 
     // Holds the current foreground user's id. Use mLock when updating
     @GuardedBy("mLock")
@@ -161,7 +196,8 @@
     /**
      * Mapping from each known user ID to the profile group ID it is associated with.
      */
-    private final SparseIntArray mUserProfileGroupIdsSelfLocked = new SparseIntArray();
+    @GuardedBy("mLock")
+    private final SparseIntArray mUserProfileGroupIds = new SparseIntArray();
 
     /**
      * Registered observers of the user switching mechanics.
@@ -192,26 +228,25 @@
     @VisibleForTesting
     UserController(Injector injector) {
         mInjector = injector;
-        mLock = injector.getLock();
-        mHandler = injector.getHandler();
+        mHandler = mInjector.getHandler(this);
+        mUiHandler = mInjector.getUiHandler(this);
         // User 0 is the first and only user that runs at boot.
         final UserState uss = new UserState(UserHandle.SYSTEM);
         mStartedUsers.put(UserHandle.USER_SYSTEM, uss);
         mUserLru.add(UserHandle.USER_SYSTEM);
         mLockPatternUtils = mInjector.getLockPatternUtils();
-        updateStartedUserArrayLocked();
+        updateStartedUserArrayLU();
     }
 
     void finishUserSwitch(UserState uss) {
+        finishUserBoot(uss);
+        startProfiles();
         synchronized (mLock) {
-            finishUserBoot(uss);
-
-            startProfilesLocked();
-            stopRunningUsersLocked(MAX_RUNNING_USERS);
+            stopRunningUsersLU(MAX_RUNNING_USERS);
         }
     }
 
-    void stopRunningUsersLocked(int maxRunningUsers) {
+    void stopRunningUsersLU(int maxRunningUsers) {
         int num = mUserLru.size();
         int i = 0;
         while (num > maxRunningUsers && i < mUserLru.size()) {
@@ -240,7 +275,7 @@
                 continue;
             }
             // This is a user to be stopped.
-            if (stopUsersLocked(oldUserId, false, null) != USER_OP_SUCCESS) {
+            if (stopUsersLU(oldUserId, false, null) != USER_OP_SUCCESS) {
                 num--;
             }
             num--;
@@ -258,55 +293,57 @@
         Slog.d(TAG, "Finishing user boot " + userId);
         synchronized (mLock) {
             // Bail if we ended up with a stale user
-            if (mStartedUsers.get(userId) != uss) return;
+            if (mStartedUsers.get(userId) != uss) {
+                return;
+            }
+        }
 
-            // We always walk through all the user lifecycle states to send
-            // consistent developer events. We step into RUNNING_LOCKED here,
-            // but we might immediately step into RUNNING below if the user
-            // storage is already unlocked.
-            if (uss.setState(STATE_BOOTING, STATE_RUNNING_LOCKED)) {
-                mInjector.getUserManagerInternal().setUserState(userId, uss.state);
-                // Do not report secondary users, runtime restarts or first boot/upgrade
-                if (userId == UserHandle.USER_SYSTEM
-                        && !mInjector.isRuntimeRestarted() && !mInjector.isFirstBootOrUpgrade()) {
-                    int uptimeSeconds = (int)(SystemClock.elapsedRealtime() / 1000);
-                    MetricsLogger.histogram(mInjector.getContext(),
-                            "framework_locked_boot_completed", uptimeSeconds);
-                    final int MAX_UPTIME_SECONDS = 120;
-                    if (uptimeSeconds > MAX_UPTIME_SECONDS) {
-                        Slog.wtf("SystemServerTiming",
-                                "finishUserBoot took too long. uptimeSeconds=" + uptimeSeconds);
-                    }
+        // We always walk through all the user lifecycle states to send
+        // consistent developer events. We step into RUNNING_LOCKED here,
+        // but we might immediately step into RUNNING below if the user
+        // storage is already unlocked.
+        if (uss.setState(STATE_BOOTING, STATE_RUNNING_LOCKED)) {
+            mInjector.getUserManagerInternal().setUserState(userId, uss.state);
+            // Do not report secondary users, runtime restarts or first boot/upgrade
+            if (userId == UserHandle.USER_SYSTEM
+                    && !mInjector.isRuntimeRestarted() && !mInjector.isFirstBootOrUpgrade()) {
+                int uptimeSeconds = (int)(SystemClock.elapsedRealtime() / 1000);
+                MetricsLogger.histogram(mInjector.getContext(),
+                        "framework_locked_boot_completed", uptimeSeconds);
+                final int MAX_UPTIME_SECONDS = 120;
+                if (uptimeSeconds > MAX_UPTIME_SECONDS) {
+                    Slog.wtf("SystemServerTiming",
+                            "finishUserBoot took too long. uptimeSeconds=" + uptimeSeconds);
                 }
-
-                mHandler.sendMessage(mHandler.obtainMessage(REPORT_LOCKED_BOOT_COMPLETE_MSG,
-                        userId, 0));
-                Intent intent = new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED, null);
-                intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
-                intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT
-                        | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
-                mInjector.broadcastIntentLocked(intent, null, resultTo, 0, null, null,
-                        new String[] { android.Manifest.permission.RECEIVE_BOOT_COMPLETED },
-                        AppOpsManager.OP_NONE, null, true, false, MY_PID, SYSTEM_UID, userId);
             }
 
-            // We need to delay unlocking managed profiles until the parent user
-            // is also unlocked.
-            if (mInjector.getUserManager().isManagedProfile(userId)) {
-                final UserInfo parent = mInjector.getUserManager().getProfileParent(userId);
-                if (parent != null
-                        && isUserRunningLocked(parent.id, ActivityManager.FLAG_AND_UNLOCKED)) {
-                    Slog.d(TAG, "User " + userId + " (parent " + parent.id
-                            + "): attempting unlock because parent is unlocked");
-                    maybeUnlockUser(userId);
-                } else {
-                    String parentId = (parent == null) ? "<null>" : String.valueOf(parent.id);
-                    Slog.d(TAG, "User " + userId + " (parent " + parentId
-                            + "): delaying unlock because parent is locked");
-                }
-            } else {
+            mHandler.sendMessage(mHandler.obtainMessage(REPORT_LOCKED_BOOT_COMPLETE_MSG,
+                    userId, 0));
+            Intent intent = new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED, null);
+            intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+            intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT
+                    | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+            mInjector.broadcastIntent(intent, null, resultTo, 0, null, null,
+                    new String[]{android.Manifest.permission.RECEIVE_BOOT_COMPLETED},
+                    AppOpsManager.OP_NONE, null, true, false, MY_PID, SYSTEM_UID, userId);
+        }
+
+        // We need to delay unlocking managed profiles until the parent user
+        // is also unlocked.
+        if (mInjector.getUserManager().isManagedProfile(userId)) {
+            final UserInfo parent = mInjector.getUserManager().getProfileParent(userId);
+            if (parent != null
+                    && isUserRunning(parent.id, ActivityManager.FLAG_AND_UNLOCKED)) {
+                Slog.d(TAG, "User " + userId + " (parent " + parent.id
+                        + "): attempting unlock because parent is unlocked");
                 maybeUnlockUser(userId);
+            } else {
+                String parentId = (parent == null) ? "<null>" : String.valueOf(parent.id);
+                Slog.d(TAG, "User " + userId + " (parent " + parentId
+                        + "): delaying unlock because parent is locked");
             }
+        } else {
+            maybeUnlockUser(userId);
         }
     }
 
@@ -316,34 +353,30 @@
      */
     private void finishUserUnlocking(final UserState uss) {
         final int userId = uss.mHandle.getIdentifier();
-        boolean proceedWithUnlock = false;
+        // Only keep marching forward if user is actually unlocked
+        if (!StorageManager.isUserKeyUnlocked(userId)) return;
         synchronized (mLock) {
             // Bail if we ended up with a stale user
             if (mStartedUsers.get(uss.mHandle.getIdentifier()) != uss) return;
 
-            // Only keep marching forward if user is actually unlocked
-            if (!StorageManager.isUserKeyUnlocked(userId)) return;
-
-            if (uss.setState(STATE_RUNNING_LOCKED, STATE_RUNNING_UNLOCKING)) {
-                mInjector.getUserManagerInternal().setUserState(userId, uss.state);
-                proceedWithUnlock = true;
+            // Do not proceed if unexpected state
+            if (!uss.setState(STATE_RUNNING_LOCKED, STATE_RUNNING_UNLOCKING)) {
+                return;
             }
         }
+        mInjector.getUserManagerInternal().setUserState(userId, uss.state);
+        uss.mUnlockProgress.start();
 
-        if (proceedWithUnlock) {
-            uss.mUnlockProgress.start();
+        // Prepare app storage before we go any further
+        uss.mUnlockProgress.setProgress(5,
+                    mInjector.getContext().getString(R.string.android_start_title));
+        mInjector.getUserManager().onBeforeUnlockUser(userId);
+        uss.mUnlockProgress.setProgress(20);
 
-            // Prepare app storage before we go any further
-            uss.mUnlockProgress.setProgress(5,
-                        mInjector.getContext().getString(R.string.android_start_title));
-            mInjector.getUserManager().onBeforeUnlockUser(userId);
-            uss.mUnlockProgress.setProgress(20);
-
-            // Dispatch unlocked to system services; when fully dispatched,
-            // that calls through to the next "unlocked" phase
-            mHandler.obtainMessage(SYSTEM_USER_UNLOCK_MSG, userId, 0, uss)
-                    .sendToTarget();
-        }
+        // Dispatch unlocked to system services; when fully dispatched,
+        // that calls through to the next "unlocked" phase
+        mHandler.obtainMessage(SYSTEM_USER_UNLOCK_MSG, userId, 0, uss)
+                .sendToTarget();
     }
 
     /**
@@ -352,64 +385,64 @@
      */
     void finishUserUnlocked(final UserState uss) {
         final int userId = uss.mHandle.getIdentifier();
+        // Only keep marching forward if user is actually unlocked
+        if (!StorageManager.isUserKeyUnlocked(userId)) return;
         synchronized (mLock) {
             // Bail if we ended up with a stale user
             if (mStartedUsers.get(uss.mHandle.getIdentifier()) != uss) return;
 
-            // Only keep marching forward if user is actually unlocked
-            if (!StorageManager.isUserKeyUnlocked(userId)) return;
-
-            if (uss.setState(STATE_RUNNING_UNLOCKING, STATE_RUNNING_UNLOCKED)) {
-                mInjector.getUserManagerInternal().setUserState(userId, uss.state);
-                uss.mUnlockProgress.finish();
-
-                // Dispatch unlocked to external apps
-                final Intent unlockedIntent = new Intent(Intent.ACTION_USER_UNLOCKED);
-                unlockedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
-                unlockedIntent.addFlags(
-                        Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
-                mInjector.broadcastIntentLocked(unlockedIntent, null, null, 0, null,
-                        null, null, AppOpsManager.OP_NONE, null, false, false, MY_PID, SYSTEM_UID,
-                        userId);
-
-                if (getUserInfo(userId).isManagedProfile()) {
-                    UserInfo parent = mInjector.getUserManager().getProfileParent(userId);
-                    if (parent != null) {
-                        final Intent profileUnlockedIntent = new Intent(
-                                Intent.ACTION_MANAGED_PROFILE_UNLOCKED);
-                        profileUnlockedIntent.putExtra(Intent.EXTRA_USER, UserHandle.of(userId));
-                        profileUnlockedIntent.addFlags(
-                                Intent.FLAG_RECEIVER_REGISTERED_ONLY
-                                | Intent.FLAG_RECEIVER_FOREGROUND);
-                        mInjector.broadcastIntentLocked(profileUnlockedIntent,
-                                null, null, 0, null, null, null, AppOpsManager.OP_NONE,
-                                null, false, false, MY_PID, SYSTEM_UID,
-                                parent.id);
-                    }
-                }
-
-                // Send PRE_BOOT broadcasts if user fingerprint changed; we
-                // purposefully block sending BOOT_COMPLETED until after all
-                // PRE_BOOT receivers are finished to avoid ANR'ing apps
-                final UserInfo info = getUserInfo(userId);
-                if (!Objects.equals(info.lastLoggedInFingerprint, Build.FINGERPRINT)) {
-                    // Suppress double notifications for managed profiles that
-                    // were unlocked automatically as part of their parent user
-                    // being unlocked.
-                    final boolean quiet;
-                    if (info.isManagedProfile()) {
-                        quiet = !uss.tokenProvided
-                                || !mLockPatternUtils.isSeparateProfileChallengeEnabled(userId);
-                    } else {
-                        quiet = false;
-                    }
-                    mInjector.sendPreBootBroadcast(userId, quiet,
-                            () -> finishUserUnlockedCompleted(uss));
-                } else {
-                    finishUserUnlockedCompleted(uss);
-                }
+            // Do not proceed if unexpected state
+            if (!uss.setState(STATE_RUNNING_UNLOCKING, STATE_RUNNING_UNLOCKED)) {
+                return;
             }
         }
+        mInjector.getUserManagerInternal().setUserState(userId, uss.state);
+        uss.mUnlockProgress.finish();
+        // Dispatch unlocked to external apps
+        final Intent unlockedIntent = new Intent(Intent.ACTION_USER_UNLOCKED);
+        unlockedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+        unlockedIntent.addFlags(
+                Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND);
+        mInjector.broadcastIntent(unlockedIntent, null, null, 0, null,
+                null, null, AppOpsManager.OP_NONE, null, false, false, MY_PID, SYSTEM_UID,
+                userId);
+
+        if (getUserInfo(userId).isManagedProfile()) {
+            UserInfo parent = mInjector.getUserManager().getProfileParent(userId);
+            if (parent != null) {
+                final Intent profileUnlockedIntent = new Intent(
+                        Intent.ACTION_MANAGED_PROFILE_UNLOCKED);
+                profileUnlockedIntent.putExtra(Intent.EXTRA_USER, UserHandle.of(userId));
+                profileUnlockedIntent.addFlags(
+                        Intent.FLAG_RECEIVER_REGISTERED_ONLY
+                                | Intent.FLAG_RECEIVER_FOREGROUND);
+                mInjector.broadcastIntent(profileUnlockedIntent,
+                        null, null, 0, null, null, null, AppOpsManager.OP_NONE,
+                        null, false, false, MY_PID, SYSTEM_UID,
+                        parent.id);
+            }
+        }
+
+        // Send PRE_BOOT broadcasts if user fingerprint changed; we
+        // purposefully block sending BOOT_COMPLETED until after all
+        // PRE_BOOT receivers are finished to avoid ANR'ing apps
+        final UserInfo info = getUserInfo(userId);
+        if (!Objects.equals(info.lastLoggedInFingerprint, Build.FINGERPRINT)) {
+            // Suppress double notifications for managed profiles that
+            // were unlocked automatically as part of their parent user
+            // being unlocked.
+            final boolean quiet;
+            if (info.isManagedProfile()) {
+                quiet = !uss.tokenProvided
+                        || !mLockPatternUtils.isSeparateProfileChallengeEnabled(userId);
+            } else {
+                quiet = false;
+            }
+            mInjector.sendPreBootBroadcast(userId, quiet,
+                    () -> finishUserUnlockedCompleted(uss));
+        } else {
+            finishUserUnlockedCompleted(uss);
+        }
     }
 
     private void finishUserUnlockedCompleted(UserState uss) {
@@ -417,60 +450,59 @@
         synchronized (mLock) {
             // Bail if we ended up with a stale user
             if (mStartedUsers.get(uss.mHandle.getIdentifier()) != uss) return;
-            final UserInfo userInfo = getUserInfo(userId);
-            if (userInfo == null) {
-                return;
-            }
-
-            // Only keep marching forward if user is actually unlocked
-            if (!StorageManager.isUserKeyUnlocked(userId)) return;
-
-            // Remember that we logged in
-            mInjector.getUserManager().onUserLoggedIn(userId);
-
-            if (!userInfo.isInitialized()) {
-                if (userId != UserHandle.USER_SYSTEM) {
-                    Slog.d(TAG, "Initializing user #" + userId);
-                    Intent intent = new Intent(Intent.ACTION_USER_INITIALIZE);
-                    intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND
-                            | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
-                    mInjector.broadcastIntentLocked(intent, null,
-                            new IIntentReceiver.Stub() {
-                                @Override
-                                public void performReceive(Intent intent, int resultCode,
-                                        String data, Bundle extras, boolean ordered,
-                                        boolean sticky, int sendingUser) {
-                                    // Note: performReceive is called with mService lock held
-                                    mInjector.getUserManager().makeInitialized(userInfo.id);
-                                }
-                            }, 0, null, null, null, AppOpsManager.OP_NONE,
-                            null, true, false, MY_PID, SYSTEM_UID, userId);
-                }
-            }
-
-            Slog.i(TAG, "Sending BOOT_COMPLETE user #" + userId);
-            // Do not report secondary users, runtime restarts or first boot/upgrade
-            if (userId == UserHandle.USER_SYSTEM
-                    && !mInjector.isRuntimeRestarted() && !mInjector.isFirstBootOrUpgrade()) {
-                int uptimeSeconds = (int) (SystemClock.elapsedRealtime() / 1000);
-                MetricsLogger.histogram(mInjector.getContext(), "framework_boot_completed",
-                        uptimeSeconds);
-            }
-            final Intent bootIntent = new Intent(Intent.ACTION_BOOT_COMPLETED, null);
-            bootIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
-            bootIntent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT
-                    | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
-            mInjector.broadcastIntentLocked(bootIntent, null, new IIntentReceiver.Stub() {
-                @Override
-                public void performReceive(Intent intent, int resultCode, String data,
-                        Bundle extras, boolean ordered, boolean sticky, int sendingUser)
-                        throws RemoteException {
-                    Slog.i(UserController.TAG, "Finished processing BOOT_COMPLETED for u" + userId);
-                }
-            }, 0, null, null,
-                    new String[] { android.Manifest.permission.RECEIVE_BOOT_COMPLETED },
-                    AppOpsManager.OP_NONE, null, true, false, MY_PID, SYSTEM_UID, userId);
         }
+        UserInfo userInfo = getUserInfo(userId);
+        if (userInfo == null) {
+            return;
+        }
+        // Only keep marching forward if user is actually unlocked
+        if (!StorageManager.isUserKeyUnlocked(userId)) return;
+
+        // Remember that we logged in
+        mInjector.getUserManager().onUserLoggedIn(userId);
+
+        if (!userInfo.isInitialized()) {
+            if (userId != UserHandle.USER_SYSTEM) {
+                Slog.d(TAG, "Initializing user #" + userId);
+                Intent intent = new Intent(Intent.ACTION_USER_INITIALIZE);
+                intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND
+                        | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+                mInjector.broadcastIntent(intent, null,
+                        new IIntentReceiver.Stub() {
+                            @Override
+                            public void performReceive(Intent intent, int resultCode,
+                                    String data, Bundle extras, boolean ordered,
+                                    boolean sticky, int sendingUser) {
+                                // Note: performReceive is called with mService lock held
+                                mInjector.getUserManager().makeInitialized(userInfo.id);
+                            }
+                        }, 0, null, null, null, AppOpsManager.OP_NONE,
+                        null, true, false, MY_PID, SYSTEM_UID, userId);
+            }
+        }
+
+        Slog.i(TAG, "Sending BOOT_COMPLETE user #" + userId);
+        // Do not report secondary users, runtime restarts or first boot/upgrade
+        if (userId == UserHandle.USER_SYSTEM
+                && !mInjector.isRuntimeRestarted() && !mInjector.isFirstBootOrUpgrade()) {
+            int uptimeSeconds = (int) (SystemClock.elapsedRealtime() / 1000);
+            MetricsLogger.histogram(mInjector.getContext(), "framework_boot_completed",
+                    uptimeSeconds);
+        }
+        final Intent bootIntent = new Intent(Intent.ACTION_BOOT_COMPLETED, null);
+        bootIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+        bootIntent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT
+                | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+        mInjector.broadcastIntent(bootIntent, null, new IIntentReceiver.Stub() {
+                    @Override
+                    public void performReceive(Intent intent, int resultCode, String data,
+                            Bundle extras, boolean ordered, boolean sticky, int sendingUser)
+                            throws RemoteException {
+                        Slog.i(UserController.TAG, "Finished processing BOOT_COMPLETED for u" + userId);
+                    }
+                }, 0, null, null,
+                new String[]{android.Manifest.permission.RECEIVE_BOOT_COMPLETED},
+                AppOpsManager.OP_NONE, null, true, false, MY_PID, SYSTEM_UID, userId);
     }
 
     int restartUser(final int userId, final boolean foreground) {
@@ -499,35 +531,35 @@
         if (userId < 0 || userId == UserHandle.USER_SYSTEM) {
             throw new IllegalArgumentException("Can't stop system user " + userId);
         }
-        mInjector.enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, userId);
+        enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, userId);
         synchronized (mLock) {
-            return stopUsersLocked(userId, force, callback);
+            return stopUsersLU(userId, force, callback);
         }
     }
 
     /**
      * Stops the user along with its related users. The method calls
-     * {@link #getUsersToStopLocked(int)} to determine the list of users that should be stopped.
+     * {@link #getUsersToStopLU(int)} to determine the list of users that should be stopped.
      */
-    private int stopUsersLocked(final int userId, boolean force, final IStopUserCallback callback) {
+    private int stopUsersLU(final int userId, boolean force, final IStopUserCallback callback) {
         if (userId == UserHandle.USER_SYSTEM) {
             return USER_OP_ERROR_IS_SYSTEM;
         }
-        if (isCurrentUserLocked(userId)) {
+        if (isCurrentUserLU(userId)) {
             return USER_OP_IS_CURRENT;
         }
-        int[] usersToStop = getUsersToStopLocked(userId);
+        int[] usersToStop = getUsersToStopLU(userId);
         // If one of related users is system or current, no related users should be stopped
         for (int i = 0; i < usersToStop.length; i++) {
             int relatedUserId = usersToStop[i];
-            if ((UserHandle.USER_SYSTEM == relatedUserId) || isCurrentUserLocked(relatedUserId)) {
+            if ((UserHandle.USER_SYSTEM == relatedUserId) || isCurrentUserLU(relatedUserId)) {
                 if (DEBUG_MU) Slog.i(TAG, "stopUsersLocked cannot stop related user "
                         + relatedUserId);
                 // We still need to stop the requested user if it's a force stop.
                 if (force) {
                     Slog.i(TAG,
                             "Force stop user " + userId + ". Related users will not be stopped");
-                    stopSingleUserLocked(userId, callback);
+                    stopSingleUserLU(userId, callback);
                     return USER_OP_SUCCESS;
                 }
                 return USER_OP_ERROR_RELATED_USERS_CANNOT_STOP;
@@ -535,25 +567,22 @@
         }
         if (DEBUG_MU) Slog.i(TAG, "stopUsersLocked usersToStop=" + Arrays.toString(usersToStop));
         for (int userIdToStop : usersToStop) {
-            stopSingleUserLocked(userIdToStop, userIdToStop == userId ? callback : null);
+            stopSingleUserLU(userIdToStop, userIdToStop == userId ? callback : null);
         }
         return USER_OP_SUCCESS;
     }
 
-    private void stopSingleUserLocked(final int userId, final IStopUserCallback callback) {
+    private void stopSingleUserLU(final int userId, final IStopUserCallback callback) {
         if (DEBUG_MU) Slog.i(TAG, "stopSingleUserLocked userId=" + userId);
         final UserState uss = mStartedUsers.get(userId);
         if (uss == null) {
             // User is not started, nothing to do...  but we do need to
             // callback if requested.
             if (callback != null) {
-                mHandler.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        try {
-                            callback.userStopped(userId);
-                        } catch (RemoteException e) {
-                        }
+                mHandler.post(() -> {
+                    try {
+                        callback.userStopped(userId);
+                    } catch (RemoteException e) {
                     }
                 });
             }
@@ -568,10 +597,10 @@
                 && uss.state != UserState.STATE_SHUTDOWN) {
             uss.setState(UserState.STATE_STOPPING);
             mInjector.getUserManagerInternal().setUserState(userId, uss.state);
-            updateStartedUserArrayLocked();
+            updateStartedUserArrayLU();
 
-            long ident = Binder.clearCallingIdentity();
-            try {
+            // Post to handler to obtain amLock
+            mHandler.post(() -> {
                 // We are going to broadcast ACTION_USER_STOPPING and then
                 // once that is done send a final ACTION_SHUTDOWN and then
                 // stop the user.
@@ -584,31 +613,24 @@
                     @Override
                     public void performReceive(Intent intent, int resultCode, String data,
                             Bundle extras, boolean ordered, boolean sticky, int sendingUser) {
-                        mHandler.post(new Runnable() {
-                            @Override
-                            public void run() {
-                                finishUserStopping(userId, uss);
-                            }
-                        });
+                        mHandler.post(() -> finishUserStopping(userId, uss));
                     }
                 };
+
                 // Clear broadcast queue for the user to avoid delivering stale broadcasts
-                mInjector.clearBroadcastQueueForUserLocked(userId);
+                mInjector.clearBroadcastQueueForUser(userId);
                 // Kick things off.
-                mInjector.broadcastIntentLocked(stoppingIntent,
+                mInjector.broadcastIntent(stoppingIntent,
                         null, stoppingReceiver, 0, null, null,
                         new String[]{INTERACT_ACROSS_USERS}, AppOpsManager.OP_NONE,
                         null, true, false, MY_PID, SYSTEM_UID, UserHandle.USER_ALL);
-            } finally {
-                Binder.restoreCallingIdentity(ident);
-            }
+            });
         }
     }
 
     void finishUserStopping(final int userId, final UserState uss) {
         // On to the next.
         final Intent shutdownIntent = new Intent(Intent.ACTION_SHUTDOWN);
-        shutdownIntent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
         // This is the result receiver for the final shutdown broadcast.
         final IIntentReceiver shutdownReceiver = new IIntentReceiver.Stub() {
             @Override
@@ -635,20 +657,19 @@
         mInjector.batteryStatsServiceNoteEvent(
                 BatteryStats.HistoryItem.EVENT_USER_RUNNING_FINISH,
                 Integer.toString(userId), userId);
-        mInjector.systemServiceManagerStopUser(userId);
+        mInjector.getSystemServiceManager().stopUser(userId);
 
-        synchronized (mLock) {
-            mInjector.broadcastIntentLocked(shutdownIntent,
-                    null, shutdownReceiver, 0, null, null, null,
-                    AppOpsManager.OP_NONE,
-                    null, true, false, MY_PID, SYSTEM_UID, userId);
-        }
+        mInjector.broadcastIntent(shutdownIntent,
+                null, shutdownReceiver, 0, null, null, null,
+                AppOpsManager.OP_NONE,
+                null, true, false, MY_PID, SYSTEM_UID, userId);
     }
 
     void finishUserStopped(UserState uss) {
         final int userId = uss.mHandle.getIdentifier();
         boolean stopped;
         ArrayList<IStopUserCallback> callbacks;
+        boolean forceStopUser = false;
         synchronized (mLock) {
             callbacks = new ArrayList<>(uss.mStopCallbacks);
             if (mStartedUsers.get(userId) != uss) {
@@ -659,16 +680,18 @@
                 stopped = true;
                 // User can no longer run.
                 mStartedUsers.remove(userId);
-                mInjector.getUserManagerInternal().removeUserState(userId);
                 mUserLru.remove(Integer.valueOf(userId));
-                updateStartedUserArrayLocked();
-
-                mInjector.activityManagerOnUserStopped(userId);
-                // Clean up all state and processes associated with the user.
-                // Kill all the processes for the user.
-                forceStopUserLocked(userId, "finish user");
+                updateStartedUserArrayLU();
+                forceStopUser = true;
             }
         }
+        if (forceStopUser) {
+            mInjector.getUserManagerInternal().removeUserState(userId);
+            mInjector.activityManagerOnUserStopped(userId);
+            // Clean up all state and processes associated with the user.
+            // Kill all the processes for the user.
+            forceStopUser(userId, "finish user");
+        }
 
         for (int i = 0; i < callbacks.size(); i++) {
             try {
@@ -680,9 +703,7 @@
 
         if (stopped) {
             mInjector.systemServiceManagerCleanupUser(userId);
-            synchronized (mLock) {
-                mInjector.getActivityStackSupervisor().removeUserLocked(userId);
-            }
+            mInjector.stackSupervisorRemoveUser(userId);
             // Remove the user if it is ephemeral.
             if (getUserInfo(userId).isEphemeral()) {
                 mInjector.getUserManager().removeUser(userId);
@@ -700,39 +721,36 @@
      * Determines the list of users that should be stopped together with the specified
      * {@code userId}. The returned list includes {@code userId}.
      */
-    private @NonNull int[] getUsersToStopLocked(int userId) {
+    private @NonNull int[] getUsersToStopLU(int userId) {
         int startedUsersSize = mStartedUsers.size();
         IntArray userIds = new IntArray();
         userIds.add(userId);
-        synchronized (mUserProfileGroupIdsSelfLocked) {
-            int userGroupId = mUserProfileGroupIdsSelfLocked.get(userId,
+        int userGroupId = mUserProfileGroupIds.get(userId, UserInfo.NO_PROFILE_GROUP_ID);
+        for (int i = 0; i < startedUsersSize; i++) {
+            UserState uss = mStartedUsers.valueAt(i);
+            int startedUserId = uss.mHandle.getIdentifier();
+            // Skip unrelated users (profileGroupId mismatch)
+            int startedUserGroupId = mUserProfileGroupIds.get(startedUserId,
                     UserInfo.NO_PROFILE_GROUP_ID);
-            for (int i = 0; i < startedUsersSize; i++) {
-                UserState uss = mStartedUsers.valueAt(i);
-                int startedUserId = uss.mHandle.getIdentifier();
-                // Skip unrelated users (profileGroupId mismatch)
-                int startedUserGroupId = mUserProfileGroupIdsSelfLocked.get(startedUserId,
-                        UserInfo.NO_PROFILE_GROUP_ID);
-                boolean sameGroup = (userGroupId != UserInfo.NO_PROFILE_GROUP_ID)
-                        && (userGroupId == startedUserGroupId);
-                // userId has already been added
-                boolean sameUserId = startedUserId == userId;
-                if (!sameGroup || sameUserId) {
-                    continue;
-                }
-                userIds.add(startedUserId);
+            boolean sameGroup = (userGroupId != UserInfo.NO_PROFILE_GROUP_ID)
+                    && (userGroupId == startedUserGroupId);
+            // userId has already been added
+            boolean sameUserId = startedUserId == userId;
+            if (!sameGroup || sameUserId) {
+                continue;
             }
+            userIds.add(startedUserId);
         }
         return userIds.toArray();
     }
 
-    private void forceStopUserLocked(int userId, String reason) {
-        mInjector.activityManagerForceStopPackageLocked(userId, reason);
+    private void forceStopUser(int userId, String reason) {
+        mInjector.activityManagerForceStopPackage(userId, reason);
         Intent intent = new Intent(Intent.ACTION_USER_STOPPED);
         intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
                 | Intent.FLAG_RECEIVER_FOREGROUND);
         intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
-        mInjector.broadcastIntentLocked(intent,
+        mInjector.broadcastIntent(intent,
                 null, null, 0, null, null, null, AppOpsManager.OP_NONE,
                 null, false, false, MY_PID, SYSTEM_UID, UserHandle.USER_ALL);
     }
@@ -741,6 +759,7 @@
      * Stops the guest or ephemeral user if it has gone to the background.
      */
     private void stopGuestOrEphemeralUserIfBackground() {
+        IntArray userIds = new IntArray();
         synchronized (mLock) {
             final int num = mUserLru.size();
             for (int i = 0; i < num; i++) {
@@ -751,28 +770,42 @@
                         || oldUss.state == UserState.STATE_SHUTDOWN) {
                     continue;
                 }
-                UserInfo userInfo = getUserInfo(oldUserId);
-                if (userInfo.isEphemeral()) {
-                    LocalServices.getService(UserManagerInternal.class)
-                            .onEphemeralUserStop(oldUserId);
+                userIds.add(oldUserId);
+            }
+        }
+        final int userIdsSize = userIds.size();
+        for (int i = 0; i < userIdsSize; i++) {
+            int oldUserId = userIds.get(i);
+            UserInfo userInfo = getUserInfo(oldUserId);
+            if (userInfo.isEphemeral()) {
+                LocalServices.getService(UserManagerInternal.class).onEphemeralUserStop(oldUserId);
+            }
+            if (userInfo.isGuest() || userInfo.isEphemeral()) {
+                // This is a user to be stopped.
+                synchronized (mLock) {
+                    stopUsersLU(oldUserId, true, null);
                 }
-                if (userInfo.isGuest() || userInfo.isEphemeral()) {
-                    // This is a user to be stopped.
-                    stopUsersLocked(oldUserId, true, null);
-                    break;
-                }
+                break;
             }
         }
     }
 
-    void startProfilesLocked() {
+    void scheduleStartProfiles() {
+        if (!mHandler.hasMessages(START_PROFILES_MSG)) {
+            mHandler.sendMessageDelayed(mHandler.obtainMessage(START_PROFILES_MSG),
+                    DateUtils.SECOND_IN_MILLIS);
+        }
+    }
+
+    void startProfiles() {
+        int currentUserId = getCurrentUserId();
         if (DEBUG_MU) Slog.i(TAG, "startProfilesLocked");
         List<UserInfo> profiles = mInjector.getUserManager().getProfiles(
-                mCurrentUserId, false /* enabledOnly */);
+                currentUserId, false /* enabledOnly */);
         List<UserInfo> profilesToStart = new ArrayList<>(profiles.size());
         for (UserInfo user : profiles) {
             if ((user.flags & UserInfo.FLAG_INITIALIZED) == UserInfo.FLAG_INITIALIZED
-                    && user.id != mCurrentUserId && !user.isQuietModeEnabled()) {
+                    && user.id != currentUserId && !user.isQuietModeEnabled()) {
                 profilesToStart.add(user);
             }
         }
@@ -834,143 +867,156 @@
 
         final long ident = Binder.clearCallingIdentity();
         try {
+            final int oldUserId = getCurrentUserId();
+            if (oldUserId == userId) {
+                return true;
+            }
+
+            if (foreground) {
+                // TODO: I don't think this does what the caller think it does. Seems to only
+                // remove one locked task and won't work if multiple locked tasks are present.
+                mInjector.clearLockTaskMode("startUser");
+            }
+
+            final UserInfo userInfo = getUserInfo(userId);
+            if (userInfo == null) {
+                Slog.w(TAG, "No user info for user #" + userId);
+                return false;
+            }
+            if (foreground && userInfo.isManagedProfile()) {
+                Slog.w(TAG, "Cannot switch to User #" + userId + ": not a full user");
+                return false;
+            }
+
+            if (foreground && mUserSwitchUiEnabled) {
+                mInjector.getWindowManager().startFreezingScreen(
+                        R.anim.screen_user_exit, R.anim.screen_user_enter);
+            }
+
+            boolean needStart = false;
+            boolean updateUmState = false;
+            UserState uss;
+
+            // If the user we are switching to is not currently started, then
+            // we need to start it now.
             synchronized (mLock) {
-                final int oldUserId = mCurrentUserId;
-                if (oldUserId == userId) {
-                    return true;
-                }
-
-                if (foreground) {
-                    // TODO: I don't think this does what the caller think it does. Seems to only
-                    // remove one locked task and won't work if multiple locked tasks are present.
-                    mInjector.getLockTaskController().clearLockTaskMode("startUser");
-                }
-
-                final UserInfo userInfo = getUserInfo(userId);
-                if (userInfo == null) {
-                    Slog.w(TAG, "No user info for user #" + userId);
-                    return false;
-                }
-                if (foreground && userInfo.isManagedProfile()) {
-                    Slog.w(TAG, "Cannot switch to User #" + userId + ": not a full user");
-                    return false;
-                }
-
-                if (foreground && mUserSwitchUiEnabled) {
-                    mInjector.getWindowManager().startFreezingScreen(
-                            R.anim.screen_user_exit, R.anim.screen_user_enter);
-                }
-
-                boolean needStart = false;
-
-                // If the user we are switching to is not currently started, then
-                // we need to start it now.
-                if (mStartedUsers.get(userId) == null) {
-                    UserState userState = new UserState(UserHandle.of(userId));
-                    mStartedUsers.put(userId, userState);
-                    mInjector.getUserManagerInternal().setUserState(userId, userState.state);
-                    updateStartedUserArrayLocked();
+                uss = mStartedUsers.get(userId);
+                if (uss == null) {
+                    uss = new UserState(UserHandle.of(userId));
+                    mStartedUsers.put(userId, uss);
+                    updateStartedUserArrayLU();
                     needStart = true;
+                    updateUmState = true;
                 }
-
-                final UserState uss = mStartedUsers.get(userId);
                 final Integer userIdInt = userId;
                 mUserLru.remove(userIdInt);
                 mUserLru.add(userIdInt);
-
-                if (foreground) {
+            }
+            if (updateUmState) {
+                mInjector.getUserManagerInternal().setUserState(userId, uss.state);
+            }
+            if (foreground) {
+                synchronized (mLock) {
                     mCurrentUserId = userId;
-                    mInjector.updateUserConfigurationLocked();
                     mTargetUserId = UserHandle.USER_NULL; // reset, mCurrentUserId has caught up
-                    updateCurrentProfileIdsLocked();
-                    mInjector.getWindowManager().setCurrentUser(userId, mCurrentProfileIds);
-                    // Once the internal notion of the active user has switched, we lock the device
-                    // with the option to show the user switcher on the keyguard.
-                    if (mUserSwitchUiEnabled) {
-                        mInjector.getWindowManager().setSwitchingUser(true);
-                        mInjector.getWindowManager().lockNow(null);
-                    }
-                } else {
-                    final Integer currentUserIdInt = mCurrentUserId;
-                    updateCurrentProfileIdsLocked();
-                    mInjector.getWindowManager().setCurrentProfileIds(mCurrentProfileIds);
+                }
+                mInjector.updateUserConfiguration();
+                updateCurrentProfileIds();
+                mInjector.getWindowManager().setCurrentUser(userId, getCurrentProfileIds());
+                // Once the internal notion of the active user has switched, we lock the device
+                // with the option to show the user switcher on the keyguard.
+                if (mUserSwitchUiEnabled) {
+                    mInjector.getWindowManager().setSwitchingUser(true);
+                    mInjector.getWindowManager().lockNow(null);
+                }
+            } else {
+                final Integer currentUserIdInt = mCurrentUserId;
+                updateCurrentProfileIds();
+                mInjector.getWindowManager().setCurrentProfileIds(getCurrentProfileIds());
+                synchronized (mLock) {
                     mUserLru.remove(currentUserIdInt);
                     mUserLru.add(currentUserIdInt);
                 }
+            }
 
-                // Make sure user is in the started state.  If it is currently
-                // stopping, we need to knock that off.
-                if (uss.state == UserState.STATE_STOPPING) {
-                    // If we are stopping, we haven't sent ACTION_SHUTDOWN,
-                    // so we can just fairly silently bring the user back from
-                    // the almost-dead.
-                    uss.setState(uss.lastState);
-                    mInjector.getUserManagerInternal().setUserState(userId, uss.state);
-                    updateStartedUserArrayLocked();
-                    needStart = true;
-                } else if (uss.state == UserState.STATE_SHUTDOWN) {
-                    // This means ACTION_SHUTDOWN has been sent, so we will
-                    // need to treat this as a new boot of the user.
-                    uss.setState(UserState.STATE_BOOTING);
-                    mInjector.getUserManagerInternal().setUserState(userId, uss.state);
-                    updateStartedUserArrayLocked();
-                    needStart = true;
+            // Make sure user is in the started state.  If it is currently
+            // stopping, we need to knock that off.
+            if (uss.state == UserState.STATE_STOPPING) {
+                // If we are stopping, we haven't sent ACTION_SHUTDOWN,
+                // so we can just fairly silently bring the user back from
+                // the almost-dead.
+                uss.setState(uss.lastState);
+                mInjector.getUserManagerInternal().setUserState(userId, uss.state);
+                synchronized (mLock) {
+                    updateStartedUserArrayLU();
                 }
-
-                if (uss.state == UserState.STATE_BOOTING) {
-                    // Give user manager a chance to propagate user restrictions
-                    // to other services and prepare app storage
-                    mInjector.getUserManager().onBeforeStartUser(userId);
-
-                    // Booting up a new user, need to tell system services about it.
-                    // Note that this is on the same handler as scheduling of broadcasts,
-                    // which is important because it needs to go first.
-                    mHandler.sendMessage(mHandler.obtainMessage(SYSTEM_USER_START_MSG, userId, 0));
+                needStart = true;
+            } else if (uss.state == UserState.STATE_SHUTDOWN) {
+                // This means ACTION_SHUTDOWN has been sent, so we will
+                // need to treat this as a new boot of the user.
+                uss.setState(UserState.STATE_BOOTING);
+                mInjector.getUserManagerInternal().setUserState(userId, uss.state);
+                synchronized (mLock) {
+                    updateStartedUserArrayLU();
                 }
+                needStart = true;
+            }
 
-                if (foreground) {
-                    mHandler.sendMessage(mHandler.obtainMessage(SYSTEM_USER_CURRENT_MSG, userId,
-                            oldUserId));
-                    mHandler.removeMessages(REPORT_USER_SWITCH_MSG);
-                    mHandler.removeMessages(USER_SWITCH_TIMEOUT_MSG);
-                    mHandler.sendMessage(mHandler.obtainMessage(REPORT_USER_SWITCH_MSG,
-                            oldUserId, userId, uss));
-                    mHandler.sendMessageDelayed(mHandler.obtainMessage(USER_SWITCH_TIMEOUT_MSG,
-                            oldUserId, userId, uss), USER_SWITCH_TIMEOUT_MS);
-                }
+            if (uss.state == UserState.STATE_BOOTING) {
+                // Give user manager a chance to propagate user restrictions
+                // to other services and prepare app storage
+                mInjector.getUserManager().onBeforeStartUser(userId);
 
-                if (needStart) {
-                    // Send USER_STARTED broadcast
-                    Intent intent = new Intent(Intent.ACTION_USER_STARTED);
-                    intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
-                            | Intent.FLAG_RECEIVER_FOREGROUND);
-                    intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
-                    mInjector.broadcastIntentLocked(intent,
-                            null, null, 0, null, null, null, AppOpsManager.OP_NONE,
-                            null, false, false, MY_PID, SYSTEM_UID, userId);
-                }
+                // Booting up a new user, need to tell system services about it.
+                // Note that this is on the same handler as scheduling of broadcasts,
+                // which is important because it needs to go first.
+                mHandler.sendMessage(
+                        mHandler.obtainMessage(SYSTEM_USER_START_MSG, userId, 0));
+            }
 
-                if (foreground) {
-                    moveUserToForegroundLocked(uss, oldUserId, userId);
-                } else {
-                    finishUserBoot(uss);
-                }
+            if (foreground) {
+                mHandler.sendMessage(mHandler.obtainMessage(SYSTEM_USER_CURRENT_MSG, userId,
+                        oldUserId));
+                mHandler.removeMessages(REPORT_USER_SWITCH_MSG);
+                mHandler.removeMessages(USER_SWITCH_TIMEOUT_MSG);
+                mHandler.sendMessage(mHandler.obtainMessage(REPORT_USER_SWITCH_MSG,
+                        oldUserId, userId, uss));
+                mHandler.sendMessageDelayed(mHandler.obtainMessage(USER_SWITCH_TIMEOUT_MSG,
+                        oldUserId, userId, uss), USER_SWITCH_TIMEOUT_MS);
+            }
 
-                if (needStart) {
-                    Intent intent = new Intent(Intent.ACTION_USER_STARTING);
-                    intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
-                    intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
-                    mInjector.broadcastIntentLocked(intent,
-                            null, new IIntentReceiver.Stub() {
-                                @Override
-                                public void performReceive(Intent intent, int resultCode,
-                                        String data, Bundle extras, boolean ordered, boolean sticky,
-                                        int sendingUser) throws RemoteException {
-                                }
-                            }, 0, null, null,
-                            new String[] {INTERACT_ACROSS_USERS}, AppOpsManager.OP_NONE,
-                            null, true, false, MY_PID, SYSTEM_UID, UserHandle.USER_ALL);
-                }
+            if (needStart) {
+                // Send USER_STARTED broadcast
+                Intent intent = new Intent(Intent.ACTION_USER_STARTED);
+                intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
+                        | Intent.FLAG_RECEIVER_FOREGROUND);
+                intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+                mInjector.broadcastIntent(intent,
+                        null, null, 0, null, null, null, AppOpsManager.OP_NONE,
+                        null, false, false, MY_PID, SYSTEM_UID, userId);
+            }
+
+            if (foreground) {
+                moveUserToForeground(uss, oldUserId, userId);
+            } else {
+                finishUserBoot(uss);
+            }
+
+            if (needStart) {
+                Intent intent = new Intent(Intent.ACTION_USER_STARTING);
+                intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+                intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
+                mInjector.broadcastIntent(intent,
+                        null, new IIntentReceiver.Stub() {
+                            @Override
+                            public void performReceive(Intent intent, int resultCode,
+                                    String data, Bundle extras, boolean ordered,
+                                    boolean sticky,
+                                    int sendingUser) throws RemoteException {
+                            }
+                        }, 0, null, null,
+                        new String[]{INTERACT_ACROSS_USERS}, AppOpsManager.OP_NONE,
+                        null, true, false, MY_PID, SYSTEM_UID, UserHandle.USER_ALL);
             }
         } finally {
             Binder.restoreCallingIdentity(ident);
@@ -1014,7 +1060,7 @@
      * when the the credential-encrypted storage isn't tied to a user-provided
      * PIN or pattern.
      */
-    boolean maybeUnlockUser(final int userId) {
+    private boolean maybeUnlockUser(final int userId) {
         // Try unlocking storage using empty token
         return unlockUserCleared(userId, null, null, null);
     }
@@ -1027,65 +1073,106 @@
         }
     }
 
-    boolean unlockUserCleared(final int userId, byte[] token, byte[] secret,
+    private boolean unlockUserCleared(final int userId, byte[] token, byte[] secret,
             IProgressListener listener) {
         UserState uss;
-        synchronized (mLock) {
-            // TODO Move this block outside of synchronized if it causes lock contention
-            if (!StorageManager.isUserKeyUnlocked(userId)) {
-                final UserInfo userInfo = getUserInfo(userId);
-                final IStorageManager storageManager = getStorageManager();
-                try {
-                    // We always want to unlock user storage, even user is not started yet
-                    storageManager.unlockUserKey(userId, userInfo.serialNumber, token, secret);
-                } catch (RemoteException | RuntimeException e) {
-                    Slog.w(TAG, "Failed to unlock: " + e.getMessage());
-                }
+        if (!StorageManager.isUserKeyUnlocked(userId)) {
+            final UserInfo userInfo = getUserInfo(userId);
+            final IStorageManager storageManager = getStorageManager();
+            try {
+                // We always want to unlock user storage, even user is not started yet
+                storageManager.unlockUserKey(userId, userInfo.serialNumber, token, secret);
+            } catch (RemoteException | RuntimeException e) {
+                Slog.w(TAG, "Failed to unlock: " + e.getMessage());
             }
-            // Bail if user isn't actually running, otherwise register the given
-            // listener to watch for unlock progress
+        }
+        synchronized (mLock) {
+            // Register the given listener to watch for unlock progress
             uss = mStartedUsers.get(userId);
-            if (uss == null) {
-                notifyFinished(userId, listener);
-                return false;
-            } else {
+            if (uss != null) {
                 uss.mUnlockProgress.addListener(listener);
                 uss.tokenProvided = (token != null);
             }
         }
+        // Bail if user isn't actually running
+        if (uss == null) {
+            notifyFinished(userId, listener);
+            return false;
+        }
 
         finishUserUnlocking(uss);
 
-        final ArraySet<Integer> childProfilesToUnlock = new ArraySet<>();
-        synchronized (mLock) {
+        // We just unlocked a user, so let's now attempt to unlock any
+        // managed profiles under that user.
 
-            // We just unlocked a user, so let's now attempt to unlock any
-            // managed profiles under that user.
-            for (int i = 0; i < mStartedUsers.size(); i++) {
-                final int testUserId = mStartedUsers.keyAt(i);
-                final UserInfo parent = mInjector.getUserManager().getProfileParent(testUserId);
-                if (parent != null && parent.id == userId && testUserId != userId) {
-                    Slog.d(TAG, "User " + testUserId + " (parent " + parent.id
-                            + "): attempting unlock because parent was just unlocked");
-                    childProfilesToUnlock.add(testUserId);
-                }
+        // First, get list of userIds. Requires mLock, so we cannot make external calls, e.g. to UMS
+        int[] userIds;
+        synchronized (mLock) {
+            userIds = new int[mStartedUsers.size()];
+            for (int i = 0; i < userIds.length; i++) {
+                userIds[i] = mStartedUsers.keyAt(i);
             }
         }
-
-        final int size = childProfilesToUnlock.size();
-        for (int i = 0; i < size; i++) {
-            maybeUnlockUser(childProfilesToUnlock.valueAt(i));
+        for (int testUserId : userIds) {
+            final UserInfo parent = mInjector.getUserManager().getProfileParent(testUserId);
+            if (parent != null && parent.id == userId && testUserId != userId) {
+                Slog.d(TAG, "User " + testUserId + " (parent " + parent.id
+                        + "): attempting unlock because parent was just unlocked");
+                maybeUnlockUser(testUserId);
+            }
         }
 
         return true;
     }
 
-    void showUserSwitchDialog(Pair<UserInfo, UserInfo> fromToUserPair) {
+    boolean switchUser(final int targetUserId) {
+        enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, targetUserId);
+        int currentUserId = getCurrentUserId();
+        UserInfo targetUserInfo = getUserInfo(targetUserId);
+        if (targetUserId == currentUserId) {
+            Slog.i(TAG, "user #" + targetUserId + " is already the current user");
+            return true;
+        }
+        if (targetUserInfo == null) {
+            Slog.w(TAG, "No user info for user #" + targetUserId);
+            return false;
+        }
+        if (!targetUserInfo.isDemo() && UserManager.isDeviceInDemoMode(mInjector.getContext())) {
+            Slog.w(TAG, "Cannot switch to non-demo user #" + targetUserId
+                    + " when device is in demo mode");
+            return false;
+        }
+        if (!targetUserInfo.supportsSwitchTo()) {
+            Slog.w(TAG, "Cannot switch to User #" + targetUserId + ": not supported");
+            return false;
+        }
+        if (targetUserInfo.isManagedProfile()) {
+            Slog.w(TAG, "Cannot switch to User #" + targetUserId + ": not a full user");
+            return false;
+        }
+        synchronized (mLock) {
+            mTargetUserId = targetUserId;
+        }
+        if (mUserSwitchUiEnabled) {
+            UserInfo currentUserInfo = getUserInfo(currentUserId);
+            Pair<UserInfo, UserInfo> userNames = new Pair<>(currentUserInfo, targetUserInfo);
+            mUiHandler.removeMessages(START_USER_SWITCH_UI_MSG);
+            mUiHandler.sendMessage(mHandler.obtainMessage(
+                    START_USER_SWITCH_UI_MSG, userNames));
+        } else {
+            mHandler.removeMessages(START_USER_SWITCH_FG_MSG);
+            mHandler.sendMessage(mHandler.obtainMessage(
+                    START_USER_SWITCH_FG_MSG, targetUserId, 0));
+        }
+        return true;
+    }
+
+    private void showUserSwitchDialog(Pair<UserInfo, UserInfo> fromToUserPair) {
         // The dialog will show and then initiate the user switch by calling startUserInForeground
         mInjector.showUserSwitchingDialog(fromToUserPair.first, fromToUserPair.second);
     }
 
-    void dispatchForegroundProfileChanged(int userId) {
+    private void dispatchForegroundProfileChanged(int userId) {
         final int observerCount = mUserSwitchObservers.beginBroadcast();
         for (int i = 0; i < observerCount; i++) {
             try {
@@ -1110,7 +1197,7 @@
         mUserSwitchObservers.finishBroadcast();
     }
 
-    void dispatchLockedBootComplete(int userId) {
+    private void dispatchLockedBootComplete(int userId) {
         final int observerCount = mUserSwitchObservers.beginBroadcast();
         for (int i = 0; i < observerCount; i++) {
             try {
@@ -1136,23 +1223,23 @@
         synchronized (mLock) {
             if (DEBUG_MU) Slog.i(TAG, "stopBackgroundUsersIfEnforced stopping " + oldUserId
                     + " and related users");
-            stopUsersLocked(oldUserId, false, null);
+            stopUsersLU(oldUserId, false, null);
         }
     }
 
-    void timeoutUserSwitch(UserState uss, int oldUserId, int newUserId) {
+    private void timeoutUserSwitch(UserState uss, int oldUserId, int newUserId) {
         synchronized (mLock) {
             Slog.e(TAG, "User switch timeout: from " + oldUserId + " to " + newUserId);
             mTimeoutUserSwitchCallbacks = mCurWaitingUserSwitchCallbacks;
             mHandler.removeMessages(USER_SWITCH_CALLBACKS_TIMEOUT_MSG);
-            sendContinueUserSwitchLocked(uss, oldUserId, newUserId);
+            sendContinueUserSwitchLU(uss, oldUserId, newUserId);
             // Report observers that never called back (USER_SWITCH_CALLBACKS_TIMEOUT)
             mHandler.sendMessageDelayed(mHandler.obtainMessage(USER_SWITCH_CALLBACKS_TIMEOUT_MSG,
                     oldUserId, newUserId), USER_SWITCH_CALLBACKS_TIMEOUT_MS);
         }
     }
 
-    void timeoutUserSwitchCallbacks(int oldUserId, int newUserId) {
+    private void timeoutUserSwitchCallbacks(int oldUserId, int newUserId) {
         synchronized (mLock) {
             if (mTimeoutUserSwitchCallbacks != null && !mTimeoutUserSwitchCallbacks.isEmpty()) {
                 Slog.wtf(TAG, "User switch timeout: from " + oldUserId + " to " + newUserId
@@ -1195,7 +1282,7 @@
                                 if (waitingCallbacksCount.decrementAndGet() == 0
                                         && (curWaitingUserSwitchCallbacks
                                         == mCurWaitingUserSwitchCallbacks)) {
-                                    sendContinueUserSwitchLocked(uss, oldUserId, newUserId);
+                                    sendContinueUserSwitchLU(uss, oldUserId, newUserId);
                                 }
                             }
                         }
@@ -1206,25 +1293,23 @@
             }
         } else {
             synchronized (mLock) {
-                sendContinueUserSwitchLocked(uss, oldUserId, newUserId);
+                sendContinueUserSwitchLU(uss, oldUserId, newUserId);
             }
         }
         mUserSwitchObservers.finishBroadcast();
     }
 
-    void sendContinueUserSwitchLocked(UserState uss, int oldUserId, int newUserId) {
+    void sendContinueUserSwitchLU(UserState uss, int oldUserId, int newUserId) {
         mCurWaitingUserSwitchCallbacks = null;
         mHandler.removeMessages(USER_SWITCH_TIMEOUT_MSG);
-        mHandler.sendMessage(mHandler.obtainMessage(ActivityManagerService.CONTINUE_USER_SWITCH_MSG,
+        mHandler.sendMessage(mHandler.obtainMessage(CONTINUE_USER_SWITCH_MSG,
                 oldUserId, newUserId, uss));
     }
 
     void continueUserSwitch(UserState uss, int oldUserId, int newUserId) {
         Slog.d(TAG, "Continue user switch oldUser #" + oldUserId + ", newUser #" + newUserId);
         if (mUserSwitchUiEnabled) {
-            synchronized (mLock) {
-                mInjector.getWindowManager().stopFreezingScreen();
-            }
+            mInjector.getWindowManager().stopFreezingScreen();
         }
         uss.switching = false;
         mHandler.removeMessages(REPORT_USER_SWITCH_COMPLETE_MSG);
@@ -1234,19 +1319,18 @@
         stopBackgroundUsersIfEnforced(oldUserId);
     }
 
-    void moveUserToForegroundLocked(UserState uss, int oldUserId, int newUserId) {
-        boolean homeInFront =
-                mInjector.getActivityStackSupervisor().switchUserLocked(newUserId, uss);
+    private void moveUserToForeground(UserState uss, int oldUserId, int newUserId) {
+        boolean homeInFront = mInjector.stackSupervisorSwitchUser(newUserId, uss);
         if (homeInFront) {
-            mInjector.startHomeActivityLocked(newUserId, "moveUserToForeground");
+            mInjector.startHomeActivity(newUserId, "moveUserToForeground");
         } else {
-            mInjector.getActivityStackSupervisor().resumeFocusedStackTopActivityLocked();
+            mInjector.stackSupervisorResumeFocusedStackTopActivity();
         }
         EventLogTags.writeAmSwitchUser(newUserId);
-        sendUserSwitchBroadcastsLocked(oldUserId, newUserId);
+        sendUserSwitchBroadcasts(oldUserId, newUserId);
     }
 
-    void sendUserSwitchBroadcastsLocked(int oldUserId, int newUserId) {
+    void sendUserSwitchBroadcasts(int oldUserId, int newUserId) {
         long ident = Binder.clearCallingIdentity();
         try {
             Intent intent;
@@ -1260,7 +1344,7 @@
                     intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
                             | Intent.FLAG_RECEIVER_FOREGROUND);
                     intent.putExtra(Intent.EXTRA_USER_HANDLE, profileUserId);
-                    mInjector.broadcastIntentLocked(intent,
+                    mInjector.broadcastIntent(intent,
                             null, null, 0, null, null, null, AppOpsManager.OP_NONE,
                             null, false, false, MY_PID, SYSTEM_UID, profileUserId);
                 }
@@ -1275,7 +1359,7 @@
                     intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
                             | Intent.FLAG_RECEIVER_FOREGROUND);
                     intent.putExtra(Intent.EXTRA_USER_HANDLE, profileUserId);
-                    mInjector.broadcastIntentLocked(intent,
+                    mInjector.broadcastIntent(intent,
                             null, null, 0, null, null, null, AppOpsManager.OP_NONE,
                             null, false, false, MY_PID, SYSTEM_UID, profileUserId);
                 }
@@ -1283,7 +1367,7 @@
                 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
                         | Intent.FLAG_RECEIVER_FOREGROUND);
                 intent.putExtra(Intent.EXTRA_USER_HANDLE, newUserId);
-                mInjector.broadcastIntentLocked(intent,
+                mInjector.broadcastIntent(intent,
                         null, null, 0, null, null,
                         new String[] {android.Manifest.permission.MANAGE_USERS},
                         AppOpsManager.OP_NONE, null, false, false, MY_PID, SYSTEM_UID,
@@ -1308,7 +1392,7 @@
         // the value the caller will receive and someone else changing it.
         // We assume that USER_CURRENT_OR_SELF will use the current user; later
         // we will switch to the calling user if access to the current user fails.
-        int targetUserId = unsafeConvertIncomingUserLocked(userId);
+        int targetUserId = unsafeConvertIncomingUser(userId);
 
         if (callingUid != 0 && callingUid != SYSTEM_UID) {
             final boolean allow;
@@ -1376,9 +1460,9 @@
         return targetUserId;
     }
 
-    int unsafeConvertIncomingUserLocked(int userId) {
+    int unsafeConvertIncomingUser(int userId) {
         return (userId == UserHandle.USER_CURRENT || userId == UserHandle.USER_CURRENT_OR_SELF)
-                ? getCurrentUserIdLocked(): userId;
+                ? getCurrentUserId(): userId;
     }
 
     void registerUserSwitchObserver(IUserSwitchObserver observer, String name) {
@@ -1395,19 +1479,26 @@
         mUserSwitchObservers.register(observer, name);
     }
 
+    void sendForegroundProfileChanged(int userId) {
+        mHandler.removeMessages(FOREGROUND_PROFILE_CHANGED_MSG);
+        mHandler.obtainMessage(FOREGROUND_PROFILE_CHANGED_MSG, userId, 0).sendToTarget();
+    }
+
     void unregisterUserSwitchObserver(IUserSwitchObserver observer) {
         mUserSwitchObservers.unregister(observer);
     }
 
-    UserState getStartedUserStateLocked(int userId) {
-        return mStartedUsers.get(userId);
+    UserState getStartedUserState(int userId) {
+        synchronized (mLock) {
+            return mStartedUsers.get(userId);
+        }
     }
 
     boolean hasStartedUserState(int userId) {
         return mStartedUsers.get(userId) != null;
     }
 
-    private void updateStartedUserArrayLocked() {
+    private void updateStartedUserArrayLU() {
         int num = 0;
         for (int i = 0; i < mStartedUsers.size(); i++) {
             UserState uss = mStartedUsers.valueAt(i);
@@ -1428,15 +1519,20 @@
         }
     }
 
-    void sendBootCompletedLocked(IIntentReceiver resultTo) {
-        for (int i = 0; i < mStartedUsers.size(); i++) {
-            UserState uss = mStartedUsers.valueAt(i);
+    void sendBootCompleted(IIntentReceiver resultTo) {
+        // Get a copy of mStartedUsers to use outside of lock
+        SparseArray<UserState> startedUsers;
+        synchronized (mLock) {
+            startedUsers = mStartedUsers.clone();
+        }
+        for (int i = 0; i < startedUsers.size(); i++) {
+            UserState uss = startedUsers.valueAt(i);
             finishUserBoot(uss, resultTo);
         }
     }
 
     void onSystemReady() {
-        updateCurrentProfileIdsLocked();
+        updateCurrentProfileIds();
     }
 
     /**
@@ -1444,33 +1540,35 @@
      * user switch happens or when a new related user is started in the
      * background.
      */
-    private void updateCurrentProfileIdsLocked() {
-        final List<UserInfo> profiles = mInjector.getUserManager().getProfiles(mCurrentUserId,
+    private void updateCurrentProfileIds() {
+        final List<UserInfo> profiles = mInjector.getUserManager().getProfiles(getCurrentUserId(),
                 false /* enabledOnly */);
         int[] currentProfileIds = new int[profiles.size()]; // profiles will not be null
         for (int i = 0; i < currentProfileIds.length; i++) {
             currentProfileIds[i] = profiles.get(i).id;
         }
-        mCurrentProfileIds = currentProfileIds;
+        final List<UserInfo> users = mInjector.getUserManager().getUsers(false);
+        synchronized (mLock) {
+            mCurrentProfileIds = currentProfileIds;
 
-        synchronized (mUserProfileGroupIdsSelfLocked) {
-            mUserProfileGroupIdsSelfLocked.clear();
-            final List<UserInfo> users = mInjector.getUserManager().getUsers(false);
+            mUserProfileGroupIds.clear();
             for (int i = 0; i < users.size(); i++) {
                 UserInfo user = users.get(i);
                 if (user.profileGroupId != UserInfo.NO_PROFILE_GROUP_ID) {
-                    mUserProfileGroupIdsSelfLocked.put(user.id, user.profileGroupId);
+                    mUserProfileGroupIds.put(user.id, user.profileGroupId);
                 }
             }
         }
     }
 
-    int[] getStartedUserArrayLocked() {
-        return mStartedUserArray;
+    int[] getStartedUserArray() {
+        synchronized (mLock) {
+            return mStartedUserArray;
+        }
     }
 
-    boolean isUserRunningLocked(int userId, int flags) {
-        UserState state = getStartedUserStateLocked(userId);
+    boolean isUserRunning(int userId, int flags) {
+        UserState state = getStartedUserState(userId);
         if (state == null) {
             return false;
         }
@@ -1533,29 +1631,38 @@
             return getUserInfo(mCurrentUserId);
         }
         synchronized (mLock) {
-            return getCurrentUserLocked();
+            return getCurrentUserLU();
         }
     }
 
-    UserInfo getCurrentUserLocked() {
+    UserInfo getCurrentUserLU() {
         int userId = mTargetUserId != UserHandle.USER_NULL ? mTargetUserId : mCurrentUserId;
         return getUserInfo(userId);
     }
 
-    int getCurrentOrTargetUserIdLocked() {
+    int getCurrentOrTargetUserId() {
+        synchronized (mLock) {
+            return mTargetUserId != UserHandle.USER_NULL ? mTargetUserId : mCurrentUserId;
+        }
+    }
+
+    int getCurrentOrTargetUserIdLU() {
         return mTargetUserId != UserHandle.USER_NULL ? mTargetUserId : mCurrentUserId;
     }
 
-    int getCurrentUserIdLocked() {
+
+    int getCurrentUserIdLU() {
         return mCurrentUserId;
     }
 
-    private boolean isCurrentUserLocked(int userId) {
-        return userId == getCurrentOrTargetUserIdLocked();
+    int getCurrentUserId() {
+        synchronized (mLock) {
+            return mCurrentUserId;
+        }
     }
 
-    int setTargetUserIdLocked(int targetUserId) {
-        return mTargetUserId = targetUserId;
+    private boolean isCurrentUserLU(int userId) {
+        return userId == getCurrentOrTargetUserIdLU();
     }
 
     int[] getUsers() {
@@ -1575,6 +1682,15 @@
         return mInjector.getUserManager().exists(userId);
     }
 
+    void enforceShellRestriction(String restriction, int userHandle) {
+        if (Binder.getCallingUid() == SHELL_UID) {
+            if (userHandle < 0 || hasUserRestriction(restriction, userHandle)) {
+                throw new SecurityException("Shell does not have permission to access user "
+                        + userHandle);
+            }
+        }
+    }
+
     boolean hasUserRestriction(String restriction, int userId) {
         return mInjector.getUserManager().hasUserRestriction(restriction, userId);
     }
@@ -1593,22 +1709,26 @@
         if (callingUserId == targetUserId) {
             return true;
         }
-        synchronized (mUserProfileGroupIdsSelfLocked) {
-            int callingProfile = mUserProfileGroupIdsSelfLocked.get(callingUserId,
+        synchronized (mLock) {
+            int callingProfile = mUserProfileGroupIds.get(callingUserId,
                     UserInfo.NO_PROFILE_GROUP_ID);
-            int targetProfile = mUserProfileGroupIdsSelfLocked.get(targetUserId,
+            int targetProfile = mUserProfileGroupIds.get(targetUserId,
                     UserInfo.NO_PROFILE_GROUP_ID);
             return callingProfile != UserInfo.NO_PROFILE_GROUP_ID
                     && callingProfile == targetProfile;
         }
     }
 
-    boolean isCurrentProfileLocked(int userId) {
-        return ArrayUtils.contains(mCurrentProfileIds, userId);
+    boolean isCurrentProfile(int userId) {
+        synchronized (mLock) {
+            return ArrayUtils.contains(mCurrentProfileIds, userId);
+        }
     }
 
-    int[] getCurrentProfileIdsLocked() {
-        return mCurrentProfileIds;
+    int[] getCurrentProfileIds() {
+        synchronized (mLock) {
+            return mCurrentProfileIds;
+        }
     }
 
     /**
@@ -1633,40 +1753,107 @@
     }
 
     void dump(PrintWriter pw, boolean dumpAll) {
-        pw.println("  mStartedUsers:");
-        for (int i = 0; i < mStartedUsers.size(); i++) {
-            UserState uss = mStartedUsers.valueAt(i);
-            pw.print("    User #"); pw.print(uss.mHandle.getIdentifier());
-            pw.print(": "); uss.dump("", pw);
-        }
-        pw.print("  mStartedUserArray: [");
-        for (int i = 0; i < mStartedUserArray.length; i++) {
-            if (i > 0) pw.print(", ");
-            pw.print(mStartedUserArray[i]);
-        }
-        pw.println("]");
-        pw.print("  mUserLru: [");
-        for (int i = 0; i < mUserLru.size(); i++) {
-            if (i > 0) pw.print(", ");
-            pw.print(mUserLru.get(i));
-        }
-        pw.println("]");
-        if (dumpAll) {
-            pw.print("  mStartedUserArray: "); pw.println(Arrays.toString(mStartedUserArray));
-        }
-        synchronized (mUserProfileGroupIdsSelfLocked) {
-            if (mUserProfileGroupIdsSelfLocked.size() > 0) {
+        synchronized (mLock) {
+            pw.println("  mStartedUsers:");
+            for (int i = 0; i < mStartedUsers.size(); i++) {
+                UserState uss = mStartedUsers.valueAt(i);
+                pw.print("    User #");
+                pw.print(uss.mHandle.getIdentifier());
+                pw.print(": ");
+                uss.dump("", pw);
+            }
+            pw.print("  mStartedUserArray: [");
+            for (int i = 0; i < mStartedUserArray.length; i++) {
+                if (i > 0)
+                    pw.print(", ");
+                pw.print(mStartedUserArray[i]);
+            }
+            pw.println("]");
+            pw.print("  mUserLru: [");
+            for (int i = 0; i < mUserLru.size(); i++) {
+                if (i > 0)
+                    pw.print(", ");
+                pw.print(mUserLru.get(i));
+            }
+            pw.println("]");
+            if (dumpAll) {
+                pw.print("  mStartedUserArray: ");
+                pw.println(Arrays.toString(mStartedUserArray));
+            }
+            if (mUserProfileGroupIds.size() > 0) {
                 pw.println("  mUserProfileGroupIds:");
-                for (int i=0; i<mUserProfileGroupIdsSelfLocked.size(); i++) {
+                for (int i=0; i< mUserProfileGroupIds.size(); i++) {
                     pw.print("    User #");
-                    pw.print(mUserProfileGroupIdsSelfLocked.keyAt(i));
+                    pw.print(mUserProfileGroupIds.keyAt(i));
                     pw.print(" -> profile #");
-                    pw.println(mUserProfileGroupIdsSelfLocked.valueAt(i));
+                    pw.println(mUserProfileGroupIds.valueAt(i));
                 }
             }
         }
     }
 
+    public boolean handleMessage(Message msg) {
+        switch (msg.what) {
+            case START_USER_SWITCH_FG_MSG:
+                startUserInForeground(msg.arg1);
+                break;
+            case REPORT_USER_SWITCH_MSG:
+                dispatchUserSwitch((UserState) msg.obj, msg.arg1, msg.arg2);
+                break;
+            case CONTINUE_USER_SWITCH_MSG:
+                continueUserSwitch((UserState) msg.obj, msg.arg1, msg.arg2);
+                break;
+            case USER_SWITCH_TIMEOUT_MSG:
+                timeoutUserSwitch((UserState) msg.obj, msg.arg1, msg.arg2);
+                break;
+            case USER_SWITCH_CALLBACKS_TIMEOUT_MSG:
+                timeoutUserSwitchCallbacks(msg.arg1, msg.arg2);
+                break;
+            case START_PROFILES_MSG:
+                startProfiles();
+                break;
+            case SYSTEM_USER_START_MSG:
+                mInjector.batteryStatsServiceNoteEvent(
+                        BatteryStats.HistoryItem.EVENT_USER_RUNNING_START,
+                        Integer.toString(msg.arg1), msg.arg1);
+                mInjector.getSystemServiceManager().startUser(msg.arg1);
+                break;
+            case SYSTEM_USER_UNLOCK_MSG:
+                final int userId = msg.arg1;
+                mInjector.getSystemServiceManager().unlockUser(userId);
+                mInjector.loadUserRecents(userId);
+                if (userId == UserHandle.USER_SYSTEM) {
+                    mInjector.startPersistentApps(PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
+                }
+                mInjector.installEncryptionUnawareProviders(userId);
+                finishUserUnlocked((UserState) msg.obj);
+                break;
+            case SYSTEM_USER_CURRENT_MSG:
+                mInjector.batteryStatsServiceNoteEvent(
+                        BatteryStats.HistoryItem.EVENT_USER_FOREGROUND_FINISH,
+                        Integer.toString(msg.arg2), msg.arg2);
+                mInjector.batteryStatsServiceNoteEvent(
+                        BatteryStats.HistoryItem.EVENT_USER_FOREGROUND_START,
+                        Integer.toString(msg.arg1), msg.arg1);
+
+                mInjector.getSystemServiceManager().switchUser(msg.arg1);
+                break;
+            case FOREGROUND_PROFILE_CHANGED_MSG:
+                dispatchForegroundProfileChanged(msg.arg1);
+                break;
+            case REPORT_USER_SWITCH_COMPLETE_MSG:
+                dispatchUserSwitchComplete(msg.arg1);
+                break;
+            case REPORT_LOCKED_BOOT_COMPLETE_MSG:
+                dispatchLockedBootComplete(msg.arg1);
+                break;
+            case START_USER_SWITCH_UI_MSG:
+                showUserSwitchDialog((Pair<UserInfo, UserInfo>) msg.obj);
+                break;
+        }
+        return false;
+    }
+
     @VisibleForTesting
     static class Injector {
         private final ActivityManagerService mService;
@@ -1677,12 +1864,12 @@
             mService = service;
         }
 
-        protected Object getLock() {
-            return mService;
+        protected Handler getHandler(Handler.Callback callback) {
+            return new Handler(mService.mHandlerThread.getLooper(), callback);
         }
 
-        protected Handler getHandler() {
-            return mService.mHandler;
+        protected Handler getUiHandler(Handler.Callback callback) {
+            return new Handler(mService.mUiHandler.getLooper(), callback);
         }
 
         protected Context getContext() {
@@ -1693,13 +1880,16 @@
             return new LockPatternUtils(getContext());
         }
 
-        protected int broadcastIntentLocked(Intent intent, String resolvedType,
+        protected int broadcastIntent(Intent intent, String resolvedType,
                 IIntentReceiver resultTo, int resultCode, String resultData,
                 Bundle resultExtras, String[] requiredPermissions, int appOp, Bundle bOptions,
                 boolean ordered, boolean sticky, int callingPid, int callingUid, int userId) {
-            return mService.broadcastIntentLocked(null, null, intent, resolvedType, resultTo,
-                    resultCode, resultData, resultExtras, requiredPermissions, appOp, bOptions,
-                    ordered, sticky, callingPid, callingUid, userId);
+            // TODO b/64165549 Verify that mLock is not held before calling AMS methods
+            synchronized (mService) {
+                return mService.broadcastIntentLocked(null, null, intent, resolvedType, resultTo,
+                        resultCode, resultData, resultExtras, requiredPermissions, appOp, bOptions,
+                        ordered, sticky, callingPid, callingUid, userId);
+            }
         }
 
         int checkCallingPermission(String permission) {
@@ -1710,7 +1900,9 @@
             return mService.mWindowManager;
         }
         void activityManagerOnUserStopped(int userId) {
-            mService.onUserStoppedLocked(userId);
+            synchronized (mService) {
+                mService.onUserStoppedLocked(userId);
+            }
         }
 
         void systemServiceManagerCleanupUser(int userId) {
@@ -1740,14 +1932,14 @@
             mService.mBatteryStatsService.noteEvent(code, name, uid);
         }
 
-        void systemServiceManagerStopUser(int userId) {
-            mService.mSystemServiceManager.stopUser(userId);
-        }
-
         boolean isRuntimeRestarted() {
             return mService.mSystemServiceManager.isRuntimeRestarted();
         }
 
+        SystemServiceManager getSystemServiceManager() {
+            return mService.mSystemServiceManager;
+        }
+
         boolean isFirstBootOrUpgrade() {
             IPackageManager pm = AppGlobals.getPackageManager();
             try {
@@ -1766,9 +1958,11 @@
             }.sendNext();
         }
 
-        void activityManagerForceStopPackageLocked(int userId, String reason) {
-            mService.forceStopPackageLocked(null, -1, false, false, true, false, false,
-                    userId, reason);
+        void activityManagerForceStopPackage(int userId, String reason) {
+            synchronized (mService) {
+                mService.forceStopPackageLocked(null, -1, false, false, true, false, false,
+                        userId, reason);
+            }
         };
 
         int checkComponentPermission(String permission, int pid, int uid, int owningUid,
@@ -1776,20 +1970,36 @@
             return mService.checkComponentPermission(permission, pid, uid, owningUid, exported);
         }
 
-        void startHomeActivityLocked(int userId, String reason) {
-            mService.startHomeActivityLocked(userId, reason);
+        protected void startHomeActivity(int userId, String reason) {
+            synchronized (mService) {
+                mService.startHomeActivityLocked(userId, reason);
+            }
         }
 
-        void updateUserConfigurationLocked() {
-            mService.updateUserConfigurationLocked();
+        void updateUserConfiguration() {
+            synchronized (mService) {
+                mService.updateUserConfigurationLocked();
+            }
         }
 
-        void clearBroadcastQueueForUserLocked(int userId) {
-            mService.clearBroadcastQueueForUserLocked(userId);
+        void clearBroadcastQueueForUser(int userId) {
+            synchronized (mService) {
+                mService.clearBroadcastQueueForUserLocked(userId);
+            }
         }
 
-        void enforceShellRestriction(String restriction, int userId) {
-            mService.enforceShellRestriction(restriction, userId);
+        void loadUserRecents(int userId) {
+            synchronized (mService) {
+                mService.mRecentTasks.loadUserRecentsLocked(userId);
+            }
+        }
+
+        void startPersistentApps(int matchFlags) {
+            mService.startPersistentApps(matchFlags);
+        }
+
+        void installEncryptionUnawareProviders(int userId) {
+            mService.installEncryptionUnawareProviders(userId);
         }
 
         void showUserSwitchingDialog(UserInfo fromUser, UserInfo toUser) {
@@ -1798,12 +2008,28 @@
             d.show();
         }
 
-        ActivityStackSupervisor getActivityStackSupervisor() {
-            return mService.mStackSupervisor;
+        void stackSupervisorRemoveUser(int userId) {
+            synchronized (mService) {
+                mService.mStackSupervisor.removeUserLocked(userId);
+            }
         }
 
-        LockTaskController getLockTaskController() {
-            return mService.mLockTaskController;
+        protected boolean stackSupervisorSwitchUser(int userId, UserState uss) {
+            synchronized (mService) {
+                return mService.mStackSupervisor.switchUserLocked(userId, uss);
+            }
+        }
+
+        protected void stackSupervisorResumeFocusedStackTopActivity() {
+            synchronized (mService) {
+                mService.mStackSupervisor.resumeFocusedStackTopActivityLocked();
+            }
+        }
+
+        protected void clearLockTaskMode(String reason) {
+            synchronized (mService) {
+                mService.mLockTaskController.clearLockTaskMode(reason);
+            }
         }
     }
 }
diff --git a/com/android/server/am/UserState.java b/com/android/server/am/UserState.java
index 2e27387..d36d9cb 100644
--- a/com/android/server/am/UserState.java
+++ b/com/android/server/am/UserState.java
@@ -59,8 +59,10 @@
     /**
      * The last time that a provider was reported to usage stats as being brought to important
      * foreground procstate.
+     * <p><strong>Important: </strong>Only access this field when holding ActivityManagerService
+     * lock.
      */
-    public final ArrayMap<String,Long> mProviderLastReportedFg = new ArrayMap<>();
+    final ArrayMap<String,Long> mProviderLastReportedFg = new ArrayMap<>();
 
     public UserState(UserHandle handle) {
         mHandle = handle;
diff --git a/com/android/server/appwidget/AppWidgetServiceImpl.java b/com/android/server/appwidget/AppWidgetServiceImpl.java
index 80b5477..a6aaaa6 100644
--- a/com/android/server/appwidget/AppWidgetServiceImpl.java
+++ b/com/android/server/appwidget/AppWidgetServiceImpl.java
@@ -2427,14 +2427,14 @@
             out.attribute(null, "p", Integer.toHexString(widget.provider.tag));
         }
         if (widget.options != null) {
-            out.attribute(null, "min_width", Integer.toHexString(widget.options.getInt(
-                    AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH)));
-            out.attribute(null, "min_height", Integer.toHexString(widget.options.getInt(
-                    AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT)));
-            out.attribute(null, "max_width", Integer.toHexString(widget.options.getInt(
-                    AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH)));
-            out.attribute(null, "max_height", Integer.toHexString(widget.options.getInt(
-                    AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT)));
+            int minWidth = widget.options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_WIDTH);
+            int minHeight = widget.options.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT);
+            int maxWidth = widget.options.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH);
+            int maxHeight = widget.options.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT);
+            out.attribute(null, "min_width", Integer.toHexString((minWidth > 0) ? minWidth : 0));
+            out.attribute(null, "min_height", Integer.toHexString((minHeight > 0) ? minHeight : 0));
+            out.attribute(null, "max_width", Integer.toHexString((maxWidth > 0) ? maxWidth : 0));
+            out.attribute(null, "max_height", Integer.toHexString((maxHeight > 0) ? maxHeight : 0));
             out.attribute(null, "host_category", Integer.toHexString(widget.options.getInt(
                     AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY)));
         }
diff --git a/com/android/server/audio/AudioEventLogger.java b/com/android/server/audio/AudioEventLogger.java
index c96138f..9ebd75b 100644
--- a/com/android/server/audio/AudioEventLogger.java
+++ b/com/android/server/audio/AudioEventLogger.java
@@ -16,6 +16,8 @@
 
 package com.android.server.audio;
 
+import android.util.Log;
+
 import java.io.PrintWriter;
 import java.text.SimpleDateFormat;
 import java.util.Date;
@@ -47,6 +49,22 @@
         }
 
         /**
+         * Causes the string message for the event to appear in the logcat.
+         * Here is an example of how to create a new event (a StringEvent), adding it to the logger
+         * (an instance of AudioEventLogger) while also making it show in the logcat:
+         * <pre>
+         *     myLogger.log(
+         *         (new StringEvent("something for logcat and logger")).printLog(MyClass.TAG) );
+         * </pre>
+         * @param tag the tag for the android.util.Log.v
+         * @return the same instance of the event
+         */
+        public Event printLog(String tag) {
+            Log.i(tag, eventToString());
+            return this;
+        }
+
+        /**
          * Convert event to String.
          * This method is only called when the logger history is about to the dumped,
          * so this method is where expensive String conversions should be made, not when the Event
diff --git a/com/android/server/audio/AudioService.java b/com/android/server/audio/AudioService.java
index 91b1591..5eb2a8d 100644
--- a/com/android/server/audio/AudioService.java
+++ b/com/android/server/audio/AudioService.java
@@ -461,6 +461,8 @@
 
     // Forced device usage for communications
     private int mForcedUseForComm;
+    private int mForcedUseForCommExt; // External state returned by getters: always consistent
+                                      // with requests by setters
 
     // List of binder death handlers for setMode() client processes.
     // The last process to have called setMode() is at the top of the list.
@@ -749,6 +751,9 @@
         // relies on audio policy having correct ranges for volume indexes.
         mSafeUsbMediaVolumeIndex = getSafeUsbMediaVolumeIndex();
 
+        mPlaybackMonitor =
+                new PlaybackActivityMonitor(context, MAX_STREAM_VOLUME[AudioSystem.STREAM_ALARM]);
+
         mMediaFocusControl = new MediaFocusControl(mContext, mPlaybackMonitor);
 
         mRecordMonitor = new RecordingActivityMonitor(mContext);
@@ -2544,13 +2549,15 @@
             }
         }
         int status = AudioSystem.AUDIO_STATUS_OK;
+        int actualMode;
         do {
+            actualMode = mode;
             if (mode == AudioSystem.MODE_NORMAL) {
                 // get new mode from client at top the list if any
                 if (!mSetModeDeathHandlers.isEmpty()) {
                     hdlr = mSetModeDeathHandlers.get(0);
                     cb = hdlr.getBinder();
-                    mode = hdlr.getMode();
+                    actualMode = hdlr.getMode();
                     if (DEBUG_MODE) {
                         Log.w(TAG, " using mode=" + mode + " instead due to death hdlr at pid="
                                 + hdlr.mPid);
@@ -2574,12 +2581,11 @@
                 hdlr.setMode(mode);
             }
 
-            if (mode != mMode) {
-                status = AudioSystem.setPhoneState(mode);
+            if (actualMode != mMode) {
+                status = AudioSystem.setPhoneState(actualMode);
                 if (status == AudioSystem.AUDIO_STATUS_OK) {
-                    if (DEBUG_MODE) { Log.v(TAG, " mode successfully set to " + mode); }
-                    mMode = mode;
-                    mModeLogger.log(new PhoneStateEvent(caller, pid, mode));
+                    if (DEBUG_MODE) { Log.v(TAG, " mode successfully set to " + actualMode); }
+                    mMode = actualMode;
                 } else {
                     if (hdlr != null) {
                         mSetModeDeathHandlers.remove(hdlr);
@@ -2595,13 +2601,16 @@
         } while (status != AudioSystem.AUDIO_STATUS_OK && !mSetModeDeathHandlers.isEmpty());
 
         if (status == AudioSystem.AUDIO_STATUS_OK) {
-            if (mode != AudioSystem.MODE_NORMAL) {
+            if (actualMode != AudioSystem.MODE_NORMAL) {
                 if (mSetModeDeathHandlers.isEmpty()) {
                     Log.e(TAG, "setMode() different from MODE_NORMAL with empty mode client stack");
                 } else {
                     newModeOwnerPid = mSetModeDeathHandlers.get(0).getPid();
                 }
             }
+            // Note: newModeOwnerPid is always 0 when actualMode is MODE_NORMAL
+            mModeLogger.log(
+                    new PhoneStateEvent(caller, pid, mode, newModeOwnerPid, actualMode));
             int streamType = getActiveStreamType(AudioManager.USE_DEFAULT_STREAM_TYPE);
             int device = getDeviceForStream(streamType);
             int index = mStreamStates[mStreamVolumeAlias[streamType]].getIndex(device);
@@ -2890,13 +2899,14 @@
             mForcedUseForComm = AudioSystem.FORCE_NONE;
         }
 
+        mForcedUseForCommExt = mForcedUseForComm;
         sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SENDMSG_QUEUE,
                 AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, eventSource, 0);
     }
 
     /** @see AudioManager#isSpeakerphoneOn() */
     public boolean isSpeakerphoneOn() {
-        return (mForcedUseForComm == AudioSystem.FORCE_SPEAKER);
+        return (mForcedUseForCommExt == AudioSystem.FORCE_SPEAKER);
     }
 
     /** @see AudioManager#setBluetoothScoOn(boolean) */
@@ -2904,6 +2914,13 @@
         if (!checkAudioSettingsPermission("setBluetoothScoOn()")) {
             return;
         }
+
+        // Only enable calls from system components
+        if (Binder.getCallingUid() >= FIRST_APPLICATION_UID) {
+            mForcedUseForCommExt = on ? AudioSystem.FORCE_BT_SCO : AudioSystem.FORCE_NONE;
+            return;
+        }
+
         // for logging only
         final String eventSource = new StringBuilder("setBluetoothScoOn(").append(on)
                 .append(") from u/pid:").append(Binder.getCallingUid()).append("/")
@@ -2913,11 +2930,21 @@
 
     public void setBluetoothScoOnInt(boolean on, String eventSource) {
         if (on) {
+            // do not accept SCO ON if SCO audio is not connected
+            synchronized(mScoClients) {
+                if ((mBluetoothHeadset != null) &&
+                    (mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice)
+                             != BluetoothHeadset.STATE_AUDIO_CONNECTED)) {
+                    mForcedUseForCommExt = AudioSystem.FORCE_BT_SCO;
+                    return;
+                }
+            }
             mForcedUseForComm = AudioSystem.FORCE_BT_SCO;
         } else if (mForcedUseForComm == AudioSystem.FORCE_BT_SCO) {
             mForcedUseForComm = AudioSystem.FORCE_NONE;
         }
-
+        mForcedUseForCommExt = mForcedUseForComm;
+        AudioSystem.setParameters("BT_SCO="+ (on ? "on" : "off"));
         sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SENDMSG_QUEUE,
                 AudioSystem.FOR_COMMUNICATION, mForcedUseForComm, eventSource, 0);
         sendMsg(mAudioHandler, MSG_SET_FORCE_USE, SENDMSG_QUEUE,
@@ -2926,7 +2953,7 @@
 
     /** @see AudioManager#isBluetoothScoOn() */
     public boolean isBluetoothScoOn() {
-        return (mForcedUseForComm == AudioSystem.FORCE_BT_SCO);
+        return (mForcedUseForCommExt == AudioSystem.FORCE_BT_SCO);
     }
 
     /** @see AudioManager#setBluetoothA2dpOn(boolean) */
@@ -4134,7 +4161,8 @@
                     newDevice, AudioSystem.getOutputDeviceName(newDevice)));
         }
         synchronized (mConnectedDevices) {
-            if ((newDevice & DEVICE_MEDIA_UNMUTED_ON_PLUG) != 0
+            if (mNm.getZenMode() != Settings.Global.ZEN_MODE_NO_INTERRUPTIONS
+                    && (newDevice & DEVICE_MEDIA_UNMUTED_ON_PLUG) != 0
                     && mStreamStates[AudioSystem.STREAM_MUSIC].mIsMuted
                     && mStreamStates[AudioSystem.STREAM_MUSIC].getIndex(newDevice) != 0
                     && (newDevice & AudioSystem.getDevicesForStream(AudioSystem.STREAM_MUSIC)) != 0)
@@ -6134,12 +6162,12 @@
     private int mSafeMediaVolumeIndex;
     // mSafeUsbMediaVolumeIndex is used for USB Headsets and is the music volume UI index
     // corresponding to a gain of -30 dBFS in audio flinger mixer.
-    // We remove -15 dBs from the theoretical -15dB to account for the EQ boost when bands are set
-    // to max gain.
+    // We remove -22 dBs from the theoretical -15dB to account for the EQ + bass boost
+    // amplification when both effects are on with all band gains at maximum.
     // This level corresponds to a loudness of 85 dB SPL for the warning to be displayed when
     // the headset is compliant to EN 60950 with a max loudness of 100dB SPL.
     private int mSafeUsbMediaVolumeIndex;
-    private static final float SAFE_VOLUME_GAIN_DBFS = -30.0f;
+    private static final float SAFE_VOLUME_GAIN_DBFS = -37.0f;
     // mSafeMediaVolumeDevices lists the devices for which safe media volume is enforced,
     private final int mSafeMediaVolumeDevices = AudioSystem.DEVICE_OUT_WIRED_HEADSET |
                                                 AudioSystem.DEVICE_OUT_WIRED_HEADPHONE |
@@ -6952,7 +6980,7 @@
     //======================
     // Audio playback notification
     //======================
-    private final PlaybackActivityMonitor mPlaybackMonitor = new PlaybackActivityMonitor();
+    private final PlaybackActivityMonitor mPlaybackMonitor;
 
     public void registerPlaybackCallback(IPlaybackConfigDispatcher pcdb) {
         final boolean isPrivileged =
diff --git a/com/android/server/audio/AudioServiceEvents.java b/com/android/server/audio/AudioServiceEvents.java
index 634c8c2..9d9e35b 100644
--- a/com/android/server/audio/AudioServiceEvents.java
+++ b/com/android/server/audio/AudioServiceEvents.java
@@ -26,20 +26,27 @@
 
     final static class PhoneStateEvent extends AudioEventLogger.Event {
         final String mPackage;
-        final int mPid;
-        final int mMode;
+        final int mOwnerPid;
+        final int mRequesterPid;
+        final int mRequestedMode;
+        final int mActualMode;
 
-        PhoneStateEvent(String callingPackage, int pid, int mode) {
+        PhoneStateEvent(String callingPackage, int requesterPid, int requestedMode,
+                        int ownerPid, int actualMode) {
             mPackage = callingPackage;
-            mPid = pid;
-            mMode = mode;
+            mRequesterPid = requesterPid;
+            mRequestedMode = requestedMode;
+            mOwnerPid = ownerPid;
+            mActualMode = actualMode;
         }
 
         @Override
         public String eventToString() {
-            return new StringBuilder("setMode(").append(AudioSystem.modeToString(mMode))
+            return new StringBuilder("setMode(").append(AudioSystem.modeToString(mRequestedMode))
                     .append(") from package=").append(mPackage)
-                    .append(" pid=").append(mPid).toString();
+                    .append(" pid=").append(mRequesterPid)
+                    .append(" selected mode=").append(AudioSystem.modeToString(mActualMode))
+                    .append(" by pid=").append(mOwnerPid).toString();
         }
     }
 
diff --git a/com/android/server/audio/MediaFocusControl.java b/com/android/server/audio/MediaFocusControl.java
index 7d742ff..c5f563c 100644
--- a/com/android/server/audio/MediaFocusControl.java
+++ b/com/android/server/audio/MediaFocusControl.java
@@ -89,6 +89,9 @@
         pw.println("\nMediaFocusControl dump time: "
                 + DateFormat.getTimeInstance().format(new Date()));
         dumpFocusStack(pw);
+        pw.println("\n");
+        // log
+        mEventLogger.dump(pw);
     }
 
     //=================================================================
@@ -120,6 +123,14 @@
     private final static Object mAudioFocusLock = new Object();
 
     /**
+     * Arbitrary maximum size of audio focus stack to prevent apps OOM'ing this process.
+     */
+    private static final int MAX_STACK_SIZE = 100;
+
+    private static final AudioEventLogger mEventLogger = new AudioEventLogger(50,
+            "focus commands as seen by MediaFocusControl");
+
+    /**
      * Discard the current audio focus owner.
      * Notify top of audio focus stack that it lost focus (regardless of possibility to reassign
      * focus), remove it from the stack, and clear the remote control display.
@@ -643,11 +654,14 @@
     protected int requestAudioFocus(AudioAttributes aa, int focusChangeHint, IBinder cb,
             IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags,
             int sdk) {
-        Log.i(TAG, " AudioFocus  requestAudioFocus() from uid/pid " + Binder.getCallingUid()
-                + "/" + Binder.getCallingPid()
-                + " clientId=" + clientId
-                + " req=" + focusChangeHint
-                + " flags=0x" + Integer.toHexString(flags));
+        mEventLogger.log((new AudioEventLogger.StringEvent(
+                "requestAudioFocus() from uid/pid " + Binder.getCallingUid()
+                    + "/" + Binder.getCallingPid()
+                    + " clientId=" + clientId + " callingPack=" + callingPackageName
+                    + " req=" + focusChangeHint
+                    + " flags=0x" + Integer.toHexString(flags)
+                    + " sdk=" + sdk))
+                .printLog(TAG));
         // we need a valid binder callback for clients
         if (!cb.pingBinder()) {
             Log.e(TAG, " AudioFocus DOA client for requestAudioFocus(), aborting.");
@@ -660,6 +674,11 @@
         }
 
         synchronized(mAudioFocusLock) {
+            if (mFocusStack.size() > MAX_STACK_SIZE) {
+                Log.e(TAG, "Max AudioFocus stack size reached, failing requestAudioFocus()");
+                return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
+            }
+
             boolean enteringRingOrCall = !mRingOrCallActive
                     & (AudioSystem.IN_VOICE_COMM_FOCUS_ID.compareTo(clientId) == 0);
             if (enteringRingOrCall) { mRingOrCallActive = true; }
@@ -770,10 +789,12 @@
      * */
     protected int abandonAudioFocus(IAudioFocusDispatcher fl, String clientId, AudioAttributes aa,
             String callingPackageName) {
-        // AudioAttributes are currently ignored, to be used for zones
-        Log.i(TAG, " AudioFocus  abandonAudioFocus() from uid/pid " + Binder.getCallingUid()
-                + "/" + Binder.getCallingPid()
-                + " clientId=" + clientId);
+        // AudioAttributes are currently ignored, to be used for zones / a11y
+        mEventLogger.log((new AudioEventLogger.StringEvent(
+                "abandonAudioFocus() from uid/pid " + Binder.getCallingUid()
+                    + "/" + Binder.getCallingPid()
+                    + " clientId=" + clientId))
+                .printLog(TAG));
         try {
             // this will take care of notifying the new focus owner if needed
             synchronized(mAudioFocusLock) {
diff --git a/com/android/server/audio/PlaybackActivityMonitor.java b/com/android/server/audio/PlaybackActivityMonitor.java
index d1a37af..6506cf7 100644
--- a/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/com/android/server/audio/PlaybackActivityMonitor.java
@@ -17,6 +17,8 @@
 package com.android.server.audio;
 
 import android.annotation.NonNull;
+import android.content.Context;
+import android.content.pm.PackageManager;
 import android.media.AudioAttributes;
 import android.media.AudioManager;
 import android.media.AudioPlaybackConfiguration;
@@ -90,7 +92,14 @@
     private final HashMap<Integer, AudioPlaybackConfiguration> mPlayers =
             new HashMap<Integer, AudioPlaybackConfiguration>();
 
-    PlaybackActivityMonitor() {
+    private final Context mContext;
+    private int mSavedAlarmVolume = -1;
+    private final int mMaxAlarmVolume;
+    private int mPrivilegedAlarmActiveCount = 0;
+
+    PlaybackActivityMonitor(Context context, int maxAlarmVolume) {
+        mContext = context;
+        mMaxAlarmVolume = maxAlarmVolume;
         PlayMonitorClient.sListenerDeathMonitor = this;
         AudioPlaybackConfiguration.sPlayerDeathMonitor = this;
     }
@@ -105,7 +114,7 @@
             if (index >= 0) {
                 if (!disable) {
                     if (DEBUG) { // hidden behind DEBUG, too noisy otherwise
-                        mEventLogger.log(new AudioEventLogger.StringEvent("unbanning uid:" + uid));
+                        sEventLogger.log(new AudioEventLogger.StringEvent("unbanning uid:" + uid));
                     }
                     mBannedUids.remove(index);
                     // nothing else to do, future playback requests from this uid are ok
@@ -116,7 +125,7 @@
                         checkBanPlayer(apc, uid);
                     }
                     if (DEBUG) { // hidden behind DEBUG, too noisy otherwise
-                        mEventLogger.log(new AudioEventLogger.StringEvent("banning uid:" + uid));
+                        sEventLogger.log(new AudioEventLogger.StringEvent("banning uid:" + uid));
                     }
                     mBannedUids.add(new Integer(uid));
                 } // no else to handle, uid already not in list, so enabling again is no-op
@@ -151,7 +160,7 @@
                 new AudioPlaybackConfiguration(pic, newPiid,
                         Binder.getCallingUid(), Binder.getCallingPid());
         apc.init();
-        mEventLogger.log(new NewPlayerEvent(apc));
+        sEventLogger.log(new NewPlayerEvent(apc));
         synchronized(mPlayerLock) {
             mPlayers.put(newPiid, apc);
         }
@@ -163,7 +172,7 @@
         synchronized(mPlayerLock) {
             final AudioPlaybackConfiguration apc = mPlayers.get(new Integer(piid));
             if (checkConfigurationCaller(piid, apc, binderUid)) {
-                mEventLogger.log(new AudioAttrEvent(piid, attr));
+                sEventLogger.log(new AudioAttrEvent(piid, attr));
                 change = apc.handleAudioAttributesEvent(attr);
             } else {
                 Log.e(TAG, "Error updating audio attributes");
@@ -175,6 +184,38 @@
         }
     }
 
+    private void checkVolumeForPrivilegedAlarm(AudioPlaybackConfiguration apc, int event) {
+        if (event == AudioPlaybackConfiguration.PLAYER_STATE_STARTED ||
+                apc.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
+            if ((apc.getAudioAttributes().getAllFlags() &
+                    AudioAttributes.FLAG_BYPASS_INTERRUPTION_POLICY) != 0 &&
+                    apc.getAudioAttributes().getUsage() == AudioAttributes.USAGE_ALARM &&
+                    mContext.checkPermission(android.Manifest.permission.MODIFY_PHONE_STATE,
+                            apc.getClientPid(), apc.getClientUid()) ==
+                            PackageManager.PERMISSION_GRANTED) {
+                if (event == AudioPlaybackConfiguration.PLAYER_STATE_STARTED &&
+                        apc.getPlayerState() != AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
+                    if (mPrivilegedAlarmActiveCount++ == 0) {
+                        mSavedAlarmVolume = AudioSystem.getStreamVolumeIndex(
+                                AudioSystem.STREAM_ALARM, AudioSystem.DEVICE_OUT_SPEAKER);
+                        AudioSystem.setStreamVolumeIndex(AudioSystem.STREAM_ALARM,
+                                mMaxAlarmVolume, AudioSystem.DEVICE_OUT_SPEAKER);
+                    }
+                } else if (event != AudioPlaybackConfiguration.PLAYER_STATE_STARTED &&
+                        apc.getPlayerState() == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
+                    if (--mPrivilegedAlarmActiveCount == 0) {
+                        if (AudioSystem.getStreamVolumeIndex(
+                                AudioSystem.STREAM_ALARM, AudioSystem.DEVICE_OUT_SPEAKER) ==
+                                mMaxAlarmVolume) {
+                            AudioSystem.setStreamVolumeIndex(AudioSystem.STREAM_ALARM,
+                                    mSavedAlarmVolume, AudioSystem.DEVICE_OUT_SPEAKER);
+                        }
+                    }
+                }
+            }
+        }
+    }
+
     public void playerEvent(int piid, int event, int binderUid) {
         if (DEBUG) { Log.v(TAG, String.format("playerEvent(piid=%d, event=%d)", piid, event)); }
         final boolean change;
@@ -183,12 +224,12 @@
             if (apc == null) {
                 return;
             }
-            mEventLogger.log(new PlayerEvent(piid, event));
+            sEventLogger.log(new PlayerEvent(piid, event));
             if (event == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
                 for (Integer uidInteger: mBannedUids) {
                     if (checkBanPlayer(apc, uidInteger.intValue())) {
                         // player was banned, do not update its state
-                        mEventLogger.log(new AudioEventLogger.StringEvent(
+                        sEventLogger.log(new AudioEventLogger.StringEvent(
                                 "not starting piid:" + piid + " ,is banned"));
                         return;
                     }
@@ -200,6 +241,7 @@
             }
             if (checkConfigurationCaller(piid, apc, binderUid)) {
                 //TODO add generation counter to only update to the latest state
+                checkVolumeForPrivilegedAlarm(apc, event);
                 change = apc.handleStateEvent(event);
             } else {
                 Log.e(TAG, "Error handling event " + event);
@@ -216,7 +258,7 @@
 
     public void playerHasOpPlayAudio(int piid, boolean hasOpPlayAudio, int binderUid) {
         // no check on UID yet because this is only for logging at the moment
-        mEventLogger.log(new PlayerOpPlayAudioEvent(piid, hasOpPlayAudio, binderUid));
+        sEventLogger.log(new PlayerOpPlayAudioEvent(piid, hasOpPlayAudio, binderUid));
     }
 
     public void releasePlayer(int piid, int binderUid) {
@@ -224,10 +266,11 @@
         synchronized(mPlayerLock) {
             final AudioPlaybackConfiguration apc = mPlayers.get(new Integer(piid));
             if (checkConfigurationCaller(piid, apc, binderUid)) {
-                mEventLogger.log(new AudioEventLogger.StringEvent(
+                sEventLogger.log(new AudioEventLogger.StringEvent(
                         "releasing player piid:" + piid));
                 mPlayers.remove(new Integer(piid));
                 mDuckingManager.removeReleased(apc);
+                checkVolumeForPrivilegedAlarm(apc, AudioPlaybackConfiguration.PLAYER_STATE_RELEASED);
                 apc.handleStateEvent(AudioPlaybackConfiguration.PLAYER_STATE_RELEASED);
             }
         }
@@ -278,7 +321,7 @@
             }
             pw.println("\n");
             // log
-            mEventLogger.dump(pw);
+            sEventLogger.dump(pw);
         }
     }
 
@@ -456,7 +499,8 @@
                 }
                 if (mute) {
                     try {
-                        Log.v(TAG, "call: muting player" + piid + " uid:" + apc.getClientUid());
+                        sEventLogger.log((new AudioEventLogger.StringEvent("call: muting piid:"
+                                + piid + " uid:" + apc.getClientUid())).printLog(TAG));
                         apc.getPlayerProxy().setVolume(0.0f);
                         mMutedPlayers.add(new Integer(piid));
                     } catch (Exception e) {
@@ -480,7 +524,8 @@
                 final AudioPlaybackConfiguration apc = mPlayers.get(piid);
                 if (apc != null) {
                     try {
-                        Log.v(TAG, "call: unmuting player" + piid + " uid:" + apc.getClientUid());
+                        sEventLogger.log(new AudioEventLogger.StringEvent("call: unmuting piid:"
+                                + piid).printLog(TAG));
                         apc.getPlayerProxy().setVolume(1.0f);
                     } catch (Exception e) {
                         Log.e(TAG, "call: error unmuting player " + piid + " uid:"
@@ -669,8 +714,7 @@
                     return;
                 }
                 try {
-                    Log.v(TAG, "ducking (skipRamp=" + skipRamp + ") player piid:"
-                            + apc.getPlayerInterfaceId() + " uid:" + mUid);
+                    sEventLogger.log((new DuckEvent(apc, skipRamp)).printLog(TAG));
                     apc.getPlayerProxy().applyVolumeShaper(
                             DUCK_VSHAPE,
                             skipRamp ? PLAY_SKIP_RAMP : PLAY_CREATE_IF_NEEDED);
@@ -685,7 +729,8 @@
                     final AudioPlaybackConfiguration apc = players.get(piid);
                     if (apc != null) {
                         try {
-                            Log.v(TAG, "unducking player " + piid + " uid:" + mUid);
+                            sEventLogger.log((new AudioEventLogger.StringEvent("unducking piid:"
+                                    + piid)).printLog(TAG));
                             apc.getPlayerProxy().applyVolumeShaper(
                                     DUCK_ID,
                                     VolumeShaper.Operation.REVERSE);
@@ -772,7 +817,28 @@
         }
     }
 
-    private final static class AudioAttrEvent extends AudioEventLogger.Event {
+    private static final class DuckEvent extends AudioEventLogger.Event {
+        private final int mPlayerIId;
+        private final boolean mSkipRamp;
+        private final int mClientUid;
+        private final int mClientPid;
+
+        DuckEvent(@NonNull AudioPlaybackConfiguration apc, boolean skipRamp) {
+            mPlayerIId = apc.getPlayerInterfaceId();
+            mSkipRamp = skipRamp;
+            mClientUid = apc.getClientUid();
+            mClientPid = apc.getClientPid();
+        }
+
+        @Override
+        public String eventToString() {
+            return new StringBuilder("ducking player piid:").append(mPlayerIId)
+                    .append(" uid/pid:").append(mClientUid).append("/").append(mClientPid)
+                    .append(" skip ramp:").append(mSkipRamp).toString();
+        }
+    }
+
+    private static final class AudioAttrEvent extends AudioEventLogger.Event {
         private final int mPlayerIId;
         private final AudioAttributes mPlayerAttr;
 
@@ -787,6 +853,6 @@
         }
     }
 
-    private final AudioEventLogger mEventLogger = new AudioEventLogger(100,
+    private static final AudioEventLogger sEventLogger = new AudioEventLogger(100,
             "playback activity as reported through PlayerBase");
 }
diff --git a/com/android/server/autofill/AutofillManagerService.java b/com/android/server/autofill/AutofillManagerService.java
index ddc819d..1f4161a 100644
--- a/com/android/server/autofill/AutofillManagerService.java
+++ b/com/android/server/autofill/AutofillManagerService.java
@@ -115,11 +115,24 @@
     private final SparseBooleanArray mDisabledUsers = new SparseBooleanArray();
 
     private final LocalLog mRequestsHistory = new LocalLog(20);
+    private final LocalLog mUiLatencyHistory = new LocalLog(20);
 
     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
             if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(intent.getAction())) {
+                if (sDebug) Slog.d(TAG, "Close system dialogs");
+
+                // TODO(b/64940307): we need to destroy all sessions that are finished but showing
+                // Save UI because there is no way to show the Save UI back when the activity
+                // beneath it is brought back to top. Ideally, we should just hide the UI and
+                // bring it back when the activity resumes.
+                synchronized (mLock) {
+                    for (int i = 0; i < mServicesCache.size(); i++) {
+                        mServicesCache.valueAt(i).destroyFinishedSessionsLocked();
+                    }
+                }
+
                 mUi.hideAll(null);
             }
         }
@@ -294,7 +307,7 @@
         AutofillManagerServiceImpl service = mServicesCache.get(resolvedUserId);
         if (service == null) {
             service = new AutofillManagerServiceImpl(mContext, mLock, mRequestsHistory,
-                    resolvedUserId, mUi, mDisabledUsers.get(resolvedUserId));
+                    mUiLatencyHistory, resolvedUserId, mUi, mDisabledUsers.get(resolvedUserId));
             mServicesCache.put(userId, service);
         }
         return service;
@@ -650,7 +663,7 @@
             synchronized (mLock) {
                 final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
                 if (service == null) return false;
-                return Objects.equals(packageName, service.getPackageName());
+                return Objects.equals(packageName, service.getServicePackageName());
             }
         }
 
@@ -724,6 +737,8 @@
                 if (showHistory) {
                     pw.println("Requests history:");
                     mRequestsHistory.reverseDump(fd, pw, args);
+                    pw.println("UI latency history:");
+                    mUiLatencyHistory.reverseDump(fd, pw, args);
                 }
             } finally {
                 setDebugLocked(oldDebug);
diff --git a/com/android/server/autofill/AutofillManagerServiceImpl.java b/com/android/server/autofill/AutofillManagerServiceImpl.java
index f2f96f8..862070a 100644
--- a/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -35,6 +35,7 @@
 import android.content.pm.ServiceInfo;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
+import android.metrics.LogMaker;
 import android.os.AsyncTask;
 import android.os.Binder;
 import android.os.Bundle;
@@ -63,6 +64,8 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.internal.os.HandlerCaller;
 import com.android.server.autofill.ui.AutoFillUI;
 
@@ -89,6 +92,7 @@
     private final Context mContext;
     private final Object mLock;
     private final AutoFillUI mUi;
+    private final MetricsLogger mMetricsLogger = new MetricsLogger();
 
     private RemoteCallbackList<IAutoFillManagerClient> mClients;
     private AutofillServiceInfo mInfo;
@@ -96,6 +100,8 @@
     private static final Random sRandom = new Random();
 
     private final LocalLog mRequestsHistory;
+    private final LocalLog mUiLatencyHistory;
+
     /**
      * Whether service was disabled for user due to {@link UserManager} restrictions.
      */
@@ -137,17 +143,19 @@
     private long mLastPrune = 0;
 
     AutofillManagerServiceImpl(Context context, Object lock, LocalLog requestsHistory,
-            int userId, AutoFillUI ui, boolean disabled) {
+            LocalLog uiLatencyHistory, int userId, AutoFillUI ui, boolean disabled) {
         mContext = context;
         mLock = lock;
         mRequestsHistory = requestsHistory;
+        mUiLatencyHistory = uiLatencyHistory;
         mUserId = userId;
         mUi = ui;
         updateLocked(disabled);
     }
 
+    @Nullable
     CharSequence getServiceName() {
-        final String packageName = getPackageName();
+        final String packageName = getServicePackageName();
         if (packageName == null) {
             return null;
         }
@@ -162,7 +170,8 @@
         }
     }
 
-    String getPackageName() {
+    @Nullable
+    String getServicePackageName() {
         final ComponentName serviceComponent = getServiceComponentName();
         if (serviceComponent != null) {
             return serviceComponent.getPackageName();
@@ -216,8 +225,10 @@
             if (serviceInfo != null) {
                 mInfo = new AutofillServiceInfo(mContext.getPackageManager(),
                         serviceComponent, mUserId);
+                if (sDebug) Slog.d(TAG, "Set component for user " + mUserId + " as " + mInfo);
             } else {
                 mInfo = null;
+                if (sDebug) Slog.d(TAG, "Reset component for user " + mUserId);
             }
             final boolean isEnabled = isEnabled();
             if (wasEnabled != isEnabled) {
@@ -343,17 +354,31 @@
     }
 
     void disableOwnedAutofillServicesLocked(int uid) {
-        if (mInfo == null || mInfo.getServiceInfo().applicationInfo.uid != uid) {
+        Slog.i(TAG, "disableOwnedServices(" + uid + "): " + mInfo);
+        if (mInfo == null) return;
+
+        final ServiceInfo serviceInfo = mInfo.getServiceInfo();
+        if (serviceInfo.applicationInfo.uid != uid) {
+            Slog.w(TAG, "disableOwnedServices(): ignored when called by UID " + uid
+                    + " instead of " + serviceInfo.applicationInfo.uid
+                    + " for service " + mInfo);
             return;
         }
+
+
         final long identity = Binder.clearCallingIdentity();
         try {
             final String autoFillService = getComponentNameFromSettings();
-            if (mInfo.getServiceInfo().getComponentName().equals(
-                    ComponentName.unflattenFromString(autoFillService))) {
+            final ComponentName componentName = serviceInfo.getComponentName();
+            if (componentName.equals(ComponentName.unflattenFromString(autoFillService))) {
+                mMetricsLogger.action(MetricsEvent.AUTOFILL_SERVICE_DISABLED_SELF,
+                        componentName.getPackageName());
                 Settings.Secure.putStringForUser(mContext.getContentResolver(),
                         Settings.Secure.AUTOFILL_SERVICE, null, mUserId);
                 destroySessionsLocked();
+            } else {
+                Slog.w(TAG, "disableOwnedServices(): ignored because current service ("
+                        + serviceInfo + ") does not match Settings (" + autoFillService + ")");
             }
         } finally {
             Binder.restoreCallingIdentity(identity);
@@ -377,7 +402,7 @@
 
         final Session newSession = new Session(this, mUi, mContext, mHandlerCaller, mUserId, mLock,
                 sessionId, uid, activityToken, appCallbackToken, hasCallback,
-                mInfo.getServiceInfo().getComponentName(), packageName);
+                mUiLatencyHistory, mInfo.getServiceInfo().getComponentName(), packageName);
         mSessions.put(newSession.id, newSession);
 
         return newSession;
@@ -450,7 +475,7 @@
             final int sessionCount = mSessions.size();
             for (int i = sessionCount - 1; i >= 0; i--) {
                 final Session session = mSessions.valueAt(i);
-                if (session.isSaveUiPendingForToken(token)) {
+                if (session.isSaveUiPendingForTokenLocked(token)) {
                     session.onPendingSaveUi(operation, token);
                     return;
                 }
@@ -636,7 +661,7 @@
 
     void destroySessionsLocked() {
         if (mSessions.size() == 0) {
-            mUi.destroyAll(null, null);
+            mUi.destroyAll(null, null, false);
             return;
         }
         while (mSessions.size() > 0) {
@@ -644,6 +669,18 @@
         }
     }
 
+    // TODO(b/64940307): remove this method if SaveUI is refactored to be attached on activities
+    void destroyFinishedSessionsLocked() {
+        final int sessionCount = mSessions.size();
+        for (int i = sessionCount - 1; i >= 0; i--) {
+            final Session session = mSessions.valueAt(i);
+            if (session.isSavingLocked()) {
+                if (sDebug) Slog.d(TAG, "destroyFinishedSessionsLocked(): " + session.id);
+                session.forceRemoveSelfLocked();
+            }
+        }
+    }
+
     void listSessionsLocked(ArrayList<String> output) {
         final int numSessions = mSessions.size();
         for (int i = 0; i < numSessions; i++) {
diff --git a/com/android/server/autofill/Helper.java b/com/android/server/autofill/Helper.java
index 086dd29..236fbfd 100644
--- a/com/android/server/autofill/Helper.java
+++ b/com/android/server/autofill/Helper.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.metrics.LogMaker;
 import android.os.Bundle;
 import android.service.autofill.Dataset;
 import android.util.ArrayMap;
@@ -25,6 +26,8 @@
 import android.view.autofill.AutofillId;
 import android.view.autofill.AutofillValue;
 
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Objects;
@@ -99,4 +102,14 @@
         }
         return fields;
     }
+
+    @NonNull
+    public static LogMaker newLogMaker(int category, String packageName,
+            String servicePackageName) {
+        final LogMaker log = new LogMaker(category).setPackageName(packageName);
+        if (servicePackageName != null) {
+            log.addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE, servicePackageName);
+        }
+        return log;
+    }
 }
diff --git a/com/android/server/autofill/RemoteFillService.java b/com/android/server/autofill/RemoteFillService.java
index f315148..af55807 100644
--- a/com/android/server/autofill/RemoteFillService.java
+++ b/com/android/server/autofill/RemoteFillService.java
@@ -578,9 +578,8 @@
         public void run() {
             synchronized (mLock) {
                 if (isCancelledLocked()) {
-                    // TODO(b/653742740): we should probably return here, but for now we're justing
-                    // logging to confirm this is the problem if it happens again.
-                    Slog.e(LOG_TAG, "run() called after canceled: " + mRequest);
+                    if (sDebug) Slog.d(LOG_TAG, "run() called after canceled: " + mRequest);
+                    return;
                 }
             }
             final RemoteFillService remoteService = getService();
diff --git a/com/android/server/autofill/Session.java b/com/android/server/autofill/Session.java
index 171053f..ed00ffe 100644
--- a/com/android/server/autofill/Session.java
+++ b/com/android/server/autofill/Session.java
@@ -50,19 +50,24 @@
 import android.os.IBinder;
 import android.os.Parcelable;
 import android.os.RemoteException;
+import android.os.SystemClock;
 import android.service.autofill.AutofillService;
 import android.service.autofill.Dataset;
 import android.service.autofill.FillContext;
 import android.service.autofill.FillRequest;
 import android.service.autofill.FillResponse;
+import android.service.autofill.InternalSanitizer;
 import android.service.autofill.InternalValidator;
 import android.service.autofill.SaveInfo;
 import android.service.autofill.SaveRequest;
+import android.service.autofill.Transformation;
 import android.service.autofill.ValueFinder;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.LocalLog;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.TimeUtils;
 import android.view.autofill.AutofillId;
 import android.view.autofill.AutofillManager;
 import android.view.autofill.AutofillValue;
@@ -183,6 +188,20 @@
     private ArrayList<String> mSelectedDatasetIds;
 
     /**
+     * When the session started (using elapsed time since boot).
+     */
+    private final long mStartTime;
+
+    /**
+     * When the UI was shown for the first time (using elapsed time since boot).
+     */
+    @GuardedBy("mLock")
+    private long mUiShownTime;
+
+    @GuardedBy("mLock")
+    private final LocalLog mUiLatencyHistory;
+
+    /**
      * Receiver of assist data from the app's {@link Activity}.
      */
     private final IResultReceiver mAssistReceiver = new IResultReceiver.Stub() {
@@ -403,10 +422,11 @@
     Session(@NonNull AutofillManagerServiceImpl service, @NonNull AutoFillUI ui,
             @NonNull Context context, @NonNull HandlerCaller handlerCaller, int userId,
             @NonNull Object lock, int sessionId, int uid, @NonNull IBinder activityToken,
-            @NonNull IBinder client, boolean hasCallback,
+            @NonNull IBinder client, boolean hasCallback, @NonNull LocalLog uiLatencyHistory,
             @NonNull ComponentName componentName, @NonNull String packageName) {
         id = sessionId;
         this.uid = uid;
+        mStartTime = SystemClock.elapsedRealtime();
         mService = service;
         mLock = lock;
         mUi = ui;
@@ -414,10 +434,11 @@
         mRemoteFillService = new RemoteFillService(context, componentName, userId, this);
         mActivityToken = activityToken;
         mHasCallback = hasCallback;
+        mUiLatencyHistory = uiLatencyHistory;
         mPackageName = packageName;
         mClient = IAutoFillManagerClient.Stub.asInterface(client);
 
-        mMetricsLogger.action(MetricsEvent.AUTOFILL_SESSION_STARTED, mPackageName);
+        writeLog(MetricsEvent.AUTOFILL_SESSION_STARTED);
     }
 
     /**
@@ -471,19 +492,16 @@
         if ((response.getDatasets() == null || response.getDatasets().isEmpty())
                         && response.getAuthentication() == null) {
             // Response is "empty" from an UI point of view, need to notify client.
-            notifyUnavailableToClient();
+            notifyUnavailableToClient(false);
         }
         synchronized (mLock) {
             processResponseLocked(response, requestFlags);
         }
 
-        final LogMaker log = (new LogMaker(MetricsEvent.AUTOFILL_REQUEST))
+        final LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_REQUEST, servicePackageName)
                 .setType(MetricsEvent.TYPE_SUCCESS)
-                .setPackageName(mPackageName)
                 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS,
-                        response.getDatasets() == null ? 0 : response.getDatasets().size())
-                .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE,
-                        servicePackageName);
+                        response.getDatasets() == null ? 0 : response.getDatasets().size());
         mMetricsLogger.write(log);
     }
 
@@ -499,10 +517,8 @@
             }
             mService.resetLastResponse();
         }
-        LogMaker log = (new LogMaker(MetricsEvent.AUTOFILL_REQUEST))
-                .setType(MetricsEvent.TYPE_FAILURE)
-                .setPackageName(mPackageName)
-                .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE, servicePackageName);
+        LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_REQUEST, servicePackageName)
+                .setType(MetricsEvent.TYPE_FAILURE);
         mMetricsLogger.write(log);
 
         getUiForShowing().showError(message, this);
@@ -521,11 +537,8 @@
                 return;
             }
         }
-        LogMaker log = (new LogMaker(
-                MetricsEvent.AUTOFILL_DATA_SAVE_REQUEST))
-                .setType(MetricsEvent.TYPE_SUCCESS)
-                .setPackageName(mPackageName)
-                .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE, servicePackageName);
+        LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_DATA_SAVE_REQUEST, servicePackageName)
+                .setType(MetricsEvent.TYPE_SUCCESS);
         mMetricsLogger.write(log);
 
         // Nothing left to do...
@@ -545,11 +558,8 @@
                 return;
             }
         }
-        LogMaker log = (new LogMaker(
-                MetricsEvent.AUTOFILL_DATA_SAVE_REQUEST))
-                .setType(MetricsEvent.TYPE_FAILURE)
-                .setPackageName(mPackageName)
-                .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE, servicePackageName);
+        LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_DATA_SAVE_REQUEST, servicePackageName)
+                .setType(MetricsEvent.TYPE_FAILURE);
         mMetricsLogger.write(log);
 
         getUiForShowing().showError(message, this);
@@ -583,6 +593,10 @@
     // FillServiceCallbacks
     @Override
     public void authenticate(int requestId, int datasetIndex, IntentSender intent, Bundle extras) {
+        if (sDebug) {
+            Slog.d(TAG, "authenticate(): requestId=" + requestId + "; datasetIdx=" + datasetIndex
+                    + "; intentSender=" + intent);
+        }
         final Intent fillInIntent;
         synchronized (mLock) {
             if (mDestroyed) {
@@ -591,6 +605,10 @@
                 return;
             }
             fillInIntent = createAuthFillInIntentLocked(requestId, extras);
+            if (fillInIntent == null) {
+                forceRemoveSelfLocked();
+                return;
+            }
         }
 
         mService.setAuthenticationSelected(id, mClientState);
@@ -746,21 +764,22 @@
         final Parcelable result = data.getParcelable(AutofillManager.EXTRA_AUTHENTICATION_RESULT);
         if (sDebug) Slog.d(TAG, "setAuthenticationResultLocked(): result=" + result);
         if (result instanceof FillResponse) {
-            final FillResponse response = (FillResponse) result;
-            mMetricsLogger.action(MetricsEvent.AUTOFILL_AUTHENTICATED, mPackageName);
-            replaceResponseLocked(authenticatedResponse, response);
+            writeLog(MetricsEvent.AUTOFILL_AUTHENTICATED);
+            replaceResponseLocked(authenticatedResponse, (FillResponse) result);
         } else if (result instanceof Dataset) {
-            // TODO: add proper metric
             if (datasetIdx != AutofillManager.AUTHENTICATION_ID_DATASET_ID_UNDEFINED) {
+                writeLog(MetricsEvent.AUTOFILL_DATASET_AUTHENTICATED);
                 final Dataset dataset = (Dataset) result;
                 authenticatedResponse.getDatasets().set(datasetIdx, dataset);
                 autoFill(requestId, datasetIdx, dataset, false);
+            } else {
+                writeLog(MetricsEvent.AUTOFILL_INVALID_DATASET_AUTHENTICATION);
             }
         } else {
             if (result != null) {
                 Slog.w(TAG, "service returned invalid auth type: " + result);
             }
-            // TODO: add proper metric (on else)
+            writeLog(MetricsEvent.AUTOFILL_INVALID_AUTHENTICATION);
             processNullResponseLocked(0);
         }
     }
@@ -774,6 +793,44 @@
         mHasCallback = hasIt;
     }
 
+    @Nullable
+    private FillResponse getLastResponseLocked(@Nullable String logPrefix) {
+        if (mContexts == null) {
+            if (sDebug && logPrefix != null) Slog.d(TAG, logPrefix + ": no contexts");
+            return null;
+        }
+        if (mResponses == null) {
+            // Happens when the activity / session was finished before the service replied, or
+            // when the service cannot autofill it (and returned a null response).
+            if (sVerbose && logPrefix != null) {
+                Slog.v(TAG, logPrefix + ": no responses on session");
+            }
+            return null;
+        }
+
+        final int lastResponseIdx = getLastResponseIndexLocked();
+        if (lastResponseIdx < 0) {
+            if (logPrefix != null) {
+                Slog.w(TAG, logPrefix + ": did not get last response. mResponses=" + mResponses
+                        + ", mViewStates=" + mViewStates);
+            }
+            return null;
+        }
+
+        final FillResponse response = mResponses.valueAt(lastResponseIdx);
+        if (sVerbose && logPrefix != null) {
+            Slog.v(TAG, logPrefix + ": mResponses=" + mResponses + ", mContexts=" + mContexts
+                    + ", mViewStates=" + mViewStates);
+        }
+        return response;
+    }
+
+    @Nullable
+    private SaveInfo getSaveInfoLocked() {
+        final FillResponse response = getLastResponseLocked(null);
+        return response == null ? null : response.getSaveInfo();
+    }
+
     /**
      * Shows the save UI, when session can be saved.
      *
@@ -785,32 +842,8 @@
                     + id + " destroyed");
             return false;
         }
-        if (mContexts == null) {
-            Slog.d(TAG, "showSaveLocked(): no contexts");
-            return true;
-        }
-        if (mResponses == null) {
-            // Happens when the activity / session was finished before the service replied, or
-            // when the service cannot autofill it (and returned a null response).
-            if (sVerbose) {
-                Slog.v(TAG, "showSaveLocked(): no responses on session");
-            }
-            return true;
-        }
-
-        final int lastResponseIdx = getLastResponseIndexLocked();
-        if (lastResponseIdx < 0) {
-            Slog.w(TAG, "showSaveLocked(): did not get last response. mResponses=" + mResponses
-                    + ", mViewStates=" + mViewStates);
-            return true;
-        }
-
-        final FillResponse response = mResponses.valueAt(lastResponseIdx);
-        final SaveInfo saveInfo = response.getSaveInfo();
-        if (sVerbose) {
-            Slog.v(TAG, "showSaveLocked(): mResponses=" + mResponses + ", mContexts=" + mContexts
-                    + ", mViewStates=" + mViewStates);
-        }
+        final FillResponse response = getLastResponseLocked("showSaveLocked()");
+        final SaveInfo saveInfo = response == null ? null : response.getSaveInfo();
 
         /*
          * The Save dialog is only shown if all conditions below are met:
@@ -825,6 +858,8 @@
             return true;
         }
 
+        final ArrayMap<AutofillId, InternalSanitizer> sanitizers = createSanitizers(saveInfo);
+
         // Cache used to make sure changed fields do not belong to a dataset.
         final ArrayMap<AutofillId, AutofillValue> currentValues = new ArrayMap<>();
         final ArraySet<AutofillId> allIds = new ArraySet<>();
@@ -864,6 +899,7 @@
                         break;
                     }
                 }
+                value = getSanitizedValue(sanitizers, id, value);
                 currentValues.put(id, value);
                 final AutofillValue filledValue = viewState.getAutofilledValue();
 
@@ -922,15 +958,21 @@
 
                 final InternalValidator validator = saveInfo.getValidator();
                 if (validator != null) {
+                    final LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_SAVE_VALIDATION);
                     boolean isValid;
                     try {
                         isValid = validator.isValid(valueFinder);
+                        log.setType(isValid
+                                ? MetricsEvent.TYPE_SUCCESS
+                                : MetricsEvent.TYPE_DISMISS);
                     } catch (Exception e) {
-                        Slog.e(TAG, "Not showing save UI because of exception during validation "
-                                + e.getClass());
+                        Slog.e(TAG, "Not showing save UI because validation failed:", e);
+                        log.setType(MetricsEvent.TYPE_FAILURE);
+                        mMetricsLogger.write(log);
                         return true;
                     }
 
+                    mMetricsLogger.write(log);
                     if (!isValid) {
                         Slog.i(TAG, "not showing save UI because fields failed validation");
                         return true;
@@ -978,7 +1020,8 @@
                 final IAutoFillManagerClient client = getClient();
                 mPendingSaveUi = new PendingUi(mActivityToken, id, client);
                 getUiForShowing().showSaveUi(mService.getServiceLabel(), mService.getServiceIcon(),
-                        saveInfo, valueFinder, mPackageName, this, mPendingSaveUi);
+                        mService.getServicePackageName(), saveInfo, valueFinder, mPackageName, this,
+                        mPendingSaveUi);
                 if (client != null) {
                     try {
                         client.setSaveUiState(id, true);
@@ -999,6 +1042,48 @@
         return true;
     }
 
+    @Nullable
+    private ArrayMap<AutofillId, InternalSanitizer> createSanitizers(@Nullable SaveInfo saveInfo) {
+        if (saveInfo == null) return null;
+
+        final InternalSanitizer[] sanitizerKeys = saveInfo.getSanitizerKeys();
+        if (sanitizerKeys == null) return null;
+
+        final int size = sanitizerKeys.length ;
+        final ArrayMap<AutofillId, InternalSanitizer> sanitizers = new ArrayMap<>(size);
+        if (sDebug) Slog.d(TAG, "Service provided " + size + " sanitizers");
+        final AutofillId[][] sanitizerValues = saveInfo.getSanitizerValues();
+        for (int i = 0; i < size; i++) {
+            final InternalSanitizer sanitizer = sanitizerKeys[i];
+            final AutofillId[] ids = sanitizerValues[i];
+            if (sDebug) {
+                Slog.d(TAG, "sanitizer #" + i + " (" + sanitizer + ") for ids "
+                        + Arrays.toString(ids));
+            }
+            for (AutofillId id : ids) {
+                sanitizers.put(id, sanitizer);
+            }
+        }
+        return sanitizers;
+    }
+
+    @NonNull
+    private AutofillValue getSanitizedValue(
+            @Nullable ArrayMap<AutofillId, InternalSanitizer> sanitizers,
+            @NonNull AutofillId id,
+            @NonNull AutofillValue value) {
+        if (sanitizers == null) return value;
+
+        final InternalSanitizer sanitizer = sanitizers.get(id);
+        if (sanitizer == null) {
+            return value;
+        }
+
+        final AutofillValue sanitized = sanitizer.sanitize(value);
+        if (sDebug) Slog.d(TAG, "Value for " + id + "(" + value + ") sanitized to " + sanitized);
+        return sanitized;
+    }
+
     /**
      * Returns whether the session is currently showing the save UI
      */
@@ -1062,6 +1147,9 @@
             return;
         }
 
+        final ArrayMap<AutofillId, InternalSanitizer> sanitizers =
+                createSanitizers(getSaveInfoLocked());
+
         final int numContexts = mContexts.size();
 
         for (int contextNum = 0; contextNum < numContexts; contextNum++) {
@@ -1088,7 +1176,9 @@
                 }
                 if (sVerbose) Slog.v(TAG, "callSaveLocked(): updating " + id + " to " + value);
 
-                node.updateAutofillValue(value);
+                final AutofillValue sanitizedValue = getSanitizedValue(sanitizers, id, value);
+
+                node.updateAutofillValue(sanitizedValue);
             }
 
             // Sanitize structure before it's sent to service.
@@ -1244,6 +1334,21 @@
                 break;
             case ACTION_VALUE_CHANGED:
                 if (value != null && !value.equals(viewState.getCurrentValue())) {
+                    if (value.isEmpty()
+                            && viewState.getCurrentValue() != null
+                            && viewState.getCurrentValue().isText()
+                            && viewState.getCurrentValue().getTextValue() != null
+                            && getSaveInfoLocked() != null) {
+                        final int length = viewState.getCurrentValue().getTextValue().length();
+                        if (sDebug) {
+                            Slog.d(TAG, "updateLocked(" + id + "): resetting value that was "
+                                    + length + " chars long");
+                        }
+                        final LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_VALUE_RESET)
+                                .addTaggedData(MetricsEvent.FIELD_AUTOFILL_PREVIOUS_LENGTH, length);
+                        mMetricsLogger.write(log);
+                    }
+
                     // Always update the internal state.
                     viewState.setCurrentValue(value);
 
@@ -1319,7 +1424,33 @@
             filterText = value.getTextValue().toString();
         }
 
-        getUiForShowing().showFillUi(filledId, response, filterText, mPackageName, this);
+        getUiForShowing().showFillUi(filledId, response, filterText,
+                mService.getServicePackageName(), mPackageName, this);
+
+        synchronized (mLock) {
+            if (mUiShownTime == 0) {
+                // Log first time UI is shown.
+                mUiShownTime = SystemClock.elapsedRealtime();
+                final long duration = mUiShownTime - mStartTime;
+                if (sDebug) {
+                    final StringBuilder msg = new StringBuilder("1st UI for ")
+                            .append(mActivityToken)
+                            .append(" shown in ");
+                    TimeUtils.formatDuration(duration, msg);
+                    Slog.d(TAG, msg.toString());
+                }
+                final StringBuilder historyLog = new StringBuilder("id=").append(id)
+                        .append(" app=").append(mActivityToken)
+                        .append(" svc=").append(mService.getServicePackageName())
+                        .append(" latency=");
+                TimeUtils.formatDuration(duration, historyLog);
+                mUiLatencyHistory.log(historyLog.toString());
+
+                final LogMaker metricsLog = newLogMaker(MetricsEvent.AUTOFILL_UI_LATENCY)
+                        .setCounterValue((int) duration);
+                mMetricsLogger.write(metricsLog);
+            }
+        }
     }
 
     boolean isDestroyed() {
@@ -1334,11 +1465,15 @@
         }
     }
 
-    private void notifyUnavailableToClient() {
+    private void notifyUnavailableToClient(boolean sessionFinished) {
         synchronized (mLock) {
-            if (!mHasCallback || mCurrentViewId == null) return;
+            if (mCurrentViewId == null) return;
             try {
-                mClient.notifyNoFillUi(id, mCurrentViewId);
+                if (mHasCallback) {
+                    mClient.notifyNoFillUi(id, mCurrentViewId, sessionFinished);
+                } else if (sessionFinished) {
+                    mClient.setSessionFinished(AutofillManager.STATE_FINISHED);
+                }
             } catch (RemoteException e) {
                 Slog.e(TAG, "Error notifying client no fill UI: id=" + mCurrentViewId, e);
             }
@@ -1426,7 +1561,7 @@
         }
         mService.resetLastResponse();
         // Nothing to be done, but need to notify client.
-        notifyUnavailableToClient();
+        notifyUnavailableToClient(true);
         removeSelf();
     }
 
@@ -1545,6 +1680,10 @@
     }
 
     void autoFill(int requestId, int datasetIndex, Dataset dataset, boolean generateEvent) {
+        if (sDebug) {
+            Slog.d(TAG, "autoFill(): requestId=" + requestId  + "; datasetIdx=" + datasetIndex
+                    + "; dataset=" + dataset);
+        }
         synchronized (mLock) {
             if (mDestroyed) {
                 Slog.w(TAG, "Call to Session#autoFill() rejected - session: "
@@ -1565,10 +1704,14 @@
             mService.logDatasetAuthenticationSelected(dataset.getId(), id, mClientState);
             setViewStatesLocked(null, dataset, ViewState.STATE_WAITING_DATASET_AUTH, false);
             final Intent fillInIntent = createAuthFillInIntentLocked(requestId, mClientState);
-
+            if (fillInIntent == null) {
+                forceRemoveSelfLocked();
+                return;
+            }
             final int authenticationId = AutofillManager.makeAuthenticationId(requestId,
                     datasetIndex);
             startAuthentication(authenticationId, dataset.getAuthentication(), fillInIntent);
+
         }
     }
 
@@ -1578,14 +1721,16 @@
         }
     }
 
+    // TODO: this should never be null, but we got at least one occurrence, probably due to a race.
+    @Nullable
     private Intent createAuthFillInIntentLocked(int requestId, Bundle extras) {
         final Intent fillInIntent = new Intent();
 
         final FillContext context = getFillContextByRequestIdLocked(requestId);
         if (context == null) {
-            // TODO(b/653742740): this will crash system_server. We need to handle it, but we're
-            // keeping it crashing for now so we can diagnose when it happens again
-            Slog.wtf(TAG, "no FillContext for requestId" + requestId + "; mContexts= " + mContexts);
+            Slog.wtf(TAG, "createAuthFillInIntentLocked(): no FillContext. requestId=" + requestId
+                    + "; mContexts= " + mContexts);
+            return null;
         }
         fillInIntent.putExtra(AutofillManager.EXTRA_ASSIST_STRUCTURE, context.getStructure());
         fillInIntent.putExtra(AutofillManager.EXTRA_CLIENT_STATE, extras);
@@ -1614,6 +1759,14 @@
         pw.print(prefix); pw.print("uid: "); pw.println(uid);
         pw.print(prefix); pw.print("mPackagename: "); pw.println(mPackageName);
         pw.print(prefix); pw.print("mActivityToken: "); pw.println(mActivityToken);
+        pw.print(prefix); pw.print("mStartTime: "); pw.println(mStartTime);
+        pw.print(prefix); pw.print("Time to show UI: ");
+        if (mUiShownTime == 0) {
+            pw.println("N/A");
+        } else {
+            TimeUtils.formatDuration(mUiShownTime - mStartTime, pw);
+            pw.println();
+        }
         pw.print(prefix); pw.print("mResponses: ");
         if (mResponses == null) {
             pw.println("null");
@@ -1732,10 +1885,10 @@
         if (mDestroyed) {
             return null;
         }
-        mUi.destroyAll(mPendingSaveUi, this);
+        mUi.destroyAll(mPendingSaveUi, this, true);
         mUi.clearCallback(this);
         mDestroyed = true;
-        mMetricsLogger.action(MetricsEvent.AUTOFILL_SESSION_FINISHED, mPackageName);
+        writeLog(MetricsEvent.AUTOFILL_SESSION_FINISHED);
         return mRemoteFillService;
     }
 
@@ -1746,9 +1899,17 @@
     void forceRemoveSelfLocked() {
         if (sVerbose) Slog.v(TAG, "forceRemoveSelfLocked(): " + mPendingSaveUi);
 
+        final boolean isPendingSaveUi = isSaveUiPendingLocked();
         mPendingSaveUi = null;
         removeSelfLocked();
-        mUi.destroyAll(mPendingSaveUi, this);
+        mUi.destroyAll(mPendingSaveUi, this, false);
+        if (!isPendingSaveUi) {
+            try {
+                mClient.setSessionFinished(AutofillManager.STATE_UNKNOWN);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Error notifying client to finish session", e);
+            }
+        }
     }
 
     /**
@@ -1771,7 +1932,7 @@
                     + id + " destroyed");
             return;
         }
-        if (isSaveUiPending()) {
+        if (isSaveUiPendingLocked()) {
             Slog.i(TAG, "removeSelfLocked() ignored, waiting for pending save ui");
             return;
         }
@@ -1792,14 +1953,14 @@
      * a specific {@code token} created by
      * {@link PendingUi#PendingUi(IBinder, int, IAutoFillManagerClient)}.
      */
-    boolean isSaveUiPendingForToken(@NonNull IBinder token) {
-        return isSaveUiPending() && token.equals(mPendingSaveUi.getToken());
+    boolean isSaveUiPendingForTokenLocked(@NonNull IBinder token) {
+        return isSaveUiPendingLocked() && token.equals(mPendingSaveUi.getToken());
     }
 
     /**
      * Checks whether this session is hiding the Save UI to handle a custom description link.
      */
-    private boolean isSaveUiPending() {
+    private boolean isSaveUiPendingLocked() {
         return mPendingSaveUi != null && mPendingSaveUi.getState() == PendingUi.STATE_PENDING;
     }
 
@@ -1820,4 +1981,16 @@
         }
         return lastResponseIdx;
     }
+
+    private LogMaker newLogMaker(int category) {
+        return newLogMaker(category, mService.getServicePackageName());
+    }
+
+    private LogMaker newLogMaker(int category, String servicePackageName) {
+        return Helper.newLogMaker(category, mPackageName, servicePackageName);
+    }
+
+    private void writeLog(int category) {
+        mMetricsLogger.write(newLogMaker(category));
+    }
 }
diff --git a/com/android/server/autofill/ui/AutoFillUI.java b/com/android/server/autofill/ui/AutoFillUI.java
index cac2bff..36b95fc 100644
--- a/com/android/server/autofill/ui/AutoFillUI.java
+++ b/com/android/server/autofill/ui/AutoFillUI.java
@@ -40,8 +40,9 @@
 import android.widget.Toast;
 
 import com.android.internal.logging.MetricsLogger;
-import com.android.internal.logging.nano.MetricsProto;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.server.UiThread;
+import com.android.server.autofill.Helper;
 
 import java.io.PrintWriter;
 
@@ -158,21 +159,22 @@
      * @param focusedId the currently focused field
      * @param response the current fill response
      * @param filterText text of the view to be filled
+     * @param servicePackageName package name of the autofill service filling the activity
      * @param packageName package name of the activity that is filled
      * @param callback Identifier for the caller
      */
     public void showFillUi(@NonNull AutofillId focusedId, @NonNull FillResponse response,
-            @Nullable String filterText, @NonNull String packageName,
-            @NonNull AutoFillUiCallback callback) {
+            @Nullable String filterText, @Nullable String servicePackageName,
+            @NonNull String packageName, @NonNull AutoFillUiCallback callback) {
         if (sDebug) {
             final int size = filterText == null ? 0 : filterText.length();
             Slog.d(TAG, "showFillUi(): id=" + focusedId + ", filter=" + size + " chars");
         }
-        final LogMaker log = (new LogMaker(MetricsProto.MetricsEvent.AUTOFILL_FILL_UI))
-                .setPackageName(packageName)
-                .addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_FILTERTEXT_LEN,
+        final LogMaker log =
+                Helper.newLogMaker(MetricsEvent.AUTOFILL_FILL_UI, packageName, servicePackageName)
+                .addTaggedData(MetricsEvent.FIELD_AUTOFILL_FILTERTEXT_LEN,
                         filterText == null ? 0 : filterText.length())
-                .addTaggedData(MetricsProto.MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS,
+                .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS,
                         response.getDatasets() == null ? 0 : response.getDatasets().size());
 
         mHandler.post(() -> {
@@ -184,7 +186,7 @@
                     filterText, mOverlayControl, new FillUi.Callback() {
                 @Override
                 public void onResponsePicked(FillResponse response) {
-                    log.setType(MetricsProto.MetricsEvent.TYPE_DETAIL);
+                    log.setType(MetricsEvent.TYPE_DETAIL);
                     hideFillUiUiThread(callback);
                     if (mCallback != null) {
                         mCallback.authenticate(response.getRequestId(),
@@ -195,7 +197,7 @@
 
                 @Override
                 public void onDatasetPicked(Dataset dataset) {
-                    log.setType(MetricsProto.MetricsEvent.TYPE_ACTION);
+                    log.setType(MetricsEvent.TYPE_ACTION);
                     hideFillUiUiThread(callback);
                     if (mCallback != null) {
                         final int datasetIndex = response.getDatasets().indexOf(dataset);
@@ -205,14 +207,14 @@
 
                 @Override
                 public void onCanceled() {
-                    log.setType(MetricsProto.MetricsEvent.TYPE_DISMISS);
+                    log.setType(MetricsEvent.TYPE_DISMISS);
                     hideFillUiUiThread(callback);
                 }
 
                 @Override
                 public void onDestroy() {
-                    if (log.getType() == MetricsProto.MetricsEvent.TYPE_UNKNOWN) {
-                        log.setType(MetricsProto.MetricsEvent.TYPE_CLOSE);
+                    if (log.getType() == MetricsEvent.TYPE_UNKNOWN) {
+                        log.setType(MetricsEvent.TYPE_CLOSE);
                     }
                     mMetricsLogger.write(log);
                 }
@@ -246,37 +248,39 @@
      * Shows the UI asking the user to save for autofill.
      */
     public void showSaveUi(@NonNull CharSequence serviceLabel, @NonNull Drawable serviceIcon,
-            @NonNull SaveInfo info,@NonNull ValueFinder valueFinder, @NonNull String packageName,
+            @Nullable String servicePackageName, @NonNull SaveInfo info,
+            @NonNull ValueFinder valueFinder, @NonNull String packageName,
             @NonNull AutoFillUiCallback callback, @NonNull PendingUi pendingSaveUi) {
         if (sVerbose) Slog.v(TAG, "showSaveUi() for " + packageName + ": " + info);
         int numIds = 0;
         numIds += info.getRequiredIds() == null ? 0 : info.getRequiredIds().length;
         numIds += info.getOptionalIds() == null ? 0 : info.getOptionalIds().length;
 
-        LogMaker log = (new LogMaker(MetricsProto.MetricsEvent.AUTOFILL_SAVE_UI))
-                .setPackageName(packageName).addTaggedData(
-                        MetricsProto.MetricsEvent.FIELD_AUTOFILL_NUM_IDS, numIds);
+        final LogMaker log =
+                Helper.newLogMaker(MetricsEvent.AUTOFILL_SAVE_UI, packageName, servicePackageName)
+                .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_IDS, numIds);
 
         mHandler.post(() -> {
             if (callback != mCallback) {
                 return;
             }
             hideAllUiThread(callback);
-            mSaveUi = new SaveUi(mContext, pendingSaveUi, serviceLabel, serviceIcon, info,
-                    valueFinder, mOverlayControl, new SaveUi.OnSaveListener() {
+            mSaveUi = new SaveUi(mContext, pendingSaveUi, serviceLabel, serviceIcon,
+                    servicePackageName, packageName, info, valueFinder, mOverlayControl,
+                    new SaveUi.OnSaveListener() {
                 @Override
                 public void onSave() {
-                    log.setType(MetricsProto.MetricsEvent.TYPE_ACTION);
+                    log.setType(MetricsEvent.TYPE_ACTION);
                     hideSaveUiUiThread(mCallback);
                     if (mCallback != null) {
                         mCallback.save();
                     }
-                    destroySaveUiUiThread(pendingSaveUi);
+                    destroySaveUiUiThread(pendingSaveUi, true);
                 }
 
                 @Override
                 public void onCancel(IntentSender listener) {
-                    log.setType(MetricsProto.MetricsEvent.TYPE_DISMISS);
+                    log.setType(MetricsEvent.TYPE_DISMISS);
                     hideSaveUiUiThread(mCallback);
                     if (listener != null) {
                         try {
@@ -289,13 +293,13 @@
                     if (mCallback != null) {
                         mCallback.cancelSave();
                     }
-                    destroySaveUiUiThread(pendingSaveUi);
+                    destroySaveUiUiThread(pendingSaveUi, true);
                 }
 
                 @Override
                 public void onDestroy() {
-                    if (log.getType() == MetricsProto.MetricsEvent.TYPE_UNKNOWN) {
-                        log.setType(MetricsProto.MetricsEvent.TYPE_CLOSE);
+                    if (log.getType() == MetricsEvent.TYPE_UNKNOWN) {
+                        log.setType(MetricsEvent.TYPE_CLOSE);
 
                         if (mCallback != null) {
                             mCallback.cancelSave();
@@ -331,8 +335,8 @@
      * Destroy all UI affordances.
      */
     public void destroyAll(@Nullable PendingUi pendingSaveUi,
-            @Nullable AutoFillUiCallback callback) {
-        mHandler.post(() -> destroyAllUiThread(pendingSaveUi, callback));
+            @Nullable AutoFillUiCallback callback, boolean notifyClient) {
+        mHandler.post(() -> destroyAllUiThread(pendingSaveUi, callback, notifyClient));
     }
 
     public void dump(PrintWriter pw) {
@@ -375,7 +379,7 @@
     }
 
     @android.annotation.UiThread
-    private void destroySaveUiUiThread(@Nullable PendingUi pendingSaveUi) {
+    private void destroySaveUiUiThread(@Nullable PendingUi pendingSaveUi, boolean notifyClient) {
         if (mSaveUi == null) {
             // Calling destroySaveUiUiThread() twice is normal - it usually happens when the
             // first call is made after the SaveUI is hidden and the second when the session is
@@ -387,7 +391,7 @@
         if (sDebug) Slog.d(TAG, "destroySaveUiUiThread(): " + pendingSaveUi);
         mSaveUi.destroy();
         mSaveUi = null;
-        if (pendingSaveUi != null) {
+        if (pendingSaveUi != null && notifyClient) {
             try {
                 if (sDebug) Slog.d(TAG, "destroySaveUiUiThread(): notifying client");
                 pendingSaveUi.client.setSaveUiState(pendingSaveUi.id, false);
@@ -399,9 +403,9 @@
 
     @android.annotation.UiThread
     private void destroyAllUiThread(@Nullable PendingUi pendingSaveUi,
-            @Nullable AutoFillUiCallback callback) {
+            @Nullable AutoFillUiCallback callback, boolean notifyClient) {
         hideFillUiUiThread(callback);
-        destroySaveUiUiThread(pendingSaveUi);
+        destroySaveUiUiThread(pendingSaveUi, notifyClient);
     }
 
     @android.annotation.UiThread
@@ -413,7 +417,7 @@
                 Slog.d(TAG, "hideAllUiThread(): "
                         + "destroying Save UI because pending restoration is finished");
             }
-            destroySaveUiUiThread(pendingSaveUi);
+            destroySaveUiUiThread(pendingSaveUi, true);
         }
     }
 }
diff --git a/com/android/server/autofill/ui/FillUi.java b/com/android/server/autofill/ui/FillUi.java
index 371e74d..bf442dc 100644
--- a/com/android/server/autofill/ui/FillUi.java
+++ b/com/android/server/autofill/ui/FillUi.java
@@ -55,6 +55,7 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.regex.Pattern;
 
 final class FillUi {
     private static final String TAG = "FillUi";
@@ -164,15 +165,18 @@
                         Slog.e(TAG, "Error inflating remote views", e);
                         continue;
                     }
-                    final AutofillValue value = dataset.getFieldValues().get(index);
+                    final Pattern filter = dataset.getFilter(index);
                     String valueText = null;
-                    // If the dataset needs auth - don't add its text to allow guessing
-                    // its content based on how filtering behaves.
-                    if (value != null && value.isText() && dataset.getAuthentication() == null) {
-                        valueText = value.getTextValue().toString().toLowerCase();
+                    if (filter == null) {
+                        final AutofillValue value = dataset.getFieldValues().get(index);
+                        // If the dataset needs auth - don't add its text to allow guessing
+                        // its content based on how filtering behaves.
+                        if (value != null && value.isText() && dataset.getAuthentication() == null) {
+                            valueText = value.getTextValue().toString().toLowerCase();
+                        }
                     }
 
-                    items.add(new ViewItem(dataset, valueText, view));
+                    items.add(new ViewItem(dataset, filter, valueText, view));
                 }
             }
 
@@ -331,11 +335,17 @@
         private final String mValue;
         private final Dataset mDataset;
         private final View mView;
+        private final Pattern mFilter;
 
-        ViewItem(Dataset dataset, String value, View view) {
+        ViewItem(Dataset dataset, Pattern filter, String value, View view) {
             mDataset = dataset;
             mValue = value;
             mView = view;
+            mFilter = filter;
+        }
+
+        public Pattern getFilter() {
+            return mFilter;
         }
 
         public View getView() {
@@ -349,12 +359,6 @@
         public String getValue() {
             return mValue;
         }
-
-        @Override
-        public String toString() {
-            // Used for filtering in the adapter
-            return mValue;
-        }
     }
 
     private final class AutofillWindowPresenter extends IAutofillWindowPresenter.Stub {
@@ -516,10 +520,16 @@
                     for (int i = 0; i < itemCount; i++) {
                         final ViewItem item = mAllItems.get(i);
                         final String value = item.getValue();
-                        // No value, i.e. null, matches any filter
-                        if ((value == null && item.mDataset.getAuthentication() == null)
-                                || (value != null
-                                        && value.toLowerCase().startsWith(constraintLowerCase))) {
+                        final Pattern filter = item.getFilter();
+                        final boolean matches;
+                        if (filter != null) {
+                            matches = filter.matcher(constraintLowerCase).matches();
+                        } else {
+                            matches = (value == null)
+                                    ? (item.mDataset.getAuthentication() == null)
+                                    : value.toLowerCase().startsWith(constraintLowerCase);
+                        }
+                        if (matches) {
                             filteredItems.add(item);
                         }
                     }
diff --git a/com/android/server/autofill/ui/SaveUi.java b/com/android/server/autofill/ui/SaveUi.java
index d0b2e92..d48f23c 100644
--- a/com/android/server/autofill/ui/SaveUi.java
+++ b/com/android/server/autofill/ui/SaveUi.java
@@ -20,16 +20,15 @@
 import static com.android.server.autofill.Helper.sVerbose;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.Dialog;
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentSender;
 import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
+import android.metrics.LogMaker;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -53,6 +52,8 @@
 import android.widget.TextView;
 
 import com.android.internal.R;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.server.UiThread;
 
 import java.io.PrintWriter;
@@ -111,6 +112,7 @@
     }
 
     private final Handler mHandler = UiThread.getHandler();
+    private final MetricsLogger mMetricsLogger = new MetricsLogger();
 
     private final @NonNull Dialog mDialog;
 
@@ -121,16 +123,21 @@
     private final CharSequence mTitle;
     private final CharSequence mSubTitle;
     private final PendingUi mPendingUi;
+    private final String mServicePackageName;
+    private final String mPackageName;
 
     private boolean mDestroyed;
 
     SaveUi(@NonNull Context context, @NonNull PendingUi pendingUi,
            @NonNull CharSequence serviceLabel, @NonNull Drawable serviceIcon,
+           @Nullable String servicePackageName, @NonNull String packageName,
            @NonNull SaveInfo info, @NonNull ValueFinder valueFinder,
            @NonNull OverlayControl overlayControl, @NonNull OnSaveListener listener) {
         mPendingUi= pendingUi;
         mListener = new OneTimeListener(listener);
         mOverlayControl = overlayControl;
+        mServicePackageName = servicePackageName;
+        mPackageName = packageName;
 
         final LayoutInflater inflater = LayoutInflater.from(context);
         final View view = inflater.inflate(R.layout.autofill_save, null);
@@ -181,6 +188,8 @@
         ScrollView subtitleContainer = null;
         final CustomDescription customDescription = info.getCustomDescription();
         if (customDescription != null) {
+            writeLog(MetricsEvent.AUTOFILL_SAVE_CUSTOM_DESCRIPTION, type);
+
             mSubTitle = null;
             if (sDebug) Slog.d(TAG, "Using custom description");
 
@@ -190,40 +199,35 @@
                     @Override
                     public boolean onClickHandler(View view, PendingIntent pendingIntent,
                             Intent intent) {
+                        final LogMaker log =
+                                newLogMaker(MetricsEvent.AUTOFILL_SAVE_LINK_TAPPED, type);
                         // We need to hide the Save UI before launching the pending intent, and
                         // restore back it once the activity is finished, and that's achieved by
                         // adding a custom extra in the activity intent.
-                        if (pendingIntent != null) {
-                            if (intent == null) {
-                                Slog.w(TAG,
-                                        "remote view on custom description does not have intent");
-                                return false;
-                            }
-                            if (!pendingIntent.isActivity()) {
-                                Slog.w(TAG, "ignoring custom description pending intent that's not "
-                                        + "for an activity: " + pendingIntent);
-                                return false;
-                            }
-                            if (sVerbose) {
-                                Slog.v(TAG,
-                                        "Intercepting custom description intent: " + intent);
-                            }
-                            final IBinder token = mPendingUi.getToken();
-                            intent.putExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN, token);
-                            try {
-                                pendingUi.client.startIntentSender(pendingIntent.getIntentSender(),
-                                        intent);
-                                mPendingUi.setState(PendingUi.STATE_PENDING);
-                                if (sDebug) {
-                                    Slog.d(TAG, "hiding UI until restored with token " + token);
-                                }
-                                hide();
-                            } catch (RemoteException e) {
-                                Slog.w(TAG, "error triggering pending intent: " + intent);
-                                return false;
-                            }
+                        final boolean isValid = isValidLink(pendingIntent, intent);
+                        if (!isValid) {
+                            log.setType(MetricsEvent.TYPE_UNKNOWN);
+                            mMetricsLogger.write(log);
+                            return false;
                         }
-                        return true;
+                        if (sVerbose) Slog.v(TAG, "Intercepting custom description intent");
+                        final IBinder token = mPendingUi.getToken();
+                        intent.putExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN, token);
+                        try {
+                            pendingUi.client.startIntentSender(pendingIntent.getIntentSender(),
+                                    intent);
+                            mPendingUi.setState(PendingUi.STATE_PENDING);
+                            if (sDebug) Slog.d(TAG, "hiding UI until restored with token " + token);
+                            hide();
+                            log.setType(MetricsEvent.TYPE_OPEN);
+                            mMetricsLogger.write(log);
+                            return true;
+                        } catch (RemoteException e) {
+                            Slog.w(TAG, "error triggering pending intent: " + intent);
+                            log.setType(MetricsEvent.TYPE_FAILURE);
+                            mMetricsLogger.write(log);
+                            return false;
+                        }
                     }
                 };
 
@@ -241,6 +245,7 @@
         } else {
             mSubTitle = info.getDescription();
             if (mSubTitle != null) {
+                writeLog(MetricsEvent.AUTOFILL_SAVE_CUSTOM_SUBTITLE, type);
                 subtitleContainer = view.findViewById(R.id.autofill_save_custom_subtitle);
                 final TextView subtitleView = new TextView(context);
                 subtitleView.setText(mSubTitle);
@@ -258,9 +263,7 @@
         } else {
             noButton.setText(R.string.autofill_save_no);
         }
-        final View.OnClickListener cancelListener =
-                (v) -> mListener.onCancel(info.getNegativeActionListener());
-        noButton.setOnClickListener(cancelListener);
+        noButton.setOnClickListener((v) -> mListener.onCancel(info.getNegativeActionListener()));
 
         final View yesButton = view.findViewById(R.id.autofill_save_yes);
         yesButton.setOnClickListener((v) -> mListener.onSave());
@@ -268,8 +271,9 @@
         mDialog = new Dialog(context, R.style.Theme_DeviceDefault_Light_Panel);
         mDialog.setContentView(view);
 
-        // Dialog can be dismissed when touched outside.
-        mDialog.setOnDismissListener((d) -> mListener.onCancel(info.getNegativeActionListener()));
+        // Dialog can be dismissed when touched outside, but the negative listener should not be
+        // notified (hence the null argument).
+        mDialog.setOnDismissListener((d) -> mListener.onCancel(null));
 
         final Window window = mDialog.getWindow();
         window.setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
@@ -300,7 +304,7 @@
 
         if (actualWidth <= maxWidth && actualHeight <= maxHeight) {
             if (sDebug) {
-                Slog.d(TAG, "Addingservice icon "
+                Slog.d(TAG, "Adding service icon "
                         + "(" + actualWidth + "x" + actualHeight + ") as it's less than maximum "
                         + "(" + maxWidth + "x" + maxHeight + ").");
             }
@@ -309,10 +313,41 @@
             Slog.w(TAG, "Not adding service icon of size "
                     + "(" + actualWidth + "x" + actualHeight + ") because maximum is "
                     + "(" + maxWidth + "x" + maxHeight + ").");
-            iconView.setVisibility(View.INVISIBLE);
+            ((ViewGroup)iconView.getParent()).removeView(iconView);
         }
     }
 
+    private static boolean isValidLink(PendingIntent pendingIntent, Intent intent) {
+        if (pendingIntent == null) {
+            Slog.w(TAG, "isValidLink(): custom description without pending intent");
+            return false;
+        }
+        if (!pendingIntent.isActivity()) {
+            Slog.w(TAG, "isValidLink(): pending intent not for activity");
+            return false;
+        }
+        if (intent == null) {
+            Slog.w(TAG, "isValidLink(): no intent");
+            return false;
+        }
+        return true;
+    }
+
+    private LogMaker newLogMaker(int category, int saveType) {
+        return newLogMaker(category)
+                .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SAVE_TYPE, saveType);
+    }
+
+    private LogMaker newLogMaker(int category) {
+        return new LogMaker(category)
+                .setPackageName(mPackageName)
+                .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE, mServicePackageName);
+    }
+
+    private void writeLog(int category, int saveType) {
+        mMetricsLogger.write(newLogMaker(category, saveType));
+    }
+
     /**
      * Update the pending UI, if any.
      *
@@ -326,17 +361,25 @@
                     + mPendingUi.getToken());
             return;
         }
-        switch (operation) {
-            case AutofillManager.PENDING_UI_OPERATION_RESTORE:
-                if (sDebug) Slog.d(TAG, "Restoring save dialog for " + token);
-                show();
-                break;
-            case AutofillManager.PENDING_UI_OPERATION_CANCEL:
-                if (sDebug) Slog.d(TAG, "Cancelling pending save dialog for " + token);
-                hide();
-                break;
-            default:
-                Slog.w(TAG, "restore(): invalid operation " + operation);
+        final LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_PENDING_SAVE_UI_OPERATION);
+        try {
+            switch (operation) {
+                case AutofillManager.PENDING_UI_OPERATION_RESTORE:
+                    if (sDebug) Slog.d(TAG, "Restoring save dialog for " + token);
+                    log.setType(MetricsEvent.TYPE_OPEN);
+                    show();
+                    break;
+                case AutofillManager.PENDING_UI_OPERATION_CANCEL:
+                    log.setType(MetricsEvent.TYPE_DISMISS);
+                    if (sDebug) Slog.d(TAG, "Cancelling pending save dialog for " + token);
+                    hide();
+                    break;
+                default:
+                    log.setType(MetricsEvent.TYPE_FAILURE);
+                    Slog.w(TAG, "restore(): invalid operation " + operation);
+            }
+        } finally {
+            mMetricsLogger.write(log);
         }
         mPendingUi.setState(PendingUi.STATE_FINISHED);
     }
@@ -385,6 +428,8 @@
         pw.print(prefix); pw.print("title: "); pw.println(mTitle);
         pw.print(prefix); pw.print("subtitle: "); pw.println(mSubTitle);
         pw.print(prefix); pw.print("pendingUi: "); pw.println(mPendingUi);
+        pw.print(prefix); pw.print("service: "); pw.println(mServicePackageName);
+        pw.print(prefix); pw.print("app: "); pw.println(mPackageName);
 
         final View view = mDialog.getWindow().getDecorView();
         final int[] loc = view.getLocationOnScreen();
diff --git a/com/android/server/backup/BackupManagerConstants.java b/com/android/server/backup/BackupManagerConstants.java
index cd60182..245241c 100644
--- a/com/android/server/backup/BackupManagerConstants.java
+++ b/com/android/server/backup/BackupManagerConstants.java
@@ -123,7 +123,7 @@
     // group the calls of these methods in a block syncrhonized on
     // a reference of this object.
     public synchronized long getKeyValueBackupIntervalMilliseconds() {
-        if (BackupManagerService.DEBUG_SCHEDULING) {
+        if (RefactoredBackupManagerService.DEBUG_SCHEDULING) {
             Slog.v(TAG, "getKeyValueBackupIntervalMilliseconds(...) returns "
                     + mKeyValueBackupIntervalMilliseconds);
         }
@@ -131,7 +131,7 @@
     }
 
     public synchronized long getKeyValueBackupFuzzMilliseconds() {
-        if (BackupManagerService.DEBUG_SCHEDULING) {
+        if (RefactoredBackupManagerService.DEBUG_SCHEDULING) {
             Slog.v(TAG, "getKeyValueBackupFuzzMilliseconds(...) returns "
                     + mKeyValueBackupFuzzMilliseconds);
         }
@@ -139,7 +139,7 @@
     }
 
     public synchronized boolean getKeyValueBackupRequireCharging() {
-        if (BackupManagerService.DEBUG_SCHEDULING) {
+        if (RefactoredBackupManagerService.DEBUG_SCHEDULING) {
             Slog.v(TAG, "getKeyValueBackupRequireCharging(...) returns "
                     + mKeyValueBackupRequireCharging);
         }
@@ -147,7 +147,7 @@
     }
 
     public synchronized int getKeyValueBackupRequiredNetworkType() {
-        if (BackupManagerService.DEBUG_SCHEDULING) {
+        if (RefactoredBackupManagerService.DEBUG_SCHEDULING) {
             Slog.v(TAG, "getKeyValueBackupRequiredNetworkType(...) returns "
                     + mKeyValueBackupRequiredNetworkType);
         }
@@ -155,7 +155,7 @@
     }
 
     public synchronized long getFullBackupIntervalMilliseconds() {
-        if (BackupManagerService.DEBUG_SCHEDULING) {
+        if (RefactoredBackupManagerService.DEBUG_SCHEDULING) {
             Slog.v(TAG, "getFullBackupIntervalMilliseconds(...) returns "
                     + mFullBackupIntervalMilliseconds);
         }
@@ -163,7 +163,7 @@
     }
 
     public synchronized boolean getFullBackupRequireCharging() {
-        if (BackupManagerService.DEBUG_SCHEDULING) {
+        if (RefactoredBackupManagerService.DEBUG_SCHEDULING) {
             Slog.v(TAG, "getFullBackupRequireCharging(...) returns " + mFullBackupRequireCharging);
         }
         return mFullBackupRequireCharging;
@@ -171,7 +171,7 @@
     }
 
     public synchronized int getFullBackupRequiredNetworkType() {
-        if (BackupManagerService.DEBUG_SCHEDULING) {
+        if (RefactoredBackupManagerService.DEBUG_SCHEDULING) {
             Slog.v(TAG, "getFullBackupRequiredNetworkType(...) returns "
                     + mFullBackupRequiredNetworkType);
         }
diff --git a/com/android/server/backup/BackupManagerService.java b/com/android/server/backup/BackupManagerService.java
index f525797..eabe21f 100644
--- a/com/android/server/backup/BackupManagerService.java
+++ b/com/android/server/backup/BackupManagerService.java
@@ -192,6 +192,10 @@
 import javax.crypto.spec.PBEKeySpec;
 import javax.crypto.spec.SecretKeySpec;
 
+/**
+ * @Deprecated Use RefactoredBackupManagerService instead. This class is only
+ * kept for fallback and archeology reasons and will be removed soon.
+ */
 public class BackupManagerService implements BackupManagerServiceInterface {
 
     private static final String TAG = "BackupManagerService";
@@ -397,45 +401,53 @@
         @Override
         public void onUnlockUser(int userId) {
             if (userId == UserHandle.USER_SYSTEM) {
-                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup init");
-                sInstance.initialize(userId);
-                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
-
-                // Migrate legacy setting
-                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup migrate");
-                if (!backupSettingMigrated(userId)) {
-                    if (DEBUG) {
-                        Slog.i(TAG, "Backup enable apparently not migrated");
-                    }
-                    final ContentResolver r = sInstance.mContext.getContentResolver();
-                    final int enableState = Settings.Secure.getIntForUser(r,
-                            Settings.Secure.BACKUP_ENABLED, -1, userId);
-                    if (enableState >= 0) {
-                        if (DEBUG) {
-                            Slog.i(TAG, "Migrating enable state " + (enableState != 0));
-                        }
-                        writeBackupEnableState(enableState != 0, userId);
-                        Settings.Secure.putStringForUser(r,
-                                Settings.Secure.BACKUP_ENABLED, null, userId);
-                    } else {
-                        if (DEBUG) {
-                            Slog.i(TAG, "Backup not yet configured; retaining null enable state");
-                        }
-                    }
-                }
-                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
-
-                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup enable");
-                try {
-                    sInstance.setBackupEnabled(readBackupEnableState(userId));
-                } catch (RemoteException e) {
-                    // can't happen; it's a local object
-                }
-                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+                sInstance.unlockSystemUser();
             }
         }
     }
 
+    // Called through the trampoline from onUnlockUser(), then we buck the work
+    // off to the background thread to keep the unlock time down.
+    public void unlockSystemUser() {
+        mBackupHandler.post(() -> {
+            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup init");
+            sInstance.initialize(UserHandle.USER_SYSTEM);
+            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+
+            // Migrate legacy setting
+            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup migrate");
+            if (!backupSettingMigrated(UserHandle.USER_SYSTEM)) {
+                if (DEBUG) {
+                    Slog.i(TAG, "Backup enable apparently not migrated");
+                }
+                final ContentResolver r = sInstance.mContext.getContentResolver();
+                final int enableState = Settings.Secure.getIntForUser(r,
+                        Settings.Secure.BACKUP_ENABLED, -1, UserHandle.USER_SYSTEM);
+                if (enableState >= 0) {
+                    if (DEBUG) {
+                        Slog.i(TAG, "Migrating enable state " + (enableState != 0));
+                    }
+                    writeBackupEnableState(enableState != 0, UserHandle.USER_SYSTEM);
+                    Settings.Secure.putStringForUser(r,
+                            Settings.Secure.BACKUP_ENABLED, null, UserHandle.USER_SYSTEM);
+                } else {
+                    if (DEBUG) {
+                        Slog.i(TAG, "Backup not yet configured; retaining null enable state");
+                    }
+                }
+            }
+            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+
+            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup enable");
+            try {
+                sInstance.setBackupEnabled(readBackupEnableState(UserHandle.USER_SYSTEM));
+            } catch (RemoteException e) {
+                // can't happen; it's a local object
+            }
+            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+        });
+    }
+
     class ProvisionedObserver extends ContentObserver {
         public ProvisionedObserver(Handler handler) {
             super(handler);
@@ -1983,7 +1995,7 @@
                 if (uri == null) {
                     return;
                 }
-                String pkgName = uri.getSchemeSpecificPart();
+                final String pkgName = uri.getSchemeSpecificPart();
                 if (pkgName != null) {
                     pkgList = new String[] { pkgName };
                 }
@@ -1991,7 +2003,7 @@
 
                 // At package-changed we only care about looking at new transport states
                 if (changed) {
-                    String[] components =
+                    final String[] components =
                             intent.getStringArrayExtra(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST);
 
                     if (MORE_DEBUG) {
@@ -2001,7 +2013,8 @@
                         }
                     }
 
-                    mTransportManager.onPackageChanged(pkgName, components);
+                    mBackupHandler.post(
+                            () -> mTransportManager.onPackageChanged(pkgName, components));
                     return; // nothing more to do in the PACKAGE_CHANGED case
                 }
 
@@ -2033,7 +2046,7 @@
                 }
                 // If they're full-backup candidates, add them there instead
                 final long now = System.currentTimeMillis();
-                for (String packageName : pkgList) {
+                for (final String packageName : pkgList) {
                     try {
                         PackageInfo app = mPackageManager.getPackageInfo(packageName, 0);
                         if (appGetsFullBackup(app)
@@ -2050,7 +2063,8 @@
                             writeFullBackupScheduleAsync();
                         }
 
-                        mTransportManager.onPackageAdded(packageName);
+                        mBackupHandler.post(
+                                () -> mTransportManager.onPackageAdded(packageName));
 
                     } catch (NameNotFoundException e) {
                         // doesn't really exist; ignore it
@@ -2074,8 +2088,9 @@
                         removePackageParticipantsLocked(pkgList, uid);
                     }
                 }
-                for (String pkgName : pkgList) {
-                    mTransportManager.onPackageRemoved(pkgName);
+                for (final String pkgName : pkgList) {
+                    mBackupHandler.post(
+                            () -> mTransportManager.onPackageRemoved(pkgName));
                 }
             }
         }
diff --git a/com/android/server/backup/BackupManagerServiceInterface.java b/com/android/server/backup/BackupManagerServiceInterface.java
index 5dfa630..041f9ed 100644
--- a/com/android/server/backup/BackupManagerServiceInterface.java
+++ b/com/android/server/backup/BackupManagerServiceInterface.java
@@ -39,6 +39,8 @@
  */
 public interface BackupManagerServiceInterface {
 
+  void unlockSystemUser();
+
   // Utility: build a new random integer token
   int generateRandomIntegerToken();
 
diff --git a/com/android/server/backup/FullBackupJob.java b/com/android/server/backup/FullBackupJob.java
index 82638b4..b81a54d 100644
--- a/com/android/server/backup/FullBackupJob.java
+++ b/com/android/server/backup/FullBackupJob.java
@@ -61,7 +61,7 @@
     @Override
     public boolean onStartJob(JobParameters params) {
         mParams = params;
-        Trampoline service = BackupManagerService.getInstance();
+        Trampoline service = RefactoredBackupManagerService.getInstance();
         return service.beginFullBackup(this);
     }
 
@@ -69,7 +69,7 @@
     public boolean onStopJob(JobParameters params) {
         if (mParams != null) {
             mParams = null;
-            Trampoline service = BackupManagerService.getInstance();
+            Trampoline service = RefactoredBackupManagerService.getInstance();
             service.endFullBackup();
         }
         return false;
diff --git a/com/android/server/backup/KeyValueAdbBackupEngine.java b/com/android/server/backup/KeyValueAdbBackupEngine.java
index 279c828..b38b25a 100644
--- a/com/android/server/backup/KeyValueAdbBackupEngine.java
+++ b/com/android/server/backup/KeyValueAdbBackupEngine.java
@@ -4,8 +4,8 @@
 import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
 import static android.os.ParcelFileDescriptor.MODE_READ_WRITE;
 import static android.os.ParcelFileDescriptor.MODE_TRUNCATE;
-import static com.android.server.backup.BackupManagerService.OP_TYPE_BACKUP_WAIT;
-import static com.android.server.backup.BackupManagerService.TIMEOUT_BACKUP_INTERVAL;
+import static com.android.server.backup.RefactoredBackupManagerService.OP_TYPE_BACKUP_WAIT;
+import static com.android.server.backup.RefactoredBackupManagerService.TIMEOUT_BACKUP_INTERVAL;
 
 import android.app.ApplicationThreadConstants;
 import android.app.IBackupAgent;
@@ -19,6 +19,8 @@
 import android.os.SELinux;
 import android.util.Slog;
 
+import com.android.server.backup.utils.FullBackupUtils;
+
 import libcore.io.IoUtils;
 
 import java.io.File;
@@ -78,7 +80,7 @@
         mNewStateName = new File(mStateDir,
                 pkg + BACKUP_KEY_VALUE_NEW_STATE_FILENAME_SUFFIX);
 
-        mManifestFile = new File(mDataDir, BackupManagerService.BACKUP_MANIFEST_FILENAME);
+        mManifestFile = new File(mDataDir, RefactoredBackupManagerService.BACKUP_MANIFEST_FILENAME);
     }
 
     public void backupOnePackage() throws IOException {
@@ -188,7 +190,7 @@
                 if (DEBUG) {
                     Slog.d(TAG, "Writing manifest for " + mPackage.packageName);
                 }
-                BackupManagerService.writeAppManifest(
+                FullBackupUtils.writeAppManifest(
                         mPackage, mPackageManager, mManifestFile, false, false);
                 FullBackup.backupToTar(mPackage.packageName, FullBackup.KEY_VALUE_DATA_TOKEN, null,
                         mDataDir.getAbsolutePath(),
@@ -251,7 +253,7 @@
             t.start();
 
             // Now pull data from the app and stuff it into the output
-            BackupManagerService.routeSocketDataToOutput(pipes[0], mOutput);
+            FullBackupUtils.routeSocketDataToOutput(pipes[0], mOutput);
 
             if (!mBackupManagerService.waitUntilOperationComplete(token)) {
                 Slog.e(TAG, "Full backup failed on package " + mCurrentPackage.packageName);
diff --git a/com/android/server/backup/KeyValueAdbRestoreEngine.java b/com/android/server/backup/KeyValueAdbRestoreEngine.java
index b62bb5c..a2de8e7 100644
--- a/com/android/server/backup/KeyValueAdbRestoreEngine.java
+++ b/com/android/server/backup/KeyValueAdbRestoreEngine.java
@@ -13,6 +13,8 @@
 import android.os.RemoteException;
 import android.util.Slog;
 
+import com.android.server.backup.restore.PerformAdbRestoreTask;
+
 import libcore.io.IoUtils;
 
 import java.io.File;
@@ -41,7 +43,7 @@
     private final File mDataDir;
 
     FileMetadata mInfo;
-    BackupManagerService.PerformAdbRestoreTask mRestoreTask;
+    PerformAdbRestoreTask mRestoreTask;
     ParcelFileDescriptor mInFD;
     IBackupAgent mAgent;
     int mToken;
diff --git a/com/android/server/backup/KeyValueBackupJob.java b/com/android/server/backup/KeyValueBackupJob.java
index d8411e2..5dfb0bc 100644
--- a/com/android/server/backup/KeyValueBackupJob.java
+++ b/com/android/server/backup/KeyValueBackupJob.java
@@ -71,7 +71,7 @@
             if (delay <= 0) {
                 delay = interval + new Random().nextInt((int) fuzz);
             }
-            if (BackupManagerService.DEBUG_SCHEDULING) {
+            if (RefactoredBackupManagerService.DEBUG_SCHEDULING) {
                 Slog.v(TAG, "Scheduling k/v pass in " + (delay / 1000 / 60) + " minutes");
             }
             JobInfo.Builder builder = new JobInfo.Builder(JOB_ID, sKeyValueJobService)
@@ -110,7 +110,7 @@
         }
 
         // Time to run a key/value backup!
-        Trampoline service = BackupManagerService.getInstance();
+        Trampoline service = RefactoredBackupManagerService.getInstance();
         try {
             service.backupNow();
         } catch (RemoteException e) {}
diff --git a/com/android/server/backup/PackageManagerBackupAgent.java b/com/android/server/backup/PackageManagerBackupAgent.java
index 8d91e0d..f658f22 100644
--- a/com/android/server/backup/PackageManagerBackupAgent.java
+++ b/com/android/server/backup/PackageManagerBackupAgent.java
@@ -30,6 +30,8 @@
 import android.os.ParcelFileDescriptor;
 import android.util.Slog;
 
+import com.android.server.backup.utils.AppBackupUtils;
+
 import java.io.BufferedInputStream;
 import java.io.BufferedOutputStream;
 import java.io.ByteArrayInputStream;
@@ -140,7 +142,7 @@
         int N = pkgs.size();
         for (int a = N-1; a >= 0; a--) {
             PackageInfo pkg = pkgs.get(a);
-            if (!BackupManagerService.appIsEligibleForBackup(pkg.applicationInfo, pm)) {
+            if (!AppBackupUtils.appIsEligibleForBackup(pkg.applicationInfo, pm)) {
                 pkgs.remove(a);
             }
         }
diff --git a/com/android/server/backup/ProcessedPackagesJournal.java b/com/android/server/backup/ProcessedPackagesJournal.java
index 187d5d9..e29b7d5 100644
--- a/com/android/server/backup/ProcessedPackagesJournal.java
+++ b/com/android/server/backup/ProcessedPackagesJournal.java
@@ -21,7 +21,10 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.server.backup.RefactoredBackupManagerService;
 
+import java.io.BufferedInputStream;
+import java.io.DataInputStream;
 import java.io.EOFException;
+import java.io.FileInputStream;
 import java.io.File;
 import java.io.IOException;
 import java.io.RandomAccessFile;
@@ -130,7 +133,8 @@
             return;
         }
 
-        try (RandomAccessFile oldJournal = new RandomAccessFile(journalFile, "r")) {
+        try (DataInputStream oldJournal = new DataInputStream(
+                new BufferedInputStream(new FileInputStream(journalFile)))) {
             while (true) {
                 String packageName = oldJournal.readUTF();
                 if (DEBUG) {
diff --git a/com/android/server/backup/RefactoredBackupManagerService.java b/com/android/server/backup/RefactoredBackupManagerService.java
index 141f920..f298065 100644
--- a/com/android/server/backup/RefactoredBackupManagerService.java
+++ b/com/android/server/backup/RefactoredBackupManagerService.java
@@ -77,6 +77,7 @@
 import android.os.SELinux;
 import android.os.ServiceManager;
 import android.os.SystemClock;
+import android.os.Trace;
 import android.os.UserHandle;
 import android.os.storage.IStorageManager;
 import android.os.storage.StorageManager;
@@ -149,6 +150,7 @@
 import java.util.Random;
 import java.util.Set;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicInteger;
 
 public class RefactoredBackupManagerService implements BackupManagerServiceInterface {
 
@@ -546,39 +548,53 @@
         @Override
         public void onUnlockUser(int userId) {
             if (userId == UserHandle.USER_SYSTEM) {
-                sInstance.initialize(userId);
-
-                // Migrate legacy setting
-                if (!backupSettingMigrated(userId)) {
-                    if (DEBUG) {
-                        Slog.i(TAG, "Backup enable apparently not migrated");
-                    }
-                    final ContentResolver r = sInstance.mContext.getContentResolver();
-                    final int enableState = Settings.Secure.getIntForUser(r,
-                            Settings.Secure.BACKUP_ENABLED, -1, userId);
-                    if (enableState >= 0) {
-                        if (DEBUG) {
-                            Slog.i(TAG, "Migrating enable state " + (enableState != 0));
-                        }
-                        writeBackupEnableState(enableState != 0, userId);
-                        Settings.Secure.putStringForUser(r,
-                                Settings.Secure.BACKUP_ENABLED, null, userId);
-                    } else {
-                        if (DEBUG) {
-                            Slog.i(TAG, "Backup not yet configured; retaining null enable state");
-                        }
-                    }
-                }
-
-                try {
-                    sInstance.setBackupEnabled(readBackupEnableState(userId));
-                } catch (RemoteException e) {
-                    // can't happen; it's a local object
-                }
+                sInstance.unlockSystemUser();
             }
         }
     }
 
+    // Called through the trampoline from onUnlockUser(), then we buck the work
+    // off to the background thread to keep the unlock time down.
+    public void unlockSystemUser() {
+        mBackupHandler.post(() -> {
+            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup init");
+            sInstance.initialize(UserHandle.USER_SYSTEM);
+            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+
+            // Migrate legacy setting
+            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup migrate");
+            if (!backupSettingMigrated(UserHandle.USER_SYSTEM)) {
+                if (DEBUG) {
+                    Slog.i(TAG, "Backup enable apparently not migrated");
+                }
+                final ContentResolver r = sInstance.mContext.getContentResolver();
+                final int enableState = Settings.Secure.getIntForUser(r,
+                        Settings.Secure.BACKUP_ENABLED, -1, UserHandle.USER_SYSTEM);
+                if (enableState >= 0) {
+                    if (DEBUG) {
+                        Slog.i(TAG, "Migrating enable state " + (enableState != 0));
+                    }
+                    writeBackupEnableState(enableState != 0, UserHandle.USER_SYSTEM);
+                    Settings.Secure.putStringForUser(r,
+                            Settings.Secure.BACKUP_ENABLED, null, UserHandle.USER_SYSTEM);
+                } else {
+                    if (DEBUG) {
+                        Slog.i(TAG, "Backup not yet configured; retaining null enable state");
+                    }
+                }
+            }
+            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+
+            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backup enable");
+            try {
+                sInstance.setBackupEnabled(readBackupEnableState(UserHandle.USER_SYSTEM));
+            } catch (RemoteException e) {
+                // can't happen; it's a local object
+            }
+            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+        });
+    }
+
     // Bookkeeping of in-flight operations for timeout etc. purposes.  The operation
     // token is the index of the entry in the pending-operations list.
     public static final int OP_PENDING = 0;
@@ -619,6 +635,7 @@
     private final SparseArray<Operation> mCurrentOperations = new SparseArray<>();
     private final Object mCurrentOpLock = new Object();
     private final Random mTokenGenerator = new Random();
+    final AtomicInteger mNextToken = new AtomicInteger();
 
     private final SparseArray<AdbParams> mAdbBackupRestoreConfirmations = new SparseArray<>();
 
@@ -644,7 +661,7 @@
 
     // Persistently track the need to do a full init
     private static final String INIT_SENTINEL_FILE_NAME = "_need_init_";
-    private ArraySet<String> mPendingInits = new ArraySet<>();  // transport names
+    private final ArraySet<String> mPendingInits = new ArraySet<>();  // transport names
 
     // Round-robin queue for scheduling full backup passes
     private static final int SCHEDULE_FILE_VERSION = 1; // current version of the schedule file
@@ -658,18 +675,41 @@
     @GuardedBy("mQueueLock")
     private ArrayList<FullBackupEntry> mFullBackupQueue;
 
-    // Utility: build a new random integer token
+    // Utility: build a new random integer token. The low bits are the ordinal of the
+    // operation for near-time uniqueness, and the upper bits are random for app-
+    // side unpredictability.
     @Override
     public int generateRandomIntegerToken() {
-        int token;
-        do {
-            synchronized (mTokenGenerator) {
-                token = mTokenGenerator.nextInt();
-            }
-        } while (token < 0);
+        int token = mTokenGenerator.nextInt();
+        if (token < 0) token = -token;
+        token &= ~0xFF;
+        token |= (mNextToken.incrementAndGet() & 0xFF);
         return token;
     }
 
+    /*
+     * Construct a backup agent instance for the metadata pseudopackage.  This is a
+     * process-local non-lifecycle agent instance, so we manually set up the context
+     * topology for it.
+     */
+    public PackageManagerBackupAgent makeMetadataAgent() {
+        PackageManagerBackupAgent pmAgent = new PackageManagerBackupAgent(mPackageManager);
+        pmAgent.attach(mContext);
+        pmAgent.onCreate();
+        return pmAgent;
+    }
+
+    /*
+     * Same as above but with the explicit package-set configuration.
+     */
+    public PackageManagerBackupAgent makeMetadataAgent(List<PackageInfo> packages) {
+        PackageManagerBackupAgent pmAgent =
+                new PackageManagerBackupAgent(mPackageManager, packages);
+        pmAgent.attach(mContext);
+        pmAgent.onCreate();
+        return pmAgent;
+    }
+
     // ----- Debug-only backup operation trace -----
     public void addBackupTrace(String s) {
         if (DEBUG_BACKUP_TRACE) {
@@ -800,17 +840,18 @@
 
         // Remember our ancestral dataset
         mTokenFile = new File(mBaseStateDir, "ancestral");
-        try (RandomAccessFile tf = new RandomAccessFile(mTokenFile, "r")) {
-            int version = tf.readInt();
+        try (DataInputStream tokenStream = new DataInputStream(new BufferedInputStream(
+                new FileInputStream(mTokenFile)))) {
+            int version = tokenStream.readInt();
             if (version == CURRENT_ANCESTRAL_RECORD_VERSION) {
-                mAncestralToken = tf.readLong();
-                mCurrentToken = tf.readLong();
+                mAncestralToken = tokenStream.readLong();
+                mCurrentToken = tokenStream.readLong();
 
-                int numPackages = tf.readInt();
+                int numPackages = tokenStream.readInt();
                 if (numPackages >= 0) {
                     mAncestralPackages = new HashSet<>();
                     for (int i = 0; i < numPackages; i++) {
-                        String pkgName = tf.readUTF();
+                        String pkgName = tokenStream.readUTF();
                         mAncestralPackages.add(pkgName);
                     }
                 }
@@ -878,7 +919,7 @@
                         PackageInfo pkg = mPackageManager.getPackageInfo(pkgName, 0);
                         if (AppBackupUtils.appGetsFullBackup(pkg)
                                 && AppBackupUtils.appIsEligibleForBackup(
-                                pkg.applicationInfo)) {
+                                pkg.applicationInfo, mPackageManager)) {
                             schedule.add(new FullBackupEntry(pkgName, lastBackup));
                         } else {
                             if (DEBUG) {
@@ -899,7 +940,7 @@
                 for (PackageInfo app : apps) {
                     if (AppBackupUtils.appGetsFullBackup(app)
                             && AppBackupUtils.appIsEligibleForBackup(
-                            app.applicationInfo)) {
+                            app.applicationInfo, mPackageManager)) {
                         if (!foundApps.contains(app.packageName)) {
                             if (MORE_DEBUG) {
                                 Slog.i(TAG, "New full backup app " + app.packageName + " found");
@@ -925,7 +966,7 @@
             schedule = new ArrayList<>(apps.size());
             for (PackageInfo info : apps) {
                 if (AppBackupUtils.appGetsFullBackup(info) && AppBackupUtils.appIsEligibleForBackup(
-                        info.applicationInfo)) {
+                        info.applicationInfo, mPackageManager)) {
                     schedule.add(new FullBackupEntry(info.packageName, 0));
                 }
             }
@@ -1155,7 +1196,7 @@
                 if (uri == null) {
                     return;
                 }
-                String pkgName = uri.getSchemeSpecificPart();
+                final String pkgName = uri.getSchemeSpecificPart();
                 if (pkgName != null) {
                     pkgList = new String[]{pkgName};
                 }
@@ -1163,7 +1204,7 @@
 
                 // At package-changed we only care about looking at new transport states
                 if (changed) {
-                    String[] components =
+                    final String[] components =
                             intent.getStringArrayExtra(Intent.EXTRA_CHANGED_COMPONENT_NAME_LIST);
 
                     if (MORE_DEBUG) {
@@ -1173,7 +1214,8 @@
                         }
                     }
 
-                    mTransportManager.onPackageChanged(pkgName, components);
+                    mBackupHandler.post(
+                            () -> mTransportManager.onPackageChanged(pkgName, components));
                     return; // nothing more to do in the PACKAGE_CHANGED case
                 }
 
@@ -1205,12 +1247,12 @@
                 }
                 // If they're full-backup candidates, add them there instead
                 final long now = System.currentTimeMillis();
-                for (String packageName : pkgList) {
+                for (final String packageName : pkgList) {
                     try {
                         PackageInfo app = mPackageManager.getPackageInfo(packageName, 0);
                         if (AppBackupUtils.appGetsFullBackup(app)
                                 && AppBackupUtils.appIsEligibleForBackup(
-                                app.applicationInfo)) {
+                                app.applicationInfo, mPackageManager)) {
                             enqueueFullBackup(packageName, now);
                             scheduleNextFullBackupJob(0);
                         } else {
@@ -1223,7 +1265,8 @@
                             writeFullBackupScheduleAsync();
                         }
 
-                        mTransportManager.onPackageAdded(packageName);
+                        mBackupHandler.post(
+                                () -> mTransportManager.onPackageAdded(packageName));
 
                     } catch (NameNotFoundException e) {
                         // doesn't really exist; ignore it
@@ -1247,8 +1290,9 @@
                         removePackageParticipantsLocked(pkgList, uid);
                     }
                 }
-                for (String pkgName : pkgList) {
-                    mTransportManager.onPackageRemoved(pkgName);
+                for (final String pkgName : pkgList) {
+                    mBackupHandler.post(
+                            () -> mTransportManager.onPackageRemoved(pkgName));
                 }
             }
         }
@@ -1507,7 +1551,7 @@
 
         long token = mAncestralToken;
         synchronized (mQueueLock) {
-            if (mProcessedPackagesJournal.hasBeenProcessed(packageName)) {
+            if (mCurrentToken != 0 && mProcessedPackagesJournal.hasBeenProcessed(packageName)) {
                 if (MORE_DEBUG) {
                     Slog.i(TAG, "App in ever-stored, so using current token");
                 }
@@ -1568,7 +1612,8 @@
             try {
                 PackageInfo packageInfo = mPackageManager.getPackageInfo(packageName,
                         PackageManager.GET_SIGNATURES);
-                if (!AppBackupUtils.appIsEligibleForBackup(packageInfo.applicationInfo)) {
+                if (!AppBackupUtils.appIsEligibleForBackup(packageInfo.applicationInfo,
+                        mPackageManager)) {
                     BackupObserverUtils.sendBackupOnPackageResult(observer, packageName,
                             BackupManager.ERROR_BACKUP_NOT_ALLOWED);
                     continue;
@@ -1759,8 +1804,12 @@
                 // Can't delete op from mCurrentOperations here. waitUntilOperationComplete may be
                 // called after we receive cancel here. We need this op's state there.
 
-                // Remove all pending timeout messages for this operation type.
-                mBackupHandler.removeMessages(getMessageIdForOperationType(op.type));
+                // Remove all pending timeout messages of types OP_TYPE_BACKUP_WAIT and
+                // OP_TYPE_RESTORE_WAIT. On the other hand, OP_TYPE_BACKUP cannot time out and
+                // doesn't require cancellation.
+                if (op.type == OP_TYPE_BACKUP_WAIT || op.type == OP_TYPE_RESTORE_WAIT) {
+                    mBackupHandler.removeMessages(getMessageIdForOperationType(op.type));
+                }
             }
             mCurrentOpLock.notifyAll();
         }
@@ -2108,14 +2157,26 @@
     // so tear down any ongoing backup task right away.
     @Override
     public void endFullBackup() {
-        synchronized (mQueueLock) {
-            if (mRunningFullBackupTask != null) {
-                if (DEBUG_SCHEDULING) {
-                    Slog.i(TAG, "Telling running backup to stop");
+        // offload the mRunningFullBackupTask.handleCancel() call to another thread,
+        // as we might have to wait for mCancelLock
+        Runnable endFullBackupRunnable = new Runnable() {
+            @Override
+            public void run() {
+                PerformFullTransportBackupTask pftbt = null;
+                synchronized (mQueueLock) {
+                    if (mRunningFullBackupTask != null) {
+                        pftbt = mRunningFullBackupTask;
+                    }
                 }
-                mRunningFullBackupTask.handleCancel(true);
+                if (pftbt != null) {
+                    if (DEBUG_SCHEDULING) {
+                        Slog.i(TAG, "Telling running backup to stop");
+                    }
+                    pftbt.handleCancel(true);
+                }
             }
-        }
+        };
+        new Thread(endFullBackupRunnable, "end-full-backup").start();
     }
 
     // Used by both incremental and full restore
@@ -2800,8 +2861,7 @@
         final long oldId = Binder.clearCallingIdentity();
         try {
             String prevTransport = mTransportManager.selectTransport(transport);
-            Settings.Secure.putString(mContext.getContentResolver(),
-                    Settings.Secure.BACKUP_TRANSPORT, transport);
+            updateStateForTransport(transport);
             Slog.v(TAG, "selectBackupTransport() set " + mTransportManager.getCurrentTransportName()
                     + " returning " + prevTransport);
             return prevTransport;
@@ -2826,9 +2886,7 @@
                     @Override
                     public void onSuccess(String transportName) {
                         mTransportManager.selectTransport(transportName);
-                        Settings.Secure.putString(mContext.getContentResolver(),
-                                Settings.Secure.BACKUP_TRANSPORT,
-                                mTransportManager.getCurrentTransportName());
+                        updateStateForTransport(mTransportManager.getCurrentTransportName());
                         Slog.v(TAG, "Transport successfully selected: "
                                 + transport.flattenToShortString());
                         try {
@@ -2853,6 +2911,28 @@
         Binder.restoreCallingIdentity(oldId);
     }
 
+    private void updateStateForTransport(String newTransportName) {
+        // Publish the name change
+        Settings.Secure.putString(mContext.getContentResolver(),
+                Settings.Secure.BACKUP_TRANSPORT, newTransportName);
+
+        // And update our current-dataset bookkeeping
+        IBackupTransport transport = mTransportManager.getTransportBinder(newTransportName);
+        if (transport != null) {
+            try {
+                mCurrentToken = transport.getCurrentRestoreSet();
+            } catch (Exception e) {
+                // Oops.  We can't know the current dataset token, so reset and figure it out
+                // when we do the next k/v backup operation on this transport.
+                mCurrentToken = 0;
+            }
+        } else {
+            // The named transport isn't bound at this particular moment, so we can't
+            // know yet what its current dataset token is.  Reset as above.
+            mCurrentToken = 0;
+        }
+    }
+
     // Supply the configuration Intent for the given transport.  If the name is not one
     // of the available transports, or if the transport does not supply any configuration
     // UI, the method returns null.
@@ -3162,19 +3242,6 @@
         }
     }
 
-    // We also avoid backups of 'disabled' apps
-    private static boolean appIsDisabled(ApplicationInfo app, PackageManager pm) {
-        switch (pm.getApplicationEnabledSetting(app.packageName)) {
-            case PackageManager.COMPONENT_ENABLED_STATE_DISABLED:
-            case PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER:
-            case PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED:
-                return true;
-
-            default:
-                return false;
-        }
-    }
-
     @Override
     public boolean isAppEligibleForBackup(String packageName) {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
@@ -3182,9 +3249,10 @@
         try {
             PackageInfo packageInfo = mPackageManager.getPackageInfo(packageName,
                     PackageManager.GET_SIGNATURES);
-            if (!AppBackupUtils.appIsEligibleForBackup(packageInfo.applicationInfo) ||
+            if (!AppBackupUtils.appIsEligibleForBackup(packageInfo.applicationInfo,
+                            mPackageManager) ||
                     AppBackupUtils.appIsStopped(packageInfo.applicationInfo) ||
-                    appIsDisabled(packageInfo.applicationInfo, mPackageManager)) {
+                    AppBackupUtils.appIsDisabled(packageInfo.applicationInfo, mPackageManager)) {
                 return false;
             }
             IBackupTransport transport = mTransportManager.getCurrentTransportBinder();
diff --git a/com/android/server/backup/Trampoline.java b/com/android/server/backup/Trampoline.java
index fcd929a..9739e38 100644
--- a/com/android/server/backup/Trampoline.java
+++ b/com/android/server/backup/Trampoline.java
@@ -98,7 +98,7 @@
 
     protected boolean isRefactoredServiceEnabled() {
         return Settings.Global.getInt(mContext.getContentResolver(),
-                Settings.Global.BACKUP_REFACTORED_SERVICE_DISABLED, 1) == 0;
+                Settings.Global.BACKUP_REFACTORED_SERVICE_DISABLED, 0) == 0;
     }
 
     protected int binderGetCallingUid() {
@@ -139,6 +139,13 @@
         }
     }
 
+    void unlockSystemUser() {
+        BackupManagerServiceInterface svc = mService;
+        if (svc != null) {
+            svc.unlockSystemUser();
+        }
+    }
+
     public void setBackupServiceActive(final int userHandle, boolean makeActive) {
         // Only the DPM should be changing the active state of backup
         final int caller = binderGetCallingUid();
diff --git a/com/android/server/backup/TransportManager.java b/com/android/server/backup/TransportManager.java
index 9aae384..7a0173f 100644
--- a/com/android/server/backup/TransportManager.java
+++ b/com/android/server/backup/TransportManager.java
@@ -341,9 +341,9 @@
     private class TransportConnection implements ServiceConnection {
 
         // Hold mTransportsLock to access these fields so as to provide a consistent view of them.
-        private IBackupTransport mBinder;
+        private volatile IBackupTransport mBinder;
         private final List<TransportReadyCallback> mListeners = new ArrayList<>();
-        private String mTransportName;
+        private volatile String mTransportName;
 
         private final ComponentName mTransportComponent;
 
@@ -426,25 +426,24 @@
                     + rebindTimeout + "ms");
         }
 
+        // Intentionally not synchronized -- the variable is volatile and changes to its value
+        // are inside synchronized blocks, providing a memory sync barrier; and this method
+        // does not touch any other state protected by that lock.
         private IBackupTransport getBinder() {
-            synchronized (mTransportLock) {
-                return mBinder;
-            }
+            return mBinder;
         }
 
+        // Intentionally not synchronized; same as getBinder()
         private String getName() {
-            synchronized (mTransportLock) {
-                return mTransportName;
-            }
+            return mTransportName;
         }
 
+        // Intentionally not synchronized; same as getBinder()
         private void bindIfUnbound() {
-            synchronized (mTransportLock) {
-                if (mBinder == null) {
-                    Slog.d(TAG,
-                            "Rebinding to transport " + mTransportComponent.flattenToShortString());
-                    bindToTransport(mTransportComponent, this);
-                }
+            if (mBinder == null) {
+                Slog.d(TAG,
+                        "Rebinding to transport " + mTransportComponent.flattenToShortString());
+                bindToTransport(mTransportComponent, this);
             }
         }
 
diff --git a/com/android/server/backup/fullbackup/PerformAdbBackupTask.java b/com/android/server/backup/fullbackup/PerformAdbBackupTask.java
index 4085f63..f0b3e4a 100644
--- a/com/android/server/backup/fullbackup/PerformAdbBackupTask.java
+++ b/com/android/server/backup/fullbackup/PerformAdbBackupTask.java
@@ -236,12 +236,11 @@
         obbConnection.establish();  // we'll want this later
 
         sendStartBackup();
+        PackageManager pm = backupManagerService.getPackageManager();
 
         // doAllApps supersedes the package set if any
         if (mAllApps) {
-            List<PackageInfo> allPackages =
-                    backupManagerService.getPackageManager().getInstalledPackages(
-                            PackageManager.GET_SIGNATURES);
+            List<PackageInfo> allPackages = pm.getInstalledPackages(PackageManager.GET_SIGNATURES);
             for (int i = 0; i < allPackages.size(); i++) {
                 PackageInfo pkg = allPackages.get(i);
                 // Exclude system apps if we've been asked to do so
@@ -288,7 +287,7 @@
         Iterator<Entry<String, PackageInfo>> iter = packagesToBackup.entrySet().iterator();
         while (iter.hasNext()) {
             PackageInfo pkg = iter.next().getValue();
-            if (!AppBackupUtils.appIsEligibleForBackup(pkg.applicationInfo)
+            if (!AppBackupUtils.appIsEligibleForBackup(pkg.applicationInfo, pm)
                     || AppBackupUtils.appIsStopped(pkg.applicationInfo)) {
                 iter.remove();
                 if (DEBUG) {
diff --git a/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java b/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
index bc7c117..90134e1 100644
--- a/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
+++ b/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
@@ -140,10 +140,10 @@
 
         for (String pkg : whichPackages) {
             try {
-                PackageInfo info = backupManagerService.getPackageManager().getPackageInfo(pkg,
-                        PackageManager.GET_SIGNATURES);
+                PackageManager pm = backupManagerService.getPackageManager();
+                PackageInfo info = pm.getPackageInfo(pkg, PackageManager.GET_SIGNATURES);
                 mCurrentPackage = info;
-                if (!AppBackupUtils.appIsEligibleForBackup(info.applicationInfo)) {
+                if (!AppBackupUtils.appIsEligibleForBackup(info.applicationInfo, pm)) {
                     // Cull any packages that have indicated that backups are not permitted,
                     // that run as system-domain uids but do not define their own backup agents,
                     // as well as any explicit mention of the 'special' shared-storage agent
@@ -306,6 +306,7 @@
             final int N = mPackages.size();
             final byte[] buffer = new byte[8192];
             for (int i = 0; i < N; i++) {
+                mBackupRunner = null;
                 PackageInfo currentPackage = mPackages.get(i);
                 String packageName = currentPackage.packageName;
                 if (DEBUG) {
@@ -491,7 +492,13 @@
                     }
                     EventLog.writeEvent(EventLogTags.FULL_BACKUP_AGENT_FAILURE, packageName,
                             "transport rejected");
-                    // Do nothing, clean up, and continue looping.
+                    // This failure state can come either a-priori from the transport, or
+                    // from the preflight pass.  If we got as far as preflight, we now need
+                    // to tear down the target process.
+                    if (mBackupRunner != null) {
+                        backupManagerService.tearDownAgentAndKill(currentPackage.applicationInfo);
+                    }
+                    // ... and continue looping.
                 } else if (backupPackageStatus == BackupTransport.TRANSPORT_QUOTA_EXCEEDED) {
                     BackupObserverUtils
                             .sendBackupOnPackageResult(mBackupObserver, packageName,
@@ -501,6 +508,7 @@
                         EventLog.writeEvent(EventLogTags.FULL_BACKUP_QUOTA_EXCEEDED,
                                 packageName);
                     }
+                    backupManagerService.tearDownAgentAndKill(currentPackage.applicationInfo);
                     // Do nothing, clean up, and continue looping.
                 } else if (backupPackageStatus == BackupTransport.AGENT_ERROR) {
                     BackupObserverUtils
@@ -527,6 +535,7 @@
                     EventLog.writeEvent(EventLogTags.FULL_BACKUP_TRANSPORT_FAILURE);
                     // Abort entire backup pass.
                     backupRunStatus = BackupManager.ERROR_TRANSPORT_ABORTED;
+                    backupManagerService.tearDownAgentAndKill(currentPackage.applicationInfo);
                     return;
                 } else {
                     // Success!
diff --git a/com/android/server/backup/internal/PerformBackupTask.java b/com/android/server/backup/internal/PerformBackupTask.java
index ce4f906..7a8a920 100644
--- a/com/android/server/backup/internal/PerformBackupTask.java
+++ b/com/android/server/backup/internal/PerformBackupTask.java
@@ -227,9 +227,8 @@
                     if (!mFinished) {
                         finalizeBackup();
                     } else {
-                        Slog.e(TAG, "Duplicate finish");
+                        Slog.e(TAG, "Duplicate finish of K/V pass");
                     }
-                    mFinished = true;
                     break;
             }
         }
@@ -322,8 +321,7 @@
                 // because it's cheap and this way we guarantee that we don't get out of
                 // step even if we're selecting among various transports at run time.
                 if (mStatus == BackupTransport.TRANSPORT_OK) {
-                    PackageManagerBackupAgent pmAgent = new PackageManagerBackupAgent(
-                            backupManagerService.getPackageManager());
+                    PackageManagerBackupAgent pmAgent = backupManagerService.makeMetadataAgent();
                     mStatus = invokeAgentForBackup(
                             PACKAGE_MANAGER_SENTINEL,
                             IBackupAgent.Stub.asInterface(pmAgent.onBind()), mTransport);
@@ -391,11 +389,9 @@
         // to sanity-check here.  This also gives us the classname of the
         // package's backup agent.
         try {
-            mCurrentPackage = backupManagerService.getPackageManager().getPackageInfo(
-                    request.packageName,
-                    PackageManager.GET_SIGNATURES);
-            if (!AppBackupUtils.appIsEligibleForBackup(
-                    mCurrentPackage.applicationInfo)) {
+            PackageManager pm = backupManagerService.getPackageManager();
+            mCurrentPackage = pm.getPackageInfo(request.packageName, PackageManager.GET_SIGNATURES);
+            if (!AppBackupUtils.appIsEligibleForBackup(mCurrentPackage.applicationInfo, pm)) {
                 // The manifest has changed but we had a stale backup request pending.
                 // This won't happen again because the app won't be requesting further
                 // backups.
@@ -609,6 +605,7 @@
                     break;
             }
         }
+        mFinished = true;
         Slog.i(TAG, "K/V backup pass finished.");
         // Only once we're entirely finished do we release the wakelock for k/v backup.
         backupManagerService.getWakelock().release();
diff --git a/com/android/server/backup/internal/RunInitializeReceiver.java b/com/android/server/backup/internal/RunInitializeReceiver.java
index a6897d0..1df0bf0 100644
--- a/com/android/server/backup/internal/RunInitializeReceiver.java
+++ b/com/android/server/backup/internal/RunInitializeReceiver.java
@@ -23,6 +23,7 @@
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
+import android.util.ArraySet;
 import android.util.Slog;
 
 import com.android.server.backup.RefactoredBackupManagerService;
@@ -38,19 +39,22 @@
     public void onReceive(Context context, Intent intent) {
         if (RUN_INITIALIZE_ACTION.equals(intent.getAction())) {
             synchronized (backupManagerService.getQueueLock()) {
+                final ArraySet<String> pendingInits = backupManagerService.getPendingInits();
                 if (DEBUG) {
-                    Slog.v(TAG, "Running a device init");
+                    Slog.v(TAG, "Running a device init; " + pendingInits.size() + " pending");
                 }
 
-                String[] pendingInits = (String[]) backupManagerService.getPendingInits().toArray();
-                backupManagerService.clearPendingInits();
-                PerformInitializeTask initTask = new PerformInitializeTask(backupManagerService,
-                        pendingInits, null);
+                if (pendingInits.size() > 0) {
+                    final String[] transports = pendingInits.toArray(new String[pendingInits.size()]);
+                    PerformInitializeTask initTask = new PerformInitializeTask(backupManagerService,
+                            transports, null);
 
-                // Acquire the wakelock and pass it to the init thread.  it will
-                // be released once init concludes.
-                backupManagerService.getWakelock().acquire();
-                backupManagerService.getBackupHandler().post(initTask);
+                    // Acquire the wakelock and pass it to the init thread.  it will
+                    // be released once init concludes.
+                    backupManagerService.clearPendingInits();
+                    backupManagerService.getWakelock().acquire();
+                    backupManagerService.getBackupHandler().post(initTask);
+                }
             }
         }
     }
diff --git a/com/android/server/backup/restore/PerformAdbRestoreTask.java b/com/android/server/backup/restore/PerformAdbRestoreTask.java
index 62ae065..22691bb 100644
--- a/com/android/server/backup/restore/PerformAdbRestoreTask.java
+++ b/com/android/server/backup/restore/PerformAdbRestoreTask.java
@@ -150,8 +150,7 @@
         mObserver = observer;
         mLatchObject = latch;
         mAgent = null;
-        mPackageManagerBackupAgent = new PackageManagerBackupAgent(
-                backupManagerService.getPackageManager());
+        mPackageManagerBackupAgent = backupManagerService.makeMetadataAgent();
         mAgentPackage = null;
         mTargetApp = null;
         mObbConnection = new FullBackupObbConnection(backupManagerService);
diff --git a/com/android/server/backup/restore/PerformUnifiedRestoreTask.java b/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
index 21d5dc2..b538c6d 100644
--- a/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
+++ b/com/android/server/backup/restore/PerformUnifiedRestoreTask.java
@@ -198,8 +198,8 @@
             boolean hasSettings = false;
             for (int i = 0; i < filterSet.length; i++) {
                 try {
-                    PackageInfo info = backupManagerService.getPackageManager().getPackageInfo(
-                            filterSet[i], 0);
+                    PackageManager pm = backupManagerService.getPackageManager();
+                    PackageInfo info = pm.getPackageInfo(filterSet[i], 0);
                     if ("android".equals(info.packageName)) {
                         hasSystem = true;
                         continue;
@@ -209,8 +209,7 @@
                         continue;
                     }
 
-                    if (AppBackupUtils.appIsEligibleForBackup(
-                            info.applicationInfo)) {
+                    if (AppBackupUtils.appIsEligibleForBackup(info.applicationInfo, pm)) {
                         mAcceptSet.add(info);
                     }
                 } catch (NameNotFoundException e) {
@@ -387,8 +386,7 @@
             // Pull the Package Manager metadata from the restore set first
             mCurrentPackage = new PackageInfo();
             mCurrentPackage.packageName = PACKAGE_MANAGER_SENTINEL;
-            mPmAgent = new PackageManagerBackupAgent(backupManagerService.getPackageManager(),
-                    null);
+            mPmAgent = backupManagerService.makeMetadataAgent(null);
             mAgent = IBackupAgent.Stub.asInterface(mPmAgent.onBind());
             if (MORE_DEBUG) {
                 Slog.v(TAG, "initiating restore for PMBA");
@@ -779,6 +777,9 @@
 
     // state RESTORE_FINISHED : provide the "no more data" signpost callback at the end
     private void restoreFinished() {
+        if (DEBUG) {
+            Slog.d(TAG, "restoreFinished packageName=" + mCurrentPackage.packageName);
+        }
         try {
             backupManagerService
                     .prepareOperationTimeout(mEphemeralOpToken,
diff --git a/com/android/server/backup/utils/AppBackupUtils.java b/com/android/server/backup/utils/AppBackupUtils.java
index 4abf18a..d7cac77 100644
--- a/com/android/server/backup/utils/AppBackupUtils.java
+++ b/com/android/server/backup/utils/AppBackupUtils.java
@@ -22,6 +22,7 @@
 
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
 import android.content.pm.Signature;
 import android.os.Process;
 import android.util.Slog;
@@ -44,7 +45,7 @@
      *     <li>it is the special shared-storage backup package used for 'adb backup'
      * </ol>
      */
-    public static boolean appIsEligibleForBackup(ApplicationInfo app) {
+    public static boolean appIsEligibleForBackup(ApplicationInfo app, PackageManager pm) {
         // 1. their manifest states android:allowBackup="false"
         if ((app.flags & ApplicationInfo.FLAG_ALLOW_BACKUP) == 0) {
             return false;
@@ -60,11 +61,33 @@
             return false;
         }
 
-        return true;
+        // 4. it is an "instant" app
+        if (app.isInstantApp()) {
+            return false;
+        }
+
+        // Everything else checks out; the only remaining roadblock would be if the
+        // package were disabled
+        return !appIsDisabled(app, pm);
+    }
+
+    /** Avoid backups of 'disabled' apps. */
+    public static boolean appIsDisabled(ApplicationInfo app, PackageManager pm) {
+        switch (pm.getApplicationEnabledSetting(app.packageName)) {
+            case PackageManager.COMPONENT_ENABLED_STATE_DISABLED:
+            case PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER:
+            case PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED:
+                return true;
+
+            default:
+                return false;
+        }
     }
 
     /**
-     * Checks if the app is in a stopped state, that means it won't receive broadcasts.
+     * Checks if the app is in a stopped state.  This is not part of the general "eligible for
+     * backup?" check because we *do* still need to restore data to apps in this state (e.g.
+     * newly-installing ones)
      */
     public static boolean appIsStopped(ApplicationInfo app) {
         return ((app.flags & ApplicationInfo.FLAG_STOPPED) != 0);
diff --git a/com/android/server/connectivity/IpConnectivityEventBuilder.java b/com/android/server/connectivity/IpConnectivityEventBuilder.java
index 22330e6..67e7216 100644
--- a/com/android/server/connectivity/IpConnectivityEventBuilder.java
+++ b/com/android/server/connectivity/IpConnectivityEventBuilder.java
@@ -126,7 +126,7 @@
         wakeupStats.systemWakeups = in.systemWakeups;
         wakeupStats.nonApplicationWakeups = in.nonApplicationWakeups;
         wakeupStats.applicationWakeups = in.applicationWakeups;
-        wakeupStats.unroutedWakeups = in.unroutedWakeups;
+        wakeupStats.noUidWakeups = in.noUidWakeups;
         final IpConnectivityEvent out = buildEvent(0, 0, in.iface);
         out.setWakeupStats(wakeupStats);
         return out;
diff --git a/com/android/server/connectivity/IpConnectivityMetrics.java b/com/android/server/connectivity/IpConnectivityMetrics.java
index 475d786..f2445fa 100644
--- a/com/android/server/connectivity/IpConnectivityMetrics.java
+++ b/com/android/server/connectivity/IpConnectivityMetrics.java
@@ -34,6 +34,7 @@
 import android.util.Log;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.RingBuffer;
 import com.android.internal.util.TokenBucket;
 import com.android.server.SystemService;
 import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;
@@ -44,7 +45,11 @@
 import java.util.List;
 import java.util.function.ToIntFunction;
 
-/** {@hide} */
+/**
+ * Event buffering service for core networking and connectivity metrics.
+ *
+ * {@hide}
+ */
 final public class IpConnectivityMetrics extends SystemService {
     private static final String TAG = IpConnectivityMetrics.class.getSimpleName();
     private static final boolean DBG = false;
@@ -58,7 +63,10 @@
 
     private static final String SERVICE_NAME = IpConnectivityLog.SERVICE_NAME;
 
-    // Default size of the event buffer. Once the buffer is full, incoming events are dropped.
+    // Default size of the event rolling log for bug report dumps.
+    private static final int DEFAULT_LOG_SIZE = 500;
+    // Default size of the event buffer for metrics reporting.
+    // Once the buffer is full, incoming events are dropped.
     private static final int DEFAULT_BUFFER_SIZE = 2000;
     // Maximum size of the event buffer.
     private static final int MAXIMUM_BUFFER_SIZE = DEFAULT_BUFFER_SIZE * 10;
@@ -67,24 +75,38 @@
 
     private static final int ERROR_RATE_LIMITED = -1;
 
-    // Lock ensuring that concurrent manipulations of the event buffer are correct.
+    // Lock ensuring that concurrent manipulations of the event buffers are correct.
     // There are three concurrent operations to synchronize:
     //  - appending events to the buffer.
     //  - iterating throught the buffer.
     //  - flushing the buffer content and replacing it by a new buffer.
     private final Object mLock = new Object();
 
+    // Implementation instance of IIpConnectivityMetrics.aidl.
     @VisibleForTesting
     public final Impl impl = new Impl();
+    // Subservice listening to Netd events via INetdEventListener.aidl.
     @VisibleForTesting
     NetdEventListenerService mNetdListener;
 
+    // Rolling log of the most recent events. This log is used for dumping
+    // connectivity events in bug reports.
+    @GuardedBy("mLock")
+    private final RingBuffer<ConnectivityMetricsEvent> mEventLog =
+            new RingBuffer(ConnectivityMetricsEvent.class, DEFAULT_LOG_SIZE);
+    // Buffer of connectivity events used for metrics reporting. This buffer
+    // does not rotate automatically and instead saturates when it becomes full.
+    // It is flushed at metrics reporting.
     @GuardedBy("mLock")
     private ArrayList<ConnectivityMetricsEvent> mBuffer;
+    // Total number of events dropped from mBuffer since last metrics reporting.
     @GuardedBy("mLock")
     private int mDropped;
+    // Capacity of mBuffer
     @GuardedBy("mLock")
     private int mCapacity;
+    // A list of rate limiting counters keyed by connectivity event types for
+    // metrics reporting mBuffer.
     @GuardedBy("mLock")
     private final ArrayMap<Class<?>, TokenBucket> mBuckets = makeRateLimitingBuckets();
 
@@ -132,6 +154,7 @@
     private int append(ConnectivityMetricsEvent event) {
         if (DBG) Log.d(TAG, "logEvent: " + event);
         synchronized (mLock) {
+            mEventLog.append(event);
             final int left = mCapacity - mBuffer.size();
             if (event == null) {
                 return left;
@@ -216,6 +239,23 @@
         }
     }
 
+    /**
+     * Prints for bug reports the content of the rolling event log and the
+     * content of Netd event listener.
+     */
+    private void cmdDumpsys(FileDescriptor fd, PrintWriter pw, String[] args) {
+        final ConnectivityMetricsEvent[] events;
+        synchronized (mLock) {
+            events = mEventLog.toArray();
+        }
+        for (ConnectivityMetricsEvent ev : events) {
+            pw.println(ev.toString());
+        }
+        if (mNetdListener != null) {
+            mNetdListener.list(pw);
+        }
+    }
+
     private void cmdStats(FileDescriptor fd, PrintWriter pw, String[] args) {
         synchronized (mLock) {
             pw.println("Buffered events: " + mBuffer.size());
@@ -258,7 +298,8 @@
                     cmdFlush(fd, pw, args);
                     return;
                 case CMD_DUMPSYS:
-                    // Fallthrough to CMD_LIST when dumpsys.cpp dumps services states (bug reports)
+                    cmdDumpsys(fd, pw, args);
+                    return;
                 case CMD_LIST:
                     cmdList(fd, pw, args);
                     return;
diff --git a/com/android/server/connectivity/Nat464Xlat.java b/com/android/server/connectivity/Nat464Xlat.java
index e6585ad..fceacba 100644
--- a/com/android/server/connectivity/Nat464Xlat.java
+++ b/com/android/server/connectivity/Nat464Xlat.java
@@ -20,6 +20,7 @@
 import android.net.ConnectivityManager;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
+import android.net.NetworkInfo;
 import android.net.RouteInfo;
 import android.os.INetworkManagementService;
 import android.os.RemoteException;
@@ -44,12 +45,18 @@
     // This must match the interface prefix in clatd.c.
     private static final String CLAT_PREFIX = "v4-";
 
-    // The network types we will start clatd on,
+    // The network types on which we will start clatd,
     // allowing clat only on networks for which we can support IPv6-only.
     private static final int[] NETWORK_TYPES = {
-            ConnectivityManager.TYPE_MOBILE,
-            ConnectivityManager.TYPE_WIFI,
-            ConnectivityManager.TYPE_ETHERNET,
+        ConnectivityManager.TYPE_MOBILE,
+        ConnectivityManager.TYPE_WIFI,
+        ConnectivityManager.TYPE_ETHERNET,
+    };
+
+    // The network states in which running clatd is supported.
+    private static final NetworkInfo.State[] NETWORK_STATES = {
+        NetworkInfo.State.CONNECTED,
+        NetworkInfo.State.SUSPENDED,
     };
 
     private final INetworkManagementService mNMService;
@@ -81,11 +88,8 @@
      */
     public static boolean requiresClat(NetworkAgentInfo nai) {
         // TODO: migrate to NetworkCapabilities.TRANSPORT_*.
-        final int netType = nai.networkInfo.getType();
         final boolean supported = ArrayUtils.contains(NETWORK_TYPES, nai.networkInfo.getType());
-        // TODO: this should also consider if the network is in SUSPENDED state to avoid stopping
-        // clatd in SUSPENDED state.
-        final boolean connected = nai.networkInfo.isConnected();
+        final boolean connected = ArrayUtils.contains(NETWORK_STATES, nai.networkInfo.getState());
         // We only run clat on networks that don't have a native IPv4 address.
         final boolean hasIPv4Address =
                 (nai.linkProperties != null) && nai.linkProperties.hasIPv4Address();
@@ -148,7 +152,6 @@
      * turn ND offload off if on WiFi.
      */
     private void enterRunningState() {
-        maybeSetIpv6NdOffload(mBaseIface, false);
         mState = State.RUNNING;
     }
 
@@ -156,10 +159,6 @@
      * Stop clatd, and turn ND offload on if it had been turned off.
      */
     private void enterStoppingState() {
-        if (isRunning()) {
-            maybeSetIpv6NdOffload(mBaseIface, true);
-        }
-
         try {
             mNMService.stopClatd(mBaseIface);
         } catch(RemoteException|IllegalStateException e) {
@@ -275,19 +274,6 @@
         }
     }
 
-    private void maybeSetIpv6NdOffload(String iface, boolean on) {
-        // TODO: migrate to NetworkCapabilities.TRANSPORT_*.
-        if (mNetwork.networkInfo.getType() != ConnectivityManager.TYPE_WIFI) {
-            return;
-        }
-        try {
-            Slog.d(TAG, (on ? "En" : "Dis") + "abling ND offload on " + iface);
-            mNMService.setInterfaceIpv6NdOffload(iface, on);
-        } catch(RemoteException|IllegalStateException e) {
-            Slog.w(TAG, "Changing IPv6 ND offload on " + iface + "failed: " + e);
-        }
-    }
-
     /**
      * Adds stacked link on base link and transitions to RUNNING state.
      */
diff --git a/com/android/server/connectivity/NetdEventListenerService.java b/com/android/server/connectivity/NetdEventListenerService.java
index 6f7ace2..6206dfc 100644
--- a/com/android/server/connectivity/NetdEventListenerService.java
+++ b/com/android/server/connectivity/NetdEventListenerService.java
@@ -38,6 +38,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.BitUtils;
 import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.RingBuffer;
 import com.android.internal.util.TokenBucket;
 import com.android.server.connectivity.metrics.nano.IpConnectivityLogClass.IpConnectivityEvent;
 import java.io.PrintWriter;
@@ -82,9 +83,8 @@
     private final ArrayMap<String, WakeupStats> mWakeupStats = new ArrayMap<>();
     // Ring buffer array for storing packet wake up events sent by Netd.
     @GuardedBy("this")
-    private final WakeupEvent[] mWakeupEvents = new WakeupEvent[WAKEUP_EVENT_BUFFER_LENGTH];
-    @GuardedBy("this")
-    private long mWakeupEventCursor = 0;
+    private final RingBuffer<WakeupEvent> mWakeupEvents =
+            new RingBuffer(WakeupEvent.class, WAKEUP_EVENT_BUFFER_LENGTH);
 
     private final ConnectivityManager mCm;
 
@@ -170,18 +170,16 @@
             timestampMs = System.currentTimeMillis();
         }
 
-        addWakupEvent(iface, timestampMs, uid);
+        addWakeupEvent(iface, timestampMs, uid);
     }
 
     @GuardedBy("this")
-    private void addWakupEvent(String iface, long timestampMs, int uid) {
-        int index = wakeupEventIndex(mWakeupEventCursor);
-        mWakeupEventCursor++;
+    private void addWakeupEvent(String iface, long timestampMs, int uid) {
         WakeupEvent event = new WakeupEvent();
         event.iface = iface;
         event.timestampMs = timestampMs;
         event.uid = uid;
-        mWakeupEvents[index] = event;
+        mWakeupEvents.append(event);
         WakeupStats stats = mWakeupStats.get(iface);
         if (stats == null) {
             stats = new WakeupStats(iface);
@@ -190,23 +188,6 @@
         stats.countEvent(event);
     }
 
-    @GuardedBy("this")
-    private WakeupEvent[] getWakeupEvents() {
-        int length = (int) Math.min(mWakeupEventCursor, (long) mWakeupEvents.length);
-        WakeupEvent[] out = new WakeupEvent[length];
-        // Reverse iteration from youngest event to oldest event.
-        long inCursor = mWakeupEventCursor - 1;
-        int outIdx = out.length - 1;
-        while (outIdx >= 0) {
-            out[outIdx--] = mWakeupEvents[wakeupEventIndex(inCursor--)];
-        }
-        return out;
-    }
-
-    private static int wakeupEventIndex(long cursor) {
-        return (int) Math.abs(cursor % WAKEUP_EVENT_BUFFER_LENGTH);
-    }
-
     public synchronized void flushStatistics(List<IpConnectivityEvent> events) {
         flushProtos(events, mConnectEvents, IpConnectivityEventBuilder::toProto);
         flushProtos(events, mDnsEvents, IpConnectivityEventBuilder::toProto);
@@ -230,7 +211,7 @@
         for (int i = 0; i < mWakeupStats.size(); i++) {
             pw.println(mWakeupStats.valueAt(i));
         }
-        for (WakeupEvent wakeup : getWakeupEvents()) {
+        for (WakeupEvent wakeup : mWakeupEvents.toArray()) {
             pw.println(wakeup);
         }
     }
diff --git a/com/android/server/connectivity/tethering/OffloadController.java b/com/android/server/connectivity/tethering/OffloadController.java
index 5eafe5f..cff216c 100644
--- a/com/android/server/connectivity/tethering/OffloadController.java
+++ b/com/android/server/connectivity/tethering/OffloadController.java
@@ -52,6 +52,7 @@
 import java.net.Inet6Address;
 import java.net.InetAddress;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -73,6 +74,8 @@
     private static final String ANYIP = "0.0.0.0";
     private static final ForwardedStats EMPTY_STATS = new ForwardedStats();
 
+    private static enum UpdateType { IF_NEEDED, FORCE };
+
     private final Handler mHandler;
     private final OffloadHardwareInterface mHwInterface;
     private final ContentResolver mContentResolver;
@@ -185,8 +188,8 @@
                         updateStatsForAllUpstreams();
                         forceTetherStatsPoll();
                         // [2] (Re)Push all state.
-                        // TODO: computeAndPushLocalPrefixes()
-                        // TODO: push all downstream state.
+                        computeAndPushLocalPrefixes(UpdateType.FORCE);
+                        pushAllDownstreamState();
                         pushUpstreamParameters(null);
                     }
 
@@ -319,7 +322,7 @@
     }
 
     private boolean maybeUpdateDataLimit(String iface) {
-        // setDataLimit may only be called while offload is occuring on this upstream.
+        // setDataLimit may only be called while offload is occurring on this upstream.
         if (!started() || !TextUtils.equals(iface, currentUpstreamInterface())) {
             return true;
         }
@@ -368,15 +371,15 @@
         // upstream parameters fails (probably just wait for a subsequent
         // onOffloadEvent() callback to tell us offload is available again and
         // then reapply all state).
-        computeAndPushLocalPrefixes();
+        computeAndPushLocalPrefixes(UpdateType.IF_NEEDED);
         pushUpstreamParameters(prevUpstream);
     }
 
     public void setLocalPrefixes(Set<IpPrefix> localPrefixes) {
-        if (!started()) return;
-
         mExemptPrefixes = localPrefixes;
-        computeAndPushLocalPrefixes();
+
+        if (!started()) return;
+        computeAndPushLocalPrefixes(UpdateType.IF_NEEDED);
     }
 
     public void notifyDownstreamLinkProperties(LinkProperties lp) {
@@ -385,27 +388,38 @@
         if (Objects.equals(oldLp, lp)) return;
 
         if (!started()) return;
+        pushDownstreamState(oldLp, lp);
+    }
 
-        final List<RouteInfo> oldRoutes = (oldLp != null) ? oldLp.getRoutes() : new ArrayList<>();
-        final List<RouteInfo> newRoutes = lp.getRoutes();
+    private void pushDownstreamState(LinkProperties oldLp, LinkProperties newLp) {
+        final String ifname = newLp.getInterfaceName();
+        final List<RouteInfo> oldRoutes =
+                (oldLp != null) ? oldLp.getRoutes() : Collections.EMPTY_LIST;
+        final List<RouteInfo> newRoutes = newLp.getRoutes();
 
         // For each old route, if not in new routes: remove.
-        for (RouteInfo oldRoute : oldRoutes) {
-            if (shouldIgnoreDownstreamRoute(oldRoute)) continue;
-            if (!newRoutes.contains(oldRoute)) {
-                mHwInterface.removeDownstreamPrefix(ifname, oldRoute.getDestination().toString());
+        for (RouteInfo ri : oldRoutes) {
+            if (shouldIgnoreDownstreamRoute(ri)) continue;
+            if (!newRoutes.contains(ri)) {
+                mHwInterface.removeDownstreamPrefix(ifname, ri.getDestination().toString());
             }
         }
 
         // For each new route, if not in old routes: add.
-        for (RouteInfo newRoute : newRoutes) {
-            if (shouldIgnoreDownstreamRoute(newRoute)) continue;
-            if (!oldRoutes.contains(newRoute)) {
-                mHwInterface.addDownstreamPrefix(ifname, newRoute.getDestination().toString());
+        for (RouteInfo ri : newRoutes) {
+            if (shouldIgnoreDownstreamRoute(ri)) continue;
+            if (!oldRoutes.contains(ri)) {
+                mHwInterface.addDownstreamPrefix(ifname, ri.getDestination().toString());
             }
         }
     }
 
+    private void pushAllDownstreamState() {
+        for (LinkProperties lp : mDownstreams.values()) {
+            pushDownstreamState(null, lp);
+        }
+    }
+
     public void removeDownstreamInterface(String ifname) {
         final LinkProperties lp = mDownstreams.remove(ifname);
         if (lp == null) return;
@@ -484,10 +498,11 @@
         return success;
     }
 
-    private boolean computeAndPushLocalPrefixes() {
+    private boolean computeAndPushLocalPrefixes(UpdateType how) {
+        final boolean force = (how == UpdateType.FORCE);
         final Set<String> localPrefixStrs = computeLocalPrefixStrings(
                 mExemptPrefixes, mUpstreamLinkProperties);
-        if (mLastLocalPrefixStrs.equals(localPrefixStrs)) return true;
+        if (!force && mLastLocalPrefixStrs.equals(localPrefixStrs)) return true;
 
         mLastLocalPrefixStrs = localPrefixStrs;
         return mHwInterface.setLocalPrefixes(new ArrayList<>(localPrefixStrs));
@@ -581,9 +596,10 @@
         }
 
         mNatUpdateCallbacksReceived++;
+        final String natDescription = String.format("%s (%s, %s) -> (%s, %s)",
+                protoName, srcAddr, srcPort, dstAddr, dstPort);
         if (DBG) {
-            mLog.log(String.format("NAT timeout update: %s (%s, %s) -> (%s, %s)",
-                     protoName, srcAddr, srcPort, dstAddr, dstPort));
+            mLog.log("NAT timeout update: " + natDescription);
         }
 
         final int timeoutSec = connectionTimeoutUpdateSecondsFor(proto);
@@ -594,7 +610,7 @@
             NetlinkSocket.sendOneShotKernelMessage(OsConstants.NETLINK_NETFILTER, msg);
         } catch (ErrnoException e) {
             mNatUpdateNetlinkErrors++;
-            mLog.e("Error updating NAT conntrack entry: " + e
+            mLog.e("Error updating NAT conntrack entry >" + natDescription + "<: " + e
                     + ", msg: " + NetlinkConstants.hexify(msg));
             mLog.log("NAT timeout update callbacks received: " + mNatUpdateCallbacksReceived);
             mLog.log("NAT timeout update netlink errors: " + mNatUpdateNetlinkErrors);
diff --git a/com/android/server/content/SyncManager.java b/com/android/server/content/SyncManager.java
index 2f3b559..9cd52d7 100644
--- a/com/android/server/content/SyncManager.java
+++ b/com/android/server/content/SyncManager.java
@@ -46,8 +46,8 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.PackageManagerInternal;
+import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ProviderInfo;
 import android.content.pm.RegisteredServicesCache;
 import android.content.pm.RegisteredServicesCacheListener;
@@ -143,6 +143,7 @@
 
     private static final boolean DEBUG_ACCOUNT_ACCESS = false;
 
+    // Only do the check on a debuggable build.
     private static final boolean ENABLE_SUSPICIOUS_CHECK = Build.IS_DEBUGGABLE;
 
     /** Delay a sync due to local changes this long. In milliseconds */
@@ -537,9 +538,11 @@
      * @return whether the device most likely has some periodic syncs.
      */
     private boolean likelyHasPeriodicSyncs() {
-        // STOPSHIP Remove the google specific string.
         try {
-            return AccountManager.get(mContext).getAccountsByType("com.google").length > 0;
+            // Each sync adapter has a daily periodic sync by default, but sync adapters can remove
+            // them by themselves. So here, we use an arbitrary threshold. If there are more than
+            // this many sync endpoints, surely one of them should have a periodic sync...
+            return mSyncStorageEngine.getAuthorityCount() >= 6;
         } catch (Throwable th) {
             // Just in case.
         }
@@ -3775,48 +3778,10 @@
         }
         if (op.isPeriodic) {
             mLogger.log("Removing periodic sync ", op, " for ", why);
-
-            if (ENABLE_SUSPICIOUS_CHECK && isSuspiciousPeriodicSyncRemoval(op)) {
-                wtfWithLog("Suspicious removal of " + op + " for " + why);
-            }
         }
         getJobScheduler().cancel(op.jobId);
     }
 
-    private boolean isSuspiciousPeriodicSyncRemoval(SyncOperation op) {
-        // STOPSHIP Remove the google specific string.
-        if (!op.isPeriodic){
-            return false;
-        }
-        boolean found = false;
-        for (UserInfo user : UserManager.get(mContext).getUsers(/*excludeDying=*/ true)) {
-            if (op.target.userId == user.id) {
-                found = true;
-                break;
-            }
-        }
-        if (!found) {
-            return false; // User is being removed, okay.
-        }
-        switch (op.target.provider) {
-            case "gmail-ls":
-            case "com.android.contacts.metadata":
-                break;
-            default:
-                return false;
-        }
-        final Account account = op.target.account;
-        final Account[] accounts = AccountManager.get(mContext)
-                .getAccountsByTypeAsUser(account.type, UserHandle.of(op.target.userId));
-        for (Account a : accounts) {
-            if (a.equals(account)) {
-                return true; // Account still exists.  Suspicious!
-            }
-        }
-        // Account no longer exists. Makes sense...
-        return false;
-    }
-
     private void wtfWithLog(String message) {
         Slog.wtf(TAG, message);
         mLogger.log("WTF: ", message);
diff --git a/com/android/server/content/SyncStorageEngine.java b/com/android/server/content/SyncStorageEngine.java
index 7b277c0..3591871 100644
--- a/com/android/server/content/SyncStorageEngine.java
+++ b/com/android/server/content/SyncStorageEngine.java
@@ -911,6 +911,12 @@
         }
     }
 
+    public int getAuthorityCount() {
+        synchronized (mAuthorities) {
+            return mAuthorities.size();
+        }
+    }
+
     public AuthorityInfo getAuthority(int authorityId) {
         synchronized (mAuthorities) {
             return mAuthorities.get(authorityId);
diff --git a/com/android/server/devicepolicy/DevicePolicyManagerService.java b/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 6c859f7..c59f44e 100644
--- a/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -97,8 +97,8 @@
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.PackageManagerInternal;
+import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.PermissionInfo;
 import android.content.pm.ResolveInfo;
@@ -5350,7 +5350,7 @@
         }
     }
 
-    private void forceWipeUser(int userId) {
+    private void forceWipeUser(int userId, String wipeReasonForUser) {
         try {
             IActivityManager am = mInjector.getIActivityManager();
             if (am.getCurrentUser().id == userId) {
@@ -5361,7 +5361,7 @@
             if (!userRemoved) {
                 Slog.w(LOG_TAG, "Couldn't remove user " + userId);
             } else if (isManagedProfile(userId)) {
-                sendWipeProfileNotification();
+                sendWipeProfileNotification(wipeReasonForUser);
             }
         } catch (RemoteException re) {
             // Shouldn't happen
@@ -5369,23 +5369,26 @@
     }
 
     @Override
-    public void wipeData(int flags) {
+    public void wipeDataWithReason(int flags, String wipeReasonForUser) {
         if (!mHasFeature) {
             return;
         }
+        Preconditions.checkStringNotEmpty(wipeReasonForUser, "wipeReasonForUser is null or empty");
         enforceFullCrossUsersPermission(mInjector.userHandleGetCallingUserId());
 
         final ActiveAdmin admin;
         synchronized (this) {
             admin = getActiveAdminForCallerLocked(null, DeviceAdminInfo.USES_POLICY_WIPE_DATA);
         }
-        String reason = "DevicePolicyManager.wipeData() from "
+        String internalReason = "DevicePolicyManager.wipeDataWithReason() from "
                 + admin.info.getComponent().flattenToShortString();
         wipeDataNoLock(
-                admin.info.getComponent(), flags, reason, admin.getUserHandle().getIdentifier());
+                admin.info.getComponent(), flags, internalReason, wipeReasonForUser,
+                admin.getUserHandle().getIdentifier());
     }
 
-    private void wipeDataNoLock(ComponentName admin, int flags, String reason, int userId) {
+    private void wipeDataNoLock(ComponentName admin, int flags, String internalReason,
+                                String wipeReasonForUser, int userId) {
         wtfIfInLock();
 
         long ident = mInjector.binderClearCallingIdentity();
@@ -5420,25 +5423,26 @@
             // (rather than system), we should probably trigger factory reset. Current code just
             // removes that user (but still clears FRP...)
             if (userId == UserHandle.USER_SYSTEM) {
-                forceWipeDeviceNoLock(/*wipeExtRequested=*/ (flags & WIPE_EXTERNAL_STORAGE) != 0,
-                        reason, /*wipeEuicc=*/ (flags & WIPE_EUICC) != 0);
+                forceWipeDeviceNoLock(/*wipeExtRequested=*/ (
+                        flags & WIPE_EXTERNAL_STORAGE) != 0,
+                        internalReason,
+                        /*wipeEuicc=*/ (flags & WIPE_EUICC) != 0);
             } else {
-                forceWipeUser(userId);
+                forceWipeUser(userId, wipeReasonForUser);
             }
         } finally {
             mInjector.binderRestoreCallingIdentity(ident);
         }
     }
 
-    private void sendWipeProfileNotification() {
-        String contentText = mContext.getString(R.string.work_profile_deleted_description_dpm_wipe);
+    private void sendWipeProfileNotification(String wipeReasonForUser) {
         Notification notification =
                 new Notification.Builder(mContext, SystemNotificationChannels.DEVICE_ADMIN)
                         .setSmallIcon(android.R.drawable.stat_sys_warning)
                         .setContentTitle(mContext.getString(R.string.work_profile_deleted))
-                        .setContentText(contentText)
+                        .setContentText(wipeReasonForUser)
                         .setColor(mContext.getColor(R.color.system_notification_accent_color))
-                        .setStyle(new Notification.BigTextStyle().bigText(contentText))
+                        .setStyle(new Notification.BigTextStyle().bigText(wipeReasonForUser))
                         .build();
         mInjector.getNotificationManager().notify(SystemMessage.NOTE_PROFILE_WIPED, notification);
     }
@@ -5610,9 +5614,12 @@
             // able to do so).
             // IMPORTANT: Call without holding the lock to prevent deadlock.
             try {
+                String wipeReasonForUser = mContext.getString(
+                        R.string.work_profile_deleted_reason_maximum_password_failure);
                 wipeDataNoLock(strictestAdmin.info.getComponent(),
                         /*flags=*/ 0,
                         /*reason=*/ "reportFailedPasswordAttempt()",
+                        wipeReasonForUser,
                         userId);
             } catch (SecurityException e) {
                 Slog.w(LOG_TAG, "Failed to wipe user " + userId
@@ -5621,7 +5628,8 @@
         }
 
         if (mInjector.securityLogIsLoggingEnabled()) {
-            SecurityLog.writeEvent(SecurityLog.TAG_KEYGUARD_DISMISS_AUTH_ATTEMPT, /*result*/ 0,
+            SecurityLog.writeEvent(SecurityLog.TAG_KEYGUARD_DISMISS_AUTH_ATTEMPT,
+                    /*result*/ 0,
                     /*method strength*/ 1);
         }
     }
diff --git a/com/android/server/display/DisplayDeviceInfo.java b/com/android/server/display/DisplayDeviceInfo.java
index ef6de4c..fddb81b 100644
--- a/com/android/server/display/DisplayDeviceInfo.java
+++ b/com/android/server/display/DisplayDeviceInfo.java
@@ -98,6 +98,12 @@
     public static final int FLAG_CAN_SHOW_WITH_INSECURE_KEYGUARD = 1 << 9;
 
     /**
+     * Flag: This display will destroy its content on removal.
+     * @hide
+     */
+    public static final int FLAG_DESTROY_CONTENT_ON_REMOVAL = 1 << 10;
+
+    /**
      * Touch attachment: Display does not receive touch.
      */
     public static final int TOUCH_NONE = 0;
diff --git a/com/android/server/display/LocalDisplayAdapter.java b/com/android/server/display/LocalDisplayAdapter.java
index 8756484..d61a418 100644
--- a/com/android/server/display/LocalDisplayAdapter.java
+++ b/com/android/server/display/LocalDisplayAdapter.java
@@ -473,17 +473,18 @@
                         }
 
                         // If the state change was from or to VR, then we need to tell the light
-                        // so that it can apply appropriate VR brightness settings. This should
-                        // happen prior to changing the brightness but also if there is no
-                        // brightness change at all.
+                        // so that it can apply appropriate VR brightness settings. Also, update the
+                        // brightness so the state is propogated to light.
+                        boolean vrModeChange = false;
                         if ((state == Display.STATE_VR || currentState == Display.STATE_VR) &&
                                 currentState != state) {
                             setVrMode(state == Display.STATE_VR);
+                            vrModeChange = true;
                         }
 
 
                         // Apply brightness changes given that we are in a non-suspended state.
-                        if (brightnessChanged) {
+                        if (brightnessChanged || vrModeChange) {
                             setDisplayBrightness(brightness);
                         }
 
diff --git a/com/android/server/display/LogicalDisplay.java b/com/android/server/display/LogicalDisplay.java
index addad0b..78a5407 100644
--- a/com/android/server/display/LogicalDisplay.java
+++ b/com/android/server/display/LogicalDisplay.java
@@ -238,6 +238,9 @@
                 // For private displays by default content is destroyed on removal.
                 mBaseDisplayInfo.removeMode = Display.REMOVE_MODE_DESTROY_CONTENT;
             }
+            if ((deviceInfo.flags & DisplayDeviceInfo.FLAG_DESTROY_CONTENT_ON_REMOVAL) != 0) {
+                mBaseDisplayInfo.removeMode = Display.REMOVE_MODE_DESTROY_CONTENT;
+            }
             if ((deviceInfo.flags & DisplayDeviceInfo.FLAG_PRESENTATION) != 0) {
                 mBaseDisplayInfo.flags |= Display.FLAG_PRESENTATION;
             }
diff --git a/com/android/server/display/NightDisplayService.java b/com/android/server/display/NightDisplayService.java
index aafc631..9cf1367 100644
--- a/com/android/server/display/NightDisplayService.java
+++ b/com/android/server/display/NightDisplayService.java
@@ -48,8 +48,10 @@
 import com.android.server.twilight.TwilightManager;
 import com.android.server.twilight.TwilightState;
 
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.ZoneId;
 import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.Calendar;
 import java.util.TimeZone;
 
 import static com.android.server.display.DisplayTransformManager.LEVEL_COLOR_MATRIX_NIGHT_DISPLAY;
@@ -306,7 +308,7 @@
     }
 
     @Override
-    public void onCustomStartTimeChanged(NightDisplayController.LocalTime startTime) {
+    public void onCustomStartTimeChanged(LocalTime startTime) {
         Slog.d(TAG, "onCustomStartTimeChanged: startTime=" + startTime);
 
         if (mAutoMode != null) {
@@ -315,7 +317,7 @@
     }
 
     @Override
-    public void onCustomEndTimeChanged(NightDisplayController.LocalTime endTime) {
+    public void onCustomEndTimeChanged(LocalTime endTime) {
         Slog.d(TAG, "onCustomEndTimeChanged: endTime=" + endTime);
 
         if (mAutoMode != null) {
@@ -414,6 +416,36 @@
         outTemp[10] = blue;
     }
 
+    /**
+     * Returns the first date time corresponding to the local time that occurs before the
+     * provided date time.
+     *
+     * @param compareTime the LocalDateTime to compare against
+     * @return the prior LocalDateTime corresponding to this local time
+     */
+    public static LocalDateTime getDateTimeBefore(LocalTime localTime, LocalDateTime compareTime) {
+        final LocalDateTime ldt = LocalDateTime.of(compareTime.getYear(), compareTime.getMonth(),
+                compareTime.getDayOfMonth(), localTime.getHour(), localTime.getMinute());
+
+        // Check if the local time has passed, if so return the same time yesterday.
+        return ldt.isAfter(compareTime) ? ldt.minusDays(1) : ldt;
+    }
+
+    /**
+     * Returns the first date time corresponding to this local time that occurs after the
+     * provided date time.
+     *
+     * @param compareTime the LocalDateTime to compare against
+     * @return the next LocalDateTime corresponding to this local time
+     */
+    public static LocalDateTime getDateTimeAfter(LocalTime localTime, LocalDateTime compareTime) {
+        final LocalDateTime ldt = LocalDateTime.of(compareTime.getYear(), compareTime.getMonth(),
+                compareTime.getDayOfMonth(), localTime.getHour(), localTime.getMinute());
+
+        // Check if the local time has passed, if so return the same time tomorrow.
+        return ldt.isBefore(compareTime) ? ldt.plusDays(1) : ldt;
+    }
+
     private abstract class AutoMode implements NightDisplayController.Callback {
         public abstract void onStart();
 
@@ -425,10 +457,10 @@
         private final AlarmManager mAlarmManager;
         private final BroadcastReceiver mTimeChangedReceiver;
 
-        private NightDisplayController.LocalTime mStartTime;
-        private NightDisplayController.LocalTime mEndTime;
+        private LocalTime mStartTime;
+        private LocalTime mEndTime;
 
-        private Calendar mLastActivatedTime;
+        private LocalDateTime mLastActivatedTime;
 
         CustomAutoMode() {
             mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE);
@@ -441,31 +473,15 @@
         }
 
         private void updateActivated() {
-            final Calendar now = Calendar.getInstance();
-            final Calendar startTime = mStartTime.getDateTimeBefore(now);
-            final Calendar endTime = mEndTime.getDateTimeAfter(startTime);
+            final LocalDateTime now = LocalDateTime.now();
+            final LocalDateTime start = getDateTimeBefore(mStartTime, now);
+            final LocalDateTime end = getDateTimeAfter(mEndTime, start);
+            boolean activate = now.isBefore(end);
 
-            boolean activate = now.before(endTime);
             if (mLastActivatedTime != null) {
-                // Convert mLastActivatedTime to the current timezone if needed.
-                final TimeZone currentTimeZone = now.getTimeZone();
-                if (!currentTimeZone.equals(mLastActivatedTime.getTimeZone())) {
-                    final int year = mLastActivatedTime.get(Calendar.YEAR);
-                    final int dayOfYear = mLastActivatedTime.get(Calendar.DAY_OF_YEAR);
-                    final int hourOfDay = mLastActivatedTime.get(Calendar.HOUR_OF_DAY);
-                    final int minute = mLastActivatedTime.get(Calendar.MINUTE);
-
-                    mLastActivatedTime.setTimeZone(currentTimeZone);
-                    mLastActivatedTime.set(Calendar.YEAR, year);
-                    mLastActivatedTime.set(Calendar.DAY_OF_YEAR, dayOfYear);
-                    mLastActivatedTime.set(Calendar.HOUR_OF_DAY, hourOfDay);
-                    mLastActivatedTime.set(Calendar.MINUTE, minute);
-                }
-
                 // Maintain the existing activated state if within the current period.
-                if (mLastActivatedTime.before(now)
-                        && mLastActivatedTime.after(startTime)
-                        && (mLastActivatedTime.after(endTime) || now.before(endTime))) {
+                if (mLastActivatedTime.isBefore(now) && mLastActivatedTime.isAfter(start)
+                        && (mLastActivatedTime.isAfter(end) || now.isBefore(end))) {
                     activate = mController.isActivated();
                 }
             }
@@ -473,14 +489,16 @@
             if (mIsActivated == null || mIsActivated != activate) {
                 mController.setActivated(activate);
             }
+
             updateNextAlarm(mIsActivated, now);
         }
 
-        private void updateNextAlarm(@Nullable Boolean activated, @NonNull Calendar now) {
+        private void updateNextAlarm(@Nullable Boolean activated, @NonNull LocalDateTime now) {
             if (activated != null) {
-                final Calendar next = activated ? mEndTime.getDateTimeAfter(now)
-                        : mStartTime.getDateTimeAfter(now);
-                mAlarmManager.setExact(AlarmManager.RTC, next.getTimeInMillis(), TAG, this, null);
+                final LocalDateTime next = activated ? getDateTimeAfter(mEndTime, now)
+                        : getDateTimeAfter(mStartTime, now);
+                final long millis = next.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
+                mAlarmManager.setExact(AlarmManager.RTC, millis, TAG, this, null);
             }
         }
 
@@ -510,18 +528,18 @@
         @Override
         public void onActivated(boolean activated) {
             mLastActivatedTime = mController.getLastActivatedTime();
-            updateNextAlarm(activated, Calendar.getInstance());
+            updateNextAlarm(activated, LocalDateTime.now());
         }
 
         @Override
-        public void onCustomStartTimeChanged(NightDisplayController.LocalTime startTime) {
+        public void onCustomStartTimeChanged(LocalTime startTime) {
             mStartTime = startTime;
             mLastActivatedTime = null;
             updateActivated();
         }
 
         @Override
-        public void onCustomEndTimeChanged(NightDisplayController.LocalTime endTime) {
+        public void onCustomEndTimeChanged(LocalTime endTime) {
             mEndTime = endTime;
             mLastActivatedTime = null;
             updateActivated();
@@ -550,15 +568,14 @@
             }
 
             boolean activate = state.isNight();
-            final Calendar lastActivatedTime = mController.getLastActivatedTime();
+            final LocalDateTime lastActivatedTime = mController.getLastActivatedTime();
             if (lastActivatedTime != null) {
-                final Calendar now = Calendar.getInstance();
-                final Calendar sunrise = state.sunrise();
-                final Calendar sunset = state.sunset();
-
+                final LocalDateTime now = LocalDateTime.now();
+                final LocalDateTime sunrise = state.sunrise();
+                final LocalDateTime sunset = state.sunset();
                 // Maintain the existing activated state if within the current period.
-                if (lastActivatedTime.before(now)
-                        && (lastActivatedTime.after(sunrise) ^ lastActivatedTime.after(sunset))) {
+                if (lastActivatedTime.isBefore(now) && (lastActivatedTime.isBefore(sunrise)
+                        ^ lastActivatedTime.isBefore(sunset))) {
                     activate = mController.isActivated();
                 }
             }
diff --git a/com/android/server/display/VirtualDisplayAdapter.java b/com/android/server/display/VirtualDisplayAdapter.java
index d6ab888..f86d576 100644
--- a/com/android/server/display/VirtualDisplayAdapter.java
+++ b/com/android/server/display/VirtualDisplayAdapter.java
@@ -24,6 +24,8 @@
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT;
+import static android.hardware.display.DisplayManager
+        .VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL;
 
 import android.content.Context;
 import android.hardware.display.IVirtualDisplayCallback;
@@ -363,6 +365,9 @@
                 if ((mFlags & VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT) != 0) {
                     mInfo.flags |= DisplayDeviceInfo.FLAG_ROTATES_WITH_CONTENT;
                 }
+                if ((mFlags & VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL) != 0) {
+                  mInfo.flags |= DisplayDeviceInfo.FLAG_DESTROY_CONTENT_ON_REMOVAL;
+                }
 
                 mInfo.type = Display.TYPE_VIRTUAL;
                 mInfo.touch = ((mFlags & VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH) == 0) ?
diff --git a/com/android/server/fingerprint/FingerprintService.java b/com/android/server/fingerprint/FingerprintService.java
index b1c165e..1df9c86 100644
--- a/com/android/server/fingerprint/FingerprintService.java
+++ b/com/android/server/fingerprint/FingerprintService.java
@@ -63,6 +63,8 @@
 import android.service.fingerprint.FingerprintServiceDumpProto;
 import android.service.fingerprint.FingerprintUserStatsProto;
 import android.util.Slog;
+import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.GuardedBy;
@@ -81,7 +83,6 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.CopyOnWriteArrayList;
@@ -89,18 +90,19 @@
 /**
  * A service to manage multiple clients that want to access the fingerprint HAL API.
  * The service is responsible for maintaining a list of clients and dispatching all
- * fingerprint -related events.
+ * fingerprint-related events.
  *
  * @hide
  */
 public class FingerprintService extends SystemService implements IHwBinder.DeathRecipient {
     static final String TAG = "FingerprintService";
     static final boolean DEBUG = true;
-    private static final boolean CLEANUP_UNUSED_FP = false;
+    private static final boolean CLEANUP_UNUSED_FP = true;
     private static final String FP_DATA_DIR = "fpdata";
     private static final int MSG_USER_SWITCHING = 10;
     private static final String ACTION_LOCKOUT_RESET =
             "com.android.server.fingerprint.ACTION_LOCKOUT_RESET";
+    private static final String KEY_LOCKOUT_RESET_USER = "lockout_reset_user";
 
     private class PerformanceStats {
         int accept; // number of accepted fingerprints
@@ -128,8 +130,8 @@
     private final FingerprintUtils mFingerprintUtils = FingerprintUtils.getInstance();
     private Context mContext;
     private long mHalDeviceId;
-    private boolean mTimedLockoutCleared;
-    private int mFailedAttempts;
+    private SparseBooleanArray mTimedLockoutCleared;
+    private SparseIntArray mFailedAttempts;
     @GuardedBy("this")
     private IBiometricsFingerprint mDaemon;
     private final PowerManager mPowerManager;
@@ -139,10 +141,8 @@
     private ClientMonitor mPendingClient;
     private PerformanceStats mPerformanceStats;
 
-
     private IBinder mToken = new Binder(); // used for internal FingerprintService enumeration
-    private LinkedList<Integer> mEnumeratingUserIds = new LinkedList<>();
-    private ArrayList<UserFingerprint> mUnknownFingerprints = new ArrayList<>(); // hw finterprints
+    private ArrayList<UserFingerprint> mUnknownFingerprints = new ArrayList<>(); // hw fingerprints
 
     private class UserFingerprint {
         Fingerprint f;
@@ -177,15 +177,17 @@
         @Override
         public void onReceive(Context context, Intent intent) {
             if (ACTION_LOCKOUT_RESET.equals(intent.getAction())) {
-                resetFailedAttempts(false /* clearAttemptCounter */);
+                final int user = intent.getIntExtra(KEY_LOCKOUT_RESET_USER, 0);
+                resetFailedAttemptsForUser(false /* clearAttemptCounter */, user);
             }
         }
     };
 
-    private final Runnable mResetFailedAttemptsRunnable = new Runnable() {
+    private final Runnable mResetFailedAttemptsForCurrentUserRunnable = new Runnable() {
         @Override
         public void run() {
-            resetFailedAttempts(true /* clearAttemptCounter */);
+            resetFailedAttemptsForUser(true /* clearAttemptCounter */,
+                    ActivityManager.getCurrentUser());
         }
     };
 
@@ -221,6 +223,8 @@
         mContext.registerReceiver(mLockoutReceiver, new IntentFilter(ACTION_LOCKOUT_RESET),
                 RESET_FINGERPRINT_LOCKOUT, null /* handler */);
         mUserManager = UserManager.get(mContext);
+        mTimedLockoutCleared = new SparseBooleanArray();
+        mFailedAttempts = new SparseIntArray();
     }
 
     @Override
@@ -233,7 +237,7 @@
 
     public synchronized IBiometricsFingerprint getFingerprintDaemon() {
         if (mDaemon == null) {
-            Slog.v(TAG, "mDeamon was null, reconnect to fingerprint");
+            Slog.v(TAG, "mDaemon was null, reconnect to fingerprint");
             try {
                 mDaemon = IBiometricsFingerprint.getService();
             } catch (java.util.NoSuchElementException e) {
@@ -259,7 +263,7 @@
             if (mHalDeviceId != 0) {
                 loadAuthenticatorIds();
                 updateActiveGroup(ActivityManager.getCurrentUser(), null);
-                doFingerprintCleanup(ActivityManager.getCurrentUser());
+                doFingerprintCleanupForUser(ActivityManager.getCurrentUser());
             } else {
                 Slog.w(TAG, "Failed to open Fingerprint HAL!");
                 MetricsLogger.count(mContext, "fingerprintd_openhal_error", 1);
@@ -288,52 +292,41 @@
         }
     }
 
-    private void doFingerprintCleanup(int userId) {
+    /**
+     * This method should be called upon connection to the daemon, and when user switches.
+     * @param userId
+     */
+    private void doFingerprintCleanupForUser(int userId) {
         if (CLEANUP_UNUSED_FP) {
-            resetEnumerateState();
-            mEnumeratingUserIds.push(userId);
-            enumerateNextUser();
+            enumerateUser(userId);
         }
     }
 
-    private void resetEnumerateState() {
-        if (DEBUG) Slog.v(TAG, "Enumerate cleaning up");
-        mEnumeratingUserIds.clear();
+    private void clearEnumerateState() {
+        if (DEBUG) Slog.v(TAG, "clearEnumerateState()");
         mUnknownFingerprints.clear();
     }
 
-    private void enumerateNextUser() {
-        int nextUser = mEnumeratingUserIds.getFirst();
-        updateActiveGroup(nextUser, null);
+    private void enumerateUser(int userId) {
+        if (DEBUG) Slog.v(TAG, "Enumerating user(" + userId + ")");
         boolean restricted = !hasPermission(MANAGE_FINGERPRINT);
-
-        if (DEBUG) Slog.v(TAG, "Enumerating user id " + nextUser + " of "
-                + mEnumeratingUserIds.size() + " remaining users");
-
-        startEnumerate(mToken, nextUser, null, restricted, true /* internal */);
+        startEnumerate(mToken, userId, null, restricted, true /* internal */);
     }
 
     // Remove unknown fingerprints from hardware
     private void cleanupUnknownFingerprints() {
         if (!mUnknownFingerprints.isEmpty()) {
-            Slog.w(TAG, "unknown fingerprint size: " + mUnknownFingerprints.size());
             UserFingerprint uf = mUnknownFingerprints.get(0);
             mUnknownFingerprints.remove(uf);
             boolean restricted = !hasPermission(MANAGE_FINGERPRINT);
-            updateActiveGroup(uf.userId, null);
             startRemove(mToken, uf.f.getFingerId(), uf.f.getGroupId(), uf.userId, null,
                     restricted, true /* internal */);
         } else {
-            resetEnumerateState();
+            clearEnumerateState();
         }
     }
 
     protected void handleEnumerate(long deviceId, int fingerId, int groupId, int remaining) {
-        if (DEBUG) Slog.w(TAG, "Enumerate: fid=" + fingerId
-                + ", gid=" + groupId
-                + ", dev=" + deviceId
-                + ", rem=" + remaining);
-
         ClientMonitor client = mCurrentClient;
 
         if ( !(client instanceof InternalRemovalClient) && !(client instanceof EnumerateClient) ) {
@@ -343,24 +336,21 @@
 
         // All fingerprints in hardware for this user were enumerated
         if (remaining == 0) {
-            mEnumeratingUserIds.poll();
-
             if (client instanceof InternalEnumerateClient) {
-                List<Fingerprint> enrolled = ((InternalEnumerateClient) client).getEnumeratedList();
-                Slog.w(TAG, "Added " + enrolled.size() + " enumerated fingerprints for deletion");
-                for (Fingerprint f : enrolled) {
+                List<Fingerprint> unknownFingerprints =
+                        ((InternalEnumerateClient) client).getUnknownFingerprints();
+
+                if (!unknownFingerprints.isEmpty()) {
+                    Slog.w(TAG, "Adding " + unknownFingerprints.size() +
+                            " fingerprints for deletion");
+                }
+                for (Fingerprint f : unknownFingerprints) {
                     mUnknownFingerprints.add(new UserFingerprint(f, client.getTargetUserId()));
                 }
-            }
-
-            removeClient(client);
-
-            if (!mEnumeratingUserIds.isEmpty()) {
-                enumerateNextUser();
-            } else if (client instanceof InternalEnumerateClient) {
-                if (DEBUG) Slog.v(TAG, "Finished enumerating all users");
-                // This will start a chain of InternalRemovalClients
+                removeClient(client);
                 cleanupUnknownFingerprints();
+            } else {
+                removeClient(client);
             }
         }
     }
@@ -368,7 +358,7 @@
     protected void handleError(long deviceId, int error, int vendorCode) {
         ClientMonitor client = mCurrentClient;
         if (client instanceof InternalRemovalClient || client instanceof InternalEnumerateClient) {
-            resetEnumerateState();
+            clearEnumerateState();
         }
         if (client != null && client.onError(error, vendorCode)) {
             removeClient(client);
@@ -412,7 +402,7 @@
         if (client instanceof InternalRemovalClient && !mUnknownFingerprints.isEmpty()) {
             cleanupUnknownFingerprints();
         } else if (client instanceof InternalRemovalClient){
-            resetEnumerateState();
+            clearEnumerateState();
         }
     }
 
@@ -466,8 +456,14 @@
     }
 
     void handleUserSwitching(int userId) {
+        if (mCurrentClient instanceof InternalRemovalClient
+                || mCurrentClient instanceof InternalEnumerateClient) {
+            Slog.w(TAG, "User switched while performing cleanup");
+            removeClient(mCurrentClient);
+            clearEnumerateState();
+        }
         updateActiveGroup(userId, null);
-        doFingerprintCleanup(userId);
+        doFingerprintCleanupForUser(userId);
     }
 
     private void removeClient(ClientMonitor client) {
@@ -488,27 +484,32 @@
     }
 
     private int getLockoutMode() {
-        if (mFailedAttempts >= MAX_FAILED_ATTEMPTS_LOCKOUT_PERMANENT) {
+        final int currentUser = ActivityManager.getCurrentUser();
+        final int failedAttempts = mFailedAttempts.get(currentUser, 0);
+        if (failedAttempts >= MAX_FAILED_ATTEMPTS_LOCKOUT_PERMANENT) {
             return AuthenticationClient.LOCKOUT_PERMANENT;
-        } else if (mFailedAttempts > 0 && mTimedLockoutCleared == false &&
-                (mFailedAttempts % MAX_FAILED_ATTEMPTS_LOCKOUT_TIMED == 0)) {
+        } else if (failedAttempts > 0 &&
+                mTimedLockoutCleared.get(currentUser, false) == false
+                && (failedAttempts % MAX_FAILED_ATTEMPTS_LOCKOUT_TIMED == 0)) {
             return AuthenticationClient.LOCKOUT_TIMED;
         }
         return AuthenticationClient.LOCKOUT_NONE;
     }
 
-    private void scheduleLockoutReset() {
-        mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
-                SystemClock.elapsedRealtime() + FAIL_LOCKOUT_TIMEOUT_MS, getLockoutResetIntent());
+    private void scheduleLockoutResetForUser(int userId) {
+        mAlarmManager.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP,
+                SystemClock.elapsedRealtime() + FAIL_LOCKOUT_TIMEOUT_MS,
+                getLockoutResetIntentForUser(userId));
     }
 
-    private void cancelLockoutReset() {
-        mAlarmManager.cancel(getLockoutResetIntent());
+    private void cancelLockoutResetForUser(int userId) {
+        mAlarmManager.cancel(getLockoutResetIntentForUser(userId));
     }
 
-    private PendingIntent getLockoutResetIntent() {
-        return PendingIntent.getBroadcast(mContext, 0,
-                new Intent(ACTION_LOCKOUT_RESET), PendingIntent.FLAG_UPDATE_CURRENT);
+    private PendingIntent getLockoutResetIntentForUser(int userId) {
+        return PendingIntent.getBroadcast(mContext, userId,
+                new Intent(ACTION_LOCKOUT_RESET).putExtra(KEY_LOCKOUT_RESET_USER, userId),
+                PendingIntent.FLAG_UPDATE_CURRENT);
     }
 
     public long startPreEnroll(IBinder token) {
@@ -555,6 +556,12 @@
                 // This condition means we're currently running internal diagnostics to
                 // remove extra fingerprints in the hardware and/or the software
                 // TODO: design an escape hatch in case client never finishes
+                if (newClient != null) {
+                    Slog.w(TAG, "Internal cleanup in progress but trying to start client "
+                            + newClient.getClass().getSuperclass().getSimpleName()
+                            + "(" + newClient.getOwnerString() + ")"
+                            + ", initiatedByClient = " + initiatedByClient);
+                }
             }
             else {
                 currentClient.stop(initiatedByClient);
@@ -567,7 +574,7 @@
             if (DEBUG) Slog.v(TAG, "starting client "
                     + newClient.getClass().getSuperclass().getSimpleName()
                     + "(" + newClient.getOwnerString() + ")"
-                    + ", initiatedByClient = " + initiatedByClient + ")");
+                    + ", initiatedByClient = " + initiatedByClient);
             notifyClientActiveCallbacks(true);
 
             newClient.start();
@@ -813,8 +820,9 @@
                 receiver, mCurrentUserId, groupId, opId, restricted, opPackageName) {
             @Override
             public int handleFailedAttempt() {
-                mFailedAttempts++;
-                mTimedLockoutCleared = false;
+                final int currentUser = ActivityManager.getCurrentUser();
+                mFailedAttempts.put(currentUser, mFailedAttempts.get(currentUser, 0) + 1);
+                mTimedLockoutCleared.put(ActivityManager.getCurrentUser(), false);
                 final int lockoutMode = getLockoutMode();
                 if (lockoutMode == AuthenticationClient.LOCKOUT_PERMANENT) {
                     mPerformanceStats.permanentLockout++;
@@ -824,7 +832,7 @@
 
                 // Failing multiple times will continue to push out the lockout time
                 if (lockoutMode != AuthenticationClient.LOCKOUT_NONE) {
-                    scheduleLockoutReset();
+                    scheduleLockoutResetForUser(currentUser);
                     return lockoutMode;
                 }
                 return AuthenticationClient.LOCKOUT_NONE;
@@ -832,7 +840,8 @@
 
             @Override
             public void resetFailedAttempts() {
-                FingerprintService.this.resetFailedAttempts(true /* clearAttemptCounter */);
+                FingerprintService.this.resetFailedAttemptsForUser(true /* clearAttemptCounter */,
+                        ActivityManager.getCurrentUser());
             }
 
             @Override
@@ -886,17 +895,17 @@
 
     // attempt counter should only be cleared when Keyguard goes away or when
     // a fingerprint is successfully authenticated
-    protected void resetFailedAttempts(boolean clearAttemptCounter) {
+    protected void resetFailedAttemptsForUser(boolean clearAttemptCounter, int userId) {
         if (DEBUG && getLockoutMode() != AuthenticationClient.LOCKOUT_NONE) {
             Slog.v(TAG, "Reset fingerprint lockout, clearAttemptCounter=" + clearAttemptCounter);
         }
         if (clearAttemptCounter) {
-            mFailedAttempts = 0;
+            mFailedAttempts.put(userId, 0);
         }
-        mTimedLockoutCleared = true;
+        mTimedLockoutCleared.put(userId, true);
         // If we're asked to reset failed attempts externally (i.e. from Keyguard),
         // the alarm might still be pending; remove it.
-        cancelLockoutReset();
+        cancelLockoutResetForUser(userId);
         notifyLockoutResetMonitors();
     }
 
@@ -1277,7 +1286,7 @@
         public void resetTimeout(byte [] token) {
             checkPermission(RESET_FINGERPRINT_LOCKOUT);
             // TODO: confirm security token when we move timeout management into the HAL layer.
-            mHandler.post(mResetFailedAttemptsRunnable);
+            mHandler.post(mResetFailedAttemptsForCurrentUserRunnable);
         }
 
         @Override
@@ -1338,6 +1347,8 @@
                 set.put("rejectCrypto", (cryptoStats != null) ? cryptoStats.reject : 0);
                 set.put("acquireCrypto", (cryptoStats != null) ? cryptoStats.acquire : 0);
                 set.put("lockoutCrypto", (cryptoStats != null) ? cryptoStats.lockout : 0);
+                set.put("permanentLockoutCrypto",
+                    (cryptoStats != null) ? cryptoStats.permanentLockout : 0);
                 sets.put(set);
             }
 
@@ -1367,7 +1378,7 @@
                 proto.write(FingerprintActionStatsProto.REJECT, normal.reject);
                 proto.write(FingerprintActionStatsProto.ACQUIRE, normal.acquire);
                 proto.write(FingerprintActionStatsProto.LOCKOUT, normal.lockout);
-                proto.write(FingerprintActionStatsProto.LOCKOUT_PERMANENT, normal.lockout);
+                proto.write(FingerprintActionStatsProto.LOCKOUT_PERMANENT, normal.permanentLockout);
                 proto.end(countsToken);
             }
 
@@ -1380,7 +1391,7 @@
                 proto.write(FingerprintActionStatsProto.REJECT, crypto.reject);
                 proto.write(FingerprintActionStatsProto.ACQUIRE, crypto.acquire);
                 proto.write(FingerprintActionStatsProto.LOCKOUT, crypto.lockout);
-                proto.write(FingerprintActionStatsProto.LOCKOUT_PERMANENT, crypto.lockout);
+                proto.write(FingerprintActionStatsProto.LOCKOUT_PERMANENT, crypto.permanentLockout);
                 proto.end(countsToken);
             }
 
diff --git a/com/android/server/fingerprint/InternalEnumerateClient.java b/com/android/server/fingerprint/InternalEnumerateClient.java
index 88d9ef4..434db98 100644
--- a/com/android/server/fingerprint/InternalEnumerateClient.java
+++ b/com/android/server/fingerprint/InternalEnumerateClient.java
@@ -30,7 +30,7 @@
 public abstract class InternalEnumerateClient extends EnumerateClient {
 
     private List<Fingerprint> mEnrolledList;
-    private List<Fingerprint> mEnumeratedList = new ArrayList<>(); // list of fp to delete
+    private List<Fingerprint> mUnknownFingerprints = new ArrayList<>(); // list of fp to delete
 
     public InternalEnumerateClient(Context context, long halDeviceId, IBinder token,
             IFingerprintServiceReceiver receiver, int groupId, int userId,
@@ -47,7 +47,6 @@
             if (mEnrolledList.get(i).getFingerId() == fingerId) {
                 mEnrolledList.remove(i);
                 matched = true;
-                Slog.e(TAG, "Matched fingerprint fid=" + fingerId);
                 break;
             }
         }
@@ -55,7 +54,7 @@
         // fingerId 0 means no fingerprints are in hardware
         if (!matched && fingerId != 0) {
             Fingerprint fingerprint = new Fingerprint("", groupId, fingerId, getHalDeviceId());
-            mEnumeratedList.add(fingerprint);
+            mUnknownFingerprints.add(fingerprint);
         }
     }
 
@@ -76,8 +75,8 @@
         mEnrolledList.clear();
     }
 
-    public List<Fingerprint> getEnumeratedList() {
-        return mEnumeratedList;
+    public List<Fingerprint> getUnknownFingerprints() {
+        return mUnknownFingerprints;
     }
 
     @Override
diff --git a/com/android/server/job/JobSchedulerService.java b/com/android/server/job/JobSchedulerService.java
index ac80794..78aa2f9 100644
--- a/com/android/server/job/JobSchedulerService.java
+++ b/com/android/server/job/JobSchedulerService.java
@@ -795,18 +795,22 @@
      * @param uid Uid to check against for removal of a job.
      *
      */
-    public void cancelJobsForUid(int uid, String reason) {
+    public boolean cancelJobsForUid(int uid, String reason) {
         if (uid == Process.SYSTEM_UID) {
             Slog.wtfStack(TAG, "Can't cancel all jobs for system uid");
-            return;
+            return false;
         }
+
+        boolean jobsCanceled = false;
         synchronized (mLock) {
             final List<JobStatus> jobsForUid = mJobs.getJobsByUid(uid);
             for (int i=0; i<jobsForUid.size(); i++) {
                 JobStatus toRemove = jobsForUid.get(i);
                 cancelJobImplLocked(toRemove, null, reason);
+                jobsCanceled = true;
             }
         }
+        return jobsCanceled;
     }
 
     /**
@@ -816,13 +820,14 @@
      * @param uid Uid of the calling client.
      * @param jobId Id of the job, provided at schedule-time.
      */
-    public void cancelJob(int uid, int jobId) {
+    public boolean cancelJob(int uid, int jobId) {
         JobStatus toCancel;
         synchronized (mLock) {
             toCancel = mJobs.getJobByUidAndJobId(uid, jobId);
             if (toCancel != null) {
                 cancelJobImplLocked(toCancel, null, "cancel() called by app");
             }
+            return (toCancel != null);
         }
     }
 
@@ -2147,6 +2152,39 @@
         return 0;
     }
 
+    // Shell command infrastructure: cancel a scheduled job
+    int executeCancelCommand(PrintWriter pw, String pkgName, int userId,
+            boolean hasJobId, int jobId) {
+        if (DEBUG) {
+            Slog.v(TAG, "executeCancelCommand(): " + pkgName + "/" + userId + " " + jobId);
+        }
+
+        int pkgUid = -1;
+        try {
+            IPackageManager pm = AppGlobals.getPackageManager();
+            pkgUid = pm.getPackageUid(pkgName, 0, userId);
+        } catch (RemoteException e) { /* can't happen */ }
+
+        if (pkgUid < 0) {
+            pw.println("Package " + pkgName + " not found.");
+            return JobSchedulerShellCommand.CMD_ERR_NO_PACKAGE;
+        }
+
+        if (!hasJobId) {
+            pw.println("Canceling all jobs for " + pkgName + " in user " + userId);
+            if (!cancelJobsForUid(pkgUid, "cancel shell command for package")) {
+                pw.println("No matching jobs found.");
+            }
+        } else {
+            pw.println("Canceling job " + pkgName + "/#" + jobId + " in user " + userId);
+            if (!cancelJob(pkgUid, jobId)) {
+                pw.println("No matching job found.");
+            }
+        }
+
+        return 0;
+    }
+
     void setMonitorBattery(boolean enabled) {
         synchronized (mLock) {
             if (mBatteryController != null) {
diff --git a/com/android/server/job/JobSchedulerShellCommand.java b/com/android/server/job/JobSchedulerShellCommand.java
index a53c088..d630aab 100644
--- a/com/android/server/job/JobSchedulerShellCommand.java
+++ b/com/android/server/job/JobSchedulerShellCommand.java
@@ -48,6 +48,8 @@
                     return runJob(pw);
                 case "timeout":
                     return timeout(pw);
+                case "cancel":
+                    return cancelJob(pw);
                 case "monitor-battery":
                     return monitorBattery(pw);
                 case "get-battery-seq":
@@ -205,6 +207,42 @@
         }
     }
 
+    private int cancelJob(PrintWriter pw) throws Exception {
+        checkPermission("cancel jobs");
+
+        int userId = UserHandle.USER_SYSTEM;
+
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            switch (opt) {
+                case "-u":
+                case "--user":
+                    userId = UserHandle.parseUserArg(getNextArgRequired());
+                    break;
+
+                default:
+                    pw.println("Error: unknown option '" + opt + "'");
+                    return -1;
+            }
+        }
+
+        if (userId < 0) {
+            pw.println("Error: must specify a concrete user ID");
+            return -1;
+        }
+
+        final String pkgName = getNextArg();
+        final String jobIdStr = getNextArg();
+        final int jobId = jobIdStr != null ? Integer.parseInt(jobIdStr) : -1;
+
+        final long ident = Binder.clearCallingIdentity();
+        try {
+            return mInternal.executeCancelCommand(pw, pkgName, userId, jobIdStr != null, jobId);
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
     private int monitorBattery(PrintWriter pw) throws Exception {
         checkPermission("change battery monitoring");
         String opt = getNextArgRequired();
@@ -315,6 +353,12 @@
         pw.println("    Options:");
         pw.println("      -u or --user: specify which user's job is to be run; the default is");
         pw.println("         all users");
+        pw.println("  cancel [-u | --user USER_ID] PACKAGE [JOB_ID]");
+        pw.println("    Cancel a scheduled job.  If a job ID is not supplied, all jobs scheduled");
+        pw.println("    by that package will be canceled.  USE WITH CAUTION.");
+        pw.println("    Options:");
+        pw.println("      -u or --user: specify which user's job is to be run; the default is");
+        pw.println("         the primary or system user");
         pw.println("  monitor-battery [on|off]");
         pw.println("    Control monitoring of all battery changes.  Off by default.  Turning");
         pw.println("    on makes get-battery-seq useful.");
diff --git a/com/android/server/locksettings/LockSettingsService.java b/com/android/server/locksettings/LockSettingsService.java
index 11043bd..a1a0106 100644
--- a/com/android/server/locksettings/LockSettingsService.java
+++ b/com/android/server/locksettings/LockSettingsService.java
@@ -376,7 +376,7 @@
         }
 
         public SyntheticPasswordManager getSyntheticPasswordManager(LockSettingsStorage storage) {
-            return new SyntheticPasswordManager(storage, getUserManager());
+            return new SyntheticPasswordManager(getContext(), storage, getUserManager());
         }
 
         public int binderGetCallingUid() {
@@ -763,7 +763,8 @@
     private void migrateOldDataAfterSystemReady() {
         try {
             // Migrate the FRP credential to the persistent data block
-            if (LockPatternUtils.frpCredentialEnabled() && !getBoolean("migrated_frp", false, 0)) {
+            if (LockPatternUtils.frpCredentialEnabled(mContext)
+                    && !getBoolean("migrated_frp", false, 0)) {
                 migrateFrpCredential();
                 setBoolean("migrated_frp", true, 0);
                 Slog.i(TAG, "Migrated migrated_frp.");
@@ -784,7 +785,7 @@
             return;
         }
         for (UserInfo userInfo : mUserManager.getUsers()) {
-            if (userOwnsFrpCredential(userInfo) && isUserSecure(userInfo.id)) {
+            if (userOwnsFrpCredential(mContext, userInfo) && isUserSecure(userInfo.id)) {
                 synchronized (mSpManager) {
                     if (isSyntheticPasswordBasedCredentialLocked(userInfo.id)) {
                         int actualQuality = (int) getLong(LockPatternUtils.PASSWORD_TYPE_KEY,
@@ -2366,6 +2367,13 @@
                 Slog.w(TAG, "Invalid escrow token supplied");
                 return false;
             }
+            if (result.gkResponse.getResponseCode() != VerifyCredentialResponse.RESPONSE_OK) {
+                // Most likely, an untrusted credential reset happened in the past which
+                // changed the synthetic password
+                Slog.e(TAG, "Obsolete token: synthetic password derived but it fails GK "
+                        + "verification.");
+                return false;
+            }
             // Update PASSWORD_TYPE_KEY since it's needed by notifyActivePasswordMetricsAvailable()
             // called by setLockCredentialWithAuthTokenLocked().
             // TODO: refactor usage of PASSWORD_TYPE_KEY b/65239740
@@ -2497,7 +2505,7 @@
         }
 
         public void onSystemReady() {
-            if (frpCredentialEnabled()) {
+            if (frpCredentialEnabled(mContext)) {
                 updateRegistration();
             } else {
                 // If we don't intend to use frpCredentials and we're not provisioned yet, send
@@ -2526,7 +2534,7 @@
         private void clearFrpCredentialIfOwnerNotSecure() {
             List<UserInfo> users = mUserManager.getUsers();
             for (UserInfo user : users) {
-                if (userOwnsFrpCredential(user)) {
+                if (userOwnsFrpCredential(mContext, user)) {
                     if (!isUserSecure(user.id)) {
                         mStorage.writePersistentDataBlock(PersistentData.TYPE_NONE, user.id,
                                 0, null);
diff --git a/com/android/server/locksettings/LockSettingsStrongAuth.java b/com/android/server/locksettings/LockSettingsStrongAuth.java
index 542b929..c9c9329 100644
--- a/com/android/server/locksettings/LockSettingsStrongAuth.java
+++ b/com/android/server/locksettings/LockSettingsStrongAuth.java
@@ -27,6 +27,7 @@
 import android.app.admin.DevicePolicyManager;
 import android.app.trust.IStrongAuthTracker;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.hardware.fingerprint.FingerprintManager;
 import android.os.Binder;
 import android.os.DeadObjectException;
@@ -74,7 +75,10 @@
     }
 
     public void systemReady() {
-        mFingerprintManager = mContext.getSystemService(FingerprintManager.class);
+        final PackageManager pm = mContext.getPackageManager();
+        if (pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
+            mFingerprintManager = mContext.getSystemService(FingerprintManager.class);
+        }
     }
 
     private void handleAddStrongAuthTracker(IStrongAuthTracker tracker) {
diff --git a/com/android/server/locksettings/SyntheticPasswordManager.java b/com/android/server/locksettings/SyntheticPasswordManager.java
index 33a9a99..9440f17 100644
--- a/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.admin.DevicePolicyManager;
+import android.content.Context;
 import android.content.pm.UserInfo;
 import android.hardware.weaver.V1_0.IWeaver;
 import android.hardware.weaver.V1_0.WeaverConfig;
@@ -255,13 +256,16 @@
         byte[] aggregatedSecret;
     }
 
+    private final Context mContext;
     private LockSettingsStorage mStorage;
     private IWeaver mWeaver;
     private WeaverConfig mWeaverConfig;
 
     private final UserManager mUserManager;
 
-    public SyntheticPasswordManager(LockSettingsStorage storage, UserManager userManager) {
+    public SyntheticPasswordManager(Context context, LockSettingsStorage storage,
+            UserManager userManager) {
+        mContext = context;
         mStorage = storage;
         mUserManager = userManager;
     }
@@ -645,7 +649,7 @@
 
     public void migrateFrpPasswordLocked(long handle, UserInfo userInfo, int requestedQuality) {
         if (mStorage.getPersistentDataBlock() != null
-                && LockPatternUtils.userOwnsFrpCredential(userInfo)) {
+                && LockPatternUtils.userOwnsFrpCredential(mContext, userInfo)) {
             PasswordData pwd = PasswordData.fromBytes(loadState(PASSWORD_DATA_NAME, handle,
                     userInfo.id));
             if (pwd.passwordType != LockPatternUtils.CREDENTIAL_TYPE_NONE) {
@@ -662,7 +666,8 @@
     private void synchronizeFrpPassword(PasswordData pwd,
             int requestedQuality, int userId) {
         if (mStorage.getPersistentDataBlock() != null
-                && LockPatternUtils.userOwnsFrpCredential(mUserManager.getUserInfo(userId))) {
+                && LockPatternUtils.userOwnsFrpCredential(mContext,
+                mUserManager.getUserInfo(userId))) {
             if (pwd.passwordType != LockPatternUtils.CREDENTIAL_TYPE_NONE) {
                 mStorage.writePersistentDataBlock(PersistentData.TYPE_SP, userId, requestedQuality,
                         pwd.toBytes());
@@ -675,7 +680,8 @@
     private void synchronizeWeaverFrpPassword(PasswordData pwd, int requestedQuality, int userId,
             int weaverSlot) {
         if (mStorage.getPersistentDataBlock() != null
-                && LockPatternUtils.userOwnsFrpCredential(mUserManager.getUserInfo(userId))) {
+                && LockPatternUtils.userOwnsFrpCredential(mContext,
+                mUserManager.getUserInfo(userId))) {
             if (pwd.passwordType != LockPatternUtils.CREDENTIAL_TYPE_NONE) {
                 mStorage.writePersistentDataBlock(PersistentData.TYPE_SP_WEAVER, weaverSlot,
                         requestedQuality, pwd.toBytes());
diff --git a/com/android/server/media/MediaRouterService.java b/com/android/server/media/MediaRouterService.java
index 3795b7f..1cfd5f0 100644
--- a/com/android/server/media/MediaRouterService.java
+++ b/com/android/server/media/MediaRouterService.java
@@ -271,14 +271,6 @@
 
     // Binder call
     @Override
-    public boolean isGlobalBluetoothA2doOn() {
-        synchronized (mLock) {
-            return mGlobalBluetoothA2dpOn;
-        }
-    }
-
-    // Binder call
-    @Override
     public void setDiscoveryRequest(IMediaRouterClient client,
             int routeTypes, boolean activeScan) {
         if (client == null) {
@@ -383,7 +375,7 @@
             synchronized (mLock) {
                 a2dpOn = mGlobalBluetoothA2dpOn;
             }
-            Slog.v(TAG, "restoreBluetoothA2dp( " + a2dpOn + ")");
+            Slog.v(TAG, "restoreBluetoothA2dp(" + a2dpOn + ")");
             mAudioService.setBluetoothA2dpOn(a2dpOn);
         } catch (RemoteException e) {
             Slog.w(TAG, "RemoteException while calling setBluetoothA2dpOn.");
diff --git a/com/android/server/media/MediaSessionRecord.java b/com/android/server/media/MediaSessionRecord.java
index 89e1050..0b11479 100644
--- a/com/android/server/media/MediaSessionRecord.java
+++ b/com/android/server/media/MediaSessionRecord.java
@@ -462,18 +462,25 @@
         mHandler.post(new Runnable() {
             @Override
             public void run() {
-                if (useSuggested) {
-                    if (AudioSystem.isStreamActive(stream, 0)) {
-                        mAudioManagerInternal.adjustSuggestedStreamVolumeForUid(stream, direction,
-                                flags, packageName, uid);
+                try {
+                    if (useSuggested) {
+                        if (AudioSystem.isStreamActive(stream, 0)) {
+                            mAudioManagerInternal.adjustSuggestedStreamVolumeForUid(stream,
+                                    direction, flags, packageName, uid);
+                        } else {
+                            mAudioManagerInternal.adjustSuggestedStreamVolumeForUid(
+                                    AudioManager.USE_DEFAULT_STREAM_TYPE, direction,
+                                    flags | previousFlagPlaySound, packageName, uid);
+                        }
                     } else {
-                        mAudioManagerInternal.adjustSuggestedStreamVolumeForUid(
-                                AudioManager.USE_DEFAULT_STREAM_TYPE, direction,
-                                flags | previousFlagPlaySound, packageName, uid);
+                        mAudioManagerInternal.adjustStreamVolumeForUid(stream, direction, flags,
+                                packageName, uid);
                     }
-                } else {
-                    mAudioManagerInternal.adjustStreamVolumeForUid(stream, direction, flags,
-                            packageName, uid);
+                } catch (IllegalArgumentException e) {
+                    Log.e(TAG, "Cannot adjust volume: direction=" + direction + ", stream="
+                            + stream + ", flags=" + flags + ", packageName=" + packageName
+                            + ", uid=" + uid + ", useSuggested=" + useSuggested
+                            + ", previousFlagPlaySound=" + previousFlagPlaySound, e);
                 }
             }
         });
diff --git a/com/android/server/media/MediaSessionService.java b/com/android/server/media/MediaSessionService.java
index b77ed91..b9a2d18 100644
--- a/com/android/server/media/MediaSessionService.java
+++ b/com/android/server/media/MediaSessionService.java
@@ -1363,6 +1363,10 @@
                                     flags, packageName, TAG);
                         } catch (RemoteException e) {
                             Log.e(TAG, "Error adjusting default volume.", e);
+                        } catch (IllegalArgumentException e) {
+                            Log.e(TAG, "Cannot adjust volume: direction=" + direction
+                                    + ", suggestedStream=" + suggestedStream + ", flags=" + flags,
+                                    e);
                         }
                     }
                 });
diff --git a/com/android/server/net/NetworkPolicyManagerService.java b/com/android/server/net/NetworkPolicyManagerService.java
index 90dab2c..b4056b3 100644
--- a/com/android/server/net/NetworkPolicyManagerService.java
+++ b/com/android/server/net/NetworkPolicyManagerService.java
@@ -331,7 +331,6 @@
     private static final int MSG_UPDATE_INTERFACE_QUOTA = 10;
     private static final int MSG_REMOVE_INTERFACE_QUOTA = 11;
     private static final int MSG_POLICIES_CHANGED = 13;
-    private static final int MSG_SET_FIREWALL_RULES = 14;
     private static final int MSG_RESET_FIREWALL_RULES_BY_UID = 15;
 
     private static final int UID_MSG_STATE_CHANGED = 100;
@@ -3138,9 +3137,9 @@
                     uidRules.put(mUidState.keyAt(i), FIREWALL_RULE_ALLOW);
                 }
             }
-            setUidFirewallRulesAsync(chain, uidRules, CHAIN_TOGGLE_ENABLE);
+            setUidFirewallRulesUL(chain, uidRules, CHAIN_TOGGLE_ENABLE);
         } else {
-            setUidFirewallRulesAsync(chain, null, CHAIN_TOGGLE_DISABLE);
+            setUidFirewallRulesUL(chain, null, CHAIN_TOGGLE_DISABLE);
         }
     }
 
@@ -3207,7 +3206,7 @@
                 }
             }
 
-            setUidFirewallRulesAsync(FIREWALL_CHAIN_STANDBY, uidRules, CHAIN_TOGGLE_NONE);
+            setUidFirewallRulesUL(FIREWALL_CHAIN_STANDBY, uidRules, CHAIN_TOGGLE_NONE);
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_NETWORK);
         }
@@ -3906,18 +3905,6 @@
                     removeInterfaceQuota((String) msg.obj);
                     return true;
                 }
-                case MSG_SET_FIREWALL_RULES: {
-                    final int chain = msg.arg1;
-                    final int toggle = msg.arg2;
-                    final SparseIntArray uidRules = (SparseIntArray) msg.obj;
-                    if (uidRules != null) {
-                        setUidFirewallRules(chain, uidRules);
-                    }
-                    if (toggle != CHAIN_TOGGLE_NONE) {
-                        enableFirewallChainUL(chain, toggle == CHAIN_TOGGLE_ENABLE);
-                    }
-                    return true;
-                }
                 case MSG_RESET_FIREWALL_RULES_BY_UID: {
                     resetUidFirewallRules(msg.arg1);
                     return true;
@@ -4063,15 +4050,20 @@
 
     /**
      * Calls {@link #setUidFirewallRules(int, SparseIntArray)} and
-     * {@link #enableFirewallChainUL(int, boolean)} asynchronously.
+     * {@link #enableFirewallChainUL(int, boolean)} synchronously.
      *
      * @param chain firewall chain.
      * @param uidRules new UID rules; if {@code null}, only toggles chain state.
      * @param toggle whether the chain should be enabled, disabled, or not changed.
      */
-    private void setUidFirewallRulesAsync(int chain, @Nullable SparseIntArray uidRules,
+    private void setUidFirewallRulesUL(int chain, @Nullable SparseIntArray uidRules,
             @ChainToggleType int toggle) {
-        mHandler.obtainMessage(MSG_SET_FIREWALL_RULES, chain, toggle, uidRules).sendToTarget();
+        if (uidRules != null) {
+            setUidFirewallRulesUL(chain, uidRules);
+        }
+        if (toggle != CHAIN_TOGGLE_NONE) {
+            enableFirewallChainUL(chain, toggle == CHAIN_TOGGLE_ENABLE);
+        }
     }
 
     /**
@@ -4079,7 +4071,7 @@
      * here to netd.  It will clean up dead rules and make sure the target chain only contains rules
      * specified here.
      */
-    private void setUidFirewallRules(int chain, SparseIntArray uidRules) {
+    private void setUidFirewallRulesUL(int chain, SparseIntArray uidRules) {
         try {
             int size = uidRules.size();
             int[] uids = new int[size];
diff --git a/com/android/server/notification/ConditionProviders.java b/com/android/server/notification/ConditionProviders.java
index 3444ef3..c0fbfbb 100644
--- a/com/android/server/notification/ConditionProviders.java
+++ b/com/android/server/notification/ConditionProviders.java
@@ -186,6 +186,11 @@
         super.onPackagesChanged(removingPackage, pkgList, uid);
     }
 
+    @Override
+    protected boolean isValidEntry(String packageOrComponent, int userId) {
+        return true;
+    }
+
     public ManagedServiceInfo checkServiceToken(IConditionProvider provider) {
         synchronized(mMutex) {
             return checkServiceTokenLocked(provider);
diff --git a/com/android/server/notification/ImportanceExtractor.java b/com/android/server/notification/ImportanceExtractor.java
index 46ec92b..452121c 100644
--- a/com/android/server/notification/ImportanceExtractor.java
+++ b/com/android/server/notification/ImportanceExtractor.java
@@ -22,7 +22,7 @@
  * Determines the importance of the given notification.
  */
 public class ImportanceExtractor implements NotificationSignalExtractor {
-    private static final String TAG = "ImportantTopicExtractor";
+    private static final String TAG = "ImportanceExtractor";
     private static final boolean DBG = false;
 
     private RankingConfig mConfig;
diff --git a/com/android/server/notification/ManagedServices.java b/com/android/server/notification/ManagedServices.java
index add4184..019c7c2 100644
--- a/com/android/server/notification/ManagedServices.java
+++ b/com/android/server/notification/ManagedServices.java
@@ -45,12 +45,16 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
+import android.service.notification.ManagedServiceInfoProto;
+import android.service.notification.ManagedServicesProto;
+import android.service.notification.ManagedServicesProto.ServiceProto;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.util.XmlUtils;
 import com.android.server.notification.NotificationManagerService.DumpFilter;
@@ -214,6 +218,53 @@
         }
     }
 
+    public void dump(ProtoOutputStream proto, DumpFilter filter) {
+        proto.write(ManagedServicesProto.CAPTION, getCaption());
+        final int N = mApproved.size();
+        for (int i = 0 ; i < N; i++) {
+            final int userId = mApproved.keyAt(i);
+            final ArrayMap<Boolean, ArraySet<String>> approvedByType = mApproved.valueAt(i);
+            if (approvedByType != null) {
+                final int M = approvedByType.size();
+                for (int j = 0; j < M; j++) {
+                    final boolean isPrimary = approvedByType.keyAt(j);
+                    final ArraySet<String> approved = approvedByType.valueAt(j);
+                    if (approvedByType != null && approvedByType.size() > 0) {
+                        final long sToken = proto.start(ManagedServicesProto.APPROVED);
+                        for (String s : approved) {
+                            proto.write(ServiceProto.NAME, s);
+                        }
+                        proto.write(ServiceProto.USER_ID, userId);
+                        proto.write(ServiceProto.IS_PRIMARY, isPrimary);
+                        proto.end(sToken);
+                    }
+                }
+            }
+        }
+
+        for (ComponentName cmpt : mEnabledServicesForCurrentProfiles) {
+            if (filter != null && !filter.matches(cmpt)) continue;
+
+            final long cToken = proto.start(ManagedServicesProto.ENABLED);
+            cmpt.toProto(proto);
+            proto.end(cToken);
+        }
+
+        for (ManagedServiceInfo info : mServices) {
+            if (filter != null && !filter.matches(info.component)) continue;
+
+            final long lToken = proto.start(ManagedServicesProto.LIVE_SERVICES);
+            info.toProto(proto, this);
+            proto.end(lToken);
+        }
+
+        for (ComponentName name : mSnoozingForCurrentProfiles) {
+            final long cToken = proto.start(ManagedServicesProto.SNOOZED);
+            name.toProto(proto);
+            proto.end(cToken);
+        }
+    }
+
     protected void onSettingRestored(String element, String value, int backupSdkInt, int userId) {
         if (!mUseXml) {
             Slog.d(TAG, "Restored managed service setting: " + element);
@@ -294,6 +345,7 @@
             }
             if (type == XmlPullParser.START_TAG) {
                 if (TAG_MANAGED_SERVICES.equals(tag)) {
+                    Slog.i(TAG, "Read " + mConfig.caption + " permissions from xml");
                     final String approved = XmlUtils.readStringAttribute(parser, ATT_APPROVED_LIST);
                     final int userId = XmlUtils.readIntAttribute(parser, ATT_USER_ID, 0);
                     final boolean isPrimary =
@@ -353,6 +405,8 @@
 
     protected void setPackageOrComponentEnabled(String pkgOrComponent, int userId,
             boolean isPrimary, boolean enabled) {
+        Slog.i(TAG,
+                (enabled ? " Allowing " : "Disallowing ") + mConfig.caption + " " + pkgOrComponent);
         ArrayMap<Boolean, ArraySet<String>> allowedByType = mApproved.get(userId);
         if (allowedByType == null) {
             allowedByType = new ArrayMap<>();
@@ -460,6 +514,7 @@
     }
 
     public void onUserRemoved(int user) {
+        Slog.i(TAG, "Removing approved services for removed user " + user);
         mApproved.remove(user);
         rebindServices(true);
     }
@@ -491,6 +546,17 @@
         return null;
     }
 
+    protected boolean isServiceTokenValidLocked(IInterface service) {
+        if (service == null) {
+            return false;
+        }
+        ManagedServiceInfo info = getServiceFromTokenLocked(service);
+        if (info != null) {
+            return true;
+        }
+        return false;
+    }
+
     protected ManagedServiceInfo checkServiceTokenLocked(IInterface service) {
         checkNotNull(service);
         ManagedServiceInfo info = getServiceFromTokenLocked(service);
@@ -543,10 +609,8 @@
         }
 
         // State changed
-        if (DEBUG) {
-            Slog.d(TAG, ((enabled) ? "Enabling " : "Disabling ") + "component " +
-                    component.flattenToShortString());
-        }
+        Slog.d(TAG, ((enabled) ? "Enabling " : "Disabling ") + "component " +
+                component.flattenToShortString());
 
         synchronized (mMutex) {
             final int[] userIds = mUserProfiles.getCurrentProfileIds();
@@ -628,12 +692,10 @@
                 int P = approved.size();
                 for (int k = P - 1; k >= 0; k--) {
                     final String approvedPackageOrComponent = approved.valueAt(k);
-                    if (!hasMatchingServices(approvedPackageOrComponent, userId)){
+                    if (!isValidEntry(approvedPackageOrComponent, userId)){
                         approved.removeAt(k);
-                        if (DEBUG) {
-                            Slog.v(TAG, "Removing " + approvedPackageOrComponent
-                                    + " from approved list; no matching services found");
-                        }
+                        Slog.v(TAG, "Removing " + approvedPackageOrComponent
+                                + " from approved list; no matching services found");
                     } else {
                         if (DEBUG) {
                             Slog.v(TAG, "Keeping " + approvedPackageOrComponent
@@ -678,6 +740,10 @@
         }
     }
 
+    protected boolean isValidEntry(String packageOrComponent, int userId) {
+        return hasMatchingServices(packageOrComponent, userId);
+    }
+
     private boolean hasMatchingServices(String packageOrComponent, int userId) {
         if (!TextUtils.isEmpty(packageOrComponent)) {
             final String packageName = getPackageName(packageOrComponent);
@@ -774,7 +840,12 @@
                     ServiceInfo info = mPm.getServiceInfo(component,
                             PackageManager.MATCH_DIRECT_BOOT_AWARE
                                     | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userIds[i]);
-                    if (info == null || !mConfig.bindPermission.equals(info.permission)) {
+                    if (info == null) {
+                        Slog.w(TAG, "Not binding " + getCaption() + " service " + component
+                                + ": service not found");
+                        continue;
+                    }
+                    if (!mConfig.bindPermission.equals(info.permission)) {
                         Slog.w(TAG, "Not binding " + getCaption() + " service " + component
                                 + ": it does not require the permission " + mConfig.bindPermission);
                         continue;
@@ -830,8 +901,7 @@
             if (name.equals(info.component)
                 && info.userid == userid) {
                 // cut old connections
-                if (DEBUG) Slog.v(TAG, "    disconnecting old " + getCaption() + ": "
-                    + info.service);
+                Slog.v(TAG, "    disconnecting old " + getCaption() + ": " + info.service);
                 removeServiceLocked(i);
                 if (info.connection != null) {
                     mContext.unbindService(info.connection);
@@ -859,7 +929,7 @@
             appInfo != null ? appInfo.targetSdkVersion : Build.VERSION_CODES.BASE;
 
         try {
-            if (DEBUG) Slog.v(TAG, "binding: " + intent);
+            Slog.v(TAG, "binding: " + intent);
             ServiceConnection serviceConnection = new ServiceConnection() {
                 IInterface mService;
 
@@ -917,8 +987,7 @@
         final int N = mServices.size();
         for (int i = N - 1; i >= 0; i--) {
             final ManagedServiceInfo info = mServices.get(i);
-            if (name.equals(info.component)
-                && info.userid == userid) {
+            if (name.equals(info.component) && info.userid == userid) {
                 removeServiceLocked(i);
                 if (info.connection != null) {
                     try {
@@ -945,9 +1014,8 @@
             final int N = mServices.size();
             for (int i = N - 1; i >= 0; i--) {
                 final ManagedServiceInfo info = mServices.get(i);
-                if (info.service.asBinder() == service.asBinder()
-                        && info.userid == userid) {
-                    if (DEBUG) Slog.d(TAG, "Removing active service " + info.component);
+                if (info.service.asBinder() == service.asBinder() && info.userid == userid) {
+                    Slog.d(TAG, "Removing active service " + info.component);
                     serviceInfo = removeServiceLocked(i);
                 }
             }
@@ -1035,6 +1103,16 @@
                     .append(']').toString();
         }
 
+        public void toProto(ProtoOutputStream proto, ManagedServices host) {
+            final long cToken = proto.start(ManagedServiceInfoProto.COMPONENT);
+            component.toProto(proto);
+            proto.end(cToken);
+            proto.write(ManagedServiceInfoProto.USER_ID, userid);
+            proto.write(ManagedServiceInfoProto.SERVICE, service.getClass().getName());
+            proto.write(ManagedServiceInfoProto.IS_SYSTEM, isSystem);
+            proto.write(ManagedServiceInfoProto.IS_GUEST, isGuest(host));
+        }
+
         public boolean enabledAndUserMatches(int nid) {
             if (!isEnabledForCurrentProfiles()) {
                 return false;
diff --git a/com/android/server/notification/NotificationAdjustmentExtractor.java b/com/android/server/notification/NotificationAdjustmentExtractor.java
index 7c82845..3bfd93f 100644
--- a/com/android/server/notification/NotificationAdjustmentExtractor.java
+++ b/com/android/server/notification/NotificationAdjustmentExtractor.java
@@ -22,7 +22,7 @@
  * Applies adjustments from the group helper and notification assistant
  */
 public class NotificationAdjustmentExtractor implements NotificationSignalExtractor {
-    private static final String TAG = "BadgeExtractor";
+    private static final String TAG = "AdjustmentExtractor";
     private static final boolean DBG = false;
 
 
@@ -35,7 +35,6 @@
             if (DBG) Slog.d(TAG, "skipping empty notification");
             return null;
         }
-
         record.applyAdjustments();
 
         return null;
diff --git a/com/android/server/notification/NotificationChannelExtractor.java b/com/android/server/notification/NotificationChannelExtractor.java
index 46ab556..11c7ab7 100644
--- a/com/android/server/notification/NotificationChannelExtractor.java
+++ b/com/android/server/notification/NotificationChannelExtractor.java
@@ -22,7 +22,7 @@
  * Stores the latest notification channel information for this notification
  */
 public class NotificationChannelExtractor implements NotificationSignalExtractor {
-    private static final String TAG = "BadgeExtractor";
+    private static final String TAG = "ChannelExtractor";
     private static final boolean DBG = false;
 
     private RankingConfig mConfig;
diff --git a/com/android/server/notification/NotificationDelegate.java b/com/android/server/notification/NotificationDelegate.java
index 6a1401c..36bc096 100644
--- a/com/android/server/notification/NotificationDelegate.java
+++ b/com/android/server/notification/NotificationDelegate.java
@@ -16,6 +16,8 @@
 
 package com.android.server.notification;
 
+import android.service.notification.NotificationStats;
+
 import com.android.internal.statusbar.NotificationVisibility;
 
 public interface NotificationDelegate {
@@ -24,7 +26,8 @@
     void onNotificationClick(int callingUid, int callingPid, String key);
     void onNotificationActionClick(int callingUid, int callingPid, String key, int actionIndex);
     void onNotificationClear(int callingUid, int callingPid,
-            String pkg, String tag, int id, int userId);
+            String pkg, String tag, int id, int userId, String key,
+            @NotificationStats.DismissalSurface int dismissalSurface);
     void onNotificationError(int callingUid, int callingPid,
             String pkg, String tag, int id,
             int uid, int initialPid, String message, int userId);
@@ -35,4 +38,6 @@
             NotificationVisibility[] newlyVisibleKeys,
             NotificationVisibility[] noLongerVisibleKeys);
     void onNotificationExpansionChanged(String key, boolean userAction, boolean expanded);
+    void onNotificationDirectReplied(String key);
+    void onNotificationSettingsViewed(String key);
 }
diff --git a/com/android/server/notification/NotificationManagerInternal.java b/com/android/server/notification/NotificationManagerInternal.java
index 4923b06..f1476b3 100644
--- a/com/android/server/notification/NotificationManagerInternal.java
+++ b/com/android/server/notification/NotificationManagerInternal.java
@@ -17,8 +17,10 @@
 package com.android.server.notification;
 
 import android.app.Notification;
+import android.app.NotificationChannel;
 
 public interface NotificationManagerInternal {
+    NotificationChannel getNotificationChannel(String pkg, int uid, String channelId);
     void enqueueNotification(String pkg, String basePkg, int callingUid, int callingPid,
             String tag, int id, Notification notification, int userId);
 
diff --git a/com/android/server/notification/NotificationManagerService.java b/com/android/server/notification/NotificationManagerService.java
index fe39fcc..14cd055 100644
--- a/com/android/server/notification/NotificationManagerService.java
+++ b/com/android/server/notification/NotificationManagerService.java
@@ -16,8 +16,10 @@
 
 package com.android.server.notification;
 
+import static android.app.NotificationManager.IMPORTANCE_LOW;
 import static android.app.NotificationManager.IMPORTANCE_MIN;
 import static android.app.NotificationManager.IMPORTANCE_NONE;
+import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
 import static android.content.pm.PackageManager.FEATURE_LEANBACK;
 import static android.content.pm.PackageManager.FEATURE_TELEVISION;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
@@ -125,12 +127,14 @@
 import android.service.notification.IConditionProvider;
 import android.service.notification.INotificationListener;
 import android.service.notification.IStatusBarNotificationHolder;
+import android.service.notification.ListenersDisablingEffectsProto;
 import android.service.notification.NotificationAssistantService;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.NotificationRankingUpdate;
 import android.service.notification.NotificationRecordProto;
 import android.service.notification.NotificationServiceDumpProto;
 import android.service.notification.NotificationServiceProto;
+import android.service.notification.NotificationStats;
 import android.service.notification.SnoozeCriterion;
 import android.service.notification.StatusBarNotification;
 import android.service.notification.ZenModeConfig;
@@ -675,7 +679,14 @@
 
         @Override
         public void onNotificationClear(int callingUid, int callingPid,
-                String pkg, String tag, int id, int userId) {
+                String pkg, String tag, int id, int userId, String key,
+                @NotificationStats.DismissalSurface int dismissalSurface) {
+            synchronized (mNotificationLock) {
+                NotificationRecord r = mNotificationsByKey.get(key);
+                if (r != null) {
+                    r.recordDismissalSurface(dismissalSurface);
+                }
+            }
             cancelNotification(callingUid, callingPid, pkg, tag, id, 0,
                     Notification.FLAG_ONGOING_EVENT | Notification.FLAG_FOREGROUND_SERVICE,
                     true, userId, REASON_CANCEL, null);
@@ -761,12 +772,35 @@
                                 .setType(expanded ? MetricsEvent.TYPE_DETAIL
                                         : MetricsEvent.TYPE_COLLAPSE));
                     }
+                    if (expanded) {
+                        r.recordExpanded();
+                    }
                     EventLogTags.writeNotificationExpansion(key,
                             userAction ? 1 : 0, expanded ? 1 : 0,
                             r.getLifespanMs(now), r.getFreshnessMs(now), r.getExposureMs(now));
                 }
             }
         }
+
+        @Override
+        public void onNotificationDirectReplied(String key) {
+            synchronized (mNotificationLock) {
+                NotificationRecord r = mNotificationsByKey.get(key);
+                if (r != null) {
+                    r.recordDirectReplied();
+                }
+            }
+        }
+
+        @Override
+        public void onNotificationSettingsViewed(String key) {
+            synchronized (mNotificationLock) {
+                NotificationRecord r = mNotificationsByKey.get(key);
+                if (r != null) {
+                    r.recordViewedSettings();
+                }
+            }
+        }
     };
 
     @GuardedBy("mNotificationLock")
@@ -1142,6 +1176,12 @@
     }
 
     @VisibleForTesting
+    NotificationRecord getNotificationRecord(String key) {
+        return mNotificationsByKey.get(key);
+    }
+
+
+    @VisibleForTesting
     void setSystemReady(boolean systemReady) {
         mSystemReady = systemReady;
     }
@@ -1216,7 +1256,7 @@
         mUsageStats = usageStats;
         mRankingHandler = new RankingHandlerWorker(mRankingThread.getLooper());
         mRankingHelper = new RankingHelper(getContext(),
-                getContext().getPackageManager(),
+                mPackageManagerClient,
                 mRankingHandler,
                 mUsageStats,
                 extractorNames);
@@ -1269,13 +1309,11 @@
                 R.array.config_notificationFallbackVibePattern,
                 VIBRATE_PATTERN_MAXLEN,
                 DEFAULT_VIBRATE_PATTERN);
-
         mInCallNotificationUri = Uri.parse("file://" +
                 resources.getString(R.string.config_inCallNotificationSound));
         mInCallNotificationAudioAttributes = new AudioAttributes.Builder()
                 .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
                 .setUsage(AudioAttributes.USAGE_VOICE_COMMUNICATION)
-                .setFlags(AudioAttributes.FLAG_AUDIBILITY_ENFORCED)
                 .build();
         mInCallNotificationVolume = resources.getFloat(R.dimen.config_inCallNotificationVolume);
 
@@ -1476,7 +1514,7 @@
                 }
             }
         }
-        mRankingHelper.updateNotificationChannel(pkg, uid, channel);
+        mRankingHelper.updateNotificationChannel(pkg, uid, channel, true);
 
         if (!fromListener) {
             final NotificationChannel modifiedChannel =
@@ -3239,14 +3277,51 @@
                 }
             }
             proto.end(records);
-        }
 
-        long zenLog = proto.start(NotificationServiceDumpProto.ZEN);
-        mZenModeHelper.dump(proto);
-        for (ComponentName suppressor : mEffectsSuppressors) {
-            proto.write(ZenModeProto.SUPPRESSORS, suppressor.toString());
+            long zenLog = proto.start(NotificationServiceDumpProto.ZEN);
+            mZenModeHelper.dump(proto);
+            for (ComponentName suppressor : mEffectsSuppressors) {
+                proto.write(ZenModeProto.SUPPRESSORS, suppressor.toString());
+            }
+            proto.end(zenLog);
+
+            long listenersToken = proto.start(NotificationServiceDumpProto.NOTIFICATION_LISTENERS);
+            mListeners.dump(proto, filter);
+            proto.end(listenersToken);
+
+            proto.write(NotificationServiceDumpProto.LISTENER_HINTS, mListenerHints);
+
+            for (int i = 0; i < mListenersDisablingEffects.size(); ++i) {
+                long effectsToken = proto.start(
+                    NotificationServiceDumpProto.LISTENERS_DISABLING_EFFECTS);
+
+                proto.write(
+                    ListenersDisablingEffectsProto.HINT, mListenersDisablingEffects.keyAt(i));
+                final ArraySet<ManagedServiceInfo> listeners =
+                    mListenersDisablingEffects.valueAt(i);
+                for (int j = 0; j < listeners.size(); j++) {
+                    final ManagedServiceInfo listener = listeners.valueAt(i);
+                    listenersToken = proto.start(ListenersDisablingEffectsProto.LISTENERS);
+                    listener.toProto(proto, null);
+                    proto.end(listenersToken);
+                }
+
+                proto.end(effectsToken);
+            }
+
+            long assistantsToken = proto.start(
+                NotificationServiceDumpProto.NOTIFICATION_ASSISTANTS);
+            mAssistants.dump(proto, filter);
+            proto.end(assistantsToken);
+
+            long conditionsToken = proto.start(NotificationServiceDumpProto.CONDITION_PROVIDERS);
+            mConditionProviders.dump(proto, filter);
+            proto.end(conditionsToken);
+
+            long rankingToken = proto.start(NotificationServiceDumpProto.RANKING_CONFIG);
+            mRankingHelper.dump(proto, filter);
+            proto.end(rankingToken);
         }
-        proto.end(zenLog);
 
         proto.flush();
     }
@@ -3401,6 +3476,12 @@
      */
     private final NotificationManagerInternal mInternalService = new NotificationManagerInternal() {
         @Override
+        public NotificationChannel getNotificationChannel(String pkg, int uid, String
+                channelId) {
+            return mRankingHelper.getNotificationChannel(pkg, uid, channelId, false);
+        }
+
+        @Override
         public void enqueueNotification(String pkg, String opPkg, int callingUid, int callingPid,
                 String tag, int id, Notification notification, int userId) {
             enqueueNotificationInternal(pkg, opPkg, callingUid, callingPid, tag, id, notification,
@@ -3519,6 +3600,21 @@
                 user, null, System.currentTimeMillis());
         final NotificationRecord r = new NotificationRecord(getContext(), n, channel);
 
+        if ((notification.flags & Notification.FLAG_FOREGROUND_SERVICE) != 0
+                && (channel.getUserLockedFields() & NotificationChannel.USER_LOCKED_IMPORTANCE) == 0
+                && (r.getImportance() == IMPORTANCE_MIN || r.getImportance() == IMPORTANCE_NONE)) {
+            // Increase the importance of foreground service notifications unless the user had an
+            // opinion otherwise
+            if (TextUtils.isEmpty(channelId)
+                    || NotificationChannel.DEFAULT_CHANNEL_ID.equals(channelId)) {
+                r.setImportance(IMPORTANCE_LOW, "Bumped for foreground service");
+            } else {
+                channel.setImportance(IMPORTANCE_LOW);
+                mRankingHelper.updateNotificationChannel(pkg, notificationUid, channel, false);
+                r.updateNotificationChannel(channel);
+            }
+        }
+
         if (!checkDisqualifyingFeatures(userId, notificationUid, id, tag, r,
                 r.sbn.getOverrideGroupKey() != null)) {
             return;
@@ -3752,6 +3848,8 @@
             MetricsLogger.action(r.getLogMaker()
                     .setCategory(MetricsEvent.NOTIFICATION_SNOOZED)
                     .setType(MetricsEvent.TYPE_CLOSE)
+                    .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_SNOOZE_DURATION_MS,
+                            mDuration)
                     .addTaggedData(MetricsEvent.NOTIFICATION_SNOOZED_CRITERIA,
                             mSnoozeCriterionId == null ? 0 : 1));
             boolean wasPosted = removeFromNotificationListsLocked(r);
@@ -3763,6 +3861,7 @@
             } else {
                 mSnoozeHelper.snooze(r, mDuration);
             }
+            r.recordSnoozed();
             savePolicyFile();
         }
     }
@@ -3902,7 +4001,7 @@
                         Slog.e(TAG, "Not posting notification without small icon: " + notification);
                         if (old != null && !old.isCanceled) {
                             mListeners.notifyRemovedLocked(n,
-                                    NotificationListenerService.REASON_ERROR);
+                                    NotificationListenerService.REASON_ERROR, null);
                             mHandler.post(new Runnable() {
                                 @Override
                                 public void run() {
@@ -4028,19 +4127,19 @@
             if (mSystemReady && mAudioManager != null) {
                 Uri soundUri = record.getSound();
                 hasValidSound = soundUri != null && !Uri.EMPTY.equals(soundUri);
-
                 long[] vibration = record.getVibration();
                 // Demote sound to vibration if vibration missing & phone in vibration mode.
                 if (vibration == null
                         && hasValidSound
                         && (mAudioManager.getRingerModeInternal()
-                        == AudioManager.RINGER_MODE_VIBRATE)) {
+                        == AudioManager.RINGER_MODE_VIBRATE)
+                        && mAudioManager.getStreamVolume(
+                        AudioAttributes.toLegacyStreamType(record.getAudioAttributes())) == 0) {
                     vibration = mFallbackVibrationPattern;
                 }
                 hasValidVibrate = vibration != null;
 
                 boolean hasAudibleAlert = hasValidSound || hasValidVibrate;
-
                 if (hasAudibleAlert && !shouldMuteNotificationLocked(record)) {
                     if (DBG) Slog.v(TAG, "Interrupting!");
                     if (hasValidSound) {
@@ -4137,8 +4236,9 @@
         boolean looping = (record.getNotification().flags & Notification.FLAG_INSISTENT) != 0;
         // do not play notifications if there is a user of exclusive audio focus
         // or the device is in vibrate mode
-        if (!mAudioManager.isAudioFocusExclusive() && mAudioManager.getRingerModeInternal()
-                != AudioManager.RINGER_MODE_VIBRATE) {
+        if (!mAudioManager.isAudioFocusExclusive() && (mAudioManager.getRingerModeInternal()
+                != AudioManager.RINGER_MODE_VIBRATE || mAudioManager.getStreamVolume(
+                AudioAttributes.toLegacyStreamType(record.getAudioAttributes())) != 0)) {
             final long identity = Binder.clearCallingIdentity();
             try {
                 final IRingtonePlayer player = mAudioManager.getRingtonePlayer();
@@ -4394,6 +4494,7 @@
             ArrayList<String> groupKeyBefore = new ArrayList<>(N);
             ArrayList<ArrayList<String>> overridePeopleBefore = new ArrayList<>(N);
             ArrayList<ArrayList<SnoozeCriterion>> snoozeCriteriaBefore = new ArrayList<>(N);
+            ArrayList<Integer> userSentimentBefore = new ArrayList<>(N);
             for (int i = 0; i < N; i++) {
                 final NotificationRecord r = mNotificationList.get(i);
                 orderBefore.add(r.getKey());
@@ -4403,6 +4504,7 @@
                 groupKeyBefore.add(r.getGroupKey());
                 overridePeopleBefore.add(r.getPeopleOverride());
                 snoozeCriteriaBefore.add(r.getSnoozeCriteria());
+                userSentimentBefore.add(r.getUserSentiment());
                 mRankingHelper.extractSignals(r);
             }
             mRankingHelper.sort(mNotificationList);
@@ -4414,7 +4516,8 @@
                         || !Objects.equals(channelBefore.get(i), r.getChannel())
                         || !Objects.equals(groupKeyBefore.get(i), r.getGroupKey())
                         || !Objects.equals(overridePeopleBefore.get(i), r.getPeopleOverride())
-                        || !Objects.equals(snoozeCriteriaBefore.get(i), r.getSnoozeCriteria())) {
+                        || !Objects.equals(snoozeCriteriaBefore.get(i), r.getSnoozeCriteria())
+                        || !Objects.equals(userSentimentBefore.get(i), r.getUserSentiment())) {
                     mHandler.scheduleSendRankingUpdate();
                     return;
                 }
@@ -4607,6 +4710,10 @@
         // Record caller.
         recordCallerLocked(r);
 
+        if (r.getStats().getDismissalSurface() == NotificationStats.DISMISSAL_NOT_DISMISSED) {
+            r.recordDismissalSurface(NotificationStats.DISMISSAL_OTHER);
+        }
+
         // tell the app
         if (sendDelete) {
             if (r.getNotification().deleteIntent != null) {
@@ -4627,7 +4734,7 @@
                 if (reason != REASON_SNOOZED) {
                     r.isCanceled = true;
                 }
-                mListeners.notifyRemovedLocked(r.sbn, reason);
+                mListeners.notifyRemovedLocked(r.sbn, reason, r.getStats());
                 mHandler.post(new Runnable() {
                     @Override
                     public void run() {
@@ -5221,6 +5328,7 @@
         Bundle overridePeople = new Bundle();
         Bundle snoozeCriteria = new Bundle();
         Bundle showBadge = new Bundle();
+        Bundle userSentiment = new Bundle();
         for (int i = 0; i < N; i++) {
             NotificationRecord record = mNotificationList.get(i);
             if (!isVisibleToListener(record.sbn, info)) {
@@ -5246,6 +5354,7 @@
             overridePeople.putStringArrayList(key, record.getPeopleOverride());
             snoozeCriteria.putParcelableArrayList(key, record.getSnoozeCriteria());
             showBadge.putBoolean(key, record.canShowBadge());
+            userSentiment.putInt(key, record.getUserSentiment());
         }
         final int M = keys.size();
         String[] keysAr = keys.toArray(new String[M]);
@@ -5256,7 +5365,7 @@
         }
         return new NotificationRankingUpdate(keysAr, interceptedKeysAr, visibilityOverrides,
                 suppressedVisualEffects, importanceAr, explanation, overrideGroupKeys,
-                channels, overridePeople, snoozeCriteria, showBadge);
+                channels, overridePeople, snoozeCriteria, showBadge, userSentiment);
     }
 
     boolean hasCompanionDevice(ManagedServiceInfo info) {
@@ -5345,7 +5454,7 @@
         @Override
         protected Config getConfig() {
             Config c = new Config();
-            c.caption = "notification assistant service";
+            c.caption = "notification assistant";
             c.serviceInterface = NotificationAssistantService.SERVICE_INTERFACE;
             c.xmlTag = TAG_ENABLED_NOTIFICATION_ASSISTANTS;
             c.secureSettingName = Settings.Secure.ENABLED_NOTIFICATION_ASSISTANT;
@@ -5388,8 +5497,6 @@
                     continue;
                 }
 
-                final int importance = r.getImportance();
-                final boolean fromUser = r.isImportanceFromUser();
                 final StatusBarNotification sbnToPost =  trimCache.ForListener(info);
                 mHandler.post(new Runnable() {
                     @Override
@@ -5420,6 +5527,10 @@
                 final String snoozeCriterionId) {
             TrimCache trimCache = new TrimCache(sbn);
             for (final ManagedServiceInfo info : getServices()) {
+                boolean sbnVisible = isVisibleToListener(sbn, info);
+                if (!sbnVisible) {
+                    continue;
+                }
                 final StatusBarNotification sbnToPost =  trimCache.ForListener(info);
                 mHandler.post(new Runnable() {
                     @Override
@@ -5541,7 +5652,8 @@
                     mHandler.post(new Runnable() {
                         @Override
                         public void run() {
-                            notifyRemoved(info, oldSbnLightClone, update, REASON_USER_STOPPED);
+                            notifyRemoved(
+                                    info, oldSbnLightClone, update, null, REASON_USER_STOPPED);
                         }
                     });
                     continue;
@@ -5561,7 +5673,8 @@
          * asynchronously notify all listeners about a removed notification
          */
         @GuardedBy("mNotificationLock")
-        public void notifyRemovedLocked(StatusBarNotification sbn, int reason) {
+        public void notifyRemovedLocked(StatusBarNotification sbn, int reason,
+                NotificationStats notificationStats) {
             // make a copy in case changes are made to the underlying Notification object
             // NOTE: this copy is lightweight: it doesn't include heavyweight parts of the
             // notification
@@ -5570,11 +5683,14 @@
                 if (!isVisibleToListener(sbn, info)) {
                     continue;
                 }
+                // Only assistants can get stats
+                final NotificationStats stats = mAssistants.isServiceTokenValidLocked(info.service)
+                        ? notificationStats : null;
                 final NotificationRankingUpdate update = makeRankingUpdateLocked(info);
                 mHandler.post(new Runnable() {
                     @Override
                     public void run() {
-                        notifyRemoved(info, sbnLight, update, reason);
+                        notifyRemoved(info, sbnLight, update, stats, reason);
                     }
                 });
             }
@@ -5679,14 +5795,14 @@
         }
 
         private void notifyRemoved(ManagedServiceInfo info, StatusBarNotification sbn,
-                NotificationRankingUpdate rankingUpdate, int reason) {
+                NotificationRankingUpdate rankingUpdate, NotificationStats stats, int reason) {
             if (!info.enabledAndUserMatches(sbn.getUserId())) {
                 return;
             }
             final INotificationListener listener = (INotificationListener) info.service;
             StatusBarNotificationHolder sbnHolder = new StatusBarNotificationHolder(sbn);
             try {
-                listener.onNotificationRemoved(sbnHolder, rankingUpdate, reason);
+                listener.onNotificationRemoved(sbnHolder, rankingUpdate, stats, reason);
             } catch (RemoteException ex) {
                 Log.e(TAG, "unable to notify listener (removed): " + listener, ex);
             }
@@ -5772,10 +5888,9 @@
             final DumpFilter filter = new DumpFilter();
             for (int ai = 0; ai < args.length; ai++) {
                 final String a = args[ai];
-                if ("--proto".equals(args[0])) {
+                if ("--proto".equals(a)) {
                     filter.proto = true;
-                }
-                if ("--noredact".equals(a) || "--reveal".equals(a)) {
+                } else if ("--noredact".equals(a) || "--reveal".equals(a)) {
                     filter.redact = false;
                 } else if ("p".equals(a) || "pkg".equals(a) || "--package".equals(a)) {
                     if (ai < args.length-1) {
@@ -5848,8 +5963,8 @@
 
     private class ShellCmd extends ShellCommand {
         public static final String USAGE = "help\n"
-                + "allow_listener COMPONENT\n"
-                + "disallow_listener COMPONENT\n"
+                + "allow_listener COMPONENT [user_id]\n"
+                + "disallow_listener COMPONENT [user_id]\n"
                 + "set_assistant COMPONENT\n"
                 + "remove_assistant COMPONENT\n"
                 + "allow_dnd PACKAGE\n"
@@ -5880,7 +5995,13 @@
                             pw.println("Invalid listener - must be a ComponentName");
                             return -1;
                         }
-                        getBinderService().setNotificationListenerAccessGranted(cn, true);
+                        String userId = getNextArg();
+                        if (userId == null) {
+                            getBinderService().setNotificationListenerAccessGranted(cn, true);
+                        } else {
+                            getBinderService().setNotificationListenerAccessGrantedForUser(
+                                    cn, Integer.parseInt(userId), true);
+                        }
                     }
                     break;
                     case "disallow_listener": {
@@ -5889,7 +6010,13 @@
                             pw.println("Invalid listener - must be a ComponentName");
                             return -1;
                         }
-                        getBinderService().setNotificationListenerAccessGranted(cn, false);
+                        String userId = getNextArg();
+                        if (userId == null) {
+                            getBinderService().setNotificationListenerAccessGranted(cn, false);
+                        } else {
+                            getBinderService().setNotificationListenerAccessGrantedForUser(
+                                    cn, Integer.parseInt(userId), false);
+                        }
                     }
                     break;
                     case "allow_assistant": {
diff --git a/com/android/server/notification/NotificationRecord.java b/com/android/server/notification/NotificationRecord.java
index 77bf9e3..faa300f 100644
--- a/com/android/server/notification/NotificationRecord.java
+++ b/com/android/server/notification/NotificationRecord.java
@@ -20,6 +20,8 @@
 import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
 import static android.app.NotificationManager.IMPORTANCE_HIGH;
 import static android.app.NotificationManager.IMPORTANCE_LOW;
+import static android.service.notification.NotificationListenerService.Ranking
+        .USER_SENTIMENT_NEUTRAL;
 
 import android.app.Notification;
 import android.app.NotificationChannel;
@@ -41,6 +43,7 @@
 import android.service.notification.Adjustment;
 import android.service.notification.NotificationListenerService;
 import android.service.notification.NotificationRecordProto;
+import android.service.notification.NotificationStats;
 import android.service.notification.SnoozeCriterion;
 import android.service.notification.StatusBarNotification;
 import android.text.TextUtils;
@@ -84,8 +87,6 @@
 
     NotificationUsageStats.SingleNotificationStats stats;
     boolean isCanceled;
-    /** Whether the notification was seen by the user via one of the notification listeners. */
-    boolean mIsSeen;
 
     // These members are used by NotificationSignalExtractors
     // to communicate with the ranking module.
@@ -136,6 +137,8 @@
     private String mChannelIdLogTag;
 
     private final List<Adjustment> mAdjustments;
+    private final NotificationStats mStats;
+    private int mUserSentiment;
 
     @VisibleForTesting
     public NotificationRecord(Context context, StatusBarNotification sbn,
@@ -156,6 +159,7 @@
         mImportance = calculateImportance();
         mLight = calculateLights();
         mAdjustments = new ArrayList<>();
+        mStats = new NotificationStats();
     }
 
     private boolean isPreChannelsNotification() {
@@ -395,7 +399,7 @@
         pw.println(prefix + "flags=0x" + Integer.toHexString(notification.flags));
         pw.println(prefix + "pri=" + notification.priority);
         pw.println(prefix + "key=" + sbn.getKey());
-        pw.println(prefix + "seen=" + mIsSeen);
+        pw.println(prefix + "seen=" + mStats.hasSeen());
         pw.println(prefix + "groupKey=" + getGroupKey());
         pw.println(prefix + "fullscreenIntent=" + notification.fullScreenIntent);
         pw.println(prefix + "contentIntent=" + notification.contentIntent);
@@ -572,6 +576,10 @@
                             adjustment.getSignals().getString(Adjustment.KEY_GROUP_KEY);
                     setOverrideGroupKey(groupOverrideKey);
                 }
+                if (signals.containsKey(Adjustment.KEY_USER_SENTIMENT)) {
+                    setUserSentiment(adjustment.getSignals().getInt(
+                            Adjustment.KEY_USER_SENTIMENT, USER_SENTIMENT_NEUTRAL));
+                }
             }
         }
     }
@@ -740,6 +748,7 @@
                 .setType(visible ? MetricsEvent.TYPE_OPEN : MetricsEvent.TYPE_CLOSE)
                 .addTaggedData(MetricsEvent.NOTIFICATION_SHADE_INDEX, rank));
         if (visible) {
+            setSeen();
             MetricsLogger.histogram(mContext, "note_freshness", getFreshnessMs(now));
         }
         EventLogTags.writeNotificationVisibility(getKey(), visible ? 1 : 0,
@@ -777,12 +786,12 @@
 
     /** Check if any of the listeners have marked this notification as seen by the user. */
     public boolean isSeen() {
-        return mIsSeen;
+        return mStats.hasSeen();
     }
 
     /** Mark the notification as seen by the user. */
     public void setSeen() {
-        mIsSeen = true;
+        mStats.setSeen();
     }
 
     public void setAuthoritativeRank(int authoritativeRank) {
@@ -883,6 +892,38 @@
         mSnoozeCriteria = snoozeCriteria;
     }
 
+    private void setUserSentiment(int userSentiment) {
+        mUserSentiment = userSentiment;
+    }
+
+    public int getUserSentiment() {
+        return mUserSentiment;
+    }
+
+    public NotificationStats getStats() {
+        return mStats;
+    }
+
+    public void recordExpanded() {
+        mStats.setExpanded();
+    }
+
+    public void recordDirectReplied() {
+        mStats.setDirectReplied();
+    }
+
+    public void recordDismissalSurface(@NotificationStats.DismissalSurface int surface) {
+        mStats.setDismissalSurface(surface);
+    }
+
+    public void recordSnoozed() {
+        mStats.setSnoozed();
+    }
+
+    public void recordViewedSettings() {
+        mStats.setViewedSettings();
+    }
+
     public LogMaker getLogMaker(long now) {
         if (mLogMaker == null) {
             // initialize fields that only change on update (so a new record)
diff --git a/com/android/server/notification/PriorityExtractor.java b/com/android/server/notification/PriorityExtractor.java
index 5d5d39d..7a287db 100644
--- a/com/android/server/notification/PriorityExtractor.java
+++ b/com/android/server/notification/PriorityExtractor.java
@@ -23,7 +23,7 @@
  * Determines if the given notification can bypass Do Not Disturb.
  */
 public class PriorityExtractor implements NotificationSignalExtractor {
-    private static final String TAG = "ImportantTopicExtractor";
+    private static final String TAG = "PriorityExtractor";
     private static final boolean DBG = false;
 
     private RankingConfig mConfig;
diff --git a/com/android/server/notification/RankingConfig.java b/com/android/server/notification/RankingConfig.java
index b5ef1c6..b9c0d90 100644
--- a/com/android/server/notification/RankingConfig.java
+++ b/com/android/server/notification/RankingConfig.java
@@ -39,7 +39,7 @@
             int uid, boolean includeDeleted);
     void createNotificationChannel(String pkg, int uid, NotificationChannel channel,
             boolean fromTargetApp);
-    void updateNotificationChannel(String pkg, int uid, NotificationChannel channel);
+    void updateNotificationChannel(String pkg, int uid, NotificationChannel channel, boolean fromUser);
     NotificationChannel getNotificationChannel(String pkg, int uid, String channelId, boolean includeDeleted);
     void deleteNotificationChannel(String pkg, int uid, String channelId);
     void permanentlyDeleteNotificationChannel(String pkg, int uid, String channelId);
diff --git a/com/android/server/notification/RankingHelper.java b/com/android/server/notification/RankingHelper.java
index fc24581..d7e9cf3 100644
--- a/com/android/server/notification/RankingHelper.java
+++ b/com/android/server/notification/RankingHelper.java
@@ -36,10 +36,13 @@
 import android.os.UserHandle;
 import android.provider.Settings.Secure;
 import android.service.notification.NotificationListenerService.Ranking;
+import android.service.notification.RankingHelperProto;
+import android.service.notification.RankingHelperProto.RecordProto;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Slog;
 import android.util.SparseBooleanArray;
+import android.util.proto.ProtoOutputStream;
 
 import org.json.JSONArray;
 import org.json.JSONException;
@@ -228,7 +231,11 @@
                                 if (!TextUtils.isEmpty(id) && !TextUtils.isEmpty(channelName)) {
                                     NotificationChannel channel = new NotificationChannel(id,
                                             channelName, channelImportance);
-                                    channel.populateFromXml(parser);
+                                    if (forRestore) {
+                                        channel.populateFromXmlForRestore(parser, mContext);
+                                    } else {
+                                        channel.populateFromXml(parser);
+                                    }
                                     r.channels.put(id, channel);
                                 }
                             }
@@ -391,7 +398,11 @@
                     }
 
                     for (NotificationChannel channel : r.channels.values()) {
-                        if (!forBackup || (forBackup && !channel.isDeleted())) {
+                        if (forBackup) {
+                            if (!channel.isDeleted()) {
+                                channel.writeXmlForBackup(out, mContext);
+                            }
+                        } else {
                             channel.writeXml(out);
                         }
                     }
@@ -613,7 +624,8 @@
     }
 
     @Override
-    public void updateNotificationChannel(String pkg, int uid, NotificationChannel updatedChannel) {
+    public void updateNotificationChannel(String pkg, int uid, NotificationChannel updatedChannel,
+            boolean fromUser) {
         Preconditions.checkNotNull(updatedChannel);
         Preconditions.checkNotNull(updatedChannel.getId());
         Record r = getOrCreateRecord(pkg, uid);
@@ -627,7 +639,11 @@
         if (updatedChannel.getLockscreenVisibility() == Notification.VISIBILITY_PUBLIC) {
             updatedChannel.setLockscreenVisibility(Ranking.VISIBILITY_NO_OVERRIDE);
         }
-        lockFieldsForUpdate(channel, updatedChannel);
+        updatedChannel.unlockFields(updatedChannel.getUserLockedFields());
+        updatedChannel.lockFields(channel.getUserLockedFields());
+        if (fromUser) {
+            lockFieldsForUpdate(channel, updatedChannel);
+        }
         r.channels.put(updatedChannel.getId(), updatedChannel);
 
         if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(updatedChannel.getId())) {
@@ -874,8 +890,6 @@
 
     @VisibleForTesting
     void lockFieldsForUpdate(NotificationChannel original, NotificationChannel update) {
-        update.unlockFields(update.getUserLockedFields());
-        update.lockFields(original.getUserLockedFields());
         if (original.canBypassDnd() != update.canBypassDnd()) {
             update.lockFields(NotificationChannel.USER_LOCKED_PRIORITY);
         }
@@ -912,8 +926,7 @@
                 pw.print("  ");
                 pw.println(mSignalExtractors[i]);
             }
-        }
-        if (filter == null) {
+
             pw.print(prefix);
             pw.println("per-package config:");
         }
@@ -925,6 +938,52 @@
         dumpRecords(pw, prefix, filter, mRestoredWithoutUids);
     }
 
+    public void dump(ProtoOutputStream proto, NotificationManagerService.DumpFilter filter) {
+        final int N = mSignalExtractors.length;
+        for (int i = 0; i < N; i++) {
+            proto.write(RankingHelperProto.NOTIFICATION_SIGNAL_EXTRACTORS,
+                mSignalExtractors[i].getClass().getSimpleName());
+        }
+        synchronized (mRecords) {
+            dumpRecords(proto, RankingHelperProto.RECORDS, filter, mRecords);
+        }
+        dumpRecords(proto, RankingHelperProto.RECORDS_RESTORED_WITHOUT_UID, filter,
+            mRestoredWithoutUids);
+    }
+
+    private static void dumpRecords(ProtoOutputStream proto, long fieldId,
+            NotificationManagerService.DumpFilter filter, ArrayMap<String, Record> records) {
+        final int N = records.size();
+        long fToken;
+        for (int i = 0; i < N; i++) {
+            final Record r = records.valueAt(i);
+            if (filter == null || filter.matches(r.pkg)) {
+                fToken = proto.start(fieldId);
+
+                proto.write(RecordProto.PACKAGE, r.pkg);
+                proto.write(RecordProto.UID, r.uid);
+                proto.write(RecordProto.IMPORTANCE, r.importance);
+                proto.write(RecordProto.PRIORITY, r.priority);
+                proto.write(RecordProto.VISIBILITY, r.visibility);
+                proto.write(RecordProto.SHOW_BADGE, r.showBadge);
+
+                long token;
+                for (NotificationChannel channel : r.channels.values()) {
+                    token = proto.start(RecordProto.CHANNELS);
+                    channel.toProto(proto);
+                    proto.end(token);
+                }
+                for (NotificationChannelGroup group : r.groups.values()) {
+                    token = proto.start(RecordProto.CHANNEL_GROUPS);
+                    group.toProto(proto);
+                    proto.end(token);
+                }
+
+                proto.end(fToken);
+            }
+        }
+    }
+
     private static void dumpRecords(PrintWriter pw, String prefix,
             NotificationManagerService.DumpFilter filter, ArrayMap<String, Record> records) {
         final int N = records.size();
diff --git a/com/android/server/notification/ScheduleCalendar.java b/com/android/server/notification/ScheduleCalendar.java
index 9e8b2e3..40230bd 100644
--- a/com/android/server/notification/ScheduleCalendar.java
+++ b/com/android/server/notification/ScheduleCalendar.java
@@ -42,7 +42,8 @@
 
     public void maybeSetNextAlarm(long now, long nextAlarm) {
         if (mSchedule != null) {
-            if (mSchedule.exitAtAlarm && now > mSchedule.nextAlarm) {
+            if (mSchedule.exitAtAlarm
+                    && (now > mSchedule.nextAlarm || nextAlarm < mSchedule.nextAlarm)) {
                 mSchedule.nextAlarm = nextAlarm;
             }
         }
diff --git a/com/android/server/notification/ZenModeHelper.java b/com/android/server/notification/ZenModeHelper.java
index ffdafc5..9fcc67d 100644
--- a/com/android/server/notification/ZenModeHelper.java
+++ b/com/android/server/notification/ZenModeHelper.java
@@ -567,7 +567,7 @@
                     proto.write(ZenModeProto.ENABLED_ACTIVE_CONDITIONS, rule.toString());
                 }
             }
-            proto.write(ZenModeProto.POLICY, mConfig.toNotificationPolicy().toString());
+            mConfig.toNotificationPolicy().toProto(proto, ZenModeProto.POLICY);
             proto.write(ZenModeProto.SUPPRESSED_EFFECTS, mSuppressedEffects);
         }
     }
diff --git a/com/android/server/oemlock/OemLockService.java b/com/android/server/oemlock/OemLockService.java
index 40c6639..5b3d1ec 100644
--- a/com/android/server/oemlock/OemLockService.java
+++ b/com/android/server/oemlock/OemLockService.java
@@ -31,6 +31,7 @@
 import android.os.UserManagerInternal;
 import android.os.UserManagerInternal.UserRestrictionsListener;
 import android.service.oemlock.IOemLockService;
+import android.service.persistentdata.PersistentDataBlockManager;
 import android.util.Slog;
 
 import com.android.server.LocalServices;
@@ -98,6 +99,7 @@
                         !newRestrictions.getBoolean(UserManager.DISALLOW_FACTORY_RESET);
                 if (!unlockAllowedByAdmin) {
                     mOemLock.setOemUnlockAllowedByDevice(false);
+                    setPersistentDataBlockOemUnlockAllowedBit(false);
                 }
             }
         }
@@ -158,6 +160,7 @@
                 }
 
                 mOemLock.setOemUnlockAllowedByDevice(allowedByUser);
+                setPersistentDataBlockOemUnlockAllowedBit(allowedByUser);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
@@ -202,6 +205,20 @@
         }
     };
 
+    /**
+     * Always synchronize the OemUnlockAllowed bit to the FRP partition, which
+     * is used to erase FRP information on a unlockable device.
+     */
+    private void setPersistentDataBlockOemUnlockAllowedBit(boolean allowed) {
+        final PersistentDataBlockManager pdbm = (PersistentDataBlockManager)
+                mContext.getSystemService(Context.PERSISTENT_DATA_BLOCK_SERVICE);
+        // if mOemLock is PersistentDataBlockLock, then the bit should have already been set
+        if (pdbm != null && !(mOemLock instanceof PersistentDataBlockLock)) {
+            Slog.i(TAG, "Update OEM Unlock bit in pst partition to " + allowed);
+            pdbm.setOemUnlockEnabled(allowed);
+        }
+    }
+
     private boolean isOemUnlockAllowedByAdmin() {
         return !UserManager.get(mContext)
                 .hasUserRestriction(UserManager.DISALLOW_FACTORY_RESET, UserHandle.SYSTEM);
diff --git a/com/android/server/pm/BackgroundDexOptService.java b/com/android/server/pm/BackgroundDexOptService.java
index 415c9a9..6d8cac0 100644
--- a/com/android/server/pm/BackgroundDexOptService.java
+++ b/com/android/server/pm/BackgroundDexOptService.java
@@ -342,8 +342,7 @@
                     DexoptOptions.DEXOPT_BOOT_COMPLETE |
                     (downgrade ? DexoptOptions.DEXOPT_DOWNGRADE : 0);
             if (is_for_primary_dex) {
-                int result = pm.performDexOptWithStatus(new DexoptOptions(pkg,
-                        PackageManagerService.REASON_BACKGROUND_DEXOPT,
+                int result = pm.performDexOptWithStatus(new DexoptOptions(pkg, reason,
                         dexoptFlags));
                 success = result != PackageDexOptimizer.DEX_OPT_FAILED;
                 if (result == PackageDexOptimizer.DEX_OPT_PERFORMED) {
@@ -351,8 +350,7 @@
                 }
             } else {
                 success = pm.performDexOpt(new DexoptOptions(pkg,
-                        PackageManagerService.REASON_BACKGROUND_DEXOPT,
-                        dexoptFlags | DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX));
+                        reason, dexoptFlags | DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX));
             }
             if (success) {
                 // Dexopt succeeded, remove package from the list of failing ones.
diff --git a/com/android/server/pm/BasePermission.java b/com/android/server/pm/BasePermission.java
deleted file mode 100644
index 30fda1e..0000000
--- a/com/android/server/pm/BasePermission.java
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright (C) 2006 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.pm;
-
-import android.content.pm.PackageParser;
-import android.content.pm.PermissionInfo;
-import android.os.UserHandle;
-
-final class BasePermission {
-    final static int TYPE_NORMAL = 0;
-
-    final static int TYPE_BUILTIN = 1;
-
-    final static int TYPE_DYNAMIC = 2;
-
-    final String name;
-
-    String sourcePackage;
-
-    PackageSettingBase packageSetting;
-
-    final int type;
-
-    int protectionLevel;
-
-    PackageParser.Permission perm;
-
-    PermissionInfo pendingInfo;
-
-    /** UID that owns the definition of this permission */
-    int uid;
-
-    /** Additional GIDs given to apps granted this permission */
-    private int[] gids;
-
-    /**
-     * Flag indicating that {@link #gids} should be adjusted based on the
-     * {@link UserHandle} the granted app is running as.
-     */
-    private boolean perUser;
-
-    BasePermission(String _name, String _sourcePackage, int _type) {
-        name = _name;
-        sourcePackage = _sourcePackage;
-        type = _type;
-        // Default to most conservative protection level.
-        protectionLevel = PermissionInfo.PROTECTION_SIGNATURE;
-    }
-
-    @Override
-    public String toString() {
-        return "BasePermission{" + Integer.toHexString(System.identityHashCode(this)) + " " + name
-                + "}";
-    }
-
-    public void setGids(int[] gids, boolean perUser) {
-        this.gids = gids;
-        this.perUser = perUser;
-    }
-
-    public int[] computeGids(int userId) {
-        if (perUser) {
-            final int[] userGids = new int[gids.length];
-            for (int i = 0; i < gids.length; i++) {
-                userGids[i] = UserHandle.getUid(userId, gids[i]);
-            }
-            return userGids;
-        } else {
-            return gids;
-        }
-    }
-
-    public boolean isRuntime() {
-        return (protectionLevel & PermissionInfo.PROTECTION_MASK_BASE)
-                == PermissionInfo.PROTECTION_DANGEROUS;
-    }
-
-    public boolean isDevelopment() {
-        return (protectionLevel & PermissionInfo.PROTECTION_MASK_BASE)
-                == PermissionInfo.PROTECTION_SIGNATURE
-                && (protectionLevel & PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) != 0;
-    }
-
-    public boolean isInstant() {
-        return (protectionLevel & PermissionInfo.PROTECTION_FLAG_INSTANT) != 0;
-    }
-
-    public boolean isRuntimeOnly() {
-        return (protectionLevel & PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY) != 0;
-    }
-}
diff --git a/com/android/server/pm/DefaultPermissionGrantPolicy.java b/com/android/server/pm/DefaultPermissionGrantPolicy.java
deleted file mode 100644
index a3811ba..0000000
--- a/com/android/server/pm/DefaultPermissionGrantPolicy.java
+++ /dev/null
@@ -1,1244 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.pm;
-
-import android.Manifest;
-import android.annotation.NonNull;
-import android.app.ActivityManager;
-import android.app.DownloadManager;
-import android.app.admin.DevicePolicyManager;
-import android.companion.CompanionDeviceManager;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManagerInternal.PackagesProvider;
-import android.content.pm.PackageManagerInternal.SyncAdapterPackagesProvider;
-import android.content.pm.PackageParser;
-import android.content.pm.ProviderInfo;
-import android.content.pm.ResolveInfo;
-import android.media.RingtoneManager;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Environment;
-import android.os.Handler;
-import android.os.Message;
-import android.os.UserHandle;
-import android.os.storage.StorageManager;
-import android.print.PrintManager;
-import android.provider.CalendarContract;
-import android.provider.ContactsContract;
-import android.provider.MediaStore;
-import android.provider.Telephony.Sms.Intents;
-import android.telephony.TelephonyManager;
-import android.security.Credentials;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-import android.util.Log;
-import android.util.Slog;
-import android.util.Xml;
-import com.android.internal.util.XmlUtils;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.BufferedInputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import static android.os.Process.FIRST_APPLICATION_UID;
-
-/**
- * This class is the policy for granting runtime permissions to
- * platform components and default handlers in the system such
- * that the device is usable out-of-the-box. For example, the
- * shell UID is a part of the system and the Phone app should
- * have phone related permission by default.
- */
-final class DefaultPermissionGrantPolicy {
-    private static final String TAG = "DefaultPermGrantPolicy"; // must be <= 23 chars
-    private static final boolean DEBUG = false;
-
-    private static final int DEFAULT_FLAGS =
-            PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
-                    | PackageManager.MATCH_UNINSTALLED_PACKAGES;
-
-    private static final String AUDIO_MIME_TYPE = "audio/mpeg";
-
-    private static final String TAG_EXCEPTIONS = "exceptions";
-    private static final String TAG_EXCEPTION = "exception";
-    private static final String TAG_PERMISSION = "permission";
-    private static final String ATTR_PACKAGE = "package";
-    private static final String ATTR_NAME = "name";
-    private static final String ATTR_FIXED = "fixed";
-
-    private static final Set<String> PHONE_PERMISSIONS = new ArraySet<>();
-    static {
-        PHONE_PERMISSIONS.add(Manifest.permission.READ_PHONE_STATE);
-        PHONE_PERMISSIONS.add(Manifest.permission.CALL_PHONE);
-        PHONE_PERMISSIONS.add(Manifest.permission.READ_CALL_LOG);
-        PHONE_PERMISSIONS.add(Manifest.permission.WRITE_CALL_LOG);
-        PHONE_PERMISSIONS.add(Manifest.permission.ADD_VOICEMAIL);
-        PHONE_PERMISSIONS.add(Manifest.permission.USE_SIP);
-        PHONE_PERMISSIONS.add(Manifest.permission.PROCESS_OUTGOING_CALLS);
-    }
-
-    private static final Set<String> CONTACTS_PERMISSIONS = new ArraySet<>();
-    static {
-        CONTACTS_PERMISSIONS.add(Manifest.permission.READ_CONTACTS);
-        CONTACTS_PERMISSIONS.add(Manifest.permission.WRITE_CONTACTS);
-        CONTACTS_PERMISSIONS.add(Manifest.permission.GET_ACCOUNTS);
-    }
-
-    private static final Set<String> LOCATION_PERMISSIONS = new ArraySet<>();
-    static {
-        LOCATION_PERMISSIONS.add(Manifest.permission.ACCESS_FINE_LOCATION);
-        LOCATION_PERMISSIONS.add(Manifest.permission.ACCESS_COARSE_LOCATION);
-    }
-
-    private static final Set<String> CALENDAR_PERMISSIONS = new ArraySet<>();
-    static {
-        CALENDAR_PERMISSIONS.add(Manifest.permission.READ_CALENDAR);
-        CALENDAR_PERMISSIONS.add(Manifest.permission.WRITE_CALENDAR);
-    }
-
-    private static final Set<String> SMS_PERMISSIONS = new ArraySet<>();
-    static {
-        SMS_PERMISSIONS.add(Manifest.permission.SEND_SMS);
-        SMS_PERMISSIONS.add(Manifest.permission.RECEIVE_SMS);
-        SMS_PERMISSIONS.add(Manifest.permission.READ_SMS);
-        SMS_PERMISSIONS.add(Manifest.permission.RECEIVE_WAP_PUSH);
-        SMS_PERMISSIONS.add(Manifest.permission.RECEIVE_MMS);
-        SMS_PERMISSIONS.add(Manifest.permission.READ_CELL_BROADCASTS);
-    }
-
-    private static final Set<String> MICROPHONE_PERMISSIONS = new ArraySet<>();
-    static {
-        MICROPHONE_PERMISSIONS.add(Manifest.permission.RECORD_AUDIO);
-    }
-
-    private static final Set<String> CAMERA_PERMISSIONS = new ArraySet<>();
-    static {
-        CAMERA_PERMISSIONS.add(Manifest.permission.CAMERA);
-    }
-
-    private static final Set<String> SENSORS_PERMISSIONS = new ArraySet<>();
-    static {
-        SENSORS_PERMISSIONS.add(Manifest.permission.BODY_SENSORS);
-    }
-
-    private static final Set<String> STORAGE_PERMISSIONS = new ArraySet<>();
-    static {
-        STORAGE_PERMISSIONS.add(Manifest.permission.READ_EXTERNAL_STORAGE);
-        STORAGE_PERMISSIONS.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
-    }
-
-    private static final int MSG_READ_DEFAULT_PERMISSION_EXCEPTIONS = 1;
-
-    private static final String ACTION_TRACK = "com.android.fitness.TRACK";
-
-    private final PackageManagerService mService;
-    private final Handler mHandler;
-
-    private PackagesProvider mLocationPackagesProvider;
-    private PackagesProvider mVoiceInteractionPackagesProvider;
-    private PackagesProvider mSmsAppPackagesProvider;
-    private PackagesProvider mDialerAppPackagesProvider;
-    private PackagesProvider mSimCallManagerPackagesProvider;
-    private SyncAdapterPackagesProvider mSyncAdapterPackagesProvider;
-
-    private ArrayMap<String, List<DefaultPermissionGrant>> mGrantExceptions;
-
-    public DefaultPermissionGrantPolicy(PackageManagerService service) {
-        mService = service;
-        mHandler = new Handler(mService.mHandlerThread.getLooper()) {
-            @Override
-            public void handleMessage(Message msg) {
-                if (msg.what == MSG_READ_DEFAULT_PERMISSION_EXCEPTIONS) {
-                    synchronized (mService.mPackages) {
-                        if (mGrantExceptions == null) {
-                            mGrantExceptions = readDefaultPermissionExceptionsLPw();
-                        }
-                    }
-                }
-            }
-        };
-    }
-
-    public void setLocationPackagesProviderLPw(PackagesProvider provider) {
-        mLocationPackagesProvider = provider;
-    }
-
-    public void setVoiceInteractionPackagesProviderLPw(PackagesProvider provider) {
-        mVoiceInteractionPackagesProvider = provider;
-    }
-
-    public void setSmsAppPackagesProviderLPw(PackagesProvider provider) {
-        mSmsAppPackagesProvider = provider;
-    }
-
-    public void setDialerAppPackagesProviderLPw(PackagesProvider provider) {
-        mDialerAppPackagesProvider = provider;
-    }
-
-    public void setSimCallManagerPackagesProviderLPw(PackagesProvider provider) {
-        mSimCallManagerPackagesProvider = provider;
-    }
-
-    public void setSyncAdapterPackagesProviderLPw(SyncAdapterPackagesProvider provider) {
-        mSyncAdapterPackagesProvider = provider;
-    }
-
-    public void grantDefaultPermissions(int userId) {
-        if (mService.hasSystemFeature(PackageManager.FEATURE_EMBEDDED, 0)) {
-            grantAllRuntimePermissions(userId);
-        } else {
-            grantPermissionsToSysComponentsAndPrivApps(userId);
-            grantDefaultSystemHandlerPermissions(userId);
-            grantDefaultPermissionExceptions(userId);
-        }
-    }
-
-    private void grantRuntimePermissionsForPackageLocked(int userId, PackageParser.Package pkg) {
-        Set<String> permissions = new ArraySet<>();
-        for (String permission :  pkg.requestedPermissions) {
-            BasePermission bp = mService.mSettings.mPermissions.get(permission);
-            if (bp != null && bp.isRuntime()) {
-                permissions.add(permission);
-            }
-        }
-        if (!permissions.isEmpty()) {
-            grantRuntimePermissionsLPw(pkg, permissions, true, userId);
-        }
-    }
-
-    private void grantAllRuntimePermissions(int userId) {
-        Log.i(TAG, "Granting all runtime permissions for user " + userId);
-        synchronized (mService.mPackages) {
-            for (PackageParser.Package pkg : mService.mPackages.values()) {
-                grantRuntimePermissionsForPackageLocked(userId, pkg);
-            }
-        }
-    }
-
-    public void scheduleReadDefaultPermissionExceptions() {
-        mHandler.sendEmptyMessage(MSG_READ_DEFAULT_PERMISSION_EXCEPTIONS);
-    }
-
-    private void grantPermissionsToSysComponentsAndPrivApps(int userId) {
-        Log.i(TAG, "Granting permissions to platform components for user " + userId);
-
-        synchronized (mService.mPackages) {
-            for (PackageParser.Package pkg : mService.mPackages.values()) {
-                if (!isSysComponentOrPersistentPlatformSignedPrivAppLPr(pkg)
-                        || !doesPackageSupportRuntimePermissions(pkg)
-                        || pkg.requestedPermissions.isEmpty()) {
-                    continue;
-                }
-                grantRuntimePermissionsForPackageLocked(userId, pkg);
-            }
-        }
-    }
-
-    private void grantDefaultSystemHandlerPermissions(int userId) {
-        Log.i(TAG, "Granting permissions to default platform handlers for user " + userId);
-
-        final PackagesProvider locationPackagesProvider;
-        final PackagesProvider voiceInteractionPackagesProvider;
-        final PackagesProvider smsAppPackagesProvider;
-        final PackagesProvider dialerAppPackagesProvider;
-        final PackagesProvider simCallManagerPackagesProvider;
-        final SyncAdapterPackagesProvider syncAdapterPackagesProvider;
-
-        synchronized (mService.mPackages) {
-            locationPackagesProvider = mLocationPackagesProvider;
-            voiceInteractionPackagesProvider = mVoiceInteractionPackagesProvider;
-            smsAppPackagesProvider = mSmsAppPackagesProvider;
-            dialerAppPackagesProvider = mDialerAppPackagesProvider;
-            simCallManagerPackagesProvider = mSimCallManagerPackagesProvider;
-            syncAdapterPackagesProvider = mSyncAdapterPackagesProvider;
-        }
-
-        String[] voiceInteractPackageNames = (voiceInteractionPackagesProvider != null)
-                ? voiceInteractionPackagesProvider.getPackages(userId) : null;
-        String[] locationPackageNames = (locationPackagesProvider != null)
-                ? locationPackagesProvider.getPackages(userId) : null;
-        String[] smsAppPackageNames = (smsAppPackagesProvider != null)
-                ? smsAppPackagesProvider.getPackages(userId) : null;
-        String[] dialerAppPackageNames = (dialerAppPackagesProvider != null)
-                ? dialerAppPackagesProvider.getPackages(userId) : null;
-        String[] simCallManagerPackageNames = (simCallManagerPackagesProvider != null)
-                ? simCallManagerPackagesProvider.getPackages(userId) : null;
-        String[] contactsSyncAdapterPackages = (syncAdapterPackagesProvider != null) ?
-                syncAdapterPackagesProvider.getPackages(ContactsContract.AUTHORITY, userId) : null;
-        String[] calendarSyncAdapterPackages = (syncAdapterPackagesProvider != null) ?
-                syncAdapterPackagesProvider.getPackages(CalendarContract.AUTHORITY, userId) : null;
-
-        synchronized (mService.mPackages) {
-            // Installer
-            PackageParser.Package installerPackage = getSystemPackageLPr(
-                    mService.mRequiredInstallerPackage);
-            if (installerPackage != null
-                    && doesPackageSupportRuntimePermissions(installerPackage)) {
-                grantRuntimePermissionsLPw(installerPackage, STORAGE_PERMISSIONS, true, userId);
-            }
-
-            // Verifier
-            PackageParser.Package verifierPackage = getSystemPackageLPr(
-                    mService.mRequiredVerifierPackage);
-            if (verifierPackage != null
-                    && doesPackageSupportRuntimePermissions(verifierPackage)) {
-                grantRuntimePermissionsLPw(verifierPackage, STORAGE_PERMISSIONS, true, userId);
-                grantRuntimePermissionsLPw(verifierPackage, PHONE_PERMISSIONS, false, userId);
-                grantRuntimePermissionsLPw(verifierPackage, SMS_PERMISSIONS, false, userId);
-            }
-
-            // SetupWizard
-            PackageParser.Package setupPackage = getSystemPackageLPr(
-                    mService.mSetupWizardPackage);
-            if (setupPackage != null
-                    && doesPackageSupportRuntimePermissions(setupPackage)) {
-                grantRuntimePermissionsLPw(setupPackage, PHONE_PERMISSIONS, userId);
-                grantRuntimePermissionsLPw(setupPackage, CONTACTS_PERMISSIONS, userId);
-                grantRuntimePermissionsLPw(setupPackage, LOCATION_PERMISSIONS, userId);
-                grantRuntimePermissionsLPw(setupPackage, CAMERA_PERMISSIONS, userId);
-            }
-
-            // Camera
-            Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
-            PackageParser.Package cameraPackage = getDefaultSystemHandlerActivityPackageLPr(
-                    cameraIntent, userId);
-            if (cameraPackage != null
-                    && doesPackageSupportRuntimePermissions(cameraPackage)) {
-                grantRuntimePermissionsLPw(cameraPackage, CAMERA_PERMISSIONS, userId);
-                grantRuntimePermissionsLPw(cameraPackage, MICROPHONE_PERMISSIONS, userId);
-                grantRuntimePermissionsLPw(cameraPackage, STORAGE_PERMISSIONS, userId);
-            }
-
-            // Media provider
-            PackageParser.Package mediaStorePackage = getDefaultProviderAuthorityPackageLPr(
-                    MediaStore.AUTHORITY, userId);
-            if (mediaStorePackage != null) {
-                grantRuntimePermissionsLPw(mediaStorePackage, STORAGE_PERMISSIONS, true, userId);
-            }
-
-            // Downloads provider
-            PackageParser.Package downloadsPackage = getDefaultProviderAuthorityPackageLPr(
-                    "downloads", userId);
-            if (downloadsPackage != null) {
-                grantRuntimePermissionsLPw(downloadsPackage, STORAGE_PERMISSIONS, true, userId);
-            }
-
-            // Downloads UI
-            Intent downloadsUiIntent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
-            PackageParser.Package downloadsUiPackage = getDefaultSystemHandlerActivityPackageLPr(
-                    downloadsUiIntent, userId);
-            if (downloadsUiPackage != null
-                    && doesPackageSupportRuntimePermissions(downloadsUiPackage)) {
-                grantRuntimePermissionsLPw(downloadsUiPackage, STORAGE_PERMISSIONS, true, userId);
-            }
-
-            // Storage provider
-            PackageParser.Package storagePackage = getDefaultProviderAuthorityPackageLPr(
-                    "com.android.externalstorage.documents", userId);
-            if (storagePackage != null) {
-                grantRuntimePermissionsLPw(storagePackage, STORAGE_PERMISSIONS, true, userId);
-            }
-
-            // CertInstaller
-            Intent certInstallerIntent = new Intent(Credentials.INSTALL_ACTION);
-            PackageParser.Package certInstallerPackage = getDefaultSystemHandlerActivityPackageLPr(
-                    certInstallerIntent, userId);
-            if (certInstallerPackage != null
-                    && doesPackageSupportRuntimePermissions(certInstallerPackage)) {
-                grantRuntimePermissionsLPw(certInstallerPackage, STORAGE_PERMISSIONS, true, userId);
-            }
-
-            // Dialer
-            if (dialerAppPackageNames == null) {
-                Intent dialerIntent = new Intent(Intent.ACTION_DIAL);
-                PackageParser.Package dialerPackage = getDefaultSystemHandlerActivityPackageLPr(
-                        dialerIntent, userId);
-                if (dialerPackage != null) {
-                    grantDefaultPermissionsToDefaultSystemDialerAppLPr(dialerPackage, userId);
-                }
-            } else {
-                for (String dialerAppPackageName : dialerAppPackageNames) {
-                    PackageParser.Package dialerPackage = getSystemPackageLPr(dialerAppPackageName);
-                    if (dialerPackage != null) {
-                        grantDefaultPermissionsToDefaultSystemDialerAppLPr(dialerPackage, userId);
-                    }
-                }
-            }
-
-            // Sim call manager
-            if (simCallManagerPackageNames != null) {
-                for (String simCallManagerPackageName : simCallManagerPackageNames) {
-                    PackageParser.Package simCallManagerPackage =
-                            getSystemPackageLPr(simCallManagerPackageName);
-                    if (simCallManagerPackage != null) {
-                        grantDefaultPermissionsToDefaultSimCallManagerLPr(simCallManagerPackage,
-                                userId);
-                    }
-                }
-            }
-
-            // SMS
-            if (smsAppPackageNames == null) {
-                Intent smsIntent = new Intent(Intent.ACTION_MAIN);
-                smsIntent.addCategory(Intent.CATEGORY_APP_MESSAGING);
-                PackageParser.Package smsPackage = getDefaultSystemHandlerActivityPackageLPr(
-                        smsIntent, userId);
-                if (smsPackage != null) {
-                   grantDefaultPermissionsToDefaultSystemSmsAppLPr(smsPackage, userId);
-                }
-            } else {
-                for (String smsPackageName : smsAppPackageNames) {
-                    PackageParser.Package smsPackage = getSystemPackageLPr(smsPackageName);
-                    if (smsPackage != null) {
-                        grantDefaultPermissionsToDefaultSystemSmsAppLPr(smsPackage, userId);
-                    }
-                }
-            }
-
-            // Cell Broadcast Receiver
-            Intent cbrIntent = new Intent(Intents.SMS_CB_RECEIVED_ACTION);
-            PackageParser.Package cbrPackage =
-                    getDefaultSystemHandlerActivityPackageLPr(cbrIntent, userId);
-            if (cbrPackage != null && doesPackageSupportRuntimePermissions(cbrPackage)) {
-                grantRuntimePermissionsLPw(cbrPackage, SMS_PERMISSIONS, userId);
-            }
-
-            // Carrier Provisioning Service
-            Intent carrierProvIntent = new Intent(Intents.SMS_CARRIER_PROVISION_ACTION);
-            PackageParser.Package carrierProvPackage =
-                    getDefaultSystemHandlerServicePackageLPr(carrierProvIntent, userId);
-            if (carrierProvPackage != null && doesPackageSupportRuntimePermissions(carrierProvPackage)) {
-                grantRuntimePermissionsLPw(carrierProvPackage, SMS_PERMISSIONS, false, userId);
-            }
-
-            // Calendar
-            Intent calendarIntent = new Intent(Intent.ACTION_MAIN);
-            calendarIntent.addCategory(Intent.CATEGORY_APP_CALENDAR);
-            PackageParser.Package calendarPackage = getDefaultSystemHandlerActivityPackageLPr(
-                    calendarIntent, userId);
-            if (calendarPackage != null
-                    && doesPackageSupportRuntimePermissions(calendarPackage)) {
-                grantRuntimePermissionsLPw(calendarPackage, CALENDAR_PERMISSIONS, userId);
-                grantRuntimePermissionsLPw(calendarPackage, CONTACTS_PERMISSIONS, userId);
-            }
-
-            // Calendar provider
-            PackageParser.Package calendarProviderPackage = getDefaultProviderAuthorityPackageLPr(
-                    CalendarContract.AUTHORITY, userId);
-            if (calendarProviderPackage != null) {
-                grantRuntimePermissionsLPw(calendarProviderPackage, CONTACTS_PERMISSIONS, userId);
-                grantRuntimePermissionsLPw(calendarProviderPackage, CALENDAR_PERMISSIONS,
-                        true, userId);
-                grantRuntimePermissionsLPw(calendarProviderPackage, STORAGE_PERMISSIONS, userId);
-            }
-
-            // Calendar provider sync adapters
-            List<PackageParser.Package> calendarSyncAdapters = getHeadlessSyncAdapterPackagesLPr(
-                    calendarSyncAdapterPackages, userId);
-            final int calendarSyncAdapterCount = calendarSyncAdapters.size();
-            for (int i = 0; i < calendarSyncAdapterCount; i++) {
-                PackageParser.Package calendarSyncAdapter = calendarSyncAdapters.get(i);
-                if (doesPackageSupportRuntimePermissions(calendarSyncAdapter)) {
-                    grantRuntimePermissionsLPw(calendarSyncAdapter, CALENDAR_PERMISSIONS, userId);
-                }
-            }
-
-            // Contacts
-            Intent contactsIntent = new Intent(Intent.ACTION_MAIN);
-            contactsIntent.addCategory(Intent.CATEGORY_APP_CONTACTS);
-            PackageParser.Package contactsPackage = getDefaultSystemHandlerActivityPackageLPr(
-                    contactsIntent, userId);
-            if (contactsPackage != null
-                    && doesPackageSupportRuntimePermissions(contactsPackage)) {
-                grantRuntimePermissionsLPw(contactsPackage, CONTACTS_PERMISSIONS, userId);
-                grantRuntimePermissionsLPw(contactsPackage, PHONE_PERMISSIONS, userId);
-            }
-
-            // Contacts provider sync adapters
-            List<PackageParser.Package> contactsSyncAdapters = getHeadlessSyncAdapterPackagesLPr(
-                    contactsSyncAdapterPackages, userId);
-            final int contactsSyncAdapterCount = contactsSyncAdapters.size();
-            for (int i = 0; i < contactsSyncAdapterCount; i++) {
-                PackageParser.Package contactsSyncAdapter = contactsSyncAdapters.get(i);
-                if (doesPackageSupportRuntimePermissions(contactsSyncAdapter)) {
-                    grantRuntimePermissionsLPw(contactsSyncAdapter, CONTACTS_PERMISSIONS, userId);
-                }
-            }
-
-            // Contacts provider
-            PackageParser.Package contactsProviderPackage = getDefaultProviderAuthorityPackageLPr(
-                    ContactsContract.AUTHORITY, userId);
-            if (contactsProviderPackage != null) {
-                grantRuntimePermissionsLPw(contactsProviderPackage, CONTACTS_PERMISSIONS,
-                        true, userId);
-                grantRuntimePermissionsLPw(contactsProviderPackage, PHONE_PERMISSIONS,
-                        true, userId);
-                grantRuntimePermissionsLPw(contactsProviderPackage, STORAGE_PERMISSIONS, userId);
-            }
-
-            // Device provisioning
-            Intent deviceProvisionIntent = new Intent(
-                    DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE);
-            PackageParser.Package deviceProvisionPackage =
-                    getDefaultSystemHandlerActivityPackageLPr(deviceProvisionIntent, userId);
-            if (deviceProvisionPackage != null
-                    && doesPackageSupportRuntimePermissions(deviceProvisionPackage)) {
-                grantRuntimePermissionsLPw(deviceProvisionPackage, CONTACTS_PERMISSIONS, userId);
-            }
-
-            // Maps
-            Intent mapsIntent = new Intent(Intent.ACTION_MAIN);
-            mapsIntent.addCategory(Intent.CATEGORY_APP_MAPS);
-            PackageParser.Package mapsPackage = getDefaultSystemHandlerActivityPackageLPr(
-                    mapsIntent, userId);
-            if (mapsPackage != null
-                    && doesPackageSupportRuntimePermissions(mapsPackage)) {
-                grantRuntimePermissionsLPw(mapsPackage, LOCATION_PERMISSIONS, userId);
-            }
-
-            // Gallery
-            Intent galleryIntent = new Intent(Intent.ACTION_MAIN);
-            galleryIntent.addCategory(Intent.CATEGORY_APP_GALLERY);
-            PackageParser.Package galleryPackage = getDefaultSystemHandlerActivityPackageLPr(
-                    galleryIntent, userId);
-            if (galleryPackage != null
-                    && doesPackageSupportRuntimePermissions(galleryPackage)) {
-                grantRuntimePermissionsLPw(galleryPackage, STORAGE_PERMISSIONS, userId);
-            }
-
-            // Email
-            Intent emailIntent = new Intent(Intent.ACTION_MAIN);
-            emailIntent.addCategory(Intent.CATEGORY_APP_EMAIL);
-            PackageParser.Package emailPackage = getDefaultSystemHandlerActivityPackageLPr(
-                    emailIntent, userId);
-            if (emailPackage != null
-                    && doesPackageSupportRuntimePermissions(emailPackage)) {
-                grantRuntimePermissionsLPw(emailPackage, CONTACTS_PERMISSIONS, userId);
-                grantRuntimePermissionsLPw(emailPackage, CALENDAR_PERMISSIONS, userId);
-            }
-
-            // Browser
-            PackageParser.Package browserPackage = null;
-            String defaultBrowserPackage = mService.getDefaultBrowserPackageName(userId);
-            if (defaultBrowserPackage != null) {
-                browserPackage = getPackageLPr(defaultBrowserPackage);
-            }
-            if (browserPackage == null) {
-                Intent browserIntent = new Intent(Intent.ACTION_MAIN);
-                browserIntent.addCategory(Intent.CATEGORY_APP_BROWSER);
-                browserPackage = getDefaultSystemHandlerActivityPackageLPr(
-                        browserIntent, userId);
-            }
-            if (browserPackage != null
-                    && doesPackageSupportRuntimePermissions(browserPackage)) {
-                grantRuntimePermissionsLPw(browserPackage, LOCATION_PERMISSIONS, userId);
-            }
-
-            // Voice interaction
-            if (voiceInteractPackageNames != null) {
-                for (String voiceInteractPackageName : voiceInteractPackageNames) {
-                    PackageParser.Package voiceInteractPackage = getSystemPackageLPr(
-                            voiceInteractPackageName);
-                    if (voiceInteractPackage != null
-                            && doesPackageSupportRuntimePermissions(voiceInteractPackage)) {
-                        grantRuntimePermissionsLPw(voiceInteractPackage,
-                                CONTACTS_PERMISSIONS, userId);
-                        grantRuntimePermissionsLPw(voiceInteractPackage,
-                                CALENDAR_PERMISSIONS, userId);
-                        grantRuntimePermissionsLPw(voiceInteractPackage,
-                                MICROPHONE_PERMISSIONS, userId);
-                        grantRuntimePermissionsLPw(voiceInteractPackage,
-                                PHONE_PERMISSIONS, userId);
-                        grantRuntimePermissionsLPw(voiceInteractPackage,
-                                SMS_PERMISSIONS, userId);
-                        grantRuntimePermissionsLPw(voiceInteractPackage,
-                                LOCATION_PERMISSIONS, userId);
-                    }
-                }
-            }
-
-            if (ActivityManager.isLowRamDeviceStatic()) {
-                // Allow voice search on low-ram devices
-                Intent globalSearchIntent = new Intent("android.search.action.GLOBAL_SEARCH");
-                PackageParser.Package globalSearchPickerPackage =
-                    getDefaultSystemHandlerActivityPackageLPr(globalSearchIntent, userId);
-
-                if (globalSearchPickerPackage != null
-                        && doesPackageSupportRuntimePermissions(globalSearchPickerPackage)) {
-                    grantRuntimePermissionsLPw(globalSearchPickerPackage,
-                        MICROPHONE_PERMISSIONS, true, userId);
-                    grantRuntimePermissionsLPw(globalSearchPickerPackage,
-                        LOCATION_PERMISSIONS, true, userId);
-                }
-            }
-
-            // Voice recognition
-            Intent voiceRecoIntent = new Intent("android.speech.RecognitionService");
-            voiceRecoIntent.addCategory(Intent.CATEGORY_DEFAULT);
-            PackageParser.Package voiceRecoPackage = getDefaultSystemHandlerServicePackageLPr(
-                    voiceRecoIntent, userId);
-            if (voiceRecoPackage != null
-                    && doesPackageSupportRuntimePermissions(voiceRecoPackage)) {
-                grantRuntimePermissionsLPw(voiceRecoPackage, MICROPHONE_PERMISSIONS, userId);
-            }
-
-            // Location
-            if (locationPackageNames != null) {
-                for (String packageName : locationPackageNames) {
-                    PackageParser.Package locationPackage = getSystemPackageLPr(packageName);
-                    if (locationPackage != null
-                            && doesPackageSupportRuntimePermissions(locationPackage)) {
-                        grantRuntimePermissionsLPw(locationPackage, CONTACTS_PERMISSIONS, userId);
-                        grantRuntimePermissionsLPw(locationPackage, CALENDAR_PERMISSIONS, userId);
-                        grantRuntimePermissionsLPw(locationPackage, MICROPHONE_PERMISSIONS, userId);
-                        grantRuntimePermissionsLPw(locationPackage, PHONE_PERMISSIONS, userId);
-                        grantRuntimePermissionsLPw(locationPackage, SMS_PERMISSIONS, userId);
-                        grantRuntimePermissionsLPw(locationPackage, LOCATION_PERMISSIONS,
-                                true, userId);
-                        grantRuntimePermissionsLPw(locationPackage, CAMERA_PERMISSIONS, userId);
-                        grantRuntimePermissionsLPw(locationPackage, SENSORS_PERMISSIONS, userId);
-                        grantRuntimePermissionsLPw(locationPackage, STORAGE_PERMISSIONS, userId);
-                    }
-                }
-            }
-
-            // Music
-            Intent musicIntent = new Intent(Intent.ACTION_VIEW);
-            musicIntent.addCategory(Intent.CATEGORY_DEFAULT);
-            musicIntent.setDataAndType(Uri.fromFile(new File("foo.mp3")),
-                    AUDIO_MIME_TYPE);
-            PackageParser.Package musicPackage = getDefaultSystemHandlerActivityPackageLPr(
-                    musicIntent, userId);
-            if (musicPackage != null
-                    && doesPackageSupportRuntimePermissions(musicPackage)) {
-                grantRuntimePermissionsLPw(musicPackage, STORAGE_PERMISSIONS, userId);
-            }
-
-            // Home
-            Intent homeIntent = new Intent(Intent.ACTION_MAIN);
-            homeIntent.addCategory(Intent.CATEGORY_HOME);
-            homeIntent.addCategory(Intent.CATEGORY_LAUNCHER_APP);
-            PackageParser.Package homePackage = getDefaultSystemHandlerActivityPackageLPr(
-                    homeIntent, userId);
-            if (homePackage != null
-                    && doesPackageSupportRuntimePermissions(homePackage)) {
-                grantRuntimePermissionsLPw(homePackage, LOCATION_PERMISSIONS, false, userId);
-            }
-
-            // Watches
-            if (mService.hasSystemFeature(PackageManager.FEATURE_WATCH, 0)) {
-                // Home application on watches
-                Intent wearHomeIntent = new Intent(Intent.ACTION_MAIN);
-                wearHomeIntent.addCategory(Intent.CATEGORY_HOME_MAIN);
-
-                PackageParser.Package wearHomePackage = getDefaultSystemHandlerActivityPackageLPr(
-                        wearHomeIntent, userId);
-
-                if (wearHomePackage != null
-                        && doesPackageSupportRuntimePermissions(wearHomePackage)) {
-                    grantRuntimePermissionsLPw(wearHomePackage, CONTACTS_PERMISSIONS, false,
-                            userId);
-                    grantRuntimePermissionsLPw(wearHomePackage, PHONE_PERMISSIONS, true, userId);
-                    grantRuntimePermissionsLPw(wearHomePackage, MICROPHONE_PERMISSIONS, false,
-                            userId);
-                    grantRuntimePermissionsLPw(wearHomePackage, LOCATION_PERMISSIONS, false,
-                            userId);
-                }
-
-                // Fitness tracking on watches
-                Intent trackIntent = new Intent(ACTION_TRACK);
-                PackageParser.Package trackPackage = getDefaultSystemHandlerActivityPackageLPr(
-                        trackIntent, userId);
-                if (trackPackage != null
-                        && doesPackageSupportRuntimePermissions(trackPackage)) {
-                    grantRuntimePermissionsLPw(trackPackage, SENSORS_PERMISSIONS, false, userId);
-                    grantRuntimePermissionsLPw(trackPackage, LOCATION_PERMISSIONS, false, userId);
-                }
-            }
-
-            // Print Spooler
-            PackageParser.Package printSpoolerPackage = getSystemPackageLPr(
-                    PrintManager.PRINT_SPOOLER_PACKAGE_NAME);
-            if (printSpoolerPackage != null
-                    && doesPackageSupportRuntimePermissions(printSpoolerPackage)) {
-                grantRuntimePermissionsLPw(printSpoolerPackage, LOCATION_PERMISSIONS, true, userId);
-            }
-
-            // EmergencyInfo
-            Intent emergencyInfoIntent = new Intent(TelephonyManager.ACTION_EMERGENCY_ASSISTANCE);
-            PackageParser.Package emergencyInfoPckg = getDefaultSystemHandlerActivityPackageLPr(
-                    emergencyInfoIntent, userId);
-            if (emergencyInfoPckg != null
-                    && doesPackageSupportRuntimePermissions(emergencyInfoPckg)) {
-                grantRuntimePermissionsLPw(emergencyInfoPckg, CONTACTS_PERMISSIONS, true, userId);
-                grantRuntimePermissionsLPw(emergencyInfoPckg, PHONE_PERMISSIONS, true, userId);
-            }
-
-            // NFC Tag viewer
-            Intent nfcTagIntent = new Intent(Intent.ACTION_VIEW);
-            nfcTagIntent.setType("vnd.android.cursor.item/ndef_msg");
-            PackageParser.Package nfcTagPkg = getDefaultSystemHandlerActivityPackageLPr(
-                    nfcTagIntent, userId);
-            if (nfcTagPkg != null
-                    && doesPackageSupportRuntimePermissions(nfcTagPkg)) {
-                grantRuntimePermissionsLPw(nfcTagPkg, CONTACTS_PERMISSIONS, false, userId);
-                grantRuntimePermissionsLPw(nfcTagPkg, PHONE_PERMISSIONS, false, userId);
-            }
-
-            // Storage Manager
-            Intent storageManagerIntent = new Intent(StorageManager.ACTION_MANAGE_STORAGE);
-            PackageParser.Package storageManagerPckg = getDefaultSystemHandlerActivityPackageLPr(
-                    storageManagerIntent, userId);
-            if (storageManagerPckg != null
-                    && doesPackageSupportRuntimePermissions(storageManagerPckg)) {
-                grantRuntimePermissionsLPw(storageManagerPckg, STORAGE_PERMISSIONS, true, userId);
-            }
-
-            // Companion devices
-            PackageParser.Package companionDeviceDiscoveryPackage = getSystemPackageLPr(
-                    CompanionDeviceManager.COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME);
-            if (companionDeviceDiscoveryPackage != null
-                    && doesPackageSupportRuntimePermissions(companionDeviceDiscoveryPackage)) {
-                grantRuntimePermissionsLPw(companionDeviceDiscoveryPackage,
-                        LOCATION_PERMISSIONS, true, userId);
-            }
-
-            // Ringtone Picker
-            Intent ringtonePickerIntent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER);
-            PackageParser.Package ringtonePickerPackage =
-                    getDefaultSystemHandlerActivityPackageLPr(ringtonePickerIntent, userId);
-            if (ringtonePickerPackage != null
-                    && doesPackageSupportRuntimePermissions(ringtonePickerPackage)) {
-                grantRuntimePermissionsLPw(ringtonePickerPackage,
-                        STORAGE_PERMISSIONS, true, userId);
-            }
-
-            mService.mSettings.onDefaultRuntimePermissionsGrantedLPr(userId);
-        }
-    }
-
-    private void grantDefaultPermissionsToDefaultSystemDialerAppLPr(
-            PackageParser.Package dialerPackage, int userId) {
-        if (doesPackageSupportRuntimePermissions(dialerPackage)) {
-            boolean isPhonePermFixed =
-                    mService.hasSystemFeature(PackageManager.FEATURE_WATCH, 0);
-            grantRuntimePermissionsLPw(
-                    dialerPackage, PHONE_PERMISSIONS, isPhonePermFixed, userId);
-            grantRuntimePermissionsLPw(dialerPackage, CONTACTS_PERMISSIONS, userId);
-            grantRuntimePermissionsLPw(dialerPackage, SMS_PERMISSIONS, userId);
-            grantRuntimePermissionsLPw(dialerPackage, MICROPHONE_PERMISSIONS, userId);
-            grantRuntimePermissionsLPw(dialerPackage, CAMERA_PERMISSIONS, userId);
-        }
-    }
-
-    private void grantDefaultPermissionsToDefaultSystemSmsAppLPr(
-            PackageParser.Package smsPackage, int userId) {
-        if (doesPackageSupportRuntimePermissions(smsPackage)) {
-            grantRuntimePermissionsLPw(smsPackage, PHONE_PERMISSIONS, userId);
-            grantRuntimePermissionsLPw(smsPackage, CONTACTS_PERMISSIONS, userId);
-            grantRuntimePermissionsLPw(smsPackage, SMS_PERMISSIONS, userId);
-            grantRuntimePermissionsLPw(smsPackage, STORAGE_PERMISSIONS, userId);
-            grantRuntimePermissionsLPw(smsPackage, MICROPHONE_PERMISSIONS, userId);
-            grantRuntimePermissionsLPw(smsPackage, CAMERA_PERMISSIONS, userId);
-        }
-    }
-
-    public void grantDefaultPermissionsToDefaultSmsAppLPr(String packageName, int userId) {
-        Log.i(TAG, "Granting permissions to default sms app for user:" + userId);
-        if (packageName == null) {
-            return;
-        }
-        PackageParser.Package smsPackage = getPackageLPr(packageName);
-        if (smsPackage != null && doesPackageSupportRuntimePermissions(smsPackage)) {
-            grantRuntimePermissionsLPw(smsPackage, PHONE_PERMISSIONS, false, true, userId);
-            grantRuntimePermissionsLPw(smsPackage, CONTACTS_PERMISSIONS, false, true, userId);
-            grantRuntimePermissionsLPw(smsPackage, SMS_PERMISSIONS, false, true, userId);
-            grantRuntimePermissionsLPw(smsPackage, STORAGE_PERMISSIONS, false, true, userId);
-            grantRuntimePermissionsLPw(smsPackage, MICROPHONE_PERMISSIONS, false, true, userId);
-            grantRuntimePermissionsLPw(smsPackage, CAMERA_PERMISSIONS, false, true, userId);
-        }
-    }
-
-    public void grantDefaultPermissionsToDefaultDialerAppLPr(String packageName, int userId) {
-        Log.i(TAG, "Granting permissions to default dialer app for user:" + userId);
-        if (packageName == null) {
-            return;
-        }
-        PackageParser.Package dialerPackage = getPackageLPr(packageName);
-        if (dialerPackage != null
-                && doesPackageSupportRuntimePermissions(dialerPackage)) {
-            grantRuntimePermissionsLPw(dialerPackage, PHONE_PERMISSIONS, false, true, userId);
-            grantRuntimePermissionsLPw(dialerPackage, CONTACTS_PERMISSIONS, false, true, userId);
-            grantRuntimePermissionsLPw(dialerPackage, SMS_PERMISSIONS, false, true, userId);
-            grantRuntimePermissionsLPw(dialerPackage, MICROPHONE_PERMISSIONS, false, true, userId);
-            grantRuntimePermissionsLPw(dialerPackage, CAMERA_PERMISSIONS, false, true, userId);
-        }
-    }
-
-    private void grantDefaultPermissionsToDefaultSimCallManagerLPr(
-            PackageParser.Package simCallManagerPackage, int userId) {
-        Log.i(TAG, "Granting permissions to sim call manager for user:" + userId);
-        if (doesPackageSupportRuntimePermissions(simCallManagerPackage)) {
-            grantRuntimePermissionsLPw(simCallManagerPackage, PHONE_PERMISSIONS, userId);
-            grantRuntimePermissionsLPw(simCallManagerPackage, MICROPHONE_PERMISSIONS, userId);
-        }
-    }
-
-    public void grantDefaultPermissionsToDefaultSimCallManagerLPr(String packageName, int userId) {
-        if (packageName == null) {
-            return;
-        }
-        PackageParser.Package simCallManagerPackage = getPackageLPr(packageName);
-        if (simCallManagerPackage != null) {
-            grantDefaultPermissionsToDefaultSimCallManagerLPr(simCallManagerPackage, userId);
-        }
-    }
-
-    public void grantDefaultPermissionsToEnabledCarrierAppsLPr(String[] packageNames, int userId) {
-        Log.i(TAG, "Granting permissions to enabled carrier apps for user:" + userId);
-        if (packageNames == null) {
-            return;
-        }
-        for (String packageName : packageNames) {
-            PackageParser.Package carrierPackage = getSystemPackageLPr(packageName);
-            if (carrierPackage != null
-                    && doesPackageSupportRuntimePermissions(carrierPackage)) {
-                grantRuntimePermissionsLPw(carrierPackage, PHONE_PERMISSIONS, userId);
-                grantRuntimePermissionsLPw(carrierPackage, LOCATION_PERMISSIONS, userId);
-                grantRuntimePermissionsLPw(carrierPackage, SMS_PERMISSIONS, userId);
-            }
-        }
-    }
-
-    public void grantDefaultPermissionsToEnabledImsServicesLPr(String[] packageNames, int userId) {
-        Log.i(TAG, "Granting permissions to enabled ImsServices for user:" + userId);
-        if (packageNames == null) {
-            return;
-        }
-        for (String packageName : packageNames) {
-            PackageParser.Package imsServicePackage = getSystemPackageLPr(packageName);
-            if (imsServicePackage != null
-                    && doesPackageSupportRuntimePermissions(imsServicePackage)) {
-                grantRuntimePermissionsLPw(imsServicePackage, PHONE_PERMISSIONS, userId);
-                grantRuntimePermissionsLPw(imsServicePackage, MICROPHONE_PERMISSIONS, userId);
-                grantRuntimePermissionsLPw(imsServicePackage, LOCATION_PERMISSIONS, userId);
-                grantRuntimePermissionsLPw(imsServicePackage, CAMERA_PERMISSIONS, userId);
-            }
-        }
-    }
-
-    public void grantDefaultPermissionsToDefaultBrowserLPr(String packageName, int userId) {
-        Log.i(TAG, "Granting permissions to default browser for user:" + userId);
-        if (packageName == null) {
-            return;
-        }
-        PackageParser.Package browserPackage = getSystemPackageLPr(packageName);
-        if (browserPackage != null
-                && doesPackageSupportRuntimePermissions(browserPackage)) {
-            grantRuntimePermissionsLPw(browserPackage, LOCATION_PERMISSIONS, false, false, userId);
-        }
-    }
-
-    private PackageParser.Package getDefaultSystemHandlerActivityPackageLPr(
-            Intent intent, int userId) {
-        ResolveInfo handler = mService.resolveIntent(intent,
-                intent.resolveType(mService.mContext.getContentResolver()), DEFAULT_FLAGS, userId);
-        if (handler == null || handler.activityInfo == null) {
-            return null;
-        }
-        ActivityInfo activityInfo = handler.activityInfo;
-        if (activityInfo.packageName.equals(mService.mResolveActivity.packageName)
-                && activityInfo.name.equals(mService.mResolveActivity.name)) {
-            return null;
-        }
-        return getSystemPackageLPr(handler.activityInfo.packageName);
-    }
-
-    private PackageParser.Package getDefaultSystemHandlerServicePackageLPr(
-            Intent intent, int userId) {
-        List<ResolveInfo> handlers = mService.queryIntentServices(intent,
-                intent.resolveType(mService.mContext.getContentResolver()), DEFAULT_FLAGS, userId)
-                .getList();
-        if (handlers == null) {
-            return null;
-        }
-        final int handlerCount = handlers.size();
-        for (int i = 0; i < handlerCount; i++) {
-            ResolveInfo handler = handlers.get(i);
-            PackageParser.Package handlerPackage = getSystemPackageLPr(
-                    handler.serviceInfo.packageName);
-            if (handlerPackage != null) {
-                return handlerPackage;
-            }
-        }
-        return null;
-    }
-
-    private List<PackageParser.Package> getHeadlessSyncAdapterPackagesLPr(
-            String[] syncAdapterPackageNames, int userId) {
-        List<PackageParser.Package> syncAdapterPackages = new ArrayList<>();
-
-        Intent homeIntent = new Intent(Intent.ACTION_MAIN);
-        homeIntent.addCategory(Intent.CATEGORY_LAUNCHER);
-
-        for (String syncAdapterPackageName : syncAdapterPackageNames) {
-            homeIntent.setPackage(syncAdapterPackageName);
-
-            ResolveInfo homeActivity = mService.resolveIntent(homeIntent,
-                    homeIntent.resolveType(mService.mContext.getContentResolver()), DEFAULT_FLAGS,
-                    userId);
-            if (homeActivity != null) {
-                continue;
-            }
-
-            PackageParser.Package syncAdapterPackage = getSystemPackageLPr(syncAdapterPackageName);
-            if (syncAdapterPackage != null) {
-                syncAdapterPackages.add(syncAdapterPackage);
-            }
-        }
-
-        return syncAdapterPackages;
-    }
-
-    private PackageParser.Package getDefaultProviderAuthorityPackageLPr(
-            String authority, int userId) {
-        ProviderInfo provider = mService.resolveContentProvider(authority, DEFAULT_FLAGS, userId);
-        if (provider != null) {
-            return getSystemPackageLPr(provider.packageName);
-        }
-        return null;
-    }
-
-    private PackageParser.Package getPackageLPr(String packageName) {
-        return mService.mPackages.get(packageName);
-    }
-
-    private PackageParser.Package getSystemPackageLPr(String packageName) {
-        PackageParser.Package pkg = getPackageLPr(packageName);
-        if (pkg != null && pkg.isSystemApp()) {
-            return !isSysComponentOrPersistentPlatformSignedPrivAppLPr(pkg) ? pkg : null;
-        }
-        return null;
-    }
-
-    private void grantRuntimePermissionsLPw(PackageParser.Package pkg, Set<String> permissions,
-            int userId) {
-        grantRuntimePermissionsLPw(pkg, permissions, false, false, userId);
-    }
-
-    private void grantRuntimePermissionsLPw(PackageParser.Package pkg, Set<String> permissions,
-            boolean systemFixed, int userId) {
-        grantRuntimePermissionsLPw(pkg, permissions, systemFixed, false, userId);
-    }
-
-    private void grantRuntimePermissionsLPw(PackageParser.Package pkg, Set<String> permissions,
-            boolean systemFixed, boolean isDefaultPhoneOrSms, int userId) {
-        if (pkg.requestedPermissions.isEmpty()) {
-            return;
-        }
-
-        List<String> requestedPermissions = pkg.requestedPermissions;
-        Set<String> grantablePermissions = null;
-
-        // If this is the default Phone or SMS app we grant permissions regardless
-        // whether the version on the system image declares the permission as used since
-        // selecting the app as the default Phone or SMS the user makes a deliberate
-        // choice to grant this app the permissions needed to function. For all other
-        // apps, (default grants on first boot and user creation) we don't grant default
-        // permissions if the version on the system image does not declare them.
-        if (!isDefaultPhoneOrSms && pkg.isUpdatedSystemApp()) {
-            PackageSetting sysPs = mService.mSettings.getDisabledSystemPkgLPr(pkg.packageName);
-            if (sysPs != null && sysPs.pkg != null) {
-                if (sysPs.pkg.requestedPermissions.isEmpty()) {
-                    return;
-                }
-                if (!requestedPermissions.equals(sysPs.pkg.requestedPermissions)) {
-                    grantablePermissions = new ArraySet<>(requestedPermissions);
-                    requestedPermissions = sysPs.pkg.requestedPermissions;
-                }
-            }
-        }
-
-        final int grantablePermissionCount = requestedPermissions.size();
-        for (int i = 0; i < grantablePermissionCount; i++) {
-            String permission = requestedPermissions.get(i);
-
-            // If there is a disabled system app it may request a permission the updated
-            // version ot the data partition doesn't, In this case skip the permission.
-            if (grantablePermissions != null && !grantablePermissions.contains(permission)) {
-                continue;
-            }
-
-            if (permissions.contains(permission)) {
-                final int flags = mService.getPermissionFlags(permission, pkg.packageName, userId);
-
-                // If any flags are set to the permission, then it is either set in
-                // its current state by the system or device/profile owner or the user.
-                // In all these cases we do not want to clobber the current state.
-                // Unless the caller wants to override user choices. The override is
-                // to make sure we can grant the needed permission to the default
-                // sms and phone apps after the user chooses this in the UI.
-                if (flags == 0 || isDefaultPhoneOrSms) {
-                    // Never clobber policy or system.
-                    final int fixedFlags = PackageManager.FLAG_PERMISSION_SYSTEM_FIXED
-                            | PackageManager.FLAG_PERMISSION_POLICY_FIXED;
-                    if ((flags & fixedFlags) != 0) {
-                        continue;
-                    }
-
-                    mService.grantRuntimePermission(pkg.packageName, permission, userId);
-                    if (DEBUG) {
-                        Log.i(TAG, "Granted " + (systemFixed ? "fixed " : "not fixed ")
-                                + permission + " to default handler " + pkg.packageName);
-                    }
-
-                    int newFlags = PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
-                    if (systemFixed) {
-                        newFlags |= PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
-                    }
-
-                    mService.updatePermissionFlags(permission, pkg.packageName,
-                            newFlags, newFlags, userId);
-                }
-
-                // If a component gets a permission for being the default handler A
-                // and also default handler B, we grant the weaker grant form.
-                if ((flags & PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT) != 0
-                        && (flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0
-                        && !systemFixed) {
-                    if (DEBUG) {
-                        Log.i(TAG, "Granted not fixed " + permission + " to default handler "
-                                + pkg.packageName);
-                    }
-                    mService.updatePermissionFlags(permission, pkg.packageName,
-                            PackageManager.FLAG_PERMISSION_SYSTEM_FIXED, 0, userId);
-                }
-            }
-        }
-    }
-
-    private boolean isSysComponentOrPersistentPlatformSignedPrivAppLPr(PackageParser.Package pkg) {
-        if (UserHandle.getAppId(pkg.applicationInfo.uid) < FIRST_APPLICATION_UID) {
-            return true;
-        }
-        if (!pkg.isPrivilegedApp()) {
-            return false;
-        }
-        PackageSetting sysPkg = mService.mSettings.getDisabledSystemPkgLPr(pkg.packageName);
-        if (sysPkg != null && sysPkg.pkg != null) {
-            if ((sysPkg.pkg.applicationInfo.flags & ApplicationInfo.FLAG_PERSISTENT) == 0) {
-                return false;
-            }
-        } else if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_PERSISTENT) == 0) {
-            return false;
-        }
-        return PackageManagerService.compareSignatures(mService.mPlatformPackage.mSignatures,
-                pkg.mSignatures) == PackageManager.SIGNATURE_MATCH;
-    }
-
-    private void grantDefaultPermissionExceptions(int userId) {
-        synchronized (mService.mPackages) {
-            mHandler.removeMessages(MSG_READ_DEFAULT_PERMISSION_EXCEPTIONS);
-
-            if (mGrantExceptions == null) {
-                mGrantExceptions = readDefaultPermissionExceptionsLPw();
-            }
-
-            // mGrantExceptions is null only before the first read and then
-            // it serves as a cache of the default grants that should be
-            // performed for every user. If there is an entry then the app
-            // is on the system image and supports runtime permissions.
-            Set<String> permissions = null;
-            final int exceptionCount = mGrantExceptions.size();
-            for (int i = 0; i < exceptionCount; i++) {
-                String packageName = mGrantExceptions.keyAt(i);
-                PackageParser.Package pkg = getSystemPackageLPr(packageName);
-                List<DefaultPermissionGrant> permissionGrants = mGrantExceptions.valueAt(i);
-                final int permissionGrantCount = permissionGrants.size();
-                for (int j = 0; j < permissionGrantCount; j++) {
-                    DefaultPermissionGrant permissionGrant = permissionGrants.get(j);
-                    if (permissions == null) {
-                        permissions = new ArraySet<>();
-                    } else {
-                        permissions.clear();
-                    }
-                    permissions.add(permissionGrant.name);
-                    grantRuntimePermissionsLPw(pkg, permissions,
-                            permissionGrant.fixed, userId);
-                }
-            }
-        }
-    }
-
-    private File[] getDefaultPermissionFiles() {
-        ArrayList<File> ret = new ArrayList<File>();
-        File dir = new File(Environment.getRootDirectory(), "etc/default-permissions");
-        if (dir.isDirectory() && dir.canRead()) {
-            Collections.addAll(ret, dir.listFiles());
-        }
-        dir = new File(Environment.getVendorDirectory(), "etc/default-permissions");
-        if (dir.isDirectory() && dir.canRead()) {
-            Collections.addAll(ret, dir.listFiles());
-        }
-        return ret.isEmpty() ? null : ret.toArray(new File[0]);
-    }
-
-    private @NonNull ArrayMap<String, List<DefaultPermissionGrant>>
-            readDefaultPermissionExceptionsLPw() {
-        File[] files = getDefaultPermissionFiles();
-        if (files == null) {
-            return new ArrayMap<>(0);
-        }
-
-        ArrayMap<String, List<DefaultPermissionGrant>> grantExceptions = new ArrayMap<>();
-
-        // Iterate over the files in the directory and scan .xml files
-        for (File file : files) {
-            if (!file.getPath().endsWith(".xml")) {
-                Slog.i(TAG, "Non-xml file " + file + " in " + file.getParent() + " directory, ignoring");
-                continue;
-            }
-            if (!file.canRead()) {
-                Slog.w(TAG, "Default permissions file " + file + " cannot be read");
-                continue;
-            }
-            try (
-                InputStream str = new BufferedInputStream(new FileInputStream(file))
-            ) {
-                XmlPullParser parser = Xml.newPullParser();
-                parser.setInput(str, null);
-                parse(parser, grantExceptions);
-            } catch (XmlPullParserException | IOException e) {
-                Slog.w(TAG, "Error reading default permissions file " + file, e);
-            }
-        }
-
-        return grantExceptions;
-    }
-
-    private void parse(XmlPullParser parser, Map<String, List<DefaultPermissionGrant>>
-            outGrantExceptions) throws IOException, XmlPullParserException {
-        final int outerDepth = parser.getDepth();
-        int type;
-        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
-                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
-            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
-                continue;
-            }
-            if (TAG_EXCEPTIONS.equals(parser.getName())) {
-                parseExceptions(parser, outGrantExceptions);
-            } else {
-                Log.e(TAG, "Unknown tag " + parser.getName());
-            }
-        }
-    }
-
-    private void parseExceptions(XmlPullParser parser, Map<String, List<DefaultPermissionGrant>>
-            outGrantExceptions) throws IOException, XmlPullParserException {
-        final int outerDepth = parser.getDepth();
-        int type;
-        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
-                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
-            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
-                continue;
-            }
-            if (TAG_EXCEPTION.equals(parser.getName())) {
-                String packageName = parser.getAttributeValue(null, ATTR_PACKAGE);
-
-                List<DefaultPermissionGrant> packageExceptions =
-                        outGrantExceptions.get(packageName);
-                if (packageExceptions == null) {
-                    // The package must be on the system image
-                    PackageParser.Package pkg = getSystemPackageLPr(packageName);
-                    if (pkg == null) {
-                        Log.w(TAG, "Unknown package:" + packageName);
-                        XmlUtils.skipCurrentTag(parser);
-                        continue;
-                    }
-
-                    // The package must support runtime permissions
-                    if (!doesPackageSupportRuntimePermissions(pkg)) {
-                        Log.w(TAG, "Skipping non supporting runtime permissions package:"
-                                + packageName);
-                        XmlUtils.skipCurrentTag(parser);
-                        continue;
-                    }
-                    packageExceptions = new ArrayList<>();
-                    outGrantExceptions.put(packageName, packageExceptions);
-                }
-
-                parsePermission(parser, packageExceptions);
-            } else {
-                Log.e(TAG, "Unknown tag " + parser.getName() + "under <exceptions>");
-            }
-        }
-    }
-
-    private void parsePermission(XmlPullParser parser, List<DefaultPermissionGrant>
-            outPackageExceptions) throws IOException, XmlPullParserException {
-        final int outerDepth = parser.getDepth();
-        int type;
-        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
-                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
-            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
-                continue;
-            }
-
-            if (TAG_PERMISSION.contains(parser.getName())) {
-                String name = parser.getAttributeValue(null, ATTR_NAME);
-                if (name == null) {
-                    Log.w(TAG, "Mandatory name attribute missing for permission tag");
-                    XmlUtils.skipCurrentTag(parser);
-                    continue;
-                }
-
-                final boolean fixed = XmlUtils.readBooleanAttribute(parser, ATTR_FIXED);
-
-                DefaultPermissionGrant exception = new DefaultPermissionGrant(name, fixed);
-                outPackageExceptions.add(exception);
-            } else {
-                Log.e(TAG, "Unknown tag " + parser.getName() + "under <exception>");
-            }
-        }
-    }
-
-    private static boolean doesPackageSupportRuntimePermissions(PackageParser.Package pkg) {
-        return pkg.applicationInfo.targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1;
-    }
-
-    private static final class DefaultPermissionGrant {
-        final String name;
-        final boolean fixed;
-
-        public DefaultPermissionGrant(String name, boolean fixed) {
-            this.name = name;
-            this.fixed = fixed;
-        }
-    }
-}
diff --git a/com/android/server/pm/DumpState.java b/com/android/server/pm/DumpState.java
new file mode 100644
index 0000000..7ebef83
--- /dev/null
+++ b/com/android/server/pm/DumpState.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.pm;
+
+public final class DumpState {
+    public static final int DUMP_LIBS = 1 << 0;
+    public static final int DUMP_FEATURES = 1 << 1;
+    public static final int DUMP_ACTIVITY_RESOLVERS = 1 << 2;
+    public static final int DUMP_SERVICE_RESOLVERS = 1 << 3;
+    public static final int DUMP_RECEIVER_RESOLVERS = 1 << 4;
+    public static final int DUMP_CONTENT_RESOLVERS = 1 << 5;
+    public static final int DUMP_PERMISSIONS = 1 << 6;
+    public static final int DUMP_PACKAGES = 1 << 7;
+    public static final int DUMP_SHARED_USERS = 1 << 8;
+    public static final int DUMP_MESSAGES = 1 << 9;
+    public static final int DUMP_PROVIDERS = 1 << 10;
+    public static final int DUMP_VERIFIERS = 1 << 11;
+    public static final int DUMP_PREFERRED = 1 << 12;
+    public static final int DUMP_PREFERRED_XML = 1 << 13;
+    public static final int DUMP_KEYSETS = 1 << 14;
+    public static final int DUMP_VERSION = 1 << 15;
+    public static final int DUMP_INSTALLS = 1 << 16;
+    public static final int DUMP_INTENT_FILTER_VERIFIERS = 1 << 17;
+    public static final int DUMP_DOMAIN_PREFERRED = 1 << 18;
+    public static final int DUMP_FROZEN = 1 << 19;
+    public static final int DUMP_DEXOPT = 1 << 20;
+    public static final int DUMP_COMPILER_STATS = 1 << 21;
+    public static final int DUMP_CHANGES = 1 << 22;
+    public static final int DUMP_VOLUMES = 1 << 23;
+
+    public static final int OPTION_SHOW_FILTERS = 1 << 0;
+
+    private int mTypes;
+
+    private int mOptions;
+
+    private boolean mTitlePrinted;
+
+    private SharedUserSetting mSharedUser;
+
+    public boolean isDumping(int type) {
+        if (mTypes == 0 && type != DUMP_PREFERRED_XML) {
+            return true;
+        }
+
+        return (mTypes & type) != 0;
+    }
+
+    public void setDump(int type) {
+        mTypes |= type;
+    }
+
+    public boolean isOptionEnabled(int option) {
+        return (mOptions & option) != 0;
+    }
+
+    public void setOptionEnabled(int option) {
+        mOptions |= option;
+    }
+
+    public boolean onTitlePrinted() {
+        final boolean printed = mTitlePrinted;
+        mTitlePrinted = true;
+        return printed;
+    }
+
+    public boolean getTitlePrinted() {
+        return mTitlePrinted;
+    }
+
+    public void setTitlePrinted(boolean enabled) {
+        mTitlePrinted = enabled;
+    }
+
+    public SharedUserSetting getSharedUser() {
+        return mSharedUser;
+    }
+
+    public void setSharedUser(SharedUserSetting user) {
+        mSharedUser = user;
+    }
+}
\ No newline at end of file
diff --git a/com/android/server/pm/InstantAppRegistry.java b/com/android/server/pm/InstantAppRegistry.java
index e1e5b35..c964f91 100644
--- a/com/android/server/pm/InstantAppRegistry.java
+++ b/com/android/server/pm/InstantAppRegistry.java
@@ -49,6 +49,8 @@
 import com.android.internal.os.SomeArgs;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.XmlUtils;
+import com.android.server.pm.permission.BasePermission;
+
 import libcore.io.IoUtils;
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -878,8 +880,9 @@
         final long identity = Binder.clearCallingIdentity();
         try {
             for (String grantedPermission : appInfo.getGrantedPermissions()) {
-                BasePermission bp = mService.mSettings.mPermissions.get(grantedPermission);
-                if (bp != null && (bp.isRuntime() || bp.isDevelopment()) && bp.isInstant()) {
+                final boolean propagatePermission =
+                        mService.mSettings.canPropagatePermissionToInstantApp(grantedPermission);
+                if (propagatePermission) {
                     mService.grantRuntimePermission(packageName, grantedPermission, userId);
                 }
             }
diff --git a/com/android/server/pm/KeySetManagerService.java b/com/android/server/pm/KeySetManagerService.java
index 49d3c8b..3574466 100644
--- a/com/android/server/pm/KeySetManagerService.java
+++ b/com/android/server/pm/KeySetManagerService.java
@@ -565,7 +565,7 @@
     }
 
     public void dumpLPr(PrintWriter pw, String packageName,
-                        PackageManagerService.DumpState dumpState) {
+                        DumpState dumpState) {
         boolean printedHeader = false;
         for (ArrayMap.Entry<String, PackageSetting> e : mPackages.entrySet()) {
             String keySetPackage = e.getKey();
diff --git a/com/android/server/pm/PackageDexOptimizer.java b/com/android/server/pm/PackageDexOptimizer.java
index 0f580d8..8ebeeae 100644
--- a/com/android/server/pm/PackageDexOptimizer.java
+++ b/com/android/server/pm/PackageDexOptimizer.java
@@ -23,6 +23,7 @@
 import android.os.FileUtils;
 import android.os.PowerManager;
 import android.os.SystemClock;
+import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.WorkSource;
 import android.util.Log;
@@ -103,7 +104,17 @@
     }
 
     static boolean canOptimizePackage(PackageParser.Package pkg) {
-        return (pkg.applicationInfo.flags & ApplicationInfo.FLAG_HAS_CODE) != 0;
+        // We do not dexopt a package with no code.
+        if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_HAS_CODE) == 0) {
+            return false;
+        }
+
+        // We do not dexopt a priv-app package when pm.dexopt.priv-apps is false.
+        if (pkg.isPrivilegedApp()) {
+            return SystemProperties.getBoolean("pm.dexopt.priv-apps", true);
+        }
+
+        return true;
     }
 
     /**
@@ -354,18 +365,13 @@
                 + " dexoptFlags=" + printDexoptFlags(dexoptFlags)
                 + " target-filter=" + compilerFilter);
 
-        String classLoaderContext;
-        if (dexUseInfo.isUnknownClassLoaderContext() ||
-                dexUseInfo.isUnsupportedClassLoaderContext() ||
-                dexUseInfo.isVariableClassLoaderContext()) {
-            // If we have an unknown (not yet set), unsupported (custom class loaders), or a
-            // variable class loader chain, compile without a context and mark the oat file with
-            // SKIP_SHARED_LIBRARY_CHECK. Note that his might lead to a incorrect compilation.
-            // TODO(calin): We should just extract in this case.
-            classLoaderContext = SKIP_SHARED_LIBRARY_CHECK;
-        } else {
-            classLoaderContext = dexUseInfo.getClassLoaderContext();
-        }
+        // TODO(calin): b/64530081 b/66984396. Use SKIP_SHARED_LIBRARY_CHECK for the context
+        // (instead of dexUseInfo.getClassLoaderContext()) in order to compile secondary dex files
+        // in isolation (and avoid to extract/verify the main apk if it's in the class path).
+        // Note this trades correctness for performance since the resulting slow down is
+        // unacceptable in some cases until b/64530081 is fixed.
+        String classLoaderContext = SKIP_SHARED_LIBRARY_CHECK;
+
         try {
             for (String isa : dexUseInfo.getLoaderIsas()) {
                 // Reuse the same dexopt path as for the primary apks. We don't need all the
@@ -425,7 +431,7 @@
             }
 
             if (useInfo.isUsedByOtherApps(path)) {
-                pw.println("used be other apps: " + useInfo.getLoadingPackages(path));
+                pw.println("used by other apps: " + useInfo.getLoadingPackages(path));
             }
 
             Map<String, PackageDexUsage.DexUseInfo> dexUseInfoMap = useInfo.getDexUseInfoMap();
@@ -438,19 +444,10 @@
                     PackageDexUsage.DexUseInfo dexUseInfo = e.getValue();
                     pw.println(dex);
                     pw.increaseIndent();
-                    for (String isa : dexUseInfo.getLoaderIsas()) {
-                        String status = null;
-                        try {
-                            status = DexFile.getDexFileStatus(path, isa);
-                        } catch (IOException ioe) {
-                             status = "[Exception]: " + ioe.getMessage();
-                        }
-                        pw.println(isa + ": " + status);
-                    }
-
+                    // TODO(calin): get the status of the oat file (needs installd call)
                     pw.println("class loader context: " + dexUseInfo.getClassLoaderContext());
                     if (dexUseInfo.isUsedByOtherApps()) {
-                        pw.println("used be other apps: " + dexUseInfo.getLoadingPackages());
+                        pw.println("used by other apps: " + dexUseInfo.getLoadingPackages());
                     }
                     pw.decreaseIndent();
                 }
@@ -474,8 +471,9 @@
         }
 
         if (isProfileGuidedCompilerFilter(targetCompilerFilter) && isUsedByOtherApps) {
-            // If the dex files is used by other apps, we cannot use profile-guided compilation.
-            return getNonProfileGuidedCompilerFilter(targetCompilerFilter);
+            // If the dex files is used by other apps, apply the shared filter.
+            return PackageManagerServiceCompilerMapping.getCompilerFilterForReason(
+                    PackageManagerService.REASON_SHARED);
         }
 
         return targetCompilerFilter;
diff --git a/com/android/server/pm/PackageInstallerService.java b/com/android/server/pm/PackageInstallerService.java
index 1fa37b9..09f9cb8 100644
--- a/com/android/server/pm/PackageInstallerService.java
+++ b/com/android/server/pm/PackageInstallerService.java
@@ -80,6 +80,8 @@
 import com.android.internal.util.ImageUtils;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.IoThread;
+import com.android.server.LocalServices;
+import com.android.server.pm.permission.PermissionManagerInternal;
 
 import libcore.io.IoUtils;
 
@@ -122,6 +124,7 @@
 
     private final Context mContext;
     private final PackageManagerService mPm;
+    private final PermissionManagerInternal mPermissionManager;
 
     private AppOpsManager mAppOps;
 
@@ -177,6 +180,7 @@
     public PackageInstallerService(Context context, PackageManagerService pm) {
         mContext = context;
         mPm = pm;
+        mPermissionManager = LocalServices.getService(PermissionManagerInternal.class);
 
         mInstallThread = new HandlerThread(TAG);
         mInstallThread.start();
@@ -243,35 +247,6 @@
         }
     }
 
-    public void onSecureContainersAvailable() {
-        synchronized (mSessions) {
-            final ArraySet<String> unclaimed = new ArraySet<>();
-            for (String cid : PackageHelper.getSecureContainerList()) {
-                if (isStageName(cid)) {
-                    unclaimed.add(cid);
-                }
-            }
-
-            // Ignore stages claimed by active sessions
-            for (int i = 0; i < mSessions.size(); i++) {
-                final PackageInstallerSession session = mSessions.valueAt(i);
-                final String cid = session.stageCid;
-
-                if (unclaimed.remove(cid)) {
-                    // Claimed by active session, mount it
-                    PackageHelper.mountSdDir(cid, PackageManagerService.getEncryptKey(),
-                            Process.SYSTEM_UID);
-                }
-            }
-
-            // Clean up orphaned staging containers
-            for (String cid : unclaimed) {
-                Slog.w(TAG, "Deleting orphan container " + cid);
-                PackageHelper.destroySdDir(cid);
-            }
-        }
-    }
-
     public static boolean isStageName(String name) {
         final boolean isFile = name.startsWith("vmdl") && name.endsWith(".tmp");
         final boolean isContainer = name.startsWith("smdl") && name.endsWith(".tmp");
@@ -426,7 +401,8 @@
     private int createSessionInternal(SessionParams params, String installerPackageName, int userId)
             throws IOException {
         final int callingUid = Binder.getCallingUid();
-        mPm.enforceCrossUserPermission(callingUid, userId, true, true, "createSession");
+        mPermissionManager.enforceCrossUserPermission(
+                callingUid, userId, true, true, "createSession");
 
         if (mPm.isUserRestricted(userId, UserManager.DISALLOW_INSTALL_APPS)) {
             throw new SecurityException("User restriction prevents installing");
@@ -671,13 +647,6 @@
         return "smdl" + sessionId + ".tmp";
     }
 
-    static void prepareExternalStageCid(String stageCid, long sizeBytes) throws IOException {
-        if (PackageHelper.createSdDir(sizeBytes, stageCid, PackageManagerService.getEncryptKey(),
-                Process.SYSTEM_UID, true) == null) {
-            throw new IOException("Failed to create session cid: " + stageCid);
-        }
-    }
-
     @Override
     public SessionInfo getSessionInfo(int sessionId) {
         synchronized (mSessions) {
@@ -688,7 +657,8 @@
 
     @Override
     public ParceledListSlice<SessionInfo> getAllSessions(int userId) {
-        mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, false, "getAllSessions");
+        mPermissionManager.enforceCrossUserPermission(
+                Binder.getCallingUid(), userId, true, false, "getAllSessions");
 
         final List<SessionInfo> result = new ArrayList<>();
         synchronized (mSessions) {
@@ -704,7 +674,8 @@
 
     @Override
     public ParceledListSlice<SessionInfo> getMySessions(String installerPackageName, int userId) {
-        mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, false, "getMySessions");
+        mPermissionManager.enforceCrossUserPermission(
+                Binder.getCallingUid(), userId, true, false, "getMySessions");
         mAppOps.checkPackage(Binder.getCallingUid(), installerPackageName);
 
         final List<SessionInfo> result = new ArrayList<>();
@@ -726,7 +697,7 @@
     public void uninstall(VersionedPackage versionedPackage, String callerPackageName, int flags,
                 IntentSender statusReceiver, int userId) throws RemoteException {
         final int callingUid = Binder.getCallingUid();
-        mPm.enforceCrossUserPermission(callingUid, userId, true, true, "uninstall");
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId, true, true, "uninstall");
         if ((callingUid != Process.SHELL_UID) && (callingUid != Process.ROOT_UID)) {
             mAppOps.checkPackage(callingUid, callerPackageName);
         }
@@ -775,7 +746,8 @@
 
     @Override
     public void registerCallback(IPackageInstallerCallback callback, int userId) {
-        mPm.enforceCrossUserPermission(Binder.getCallingUid(), userId, true, false, "registerCallback");
+        mPermissionManager.enforceCrossUserPermission(
+                Binder.getCallingUid(), userId, true, false, "registerCallback");
         mCallbacks.register(callback, userId);
     }
 
diff --git a/com/android/server/pm/PackageInstallerSession.java b/com/android/server/pm/PackageInstallerSession.java
index ff6e5b3..d62f093 100644
--- a/com/android/server/pm/PackageInstallerSession.java
+++ b/com/android/server/pm/PackageInstallerSession.java
@@ -36,7 +36,6 @@
 import static com.android.internal.util.XmlUtils.writeLongAttribute;
 import static com.android.internal.util.XmlUtils.writeStringAttribute;
 import static com.android.internal.util.XmlUtils.writeUriAttribute;
-import static com.android.server.pm.PackageInstallerService.prepareExternalStageCid;
 import static com.android.server.pm.PackageInstallerService.prepareStageDir;
 
 import android.Manifest;
@@ -481,12 +480,7 @@
             if (stageDir != null) {
                 mResolvedStageDir = stageDir;
             } else {
-                final String path = PackageHelper.getSdDir(stageCid);
-                if (path != null) {
-                    mResolvedStageDir = new File(path);
-                } else {
-                    throw new IOException("Failed to resolve path to container " + stageCid);
-                }
+                throw new IOException("Missing stageDir");
             }
         }
         return mResolvedStageDir;
@@ -880,14 +874,6 @@
             return;
         }
 
-        if (stageCid != null) {
-            // Figure out the final installed size and resize the container once
-            // and for all. Internally the parser handles straddling between two
-            // locations when inheriting.
-            final long finalSize = calculateInstalledSize();
-            resizeContainer(stageCid, finalSize);
-        }
-
         // Inherit any packages and native libraries from existing install that
         // haven't been overridden.
         if (params.mode == SessionParams.MODE_INHERIT_EXISTING) {
@@ -924,11 +910,6 @@
         // Unpack native libraries
         extractNativeLibraries(mResolvedStageDir, params.abiOverride);
 
-        // Container is ready to go, let's seal it up!
-        if (stageCid != null) {
-            finalizeAndFixContainer(stageCid);
-        }
-
         // We've reached point of no return; call into PMS to install the stage.
         // Regardless of success or failure we always destroy session.
         final IPackageInstallObserver2 localObserver = new IPackageInstallObserver2.Stub() {
@@ -953,7 +934,7 @@
         }
 
         mRelinquished = true;
-        mPm.installStage(mPackageName, stageDir, stageCid, localObserver, params,
+        mPm.installStage(mPackageName, stageDir, localObserver, params,
                 mInstallerPackageName, mInstallerUid, user, mCertificates);
     }
 
@@ -1212,11 +1193,9 @@
         // straddled between the inherited and staged APKs.
         final PackageLite pkg = new PackageLite(null, baseApk, null, null, null, null,
                 splitPaths.toArray(new String[splitPaths.size()]), null);
-        final boolean isForwardLocked =
-                (params.installFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0;
 
         try {
-            return PackageHelper.calculateInstalledSize(pkg, isForwardLocked, params.abiOverride);
+            return PackageHelper.calculateInstalledSize(pkg, params.abiOverride);
         } catch (IOException e) {
             throw new PackageManagerException(INSTALL_FAILED_INVALID_APK,
                     "Failed to calculate install size", e);
@@ -1345,52 +1324,6 @@
         }
     }
 
-    private static void resizeContainer(String cid, long targetSize)
-            throws PackageManagerException {
-        String path = PackageHelper.getSdDir(cid);
-        if (path == null) {
-            throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
-                    "Failed to find mounted " + cid);
-        }
-
-        final long currentSize = new File(path).getTotalSpace();
-        if (currentSize > targetSize) {
-            Slog.w(TAG, "Current size " + currentSize + " is larger than target size "
-                    + targetSize + "; skipping resize");
-            return;
-        }
-
-        if (!PackageHelper.unMountSdDir(cid)) {
-            throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
-                    "Failed to unmount " + cid + " before resize");
-        }
-
-        if (!PackageHelper.resizeSdDir(targetSize, cid,
-                PackageManagerService.getEncryptKey())) {
-            throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
-                    "Failed to resize " + cid + " to " + targetSize + " bytes");
-        }
-
-        path = PackageHelper.mountSdDir(cid, PackageManagerService.getEncryptKey(),
-                Process.SYSTEM_UID, false);
-        if (path == null) {
-            throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
-                    "Failed to mount " + cid + " after resize");
-        }
-    }
-
-    private void finalizeAndFixContainer(String cid) throws PackageManagerException {
-        if (!PackageHelper.finalizeSdDir(cid)) {
-            throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
-                    "Failed to finalize container " + cid);
-        }
-
-        if (!PackageHelper.fixSdPermissions(cid, defaultContainerGid, null)) {
-            throw new PackageManagerException(INSTALL_FAILED_CONTAINER_ERROR,
-                    "Failed to fix permissions on container " + cid);
-        }
-    }
-
     void setPermissionsResult(boolean accepted) {
         if (!mSealed) {
             throw new SecurityException("Must be sealed to accept permissions");
@@ -1419,20 +1352,8 @@
             if (!mPrepared) {
                 if (stageDir != null) {
                     prepareStageDir(stageDir);
-                } else if (stageCid != null) {
-                    final long identity = Binder.clearCallingIdentity();
-                    try {
-                        prepareExternalStageCid(stageCid, params.sizeBytes);
-                    } finally {
-                        Binder.restoreCallingIdentity(identity);
-                    }
-
-                    // TODO: deliver more granular progress for ASEC allocation
-                    mInternalProgress = 0.25f;
-                    computeProgressLocked(true);
                 } else {
-                    throw new IllegalArgumentException(
-                            "Exactly one of stageDir or stageCid stage must be set");
+                    throw new IllegalArgumentException("stageDir must be set");
                 }
 
                 mPrepared = true;
@@ -1534,9 +1455,6 @@
             } catch (InstallerException ignored) {
             }
         }
-        if (stageCid != null) {
-            PackageHelper.destroySdDir(stageCid);
-        }
     }
 
     void dump(IndentingPrintWriter pw) {
diff --git a/com/android/server/pm/PackageManagerService.java b/com/android/server/pm/PackageManagerService.java
index ff52e0e..7d1a647 100644
--- a/com/android/server/pm/PackageManagerService.java
+++ b/com/android/server/pm/PackageManagerService.java
@@ -55,7 +55,6 @@
 import static android.content.pm.PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE;
 import static android.content.pm.PackageManager.INSTALL_FAILED_USER_RESTRICTED;
 import static android.content.pm.PackageManager.INSTALL_FAILED_VERSION_DOWNGRADE;
-import static android.content.pm.PackageManager.INSTALL_FORWARD_LOCK;
 import static android.content.pm.PackageManager.INSTALL_INTERNAL;
 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
 import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
@@ -103,10 +102,9 @@
 import static com.android.server.pm.InstructionSets.getPrimaryInstructionSet;
 import static com.android.server.pm.PackageManagerServiceCompilerMapping.getCompilerFilterForReason;
 import static com.android.server.pm.PackageManagerServiceCompilerMapping.getDefaultCompilerFilter;
-import static com.android.server.pm.PermissionsState.PERMISSION_OPERATION_FAILURE;
-import static com.android.server.pm.PermissionsState.PERMISSION_OPERATION_SUCCESS;
-import static com.android.server.pm.PermissionsState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED;
-
+import static com.android.server.pm.permission.PermissionsState.PERMISSION_OPERATION_FAILURE;
+import static com.android.server.pm.permission.PermissionsState.PERMISSION_OPERATION_SUCCESS;
+import static com.android.server.pm.permission.PermissionsState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED;
 import static dalvik.system.DexFile.getNonProfileGuidedCompilerFilter;
 
 import android.Manifest;
@@ -160,10 +158,11 @@
 import android.content.pm.PackageInfoLite;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.LegacyPackageDeleteObserver;
 import android.content.pm.PackageManagerInternal;
+import android.content.pm.PackageManager.LegacyPackageDeleteObserver;
 import android.content.pm.PackageParser;
 import android.content.pm.PackageParser.ActivityIntentInfo;
+import android.content.pm.PackageParser.Package;
 import android.content.pm.PackageParser.PackageLite;
 import android.content.pm.PackageParser.PackageParserException;
 import android.content.pm.PackageStats;
@@ -230,7 +229,6 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Base64;
-import android.util.TimingsTraceLog;
 import android.util.DisplayMetrics;
 import android.util.EventLog;
 import android.util.ExceptionUtils;
@@ -244,6 +242,7 @@
 import android.util.SparseArray;
 import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
+import android.util.TimingsTraceLog;
 import android.util.Xml;
 import android.util.jar.StrictJarFile;
 import android.util.proto.ProtoOutputStream;
@@ -283,12 +282,19 @@
 import com.android.server.Watchdog;
 import com.android.server.net.NetworkPolicyManagerInternal;
 import com.android.server.pm.Installer.InstallerException;
-import com.android.server.pm.PermissionsState.PermissionState;
 import com.android.server.pm.Settings.DatabaseVersion;
 import com.android.server.pm.Settings.VersionInfo;
 import com.android.server.pm.dex.DexManager;
 import com.android.server.pm.dex.DexoptOptions;
 import com.android.server.pm.dex.PackageDexUsage;
+import com.android.server.pm.permission.BasePermission;
+import com.android.server.pm.permission.DefaultPermissionGrantPolicy;
+import com.android.server.pm.permission.PermissionManagerService;
+import com.android.server.pm.permission.PermissionManagerInternal;
+import com.android.server.pm.permission.DefaultPermissionGrantPolicy.DefaultPermissionGrantedCallback;
+import com.android.server.pm.permission.PermissionManagerInternal.PermissionCallback;
+import com.android.server.pm.permission.PermissionsState;
+import com.android.server.pm.permission.PermissionsState.PermissionState;
 import com.android.server.storage.DeviceStorageMonitorInternal;
 
 import dalvik.system.CloseGuard;
@@ -338,6 +344,7 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -385,7 +392,7 @@
 public class PackageManagerService extends IPackageManager.Stub
         implements PackageSender {
     static final String TAG = "PackageManager";
-    static final boolean DEBUG_SETTINGS = false;
+    public static final boolean DEBUG_SETTINGS = false;
     static final boolean DEBUG_PREFERRED = false;
     static final boolean DEBUG_UPGRADE = false;
     static final boolean DEBUG_DOMAIN_VERIFICATION = false;
@@ -396,7 +403,7 @@
     private static final boolean DEBUG_SHOW_INFO = false;
     private static final boolean DEBUG_PACKAGE_INFO = false;
     private static final boolean DEBUG_INTENT_MATCHING = false;
-    private static final boolean DEBUG_PACKAGE_SCANNING = false;
+    public static final boolean DEBUG_PACKAGE_SCANNING = false;
     private static final boolean DEBUG_VERIFY = false;
     private static final boolean DEBUG_FILTERS = false;
     private static final boolean DEBUG_PERMISSIONS = false;
@@ -427,9 +434,6 @@
     private static final int BLUETOOTH_UID = Process.BLUETOOTH_UID;
     private static final int SHELL_UID = Process.SHELL_UID;
 
-    // Cap the size of permission trees that 3rd party apps can define
-    private static final int MAX_PERMISSION_TREE_FOOTPRINT = 32768;     // characters of text
-
     // Suffix used during package installation when copying/moving
     // package apks to install directory.
     private static final String INSTALL_PACKAGE_SUFFIX = "-";
@@ -578,8 +582,9 @@
     public static final int REASON_BACKGROUND_DEXOPT = 3;
     public static final int REASON_AB_OTA = 4;
     public static final int REASON_INACTIVE_PACKAGE_DOWNGRADE = 5;
+    public static final int REASON_SHARED = 6;
 
-    public static final int REASON_LAST = REASON_INACTIVE_PACKAGE_DOWNGRADE;
+    public static final int REASON_LAST = REASON_SHARED;
 
     /** All dangerous permission names in the same order as the events in MetricsEvent */
     private static final List<String> ALL_DANGEROUS_PERMISSIONS = Arrays.asList(
@@ -654,9 +659,6 @@
     @GuardedBy("mPackages")
     private boolean mDexOptDialogShown;
 
-    /** The location for ASEC container files on internal storage. */
-    final String mAsecInternalPath;
-
     // Used for privilege escalation. MUST NOT BE CALLED WITH mPackages
     // LOCK HELD.  Can be called with mInstallLock held.
     @GuardedBy("mInstallLock")
@@ -862,7 +864,7 @@
                 String targetPath) {
             return getStaticOverlayPaths(targetPackageName, targetPath);
         }
-    };
+    }
 
     class ParallelPackageParserCallback extends PackageParserCallback {
         List<PackageParser.Package> mOverlayPackages = null;
@@ -1004,7 +1006,9 @@
     final SparseArray<IntentFilterVerificationState> mIntentFilterVerificationStates
             = new SparseArray<IntentFilterVerificationState>();
 
+    // TODO remove this and go through mPermissonManager directly
     final DefaultPermissionGrantPolicy mDefaultPermissionPolicy;
+    private final PermissionManagerInternal mPermissionManager;
 
     // List of packages names to keep cached, even if they are uninstalled for all users
     private List<String> mKeepUninstalledPackages;
@@ -1315,7 +1319,6 @@
     static final int POST_INSTALL = 9;
     static final int MCS_RECONNECT = 10;
     static final int MCS_GIVE_UP = 11;
-    static final int UPDATED_MEDIA_STATUS = 12;
     static final int WRITE_SETTINGS = 13;
     static final int WRITE_PACKAGE_RESTRICTIONS = 14;
     static final int PACKAGE_VERIFIED = 15;
@@ -1714,32 +1717,6 @@
 
                     Trace.asyncTraceEnd(TRACE_TAG_PACKAGE_MANAGER, "postInstall", msg.arg1);
                 } break;
-                case UPDATED_MEDIA_STATUS: {
-                    if (DEBUG_SD_INSTALL) Log.i(TAG, "Got message UPDATED_MEDIA_STATUS");
-                    boolean reportStatus = msg.arg1 == 1;
-                    boolean doGc = msg.arg2 == 1;
-                    if (DEBUG_SD_INSTALL) Log.i(TAG, "reportStatus=" + reportStatus + ", doGc = " + doGc);
-                    if (doGc) {
-                        // Force a gc to clear up stale containers.
-                        Runtime.getRuntime().gc();
-                    }
-                    if (msg.obj != null) {
-                        @SuppressWarnings("unchecked")
-                        Set<AsecInstallArgs> args = (Set<AsecInstallArgs>) msg.obj;
-                        if (DEBUG_SD_INSTALL) Log.i(TAG, "Unloading all containers");
-                        // Unload containers
-                        unloadAllContainers(args);
-                    }
-                    if (reportStatus) {
-                        try {
-                            if (DEBUG_SD_INSTALL) Log.i(TAG,
-                                    "Invoking StorageManagerService call back");
-                            PackageHelper.getStorageManager().finishMediaUpdate();
-                        } catch (RemoteException e) {
-                            Log.e(TAG, "StorageManagerService not running?");
-                        }
-                    }
-                } break;
                 case WRITE_SETTINGS: {
                     Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
                     synchronized (mPackages) {
@@ -1910,6 +1887,69 @@
         }
     }
 
+    private PermissionCallback mPermissionCallback = new PermissionCallback() {
+        @Override
+        public void onGidsChanged(int appId, int userId) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    killUid(appId, userId, KILL_APP_REASON_GIDS_CHANGED);
+                }
+            });
+        }
+        @Override
+        public void onPermissionGranted(int uid, int userId) {
+            mOnPermissionChangeListeners.onPermissionsChanged(uid);
+
+            // Not critical; if this is lost, the application has to request again.
+            synchronized (mPackages) {
+                mSettings.writeRuntimePermissionsForUserLPr(userId, false);
+            }
+        }
+        @Override
+        public void onInstallPermissionGranted() {
+            synchronized (mPackages) {
+                scheduleWriteSettingsLocked();
+            }
+        }
+        @Override
+        public void onPermissionRevoked(int uid, int userId) {
+            mOnPermissionChangeListeners.onPermissionsChanged(uid);
+
+            synchronized (mPackages) {
+                // Critical; after this call the application should never have the permission
+                mSettings.writeRuntimePermissionsForUserLPr(userId, true);
+            }
+
+            final int appId = UserHandle.getAppId(uid);
+            killUid(appId, userId, KILL_APP_REASON_PERMISSIONS_REVOKED);
+        }
+        @Override
+        public void onInstallPermissionRevoked() {
+            synchronized (mPackages) {
+                scheduleWriteSettingsLocked();
+            }
+        }
+        @Override
+        public void onPermissionUpdated(int userId) {
+            synchronized (mPackages) {
+                mSettings.writeRuntimePermissionsForUserLPr(userId, false);
+            }
+        }
+        @Override
+        public void onInstallPermissionUpdated() {
+            synchronized (mPackages) {
+                scheduleWriteSettingsLocked();
+            }
+        }
+        @Override
+        public void onPermissionRemoved() {
+            synchronized (mPackages) {
+                mSettings.writeLPr();
+            }
+        }
+    };
+
     private void handlePackagePostInstall(PackageInstalledInfo res, boolean grantPermissions,
             boolean killApp, boolean virtualPreload, String[] grantedPermissions,
             boolean launchedForRestore, String installerPackage,
@@ -1926,7 +1966,10 @@
             // review flag which is used to emulate runtime permissions for
             // legacy apps.
             if (grantPermissions) {
-                grantRequestedRuntimePermissions(res.pkg, res.newUsers, grantedPermissions);
+                final int callingUid = Binder.getCallingUid();
+                mPermissionManager.grantRequestedRuntimePermissions(
+                        res.pkg, res.newUsers, grantedPermissions, callingUid,
+                        mPermissionCallback);
             }
 
             final boolean update = res.removedInfo != null
@@ -1942,9 +1985,9 @@
             // app that had no children, we grant requested runtime permissions to the new
             // children if the parent on the system image had them already granted.
             if (res.pkg.parentPackage != null) {
-                synchronized (mPackages) {
-                    grantRuntimePermissionsGrantedToDisabledPrivSysPackageParentLPw(res.pkg);
-                }
+                final int callingUid = Binder.getCallingUid();
+                mPermissionManager.grantRuntimePermissionsGrantedToDisabledPackage(
+                        res.pkg, callingUid, mPermissionCallback);
             }
 
             synchronized (mPackages) {
@@ -2109,39 +2152,6 @@
         }
     }
 
-    private void grantRuntimePermissionsGrantedToDisabledPrivSysPackageParentLPw(
-            PackageParser.Package pkg) {
-        if (pkg.parentPackage == null) {
-            return;
-        }
-        if (pkg.requestedPermissions == null) {
-            return;
-        }
-        final PackageSetting disabledSysParentPs = mSettings
-                .getDisabledSystemPkgLPr(pkg.parentPackage.packageName);
-        if (disabledSysParentPs == null || disabledSysParentPs.pkg == null
-                || !disabledSysParentPs.isPrivileged()
-                || (disabledSysParentPs.childPackageNames != null
-                        && !disabledSysParentPs.childPackageNames.isEmpty())) {
-            return;
-        }
-        final int[] allUserIds = sUserManager.getUserIds();
-        final int permCount = pkg.requestedPermissions.size();
-        for (int i = 0; i < permCount; i++) {
-            String permission = pkg.requestedPermissions.get(i);
-            BasePermission bp = mSettings.mPermissions.get(permission);
-            if (bp == null || !(bp.isRuntime() || bp.isDevelopment())) {
-                continue;
-            }
-            for (int userId : allUserIds) {
-                if (disabledSysParentPs.getPermissionsState().hasRuntimePermission(
-                        permission, userId)) {
-                    grantRuntimePermission(pkg.packageName, permission, userId);
-                }
-            }
-        }
-    }
-
     private StorageEventListener mStorageListener = new StorageEventListener() {
         @Override
         public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) {
@@ -2164,14 +2174,6 @@
                     unloadPrivatePackages(vol);
                 }
             }
-
-            if (vol.type == VolumeInfo.TYPE_PUBLIC && vol.isPrimary()) {
-                if (vol.state == VolumeInfo.STATE_MOUNTED) {
-                    updateExternalMediaStatus(true, false);
-                } else if (vol.state == VolumeInfo.STATE_EJECTING) {
-                    updateExternalMediaStatus(false, false);
-                }
-            }
         }
 
         @Override
@@ -2202,58 +2204,6 @@
         }
     };
 
-    private void grantRequestedRuntimePermissions(PackageParser.Package pkg, int[] userIds,
-            String[] grantedPermissions) {
-        for (int userId : userIds) {
-            grantRequestedRuntimePermissionsForUser(pkg, userId, grantedPermissions);
-        }
-    }
-
-    private void grantRequestedRuntimePermissionsForUser(PackageParser.Package pkg, int userId,
-            String[] grantedPermissions) {
-        PackageSetting ps = (PackageSetting) pkg.mExtras;
-        if (ps == null) {
-            return;
-        }
-
-        PermissionsState permissionsState = ps.getPermissionsState();
-
-        final int immutableFlags = PackageManager.FLAG_PERMISSION_SYSTEM_FIXED
-                | PackageManager.FLAG_PERMISSION_POLICY_FIXED;
-
-        final boolean supportsRuntimePermissions = pkg.applicationInfo.targetSdkVersion
-                >= Build.VERSION_CODES.M;
-
-        final boolean instantApp = isInstantApp(pkg.packageName, userId);
-
-        for (String permission : pkg.requestedPermissions) {
-            final BasePermission bp;
-            synchronized (mPackages) {
-                bp = mSettings.mPermissions.get(permission);
-            }
-            if (bp != null && (bp.isRuntime() || bp.isDevelopment())
-                    && (!instantApp || bp.isInstant())
-                    && (supportsRuntimePermissions || !bp.isRuntimeOnly())
-                    && (grantedPermissions == null
-                           || ArrayUtils.contains(grantedPermissions, permission))) {
-                final int flags = permissionsState.getPermissionFlags(permission, userId);
-                if (supportsRuntimePermissions) {
-                    // Installer cannot change immutable permissions.
-                    if ((flags & immutableFlags) == 0) {
-                        grantRuntimePermission(pkg.packageName, permission, userId);
-                    }
-                } else if (mPermissionReviewRequired) {
-                    // In permission review mode we clear the review flag when we
-                    // are asked to install the app with all permissions granted.
-                    if ((flags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0) {
-                        updatePermissionFlags(permission, pkg.packageName,
-                                PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED, 0, userId);
-                    }
-                }
-            }
-        }
-    }
-
     Bundle extrasForInstallResult(PackageInstalledInfo res) {
         Bundle extras = null;
         switch (res.returnCode) {
@@ -2422,7 +2372,29 @@
         mFactoryTest = factoryTest;
         mOnlyCore = onlyCore;
         mMetrics = new DisplayMetrics();
-        mSettings = new Settings(mPackages);
+        mInstaller = installer;
+
+        // Create sub-components that provide services / data. Order here is important.
+        synchronized (mInstallLock) {
+        synchronized (mPackages) {
+            // Expose private service for system components to use.
+            LocalServices.addService(
+                    PackageManagerInternal.class, new PackageManagerInternalImpl());
+            sUserManager = new UserManagerService(context, this,
+                    new UserDataPreparer(mInstaller, mInstallLock, mContext, mOnlyCore), mPackages);
+            mPermissionManager = PermissionManagerService.create(context,
+                    new DefaultPermissionGrantedCallback() {
+                        @Override
+                        public void onDefaultRuntimePermissionsGranted(int userId) {
+                            synchronized(mPackages) {
+                                mSettings.onDefaultRuntimePermissionsGrantedLPr(userId);
+                            }
+                        }
+                    }, mPackages /*externalLock*/);
+            mDefaultPermissionPolicy = mPermissionManager.getDefaultPermissionGrantPolicy();
+            mSettings = new Settings(mPermissionManager.getPermissionSettings(), mPackages);
+        }
+        }
         mSettings.addSharedUserLPw("android.uid.system", Process.SYSTEM_UID,
                 ApplicationInfo.FLAG_SYSTEM, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
         mSettings.addSharedUserLPw("android.uid.phone", RADIO_UID,
@@ -2453,7 +2425,6 @@
             mSeparateProcesses = null;
         }
 
-        mInstaller = installer;
         mPackageDexOptimizer = new PackageDexOptimizer(installer, mInstallLock, context,
                 "*dexopt*");
         mDexManager = new DexManager(this, mPackageDexOptimizer, installer, mInstallLock);
@@ -2482,32 +2453,12 @@
             mHandler = new PackageHandler(mHandlerThread.getLooper());
             mProcessLoggingHandler = new ProcessLoggingHandler();
             Watchdog.getInstance().addThread(mHandler, WATCHDOG_TIMEOUT);
-
-            mDefaultPermissionPolicy = new DefaultPermissionGrantPolicy(this);
             mInstantAppRegistry = new InstantAppRegistry(this);
 
             File dataDir = Environment.getDataDirectory();
             mAppInstallDir = new File(dataDir, "app");
             mAppLib32InstallDir = new File(dataDir, "app-lib");
-            mAsecInternalPath = new File(dataDir, "app-asec").getPath();
             mDrmAppPrivateInstallDir = new File(dataDir, "app-private");
-            sUserManager = new UserManagerService(context, this,
-                    new UserDataPreparer(mInstaller, mInstallLock, mContext, mOnlyCore), mPackages);
-
-            // Propagate permission configuration in to package manager.
-            ArrayMap<String, SystemConfig.PermissionEntry> permConfig
-                    = systemConfig.getPermissions();
-            for (int i=0; i<permConfig.size(); i++) {
-                SystemConfig.PermissionEntry perm = permConfig.valueAt(i);
-                BasePermission bp = mSettings.mPermissions.get(perm.name);
-                if (bp == null) {
-                    bp = new BasePermission(perm.name, "android", BasePermission.TYPE_BUILTIN);
-                    mSettings.mPermissions.put(perm.name, bp);
-                }
-                if (perm.gids != null) {
-                    bp.setGids(perm.gids, perm.perUser);
-                }
-            }
 
             ArrayMap<String, String> libConfig = systemConfig.getSharedLibraries();
             final int builtInLibCount = libConfig.size();
@@ -3110,8 +3061,6 @@
         // once we have a booted system.
         mInstaller.setWarnIfHeld(mPackages);
 
-        // Expose private service for system components to use.
-        LocalServices.addService(PackageManagerInternal.class, new PackageManagerInternalImpl());
         Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
     }
 
@@ -3314,6 +3263,24 @@
             removeCodePathLI(dstCodePath);
             return null;
         }
+
+        // If we have a profile for a compressed APK, copy it to the reference location.
+        // Since the package is the stub one, remove the stub suffix to get the normal package and
+        // APK name.
+        File profileFile = new File(getPrebuildProfilePath(pkg).replace(STUB_SUFFIX, ""));
+        if (profileFile.exists()) {
+            try {
+                // We could also do this lazily before calling dexopt in
+                // PackageDexOptimizer to prevent this happening on first boot. The issue
+                // is that we don't have a good way to say "do this only once".
+                if (!mInstaller.copySystemProfile(profileFile.getAbsolutePath(),
+                        pkg.applicationInfo.uid, pkg.packageName)) {
+                    Log.e(TAG, "decompressPackage failed to copy system profile!");
+                }
+            } catch (Exception e) {
+                Log.e(TAG, "Failed to copy profile " + profileFile.getAbsolutePath() + " ", e);
+            }
+        }
         return dstCodePath;
     }
 
@@ -3909,7 +3876,7 @@
     public boolean isPackageAvailable(String packageName, int userId) {
         if (!sUserManager.exists(userId)) return false;
         final int callingUid = Binder.getCallingUid();
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 false /*requireFullPermission*/, false /*checkShell*/, "is package available");
         synchronized (mPackages) {
             PackageParser.Package p = mPackages.get(packageName);
@@ -3952,7 +3919,7 @@
             int flags, int filterCallingUid, int userId) {
         if (!sUserManager.exists(userId)) return null;
         flags = updateFlagsForPackage(flags, userId, packageName);
-        enforceCrossUserPermission(Binder.getCallingUid(), userId,
+        mPermissionManager.enforceCrossUserPermission(Binder.getCallingUid(), userId,
                 false /* requireFullPermission */, false /* checkShell */, "get package info");
 
         // reader
@@ -4214,7 +4181,7 @@
         if (!sUserManager.exists(userId)) return -1;
         final int callingUid = Binder.getCallingUid();
         flags = updateFlagsForPackage(flags, userId, packageName);
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 false /*requireFullPermission*/, false /*checkShell*/, "getPackageUid");
 
         // reader
@@ -4244,7 +4211,7 @@
         if (!sUserManager.exists(userId)) return null;
         final int callingUid = Binder.getCallingUid();
         flags = updateFlagsForPackage(flags, userId, packageName);
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 false /*requireFullPermission*/, false /*checkShell*/, "getPackageGids");
 
         // reader
@@ -4271,116 +4238,23 @@
         return null;
     }
 
-    static PermissionInfo generatePermissionInfo(BasePermission bp, int flags) {
-        if (bp.perm != null) {
-            return PackageParser.generatePermissionInfo(bp.perm, flags);
-        }
-        PermissionInfo pi = new PermissionInfo();
-        pi.name = bp.name;
-        pi.packageName = bp.sourcePackage;
-        pi.nonLocalizedLabel = bp.name;
-        pi.protectionLevel = bp.protectionLevel;
-        return pi;
-    }
-
     @Override
     public PermissionInfo getPermissionInfo(String name, String packageName, int flags) {
-        final int callingUid = Binder.getCallingUid();
-        if (getInstantAppPackageName(callingUid) != null) {
-            return null;
-        }
-        // reader
-        synchronized (mPackages) {
-            final BasePermission p = mSettings.mPermissions.get(name);
-            if (p == null) {
-                return null;
-            }
-            // If the caller is an app that targets pre 26 SDK drop protection flags.
-            PermissionInfo permissionInfo = generatePermissionInfo(p, flags);
-            if (permissionInfo != null) {
-                final int protectionLevel = adjustPermissionProtectionFlagsLPr(
-                        permissionInfo.protectionLevel, packageName, callingUid);
-                if (permissionInfo.protectionLevel != protectionLevel) {
-                    // If we return different protection level, don't use the cached info
-                    if (p.perm != null && p.perm.info == permissionInfo) {
-                        permissionInfo = new PermissionInfo(permissionInfo);
-                    }
-                    permissionInfo.protectionLevel = protectionLevel;
-                }
-            }
-            return permissionInfo;
-        }
-    }
-
-    private int adjustPermissionProtectionFlagsLPr(int protectionLevel,
-            String packageName, int uid) {
-        // Signature permission flags area always reported
-        final int protectionLevelMasked = protectionLevel
-                & (PermissionInfo.PROTECTION_NORMAL
-                | PermissionInfo.PROTECTION_DANGEROUS
-                | PermissionInfo.PROTECTION_SIGNATURE);
-        if (protectionLevelMasked == PermissionInfo.PROTECTION_SIGNATURE) {
-            return protectionLevel;
-        }
-
-        // System sees all flags.
-        final int appId = UserHandle.getAppId(uid);
-        if (appId == Process.SYSTEM_UID || appId == Process.ROOT_UID
-                || appId == Process.SHELL_UID) {
-            return protectionLevel;
-        }
-
-        // Normalize package name to handle renamed packages and static libs
-        packageName = resolveInternalPackageNameLPr(packageName,
-                PackageManager.VERSION_CODE_HIGHEST);
-
-        // Apps that target O see flags for all protection levels.
-        final PackageSetting ps = mSettings.mPackages.get(packageName);
-        if (ps == null) {
-            return protectionLevel;
-        }
-        if (ps.appId != appId) {
-            return protectionLevel;
-        }
-
-        final PackageParser.Package pkg = mPackages.get(packageName);
-        if (pkg == null) {
-            return protectionLevel;
-        }
-        if (pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.O) {
-            return protectionLevelMasked;
-        }
-
-        return protectionLevel;
+        return mPermissionManager.getPermissionInfo(name, packageName, flags, getCallingUid());
     }
 
     @Override
-    public @Nullable ParceledListSlice<PermissionInfo> queryPermissionsByGroup(String group,
+    public @Nullable ParceledListSlice<PermissionInfo> queryPermissionsByGroup(String groupName,
             int flags) {
-        if (getInstantAppPackageName(Binder.getCallingUid()) != null) {
-            return null;
-        }
-        // reader
+        // TODO Move this to PermissionManager when mPermissionGroups is moved there
         synchronized (mPackages) {
-            if (group != null && !mPermissionGroups.containsKey(group)) {
+            if (groupName != null && !mPermissionGroups.containsKey(groupName)) {
                 // This is thrown as NameNotFoundException
                 return null;
             }
-
-            ArrayList<PermissionInfo> out = new ArrayList<PermissionInfo>(10);
-            for (BasePermission p : mSettings.mPermissions.values()) {
-                if (group == null) {
-                    if (p.perm == null || p.perm.info.group == null) {
-                        out.add(generatePermissionInfo(p, flags));
-                    }
-                } else {
-                    if (p.perm != null && group.equals(p.perm.info.group)) {
-                        out.add(PackageParser.generatePermissionInfo(p.perm, flags));
-                    }
-                }
-            }
-            return new ParceledListSlice<>(out);
         }
+        return new ParceledListSlice<>(
+                mPermissionManager.getPermissionInfoByGroup(groupName, flags, getCallingUid()));
     }
 
     @Override
@@ -4455,7 +4329,7 @@
             int filterCallingUid, int userId) {
         if (!sUserManager.exists(userId)) return null;
         flags = updateFlagsForApplication(flags, userId, packageName);
-        enforceCrossUserPermission(Binder.getCallingUid(), userId,
+        mPermissionManager.enforceCrossUserPermission(Binder.getCallingUid(), userId,
                 false /* requireFullPermission */, false /* checkShell */, "get application info");
 
         // writer
@@ -4764,7 +4638,8 @@
             triaged = false;
         }
         if ((flags & PackageManager.MATCH_ANY_USER) != 0) {
-            enforceCrossUserPermission(Binder.getCallingUid(), userId, false, false,
+            mPermissionManager.enforceCrossUserPermission(
+                    Binder.getCallingUid(), userId, false, false,
                     "MATCH_ANY_USER flag requires INTERACT_ACROSS_USERS permission at "
                     + Debug.getCallers(5));
         } else if ((flags & PackageManager.MATCH_UNINSTALLED_PACKAGES) != 0 && isCallerSystemUser
@@ -4893,7 +4768,7 @@
             int filterCallingUid, int userId) {
         if (!sUserManager.exists(userId)) return null;
         flags = updateFlagsForComponent(flags, userId, component);
-        enforceCrossUserPermission(Binder.getCallingUid(), userId,
+        mPermissionManager.enforceCrossUserPermission(Binder.getCallingUid(), userId,
                 false /* requireFullPermission */, false /* checkShell */, "get activity info");
         synchronized (mPackages) {
             PackageParser.Activity a = mActivities.mActivities.get(component);
@@ -4952,7 +4827,7 @@
         if (!sUserManager.exists(userId)) return null;
         final int callingUid = Binder.getCallingUid();
         flags = updateFlagsForComponent(flags, userId, component);
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 false /* requireFullPermission */, false /* checkShell */, "get receiver info");
         synchronized (mPackages) {
             PackageParser.Activity a = mReceivers.mActivities.get(component);
@@ -5089,7 +4964,7 @@
         if (!sUserManager.exists(userId)) return null;
         final int callingUid = Binder.getCallingUid();
         flags = updateFlagsForComponent(flags, userId, component);
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 false /* requireFullPermission */, false /* checkShell */, "get service info");
         synchronized (mPackages) {
             PackageParser.Service s = mServices.mServices.get(component);
@@ -5113,7 +4988,7 @@
         if (!sUserManager.exists(userId)) return null;
         final int callingUid = Binder.getCallingUid();
         flags = updateFlagsForComponent(flags, userId, component);
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 false /* requireFullPermission */, false /* checkShell */, "get provider info");
         synchronized (mPackages) {
             PackageParser.Provider p = mProviders.mProviders.get(component);
@@ -5276,39 +5151,7 @@
 
     @Override
     public int checkPermission(String permName, String pkgName, int userId) {
-        if (!sUserManager.exists(userId)) {
-            return PackageManager.PERMISSION_DENIED;
-        }
-        final int callingUid = Binder.getCallingUid();
-
-        synchronized (mPackages) {
-            final PackageParser.Package p = mPackages.get(pkgName);
-            if (p != null && p.mExtras != null) {
-                final PackageSetting ps = (PackageSetting) p.mExtras;
-                if (filterAppAccessLPr(ps, callingUid, userId)) {
-                    return PackageManager.PERMISSION_DENIED;
-                }
-                final boolean instantApp = ps.getInstantApp(userId);
-                final PermissionsState permissionsState = ps.getPermissionsState();
-                if (permissionsState.hasPermission(permName, userId)) {
-                    if (instantApp) {
-                        BasePermission bp = mSettings.mPermissions.get(permName);
-                        if (bp != null && bp.isInstant()) {
-                            return PackageManager.PERMISSION_GRANTED;
-                        }
-                    } else {
-                        return PackageManager.PERMISSION_GRANTED;
-                    }
-                }
-                // Special case: ACCESS_FINE_LOCATION permission includes ACCESS_COARSE_LOCATION
-                if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permName) && permissionsState
-                        .hasPermission(Manifest.permission.ACCESS_FINE_LOCATION, userId)) {
-                    return PackageManager.PERMISSION_GRANTED;
-                }
-            }
-        }
-
-        return PackageManager.PERMISSION_DENIED;
+        return mPermissionManager.checkPermission(permName, pkgName, getCallingUid(), userId);
     }
 
     @Override
@@ -5339,8 +5182,7 @@
                 final PermissionsState permissionsState = settingBase.getPermissionsState();
                 if (permissionsState.hasPermission(permName, userId)) {
                     if (isUidInstantApp) {
-                        BasePermission bp = mSettings.mPermissions.get(permName);
-                        if (bp != null && bp.isInstant()) {
+                        if (mPermissionManager.isPermissionInstant(permName)) {
                             return PackageManager.PERMISSION_GRANTED;
                         }
                     } else {
@@ -5409,448 +5251,49 @@
         }
     }
 
-    /**
-     * Checks if the request is from the system or an app that has INTERACT_ACROSS_USERS
-     * or INTERACT_ACROSS_USERS_FULL permissions, if the userid is not for the caller.
-     * @param checkShell whether to prevent shell from access if there's a debugging restriction
-     * @param message the message to log on security exception
-     */
-    void enforceCrossUserPermission(int callingUid, int userId, boolean requireFullPermission,
-            boolean checkShell, String message) {
-        if (userId < 0) {
-            throw new IllegalArgumentException("Invalid userId " + userId);
-        }
-        if (checkShell) {
-            enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, userId);
-        }
-        if (userId == UserHandle.getUserId(callingUid)) return;
-        if (callingUid != Process.SYSTEM_UID && callingUid != 0) {
-            if (requireFullPermission) {
-                mContext.enforceCallingOrSelfPermission(
-                        android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, message);
-            } else {
-                try {
-                    mContext.enforceCallingOrSelfPermission(
-                            android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, message);
-                } catch (SecurityException se) {
-                    mContext.enforceCallingOrSelfPermission(
-                            android.Manifest.permission.INTERACT_ACROSS_USERS, message);
-                }
-            }
-        }
-    }
-
-    void enforceShellRestriction(String restriction, int callingUid, int userHandle) {
-        if (callingUid == Process.SHELL_UID) {
-            if (userHandle >= 0
-                    && sUserManager.hasUserRestriction(restriction, userHandle)) {
-                throw new SecurityException("Shell does not have permission to access user "
-                        + userHandle);
-            } else if (userHandle < 0) {
-                Slog.e(TAG, "Unable to check shell permission for user " + userHandle + "\n\t"
-                        + Debug.getCallers(3));
-            }
-        }
-    }
-
-    private BasePermission findPermissionTreeLP(String permName) {
-        for(BasePermission bp : mSettings.mPermissionTrees.values()) {
-            if (permName.startsWith(bp.name) &&
-                    permName.length() > bp.name.length() &&
-                    permName.charAt(bp.name.length()) == '.') {
-                return bp;
-            }
-        }
-        return null;
-    }
-
-    private BasePermission checkPermissionTreeLP(String permName) {
-        if (permName != null) {
-            BasePermission bp = findPermissionTreeLP(permName);
-            if (bp != null) {
-                if (bp.uid == UserHandle.getAppId(Binder.getCallingUid())) {
-                    return bp;
-                }
-                throw new SecurityException("Calling uid "
-                        + Binder.getCallingUid()
-                        + " is not allowed to add to permission tree "
-                        + bp.name + " owned by uid " + bp.uid);
-            }
-        }
-        throw new SecurityException("No permission tree found for " + permName);
-    }
-
-    static boolean compareStrings(CharSequence s1, CharSequence s2) {
-        if (s1 == null) {
-            return s2 == null;
-        }
-        if (s2 == null) {
-            return false;
-        }
-        if (s1.getClass() != s2.getClass()) {
-            return false;
-        }
-        return s1.equals(s2);
-    }
-
-    static boolean comparePermissionInfos(PermissionInfo pi1, PermissionInfo pi2) {
-        if (pi1.icon != pi2.icon) return false;
-        if (pi1.logo != pi2.logo) return false;
-        if (pi1.protectionLevel != pi2.protectionLevel) return false;
-        if (!compareStrings(pi1.name, pi2.name)) return false;
-        if (!compareStrings(pi1.nonLocalizedLabel, pi2.nonLocalizedLabel)) return false;
-        // We'll take care of setting this one.
-        if (!compareStrings(pi1.packageName, pi2.packageName)) return false;
-        // These are not currently stored in settings.
-        //if (!compareStrings(pi1.group, pi2.group)) return false;
-        //if (!compareStrings(pi1.nonLocalizedDescription, pi2.nonLocalizedDescription)) return false;
-        //if (pi1.labelRes != pi2.labelRes) return false;
-        //if (pi1.descriptionRes != pi2.descriptionRes) return false;
-        return true;
-    }
-
-    int permissionInfoFootprint(PermissionInfo info) {
-        int size = info.name.length();
-        if (info.nonLocalizedLabel != null) size += info.nonLocalizedLabel.length();
-        if (info.nonLocalizedDescription != null) size += info.nonLocalizedDescription.length();
-        return size;
-    }
-
-    int calculateCurrentPermissionFootprintLocked(BasePermission tree) {
-        int size = 0;
-        for (BasePermission perm : mSettings.mPermissions.values()) {
-            if (perm.uid == tree.uid) {
-                size += perm.name.length() + permissionInfoFootprint(perm.perm.info);
-            }
-        }
-        return size;
-    }
-
-    void enforcePermissionCapLocked(PermissionInfo info, BasePermission tree) {
-        // We calculate the max size of permissions defined by this uid and throw
-        // if that plus the size of 'info' would exceed our stated maximum.
-        if (tree.uid != Process.SYSTEM_UID) {
-            final int curTreeSize = calculateCurrentPermissionFootprintLocked(tree);
-            if (curTreeSize + permissionInfoFootprint(info) > MAX_PERMISSION_TREE_FOOTPRINT) {
-                throw new SecurityException("Permission tree size cap exceeded");
-            }
-        }
-    }
-
-    boolean addPermissionLocked(PermissionInfo info, boolean async) {
-        if (getInstantAppPackageName(Binder.getCallingUid()) != null) {
-            throw new SecurityException("Instant apps can't add permissions");
-        }
-        if (info.labelRes == 0 && info.nonLocalizedLabel == null) {
-            throw new SecurityException("Label must be specified in permission");
-        }
-        BasePermission tree = checkPermissionTreeLP(info.name);
-        BasePermission bp = mSettings.mPermissions.get(info.name);
-        boolean added = bp == null;
-        boolean changed = true;
-        int fixedLevel = PermissionInfo.fixProtectionLevel(info.protectionLevel);
-        if (added) {
-            enforcePermissionCapLocked(info, tree);
-            bp = new BasePermission(info.name, tree.sourcePackage,
-                    BasePermission.TYPE_DYNAMIC);
-        } else if (bp.type != BasePermission.TYPE_DYNAMIC) {
-            throw new SecurityException(
-                    "Not allowed to modify non-dynamic permission "
-                    + info.name);
-        } else {
-            if (bp.protectionLevel == fixedLevel
-                    && bp.perm.owner.equals(tree.perm.owner)
-                    && bp.uid == tree.uid
-                    && comparePermissionInfos(bp.perm.info, info)) {
-                changed = false;
-            }
-        }
-        bp.protectionLevel = fixedLevel;
-        info = new PermissionInfo(info);
-        info.protectionLevel = fixedLevel;
-        bp.perm = new PackageParser.Permission(tree.perm.owner, info);
-        bp.perm.info.packageName = tree.perm.info.packageName;
-        bp.uid = tree.uid;
-        if (added) {
-            mSettings.mPermissions.put(info.name, bp);
-        }
-        if (changed) {
-            if (!async) {
-                mSettings.writeLPr();
-            } else {
-                scheduleWriteSettingsLocked();
-            }
-        }
-        return added;
+    boolean addPermission(PermissionInfo info, final boolean async) {
+        return mPermissionManager.addPermission(
+                info, async, getCallingUid(), new PermissionCallback() {
+                    @Override
+                    public void onPermissionChanged() {
+                        if (!async) {
+                            mSettings.writeLPr();
+                        } else {
+                            scheduleWriteSettingsLocked();
+                        }
+                    }
+                });
     }
 
     @Override
     public boolean addPermission(PermissionInfo info) {
         synchronized (mPackages) {
-            return addPermissionLocked(info, false);
+            return addPermission(info, false);
         }
     }
 
     @Override
     public boolean addPermissionAsync(PermissionInfo info) {
         synchronized (mPackages) {
-            return addPermissionLocked(info, true);
+            return addPermission(info, true);
         }
     }
 
     @Override
-    public void removePermission(String name) {
-        if (getInstantAppPackageName(Binder.getCallingUid()) != null) {
-            throw new SecurityException("Instant applications don't have access to this method");
-        }
-        synchronized (mPackages) {
-            checkPermissionTreeLP(name);
-            BasePermission bp = mSettings.mPermissions.get(name);
-            if (bp != null) {
-                if (bp.type != BasePermission.TYPE_DYNAMIC) {
-                    throw new SecurityException(
-                            "Not allowed to modify non-dynamic permission "
-                            + name);
-                }
-                mSettings.mPermissions.remove(name);
-                mSettings.writeLPr();
-            }
-        }
-    }
-
-    private static void enforceDeclaredAsUsedAndRuntimeOrDevelopmentPermission(
-            PackageParser.Package pkg, BasePermission bp) {
-        int index = pkg.requestedPermissions.indexOf(bp.name);
-        if (index == -1) {
-            throw new SecurityException("Package " + pkg.packageName
-                    + " has not requested permission " + bp.name);
-        }
-        if (!bp.isRuntime() && !bp.isDevelopment()) {
-            throw new SecurityException("Permission " + bp.name
-                    + " is not a changeable permission type");
-        }
+    public void removePermission(String permName) {
+        mPermissionManager.removePermission(permName, getCallingUid(), mPermissionCallback);
     }
 
     @Override
-    public void grantRuntimePermission(String packageName, String name, final int userId) {
-        grantRuntimePermission(packageName, name, userId, false /* Only if not fixed by policy */);
-    }
-
-    private void grantRuntimePermission(String packageName, String name, final int userId,
-            boolean overridePolicy) {
-        if (!sUserManager.exists(userId)) {
-            Log.e(TAG, "No such user:" + userId);
-            return;
-        }
-        final int callingUid = Binder.getCallingUid();
-
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
-                "grantRuntimePermission");
-
-        enforceCrossUserPermission(callingUid, userId,
-                true /* requireFullPermission */, true /* checkShell */,
-                "grantRuntimePermission");
-
-        final int uid;
-        final PackageSetting ps;
-
-        synchronized (mPackages) {
-            final PackageParser.Package pkg = mPackages.get(packageName);
-            if (pkg == null) {
-                throw new IllegalArgumentException("Unknown package: " + packageName);
-            }
-            final BasePermission bp = mSettings.mPermissions.get(name);
-            if (bp == null) {
-                throw new IllegalArgumentException("Unknown permission: " + name);
-            }
-            ps = (PackageSetting) pkg.mExtras;
-            if (ps == null
-                    || filterAppAccessLPr(ps, callingUid, userId)) {
-                throw new IllegalArgumentException("Unknown package: " + packageName);
-            }
-
-            enforceDeclaredAsUsedAndRuntimeOrDevelopmentPermission(pkg, bp);
-
-            // If a permission review is required for legacy apps we represent
-            // their permissions as always granted runtime ones since we need
-            // to keep the review required permission flag per user while an
-            // install permission's state is shared across all users.
-            if (mPermissionReviewRequired
-                    && pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M
-                    && bp.isRuntime()) {
-                return;
-            }
-
-            uid = UserHandle.getUid(userId, pkg.applicationInfo.uid);
-
-            final PermissionsState permissionsState = ps.getPermissionsState();
-
-            final int flags = permissionsState.getPermissionFlags(name, userId);
-            if ((flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0) {
-                throw new SecurityException("Cannot grant system fixed permission "
-                        + name + " for package " + packageName);
-            }
-            if (!overridePolicy && (flags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0) {
-                throw new SecurityException("Cannot grant policy fixed permission "
-                        + name + " for package " + packageName);
-            }
-
-            if (bp.isDevelopment()) {
-                // Development permissions must be handled specially, since they are not
-                // normal runtime permissions.  For now they apply to all users.
-                if (permissionsState.grantInstallPermission(bp) !=
-                        PermissionsState.PERMISSION_OPERATION_FAILURE) {
-                    scheduleWriteSettingsLocked();
-                }
-                return;
-            }
-
-            if (ps.getInstantApp(userId) && !bp.isInstant()) {
-                throw new SecurityException("Cannot grant non-ephemeral permission"
-                        + name + " for package " + packageName);
-            }
-
-            if (pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M) {
-                Slog.w(TAG, "Cannot grant runtime permission to a legacy app");
-                return;
-            }
-
-            final int result = permissionsState.grantRuntimePermission(bp, userId);
-            switch (result) {
-                case PermissionsState.PERMISSION_OPERATION_FAILURE: {
-                    return;
-                }
-
-                case PermissionsState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED: {
-                    final int appId = UserHandle.getAppId(pkg.applicationInfo.uid);
-                    mHandler.post(new Runnable() {
-                        @Override
-                        public void run() {
-                            killUid(appId, userId, KILL_APP_REASON_GIDS_CHANGED);
-                        }
-                    });
-                }
-                break;
-            }
-
-            if (bp.isRuntime()) {
-                logPermissionGranted(mContext, name, packageName);
-            }
-
-            mOnPermissionChangeListeners.onPermissionsChanged(uid);
-
-            // Not critical if that is lost - app has to request again.
-            mSettings.writeRuntimePermissionsForUserLPr(userId, false);
-        }
-
-        // Only need to do this if user is initialized. Otherwise it's a new user
-        // and there are no processes running as the user yet and there's no need
-        // to make an expensive call to remount processes for the changed permissions.
-        if (READ_EXTERNAL_STORAGE.equals(name)
-                || WRITE_EXTERNAL_STORAGE.equals(name)) {
-            final long token = Binder.clearCallingIdentity();
-            try {
-                if (sUserManager.isInitialized(userId)) {
-                    StorageManagerInternal storageManagerInternal = LocalServices.getService(
-                            StorageManagerInternal.class);
-                    storageManagerInternal.onExternalStoragePolicyChanged(uid, packageName);
-                }
-            } finally {
-                Binder.restoreCallingIdentity(token);
-            }
-        }
+    public void grantRuntimePermission(String packageName, String permName, final int userId) {
+        mPermissionManager.grantRuntimePermission(permName, packageName, false /*overridePolicy*/,
+                getCallingUid(), userId, mPermissionCallback);
     }
 
     @Override
-    public void revokeRuntimePermission(String packageName, String name, int userId) {
-        revokeRuntimePermission(packageName, name, userId, false /* Only if not fixed by policy */);
-    }
-
-    private void revokeRuntimePermission(String packageName, String name, int userId,
-            boolean overridePolicy) {
-        if (!sUserManager.exists(userId)) {
-            Log.e(TAG, "No such user:" + userId);
-            return;
-        }
-
-        mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS,
-                "revokeRuntimePermission");
-
-        enforceCrossUserPermission(Binder.getCallingUid(), userId,
-                true /* requireFullPermission */, true /* checkShell */,
-                "revokeRuntimePermission");
-
-        final int appId;
-
-        synchronized (mPackages) {
-            final PackageParser.Package pkg = mPackages.get(packageName);
-            if (pkg == null) {
-                throw new IllegalArgumentException("Unknown package: " + packageName);
-            }
-            final PackageSetting ps = (PackageSetting) pkg.mExtras;
-            if (ps == null
-                    || filterAppAccessLPr(ps, Binder.getCallingUid(), userId)) {
-                throw new IllegalArgumentException("Unknown package: " + packageName);
-            }
-            final BasePermission bp = mSettings.mPermissions.get(name);
-            if (bp == null) {
-                throw new IllegalArgumentException("Unknown permission: " + name);
-            }
-
-            enforceDeclaredAsUsedAndRuntimeOrDevelopmentPermission(pkg, bp);
-
-            // If a permission review is required for legacy apps we represent
-            // their permissions as always granted runtime ones since we need
-            // to keep the review required permission flag per user while an
-            // install permission's state is shared across all users.
-            if (mPermissionReviewRequired
-                    && pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M
-                    && bp.isRuntime()) {
-                return;
-            }
-
-            final PermissionsState permissionsState = ps.getPermissionsState();
-
-            final int flags = permissionsState.getPermissionFlags(name, userId);
-            if ((flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0) {
-                throw new SecurityException("Cannot revoke system fixed permission "
-                        + name + " for package " + packageName);
-            }
-            if (!overridePolicy && (flags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0) {
-                throw new SecurityException("Cannot revoke policy fixed permission "
-                        + name + " for package " + packageName);
-            }
-
-            if (bp.isDevelopment()) {
-                // Development permissions must be handled specially, since they are not
-                // normal runtime permissions.  For now they apply to all users.
-                if (permissionsState.revokeInstallPermission(bp) !=
-                        PermissionsState.PERMISSION_OPERATION_FAILURE) {
-                    scheduleWriteSettingsLocked();
-                }
-                return;
-            }
-
-            if (permissionsState.revokeRuntimePermission(bp, userId) ==
-                    PermissionsState.PERMISSION_OPERATION_FAILURE) {
-                return;
-            }
-
-            if (bp.isRuntime()) {
-                logPermissionRevoked(mContext, name, packageName);
-            }
-
-            mOnPermissionChangeListeners.onPermissionsChanged(pkg.applicationInfo.uid);
-
-            // Critical, after this call app should never have the permission.
-            mSettings.writeRuntimePermissionsForUserLPr(userId, true);
-
-            appId = UserHandle.getAppId(pkg.applicationInfo.uid);
-        }
-
-        killUid(appId, userId, KILL_APP_REASON_PERMISSIONS_REVOKED);
+    public void revokeRuntimePermission(String packageName, String permName, int userId) {
+        mPermissionManager.revokeRuntimePermission(permName, packageName, false /*overridePolicy*/,
+                getCallingUid(), userId, mPermissionCallback);
     }
 
     /**
@@ -5943,91 +5386,16 @@
     }
 
     @Override
-    public int getPermissionFlags(String name, String packageName, int userId) {
-        if (!sUserManager.exists(userId)) {
-            return 0;
-        }
-
-        enforceGrantRevokeRuntimePermissionPermissions("getPermissionFlags");
-
-        final int callingUid = Binder.getCallingUid();
-        enforceCrossUserPermission(callingUid, userId,
-                true /* requireFullPermission */, false /* checkShell */,
-                "getPermissionFlags");
-
-        synchronized (mPackages) {
-            final PackageParser.Package pkg = mPackages.get(packageName);
-            if (pkg == null) {
-                return 0;
-            }
-            final BasePermission bp = mSettings.mPermissions.get(name);
-            if (bp == null) {
-                return 0;
-            }
-            final PackageSetting ps = (PackageSetting) pkg.mExtras;
-            if (ps == null
-                    || filterAppAccessLPr(ps, callingUid, userId)) {
-                return 0;
-            }
-            PermissionsState permissionsState = ps.getPermissionsState();
-            return permissionsState.getPermissionFlags(name, userId);
-        }
+    public int getPermissionFlags(String permName, String packageName, int userId) {
+        return mPermissionManager.getPermissionFlags(permName, packageName, getCallingUid(), userId);
     }
 
     @Override
-    public void updatePermissionFlags(String name, String packageName, int flagMask,
+    public void updatePermissionFlags(String permName, String packageName, int flagMask,
             int flagValues, int userId) {
-        if (!sUserManager.exists(userId)) {
-            return;
-        }
-
-        enforceGrantRevokeRuntimePermissionPermissions("updatePermissionFlags");
-
-        final int callingUid = Binder.getCallingUid();
-        enforceCrossUserPermission(callingUid, userId,
-                true /* requireFullPermission */, true /* checkShell */,
-                "updatePermissionFlags");
-
-        // Only the system can change these flags and nothing else.
-        if (getCallingUid() != Process.SYSTEM_UID) {
-            flagMask &= ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
-            flagValues &= ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
-            flagMask &= ~PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
-            flagValues &= ~PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
-            flagValues &= ~PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
-        }
-
-        synchronized (mPackages) {
-            final PackageParser.Package pkg = mPackages.get(packageName);
-            if (pkg == null) {
-                throw new IllegalArgumentException("Unknown package: " + packageName);
-            }
-            final PackageSetting ps = (PackageSetting) pkg.mExtras;
-            if (ps == null
-                    || filterAppAccessLPr(ps, callingUid, userId)) {
-                throw new IllegalArgumentException("Unknown package: " + packageName);
-            }
-
-            final BasePermission bp = mSettings.mPermissions.get(name);
-            if (bp == null) {
-                throw new IllegalArgumentException("Unknown permission: " + name);
-            }
-
-            PermissionsState permissionsState = ps.getPermissionsState();
-
-            boolean hadState = permissionsState.getRuntimePermissionState(name, userId) != null;
-
-            if (permissionsState.updatePermissionFlags(bp, userId, flagMask, flagValues)) {
-                // Install and runtime permissions are stored in different places,
-                // so figure out what permission changed and persist the change.
-                if (permissionsState.getInstallPermissionState(name) != null) {
-                    scheduleWriteSettingsLocked();
-                } else if (permissionsState.getRuntimePermissionState(name, userId) != null
-                        || hadState) {
-                    mSettings.writeRuntimePermissionsForUserLPr(userId, false);
-                }
-            }
-        }
+        mPermissionManager.updatePermissionFlags(
+                permName, packageName, flagMask, flagValues, getCallingUid(), userId,
+                mPermissionCallback);
     }
 
     /**
@@ -6036,52 +5404,16 @@
      */
     @Override
     public void updatePermissionFlagsForAllApps(int flagMask, int flagValues, int userId) {
-        if (!sUserManager.exists(userId)) {
-            return;
-        }
-
-        enforceGrantRevokeRuntimePermissionPermissions("updatePermissionFlagsForAllApps");
-
-        enforceCrossUserPermission(Binder.getCallingUid(), userId,
-                true /* requireFullPermission */, true /* checkShell */,
-                "updatePermissionFlagsForAllApps");
-
-        // Only the system can change system fixed flags.
-        if (getCallingUid() != Process.SYSTEM_UID) {
-            flagMask &= ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
-            flagValues &= ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
-        }
-
         synchronized (mPackages) {
-            boolean changed = false;
-            final int packageCount = mPackages.size();
-            for (int pkgIndex = 0; pkgIndex < packageCount; pkgIndex++) {
-                final PackageParser.Package pkg = mPackages.valueAt(pkgIndex);
-                final PackageSetting ps = (PackageSetting) pkg.mExtras;
-                if (ps == null) {
-                    continue;
-                }
-                PermissionsState permissionsState = ps.getPermissionsState();
-                changed |= permissionsState.updatePermissionFlagsForAllPermissions(
-                        userId, flagMask, flagValues);
-            }
+            final boolean changed = mPermissionManager.updatePermissionFlagsForAllApps(
+                    flagMask, flagValues, getCallingUid(), userId, mPackages.values(),
+                    mPermissionCallback);
             if (changed) {
                 mSettings.writeRuntimePermissionsForUserLPr(userId, false);
             }
         }
     }
 
-    private void enforceGrantRevokeRuntimePermissionPermissions(String message) {
-        if (mContext.checkCallingOrSelfPermission(Manifest.permission.GRANT_RUNTIME_PERMISSIONS)
-                != PackageManager.PERMISSION_GRANTED
-            && mContext.checkCallingOrSelfPermission(Manifest.permission.REVOKE_RUNTIME_PERMISSIONS)
-                != PackageManager.PERMISSION_GRANTED) {
-            throw new SecurityException(message + " requires "
-                    + Manifest.permission.GRANT_RUNTIME_PERMISSIONS + " or "
-                    + Manifest.permission.REVOKE_RUNTIME_PERMISSIONS);
-        }
-    }
-
     @Override
     public boolean shouldShowRequestPermissionRationale(String permissionName,
             String packageName, int userId) {
@@ -6273,7 +5605,7 @@
      * <br />
      * {@link PackageManager#SIGNATURE_NO_MATCH}: if the two signature sets differ.
      */
-    static int compareSignatures(Signature[] s1, Signature[] s2) {
+    public static int compareSignatures(Signature[] s1, Signature[] s2) {
         if (s1 == null) {
             return s2 == null
                     ? PackageManager.SIGNATURE_NEITHER_SIGNED
@@ -6627,9 +5959,14 @@
     public ResolveInfo resolveIntent(Intent intent, String resolvedType,
             int flags, int userId) {
         return resolveIntentInternal(
-                intent, resolvedType, flags, userId, false /*includeInstantApps*/);
+                intent, resolvedType, flags, userId, false /*resolveForStart*/);
     }
 
+    /**
+     * Normally instant apps can only be resolved when they're visible to the caller.
+     * However, if {@code resolveForStart} is {@code true}, all instant apps are visible
+     * since we need to allow the system to start any installed application.
+     */
     private ResolveInfo resolveIntentInternal(Intent intent, String resolvedType,
             int flags, int userId, boolean resolveForStart) {
         try {
@@ -6638,7 +5975,7 @@
             if (!sUserManager.exists(userId)) return null;
             final int callingUid = Binder.getCallingUid();
             flags = updateFlagsForResolve(flags, userId, intent, callingUid, resolveForStart);
-            enforceCrossUserPermission(callingUid, userId,
+            mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                     false /*requireFullPermission*/, false /*checkShell*/, "resolve intent");
 
             Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "queryIntentActivities");
@@ -7219,7 +6556,7 @@
             boolean resolveForStart, boolean allowDynamicSplits) {
         if (!sUserManager.exists(userId)) return Collections.emptyList();
         final String instantAppPkgName = getInstantAppPackageName(filterCallingUid);
-        enforceCrossUserPermission(Binder.getCallingUid(), userId,
+        mPermissionManager.enforceCrossUserPermission(Binder.getCallingUid(), userId,
                 false /* requireFullPermission */, false /* checkShell */,
                 "query intent activities");
         final String pkgName = intent.getPackage();
@@ -7974,7 +7311,7 @@
         final int callingUid = Binder.getCallingUid();
         flags = updateFlagsForResolve(flags, userId, intent, callingUid,
                 false /*includeInstantApps*/);
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 false /*requireFullPermission*/, false /*checkShell*/,
                 "query intent activity options");
         final String resultsAction = intent.getAction();
@@ -8155,7 +7492,7 @@
             String resolvedType, int flags, int userId, boolean allowDynamicSplits) {
         if (!sUserManager.exists(userId)) return Collections.emptyList();
         final int callingUid = Binder.getCallingUid();
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 false /*requireFullPermission*/, false /*checkShell*/,
                 "query intent receivers");
         final String instantAppPkgName = getInstantAppPackageName(callingUid);
@@ -8267,7 +7604,7 @@
             String resolvedType, int flags, int userId, int callingUid,
             boolean includeInstantApps) {
         if (!sUserManager.exists(userId)) return Collections.emptyList();
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 false /*requireFullPermission*/, false /*checkShell*/,
                 "query intent receivers");
         final String instantAppPkgName = getInstantAppPackageName(callingUid);
@@ -8507,7 +7844,7 @@
         if (!sUserManager.exists(userId)) return ParceledListSlice.emptyList();
         flags = updateFlagsForPackage(flags, userId, null);
         final boolean listUninstalled = (flags & MATCH_KNOWN_PACKAGES) != 0;
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 true /* requireFullPermission */, false /* checkShell */,
                 "get installed packages");
 
@@ -8594,7 +7931,7 @@
             String[] permissions, int flags, int userId) {
         if (!sUserManager.exists(userId)) return ParceledListSlice.emptyList();
         flags = updateFlagsForPackage(flags, userId, permissions);
-        enforceCrossUserPermission(Binder.getCallingUid(), userId,
+        mPermissionManager.enforceCrossUserPermission(Binder.getCallingUid(), userId,
                 true /* requireFullPermission */, false /* checkShell */,
                 "get packages holding permissions");
         final boolean listUninstalled = (flags & MATCH_KNOWN_PACKAGES) != 0;
@@ -8699,7 +8036,7 @@
             mContext.enforceCallingOrSelfPermission(Manifest.permission.ACCESS_INSTANT_APPS,
                     "getEphemeralApplications");
         }
-        enforceCrossUserPermission(Binder.getCallingUid(), userId,
+        mPermissionManager.enforceCrossUserPermission(Binder.getCallingUid(), userId,
                 true /* requireFullPermission */, false /* checkShell */,
                 "getEphemeralApplications");
         synchronized (mPackages) {
@@ -8714,7 +8051,7 @@
 
     @Override
     public boolean isInstantApp(String packageName, int userId) {
-        enforceCrossUserPermission(Binder.getCallingUid(), userId,
+        mPermissionManager.enforceCrossUserPermission(Binder.getCallingUid(), userId,
                 true /* requireFullPermission */, false /* checkShell */,
                 "isInstantApp");
         if (HIDE_EPHEMERAL_APIS || isEphemeralDisabled()) {
@@ -8747,7 +8084,7 @@
             return null;
         }
 
-        enforceCrossUserPermission(Binder.getCallingUid(), userId,
+        mPermissionManager.enforceCrossUserPermission(Binder.getCallingUid(), userId,
                 true /* requireFullPermission */, false /* checkShell */,
                 "getInstantAppCookie");
         if (!isCallerSameApp(packageName, Binder.getCallingUid())) {
@@ -8765,7 +8102,7 @@
             return true;
         }
 
-        enforceCrossUserPermission(Binder.getCallingUid(), userId,
+        mPermissionManager.enforceCrossUserPermission(Binder.getCallingUid(), userId,
                 true /* requireFullPermission */, true /* checkShell */,
                 "setInstantAppCookie");
         if (!isCallerSameApp(packageName, Binder.getCallingUid())) {
@@ -8787,7 +8124,7 @@
             mContext.enforceCallingOrSelfPermission(Manifest.permission.ACCESS_INSTANT_APPS,
                     "getInstantAppIcon");
         }
-        enforceCrossUserPermission(Binder.getCallingUid(), userId,
+        mPermissionManager.enforceCrossUserPermission(Binder.getCallingUid(), userId,
                 true /* requireFullPermission */, false /* checkShell */,
                 "getInstantAppIcon");
 
@@ -8847,6 +8184,10 @@
 
     @Override
     public ProviderInfo resolveContentProvider(String name, int flags, int userId) {
+        return resolveContentProviderInternal(name, flags, userId);
+    }
+
+    private ProviderInfo resolveContentProviderInternal(String name, int flags, int userId) {
         if (!sUserManager.exists(userId)) return null;
         flags = updateFlagsForComponent(flags, userId, name);
         final String instantAppPkgName = getInstantAppPackageName(Binder.getCallingUid());
@@ -9105,7 +8446,7 @@
         return fname;
     }
 
-    static void reportSettingsProblem(int priority, String msg) {
+    public static void reportSettingsProblem(int priority, String msg) {
         logCriticalInfo(priority, msg);
     }
 
@@ -9738,7 +9079,7 @@
      * and {@code numberOfPackagesFailed}.
      */
     private int[] performDexOptUpgrade(List<PackageParser.Package> pkgs, boolean showDialog,
-            String compilerFilter, boolean bootComplete) {
+            final String compilerFilter, boolean bootComplete) {
 
         int numberOfPackagesVisited = 0;
         int numberOfPackagesOptimized = 0;
@@ -9749,6 +9090,8 @@
         for (PackageParser.Package pkg : pkgs) {
             numberOfPackagesVisited++;
 
+            boolean useProfileForDexopt = false;
+
             if ((isFirstBoot() || isUpgrade()) && isSystemApp(pkg)) {
                 // Copy over initial preopt profiles since we won't get any JIT samples for methods
                 // that are already compiled.
@@ -9762,11 +9105,30 @@
                         if (!mInstaller.copySystemProfile(profileFile.getAbsolutePath(),
                                 pkg.applicationInfo.uid, pkg.packageName)) {
                             Log.e(TAG, "Installer failed to copy system profile!");
+                        } else {
+                            // Disabled as this causes speed-profile compilation during first boot
+                            // even if things are already compiled.
+                            // useProfileForDexopt = true;
                         }
                     } catch (Exception e) {
                         Log.e(TAG, "Failed to copy profile " + profileFile.getAbsolutePath() + " ",
                                 e);
                     }
+                } else {
+                    PackageSetting disabledPs = mSettings.getDisabledSystemPkgLPr(pkg.packageName);
+                    // Handle compressed APKs in this path. Only do this for stubs with profiles to
+                    // minimize the number off apps being speed-profile compiled during first boot.
+                    // The other paths will not change the filter.
+                    if (disabledPs != null && disabledPs.pkg.isStub) {
+                        // The package is the stub one, remove the stub suffix to get the normal
+                        // package and APK names.
+                        String systemProfilePath =
+                                getPrebuildProfilePath(disabledPs.pkg).replace(STUB_SUFFIX, "");
+                        File systemProfile = new File(systemProfilePath);
+                        // Use the profile for compilation if there exists one for the same package
+                        // in the system partition.
+                        useProfileForDexopt = systemProfile.exists();
+                    }
                 }
             }
 
@@ -9795,17 +9157,13 @@
                 }
             }
 
-            // If the OTA updates a system app which was previously preopted to a non-preopted state
-            // the app might end up being verified at runtime. That's because by default the apps
-            // are verify-profile but for preopted apps there's no profile.
-            // Do a hacky check to ensure that if we have no profiles (a reasonable indication
-            // that before the OTA the app was preopted) the app gets compiled with a non-profile
-            // filter (by default 'quicken').
-            // Note that at this stage unused apps are already filtered.
-            if (isSystemApp(pkg) &&
-                    DexFile.isProfileGuidedCompilerFilter(compilerFilter) &&
-                    !Environment.getReferenceProfile(pkg.packageName).exists()) {
-                compilerFilter = getNonProfileGuidedCompilerFilter(compilerFilter);
+            String pkgCompilerFilter = compilerFilter;
+            if (useProfileForDexopt) {
+                // Use background dexopt mode to try and use the profile. Note that this does not
+                // guarantee usage of the profile.
+                pkgCompilerFilter =
+                        PackageManagerServiceCompilerMapping.getCompilerFilterForReason(
+                                PackageManagerService.REASON_BACKGROUND_DEXOPT);
             }
 
             // checkProfiles is false to avoid merging profiles during boot which
@@ -9816,22 +9174,9 @@
             int dexoptFlags = bootComplete ? DexoptOptions.DEXOPT_BOOT_COMPLETE : 0;
             int primaryDexOptStaus = performDexOptTraced(new DexoptOptions(
                     pkg.packageName,
-                    compilerFilter,
+                    pkgCompilerFilter,
                     dexoptFlags));
 
-            if (pkg.isSystemApp()) {
-                // Only dexopt shared secondary dex files belonging to system apps to not slow down
-                // too much boot after an OTA.
-                int secondaryDexoptFlags = dexoptFlags |
-                        DexoptOptions.DEXOPT_ONLY_SECONDARY_DEX |
-                        DexoptOptions.DEXOPT_ONLY_SHARED_DEX;
-                mDexManager.dexoptSecondaryDex(new DexoptOptions(
-                        pkg.packageName,
-                        compilerFilter,
-                        secondaryDexoptFlags));
-            }
-
-            // TODO(shubhamajmera): Record secondary dexopt stats.
             switch (primaryDexOptStaus) {
                 case PackageDexOptimizer.DEX_OPT_PERFORMED:
                     numberOfPackagesOptimized++;
@@ -10388,7 +9733,8 @@
         }
     }
 
-    private void addSharedLibraryLPr(ArraySet<String> usesLibraryFiles, SharedLibraryEntry file,
+    private void addSharedLibraryLPr(Set<String> usesLibraryFiles,
+            SharedLibraryEntry file,
             PackageParser.Package changingLib) {
         if (file.path != null) {
             usesLibraryFiles.add(file.path);
@@ -10417,7 +9763,10 @@
         if (pkg == null) {
             return;
         }
-        ArraySet<String> usesLibraryFiles = null;
+        // The collection used here must maintain the order of addition (so
+        // that libraries are searched in the correct order) and must have no
+        // duplicates.
+        Set<String> usesLibraryFiles = null;
         if (pkg.usesLibraries != null) {
             usesLibraryFiles = addSharedLibrariesLPw(pkg.usesLibraries,
                     null, null, pkg.packageName, changingLib, true,
@@ -10441,10 +9790,10 @@
         }
     }
 
-    private ArraySet<String> addSharedLibrariesLPw(@NonNull List<String> requestedLibraries,
+    private Set<String> addSharedLibrariesLPw(@NonNull List<String> requestedLibraries,
             @Nullable int[] requiredVersions, @Nullable String[][] requiredCertDigests,
             @NonNull String packageName, @Nullable PackageParser.Package changingLib,
-            boolean required, int targetSdk, @Nullable ArraySet<String> outUsedLibraries)
+            boolean required, int targetSdk, @Nullable Set<String> outUsedLibraries)
             throws PackageManagerException {
         final int libCount = requestedLibraries.size();
         for (int i = 0; i < libCount; i++) {
@@ -10510,7 +9859,9 @@
                 }
 
                 if (outUsedLibraries == null) {
-                    outUsedLibraries = new ArraySet<>();
+                    // Use LinkedHashSet to preserve the order of files added to
+                    // usesLibraryFiles while eliminating duplicates.
+                    outUsedLibraries = new LinkedHashSet<>();
                 }
                 addSharedLibraryLPr(outUsedLibraries, libEntry, changingLib);
             }
@@ -10703,6 +10054,12 @@
 
         assertPackageIsValid(pkg, policyFlags, scanFlags);
 
+        if (Build.IS_DEBUGGABLE &&
+                pkg.isPrivilegedApp() &&
+                !SystemProperties.getBoolean("pm.dexopt.priv-apps", true)) {
+            PackageManagerServiceUtils.logPackageHasUncompressedCode(pkg);
+        }
+
         // Initialize package source and resource directories
         final File scanFile = new File(pkg.codePath);
         final File destCodeFile = new File(pkg.applicationInfo.getCodePath());
@@ -11866,84 +11223,25 @@
                     }
                 }
 
-                ArrayMap<String, BasePermission> permissionMap =
-                        p.tree ? mSettings.mPermissionTrees
-                                : mSettings.mPermissions;
-                BasePermission bp = permissionMap.get(p.info.name);
-
-                // Allow system apps to redefine non-system permissions
-                if (bp != null && !Objects.equals(bp.sourcePackage, p.info.packageName)) {
-                    final boolean currentOwnerIsSystem = (bp.perm != null
-                            && isSystemApp(bp.perm.owner));
-                    if (isSystemApp(p.owner)) {
-                        if (bp.type == BasePermission.TYPE_BUILTIN && bp.perm == null) {
-                            // It's a built-in permission and no owner, take ownership now
-                            bp.packageSetting = pkgSetting;
-                            bp.perm = p;
-                            bp.uid = pkg.applicationInfo.uid;
-                            bp.sourcePackage = p.info.packageName;
-                            p.info.flags |= PermissionInfo.FLAG_INSTALLED;
-                        } else if (!currentOwnerIsSystem) {
-                            String msg = "New decl " + p.owner + " of permission  "
-                                    + p.info.name + " is system; overriding " + bp.sourcePackage;
-                            reportSettingsProblem(Log.WARN, msg);
-                            bp = null;
-                        }
-                    }
-                }
-
-                if (bp == null) {
-                    bp = new BasePermission(p.info.name, p.info.packageName,
-                            BasePermission.TYPE_NORMAL);
+                // TODO Move to PermissionManager once mPermissionTrees moves there.
+//                        p.tree ? mSettings.mPermissionTrees
+//                                : mSettings.mPermissions;
+//                final BasePermission bp = BasePermission.createOrUpdate(
+//                        permissionMap.get(p.info.name), p, pkg, mSettings.mPermissionTrees, chatty);
+//                permissionMap.put(p.info.name, bp);
+                if (p.tree) {
+                    final ArrayMap<String, BasePermission> permissionMap =
+                            mSettings.mPermissionTrees;
+                    final BasePermission bp = BasePermission.createOrUpdate(
+                            permissionMap.get(p.info.name), p, pkg, mSettings.mPermissionTrees,
+                            chatty);
                     permissionMap.put(p.info.name, bp);
+                } else {
+                    final BasePermission bp = BasePermission.createOrUpdate(
+                            (BasePermission) mPermissionManager.getPermissionTEMP(p.info.name),
+                            p, pkg, mSettings.mPermissionTrees, chatty);
+                    mPermissionManager.putPermissionTEMP(p.info.name, bp);
                 }
-
-                if (bp.perm == null) {
-                    if (bp.sourcePackage == null
-                            || bp.sourcePackage.equals(p.info.packageName)) {
-                        BasePermission tree = findPermissionTreeLP(p.info.name);
-                        if (tree == null
-                                || tree.sourcePackage.equals(p.info.packageName)) {
-                            bp.packageSetting = pkgSetting;
-                            bp.perm = p;
-                            bp.uid = pkg.applicationInfo.uid;
-                            bp.sourcePackage = p.info.packageName;
-                            p.info.flags |= PermissionInfo.FLAG_INSTALLED;
-                            if (chatty) {
-                                if (r == null) {
-                                    r = new StringBuilder(256);
-                                } else {
-                                    r.append(' ');
-                                }
-                                r.append(p.info.name);
-                            }
-                        } else {
-                            Slog.w(TAG, "Permission " + p.info.name + " from package "
-                                    + p.info.packageName + " ignored: base tree "
-                                    + tree.name + " is from package "
-                                    + tree.sourcePackage);
-                        }
-                    } else {
-                        Slog.w(TAG, "Permission " + p.info.name + " from package "
-                                + p.info.packageName + " ignored: original from "
-                                + bp.sourcePackage);
-                    }
-                } else if (chatty) {
-                    if (r == null) {
-                        r = new StringBuilder(256);
-                    } else {
-                        r.append(' ');
-                    }
-                    r.append("DUP:");
-                    r.append(p.info.name);
-                }
-                if (bp.perm == p) {
-                    bp.protectionLevel = p.info.protectionLevel;
-                }
-            }
-
-            if (r != null) {
-                if (DEBUG_PACKAGE_SCANNING) Log.d(TAG, "  Permissions: " + r);
             }
 
             N = pkg.instrumentation.size();
@@ -12673,12 +11971,12 @@
         r = null;
         for (i=0; i<N; i++) {
             PackageParser.Permission p = pkg.permissions.get(i);
-            BasePermission bp = mSettings.mPermissions.get(p.info.name);
+            BasePermission bp = (BasePermission) mPermissionManager.getPermissionTEMP(p.info.name);
             if (bp == null) {
                 bp = mSettings.mPermissionTrees.get(p.info.name);
             }
-            if (bp != null && bp.perm == p) {
-                bp.perm = null;
+            if (bp != null && bp.isPermission(p)) {
+                bp.setPermission(null);
                 if (DEBUG_REMOVE && chatty) {
                     if (r == null) {
                         r = new StringBuilder(256);
@@ -12703,8 +12001,7 @@
         r = null;
         for (i=0; i<N; i++) {
             String perm = pkg.requestedPermissions.get(i);
-            BasePermission bp = mSettings.mPermissions.get(perm);
-            if (bp != null && (bp.protectionLevel&PermissionInfo.PROTECTION_FLAG_APPOP) != 0) {
+            if (mPermissionManager.isPermissionAppOp(perm)) {
                 ArraySet<String> appOpPkgs = mAppOpPermissionPackages.get(perm);
                 if (appOpPkgs != null) {
                     appOpPkgs.remove(pkg.packageName);
@@ -12809,23 +12106,32 @@
 
     private void updatePermissionsLPw(String changingPkg,
             PackageParser.Package pkgInfo, String replaceVolumeUuid, int flags) {
+        // TODO: Most of the methods exposing BasePermission internals [source package name,
+        // etc..] shouldn't be needed. Instead, when we've parsed a permission that doesn't
+        // have package settings, we should make note of it elsewhere [map between
+        // source package name and BasePermission] and cycle through that here. Then we
+        // define a single method on BasePermission that takes a PackageSetting, changing
+        // package name and a package.
+        // NOTE: With this approach, we also don't need to tree trees differently than
+        // normal permissions. Today, we need two separate loops because these BasePermission
+        // objects are stored separately.
         // Make sure there are no dangling permission trees.
         Iterator<BasePermission> it = mSettings.mPermissionTrees.values().iterator();
         while (it.hasNext()) {
             final BasePermission bp = it.next();
-            if (bp.packageSetting == null) {
+            if (bp.getSourcePackageSetting() == null) {
                 // We may not yet have parsed the package, so just see if
                 // we still know about its settings.
-                bp.packageSetting = mSettings.mPackages.get(bp.sourcePackage);
+                bp.setSourcePackageSetting(mSettings.mPackages.get(bp.getSourcePackageName()));
             }
-            if (bp.packageSetting == null) {
-                Slog.w(TAG, "Removing dangling permission tree: " + bp.name
-                        + " from package " + bp.sourcePackage);
+            if (bp.getSourcePackageSetting() == null) {
+                Slog.w(TAG, "Removing dangling permission tree: " + bp.getName()
+                        + " from package " + bp.getSourcePackageName());
                 it.remove();
-            } else if (changingPkg != null && changingPkg.equals(bp.sourcePackage)) {
-                if (pkgInfo == null || !hasPermission(pkgInfo, bp.name)) {
-                    Slog.i(TAG, "Removing old permission tree: " + bp.name
-                            + " from package " + bp.sourcePackage);
+            } else if (changingPkg != null && changingPkg.equals(bp.getSourcePackageName())) {
+                if (pkgInfo == null || !hasPermission(pkgInfo, bp.getName())) {
+                    Slog.i(TAG, "Removing old permission tree: " + bp.getName()
+                            + " from package " + bp.getSourcePackageName());
                     flags |= UPDATE_PERMISSIONS_ALL;
                     it.remove();
                 }
@@ -12834,40 +12140,28 @@
 
         // Make sure all dynamic permissions have been assigned to a package,
         // and make sure there are no dangling permissions.
-        it = mSettings.mPermissions.values().iterator();
-        while (it.hasNext()) {
-            final BasePermission bp = it.next();
-            if (bp.type == BasePermission.TYPE_DYNAMIC) {
-                if (DEBUG_SETTINGS) Log.v(TAG, "Dynamic permission: name="
-                        + bp.name + " pkg=" + bp.sourcePackage
-                        + " info=" + bp.pendingInfo);
-                if (bp.packageSetting == null && bp.pendingInfo != null) {
-                    final BasePermission tree = findPermissionTreeLP(bp.name);
-                    if (tree != null && tree.perm != null) {
-                        bp.packageSetting = tree.packageSetting;
-                        bp.perm = new PackageParser.Permission(tree.perm.owner,
-                                new PermissionInfo(bp.pendingInfo));
-                        bp.perm.info.packageName = tree.perm.info.packageName;
-                        bp.perm.info.name = bp.name;
-                        bp.uid = tree.uid;
-                    }
-                }
+        final Iterator<BasePermission> permissionIter =
+                mPermissionManager.getPermissionIteratorTEMP();
+        while (permissionIter.hasNext()) {
+            final BasePermission bp = permissionIter.next();
+            if (bp.isDynamic()) {
+                bp.updateDynamicPermission(mSettings.mPermissionTrees);
             }
-            if (bp.packageSetting == null) {
+            if (bp.getSourcePackageSetting() == null) {
                 // We may not yet have parsed the package, so just see if
                 // we still know about its settings.
-                bp.packageSetting = mSettings.mPackages.get(bp.sourcePackage);
+                bp.setSourcePackageSetting(mSettings.mPackages.get(bp.getSourcePackageName()));
             }
-            if (bp.packageSetting == null) {
-                Slog.w(TAG, "Removing dangling permission: " + bp.name
-                        + " from package " + bp.sourcePackage);
-                it.remove();
-            } else if (changingPkg != null && changingPkg.equals(bp.sourcePackage)) {
-                if (pkgInfo == null || !hasPermission(pkgInfo, bp.name)) {
-                    Slog.i(TAG, "Removing old permission: " + bp.name
-                            + " from package " + bp.sourcePackage);
+            if (bp.getSourcePackageSetting() == null) {
+                Slog.w(TAG, "Removing dangling permission: " + bp.getName()
+                        + " from package " + bp.getSourcePackageName());
+                permissionIter.remove();
+            } else if (changingPkg != null && changingPkg.equals(bp.getSourcePackageName())) {
+                if (pkgInfo == null || !hasPermission(pkgInfo, bp.getName())) {
+                    Slog.i(TAG, "Removing old permission: " + bp.getName()
+                            + " from package " + bp.getSourcePackageName());
                     flags |= UPDATE_PERMISSIONS_ALL;
-                    it.remove();
+                    permissionIter.remove();
                 }
             }
         }
@@ -12936,8 +12230,9 @@
                 // the runtime ones are written only if changed. The only cases of
                 // changed runtime permissions here are promotion of an install to
                 // runtime and revocation of a runtime from a shared user.
-                changedRuntimePermissionUserIds = revokeUnusedSharedUserPermissionsLPw(
-                        ps.sharedUser, UserManagerService.getInstance().getUserIds());
+                changedRuntimePermissionUserIds =
+                        mPermissionManager.revokeUnusedSharedUserPermissions(
+                                ps.sharedUser, UserManagerService.getInstance().getUserIds());
                 if (!ArrayUtils.isEmpty(changedRuntimePermissionUserIds)) {
                     runtimePermissionsRevoked = true;
                 }
@@ -12949,7 +12244,7 @@
         final int N = pkg.requestedPermissions.size();
         for (int i=0; i<N; i++) {
             final String name = pkg.requestedPermissions.get(i);
-            final BasePermission bp = mSettings.mPermissions.get(name);
+            final BasePermission bp = (BasePermission) mPermissionManager.getPermissionTEMP(name);
             final boolean appSupportsRuntimePermissions = pkg.applicationInfo.targetSdkVersion
                     >= Build.VERSION_CODES.M;
 
@@ -12957,7 +12252,7 @@
                 Log.i(TAG, "Package " + pkg.packageName + " checking " + name + ": " + bp);
             }
 
-            if (bp == null || bp.packageSetting == null) {
+            if (bp == null || bp.getSourcePackageSetting() == null) {
                 if (packageOfInterest == null || packageOfInterest.equals(pkg.packageName)) {
                     if (DEBUG_PERMISSIONS) {
                         Slog.i(TAG, "Unknown permission " + name
@@ -12971,7 +12266,7 @@
             // Limit ephemeral apps to ephemeral allowed permissions.
             if (pkg.applicationInfo.isInstantApp() && !bp.isInstant()) {
                 if (DEBUG_PERMISSIONS) {
-                    Log.i(TAG, "Denying non-ephemeral permission " + bp.name + " for package "
+                    Log.i(TAG, "Denying non-ephemeral permission " + bp.getName() + " for package "
                             + pkg.packageName);
                 }
                 continue;
@@ -12979,64 +12274,57 @@
 
             if (bp.isRuntimeOnly() && !appSupportsRuntimePermissions) {
                 if (DEBUG_PERMISSIONS) {
-                    Log.i(TAG, "Denying runtime-only permission " + bp.name + " for package "
+                    Log.i(TAG, "Denying runtime-only permission " + bp.getName() + " for package "
                             + pkg.packageName);
                 }
                 continue;
             }
 
-            final String perm = bp.name;
+            final String perm = bp.getName();
             boolean allowedSig = false;
             int grant = GRANT_DENIED;
 
             // Keep track of app op permissions.
-            if ((bp.protectionLevel & PermissionInfo.PROTECTION_FLAG_APPOP) != 0) {
-                ArraySet<String> pkgs = mAppOpPermissionPackages.get(bp.name);
+            if (bp.isAppOp()) {
+                ArraySet<String> pkgs = mAppOpPermissionPackages.get(perm);
                 if (pkgs == null) {
                     pkgs = new ArraySet<>();
-                    mAppOpPermissionPackages.put(bp.name, pkgs);
+                    mAppOpPermissionPackages.put(perm, pkgs);
                 }
                 pkgs.add(pkg.packageName);
             }
 
-            final int level = bp.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE;
-            switch (level) {
-                case PermissionInfo.PROTECTION_NORMAL: {
-                    // For all apps normal permissions are install time ones.
+            if (bp.isNormal()) {
+                // For all apps normal permissions are install time ones.
+                grant = GRANT_INSTALL;
+            } else if (bp.isRuntime()) {
+                // If a permission review is required for legacy apps we represent
+                // their permissions as always granted runtime ones since we need
+                // to keep the review required permission flag per user while an
+                // install permission's state is shared across all users.
+                if (!appSupportsRuntimePermissions && !mPermissionReviewRequired) {
+                    // For legacy apps dangerous permissions are install time ones.
                     grant = GRANT_INSTALL;
-                } break;
-
-                case PermissionInfo.PROTECTION_DANGEROUS: {
-                    // If a permission review is required for legacy apps we represent
-                    // their permissions as always granted runtime ones since we need
-                    // to keep the review required permission flag per user while an
-                    // install permission's state is shared across all users.
-                    if (!appSupportsRuntimePermissions && !mPermissionReviewRequired) {
-                        // For legacy apps dangerous permissions are install time ones.
-                        grant = GRANT_INSTALL;
-                    } else if (origPermissions.hasInstallPermission(bp.name)) {
-                        // For legacy apps that became modern, install becomes runtime.
-                        grant = GRANT_UPGRADE;
-                    } else if (mPromoteSystemApps
-                            && isSystemApp(ps)
-                            && mExistingSystemPackages.contains(ps.name)) {
-                        // For legacy system apps, install becomes runtime.
-                        // We cannot check hasInstallPermission() for system apps since those
-                        // permissions were granted implicitly and not persisted pre-M.
-                        grant = GRANT_UPGRADE;
-                    } else {
-                        // For modern apps keep runtime permissions unchanged.
-                        grant = GRANT_RUNTIME;
-                    }
-                } break;
-
-                case PermissionInfo.PROTECTION_SIGNATURE: {
-                    // For all apps signature permissions are install time ones.
-                    allowedSig = grantSignaturePermission(perm, pkg, bp, origPermissions);
-                    if (allowedSig) {
-                        grant = GRANT_INSTALL;
-                    }
-                } break;
+                } else if (origPermissions.hasInstallPermission(bp.getName())) {
+                    // For legacy apps that became modern, install becomes runtime.
+                    grant = GRANT_UPGRADE;
+                } else if (mPromoteSystemApps
+                        && isSystemApp(ps)
+                        && mExistingSystemPackages.contains(ps.name)) {
+                    // For legacy system apps, install becomes runtime.
+                    // We cannot check hasInstallPermission() for system apps since those
+                    // permissions were granted implicitly and not persisted pre-M.
+                    grant = GRANT_UPGRADE;
+                } else {
+                    // For modern apps keep runtime permissions unchanged.
+                    grant = GRANT_RUNTIME;
+                }
+            } else if (bp.isSignature()) {
+                // For all apps signature permissions are install time ones.
+                allowedSig = grantSignaturePermission(perm, pkg, bp, origPermissions);
+                if (allowedSig) {
+                    grant = GRANT_INSTALL;
+                }
             }
 
             if (DEBUG_PERMISSIONS) {
@@ -13065,7 +12353,7 @@
                         // for legacy apps
                         for (int userId : UserManagerService.getInstance().getUserIds()) {
                             if (origPermissions.getRuntimePermissionState(
-                                    bp.name, userId) != null) {
+                                    perm, userId) != null) {
                                 // Revoke the runtime permission and clear the flags.
                                 origPermissions.revokeRuntimePermission(bp, userId);
                                 origPermissions.updatePermissionFlags(bp, userId,
@@ -13086,10 +12374,10 @@
                         // Grant previously granted runtime permissions.
                         for (int userId : UserManagerService.getInstance().getUserIds()) {
                             PermissionState permissionState = origPermissions
-                                    .getRuntimePermissionState(bp.name, userId);
+                                    .getRuntimePermissionState(perm, userId);
                             int flags = permissionState != null
                                     ? permissionState.getFlags() : 0;
-                            if (origPermissions.hasRuntimePermission(bp.name, userId)) {
+                            if (origPermissions.hasRuntimePermission(perm, userId)) {
                                 // Don't propagate the permission in a permission review mode if
                                 // the former was revoked, i.e. marked to not propagate on upgrade.
                                 // Note that in a permission review mode install permissions are
@@ -13132,7 +12420,7 @@
                                 // permissions as these are the only ones the platform knows
                                 // how to disable the API to simulate revocation as legacy
                                 // apps don't expect to run with revoked permissions.
-                                if (PLATFORM_PACKAGE_NAME.equals(bp.sourcePackage)) {
+                                if (PLATFORM_PACKAGE_NAME.equals(bp.getSourcePackageName())) {
                                     if ((flags & FLAG_PERMISSION_REVIEW_REQUIRED) == 0) {
                                         flags |= FLAG_PERMISSION_REVIEW_REQUIRED;
                                         // We changed the flags, hence have to write.
@@ -13155,7 +12443,7 @@
                     case GRANT_UPGRADE: {
                         // Grant runtime permissions for a previously held install permission.
                         PermissionState permissionState = origPermissions
-                                .getInstallPermissionState(bp.name);
+                                .getInstallPermissionState(perm);
                         final int flags = permissionState != null ? permissionState.getFlags() : 0;
 
                         if (origPermissions.revokeInstallPermission(bp)
@@ -13203,10 +12491,10 @@
                     changedInstallPermission = true;
                     Slog.i(TAG, "Un-granting permission " + perm
                             + " from package " + pkg.packageName
-                            + " (protectionLevel=" + bp.protectionLevel
+                            + " (protectionLevel=" + bp.getProtectionLevel()
                             + " flags=0x" + Integer.toHexString(pkg.applicationInfo.flags)
                             + ")");
-                } else if ((bp.protectionLevel&PermissionInfo.PROTECTION_FLAG_APPOP) == 0) {
+                } else if (bp.isAppOp()) {
                     // Don't print warning for app op permissions, since it is fine for them
                     // not to be granted, there is a UI for the user to decide.
                     if (DEBUG_PERMISSIONS
@@ -13214,7 +12502,7 @@
                                     || packageOfInterest.equals(pkg.packageName))) {
                         Slog.i(TAG, "Not granting permission " + perm
                                 + " to package " + pkg.packageName
-                                + " (protectionLevel=" + bp.protectionLevel
+                                + " (protectionLevel=" + bp.getProtectionLevel()
                                 + " flags=0x" + Integer.toHexString(pkg.applicationInfo.flags)
                                 + ")");
                     }
@@ -13274,13 +12562,11 @@
 
     private boolean grantSignaturePermission(String perm, PackageParser.Package pkg,
             BasePermission bp, PermissionsState origPermissions) {
-        boolean oemPermission = (bp.protectionLevel
-                & PermissionInfo.PROTECTION_FLAG_OEM) != 0;
-        boolean privilegedPermission = (bp.protectionLevel
-                & PermissionInfo.PROTECTION_FLAG_PRIVILEGED) != 0;
+        boolean oemPermission = bp.isOEM();
+        boolean privilegedPermission = bp.isPrivileged();
         boolean privappPermissionsDisable =
                 RoSystemProperties.CONTROL_PRIVAPP_PERMISSIONS_DISABLE;
-        boolean platformPermission = PLATFORM_PACKAGE_NAME.equals(bp.sourcePackage);
+        boolean platformPermission = PLATFORM_PACKAGE_NAME.equals(bp.getSourcePackageName());
         boolean platformPackage = PLATFORM_PACKAGE_NAME.equals(pkg.packageName);
         if (!privappPermissionsDisable && privilegedPermission && pkg.isPrivilegedApp()
                 && !platformPackage && platformPermission) {
@@ -13309,7 +12595,7 @@
             }
         }
         boolean allowed = (compareSignatures(
-                bp.packageSetting.signatures.mSignatures, pkg.mSignatures)
+                bp.getSourcePackageSetting().signatures.mSignatures, pkg.mSignatures)
                         == PackageManager.SIGNATURE_MATCH)
                 || (compareSignatures(mPlatformPackage.mSignatures, pkg.mSignatures)
                         == PackageManager.SIGNATURE_MATCH);
@@ -13386,39 +12672,37 @@
             }
         }
         if (!allowed) {
-            if (!allowed && (bp.protectionLevel
-                    & PermissionInfo.PROTECTION_FLAG_PRE23) != 0
+            if (!allowed
+                    && bp.isPre23()
                     && pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M) {
                 // If this was a previously normal/dangerous permission that got moved
                 // to a system permission as part of the runtime permission redesign, then
                 // we still want to blindly grant it to old apps.
                 allowed = true;
             }
-            if (!allowed && (bp.protectionLevel & PermissionInfo.PROTECTION_FLAG_INSTALLER) != 0
+            if (!allowed && bp.isInstaller()
                     && pkg.packageName.equals(mRequiredInstallerPackage)) {
                 // If this permission is to be granted to the system installer and
                 // this app is an installer, then it gets the permission.
                 allowed = true;
             }
-            if (!allowed && (bp.protectionLevel & PermissionInfo.PROTECTION_FLAG_VERIFIER) != 0
+            if (!allowed && bp.isVerifier()
                     && pkg.packageName.equals(mRequiredVerifierPackage)) {
                 // If this permission is to be granted to the system verifier and
                 // this app is a verifier, then it gets the permission.
                 allowed = true;
             }
-            if (!allowed && (bp.protectionLevel
-                    & PermissionInfo.PROTECTION_FLAG_PREINSTALLED) != 0
+            if (!allowed && bp.isPreInstalled()
                     && isSystemApp(pkg)) {
                 // Any pre-installed system app is allowed to get this permission.
                 allowed = true;
             }
-            if (!allowed && (bp.protectionLevel
-                    & PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) != 0) {
+            if (!allowed && bp.isDevelopment()) {
                 // For development permissions, a development permission
                 // is granted only if it was already granted.
                 allowed = origPermissions.hasInstallPermission(perm);
             }
-            if (!allowed && (bp.protectionLevel & PermissionInfo.PROTECTION_FLAG_SETUP) != 0
+            if (!allowed && bp.isSetup()
                     && pkg.packageName.equals(mSetupWizardPackage)) {
                 // If this permission is to be granted to the system setup wizard and
                 // this app is a setup wizard, then it gets the permission.
@@ -14719,7 +14003,7 @@
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.INSTALL_PACKAGES, null);
 
         final int callingUid = Binder.getCallingUid();
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 true /* requireFullPermission */, true /* checkShell */, "installPackageAsUser");
 
         if (isUserRestricted(userId, UserManager.DISALLOW_INSTALL_APPS)) {
@@ -14847,7 +14131,7 @@
         return installReason;
     }
 
-    void installStage(String packageName, File stagedDir, String stagedCid,
+    void installStage(String packageName, File stagedDir,
             IPackageInstallObserver2 observer, PackageInstaller.SessionParams sessionParams,
             String installerPackageName, int installerUid, UserHandle user,
             Certificate[][] certificates) {
@@ -14860,12 +14144,7 @@
                 sessionParams.originatingUri, sessionParams.referrerUri,
                 sessionParams.originatingUid, installerUid);
 
-        final OriginInfo origin;
-        if (stagedDir != null) {
-            origin = OriginInfo.fromStagedFile(stagedDir);
-        } else {
-            origin = OriginInfo.fromStagedContainer(stagedCid);
-        }
+        final OriginInfo origin = OriginInfo.fromStagedFile(stagedDir);
 
         final Message msg = mHandler.obtainMessage(INIT_COPY);
         final int installReason = fixUpInstallReason(installerPackageName, installerUid,
@@ -14963,7 +14242,7 @@
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USERS, null);
         PackageSetting pkgSetting;
         final int callingUid = Binder.getCallingUid();
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 true /* requireFullPermission */, true /* checkShell */,
                 "setApplicationHiddenSetting for user " + userId);
 
@@ -15065,7 +14344,7 @@
     public boolean getApplicationHiddenSettingAsUser(String packageName, int userId) {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USERS, null);
         final int callingUid = Binder.getCallingUid();
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 true /* requireFullPermission */, false /* checkShell */,
                 "getApplicationHidden for user " + userId);
         PackageSetting ps;
@@ -15097,7 +14376,7 @@
                 null);
         PackageSetting pkgSetting;
         final int callingUid = Binder.getCallingUid();
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 true /* requireFullPermission */, true /* checkShell */,
                 "installExistingPackage for user " + userId);
         if (isUserRestricted(userId, UserManager.DISALLOW_INSTALL_APPS)) {
@@ -15202,7 +14481,7 @@
             int userId) {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USERS, null);
         final int callingUid = Binder.getCallingUid();
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 true /* requireFullPermission */, true /* checkShell */,
                 "setPackagesSuspended for user " + userId);
 
@@ -15263,7 +14542,7 @@
     @Override
     public boolean isPackageSuspendedForUser(String packageName, int userId) {
         final int callingUid = Binder.getCallingUid();
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 true /* requireFullPermission */, false /* checkShell */,
                 "isPackageSuspendedForUser for user " + userId);
         synchronized (mPackages) {
@@ -15710,7 +14989,7 @@
         synchronized (mPackages) {
             boolean result = mSettings.setDefaultBrowserPackageNameLPw(packageName, userId);
             if (packageName != null) {
-                mDefaultPermissionPolicy.grantDefaultPermissionsToDefaultBrowserLPr(
+                mDefaultPermissionPolicy.grantDefaultPermissionsToDefaultBrowser(
                         packageName, userId);
             }
             return result;
@@ -16065,7 +15344,6 @@
          * file, or a cluster directory. This location may be untrusted.
          */
         final File file;
-        final String cid;
 
         /**
          * Flag indicating that {@link #file} or {@link #cid} has already been
@@ -16084,35 +15362,27 @@
         final File resolvedFile;
 
         static OriginInfo fromNothing() {
-            return new OriginInfo(null, null, false, false);
+            return new OriginInfo(null, false, false);
         }
 
         static OriginInfo fromUntrustedFile(File file) {
-            return new OriginInfo(file, null, false, false);
+            return new OriginInfo(file, false, false);
         }
 
         static OriginInfo fromExistingFile(File file) {
-            return new OriginInfo(file, null, false, true);
+            return new OriginInfo(file, false, true);
         }
 
         static OriginInfo fromStagedFile(File file) {
-            return new OriginInfo(file, null, true, false);
+            return new OriginInfo(file, true, false);
         }
 
-        static OriginInfo fromStagedContainer(String cid) {
-            return new OriginInfo(null, cid, true, false);
-        }
-
-        private OriginInfo(File file, String cid, boolean staged, boolean existing) {
+        private OriginInfo(File file, boolean staged, boolean existing) {
             this.file = file;
-            this.cid = cid;
             this.staged = staged;
             this.existing = existing;
 
-            if (cid != null) {
-                resolvedPath = PackageHelper.getSdDir(cid);
-                resolvedFile = new File(resolvedPath);
-            } else if (file != null) {
+            if (file != null) {
                 resolvedPath = file.getAbsolutePath();
                 resolvedFile = file;
             } else {
@@ -16205,7 +15475,7 @@
         @Override
         public String toString() {
             return "InstallParams{" + Integer.toHexString(System.identityHashCode(this))
-                    + " file=" + origin.file + " cid=" + origin.cid + "}";
+                    + " file=" + origin.file + "}";
         }
 
         private int installLocationPolicy(PackageInfoLite pkgLite) {
@@ -16316,9 +15586,6 @@
                 if (origin.file != null) {
                     installFlags |= PackageManager.INSTALL_INTERNAL;
                     installFlags &= ~PackageManager.INSTALL_EXTERNAL;
-                } else if (origin.cid != null) {
-                    installFlags |= PackageManager.INSTALL_EXTERNAL;
-                    installFlags &= ~PackageManager.INSTALL_INTERNAL;
                 } else {
                     throw new IllegalStateException("Invalid stage location");
                 }
@@ -16356,7 +15623,7 @@
                             Environment.getDataDirectory());
 
                     final long sizeBytes = mContainerService.calculateInstalledSize(
-                            origin.resolvedPath, isForwardLocked(), packageAbiOverride);
+                            origin.resolvedPath, packageAbiOverride);
 
                     try {
                         mInstaller.freeCache(null, sizeBytes + lowThreshold, 0, 0);
@@ -16593,43 +15860,11 @@
             mArgs = createInstallArgs(this);
             mRet = PackageManager.INSTALL_FAILED_INTERNAL_ERROR;
         }
-
-        public boolean isForwardLocked() {
-            return (installFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0;
-        }
-    }
-
-    /**
-     * Used during creation of InstallArgs
-     *
-     * @param installFlags package installation flags
-     * @return true if should be installed on external storage
-     */
-    private static boolean installOnExternalAsec(int installFlags) {
-        if ((installFlags & PackageManager.INSTALL_INTERNAL) != 0) {
-            return false;
-        }
-        if ((installFlags & PackageManager.INSTALL_EXTERNAL) != 0) {
-            return true;
-        }
-        return false;
-    }
-
-    /**
-     * Used during creation of InstallArgs
-     *
-     * @param installFlags package installation flags
-     * @return true if should be installed as forward locked
-     */
-    private static boolean installForwardLocked(int installFlags) {
-        return (installFlags & PackageManager.INSTALL_FORWARD_LOCK) != 0;
     }
 
     private InstallArgs createInstallArgs(InstallParams params) {
         if (params.move != null) {
             return new MoveInstallArgs(params);
-        } else if (installOnExternalAsec(params.installFlags) || params.isForwardLocked()) {
-            return new AsecInstallArgs(params);
         } else {
             return new FileInstallArgs(params);
         }
@@ -16641,27 +15876,7 @@
      */
     private InstallArgs createInstallArgsForExisting(int installFlags, String codePath,
             String resourcePath, String[] instructionSets) {
-        final boolean isInAsec;
-        if (installOnExternalAsec(installFlags)) {
-            /* Apps on SD card are always in ASEC containers. */
-            isInAsec = true;
-        } else if (installForwardLocked(installFlags)
-                && !codePath.startsWith(mDrmAppPrivateInstallDir.getAbsolutePath())) {
-            /*
-             * Forward-locked apps are only in ASEC containers if they're the
-             * new style
-             */
-            isInAsec = true;
-        } else {
-            isInAsec = false;
-        }
-
-        if (isInAsec) {
-            return new AsecInstallArgs(codePath, instructionSets,
-                    installOnExternalAsec(installFlags), installForwardLocked(installFlags));
-        } else {
-            return new FileInstallArgs(codePath, resourcePath, instructionSets);
-        }
+        return new FileInstallArgs(codePath, resourcePath, instructionSets);
     }
 
     static abstract class InstallArgs {
@@ -16995,11 +16210,6 @@
         }
     }
 
-    private boolean isAsecExternal(String cid) {
-        final String asecPath = PackageHelper.getSdFilesystem(cid);
-        return !asecPath.startsWith(mAsecInternalPath);
-    }
-
     private static void maybeThrowExceptionForMultiArchCopy(String message, int copyRet) throws
             PackageManagerException {
         if (copyRet < 0) {
@@ -17022,308 +16232,6 @@
     }
 
     /**
-     * Logic to handle installation of ASEC applications, including copying and
-     * renaming logic.
-     */
-    class AsecInstallArgs extends InstallArgs {
-        static final String RES_FILE_NAME = "pkg.apk";
-        static final String PUBLIC_RES_FILE_NAME = "res.zip";
-
-        String cid;
-        String packagePath;
-        String resourcePath;
-
-        /** New install */
-        AsecInstallArgs(InstallParams params) {
-            super(params.origin, params.move, params.observer, params.installFlags,
-                    params.installerPackageName, params.volumeUuid,
-                    params.getUser(), null /* instruction sets */, params.packageAbiOverride,
-                    params.grantedRuntimePermissions,
-                    params.traceMethod, params.traceCookie, params.certificates,
-                    params.installReason);
-        }
-
-        /** Existing install */
-        AsecInstallArgs(String fullCodePath, String[] instructionSets,
-                        boolean isExternal, boolean isForwardLocked) {
-            super(OriginInfo.fromNothing(), null, null, (isExternal ? INSTALL_EXTERNAL : 0)
-                    | (isForwardLocked ? INSTALL_FORWARD_LOCK : 0), null, null, null,
-                    instructionSets, null, null, null, 0, null /*certificates*/,
-                    PackageManager.INSTALL_REASON_UNKNOWN);
-            // Hackily pretend we're still looking at a full code path
-            if (!fullCodePath.endsWith(RES_FILE_NAME)) {
-                fullCodePath = new File(fullCodePath, RES_FILE_NAME).getAbsolutePath();
-            }
-
-            // Extract cid from fullCodePath
-            int eidx = fullCodePath.lastIndexOf("/");
-            String subStr1 = fullCodePath.substring(0, eidx);
-            int sidx = subStr1.lastIndexOf("/");
-            cid = subStr1.substring(sidx+1, eidx);
-            setMountPath(subStr1);
-        }
-
-        AsecInstallArgs(String cid, String[] instructionSets, boolean isForwardLocked) {
-            super(OriginInfo.fromNothing(), null, null, (isAsecExternal(cid) ? INSTALL_EXTERNAL : 0)
-                    | (isForwardLocked ? INSTALL_FORWARD_LOCK : 0), null, null, null,
-                    instructionSets, null, null, null, 0, null /*certificates*/,
-                    PackageManager.INSTALL_REASON_UNKNOWN);
-            this.cid = cid;
-            setMountPath(PackageHelper.getSdDir(cid));
-        }
-
-        void createCopyFile() {
-            cid = mInstallerService.allocateExternalStageCidLegacy();
-        }
-
-        int copyApk(IMediaContainerService imcs, boolean temp) throws RemoteException {
-            if (origin.staged && origin.cid != null) {
-                if (DEBUG_INSTALL) Slog.d(TAG, origin.cid + " already staged; skipping copy");
-                cid = origin.cid;
-                setMountPath(PackageHelper.getSdDir(cid));
-                return PackageManager.INSTALL_SUCCEEDED;
-            }
-
-            if (temp) {
-                createCopyFile();
-            } else {
-                /*
-                 * Pre-emptively destroy the container since it's destroyed if
-                 * copying fails due to it existing anyway.
-                 */
-                PackageHelper.destroySdDir(cid);
-            }
-
-            final String newMountPath = imcs.copyPackageToContainer(
-                    origin.file.getAbsolutePath(), cid, getEncryptKey(), isExternalAsec(),
-                    isFwdLocked(), deriveAbiOverride(abiOverride, null /* settings */));
-
-            if (newMountPath != null) {
-                setMountPath(newMountPath);
-                return PackageManager.INSTALL_SUCCEEDED;
-            } else {
-                return PackageManager.INSTALL_FAILED_CONTAINER_ERROR;
-            }
-        }
-
-        @Override
-        String getCodePath() {
-            return packagePath;
-        }
-
-        @Override
-        String getResourcePath() {
-            return resourcePath;
-        }
-
-        int doPreInstall(int status) {
-            if (status != PackageManager.INSTALL_SUCCEEDED) {
-                // Destroy container
-                PackageHelper.destroySdDir(cid);
-            } else {
-                boolean mounted = PackageHelper.isContainerMounted(cid);
-                if (!mounted) {
-                    String newMountPath = PackageHelper.mountSdDir(cid, getEncryptKey(),
-                            Process.SYSTEM_UID);
-                    if (newMountPath != null) {
-                        setMountPath(newMountPath);
-                    } else {
-                        return PackageManager.INSTALL_FAILED_CONTAINER_ERROR;
-                    }
-                }
-            }
-            return status;
-        }
-
-        boolean doRename(int status, PackageParser.Package pkg, String oldCodePath) {
-            String newCacheId = getNextCodePath(oldCodePath, pkg.packageName, "/" + RES_FILE_NAME);
-            String newMountPath = null;
-            if (PackageHelper.isContainerMounted(cid)) {
-                // Unmount the container
-                if (!PackageHelper.unMountSdDir(cid)) {
-                    Slog.i(TAG, "Failed to unmount " + cid + " before renaming");
-                    return false;
-                }
-            }
-            if (!PackageHelper.renameSdDir(cid, newCacheId)) {
-                Slog.e(TAG, "Failed to rename " + cid + " to " + newCacheId +
-                        " which might be stale. Will try to clean up.");
-                // Clean up the stale container and proceed to recreate.
-                if (!PackageHelper.destroySdDir(newCacheId)) {
-                    Slog.e(TAG, "Very strange. Cannot clean up stale container " + newCacheId);
-                    return false;
-                }
-                // Successfully cleaned up stale container. Try to rename again.
-                if (!PackageHelper.renameSdDir(cid, newCacheId)) {
-                    Slog.e(TAG, "Failed to rename " + cid + " to " + newCacheId
-                            + " inspite of cleaning it up.");
-                    return false;
-                }
-            }
-            if (!PackageHelper.isContainerMounted(newCacheId)) {
-                Slog.w(TAG, "Mounting container " + newCacheId);
-                newMountPath = PackageHelper.mountSdDir(newCacheId,
-                        getEncryptKey(), Process.SYSTEM_UID);
-            } else {
-                newMountPath = PackageHelper.getSdDir(newCacheId);
-            }
-            if (newMountPath == null) {
-                Slog.w(TAG, "Failed to get cache path for  " + newCacheId);
-                return false;
-            }
-            Log.i(TAG, "Succesfully renamed " + cid +
-                    " to " + newCacheId +
-                    " at new path: " + newMountPath);
-            cid = newCacheId;
-
-            final File beforeCodeFile = new File(packagePath);
-            setMountPath(newMountPath);
-            final File afterCodeFile = new File(packagePath);
-
-            // Reflect the rename in scanned details
-            pkg.setCodePath(afterCodeFile.getAbsolutePath());
-            pkg.setBaseCodePath(FileUtils.rewriteAfterRename(beforeCodeFile,
-                    afterCodeFile, pkg.baseCodePath));
-            pkg.setSplitCodePaths(FileUtils.rewriteAfterRename(beforeCodeFile,
-                    afterCodeFile, pkg.splitCodePaths));
-
-            // Reflect the rename in app info
-            pkg.setApplicationVolumeUuid(pkg.volumeUuid);
-            pkg.setApplicationInfoCodePath(pkg.codePath);
-            pkg.setApplicationInfoBaseCodePath(pkg.baseCodePath);
-            pkg.setApplicationInfoSplitCodePaths(pkg.splitCodePaths);
-            pkg.setApplicationInfoResourcePath(pkg.codePath);
-            pkg.setApplicationInfoBaseResourcePath(pkg.baseCodePath);
-            pkg.setApplicationInfoSplitResourcePaths(pkg.splitCodePaths);
-
-            return true;
-        }
-
-        private void setMountPath(String mountPath) {
-            final File mountFile = new File(mountPath);
-
-            final File monolithicFile = new File(mountFile, RES_FILE_NAME);
-            if (monolithicFile.exists()) {
-                packagePath = monolithicFile.getAbsolutePath();
-                if (isFwdLocked()) {
-                    resourcePath = new File(mountFile, PUBLIC_RES_FILE_NAME).getAbsolutePath();
-                } else {
-                    resourcePath = packagePath;
-                }
-            } else {
-                packagePath = mountFile.getAbsolutePath();
-                resourcePath = packagePath;
-            }
-        }
-
-        int doPostInstall(int status, int uid) {
-            if (status != PackageManager.INSTALL_SUCCEEDED) {
-                cleanUp();
-            } else {
-                final int groupOwner;
-                final String protectedFile;
-                if (isFwdLocked()) {
-                    groupOwner = UserHandle.getSharedAppGid(uid);
-                    protectedFile = RES_FILE_NAME;
-                } else {
-                    groupOwner = -1;
-                    protectedFile = null;
-                }
-
-                if (uid < Process.FIRST_APPLICATION_UID
-                        || !PackageHelper.fixSdPermissions(cid, groupOwner, protectedFile)) {
-                    Slog.e(TAG, "Failed to finalize " + cid);
-                    PackageHelper.destroySdDir(cid);
-                    return PackageManager.INSTALL_FAILED_CONTAINER_ERROR;
-                }
-
-                boolean mounted = PackageHelper.isContainerMounted(cid);
-                if (!mounted) {
-                    PackageHelper.mountSdDir(cid, getEncryptKey(), Process.myUid());
-                }
-            }
-            return status;
-        }
-
-        private void cleanUp() {
-            if (DEBUG_SD_INSTALL) Slog.i(TAG, "cleanUp");
-
-            // Destroy secure container
-            PackageHelper.destroySdDir(cid);
-        }
-
-        private List<String> getAllCodePaths() {
-            final File codeFile = new File(getCodePath());
-            if (codeFile != null && codeFile.exists()) {
-                try {
-                    final PackageLite pkg = PackageParser.parsePackageLite(codeFile, 0);
-                    return pkg.getAllCodePaths();
-                } catch (PackageParserException e) {
-                    // Ignored; we tried our best
-                }
-            }
-            return Collections.EMPTY_LIST;
-        }
-
-        void cleanUpResourcesLI() {
-            // Enumerate all code paths before deleting
-            cleanUpResourcesLI(getAllCodePaths());
-        }
-
-        private void cleanUpResourcesLI(List<String> allCodePaths) {
-            cleanUp();
-            removeDexFiles(allCodePaths, instructionSets);
-        }
-
-        String getPackageName() {
-            return getAsecPackageName(cid);
-        }
-
-        boolean doPostDeleteLI(boolean delete) {
-            if (DEBUG_SD_INSTALL) Slog.i(TAG, "doPostDeleteLI() del=" + delete);
-            final List<String> allCodePaths = getAllCodePaths();
-            boolean mounted = PackageHelper.isContainerMounted(cid);
-            if (mounted) {
-                // Unmount first
-                if (PackageHelper.unMountSdDir(cid)) {
-                    mounted = false;
-                }
-            }
-            if (!mounted && delete) {
-                cleanUpResourcesLI(allCodePaths);
-            }
-            return !mounted;
-        }
-
-        @Override
-        int doPreCopy() {
-            if (isFwdLocked()) {
-                if (!PackageHelper.fixSdPermissions(cid, getPackageUid(DEFAULT_CONTAINER_PACKAGE,
-                        MATCH_SYSTEM_ONLY, UserHandle.USER_SYSTEM), RES_FILE_NAME)) {
-                    return PackageManager.INSTALL_FAILED_CONTAINER_ERROR;
-                }
-            }
-
-            return PackageManager.INSTALL_SUCCEEDED;
-        }
-
-        @Override
-        int doPostCopy(int uid) {
-            if (isFwdLocked()) {
-                if (uid < Process.FIRST_APPLICATION_UID
-                        || !PackageHelper.fixSdPermissions(cid, UserHandle.getSharedAppGid(uid),
-                                RES_FILE_NAME)) {
-                    Slog.e(TAG, "Failed to finalize " + cid);
-                    PackageHelper.destroySdDir(cid);
-                    return PackageManager.INSTALL_FAILED_CONTAINER_ERROR;
-                }
-            }
-
-            return PackageManager.INSTALL_SUCCEEDED;
-        }
-    }
-
-    /**
      * Logic to handle movement of existing installed applications.
      */
     class MoveInstallArgs extends InstallArgs {
@@ -17627,9 +16535,9 @@
         Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
     }
 
-    private boolean shouldCheckUpgradeKeySetLP(PackageSetting oldPs, int scanFlags) {
+    private boolean shouldCheckUpgradeKeySetLP(PackageSettingBase oldPs, int scanFlags) {
         // Can't rotate keys during boot or if sharedUser.
-        if (oldPs == null || (scanFlags&SCAN_INITIAL) != 0 || oldPs.sharedUser != null
+        if (oldPs == null || (scanFlags&SCAN_INITIAL) != 0 || oldPs.isSharedUser()
                 || !oldPs.keySetData.isUsingUpgradeKeySets()) {
             return false;
         }
@@ -17649,7 +16557,7 @@
         return true;
     }
 
-    private boolean checkUpgradeKeySetLP(PackageSetting oldPS, PackageParser.Package newPkg) {
+    private boolean checkUpgradeKeySetLP(PackageSettingBase oldPS, PackageParser.Package newPkg) {
         // Upgrade keysets are being used.  Determine if new package has a superset of the
         // required keys.
         long[] upgradeKeySets = oldPS.keySetData.getUpgradeKeySets();
@@ -18225,66 +17133,6 @@
         }
     }
 
-    private int[] revokeUnusedSharedUserPermissionsLPw(SharedUserSetting su, int[] allUserIds) {
-        // Collect all used permissions in the UID
-        ArraySet<String> usedPermissions = new ArraySet<>();
-        final int packageCount = su.packages.size();
-        for (int i = 0; i < packageCount; i++) {
-            PackageSetting ps = su.packages.valueAt(i);
-            if (ps.pkg == null) {
-                continue;
-            }
-            final int requestedPermCount = ps.pkg.requestedPermissions.size();
-            for (int j = 0; j < requestedPermCount; j++) {
-                String permission = ps.pkg.requestedPermissions.get(j);
-                BasePermission bp = mSettings.mPermissions.get(permission);
-                if (bp != null) {
-                    usedPermissions.add(permission);
-                }
-            }
-        }
-
-        PermissionsState permissionsState = su.getPermissionsState();
-        // Prune install permissions
-        List<PermissionState> installPermStates = permissionsState.getInstallPermissionStates();
-        final int installPermCount = installPermStates.size();
-        for (int i = installPermCount - 1; i >= 0;  i--) {
-            PermissionState permissionState = installPermStates.get(i);
-            if (!usedPermissions.contains(permissionState.getName())) {
-                BasePermission bp = mSettings.mPermissions.get(permissionState.getName());
-                if (bp != null) {
-                    permissionsState.revokeInstallPermission(bp);
-                    permissionsState.updatePermissionFlags(bp, UserHandle.USER_ALL,
-                            PackageManager.MASK_PERMISSION_FLAGS, 0);
-                }
-            }
-        }
-
-        int[] runtimePermissionChangedUserIds = EmptyArray.INT;
-
-        // Prune runtime permissions
-        for (int userId : allUserIds) {
-            List<PermissionState> runtimePermStates = permissionsState
-                    .getRuntimePermissionStates(userId);
-            final int runtimePermCount = runtimePermStates.size();
-            for (int i = runtimePermCount - 1; i >= 0; i--) {
-                PermissionState permissionState = runtimePermStates.get(i);
-                if (!usedPermissions.contains(permissionState.getName())) {
-                    BasePermission bp = mSettings.mPermissions.get(permissionState.getName());
-                    if (bp != null) {
-                        permissionsState.revokeRuntimePermission(bp, userId);
-                        permissionsState.updatePermissionFlags(bp, userId,
-                                PackageManager.MASK_PERMISSION_FLAGS, 0);
-                        runtimePermissionChangedUserIds = ArrayUtils.appendInt(
-                                runtimePermissionChangedUserIds, userId);
-                    }
-                }
-            }
-        }
-
-        return runtimePermissionChangedUserIds;
-    }
-
     private void updateSettingsLI(PackageParser.Package newPackage, String installerPackageName,
             int[] allUsers, PackageInstalledInfo res, UserHandle user, int installReason) {
         // Update the parent package setting
@@ -18685,8 +17533,9 @@
 
             int N = pkg.permissions.size();
             for (int i = N-1; i >= 0; i--) {
-                PackageParser.Permission perm = pkg.permissions.get(i);
-                BasePermission bp = mSettings.mPermissions.get(perm.info.name);
+                final PackageParser.Permission perm = pkg.permissions.get(i);
+                final BasePermission bp =
+                        (BasePermission) mPermissionManager.getPermissionTEMP(perm.info.name);
 
                 // Don't allow anyone but the system to define ephemeral permissions.
                 if ((perm.info.protectionLevel & PermissionInfo.PROTECTION_FLAG_INSTANT) != 0
@@ -18703,25 +17552,26 @@
                     // also includes the "updating the same package" case, of course.
                     // "updating same package" could also involve key-rotation.
                     final boolean sigsOk;
-                    if (bp.sourcePackage.equals(pkg.packageName)
-                            && (bp.packageSetting instanceof PackageSetting)
-                            && (shouldCheckUpgradeKeySetLP((PackageSetting) bp.packageSetting,
+                    final String sourcePackageName = bp.getSourcePackageName();
+                    final PackageSettingBase sourcePackageSetting = bp.getSourcePackageSetting();
+                    if (sourcePackageName.equals(pkg.packageName)
+                            && (shouldCheckUpgradeKeySetLP(sourcePackageSetting,
                                     scanFlags))) {
-                        sigsOk = checkUpgradeKeySetLP((PackageSetting) bp.packageSetting, pkg);
+                        sigsOk = checkUpgradeKeySetLP(sourcePackageSetting, pkg);
                     } else {
-                        sigsOk = compareSignatures(bp.packageSetting.signatures.mSignatures,
+                        sigsOk = compareSignatures(sourcePackageSetting.signatures.mSignatures,
                                 pkg.mSignatures) == PackageManager.SIGNATURE_MATCH;
                     }
                     if (!sigsOk) {
                         // If the owning package is the system itself, we log but allow
                         // install to proceed; we fail the install on all other permission
                         // redefinitions.
-                        if (!bp.sourcePackage.equals("android")) {
+                        if (!sourcePackageName.equals("android")) {
                             res.setError(INSTALL_FAILED_DUPLICATE_PERMISSION, "Package "
                                     + pkg.packageName + " attempting to redeclare permission "
-                                    + perm.info.name + " already owned by " + bp.sourcePackage);
+                                    + perm.info.name + " already owned by " + sourcePackageName);
                             res.origPermission = perm.info.name;
-                            res.origPackage = bp.sourcePackage;
+                            res.origPackage = sourcePackageName;
                             return;
                         } else {
                             Slog.w(TAG, "Package " + pkg.packageName
@@ -18740,7 +17590,7 @@
                                 Slog.w(TAG, "Package " + pkg.packageName + " trying to change a "
                                         + "non-runtime permission " + perm.info.name
                                         + " to runtime; keeping old protection level");
-                                perm.info.protectionLevel = bp.protectionLevel;
+                                perm.info.protectionLevel = bp.getProtectionLevel();
                             }
                         }
                     }
@@ -18855,7 +17705,13 @@
         // TODO: Layering violation
         BackgroundDexOptService.notifyPackageChanged(pkg.packageName);
 
-        startIntentFilterVerifications(args.user.getIdentifier(), replace, pkg);
+        if (!instantApp) {
+            startIntentFilterVerifications(args.user.getIdentifier(), replace, pkg);
+        } else {
+            if (DEBUG_DOMAIN_VERIFICATION) {
+                Slog.d(TAG, "Not verifying instant app install for app links: " + pkgName);
+            }
+        }
 
         try (PackageFreezer freezer = freezePackageForInstall(pkgName, installFlags,
                 "installPackageLI")) {
@@ -20440,7 +19296,7 @@
                 android.Manifest.permission.CLEAR_APP_USER_DATA, null);
 
         final int callingUid = Binder.getCallingUid();
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 true /* requireFullPermission */, false /* checkShell */, "clear application data");
 
         final PackageSetting ps = mSettings.getPackageLPr(packageName);
@@ -20579,9 +19435,9 @@
 
         final int permissionCount = ps.pkg.requestedPermissions.size();
         for (int i = 0; i < permissionCount; i++) {
-            String permission = ps.pkg.requestedPermissions.get(i);
-
-            BasePermission bp = mSettings.mPermissions.get(permission);
+            final String permName = ps.pkg.requestedPermissions.get(i);
+            final BasePermission bp =
+                    (BasePermission) mPermissionManager.getPermissionTEMP(permName);
             if (bp == null) {
                 continue;
             }
@@ -20593,7 +19449,7 @@
                 for (int j = 0; j < packageCount; j++) {
                     PackageSetting pkg = ps.sharedUser.packages.valueAt(j);
                     if (pkg.pkg != null && !pkg.pkg.packageName.equals(ps.pkg.packageName)
-                            && pkg.pkg.requestedPermissions.contains(permission)) {
+                            && pkg.pkg.requestedPermissions.contains(permName)) {
                         used = true;
                         break;
                     }
@@ -20603,13 +19459,13 @@
                 }
             }
 
-            PermissionsState permissionsState = ps.getPermissionsState();
+            final PermissionsState permissionsState = ps.getPermissionsState();
 
-            final int oldFlags = permissionsState.getPermissionFlags(bp.name, userId);
+            final int oldFlags = permissionsState.getPermissionFlags(permName, userId);
 
             // Always clear the user settable flags.
-            final boolean hasInstallState = permissionsState.getInstallPermissionState(
-                    bp.name) != null;
+            final boolean hasInstallState =
+                    permissionsState.getInstallPermissionState(permName) != null;
             // If permission review is enabled and this is a legacy app, mark the
             // permission as requiring a review as this is the initial state.
             int flags = 0;
@@ -20709,7 +19565,7 @@
         final int callingUid = Binder.getCallingUid();
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.DELETE_CACHE_FILES, null);
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 /* requireFullPermission= */ true, /* checkShell= */ false,
                 "delete application cache files");
         final int hasAccessInstantApps = mContext.checkCallingOrSelfPermission(
@@ -20829,7 +19685,7 @@
             String opname) {
         // writer
         int callingUid = Binder.getCallingUid();
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 true /* requireFullPermission */, false /* checkShell */, "add preferred activity");
         if (filter.countActions() == 0) {
             Slog.w(TAG, "Cannot set a preferred activity with no filter actions");
@@ -20894,7 +19750,7 @@
         }
 
         final int callingUid = Binder.getCallingUid();
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 true /* requireFullPermission */, false /* checkShell */,
                 "replace preferred activity");
         synchronized (mPackages) {
@@ -21571,8 +20427,11 @@
                     newFlagSet |= FLAG_PERMISSION_REVOKE_ON_UPGRADE;
                 }
                 if (DEBUG_BACKUP) {
-                    Slog.v(TAG, "  + Restoring grant: pkg=" + pkgName + " perm=" + permName
-                            + " granted=" + isGranted + " bits=0x" + Integer.toHexString(newFlagSet));
+                    Slog.v(TAG, "  + Restoring grant:"
+                            + " pkg=" + pkgName
+                            + " perm=" + permName
+                            + " granted=" + isGranted
+                            + " bits=0x" + Integer.toHexString(newFlagSet));
                 }
                 final PackageSetting ps = mSettings.mPackages.get(pkgName);
                 if (ps != null) {
@@ -21581,13 +20440,15 @@
                         Slog.v(TAG, "        + already installed; applying");
                     }
                     PermissionsState perms = ps.getPermissionsState();
-                    BasePermission bp = mSettings.mPermissions.get(permName);
+                    BasePermission bp =
+                            (BasePermission) mPermissionManager.getPermissionTEMP(permName);
                     if (bp != null) {
                         if (isGranted) {
                             perms.grantRuntimePermission(bp, userId);
                         }
                         if (newFlagSet != 0) {
-                            perms.updatePermissionFlags(bp, userId, USER_RUNTIME_GRANT_MASK, newFlagSet);
+                            perms.updatePermissionFlags(
+                                    bp, userId, USER_RUNTIME_GRANT_MASK, newFlagSet);
                         }
                     }
                 } else {
@@ -21616,7 +20477,8 @@
                         android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
         int callingUid = Binder.getCallingUid();
         enforceOwnerRights(ownerPackage, callingUid);
-        enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, sourceUserId);
+        PackageManagerServiceUtils.enforceShellRestriction(
+                UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, sourceUserId);
         if (intentFilter.countActions() == 0) {
             Slog.w(TAG, "Cannot set a crossProfile intent filter with no filter actions");
             return;
@@ -21647,7 +20509,8 @@
                         android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, null);
         final int callingUid = Binder.getCallingUid();
         enforceOwnerRights(ownerPackage, callingUid);
-        enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, sourceUserId);
+        PackageManagerServiceUtils.enforceShellRestriction(
+                UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, sourceUserId);
         synchronized (mPackages) {
             CrossProfileIntentResolver resolver =
                     mSettings.editCrossProfileIntentResolverLPw(sourceUserId);
@@ -21877,7 +20740,7 @@
             permission = mContext.checkCallingOrSelfPermission(
                     android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE);
         }
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 false /* requireFullPermission */, true /* checkShell */, "set enabled");
         final boolean allowedByPermission = (permission == PackageManager.PERMISSION_GRANTED);
         boolean sendNow = false;
@@ -22166,7 +21029,7 @@
         if (!sUserManager.exists(userId)) {
             return;
         }
-        enforceCrossUserPermission(Binder.getCallingUid(), userId, false /* requireFullPermission*/,
+        mPermissionManager.enforceCrossUserPermission(Binder.getCallingUid(), userId, false /* requireFullPermission*/,
                 false /* checkShell */, "flushPackageRestrictions");
         synchronized (mPackages) {
             mSettings.writePackageRestrictionsLPr(userId);
@@ -22208,7 +21071,7 @@
         final int permission = mContext.checkCallingOrSelfPermission(
                 android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE);
         final boolean allowedByPermission = (permission == PackageManager.PERMISSION_GRANTED);
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 true /* requireFullPermission */, true /* checkShell */, "stop package");
         // writer
         synchronized (mPackages) {
@@ -22248,7 +21111,7 @@
     public int getApplicationEnabledSetting(String packageName, int userId) {
         if (!sUserManager.exists(userId)) return COMPONENT_ENABLED_STATE_DISABLED;
         int callingUid = Binder.getCallingUid();
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 false /* requireFullPermission */, false /* checkShell */, "get enabled");
         // reader
         synchronized (mPackages) {
@@ -22263,7 +21126,7 @@
     public int getComponentEnabledSetting(ComponentName component, int userId) {
         if (!sUserManager.exists(userId)) return COMPONENT_ENABLED_STATE_DISABLED;
         int callingUid = Binder.getCallingUid();
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 false /*requireFullPermission*/, false /*checkShell*/, "getComponentEnabled");
         synchronized (mPackages) {
             if (filterAppAccessLPr(mSettings.getPackageLPr(component.getPackageName()), callingUid,
@@ -22361,7 +21224,7 @@
 
         // If we upgraded grant all default permissions before kicking off.
         for (int userId : grantPermissionsUserIds) {
-            mDefaultPermissionPolicy.grantDefaultPermissions(userId);
+            mDefaultPermissionPolicy.grantDefaultPermissions(mPackages.values(), userId);
         }
 
         if (grantPermissionsUserIds == EMPTY_INT_ARRAY) {
@@ -22465,85 +21328,6 @@
         return buf.toString();
     }
 
-    static class DumpState {
-        public static final int DUMP_LIBS = 1 << 0;
-        public static final int DUMP_FEATURES = 1 << 1;
-        public static final int DUMP_ACTIVITY_RESOLVERS = 1 << 2;
-        public static final int DUMP_SERVICE_RESOLVERS = 1 << 3;
-        public static final int DUMP_RECEIVER_RESOLVERS = 1 << 4;
-        public static final int DUMP_CONTENT_RESOLVERS = 1 << 5;
-        public static final int DUMP_PERMISSIONS = 1 << 6;
-        public static final int DUMP_PACKAGES = 1 << 7;
-        public static final int DUMP_SHARED_USERS = 1 << 8;
-        public static final int DUMP_MESSAGES = 1 << 9;
-        public static final int DUMP_PROVIDERS = 1 << 10;
-        public static final int DUMP_VERIFIERS = 1 << 11;
-        public static final int DUMP_PREFERRED = 1 << 12;
-        public static final int DUMP_PREFERRED_XML = 1 << 13;
-        public static final int DUMP_KEYSETS = 1 << 14;
-        public static final int DUMP_VERSION = 1 << 15;
-        public static final int DUMP_INSTALLS = 1 << 16;
-        public static final int DUMP_INTENT_FILTER_VERIFIERS = 1 << 17;
-        public static final int DUMP_DOMAIN_PREFERRED = 1 << 18;
-        public static final int DUMP_FROZEN = 1 << 19;
-        public static final int DUMP_DEXOPT = 1 << 20;
-        public static final int DUMP_COMPILER_STATS = 1 << 21;
-        public static final int DUMP_CHANGES = 1 << 22;
-        public static final int DUMP_VOLUMES = 1 << 23;
-
-        public static final int OPTION_SHOW_FILTERS = 1 << 0;
-
-        private int mTypes;
-
-        private int mOptions;
-
-        private boolean mTitlePrinted;
-
-        private SharedUserSetting mSharedUser;
-
-        public boolean isDumping(int type) {
-            if (mTypes == 0 && type != DUMP_PREFERRED_XML) {
-                return true;
-            }
-
-            return (mTypes & type) != 0;
-        }
-
-        public void setDump(int type) {
-            mTypes |= type;
-        }
-
-        public boolean isOptionEnabled(int option) {
-            return (mOptions & option) != 0;
-        }
-
-        public void setOptionEnabled(int option) {
-            mOptions |= option;
-        }
-
-        public boolean onTitlePrinted() {
-            final boolean printed = mTitlePrinted;
-            mTitlePrinted = true;
-            return printed;
-        }
-
-        public boolean getTitlePrinted() {
-            return mTitlePrinted;
-        }
-
-        public void setTitlePrinted(boolean enabled) {
-            mTitlePrinted = enabled;
-        }
-
-        public SharedUserSetting getSharedUser() {
-            return mSharedUser;
-        }
-
-        public void setSharedUser(SharedUserSetting user) {
-            mSharedUser = user;
-        }
-    }
-
     @Override
     public void onShellCommand(FileDescriptor in, FileDescriptor out,
             FileDescriptor err, String[] args, ShellCallback callback,
@@ -23401,135 +22185,6 @@
         }
     }
 
-    /*
-     * Update media status on PackageManager.
-     */
-    @Override
-    public void updateExternalMediaStatus(final boolean mediaStatus, final boolean reportStatus) {
-        enforceSystemOrRoot("Media status can only be updated by the system");
-        // reader; this apparently protects mMediaMounted, but should probably
-        // be a different lock in that case.
-        synchronized (mPackages) {
-            Log.i(TAG, "Updating external media status from "
-                    + (mMediaMounted ? "mounted" : "unmounted") + " to "
-                    + (mediaStatus ? "mounted" : "unmounted"));
-            if (DEBUG_SD_INSTALL)
-                Log.i(TAG, "updateExternalMediaStatus:: mediaStatus=" + mediaStatus
-                        + ", mMediaMounted=" + mMediaMounted);
-            if (mediaStatus == mMediaMounted) {
-                final Message msg = mHandler.obtainMessage(UPDATED_MEDIA_STATUS, reportStatus ? 1
-                        : 0, -1);
-                mHandler.sendMessage(msg);
-                return;
-            }
-            mMediaMounted = mediaStatus;
-        }
-        // Queue up an async operation since the package installation may take a
-        // little while.
-        mHandler.post(new Runnable() {
-            public void run() {
-                updateExternalMediaStatusInner(mediaStatus, reportStatus, true);
-            }
-        });
-    }
-
-    /**
-     * Called by StorageManagerService when the initial ASECs to scan are available.
-     * Should block until all the ASEC containers are finished being scanned.
-     */
-    public void scanAvailableAsecs() {
-        updateExternalMediaStatusInner(true, false, false);
-    }
-
-    /*
-     * Collect information of applications on external media, map them against
-     * existing containers and update information based on current mount status.
-     * Please note that we always have to report status if reportStatus has been
-     * set to true especially when unloading packages.
-     */
-    private void updateExternalMediaStatusInner(boolean isMounted, boolean reportStatus,
-            boolean externalStorage) {
-        ArrayMap<AsecInstallArgs, String> processCids = new ArrayMap<>();
-        int[] uidArr = EmptyArray.INT;
-
-        final String[] list = PackageHelper.getSecureContainerList();
-        if (ArrayUtils.isEmpty(list)) {
-            Log.i(TAG, "No secure containers found");
-        } else {
-            // Process list of secure containers and categorize them
-            // as active or stale based on their package internal state.
-
-            // reader
-            synchronized (mPackages) {
-                for (String cid : list) {
-                    // Leave stages untouched for now; installer service owns them
-                    if (PackageInstallerService.isStageName(cid)) continue;
-
-                    if (DEBUG_SD_INSTALL)
-                        Log.i(TAG, "Processing container " + cid);
-                    String pkgName = getAsecPackageName(cid);
-                    if (pkgName == null) {
-                        Slog.i(TAG, "Found stale container " + cid + " with no package name");
-                        continue;
-                    }
-                    if (DEBUG_SD_INSTALL)
-                        Log.i(TAG, "Looking for pkg : " + pkgName);
-
-                    final PackageSetting ps = mSettings.mPackages.get(pkgName);
-                    if (ps == null) {
-                        Slog.i(TAG, "Found stale container " + cid + " with no matching settings");
-                        continue;
-                    }
-
-                    /*
-                     * Skip packages that are not external if we're unmounting
-                     * external storage.
-                     */
-                    if (externalStorage && !isMounted && !isExternal(ps)) {
-                        continue;
-                    }
-
-                    final AsecInstallArgs args = new AsecInstallArgs(cid,
-                            getAppDexInstructionSets(ps), ps.isForwardLocked());
-                    // The package status is changed only if the code path
-                    // matches between settings and the container id.
-                    if (ps.codePathString != null
-                            && ps.codePathString.startsWith(args.getCodePath())) {
-                        if (DEBUG_SD_INSTALL) {
-                            Log.i(TAG, "Container : " + cid + " corresponds to pkg : " + pkgName
-                                    + " at code path: " + ps.codePathString);
-                        }
-
-                        // We do have a valid package installed on sdcard
-                        processCids.put(args, ps.codePathString);
-                        final int uid = ps.appId;
-                        if (uid != -1) {
-                            uidArr = ArrayUtils.appendInt(uidArr, uid);
-                        }
-                    } else {
-                        Slog.i(TAG, "Found stale container " + cid + ": expected codePath="
-                                + ps.codePathString);
-                    }
-                }
-            }
-
-            Arrays.sort(uidArr);
-        }
-
-        // Process packages with valid entries.
-        if (isMounted) {
-            if (DEBUG_SD_INSTALL)
-                Log.i(TAG, "Loading packages");
-            loadMediaPackages(processCids, uidArr, externalStorage);
-            startCleaningPackages();
-            mInstallerService.onSecureContainersAvailable();
-        } else {
-            if (DEBUG_SD_INSTALL)
-                Log.i(TAG, "Unloading packages");
-            unloadMediaPackages(processCids, uidArr, reportStatus);
-        }
-    }
-
     private void sendResourcesChangedBroadcast(boolean mediaStatus, boolean replacing,
             ArrayList<ApplicationInfo> infos, IIntentReceiver finishedReceiver) {
         final int size = infos.size();
@@ -23569,193 +22224,6 @@
         }
     }
 
-   /*
-     * Look at potentially valid container ids from processCids If package
-     * information doesn't match the one on record or package scanning fails,
-     * the cid is added to list of removeCids. We currently don't delete stale
-     * containers.
-     */
-    private void loadMediaPackages(ArrayMap<AsecInstallArgs, String> processCids, int[] uidArr,
-            boolean externalStorage) {
-        ArrayList<String> pkgList = new ArrayList<String>();
-        Set<AsecInstallArgs> keys = processCids.keySet();
-
-        for (AsecInstallArgs args : keys) {
-            String codePath = processCids.get(args);
-            if (DEBUG_SD_INSTALL)
-                Log.i(TAG, "Loading container : " + args.cid);
-            int retCode = PackageManager.INSTALL_FAILED_CONTAINER_ERROR;
-            try {
-                // Make sure there are no container errors first.
-                if (args.doPreInstall(PackageManager.INSTALL_SUCCEEDED) != PackageManager.INSTALL_SUCCEEDED) {
-                    Slog.e(TAG, "Failed to mount cid : " + args.cid
-                            + " when installing from sdcard");
-                    continue;
-                }
-                // Check code path here.
-                if (codePath == null || !codePath.startsWith(args.getCodePath())) {
-                    Slog.e(TAG, "Container " + args.cid + " cachepath " + args.getCodePath()
-                            + " does not match one in settings " + codePath);
-                    continue;
-                }
-                // Parse package
-                int parseFlags = mDefParseFlags;
-                if (args.isExternalAsec()) {
-                    parseFlags |= PackageParser.PARSE_EXTERNAL_STORAGE;
-                }
-                if (args.isFwdLocked()) {
-                    parseFlags |= PackageParser.PARSE_FORWARD_LOCK;
-                }
-
-                synchronized (mInstallLock) {
-                    PackageParser.Package pkg = null;
-                    try {
-                        // Sadly we don't know the package name yet to freeze it
-                        pkg = scanPackageTracedLI(new File(codePath), parseFlags,
-                                SCAN_IGNORE_FROZEN, 0, null);
-                    } catch (PackageManagerException e) {
-                        Slog.w(TAG, "Failed to scan " + codePath + ": " + e.getMessage());
-                    }
-                    // Scan the package
-                    if (pkg != null) {
-                        /*
-                         * TODO why is the lock being held? doPostInstall is
-                         * called in other places without the lock. This needs
-                         * to be straightened out.
-                         */
-                        // writer
-                        synchronized (mPackages) {
-                            retCode = PackageManager.INSTALL_SUCCEEDED;
-                            pkgList.add(pkg.packageName);
-                            // Post process args
-                            args.doPostInstall(PackageManager.INSTALL_SUCCEEDED,
-                                    pkg.applicationInfo.uid);
-                        }
-                    } else {
-                        Slog.i(TAG, "Failed to install pkg from  " + codePath + " from sdcard");
-                    }
-                }
-
-            } finally {
-                if (retCode != PackageManager.INSTALL_SUCCEEDED) {
-                    Log.w(TAG, "Container " + args.cid + " is stale, retCode=" + retCode);
-                }
-            }
-        }
-        // writer
-        synchronized (mPackages) {
-            // If the platform SDK has changed since the last time we booted,
-            // we need to re-grant app permission to catch any new ones that
-            // appear. This is really a hack, and means that apps can in some
-            // cases get permissions that the user didn't initially explicitly
-            // allow... it would be nice to have some better way to handle
-            // this situation.
-            final VersionInfo ver = externalStorage ? mSettings.getExternalVersion()
-                    : mSettings.getInternalVersion();
-            final String volumeUuid = externalStorage ? StorageManager.UUID_PRIMARY_PHYSICAL
-                    : StorageManager.UUID_PRIVATE_INTERNAL;
-
-            int updateFlags = UPDATE_PERMISSIONS_ALL;
-            if (ver.sdkVersion != mSdkVersion) {
-                logCriticalInfo(Log.INFO, "Platform changed from " + ver.sdkVersion + " to "
-                        + mSdkVersion + "; regranting permissions for external");
-                updateFlags |= UPDATE_PERMISSIONS_REPLACE_PKG | UPDATE_PERMISSIONS_REPLACE_ALL;
-            }
-            updatePermissionsLPw(null, null, volumeUuid, updateFlags);
-
-            // Yay, everything is now upgraded
-            ver.forceCurrent();
-
-            // can downgrade to reader
-            // Persist settings
-            mSettings.writeLPr();
-        }
-        // Send a broadcast to let everyone know we are done processing
-        if (pkgList.size() > 0) {
-            sendResourcesChangedBroadcast(true, false, pkgList, uidArr, null);
-        }
-    }
-
-   /*
-     * Utility method to unload a list of specified containers
-     */
-    private void unloadAllContainers(Set<AsecInstallArgs> cidArgs) {
-        // Just unmount all valid containers.
-        for (AsecInstallArgs arg : cidArgs) {
-            synchronized (mInstallLock) {
-                arg.doPostDeleteLI(false);
-           }
-       }
-   }
-
-    /*
-     * Unload packages mounted on external media. This involves deleting package
-     * data from internal structures, sending broadcasts about disabled packages,
-     * gc'ing to free up references, unmounting all secure containers
-     * corresponding to packages on external media, and posting a
-     * UPDATED_MEDIA_STATUS message if status has been requested. Please note
-     * that we always have to post this message if status has been requested no
-     * matter what.
-     */
-    private void unloadMediaPackages(ArrayMap<AsecInstallArgs, String> processCids, int uidArr[],
-            final boolean reportStatus) {
-        if (DEBUG_SD_INSTALL)
-            Log.i(TAG, "unloading media packages");
-        ArrayList<String> pkgList = new ArrayList<String>();
-        ArrayList<AsecInstallArgs> failedList = new ArrayList<AsecInstallArgs>();
-        final Set<AsecInstallArgs> keys = processCids.keySet();
-        for (AsecInstallArgs args : keys) {
-            String pkgName = args.getPackageName();
-            if (DEBUG_SD_INSTALL)
-                Log.i(TAG, "Trying to unload pkg : " + pkgName);
-            // Delete package internally
-            PackageRemovedInfo outInfo = new PackageRemovedInfo(this);
-            synchronized (mInstallLock) {
-                final int deleteFlags = PackageManager.DELETE_KEEP_DATA;
-                final boolean res;
-                try (PackageFreezer freezer = freezePackageForDelete(pkgName, deleteFlags,
-                        "unloadMediaPackages")) {
-                    res = deletePackageLIF(pkgName, null, false, null, deleteFlags, outInfo, false,
-                            null);
-                }
-                if (res) {
-                    pkgList.add(pkgName);
-                } else {
-                    Slog.e(TAG, "Failed to delete pkg from sdcard : " + pkgName);
-                    failedList.add(args);
-                }
-            }
-        }
-
-        // reader
-        synchronized (mPackages) {
-            // We didn't update the settings after removing each package;
-            // write them now for all packages.
-            mSettings.writeLPr();
-        }
-
-        // We have to absolutely send UPDATED_MEDIA_STATUS only
-        // after confirming that all the receivers processed the ordered
-        // broadcast when packages get disabled, force a gc to clean things up.
-        // and unload all the containers.
-        if (pkgList.size() > 0) {
-            sendResourcesChangedBroadcast(false, false, pkgList, uidArr,
-                    new IIntentReceiver.Stub() {
-                public void performReceive(Intent intent, int resultCode, String data,
-                        Bundle extras, boolean ordered, boolean sticky,
-                        int sendingUser) throws RemoteException {
-                    Message msg = mHandler.obtainMessage(UPDATED_MEDIA_STATUS,
-                            reportStatus ? 1 : 0, 1, keys);
-                    mHandler.sendMessage(msg);
-                }
-            });
-        } else {
-            Message msg = mHandler.obtainMessage(UPDATED_MEDIA_STATUS, reportStatus ? 1 : 0, -1,
-                    keys);
-            mHandler.sendMessage(msg);
-        }
-    }
-
     private void loadPrivatePackages(final VolumeInfo vol) {
         mHandler.post(new Runnable() {
             @Override
@@ -24841,7 +23309,9 @@
     }
 
     void onNewUserCreated(final int userId) {
-        mDefaultPermissionPolicy.grantDefaultPermissions(userId);
+        synchronized(mPackages) {
+            mDefaultPermissionPolicy.grantDefaultPermissions(mPackages.values(), userId);
+        }
         // If permission review for legacy apps is required, we represent
         // dagerous permissions for such apps as always granted runtime
         // permissions to keep per user flag state whether review is needed.
@@ -24873,7 +23343,8 @@
             synchronized (mPackages) {
                 if (mSettings.mReadExternalStorageEnforced == null
                         || mSettings.mReadExternalStorageEnforced != enforced) {
-                    mSettings.mReadExternalStorageEnforced = enforced;
+                    mSettings.mReadExternalStorageEnforced =
+                            enforced ? Boolean.TRUE : Boolean.FALSE;
                     mSettings.writeLPr();
                 }
             }
@@ -25245,74 +23716,164 @@
             }
             return results;
         }
+
+        // NB: this differentiates between preloads and sideloads
+        @Override
+        public String getInstallerForPackage(String packageName) throws RemoteException {
+            final String installerName = getInstallerPackageName(packageName);
+            if (!TextUtils.isEmpty(installerName)) {
+                return installerName;
+            }
+            // differentiate between preload and sideload
+            int callingUser = UserHandle.getUserId(Binder.getCallingUid());
+            ApplicationInfo appInfo = getApplicationInfo(packageName,
+                                    /*flags*/ 0,
+                                    /*userId*/ callingUser);
+            if (appInfo != null && (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+                return "preload";
+            }
+            return "";
+        }
+
+        @Override
+        public int getVersionCodeForPackage(String packageName) throws RemoteException {
+            try {
+                int callingUser = UserHandle.getUserId(Binder.getCallingUid());
+                PackageInfo pInfo = getPackageInfo(packageName, 0, callingUser);
+                if (pInfo != null) {
+                    return pInfo.versionCode;
+                }
+            } catch (Exception e) {
+            }
+            return 0;
+        }
     }
 
     private class PackageManagerInternalImpl extends PackageManagerInternal {
         @Override
-        public void setLocationPackagesProvider(PackagesProvider provider) {
+        public void updatePermissionFlagsTEMP(String permName, String packageName, int flagMask,
+                int flagValues, int userId) {
+            PackageManagerService.this.updatePermissionFlags(
+                    permName, packageName, flagMask, flagValues, userId);
+        }
+
+        @Override
+        public int getPermissionFlagsTEMP(String permName, String packageName, int userId) {
+            return PackageManagerService.this.getPermissionFlags(permName, packageName, userId);
+        }
+
+        @Override
+        public Object enforcePermissionTreeTEMP(String permName, int callingUid) {
             synchronized (mPackages) {
-                mDefaultPermissionPolicy.setLocationPackagesProviderLPw(provider);
+                return BasePermission.enforcePermissionTreeLP(
+                        mSettings.mPermissionTrees, permName, callingUid);
             }
         }
+        @Override
+        public boolean isInstantApp(String packageName, int userId) {
+            return PackageManagerService.this.isInstantApp(packageName, userId);
+        }
+
+        @Override
+        public String getInstantAppPackageName(int uid) {
+            return PackageManagerService.this.getInstantAppPackageName(uid);
+        }
+
+        @Override
+        public boolean filterAppAccess(PackageParser.Package pkg, int callingUid, int userId) {
+            synchronized (mPackages) {
+                return PackageManagerService.this.filterAppAccessLPr(
+                        (PackageSetting) pkg.mExtras, callingUid, userId);
+            }
+        }
+
+        @Override
+        public PackageParser.Package getPackage(String packageName) {
+            synchronized (mPackages) {
+                packageName = resolveInternalPackageNameLPr(
+                        packageName, PackageManager.VERSION_CODE_HIGHEST);
+                return mPackages.get(packageName);
+            }
+        }
+
+        @Override
+        public PackageParser.Package getDisabledPackage(String packageName) {
+            synchronized (mPackages) {
+                final PackageSetting ps = mSettings.getDisabledSystemPkgLPr(packageName);
+                return (ps != null) ? ps.pkg : null;
+            }
+        }
+
+        @Override
+        public String getKnownPackageName(int knownPackage, int userId) {
+            switch(knownPackage) {
+                case PackageManagerInternal.PACKAGE_BROWSER:
+                    return getDefaultBrowserPackageName(userId);
+                case PackageManagerInternal.PACKAGE_INSTALLER:
+                    return mRequiredInstallerPackage;
+                case PackageManagerInternal.PACKAGE_SETUP_WIZARD:
+                    return mSetupWizardPackage;
+                case PackageManagerInternal.PACKAGE_SYSTEM:
+                    return "android";
+                case PackageManagerInternal.PACKAGE_VERIFIER:
+                    return mRequiredVerifierPackage;
+            }
+            return null;
+        }
+
+        @Override
+        public boolean isResolveActivityComponent(ComponentInfo component) {
+            return mResolveActivity.packageName.equals(component.packageName)
+                    && mResolveActivity.name.equals(component.name);
+        }
+
+        @Override
+        public void setLocationPackagesProvider(PackagesProvider provider) {
+            mDefaultPermissionPolicy.setLocationPackagesProvider(provider);
+        }
 
         @Override
         public void setVoiceInteractionPackagesProvider(PackagesProvider provider) {
-            synchronized (mPackages) {
-                mDefaultPermissionPolicy.setVoiceInteractionPackagesProviderLPw(provider);
-            }
+            mDefaultPermissionPolicy.setVoiceInteractionPackagesProvider(provider);
         }
 
         @Override
         public void setSmsAppPackagesProvider(PackagesProvider provider) {
-            synchronized (mPackages) {
-                mDefaultPermissionPolicy.setSmsAppPackagesProviderLPw(provider);
-            }
+            mDefaultPermissionPolicy.setSmsAppPackagesProvider(provider);
         }
 
         @Override
         public void setDialerAppPackagesProvider(PackagesProvider provider) {
-            synchronized (mPackages) {
-                mDefaultPermissionPolicy.setDialerAppPackagesProviderLPw(provider);
-            }
+            mDefaultPermissionPolicy.setDialerAppPackagesProvider(provider);
         }
 
         @Override
         public void setSimCallManagerPackagesProvider(PackagesProvider provider) {
-            synchronized (mPackages) {
-                mDefaultPermissionPolicy.setSimCallManagerPackagesProviderLPw(provider);
-            }
+            mDefaultPermissionPolicy.setSimCallManagerPackagesProvider(provider);
         }
 
         @Override
         public void setSyncAdapterPackagesprovider(SyncAdapterPackagesProvider provider) {
-            synchronized (mPackages) {
-                mDefaultPermissionPolicy.setSyncAdapterPackagesProviderLPw(provider);
-            }
+            mDefaultPermissionPolicy.setSyncAdapterPackagesProvider(provider);
         }
 
         @Override
         public void grantDefaultPermissionsToDefaultSmsApp(String packageName, int userId) {
-            synchronized (mPackages) {
-                mDefaultPermissionPolicy.grantDefaultPermissionsToDefaultSmsAppLPr(
-                        packageName, userId);
-            }
+            mDefaultPermissionPolicy.grantDefaultPermissionsToDefaultSmsApp(packageName, userId);
         }
 
         @Override
         public void grantDefaultPermissionsToDefaultDialerApp(String packageName, int userId) {
             synchronized (mPackages) {
                 mSettings.setDefaultDialerPackageNameLPw(packageName, userId);
-                mDefaultPermissionPolicy.grantDefaultPermissionsToDefaultDialerAppLPr(
-                        packageName, userId);
             }
+            mDefaultPermissionPolicy.grantDefaultPermissionsToDefaultDialerApp(packageName, userId);
         }
 
         @Override
         public void grantDefaultPermissionsToDefaultSimCallManager(String packageName, int userId) {
-            synchronized (mPackages) {
-                mDefaultPermissionPolicy.grantDefaultPermissionsToDefaultSimCallManagerLPr(
-                        packageName, userId);
-            }
+            mDefaultPermissionPolicy.grantDefaultPermissionsToDefaultSimCallManager(
+                    packageName, userId);
         }
 
         @Override
@@ -25399,6 +23960,15 @@
         }
 
         @Override
+        public List<ResolveInfo> queryIntentServices(
+                Intent intent, int flags, int callingUid, int userId) {
+            final String resolvedType = intent.resolveTypeIfNeeded(mContext.getContentResolver());
+            return PackageManagerService.this
+                    .queryIntentServicesInternal(intent, resolvedType, flags, userId, callingUid,
+                            false);
+        }
+
+        @Override
         public ComponentName getHomeActivitiesAsUser(List<ResolveInfo> allHomeCandidates,
                 int userId) {
             return PackageManagerService.this.getHomeActivitiesAsUser(allHomeCandidates, userId);
@@ -25433,17 +24003,19 @@
         }
 
         @Override
-        public void grantRuntimePermission(String packageName, String name, int userId,
+        public void grantRuntimePermission(String packageName, String permName, int userId,
                 boolean overridePolicy) {
-            PackageManagerService.this.grantRuntimePermission(packageName, name, userId,
-                    overridePolicy);
+            PackageManagerService.this.mPermissionManager.grantRuntimePermission(
+                    permName, packageName, overridePolicy, getCallingUid(), userId,
+                    mPermissionCallback);
         }
 
         @Override
-        public void revokeRuntimePermission(String packageName, String name, int userId,
+        public void revokeRuntimePermission(String packageName, String permName, int userId,
                 boolean overridePolicy) {
-            PackageManagerService.this.revokeRuntimePermission(packageName, name, userId,
-                    overridePolicy);
+            mPermissionManager.revokeRuntimePermission(
+                    permName, packageName, overridePolicy, getCallingUid(), userId,
+                    mPermissionCallback);
         }
 
         @Override
@@ -25565,9 +24137,9 @@
 
         @Override
         public ResolveInfo resolveIntent(Intent intent, String resolvedType,
-                int flags, int userId) {
+                int flags, int userId, boolean resolveForStart) {
             return resolveIntentInternal(
-                    intent, resolvedType, flags, userId, true /*resolveForStart*/);
+                    intent, resolvedType, flags, userId, resolveForStart);
         }
 
         @Override
@@ -25577,6 +24149,12 @@
         }
 
         @Override
+        public ProviderInfo resolveContentProvider(String name, int flags, int userId) {
+            return PackageManagerService.this.resolveContentProviderInternal(
+                    name, flags, userId);
+        }
+
+        @Override
         public void addIsolatedUid(int isolatedUid, int ownerUid) {
             synchronized (mPackages) {
                 mIsolatedOwners.put(isolatedUid, ownerUid);
@@ -25623,7 +24201,7 @@
         synchronized (mPackages) {
             final long identity = Binder.clearCallingIdentity();
             try {
-                mDefaultPermissionPolicy.grantDefaultPermissionsToEnabledCarrierAppsLPr(
+                mDefaultPermissionPolicy.grantDefaultPermissionsToEnabledCarrierApps(
                         packageNames, userId);
             } finally {
                 Binder.restoreCallingIdentity(identity);
@@ -25637,7 +24215,7 @@
         synchronized (mPackages) {
             final long identity = Binder.clearCallingIdentity();
             try {
-                mDefaultPermissionPolicy.grantDefaultPermissionsToEnabledImsServicesLPr(
+                mDefaultPermissionPolicy.grantDefaultPermissionsToEnabledImsServices(
                         packageNames, userId);
             } finally {
                 Binder.restoreCallingIdentity(identity);
@@ -25712,7 +24290,7 @@
     @Override
     public int getInstallReason(String packageName, int userId) {
         final int callingUid = Binder.getCallingUid();
-        enforceCrossUserPermission(callingUid, userId,
+        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
                 true /* requireFullPermission */, false /* checkShell */,
                 "get install reason");
         synchronized (mPackages) {
@@ -25790,7 +24368,7 @@
     public String getInstantAppAndroidId(String packageName, int userId) {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_INSTANT_APPS,
                 "getInstantAppAndroidId");
-        enforceCrossUserPermission(Binder.getCallingUid(), userId,
+        mPermissionManager.enforceCrossUserPermission(Binder.getCallingUid(), userId,
                 true /* requireFullPermission */, false /* checkShell */,
                 "getInstantAppAndroidId");
         // Make sure the target is an Instant App.
diff --git a/com/android/server/pm/PackageManagerServiceCompilerMapping.java b/com/android/server/pm/PackageManagerServiceCompilerMapping.java
index 1a97a72..19b0d9b 100644
--- a/com/android/server/pm/PackageManagerServiceCompilerMapping.java
+++ b/com/android/server/pm/PackageManagerServiceCompilerMapping.java
@@ -26,14 +26,19 @@
 public class PackageManagerServiceCompilerMapping {
     // Names for compilation reasons.
     static final String REASON_STRINGS[] = {
-            "first-boot", "boot", "install", "bg-dexopt", "ab-ota", "inactive"
+            "first-boot", "boot", "install", "bg-dexopt", "ab-ota", "inactive", "shared"
     };
 
+    static final int REASON_SHARED_INDEX = 6;
+
     // Static block to ensure the strings array is of the right length.
     static {
         if (PackageManagerService.REASON_LAST + 1 != REASON_STRINGS.length) {
             throw new IllegalStateException("REASON_STRINGS not correct");
         }
+        if (!"shared".equals(REASON_STRINGS[REASON_SHARED_INDEX])) {
+            throw new IllegalStateException("REASON_STRINGS not correct because of shared index");
+        }
     }
 
     private static String getSystemPropertyName(int reason) {
@@ -52,11 +57,18 @@
                 !DexFile.isValidCompilerFilter(sysPropValue)) {
             throw new IllegalStateException("Value \"" + sysPropValue +"\" not valid "
                     + "(reason " + REASON_STRINGS[reason] + ")");
+        } else if (!isFilterAllowedForReason(reason, sysPropValue)) {
+            throw new IllegalStateException("Value \"" + sysPropValue +"\" not allowed "
+                    + "(reason " + REASON_STRINGS[reason] + ")");
         }
 
         return sysPropValue;
     }
 
+    private static boolean isFilterAllowedForReason(int reason, String filter) {
+        return reason != REASON_SHARED_INDEX || !DexFile.isProfileGuidedCompilerFilter(filter);
+    }
+
     // Check that the properties are set and valid.
     // Note: this is done in a separate method so this class can be statically initialized.
     static void checkProperties() {
diff --git a/com/android/server/pm/PackageManagerServiceUtils.java b/com/android/server/pm/PackageManagerServiceUtils.java
index 25fef0a..8f7971e 100644
--- a/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/com/android/server/pm/PackageManagerServiceUtils.java
@@ -22,17 +22,23 @@
 import static com.android.server.pm.PackageManagerService.DEBUG_DEXOPT;
 import static com.android.server.pm.PackageManagerService.TAG;
 
+import com.android.internal.util.ArrayUtils;
+
 import android.annotation.NonNull;
 import android.app.AppGlobals;
 import android.content.Intent;
 import android.content.pm.PackageParser;
 import android.content.pm.ResolveInfo;
 import android.os.Build;
+import android.os.Debug;
+import android.os.Process;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.system.ErrnoException;
 import android.util.ArraySet;
 import android.util.Log;
+import android.util.Slog;
+import android.util.jar.StrictJarFile;
 import dalvik.system.VMRuntime;
 import libcore.io.Libcore;
 
@@ -41,9 +47,11 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.function.Predicate;
+import java.util.zip.ZipEntry;
 
 /**
  * Class containing helper methods for the PackageManagerService.
@@ -253,4 +261,73 @@
         }
         return false;
     }
+
+    /**
+     * Checks that the archive located at {@code fileName} has uncompressed dex file and so
+     * files that can be direclty mapped.
+     */
+    public static void logApkHasUncompressedCode(String fileName) {
+        StrictJarFile jarFile = null;
+        try {
+            jarFile = new StrictJarFile(fileName,
+                    false /*verify*/, false /*signatureSchemeRollbackProtectionsEnforced*/);
+            Iterator<ZipEntry> it = jarFile.iterator();
+            while (it.hasNext()) {
+                ZipEntry entry = it.next();
+                if (entry.getName().endsWith(".dex")) {
+                    if (entry.getMethod() != ZipEntry.STORED) {
+                        Slog.wtf(TAG, "APK " + fileName + " has compressed dex code " +
+                                entry.getName());
+                    } else if ((entry.getDataOffset() & 0x3) != 0) {
+                        Slog.wtf(TAG, "APK " + fileName + " has unaligned dex code " +
+                                entry.getName());
+                    }
+                } else if (entry.getName().endsWith(".so")) {
+                    if (entry.getMethod() != ZipEntry.STORED) {
+                        Slog.wtf(TAG, "APK " + fileName + " has compressed native code " +
+                                entry.getName());
+                    } else if ((entry.getDataOffset() & (0x1000 - 1)) != 0) {
+                        Slog.wtf(TAG, "APK " + fileName + " has unaligned native code " +
+                                entry.getName());
+                    }
+                }
+            }
+        } catch (IOException ignore) {
+            Slog.wtf(TAG, "Error when parsing APK " + fileName);
+        } finally {
+            try {
+                if (jarFile != null) {
+                    jarFile.close();
+                }
+            } catch (IOException ignore) {}
+        }
+        return;
+    }
+
+    /**
+     * Checks that the APKs in the given package have uncompressed dex file and so
+     * files that can be direclty mapped.
+     */
+    public static void logPackageHasUncompressedCode(PackageParser.Package pkg) {
+        logApkHasUncompressedCode(pkg.baseCodePath);
+        if (!ArrayUtils.isEmpty(pkg.splitCodePaths)) {
+            for (int i = 0; i < pkg.splitCodePaths.length; i++) {
+                logApkHasUncompressedCode(pkg.splitCodePaths[i]);
+            }
+        }
+    }
+
+    public static void enforceShellRestriction(String restriction, int callingUid, int userHandle) {
+        if (callingUid == Process.SHELL_UID) {
+            if (userHandle >= 0
+                    && PackageManagerService.sUserManager.hasUserRestriction(
+                            restriction, userHandle)) {
+                throw new SecurityException("Shell does not have permission to access user "
+                        + userHandle);
+            } else if (userHandle < 0) {
+                Slog.e(PackageManagerService.TAG, "Unable to check shell permission for user "
+                        + userHandle + "\n\t" + Debug.getCallers(3));
+            }
+        }
+    }
 }
diff --git a/com/android/server/pm/PackageManagerShellCommand.java b/com/android/server/pm/PackageManagerShellCommand.java
index 930e4f0..1fea003 100644
--- a/com/android/server/pm/PackageManagerShellCommand.java
+++ b/com/android/server/pm/PackageManagerShellCommand.java
@@ -183,7 +183,7 @@
                     PackageLite pkgLite = new PackageLite(null, baseApk, null, null, null, null,
                             null, null);
                     params.sessionParams.setSize(PackageHelper.calculateInstalledSize(
-                            pkgLite, false, params.sessionParams.abiOverride));
+                            pkgLite, params.sessionParams.abiOverride));
                 } catch (PackageParserException | IOException e) {
                     pw.println("Error: Failed to parse APK file: " + file);
                     throw new IllegalArgumentException(
@@ -1441,7 +1441,7 @@
             out = session.openWrite(splitName, 0, sizeBytes);
 
             int total = 0;
-            byte[] buffer = new byte[65536];
+            byte[] buffer = new byte[1024 * 1024];
             int c;
             while ((c = in.read(buffer)) != -1) {
                 total += c;
diff --git a/com/android/server/pm/PackageSetting.java b/com/android/server/pm/PackageSetting.java
index 52bf641..83cb2db 100644
--- a/com/android/server/pm/PackageSetting.java
+++ b/com/android/server/pm/PackageSetting.java
@@ -23,13 +23,15 @@
 import android.service.pm.PackageProto;
 import android.util.proto.ProtoOutputStream;
 
+import com.android.server.pm.permission.PermissionsState;
+
 import java.io.File;
 import java.util.List;
 
 /**
  * Settings data for a particular package we know about.
  */
-final class PackageSetting extends PackageSettingBase {
+public final class PackageSetting extends PackageSettingBase {
     int appId;
     PackageParser.Package pkg;
     /**
@@ -103,12 +105,21 @@
         sharedUserId = orig.sharedUserId;
     }
 
+    @Override
     public PermissionsState getPermissionsState() {
         return (sharedUser != null)
                 ? sharedUser.getPermissionsState()
                 : super.getPermissionsState();
     }
 
+    public PackageParser.Package getPackage() {
+        return pkg;
+    }
+
+    public int getAppId() {
+        return appId;
+    }
+
     public boolean isPrivileged() {
         return (pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0;
     }
@@ -125,6 +136,7 @@
         return (pkgFlags & ApplicationInfo.FLAG_SYSTEM) != 0;
     }
 
+    @Override
     public boolean isSharedUser() {
         return sharedUser != null;
     }
@@ -136,6 +148,10 @@
         return true;
     }
 
+    public boolean hasChildPackages() {
+        return childPackageNames != null && !childPackageNames.isEmpty();
+    }
+
     public void writeToProto(ProtoOutputStream proto, long fieldId, List<UserInfo> users) {
         final long packageToken = proto.start(fieldId);
         proto.write(PackageProto.NAME, (realName != null ? realName : name));
diff --git a/com/android/server/pm/PackageSettingBase.java b/com/android/server/pm/PackageSettingBase.java
index d3ca1fd..e19e83f 100644
--- a/com/android/server/pm/PackageSettingBase.java
+++ b/com/android/server/pm/PackageSettingBase.java
@@ -24,14 +24,12 @@
 import android.content.pm.IntentFilterVerificationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageUserState;
-import android.os.storage.VolumeInfo;
 import android.service.pm.PackageProto;
 import android.util.ArraySet;
 import android.util.SparseArray;
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.google.android.collect.Lists;
 
 import java.io.File;
 import java.util.ArrayList;
@@ -42,7 +40,7 @@
 /**
  * Settings base class for pending and resolved classes.
  */
-abstract class PackageSettingBase extends SettingBase {
+public abstract class PackageSettingBase extends SettingBase {
 
     private static final int[] EMPTY_INT_ARRAY = new int[0];
 
@@ -230,6 +228,9 @@
         return updateAvailable;
     }
 
+    public boolean isSharedUser() {
+        return false;
+    }
     /**
      * Makes a shallow copy of the given package settings.
      *
@@ -412,7 +413,7 @@
         modifyUserState(userId).suspended = suspended;
     }
 
-    boolean getInstantApp(int userId) {
+    public boolean getInstantApp(int userId) {
         return readUserState(userId).instantApp;
     }
 
diff --git a/com/android/server/pm/SettingBase.java b/com/android/server/pm/SettingBase.java
index e17cec0..c97f5e5 100644
--- a/com/android/server/pm/SettingBase.java
+++ b/com/android/server/pm/SettingBase.java
@@ -18,6 +18,8 @@
 
 import android.content.pm.ApplicationInfo;
 
+import com.android.server.pm.permission.PermissionsState;
+
 abstract class SettingBase {
     int pkgFlags;
     int pkgPrivateFlags;
diff --git a/com/android/server/pm/Settings.java b/com/android/server/pm/Settings.java
index 51d3e10..0084411 100644
--- a/com/android/server/pm/Settings.java
+++ b/com/android/server/pm/Settings.java
@@ -16,7 +16,6 @@
 
 package com.android.server.pm;
 
-import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
@@ -64,7 +63,6 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.storage.StorageManager;
-import android.os.storage.VolumeInfo;
 import android.service.pm.PackageServiceDumpProto;
 import android.text.TextUtils;
 import android.util.ArrayMap;
@@ -87,10 +85,11 @@
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.JournaledFile;
 import com.android.internal.util.XmlUtils;
-import com.android.server.backup.PreferredActivityBackupHelper;
 import com.android.server.pm.Installer.InstallerException;
-import com.android.server.pm.PackageManagerService.DumpState;
-import com.android.server.pm.PermissionsState.PermissionState;
+import com.android.server.pm.permission.BasePermission;
+import com.android.server.pm.permission.PermissionSettings;
+import com.android.server.pm.permission.PermissionsState;
+import com.android.server.pm.permission.PermissionsState.PermissionState;
 
 import libcore.io.IoUtils;
 
@@ -127,7 +126,7 @@
 /**
  * Holds information about dynamic settings.
  */
-final class Settings {
+public final class Settings {
     private static final String TAG = "PackageSettings";
 
     /**
@@ -176,7 +175,7 @@
     private static final String TAG_READ_EXTERNAL_STORAGE = "read-external-storage";
     private static final String ATTR_ENFORCEMENT = "enforcement";
 
-    private static final String TAG_ITEM = "item";
+    public static final String TAG_ITEM = "item";
     private static final String TAG_DISABLED_COMPONENTS = "disabled-components";
     private static final String TAG_ENABLED_COMPONENTS = "enabled-components";
     private static final String TAG_PACKAGE_RESTRICTIONS = "package-restrictions";
@@ -201,7 +200,8 @@
     private static final String TAG_DEFAULT_DIALER = "default-dialer";
     private static final String TAG_VERSION = "version";
 
-    private static final String ATTR_NAME = "name";
+    public static final String ATTR_NAME = "name";
+    public static final String ATTR_PACKAGE = "package";
     private static final String ATTR_USER = "user";
     private static final String ATTR_CODE = "code";
     private static final String ATTR_GRANTED = "granted";
@@ -233,7 +233,6 @@
     private static final String ATTR_VOLUME_UUID = "volumeUuid";
     private static final String ATTR_SDK_VERSION = "sdkVersion";
     private static final String ATTR_DATABASE_VERSION = "databaseVersion";
-    private static final String ATTR_DONE = "done";
 
     // Bookkeeping for restored permission grants
     private static final String TAG_RESTORED_RUNTIME_PERMISSIONS = "restored-perms";
@@ -379,10 +378,6 @@
     private final ArrayMap<Long, Integer> mKeySetRefs =
             new ArrayMap<Long, Integer>();
 
-    // Mapping from permission names to info about them.
-    final ArrayMap<String, BasePermission> mPermissions =
-            new ArrayMap<String, BasePermission>();
-
     // Mapping from permission tree names to info about them.
     final ArrayMap<String, BasePermission> mPermissionTrees =
             new ArrayMap<String, BasePermission>();
@@ -420,14 +415,16 @@
     private final File mSystemDir;
 
     public final KeySetManagerService mKeySetManagerService = new KeySetManagerService(mPackages);
+    /** Settings and other information about permissions */
+    private final PermissionSettings mPermissions;
 
-    Settings(Object lock) {
-        this(Environment.getDataDirectory(), lock);
+    Settings(PermissionSettings permissions, Object lock) {
+        this(Environment.getDataDirectory(), permissions, lock);
     }
 
-    Settings(File dataDir, Object lock) {
+    Settings(File dataDir, PermissionSettings permission, Object lock) {
         mLock = lock;
-
+        mPermissions = permission;
         mRuntimePermissionsPersistence = new RuntimePermissionPersistence(mLock);
 
         mSystemDir = new File(dataDir, "system");
@@ -490,7 +487,7 @@
         final PermissionsState perms = ps.getPermissionsState();
 
         for (RestoredPermissionGrant grant : grants) {
-            BasePermission bp = mPermissions.get(grant.permissionName);
+            BasePermission bp = mPermissions.getPermission(grant.permissionName);
             if (bp != null) {
                 if (grant.granted) {
                     perms.grantRuntimePermission(bp, userId);
@@ -507,6 +504,10 @@
         writeRuntimePermissionsForUserLPr(userId, false);
     }
 
+    public boolean canPropagatePermissionToInstantApp(String permName) {
+        return mPermissions.canPropagatePermissionToInstantApp(permName);
+    }
+
     void setInstallerPackageName(String pkgName, String installerPkgName) {
         PackageSetting p = mPackages.get(pkgName);
         if (p != null) {
@@ -664,29 +665,11 @@
         }
     }
 
-    // Transfer ownership of permissions from one package to another.
-    void transferPermissionsLPw(String origPkg, String newPkg) {
-        // Transfer ownership of permissions to the new package.
-        for (int i=0; i<2; i++) {
-            ArrayMap<String, BasePermission> permissions =
-                    i == 0 ? mPermissionTrees : mPermissions;
-            for (BasePermission bp : permissions.values()) {
-                if (origPkg.equals(bp.sourcePackage)) {
-                    if (PackageManagerService.DEBUG_UPGRADE) Log.v(PackageManagerService.TAG,
-                            "Moving permission " + bp.name
-                            + " from pkg " + bp.sourcePackage
-                            + " to " + newPkg);
-                    bp.sourcePackage = newPkg;
-                    bp.packageSetting = null;
-                    bp.perm = null;
-                    if (bp.pendingInfo != null) {
-                        bp.pendingInfo.packageName = newPkg;
-                    }
-                    bp.uid = 0;
-                    bp.setGids(null, false);
-                }
-            }
-        }
+    /**
+     * Transfers ownership of permissions from one package to another.
+     */
+    void transferPermissionsLPw(String origPackageName, String newPackageName) {
+        mPermissions.transferPermissions(origPackageName, newPackageName, mPermissionTrees);
     }
 
     /**
@@ -1074,7 +1057,7 @@
 
         // Update permissions
         for (String eachPerm : deletedPs.pkg.requestedPermissions) {
-            BasePermission bp = mPermissions.get(eachPerm);
+            BasePermission bp = mPermissions.getPermission(eachPerm);
             if (bp == null) {
                 continue;
             }
@@ -2022,8 +2005,7 @@
 
     // Specifically for backup/restore
     public void processRestoredPermissionGrantLPr(String pkgName, String permission,
-            boolean isGranted, int restoredFlagSet, int userId)
-            throws IOException, XmlPullParserException {
+            boolean isGranted, int restoredFlagSet, int userId) {
         mRuntimePermissionsPersistence.rememberRestoredUserGrantLPr(
                 pkgName, permission, isGranted, restoredFlagSet, userId);
     }
@@ -2225,7 +2207,7 @@
             if (tagName.equals(TAG_ITEM)) {
                 String name = parser.getAttributeValue(null, ATTR_NAME);
 
-                BasePermission bp = mPermissions.get(name);
+                BasePermission bp = mPermissions.getPermission(name);
                 if (bp == null) {
                     Slog.w(PackageManagerService.TAG, "Unknown permission: " + name);
                     XmlUtils.skipCurrentTag(parser);
@@ -2520,9 +2502,7 @@
             serializer.endTag(null, "permission-trees");
 
             serializer.startTag(null, "permissions");
-            for (BasePermission bp : mPermissions.values()) {
-                writePermissionLPr(serializer, bp);
-            }
+            mPermissions.writePermissions(serializer);
             serializer.endTag(null, "permissions");
 
             for (final PackageSetting pkg : mPackages.values()) {
@@ -2605,9 +2585,6 @@
             writeAllRuntimePermissionsLPr();
             return;
 
-        } catch(XmlPullParserException e) {
-            Slog.wtf(PackageManagerService.TAG, "Unable to write package manager settings, "
-                    + "current changes will be lost at reboot", e);
         } catch(java.io.IOException e) {
             Slog.wtf(PackageManagerService.TAG, "Unable to write package manager settings, "
                     + "current changes will be lost at reboot", e);
@@ -2951,7 +2928,6 @@
 
     void writeUpgradeKeySetsLPr(XmlSerializer serializer,
             PackageKeySetData data) throws IOException {
-        long properSigning = data.getProperSigningKeySet();
         if (data.isUsingUpgradeKeySets()) {
             for (long id : data.getUpgradeKeySets()) {
                 serializer.startTag(null, "upgrade-keyset");
@@ -2971,32 +2947,8 @@
         }
     }
 
-    void writePermissionLPr(XmlSerializer serializer, BasePermission bp)
-            throws XmlPullParserException, java.io.IOException {
-        if (bp.sourcePackage != null) {
-            serializer.startTag(null, TAG_ITEM);
-            serializer.attribute(null, ATTR_NAME, bp.name);
-            serializer.attribute(null, "package", bp.sourcePackage);
-            if (bp.protectionLevel != PermissionInfo.PROTECTION_NORMAL) {
-                serializer.attribute(null, "protection", Integer.toString(bp.protectionLevel));
-            }
-            if (PackageManagerService.DEBUG_SETTINGS)
-                Log.v(PackageManagerService.TAG, "Writing perm: name=" + bp.name + " type="
-                        + bp.type);
-            if (bp.type == BasePermission.TYPE_DYNAMIC) {
-                final PermissionInfo pi = bp.perm != null ? bp.perm.info : bp.pendingInfo;
-                if (pi != null) {
-                    serializer.attribute(null, "type", "dynamic");
-                    if (pi.icon != 0) {
-                        serializer.attribute(null, "icon", Integer.toString(pi.icon));
-                    }
-                    if (pi.nonLocalizedLabel != null) {
-                        serializer.attribute(null, "label", pi.nonLocalizedLabel.toString());
-                    }
-                }
-            }
-            serializer.endTag(null, TAG_ITEM);
-        }
+    void writePermissionLPr(XmlSerializer serializer, BasePermission bp) throws IOException {
+        bp.writeLPr(serializer);
     }
 
     ArrayList<PackageSetting> getListOfIncompleteInstallPackagesLPr() {
@@ -3088,9 +3040,9 @@
                 if (tagName.equals("package")) {
                     readPackageLPw(parser);
                 } else if (tagName.equals("permissions")) {
-                    readPermissionsLPw(mPermissions, parser);
+                    mPermissions.readPermissions(parser);
                 } else if (tagName.equals("permission-trees")) {
-                    readPermissionsLPw(mPermissionTrees, parser);
+                    PermissionSettings.readPermissions(mPermissionTrees, parser);
                 } else if (tagName.equals("shared-user")) {
                     readSharedUserLPw(parser);
                 } else if (tagName.equals("preferred-packages")) {
@@ -3169,7 +3121,8 @@
                     }
                 } else if (TAG_READ_EXTERNAL_STORAGE.equals(tagName)) {
                     final String enforcement = parser.getAttributeValue(null, ATTR_ENFORCEMENT);
-                    mReadExternalStorageEnforced = "1".equals(enforcement);
+                    mReadExternalStorageEnforced =
+                            "1".equals(enforcement) ? Boolean.TRUE : Boolean.FALSE;
                 } else if (tagName.equals("keyset-settings")) {
                     mKeySetManagerService.readKeySetsLPw(parser, mKeySetRefs);
                 } else if (TAG_VERSION.equals(tagName)) {
@@ -3593,72 +3546,6 @@
         }
     }
 
-    private int readInt(XmlPullParser parser, String ns, String name, int defValue) {
-        String v = parser.getAttributeValue(ns, name);
-        try {
-            if (v == null) {
-                return defValue;
-            }
-            return Integer.parseInt(v);
-        } catch (NumberFormatException e) {
-            PackageManagerService.reportSettingsProblem(Log.WARN,
-                    "Error in package manager settings: attribute " + name
-                            + " has bad integer value " + v + " at "
-                            + parser.getPositionDescription());
-        }
-        return defValue;
-    }
-
-    private void readPermissionsLPw(ArrayMap<String, BasePermission> out, XmlPullParser parser)
-            throws IOException, XmlPullParserException {
-        int outerDepth = parser.getDepth();
-        int type;
-        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
-                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
-            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
-                continue;
-            }
-
-            final String tagName = parser.getName();
-            if (tagName.equals(TAG_ITEM)) {
-                final String name = parser.getAttributeValue(null, ATTR_NAME);
-                final String sourcePackage = parser.getAttributeValue(null, "package");
-                final String ptype = parser.getAttributeValue(null, "type");
-                if (name != null && sourcePackage != null) {
-                    final boolean dynamic = "dynamic".equals(ptype);
-                    BasePermission bp = out.get(name);
-                    // If the permission is builtin, do not clobber it.
-                    if (bp == null || bp.type != BasePermission.TYPE_BUILTIN) {
-                        bp = new BasePermission(name.intern(), sourcePackage,
-                                dynamic ? BasePermission.TYPE_DYNAMIC : BasePermission.TYPE_NORMAL);
-                    }
-                    bp.protectionLevel = readInt(parser, null, "protection",
-                            PermissionInfo.PROTECTION_NORMAL);
-                    bp.protectionLevel = PermissionInfo.fixProtectionLevel(bp.protectionLevel);
-                    if (dynamic) {
-                        PermissionInfo pi = new PermissionInfo();
-                        pi.packageName = sourcePackage.intern();
-                        pi.name = name.intern();
-                        pi.icon = readInt(parser, null, "icon", 0);
-                        pi.nonLocalizedLabel = parser.getAttributeValue(null, "label");
-                        pi.protectionLevel = bp.protectionLevel;
-                        bp.pendingInfo = pi;
-                    }
-                    out.put(bp.name, bp);
-                } else {
-                    PackageManagerService.reportSettingsProblem(Log.WARN,
-                            "Error in package manager settings: permissions has" + " no name at "
-                                    + parser.getPositionDescription());
-                }
-            } else {
-                PackageManagerService.reportSettingsProblem(Log.WARN,
-                        "Unknown element reading permissions: " + parser.getName() + " at "
-                                + parser.getPositionDescription());
-            }
-            XmlUtils.skipCurrentTag(parser);
-        }
-    }
-
     private void readDisabledSysPackageLPw(XmlPullParser parser) throws XmlPullParserException,
             IOException {
         String name = parser.getAttributeValue(null, ATTR_NAME);
@@ -4385,10 +4272,6 @@
         return ps;
     }
 
-    private String compToString(ArraySet<String> cmp) {
-        return cmp != null ? Arrays.toString(cmp.toArray()) : "[]";
-    }
-
     boolean isEnabledAndMatchLPr(ComponentInfo componentInfo, int flags, int userId) {
         final PackageSetting ps = mPackages.get(componentInfo.packageName);
         if (ps == null) return false;
@@ -5001,45 +4884,8 @@
 
     void dumpPermissionsLPr(PrintWriter pw, String packageName, ArraySet<String> permissionNames,
             DumpState dumpState) {
-        boolean printedSomething = false;
-        for (BasePermission p : mPermissions.values()) {
-            if (packageName != null && !packageName.equals(p.sourcePackage)) {
-                continue;
-            }
-            if (permissionNames != null && !permissionNames.contains(p.name)) {
-                continue;
-            }
-            if (!printedSomething) {
-                if (dumpState.onTitlePrinted())
-                    pw.println();
-                pw.println("Permissions:");
-                printedSomething = true;
-            }
-            pw.print("  Permission ["); pw.print(p.name); pw.print("] (");
-                    pw.print(Integer.toHexString(System.identityHashCode(p)));
-                    pw.println("):");
-            pw.print("    sourcePackage="); pw.println(p.sourcePackage);
-            pw.print("    uid="); pw.print(p.uid);
-                    pw.print(" gids="); pw.print(Arrays.toString(
-                            p.computeGids(UserHandle.USER_SYSTEM)));
-                    pw.print(" type="); pw.print(p.type);
-                    pw.print(" prot=");
-                    pw.println(PermissionInfo.protectionToString(p.protectionLevel));
-            if (p.perm != null) {
-                pw.print("    perm="); pw.println(p.perm);
-                if ((p.perm.info.flags & PermissionInfo.FLAG_INSTALLED) == 0
-                        || (p.perm.info.flags & PermissionInfo.FLAG_REMOVED) != 0) {
-                    pw.print("    flags=0x"); pw.println(Integer.toHexString(p.perm.info.flags));
-                }
-            }
-            if (p.packageSetting != null) {
-                pw.print("    packageSetting="); pw.println(p.packageSetting);
-            }
-            if (READ_EXTERNAL_STORAGE.equals(p.name)) {
-                pw.print("    enforced=");
-                pw.println(mReadExternalStorageEnforced);
-            }
-        }
+        mPermissions.dumpPermissions(pw, packageName, permissionNames,
+                (mReadExternalStorageEnforced == Boolean.TRUE), dumpState);
     }
 
     void dumpSharedUsersLPr(PrintWriter pw, String packageName, ArraySet<String> permissionNames,
@@ -5248,7 +5094,7 @@
 
         private final Handler mHandler = new MyHandler();
 
-        private final Object mLock;
+        private final Object mPersistenceLock;
 
         @GuardedBy("mLock")
         private final SparseBooleanArray mWriteScheduled = new SparseBooleanArray();
@@ -5265,8 +5111,8 @@
         // The mapping keys are user ids.
         private final SparseBooleanArray mDefaultPermissionsGranted = new SparseBooleanArray();
 
-        public RuntimePermissionPersistence(Object lock) {
-            mLock = lock;
+        public RuntimePermissionPersistence(Object persistenceLock) {
+            mPersistenceLock = persistenceLock;
         }
 
         public boolean areDefaultRuntimPermissionsGrantedLPr(int userId) {
@@ -5321,7 +5167,7 @@
             ArrayMap<String, List<PermissionState>> permissionsForPackage = new ArrayMap<>();
             ArrayMap<String, List<PermissionState>> permissionsForSharedUser = new ArrayMap<>();
 
-            synchronized (mLock) {
+            synchronized (mPersistenceLock) {
                 mWriteScheduled.delete(userId);
 
                 final int packageCount = mPackages.size();
@@ -5470,7 +5316,7 @@
             PermissionsState permissionsState = sb.getPermissionsState();
             for (PermissionState permissionState
                     : permissionsState.getRuntimePermissionStates(userId)) {
-                BasePermission bp = mPermissions.get(permissionState.getName());
+                BasePermission bp = mPermissions.getPermission(permissionState.getName());
                 if (bp != null) {
                     permissionsState.revokeRuntimePermission(bp, userId);
                     permissionsState.updatePermissionFlags(bp, userId,
@@ -5631,7 +5477,7 @@
                 switch (parser.getName()) {
                     case TAG_ITEM: {
                         String name = parser.getAttributeValue(null, ATTR_NAME);
-                        BasePermission bp = mPermissions.get(name);
+                        BasePermission bp = mPermissions.getPermission(name);
                         if (bp == null) {
                             Slog.w(PackageManagerService.TAG, "Unknown permission:" + name);
                             XmlUtils.skipCurrentTag(parser);
diff --git a/com/android/server/pm/SharedUserSetting.java b/com/android/server/pm/SharedUserSetting.java
index 06e020a..a0dadae 100644
--- a/com/android/server/pm/SharedUserSetting.java
+++ b/com/android/server/pm/SharedUserSetting.java
@@ -16,12 +16,18 @@
 
 package com.android.server.pm;
 
+import android.annotation.Nullable;
+import android.content.pm.PackageParser;
 import android.util.ArraySet;
 
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
 /**
  * Settings data for a particular shared user ID we know about.
  */
-final class SharedUserSetting extends SettingBase {
+public final class SharedUserSetting extends SettingBase {
     final String name;
 
     int userId;
@@ -73,4 +79,18 @@
             setPrivateFlags(this.pkgPrivateFlags | packageSetting.pkgPrivateFlags);
         }
     }
+
+    public @Nullable List<PackageParser.Package> getPackages() {
+        if (packages == null || packages.size() == 0) {
+            return null;
+        }
+        final ArrayList<PackageParser.Package> pkgList = new ArrayList<>(packages.size());
+        for (PackageSetting ps : packages) {
+            if (ps == null) {
+                continue;
+            }
+            pkgList.add(ps.pkg);
+        }
+        return pkgList;
+    }
 }
diff --git a/com/android/server/pm/ShortcutLauncher.java b/com/android/server/pm/ShortcutLauncher.java
index 3060840..f922ad1 100644
--- a/com/android/server/pm/ShortcutLauncher.java
+++ b/com/android/server/pm/ShortcutLauncher.java
@@ -25,6 +25,7 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.pm.ShortcutService.DumpFilter;
 import com.android.server.pm.ShortcutUser.PackageWithUser;
 
 import org.json.JSONException;
@@ -293,7 +294,7 @@
         return ret;
     }
 
-    public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+    public void dump(@NonNull PrintWriter pw, @NonNull String prefix, DumpFilter filter) {
         pw.println();
 
         pw.print(prefix);
diff --git a/com/android/server/pm/ShortcutPackage.java b/com/android/server/pm/ShortcutPackage.java
index 6f70f4c..6fc1e73 100644
--- a/com/android/server/pm/ShortcutPackage.java
+++ b/com/android/server/pm/ShortcutPackage.java
@@ -33,6 +33,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Preconditions;
 import com.android.internal.util.XmlUtils;
+import com.android.server.pm.ShortcutService.DumpFilter;
 import com.android.server.pm.ShortcutService.ShortcutOperation;
 import com.android.server.pm.ShortcutService.Stats;
 
@@ -1144,7 +1145,7 @@
         return false;
     }
 
-    public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
+    public void dump(@NonNull PrintWriter pw, @NonNull String prefix, DumpFilter filter) {
         pw.println();
 
         pw.print(prefix);
@@ -1186,9 +1187,7 @@
         final int size = shortcuts.size();
         for (int i = 0; i < size; i++) {
             final ShortcutInfo si = shortcuts.valueAt(i);
-            pw.print(prefix);
-            pw.print("    ");
-            pw.println(si.toInsecureString());
+            pw.println(si.toDumpString(prefix + "    "));
             if (si.getBitmapPath() != null) {
                 final long len = new File(si.getBitmapPath()).length();
                 pw.print(prefix);
diff --git a/com/android/server/pm/ShortcutService.java b/com/android/server/pm/ShortcutService.java
index 0e572d8..27560c5 100644
--- a/com/android/server/pm/ShortcutService.java
+++ b/com/android/server/pm/ShortcutService.java
@@ -40,8 +40,8 @@
 import android.content.pm.LauncherApps.ShortcutQuery;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.PackageManagerInternal;
+import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ShortcutInfo;
@@ -134,6 +134,7 @@
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
+import java.util.regex.Pattern;
 
 /**
  * TODO:
@@ -484,12 +485,13 @@
     final private IUidObserver mUidObserver = new IUidObserver.Stub() {
         @Override
         public void onUidStateChanged(int uid, int procState, long procStateSeq) {
-            handleOnUidStateChanged(uid, procState);
+            injectPostToHandler(() -> handleOnUidStateChanged(uid, procState));
         }
 
         @Override
         public void onUidGone(int uid, boolean disabled) {
-            handleOnUidStateChanged(uid, ActivityManager.PROCESS_STATE_NONEXISTENT);
+            injectPostToHandler(() ->
+                    handleOnUidStateChanged(uid, ActivityManager.PROCESS_STATE_NONEXISTENT));
         }
 
         @Override
@@ -3454,121 +3456,265 @@
 
     @VisibleForTesting
     void dumpNoCheck(FileDescriptor fd, PrintWriter pw, String[] args) {
+        final DumpFilter filter = parseDumpArgs(args);
 
-        boolean dumpMain = true;
-        boolean checkin = false;
-        boolean clear = false;
-        boolean dumpUid = false;
-        boolean dumpFiles = false;
-
-        if (args != null) {
-            for (String arg : args) {
-                if ("-c".equals(arg)) {
-                    checkin = true;
-
-                } else if ("--checkin".equals(arg)) {
-                    checkin = true;
-                    clear = true;
-
-                } else if ("-a".equals(arg) || "--all".equals(arg)) {
-                    dumpUid = true;
-                    dumpFiles = true;
-
-                } else if ("-u".equals(arg) || "--uid".equals(arg)) {
-                    dumpUid = true;
-
-                } else if ("-f".equals(arg) || "--files".equals(arg)) {
-                    dumpFiles = true;
-
-                } else if ("-n".equals(arg) || "--no-main".equals(arg)) {
-                    dumpMain = false;
-                }
-            }
-        }
-
-        if (checkin) {
+        if (filter.shouldDumpCheckIn()) {
             // Other flags are not supported for checkin.
-            dumpCheckin(pw, clear);
+            dumpCheckin(pw, filter.shouldCheckInClear());
         } else {
-            if (dumpMain) {
-                dumpInner(pw);
+            if (filter.shouldDumpMain()) {
+                dumpInner(pw, filter);
                 pw.println();
             }
-            if (dumpUid) {
+            if (filter.shouldDumpUid()) {
                 dumpUid(pw);
                 pw.println();
             }
-            if (dumpFiles) {
+            if (filter.shouldDumpFiles()) {
                 dumpDumpFiles(pw);
                 pw.println();
             }
         }
     }
 
-    private void dumpInner(PrintWriter pw) {
-        synchronized (mLock) {
-            final long now = injectCurrentTimeMillis();
-            pw.print("Now: [");
-            pw.print(now);
-            pw.print("] ");
-            pw.print(formatTime(now));
+    private static DumpFilter parseDumpArgs(String[] args) {
+        final DumpFilter filter = new DumpFilter();
+        if (args == null) {
+            return filter;
+        }
 
-            pw.print("  Raw last reset: [");
-            pw.print(mRawLastResetTime);
-            pw.print("] ");
-            pw.print(formatTime(mRawLastResetTime));
+        int argIndex = 0;
+        while (argIndex < args.length) {
+            final String arg = args[argIndex++];
 
-            final long last = getLastResetTimeLocked();
-            pw.print("  Last reset: [");
-            pw.print(last);
-            pw.print("] ");
-            pw.print(formatTime(last));
+            if ("-c".equals(arg)) {
+                filter.setDumpCheckIn(true);
+                continue;
+            }
+            if ("--checkin".equals(arg)) {
+                filter.setDumpCheckIn(true);
+                filter.setCheckInClear(true);
+                continue;
+            }
+            if ("-a".equals(arg) || "--all".equals(arg)) {
+                filter.setDumpUid(true);
+                filter.setDumpFiles(true);
+                continue;
+            }
+            if ("-u".equals(arg) || "--uid".equals(arg)) {
+                filter.setDumpUid(true);
+                continue;
+            }
+            if ("-f".equals(arg) || "--files".equals(arg)) {
+                filter.setDumpFiles(true);
+                continue;
+            }
+            if ("-n".equals(arg) || "--no-main".equals(arg)) {
+                filter.setDumpMain(false);
+                continue;
+            }
+            if ("--user".equals(arg)) {
+                if (argIndex >= args.length) {
+                    throw new IllegalArgumentException("Missing user ID for --user");
+                }
+                try {
+                    filter.addUser(Integer.parseInt(args[argIndex++]));
+                } catch (NumberFormatException e) {
+                    throw new IllegalArgumentException("Invalid user ID", e);
+                }
+                continue;
+            }
+            if ("-p".equals(arg) || "--package".equals(arg)) {
+                if (argIndex >= args.length) {
+                    throw new IllegalArgumentException("Missing package name for --package");
+                }
+                filter.addPackageRegex(args[argIndex++]);
+                filter.setDumpDetails(false);
+                continue;
+            }
+            if (arg.startsWith("-")) {
+                throw new IllegalArgumentException("Unknown option " + arg);
+            }
+            break;
+        }
+        while (argIndex < args.length) {
+            filter.addPackage(args[argIndex++]);
+        }
+        return filter;
+    }
 
-            final long next = getNextResetTimeLocked();
-            pw.print("  Next reset: [");
-            pw.print(next);
-            pw.print("] ");
-            pw.print(formatTime(next));
+    static class DumpFilter {
+        private boolean mDumpCheckIn = false;
+        private boolean mCheckInClear = false;
 
-            pw.print("  Config:");
-            pw.print("    Max icon dim: ");
-            pw.println(mMaxIconDimension);
-            pw.print("    Icon format: ");
-            pw.println(mIconPersistFormat);
-            pw.print("    Icon quality: ");
-            pw.println(mIconPersistQuality);
-            pw.print("    saveDelayMillis: ");
-            pw.println(mSaveDelayMillis);
-            pw.print("    resetInterval: ");
-            pw.println(mResetInterval);
-            pw.print("    maxUpdatesPerInterval: ");
-            pw.println(mMaxUpdatesPerInterval);
-            pw.print("    maxShortcutsPerActivity: ");
-            pw.println(mMaxShortcuts);
-            pw.println();
+        private boolean mDumpMain = true;
+        private boolean mDumpUid = false;
+        private boolean mDumpFiles = false;
 
-            pw.println("  Stats:");
-            synchronized (mStatLock) {
-                for (int i = 0; i < Stats.COUNT; i++) {
-                    dumpStatLS(pw, "    ", i);
+        private boolean mDumpDetails = true;
+        private List<Pattern> mPackagePatterns = new ArrayList<>();
+        private List<Integer> mUsers = new ArrayList<>();
+
+        void addPackageRegex(String regex) {
+            mPackagePatterns.add(Pattern.compile(regex));
+        }
+
+        public void addPackage(String packageName) {
+            addPackageRegex(Pattern.quote(packageName));
+        }
+
+        void addUser(int userId) {
+            mUsers.add(userId);
+        }
+
+        boolean isPackageMatch(String packageName) {
+            if (mPackagePatterns.size() == 0) {
+                return true;
+            }
+            for (int i = 0; i < mPackagePatterns.size(); i++) {
+                if (mPackagePatterns.get(i).matcher(packageName).find()) {
+                    return true;
                 }
             }
+            return false;
+        }
 
-            pw.println();
-            pw.print("  #Failures: ");
-            pw.println(mWtfCount);
+        boolean isUserMatch(int userId) {
+            if (mUsers.size() == 0) {
+                return true;
+            }
+            for (int i = 0; i < mUsers.size(); i++) {
+                if (mUsers.get(i) == userId) {
+                    return true;
+                }
+            }
+            return false;
+        }
 
-            if (mLastWtfStacktrace != null) {
-                pw.print("  Last failure stack trace: ");
-                pw.println(Log.getStackTraceString(mLastWtfStacktrace));
+        public boolean shouldDumpCheckIn() {
+            return mDumpCheckIn;
+        }
+
+        public void setDumpCheckIn(boolean dumpCheckIn) {
+            mDumpCheckIn = dumpCheckIn;
+        }
+
+        public boolean shouldCheckInClear() {
+            return mCheckInClear;
+        }
+
+        public void setCheckInClear(boolean checkInClear) {
+            mCheckInClear = checkInClear;
+        }
+
+        public boolean shouldDumpMain() {
+            return mDumpMain;
+        }
+
+        public void setDumpMain(boolean dumpMain) {
+            mDumpMain = dumpMain;
+        }
+
+        public boolean shouldDumpUid() {
+            return mDumpUid;
+        }
+
+        public void setDumpUid(boolean dumpUid) {
+            mDumpUid = dumpUid;
+        }
+
+        public boolean shouldDumpFiles() {
+            return mDumpFiles;
+        }
+
+        public void setDumpFiles(boolean dumpFiles) {
+            mDumpFiles = dumpFiles;
+        }
+
+        public boolean shouldDumpDetails() {
+            return mDumpDetails;
+        }
+
+        public void setDumpDetails(boolean dumpDetails) {
+            mDumpDetails = dumpDetails;
+        }
+    }
+
+    private void dumpInner(PrintWriter pw) {
+        dumpInner(pw, new DumpFilter());
+    }
+
+    private void dumpInner(PrintWriter pw, DumpFilter filter) {
+        synchronized (mLock) {
+            if (filter.shouldDumpDetails()) {
+                final long now = injectCurrentTimeMillis();
+                pw.print("Now: [");
+                pw.print(now);
+                pw.print("] ");
+                pw.print(formatTime(now));
+
+                pw.print("  Raw last reset: [");
+                pw.print(mRawLastResetTime);
+                pw.print("] ");
+                pw.print(formatTime(mRawLastResetTime));
+
+                final long last = getLastResetTimeLocked();
+                pw.print("  Last reset: [");
+                pw.print(last);
+                pw.print("] ");
+                pw.print(formatTime(last));
+
+                final long next = getNextResetTimeLocked();
+                pw.print("  Next reset: [");
+                pw.print(next);
+                pw.print("] ");
+                pw.print(formatTime(next));
+
+                pw.print("  Config:");
+                pw.print("    Max icon dim: ");
+                pw.println(mMaxIconDimension);
+                pw.print("    Icon format: ");
+                pw.println(mIconPersistFormat);
+                pw.print("    Icon quality: ");
+                pw.println(mIconPersistQuality);
+                pw.print("    saveDelayMillis: ");
+                pw.println(mSaveDelayMillis);
+                pw.print("    resetInterval: ");
+                pw.println(mResetInterval);
+                pw.print("    maxUpdatesPerInterval: ");
+                pw.println(mMaxUpdatesPerInterval);
+                pw.print("    maxShortcutsPerActivity: ");
+                pw.println(mMaxShortcuts);
+                pw.println();
+
+                pw.println("  Stats:");
+                synchronized (mStatLock) {
+                    for (int i = 0; i < Stats.COUNT; i++) {
+                        dumpStatLS(pw, "    ", i);
+                    }
+                }
+
+                pw.println();
+                pw.print("  #Failures: ");
+                pw.println(mWtfCount);
+
+                if (mLastWtfStacktrace != null) {
+                    pw.print("  Last failure stack trace: ");
+                    pw.println(Log.getStackTraceString(mLastWtfStacktrace));
+                }
+
+                pw.println();
+                mShortcutBitmapSaver.dumpLocked(pw, "  ");
+
+                pw.println();
             }
 
-            pw.println();
-            mShortcutBitmapSaver.dumpLocked(pw, "  ");
-
             for (int i = 0; i < mUsers.size(); i++) {
-                pw.println();
-                mUsers.valueAt(i).dump(pw, "  ");
+                final ShortcutUser user = mUsers.valueAt(i);
+                if (filter.isUserMatch(user.getUserId())) {
+                    user.dump(pw, "  ", filter);
+                    pw.println();
+                }
             }
         }
     }
diff --git a/com/android/server/pm/ShortcutUser.java b/com/android/server/pm/ShortcutUser.java
index 2c388c4..55e6d28 100644
--- a/com/android/server/pm/ShortcutUser.java
+++ b/com/android/server/pm/ShortcutUser.java
@@ -28,6 +28,7 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Preconditions;
+import com.android.server.pm.ShortcutService.DumpFilter;
 import com.android.server.pm.ShortcutService.InvalidFileFormatException;
 
 import libcore.util.Objects;
@@ -531,44 +532,54 @@
                 + " S=" + restoredShortcuts[0]);
     }
 
-    public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
-        pw.print(prefix);
-        pw.print("User: ");
-        pw.print(mUserId);
-        pw.print("  Known locales: ");
-        pw.print(mKnownLocales);
-        pw.print("  Last app scan: [");
-        pw.print(mLastAppScanTime);
-        pw.print("] ");
-        pw.print(ShortcutService.formatTime(mLastAppScanTime));
-        pw.print("  Last app scan FP: ");
-        pw.print(mLastAppScanOsFingerprint);
-        pw.println();
+    public void dump(@NonNull PrintWriter pw, @NonNull String prefix, DumpFilter filter) {
+        if (filter.shouldDumpDetails()) {
+            pw.print(prefix);
+            pw.print("User: ");
+            pw.print(mUserId);
+            pw.print("  Known locales: ");
+            pw.print(mKnownLocales);
+            pw.print("  Last app scan: [");
+            pw.print(mLastAppScanTime);
+            pw.print("] ");
+            pw.print(ShortcutService.formatTime(mLastAppScanTime));
+            pw.print("  Last app scan FP: ");
+            pw.print(mLastAppScanOsFingerprint);
+            pw.println();
 
-        prefix += prefix + "  ";
+            prefix += prefix + "  ";
 
-        pw.print(prefix);
-        pw.print("Cached launcher: ");
-        pw.print(mCachedLauncher);
-        pw.println();
+            pw.print(prefix);
+            pw.print("Cached launcher: ");
+            pw.print(mCachedLauncher);
+            pw.println();
 
-        pw.print(prefix);
-        pw.print("Last known launcher: ");
-        pw.print(mLastKnownLauncher);
-        pw.println();
+            pw.print(prefix);
+            pw.print("Last known launcher: ");
+            pw.print(mLastKnownLauncher);
+            pw.println();
+        }
 
         for (int i = 0; i < mLaunchers.size(); i++) {
-            mLaunchers.valueAt(i).dump(pw, prefix);
+            ShortcutLauncher launcher = mLaunchers.valueAt(i);
+            if (filter.isPackageMatch(launcher.getPackageName())) {
+                launcher.dump(pw, prefix, filter);
+            }
         }
 
         for (int i = 0; i < mPackages.size(); i++) {
-            mPackages.valueAt(i).dump(pw, prefix);
+            ShortcutPackage pkg = mPackages.valueAt(i);
+            if (filter.isPackageMatch(pkg.getPackageName())) {
+                pkg.dump(pw, prefix, filter);
+            }
         }
 
-        pw.println();
-        pw.print(prefix);
-        pw.println("Bitmap directories: ");
-        dumpDirectorySize(pw, prefix + "  ", mService.getUserBitmapFilePath(mUserId));
+        if (filter.shouldDumpDetails()) {
+            pw.println();
+            pw.print(prefix);
+            pw.println("Bitmap directories: ");
+            dumpDirectorySize(pw, prefix + "  ", mService.getUserBitmapFilePath(mUserId));
+        }
     }
 
     private void dumpDirectorySize(@NonNull PrintWriter pw,
diff --git a/com/android/server/pm/UserManagerService.java b/com/android/server/pm/UserManagerService.java
index f2d527b..1e5245c 100644
--- a/com/android/server/pm/UserManagerService.java
+++ b/com/android/server/pm/UserManagerService.java
@@ -1055,7 +1055,7 @@
 
     /** Called by PackageManagerService */
     public boolean exists(int userId) {
-        return getUserInfoNoChecks(userId) != null;
+        return mLocalService.exists(userId);
     }
 
     @Override
@@ -3502,8 +3502,8 @@
      * @param userId
      * @return whether the user has been initialized yet
      */
-    boolean isInitialized(int userId) {
-        return (getUserInfo(userId).flags & UserInfo.FLAG_INITIALIZED) != 0;
+    boolean isUserInitialized(int userId) {
+        return mLocalService.isUserInitialized(userId);
     }
 
     private class LocalService extends UserManagerInternal {
@@ -3715,6 +3715,16 @@
             }
             return state == UserState.STATE_RUNNING_UNLOCKED;
         }
+
+        @Override
+        public boolean isUserInitialized(int userId) {
+            return (getUserInfo(userId).flags & UserInfo.FLAG_INITIALIZED) != 0;
+        }
+
+        @Override
+        public boolean exists(int userId) {
+            return getUserInfoNoChecks(userId) != null;
+        }
     }
 
     /* Remove all the users except of the system one. */
diff --git a/com/android/server/pm/permission/BasePermission.java b/com/android/server/pm/permission/BasePermission.java
new file mode 100644
index 0000000..09a6e9c
--- /dev/null
+++ b/com/android/server/pm/permission/BasePermission.java
@@ -0,0 +1,564 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.permission;
+
+import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
+import static android.content.pm.PermissionInfo.PROTECTION_DANGEROUS;
+import static android.content.pm.PermissionInfo.PROTECTION_NORMAL;
+import static android.content.pm.PermissionInfo.PROTECTION_SIGNATURE;
+import static android.content.pm.PermissionInfo.PROTECTION_SIGNATURE_OR_SYSTEM;
+
+import static com.android.server.pm.Settings.ATTR_NAME;
+import static com.android.server.pm.Settings.ATTR_PACKAGE;
+import static com.android.server.pm.Settings.TAG_ITEM;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.PackageParser;
+import android.content.pm.PackageParser.Permission;
+import android.content.pm.PermissionInfo;
+import android.os.UserHandle;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.server.pm.DumpState;
+import com.android.server.pm.PackageManagerService;
+import com.android.server.pm.PackageSettingBase;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+public final class BasePermission {
+    static final String TAG = "PackageManager";
+
+    public static final int TYPE_NORMAL = 0;
+    public static final int TYPE_BUILTIN = 1;
+    public static final int TYPE_DYNAMIC = 2;
+    @IntDef(value = {
+        TYPE_NORMAL,
+        TYPE_BUILTIN,
+        TYPE_DYNAMIC,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface PermissionType {}
+
+    @IntDef(value = {
+        PROTECTION_DANGEROUS,
+        PROTECTION_NORMAL,
+        PROTECTION_SIGNATURE,
+        PROTECTION_SIGNATURE_OR_SYSTEM,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ProtectionLevel {}
+
+    final String name;
+
+    @PermissionType final int type;
+
+    String sourcePackageName;
+
+    // TODO: Can we get rid of this? Seems we only use some signature info from the setting
+    PackageSettingBase sourcePackageSetting;
+
+    int protectionLevel;
+
+    PackageParser.Permission perm;
+
+    PermissionInfo pendingPermissionInfo;
+
+    /** UID that owns the definition of this permission */
+    int uid;
+
+    /** Additional GIDs given to apps granted this permission */
+    private int[] gids;
+
+    /**
+     * Flag indicating that {@link #gids} should be adjusted based on the
+     * {@link UserHandle} the granted app is running as.
+     */
+    private boolean perUser;
+
+    public BasePermission(String _name, String _sourcePackageName, @PermissionType int _type) {
+        name = _name;
+        sourcePackageName = _sourcePackageName;
+        type = _type;
+        // Default to most conservative protection level.
+        protectionLevel = PermissionInfo.PROTECTION_SIGNATURE;
+    }
+
+    @Override
+    public String toString() {
+        return "BasePermission{" + Integer.toHexString(System.identityHashCode(this)) + " " + name
+                + "}";
+    }
+
+    public String getName() {
+        return name;
+    }
+    public int getProtectionLevel() {
+        return protectionLevel;
+    }
+    public String getSourcePackageName() {
+        return sourcePackageName;
+    }
+    public PackageSettingBase getSourcePackageSetting() {
+        return sourcePackageSetting;
+    }
+    public int getType() {
+        return type;
+    }
+    public int getUid() {
+        return uid;
+    }
+    public void setGids(int[] gids, boolean perUser) {
+        this.gids = gids;
+        this.perUser = perUser;
+    }
+    public void setPermission(@Nullable Permission perm) {
+        this.perm = perm;
+    }
+    public void setSourcePackageSetting(PackageSettingBase sourcePackageSetting) {
+        this.sourcePackageSetting = sourcePackageSetting;
+    }
+
+    public int[] computeGids(int userId) {
+        if (perUser) {
+            final int[] userGids = new int[gids.length];
+            for (int i = 0; i < gids.length; i++) {
+                userGids[i] = UserHandle.getUid(userId, gids[i]);
+            }
+            return userGids;
+        } else {
+            return gids;
+        }
+    }
+
+    public int calculateFootprint(BasePermission perm) {
+        if (uid == perm.uid) {
+            return perm.name.length() + perm.perm.info.calculateFootprint();
+        }
+        return 0;
+    }
+
+    public boolean isPermission(Permission perm) {
+        return this.perm == perm;
+    }
+
+    public boolean isDynamic() {
+        return type == TYPE_DYNAMIC;
+    }
+
+
+    public boolean isNormal() {
+        return (protectionLevel & PermissionInfo.PROTECTION_MASK_BASE)
+                == PermissionInfo.PROTECTION_NORMAL;
+    }
+    public boolean isRuntime() {
+        return (protectionLevel & PermissionInfo.PROTECTION_MASK_BASE)
+                == PermissionInfo.PROTECTION_DANGEROUS;
+    }
+    public boolean isSignature() {
+        return (protectionLevel & PermissionInfo.PROTECTION_MASK_BASE) ==
+                PermissionInfo.PROTECTION_SIGNATURE;
+    }
+
+    public boolean isAppOp() {
+        return (protectionLevel & PermissionInfo.PROTECTION_FLAG_APPOP) != 0;
+    }
+    public boolean isDevelopment() {
+        return isSignature()
+                && (protectionLevel & PermissionInfo.PROTECTION_FLAG_DEVELOPMENT) != 0;
+    }
+    public boolean isInstaller() {
+        return (protectionLevel & PermissionInfo.PROTECTION_FLAG_INSTALLER) != 0;
+    }
+    public boolean isInstant() {
+        return (protectionLevel & PermissionInfo.PROTECTION_FLAG_INSTANT) != 0;
+    }
+    public boolean isOEM() {
+        return (protectionLevel & PermissionInfo.PROTECTION_FLAG_OEM) != 0;
+    }
+    public boolean isPre23() {
+        return (protectionLevel & PermissionInfo.PROTECTION_FLAG_PRE23) != 0;
+    }
+    public boolean isPreInstalled() {
+        return (protectionLevel & PermissionInfo.PROTECTION_FLAG_PREINSTALLED) != 0;
+    }
+    public boolean isPrivileged() {
+        return (protectionLevel & PermissionInfo.PROTECTION_FLAG_PRIVILEGED) != 0;
+    }
+    public boolean isRuntimeOnly() {
+        return (protectionLevel & PermissionInfo.PROTECTION_FLAG_RUNTIME_ONLY) != 0;
+    }
+    public boolean isSetup() {
+        return (protectionLevel & PermissionInfo.PROTECTION_FLAG_SETUP) != 0;
+    }
+    public boolean isVerifier() {
+        return (protectionLevel & PermissionInfo.PROTECTION_FLAG_VERIFIER) != 0;
+    }
+
+    public void transfer(@NonNull String origPackageName, @NonNull String newPackageName) {
+        if (!origPackageName.equals(sourcePackageName)) {
+            return;
+        }
+        sourcePackageName = newPackageName;
+        sourcePackageSetting = null;
+        perm = null;
+        if (pendingPermissionInfo != null) {
+            pendingPermissionInfo.packageName = newPackageName;
+        }
+        uid = 0;
+        setGids(null, false);
+    }
+
+    public boolean addToTree(@ProtectionLevel int protectionLevel,
+            @NonNull PermissionInfo info, @NonNull BasePermission tree) {
+        final boolean changed =
+                (this.protectionLevel != protectionLevel
+                    || perm == null
+                    || uid != tree.uid
+                    || !perm.owner.equals(tree.perm.owner)
+                    || !comparePermissionInfos(perm.info, info));
+        this.protectionLevel = protectionLevel;
+        info = new PermissionInfo(info);
+        info.protectionLevel = protectionLevel;
+        perm = new PackageParser.Permission(tree.perm.owner, info);
+        perm.info.packageName = tree.perm.info.packageName;
+        uid = tree.uid;
+        return changed;
+    }
+
+    public void updateDynamicPermission(Map<String, BasePermission> permissionTrees) {
+        if (PackageManagerService.DEBUG_SETTINGS) Log.v(TAG, "Dynamic permission: name="
+                + getName() + " pkg=" + getSourcePackageName()
+                + " info=" + pendingPermissionInfo);
+        if (sourcePackageSetting == null && pendingPermissionInfo != null) {
+            final BasePermission tree = findPermissionTreeLP(permissionTrees, name);
+            if (tree != null && tree.perm != null) {
+                sourcePackageSetting = tree.sourcePackageSetting;
+                perm = new PackageParser.Permission(tree.perm.owner,
+                        new PermissionInfo(pendingPermissionInfo));
+                perm.info.packageName = tree.perm.info.packageName;
+                perm.info.name = name;
+                uid = tree.uid;
+            }
+        }
+    }
+
+    public static BasePermission createOrUpdate(@Nullable BasePermission bp, @NonNull Permission p,
+            @NonNull PackageParser.Package pkg, Map<String, BasePermission> permissionTrees,
+            boolean chatty) {
+        final PackageSettingBase pkgSetting = (PackageSettingBase) pkg.mExtras;
+        // Allow system apps to redefine non-system permissions
+        if (bp != null && !Objects.equals(bp.sourcePackageName, p.info.packageName)) {
+            final boolean currentOwnerIsSystem = (bp.perm != null
+                    && bp.perm.owner.isSystemApp());
+            if (p.owner.isSystemApp()) {
+                if (bp.type == BasePermission.TYPE_BUILTIN && bp.perm == null) {
+                    // It's a built-in permission and no owner, take ownership now
+                    bp.sourcePackageSetting = pkgSetting;
+                    bp.perm = p;
+                    bp.uid = pkg.applicationInfo.uid;
+                    bp.sourcePackageName = p.info.packageName;
+                    p.info.flags |= PermissionInfo.FLAG_INSTALLED;
+                } else if (!currentOwnerIsSystem) {
+                    String msg = "New decl " + p.owner + " of permission  "
+                            + p.info.name + " is system; overriding " + bp.sourcePackageName;
+                    PackageManagerService.reportSettingsProblem(Log.WARN, msg);
+                    bp = null;
+                }
+            }
+        }
+        if (bp == null) {
+            bp = new BasePermission(p.info.name, p.info.packageName, TYPE_NORMAL);
+        }
+        StringBuilder r = null;
+        if (bp.perm == null) {
+            if (bp.sourcePackageName == null
+                    || bp.sourcePackageName.equals(p.info.packageName)) {
+                final BasePermission tree = findPermissionTreeLP(permissionTrees, p.info.name);
+                if (tree == null
+                        || tree.sourcePackageName.equals(p.info.packageName)) {
+                    bp.sourcePackageSetting = pkgSetting;
+                    bp.perm = p;
+                    bp.uid = pkg.applicationInfo.uid;
+                    bp.sourcePackageName = p.info.packageName;
+                    p.info.flags |= PermissionInfo.FLAG_INSTALLED;
+                    if (chatty) {
+                        if (r == null) {
+                            r = new StringBuilder(256);
+                        } else {
+                            r.append(' ');
+                        }
+                        r.append(p.info.name);
+                    }
+                } else {
+                    Slog.w(TAG, "Permission " + p.info.name + " from package "
+                            + p.info.packageName + " ignored: base tree "
+                            + tree.name + " is from package "
+                            + tree.sourcePackageName);
+                }
+            } else {
+                Slog.w(TAG, "Permission " + p.info.name + " from package "
+                        + p.info.packageName + " ignored: original from "
+                        + bp.sourcePackageName);
+            }
+        } else if (chatty) {
+            if (r == null) {
+                r = new StringBuilder(256);
+            } else {
+                r.append(' ');
+            }
+            r.append("DUP:");
+            r.append(p.info.name);
+        }
+        if (bp.perm == p) {
+            bp.protectionLevel = p.info.protectionLevel;
+        }
+        if (PackageManagerService.DEBUG_PACKAGE_SCANNING && r != null) {
+            Log.d(TAG, "  Permissions: " + r);
+        }
+        return bp;
+    }
+
+    public static BasePermission enforcePermissionTreeLP(
+            Map<String, BasePermission> permissionTrees, String permName, int callingUid) {
+        if (permName != null) {
+            BasePermission bp = findPermissionTreeLP(permissionTrees, permName);
+            if (bp != null) {
+                if (bp.uid == UserHandle.getAppId(callingUid)) {//UserHandle.getAppId(Binder.getCallingUid())) {
+                    return bp;
+                }
+                throw new SecurityException("Calling uid " + callingUid
+                        + " is not allowed to add to permission tree "
+                        + bp.name + " owned by uid " + bp.uid);
+            }
+        }
+        throw new SecurityException("No permission tree found for " + permName);
+    }
+
+    public void enforceDeclaredUsedAndRuntimeOrDevelopment(PackageParser.Package pkg) {
+        int index = pkg.requestedPermissions.indexOf(name);
+        if (index == -1) {
+            throw new SecurityException("Package " + pkg.packageName
+                    + " has not requested permission " + name);
+        }
+        if (!isRuntime() && !isDevelopment()) {
+            throw new SecurityException("Permission " + name
+                    + " is not a changeable permission type");
+        }
+    }
+
+    private static BasePermission findPermissionTreeLP(
+            Map<String, BasePermission> permissionTrees, String permName) {
+        for (BasePermission bp : permissionTrees.values()) {
+            if (permName.startsWith(bp.name) &&
+                    permName.length() > bp.name.length() &&
+                    permName.charAt(bp.name.length()) == '.') {
+                return bp;
+            }
+        }
+        return null;
+    }
+
+    public @Nullable PermissionInfo generatePermissionInfo(@NonNull String groupName, int flags) {
+        if (groupName == null) {
+            if (perm == null || perm.info.group == null) {
+                return generatePermissionInfo(protectionLevel, flags);
+            }
+        } else {
+            if (perm != null && groupName.equals(perm.info.group)) {
+                return PackageParser.generatePermissionInfo(perm, flags);
+            }
+        }
+        return null;
+    }
+
+    public @NonNull PermissionInfo generatePermissionInfo(int adjustedProtectionLevel, int flags) {
+        final boolean protectionLevelChanged = protectionLevel != adjustedProtectionLevel;
+        // if we return different protection level, don't use the cached info
+        if (perm != null && !protectionLevelChanged) {
+            return PackageParser.generatePermissionInfo(perm, flags);
+        }
+        final PermissionInfo pi = new PermissionInfo();
+        pi.name = name;
+        pi.packageName = sourcePackageName;
+        pi.nonLocalizedLabel = name;
+        pi.protectionLevel = protectionLevelChanged ? adjustedProtectionLevel : protectionLevel;
+        return pi;
+    }
+
+    public static boolean readLPw(@NonNull Map<String, BasePermission> out,
+            @NonNull XmlPullParser parser) {
+        final String tagName = parser.getName();
+        if (!tagName.equals(TAG_ITEM)) {
+            return false;
+        }
+        final String name = parser.getAttributeValue(null, ATTR_NAME);
+        final String sourcePackage = parser.getAttributeValue(null, ATTR_PACKAGE);
+        final String ptype = parser.getAttributeValue(null, "type");
+        if (name == null || sourcePackage == null) {
+            PackageManagerService.reportSettingsProblem(Log.WARN,
+                    "Error in package manager settings: permissions has" + " no name at "
+                            + parser.getPositionDescription());
+            return false;
+        }
+        final boolean dynamic = "dynamic".equals(ptype);
+        BasePermission bp = out.get(name);
+        // If the permission is builtin, do not clobber it.
+        if (bp == null || bp.type != TYPE_BUILTIN) {
+            bp = new BasePermission(name.intern(), sourcePackage,
+                    dynamic ? TYPE_DYNAMIC : TYPE_NORMAL);
+        }
+        bp.protectionLevel = readInt(parser, null, "protection",
+                PermissionInfo.PROTECTION_NORMAL);
+        bp.protectionLevel = PermissionInfo.fixProtectionLevel(bp.protectionLevel);
+        if (dynamic) {
+            final PermissionInfo pi = new PermissionInfo();
+            pi.packageName = sourcePackage.intern();
+            pi.name = name.intern();
+            pi.icon = readInt(parser, null, "icon", 0);
+            pi.nonLocalizedLabel = parser.getAttributeValue(null, "label");
+            pi.protectionLevel = bp.protectionLevel;
+            bp.pendingPermissionInfo = pi;
+        }
+        out.put(bp.name, bp);
+        return true;
+    }
+
+    private static int readInt(XmlPullParser parser, String ns, String name, int defValue) {
+        String v = parser.getAttributeValue(ns, name);
+        try {
+            if (v == null) {
+                return defValue;
+            }
+            return Integer.parseInt(v);
+        } catch (NumberFormatException e) {
+            PackageManagerService.reportSettingsProblem(Log.WARN,
+                    "Error in package manager settings: attribute " + name
+                            + " has bad integer value " + v + " at "
+                            + parser.getPositionDescription());
+        }
+        return defValue;
+    }
+
+    public void writeLPr(@NonNull XmlSerializer serializer) throws IOException {
+        if (sourcePackageName == null) {
+            return;
+        }
+        serializer.startTag(null, TAG_ITEM);
+        serializer.attribute(null, ATTR_NAME, name);
+        serializer.attribute(null, ATTR_PACKAGE, sourcePackageName);
+        if (protectionLevel != PermissionInfo.PROTECTION_NORMAL) {
+            serializer.attribute(null, "protection", Integer.toString(protectionLevel));
+        }
+        if (type == BasePermission.TYPE_DYNAMIC) {
+            final PermissionInfo pi = perm != null ? perm.info : pendingPermissionInfo;
+            if (pi != null) {
+                serializer.attribute(null, "type", "dynamic");
+                if (pi.icon != 0) {
+                    serializer.attribute(null, "icon", Integer.toString(pi.icon));
+                }
+                if (pi.nonLocalizedLabel != null) {
+                    serializer.attribute(null, "label", pi.nonLocalizedLabel.toString());
+                }
+            }
+        }
+        serializer.endTag(null, TAG_ITEM);
+    }
+
+    private static boolean compareStrings(CharSequence s1, CharSequence s2) {
+        if (s1 == null) {
+            return s2 == null;
+        }
+        if (s2 == null) {
+            return false;
+        }
+        if (s1.getClass() != s2.getClass()) {
+            return false;
+        }
+        return s1.equals(s2);
+    }
+
+    private static boolean comparePermissionInfos(PermissionInfo pi1, PermissionInfo pi2) {
+        if (pi1.icon != pi2.icon) return false;
+        if (pi1.logo != pi2.logo) return false;
+        if (pi1.protectionLevel != pi2.protectionLevel) return false;
+        if (!compareStrings(pi1.name, pi2.name)) return false;
+        if (!compareStrings(pi1.nonLocalizedLabel, pi2.nonLocalizedLabel)) return false;
+        // We'll take care of setting this one.
+        if (!compareStrings(pi1.packageName, pi2.packageName)) return false;
+        // These are not currently stored in settings.
+        //if (!compareStrings(pi1.group, pi2.group)) return false;
+        //if (!compareStrings(pi1.nonLocalizedDescription, pi2.nonLocalizedDescription)) return false;
+        //if (pi1.labelRes != pi2.labelRes) return false;
+        //if (pi1.descriptionRes != pi2.descriptionRes) return false;
+        return true;
+    }
+
+    public boolean dumpPermissionsLPr(@NonNull PrintWriter pw, @NonNull String packageName,
+            @NonNull Set<String> permissionNames, boolean readEnforced,
+            boolean printedSomething, @NonNull DumpState dumpState) {
+        if (packageName != null && !packageName.equals(sourcePackageName)) {
+            return false;
+        }
+        if (permissionNames != null && !permissionNames.contains(name)) {
+            return false;
+        }
+        if (!printedSomething) {
+            if (dumpState.onTitlePrinted())
+                pw.println();
+            pw.println("Permissions:");
+            printedSomething = true;
+        }
+        pw.print("  Permission ["); pw.print(name); pw.print("] (");
+                pw.print(Integer.toHexString(System.identityHashCode(this)));
+                pw.println("):");
+        pw.print("    sourcePackage="); pw.println(sourcePackageName);
+        pw.print("    uid="); pw.print(uid);
+                pw.print(" gids="); pw.print(Arrays.toString(
+                        computeGids(UserHandle.USER_SYSTEM)));
+                pw.print(" type="); pw.print(type);
+                pw.print(" prot=");
+                pw.println(PermissionInfo.protectionToString(protectionLevel));
+        if (perm != null) {
+            pw.print("    perm="); pw.println(perm);
+            if ((perm.info.flags & PermissionInfo.FLAG_INSTALLED) == 0
+                    || (perm.info.flags & PermissionInfo.FLAG_REMOVED) != 0) {
+                pw.print("    flags=0x"); pw.println(Integer.toHexString(perm.info.flags));
+            }
+        }
+        if (sourcePackageSetting != null) {
+            pw.print("    packageSetting="); pw.println(sourcePackageSetting);
+        }
+        if (READ_EXTERNAL_STORAGE.equals(name)) {
+            pw.print("    enforced=");
+            pw.println(readEnforced);
+        }
+        return true;
+    }
+}
diff --git a/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
new file mode 100644
index 0000000..161efd3
--- /dev/null
+++ b/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -0,0 +1,1296 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.permission;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.DownloadManager;
+import android.app.admin.DevicePolicyManager;
+import android.companion.CompanionDeviceManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.PackageParser;
+import android.content.pm.ProviderInfo;
+import android.content.pm.ResolveInfo;
+import android.content.pm.PackageManagerInternal.PackagesProvider;
+import android.content.pm.PackageManagerInternal.SyncAdapterPackagesProvider;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.UserHandle;
+import android.os.storage.StorageManager;
+import android.print.PrintManager;
+import android.provider.CalendarContract;
+import android.provider.ContactsContract;
+import android.provider.MediaStore;
+import android.provider.Telephony.Sms.Intents;
+import android.telephony.TelephonyManager;
+import android.security.Credentials;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.Slog;
+import android.util.Xml;
+import com.android.internal.util.XmlUtils;
+import com.android.server.LocalServices;
+import com.android.server.pm.PackageManagerService;
+import com.android.server.pm.PackageSetting;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static android.os.Process.FIRST_APPLICATION_UID;
+
+/**
+ * This class is the policy for granting runtime permissions to
+ * platform components and default handlers in the system such
+ * that the device is usable out-of-the-box. For example, the
+ * shell UID is a part of the system and the Phone app should
+ * have phone related permission by default.
+ * <p>
+ * NOTE: This class is at the wrong abstraction level. It is a part of the package manager
+ * service but knows about lots of higher level subsystems. The correct way to do this is
+ * to have an interface defined in the package manager but have the impl next to other
+ * policy stuff like PhoneWindowManager
+ */
+public final class DefaultPermissionGrantPolicy {
+    private static final String TAG = "DefaultPermGrantPolicy"; // must be <= 23 chars
+    private static final boolean DEBUG = false;
+
+    private static final int DEFAULT_FLAGS =
+            PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+                    | PackageManager.MATCH_UNINSTALLED_PACKAGES;
+
+    private static final String AUDIO_MIME_TYPE = "audio/mpeg";
+
+    private static final String TAG_EXCEPTIONS = "exceptions";
+    private static final String TAG_EXCEPTION = "exception";
+    private static final String TAG_PERMISSION = "permission";
+    private static final String ATTR_PACKAGE = "package";
+    private static final String ATTR_NAME = "name";
+    private static final String ATTR_FIXED = "fixed";
+
+    private static final Set<String> PHONE_PERMISSIONS = new ArraySet<>();
+    static {
+        PHONE_PERMISSIONS.add(Manifest.permission.READ_PHONE_STATE);
+        PHONE_PERMISSIONS.add(Manifest.permission.CALL_PHONE);
+        PHONE_PERMISSIONS.add(Manifest.permission.READ_CALL_LOG);
+        PHONE_PERMISSIONS.add(Manifest.permission.WRITE_CALL_LOG);
+        PHONE_PERMISSIONS.add(Manifest.permission.ADD_VOICEMAIL);
+        PHONE_PERMISSIONS.add(Manifest.permission.USE_SIP);
+        PHONE_PERMISSIONS.add(Manifest.permission.PROCESS_OUTGOING_CALLS);
+    }
+
+    private static final Set<String> CONTACTS_PERMISSIONS = new ArraySet<>();
+    static {
+        CONTACTS_PERMISSIONS.add(Manifest.permission.READ_CONTACTS);
+        CONTACTS_PERMISSIONS.add(Manifest.permission.WRITE_CONTACTS);
+        CONTACTS_PERMISSIONS.add(Manifest.permission.GET_ACCOUNTS);
+    }
+
+    private static final Set<String> LOCATION_PERMISSIONS = new ArraySet<>();
+    static {
+        LOCATION_PERMISSIONS.add(Manifest.permission.ACCESS_FINE_LOCATION);
+        LOCATION_PERMISSIONS.add(Manifest.permission.ACCESS_COARSE_LOCATION);
+    }
+
+    private static final Set<String> CALENDAR_PERMISSIONS = new ArraySet<>();
+    static {
+        CALENDAR_PERMISSIONS.add(Manifest.permission.READ_CALENDAR);
+        CALENDAR_PERMISSIONS.add(Manifest.permission.WRITE_CALENDAR);
+    }
+
+    private static final Set<String> SMS_PERMISSIONS = new ArraySet<>();
+    static {
+        SMS_PERMISSIONS.add(Manifest.permission.SEND_SMS);
+        SMS_PERMISSIONS.add(Manifest.permission.RECEIVE_SMS);
+        SMS_PERMISSIONS.add(Manifest.permission.READ_SMS);
+        SMS_PERMISSIONS.add(Manifest.permission.RECEIVE_WAP_PUSH);
+        SMS_PERMISSIONS.add(Manifest.permission.RECEIVE_MMS);
+        SMS_PERMISSIONS.add(Manifest.permission.READ_CELL_BROADCASTS);
+    }
+
+    private static final Set<String> MICROPHONE_PERMISSIONS = new ArraySet<>();
+    static {
+        MICROPHONE_PERMISSIONS.add(Manifest.permission.RECORD_AUDIO);
+    }
+
+    private static final Set<String> CAMERA_PERMISSIONS = new ArraySet<>();
+    static {
+        CAMERA_PERMISSIONS.add(Manifest.permission.CAMERA);
+    }
+
+    private static final Set<String> SENSORS_PERMISSIONS = new ArraySet<>();
+    static {
+        SENSORS_PERMISSIONS.add(Manifest.permission.BODY_SENSORS);
+    }
+
+    private static final Set<String> STORAGE_PERMISSIONS = new ArraySet<>();
+    static {
+        STORAGE_PERMISSIONS.add(Manifest.permission.READ_EXTERNAL_STORAGE);
+        STORAGE_PERMISSIONS.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
+    }
+
+    private static final int MSG_READ_DEFAULT_PERMISSION_EXCEPTIONS = 1;
+
+    private static final String ACTION_TRACK = "com.android.fitness.TRACK";
+
+    private final Handler mHandler;
+
+    private PackagesProvider mLocationPackagesProvider;
+    private PackagesProvider mVoiceInteractionPackagesProvider;
+    private PackagesProvider mSmsAppPackagesProvider;
+    private PackagesProvider mDialerAppPackagesProvider;
+    private PackagesProvider mSimCallManagerPackagesProvider;
+    private SyncAdapterPackagesProvider mSyncAdapterPackagesProvider;
+
+    private ArrayMap<String, List<DefaultPermissionGrant>> mGrantExceptions;
+    private final Context mContext;
+    private final Object mLock = new Object();
+    private final PackageManagerInternal mServiceInternal;
+    private final PermissionManagerService mPermissionManager;
+    private final DefaultPermissionGrantedCallback mPermissionGrantedCallback;
+    public interface DefaultPermissionGrantedCallback {
+        /** Callback when permissions have been granted */
+        public void onDefaultRuntimePermissionsGranted(int userId);
+    }
+
+    public DefaultPermissionGrantPolicy(Context context, Looper looper,
+            @Nullable DefaultPermissionGrantedCallback callback,
+            @NonNull PermissionManagerService permissionManager) {
+        mContext = context;
+        mHandler = new Handler(looper) {
+            @Override
+            public void handleMessage(Message msg) {
+                if (msg.what == MSG_READ_DEFAULT_PERMISSION_EXCEPTIONS) {
+                    synchronized (mLock) {
+                        if (mGrantExceptions == null) {
+                            mGrantExceptions = readDefaultPermissionExceptionsLocked();
+                        }
+                    }
+                }
+            }
+        };
+        mPermissionGrantedCallback = callback;
+        mPermissionManager = permissionManager;
+        mServiceInternal = LocalServices.getService(PackageManagerInternal.class);
+    }
+
+    public void setLocationPackagesProvider(PackagesProvider provider) {
+        synchronized (mLock) {
+            mLocationPackagesProvider = provider;
+        }
+    }
+
+    public void setVoiceInteractionPackagesProvider(PackagesProvider provider) {
+        synchronized (mLock) {
+            mVoiceInteractionPackagesProvider = provider;
+        }
+    }
+
+    public void setSmsAppPackagesProvider(PackagesProvider provider) {
+        synchronized (mLock) {
+            mSmsAppPackagesProvider = provider;
+        }
+    }
+
+    public void setDialerAppPackagesProvider(PackagesProvider provider) {
+        synchronized (mLock) {
+            mDialerAppPackagesProvider = provider;
+        }
+    }
+
+    public void setSimCallManagerPackagesProvider(PackagesProvider provider) {
+        synchronized (mLock) {
+            mSimCallManagerPackagesProvider = provider;
+        }
+    }
+
+    public void setSyncAdapterPackagesProvider(SyncAdapterPackagesProvider provider) {
+        synchronized (mLock) {
+            mSyncAdapterPackagesProvider = provider;
+        }
+    }
+
+    public void grantDefaultPermissions(Collection<PackageParser.Package> packages, int userId) {
+        if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_EMBEDDED, 0)) {
+            grantAllRuntimePermissions(packages, userId);
+        } else {
+            grantPermissionsToSysComponentsAndPrivApps(packages, userId);
+            grantDefaultSystemHandlerPermissions(userId);
+            grantDefaultPermissionExceptions(userId);
+        }
+    }
+
+    private void grantRuntimePermissionsForPackage(int userId, PackageParser.Package pkg) {
+        Set<String> permissions = new ArraySet<>();
+        for (String permission :  pkg.requestedPermissions) {
+            final BasePermission bp = mPermissionManager.getPermission(permission);
+            if (bp == null) {
+                continue;
+            }
+            if (bp.isRuntime()) {
+                permissions.add(permission);
+            }
+        }
+        if (!permissions.isEmpty()) {
+            grantRuntimePermissions(pkg, permissions, true, userId);
+        }
+    }
+
+    private void grantAllRuntimePermissions(
+            Collection<PackageParser.Package> packages, int userId) {
+        Log.i(TAG, "Granting all runtime permissions for user " + userId);
+        for (PackageParser.Package pkg : packages) {
+            grantRuntimePermissionsForPackage(userId, pkg);
+        }
+    }
+
+    public void scheduleReadDefaultPermissionExceptions() {
+        mHandler.sendEmptyMessage(MSG_READ_DEFAULT_PERMISSION_EXCEPTIONS);
+    }
+
+    private void grantPermissionsToSysComponentsAndPrivApps(
+            Collection<PackageParser.Package> packages, int userId) {
+        Log.i(TAG, "Granting permissions to platform components for user " + userId);
+        for (PackageParser.Package pkg : packages) {
+            if (!isSysComponentOrPersistentPlatformSignedPrivApp(pkg)
+                    || !doesPackageSupportRuntimePermissions(pkg)
+                    || pkg.requestedPermissions.isEmpty()) {
+                continue;
+            }
+            grantRuntimePermissionsForPackage(userId, pkg);
+        }
+    }
+
+    private void grantDefaultSystemHandlerPermissions(int userId) {
+        Log.i(TAG, "Granting permissions to default platform handlers for user " + userId);
+
+        final PackagesProvider locationPackagesProvider;
+        final PackagesProvider voiceInteractionPackagesProvider;
+        final PackagesProvider smsAppPackagesProvider;
+        final PackagesProvider dialerAppPackagesProvider;
+        final PackagesProvider simCallManagerPackagesProvider;
+        final SyncAdapterPackagesProvider syncAdapterPackagesProvider;
+
+        synchronized (mLock) {
+            locationPackagesProvider = mLocationPackagesProvider;
+            voiceInteractionPackagesProvider = mVoiceInteractionPackagesProvider;
+            smsAppPackagesProvider = mSmsAppPackagesProvider;
+            dialerAppPackagesProvider = mDialerAppPackagesProvider;
+            simCallManagerPackagesProvider = mSimCallManagerPackagesProvider;
+            syncAdapterPackagesProvider = mSyncAdapterPackagesProvider;
+        }
+
+        String[] voiceInteractPackageNames = (voiceInteractionPackagesProvider != null)
+                ? voiceInteractionPackagesProvider.getPackages(userId) : null;
+        String[] locationPackageNames = (locationPackagesProvider != null)
+                ? locationPackagesProvider.getPackages(userId) : null;
+        String[] smsAppPackageNames = (smsAppPackagesProvider != null)
+                ? smsAppPackagesProvider.getPackages(userId) : null;
+        String[] dialerAppPackageNames = (dialerAppPackagesProvider != null)
+                ? dialerAppPackagesProvider.getPackages(userId) : null;
+        String[] simCallManagerPackageNames = (simCallManagerPackagesProvider != null)
+                ? simCallManagerPackagesProvider.getPackages(userId) : null;
+        String[] contactsSyncAdapterPackages = (syncAdapterPackagesProvider != null) ?
+                syncAdapterPackagesProvider.getPackages(ContactsContract.AUTHORITY, userId) : null;
+        String[] calendarSyncAdapterPackages = (syncAdapterPackagesProvider != null) ?
+                syncAdapterPackagesProvider.getPackages(CalendarContract.AUTHORITY, userId) : null;
+
+        // Installer
+        final String installerPackageName = mServiceInternal.getKnownPackageName(
+                PackageManagerInternal.PACKAGE_INSTALLER, userId);
+        PackageParser.Package installerPackage = getSystemPackage(installerPackageName);
+        if (installerPackage != null
+                && doesPackageSupportRuntimePermissions(installerPackage)) {
+            grantRuntimePermissions(installerPackage, STORAGE_PERMISSIONS, true, userId);
+        }
+
+        // Verifier
+        final String verifierPackageName = mServiceInternal.getKnownPackageName(
+                PackageManagerInternal.PACKAGE_VERIFIER, userId);
+        PackageParser.Package verifierPackage = getSystemPackage(verifierPackageName);
+        if (verifierPackage != null
+                && doesPackageSupportRuntimePermissions(verifierPackage)) {
+            grantRuntimePermissions(verifierPackage, STORAGE_PERMISSIONS, true, userId);
+            grantRuntimePermissions(verifierPackage, PHONE_PERMISSIONS, false, userId);
+            grantRuntimePermissions(verifierPackage, SMS_PERMISSIONS, false, userId);
+        }
+
+        // SetupWizard
+        final String setupWizardPackageName = mServiceInternal.getKnownPackageName(
+                PackageManagerInternal.PACKAGE_SETUP_WIZARD, userId);
+        PackageParser.Package setupPackage = getSystemPackage(setupWizardPackageName);
+        if (setupPackage != null
+                && doesPackageSupportRuntimePermissions(setupPackage)) {
+            grantRuntimePermissions(setupPackage, PHONE_PERMISSIONS, userId);
+            grantRuntimePermissions(setupPackage, CONTACTS_PERMISSIONS, userId);
+            grantRuntimePermissions(setupPackage, LOCATION_PERMISSIONS, userId);
+            grantRuntimePermissions(setupPackage, CAMERA_PERMISSIONS, userId);
+        }
+
+        // Camera
+        Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
+        PackageParser.Package cameraPackage = getDefaultSystemHandlerActivityPackage(
+                cameraIntent, userId);
+        if (cameraPackage != null
+                && doesPackageSupportRuntimePermissions(cameraPackage)) {
+            grantRuntimePermissions(cameraPackage, CAMERA_PERMISSIONS, userId);
+            grantRuntimePermissions(cameraPackage, MICROPHONE_PERMISSIONS, userId);
+            grantRuntimePermissions(cameraPackage, STORAGE_PERMISSIONS, userId);
+        }
+
+        // Media provider
+        PackageParser.Package mediaStorePackage = getDefaultProviderAuthorityPackage(
+                MediaStore.AUTHORITY, userId);
+        if (mediaStorePackage != null) {
+            grantRuntimePermissions(mediaStorePackage, STORAGE_PERMISSIONS, true, userId);
+        }
+
+        // Downloads provider
+        PackageParser.Package downloadsPackage = getDefaultProviderAuthorityPackage(
+                "downloads", userId);
+        if (downloadsPackage != null) {
+            grantRuntimePermissions(downloadsPackage, STORAGE_PERMISSIONS, true, userId);
+        }
+
+        // Downloads UI
+        Intent downloadsUiIntent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS);
+        PackageParser.Package downloadsUiPackage = getDefaultSystemHandlerActivityPackage(
+                downloadsUiIntent, userId);
+        if (downloadsUiPackage != null
+                && doesPackageSupportRuntimePermissions(downloadsUiPackage)) {
+            grantRuntimePermissions(downloadsUiPackage, STORAGE_PERMISSIONS, true, userId);
+        }
+
+        // Storage provider
+        PackageParser.Package storagePackage = getDefaultProviderAuthorityPackage(
+                "com.android.externalstorage.documents", userId);
+        if (storagePackage != null) {
+            grantRuntimePermissions(storagePackage, STORAGE_PERMISSIONS, true, userId);
+        }
+
+        // CertInstaller
+        Intent certInstallerIntent = new Intent(Credentials.INSTALL_ACTION);
+        PackageParser.Package certInstallerPackage = getDefaultSystemHandlerActivityPackage(
+                certInstallerIntent, userId);
+        if (certInstallerPackage != null
+                && doesPackageSupportRuntimePermissions(certInstallerPackage)) {
+            grantRuntimePermissions(certInstallerPackage, STORAGE_PERMISSIONS, true, userId);
+        }
+
+        // Dialer
+        if (dialerAppPackageNames == null) {
+            Intent dialerIntent = new Intent(Intent.ACTION_DIAL);
+            PackageParser.Package dialerPackage = getDefaultSystemHandlerActivityPackage(
+                    dialerIntent, userId);
+            if (dialerPackage != null) {
+                grantDefaultPermissionsToDefaultSystemDialerApp(dialerPackage, userId);
+            }
+        } else {
+            for (String dialerAppPackageName : dialerAppPackageNames) {
+                PackageParser.Package dialerPackage = getSystemPackage(dialerAppPackageName);
+                if (dialerPackage != null) {
+                    grantDefaultPermissionsToDefaultSystemDialerApp(dialerPackage, userId);
+                }
+            }
+        }
+
+        // Sim call manager
+        if (simCallManagerPackageNames != null) {
+            for (String simCallManagerPackageName : simCallManagerPackageNames) {
+                PackageParser.Package simCallManagerPackage =
+                        getSystemPackage(simCallManagerPackageName);
+                if (simCallManagerPackage != null) {
+                    grantDefaultPermissionsToDefaultSimCallManager(simCallManagerPackage,
+                            userId);
+                }
+            }
+        }
+
+        // SMS
+        if (smsAppPackageNames == null) {
+            Intent smsIntent = new Intent(Intent.ACTION_MAIN);
+            smsIntent.addCategory(Intent.CATEGORY_APP_MESSAGING);
+            PackageParser.Package smsPackage = getDefaultSystemHandlerActivityPackage(
+                    smsIntent, userId);
+            if (smsPackage != null) {
+               grantDefaultPermissionsToDefaultSystemSmsApp(smsPackage, userId);
+            }
+        } else {
+            for (String smsPackageName : smsAppPackageNames) {
+                PackageParser.Package smsPackage = getSystemPackage(smsPackageName);
+                if (smsPackage != null) {
+                    grantDefaultPermissionsToDefaultSystemSmsApp(smsPackage, userId);
+                }
+            }
+        }
+
+        // Cell Broadcast Receiver
+        Intent cbrIntent = new Intent(Intents.SMS_CB_RECEIVED_ACTION);
+        PackageParser.Package cbrPackage =
+                getDefaultSystemHandlerActivityPackage(cbrIntent, userId);
+        if (cbrPackage != null && doesPackageSupportRuntimePermissions(cbrPackage)) {
+            grantRuntimePermissions(cbrPackage, SMS_PERMISSIONS, userId);
+        }
+
+        // Carrier Provisioning Service
+        Intent carrierProvIntent = new Intent(Intents.SMS_CARRIER_PROVISION_ACTION);
+        PackageParser.Package carrierProvPackage =
+                getDefaultSystemHandlerServicePackage(carrierProvIntent, userId);
+        if (carrierProvPackage != null
+                && doesPackageSupportRuntimePermissions(carrierProvPackage)) {
+            grantRuntimePermissions(carrierProvPackage, SMS_PERMISSIONS, false, userId);
+        }
+
+        // Calendar
+        Intent calendarIntent = new Intent(Intent.ACTION_MAIN);
+        calendarIntent.addCategory(Intent.CATEGORY_APP_CALENDAR);
+        PackageParser.Package calendarPackage = getDefaultSystemHandlerActivityPackage(
+                calendarIntent, userId);
+        if (calendarPackage != null
+                && doesPackageSupportRuntimePermissions(calendarPackage)) {
+            grantRuntimePermissions(calendarPackage, CALENDAR_PERMISSIONS, userId);
+            grantRuntimePermissions(calendarPackage, CONTACTS_PERMISSIONS, userId);
+        }
+
+        // Calendar provider
+        PackageParser.Package calendarProviderPackage = getDefaultProviderAuthorityPackage(
+                CalendarContract.AUTHORITY, userId);
+        if (calendarProviderPackage != null) {
+            grantRuntimePermissions(calendarProviderPackage, CONTACTS_PERMISSIONS, userId);
+            grantRuntimePermissions(calendarProviderPackage, CALENDAR_PERMISSIONS,
+                    true, userId);
+            grantRuntimePermissions(calendarProviderPackage, STORAGE_PERMISSIONS, userId);
+        }
+
+        // Calendar provider sync adapters
+        List<PackageParser.Package> calendarSyncAdapters = getHeadlessSyncAdapterPackages(
+                calendarSyncAdapterPackages, userId);
+        final int calendarSyncAdapterCount = calendarSyncAdapters.size();
+        for (int i = 0; i < calendarSyncAdapterCount; i++) {
+            PackageParser.Package calendarSyncAdapter = calendarSyncAdapters.get(i);
+            if (doesPackageSupportRuntimePermissions(calendarSyncAdapter)) {
+                grantRuntimePermissions(calendarSyncAdapter, CALENDAR_PERMISSIONS, userId);
+            }
+        }
+
+        // Contacts
+        Intent contactsIntent = new Intent(Intent.ACTION_MAIN);
+        contactsIntent.addCategory(Intent.CATEGORY_APP_CONTACTS);
+        PackageParser.Package contactsPackage = getDefaultSystemHandlerActivityPackage(
+                contactsIntent, userId);
+        if (contactsPackage != null
+                && doesPackageSupportRuntimePermissions(contactsPackage)) {
+            grantRuntimePermissions(contactsPackage, CONTACTS_PERMISSIONS, userId);
+            grantRuntimePermissions(contactsPackage, PHONE_PERMISSIONS, userId);
+        }
+
+        // Contacts provider sync adapters
+        List<PackageParser.Package> contactsSyncAdapters = getHeadlessSyncAdapterPackages(
+                contactsSyncAdapterPackages, userId);
+        final int contactsSyncAdapterCount = contactsSyncAdapters.size();
+        for (int i = 0; i < contactsSyncAdapterCount; i++) {
+            PackageParser.Package contactsSyncAdapter = contactsSyncAdapters.get(i);
+            if (doesPackageSupportRuntimePermissions(contactsSyncAdapter)) {
+                grantRuntimePermissions(contactsSyncAdapter, CONTACTS_PERMISSIONS, userId);
+            }
+        }
+
+        // Contacts provider
+        PackageParser.Package contactsProviderPackage = getDefaultProviderAuthorityPackage(
+                ContactsContract.AUTHORITY, userId);
+        if (contactsProviderPackage != null) {
+            grantRuntimePermissions(contactsProviderPackage, CONTACTS_PERMISSIONS,
+                    true, userId);
+            grantRuntimePermissions(contactsProviderPackage, PHONE_PERMISSIONS,
+                    true, userId);
+            grantRuntimePermissions(contactsProviderPackage, STORAGE_PERMISSIONS, userId);
+        }
+
+        // Device provisioning
+        Intent deviceProvisionIntent = new Intent(
+                DevicePolicyManager.ACTION_PROVISION_MANAGED_DEVICE);
+        PackageParser.Package deviceProvisionPackage =
+                getDefaultSystemHandlerActivityPackage(deviceProvisionIntent, userId);
+        if (deviceProvisionPackage != null
+                && doesPackageSupportRuntimePermissions(deviceProvisionPackage)) {
+            grantRuntimePermissions(deviceProvisionPackage, CONTACTS_PERMISSIONS, userId);
+        }
+
+        // Maps
+        Intent mapsIntent = new Intent(Intent.ACTION_MAIN);
+        mapsIntent.addCategory(Intent.CATEGORY_APP_MAPS);
+        PackageParser.Package mapsPackage = getDefaultSystemHandlerActivityPackage(
+                mapsIntent, userId);
+        if (mapsPackage != null
+                && doesPackageSupportRuntimePermissions(mapsPackage)) {
+            grantRuntimePermissions(mapsPackage, LOCATION_PERMISSIONS, userId);
+        }
+
+        // Gallery
+        Intent galleryIntent = new Intent(Intent.ACTION_MAIN);
+        galleryIntent.addCategory(Intent.CATEGORY_APP_GALLERY);
+        PackageParser.Package galleryPackage = getDefaultSystemHandlerActivityPackage(
+                galleryIntent, userId);
+        if (galleryPackage != null
+                && doesPackageSupportRuntimePermissions(galleryPackage)) {
+            grantRuntimePermissions(galleryPackage, STORAGE_PERMISSIONS, userId);
+        }
+
+        // Email
+        Intent emailIntent = new Intent(Intent.ACTION_MAIN);
+        emailIntent.addCategory(Intent.CATEGORY_APP_EMAIL);
+        PackageParser.Package emailPackage = getDefaultSystemHandlerActivityPackage(
+                emailIntent, userId);
+        if (emailPackage != null
+                && doesPackageSupportRuntimePermissions(emailPackage)) {
+            grantRuntimePermissions(emailPackage, CONTACTS_PERMISSIONS, userId);
+            grantRuntimePermissions(emailPackage, CALENDAR_PERMISSIONS, userId);
+        }
+
+        // Browser
+        PackageParser.Package browserPackage = null;
+        String defaultBrowserPackage = mServiceInternal.getKnownPackageName(
+                PackageManagerInternal.PACKAGE_BROWSER, userId);
+        if (defaultBrowserPackage != null) {
+            browserPackage = getPackage(defaultBrowserPackage);
+        }
+        if (browserPackage == null) {
+            Intent browserIntent = new Intent(Intent.ACTION_MAIN);
+            browserIntent.addCategory(Intent.CATEGORY_APP_BROWSER);
+            browserPackage = getDefaultSystemHandlerActivityPackage(
+                    browserIntent, userId);
+        }
+        if (browserPackage != null
+                && doesPackageSupportRuntimePermissions(browserPackage)) {
+            grantRuntimePermissions(browserPackage, LOCATION_PERMISSIONS, userId);
+        }
+
+        // Voice interaction
+        if (voiceInteractPackageNames != null) {
+            for (String voiceInteractPackageName : voiceInteractPackageNames) {
+                PackageParser.Package voiceInteractPackage = getSystemPackage(
+                        voiceInteractPackageName);
+                if (voiceInteractPackage != null
+                        && doesPackageSupportRuntimePermissions(voiceInteractPackage)) {
+                    grantRuntimePermissions(voiceInteractPackage,
+                            CONTACTS_PERMISSIONS, userId);
+                    grantRuntimePermissions(voiceInteractPackage,
+                            CALENDAR_PERMISSIONS, userId);
+                    grantRuntimePermissions(voiceInteractPackage,
+                            MICROPHONE_PERMISSIONS, userId);
+                    grantRuntimePermissions(voiceInteractPackage,
+                            PHONE_PERMISSIONS, userId);
+                    grantRuntimePermissions(voiceInteractPackage,
+                            SMS_PERMISSIONS, userId);
+                    grantRuntimePermissions(voiceInteractPackage,
+                            LOCATION_PERMISSIONS, userId);
+                }
+            }
+        }
+
+        if (ActivityManager.isLowRamDeviceStatic()) {
+            // Allow voice search on low-ram devices
+            Intent globalSearchIntent = new Intent("android.search.action.GLOBAL_SEARCH");
+            PackageParser.Package globalSearchPickerPackage =
+                getDefaultSystemHandlerActivityPackage(globalSearchIntent, userId);
+
+            if (globalSearchPickerPackage != null
+                    && doesPackageSupportRuntimePermissions(globalSearchPickerPackage)) {
+                grantRuntimePermissions(globalSearchPickerPackage,
+                    MICROPHONE_PERMISSIONS, true, userId);
+                grantRuntimePermissions(globalSearchPickerPackage,
+                    LOCATION_PERMISSIONS, true, userId);
+            }
+        }
+
+        // Voice recognition
+        Intent voiceRecoIntent = new Intent("android.speech.RecognitionService");
+        voiceRecoIntent.addCategory(Intent.CATEGORY_DEFAULT);
+        PackageParser.Package voiceRecoPackage = getDefaultSystemHandlerServicePackage(
+                voiceRecoIntent, userId);
+        if (voiceRecoPackage != null
+                && doesPackageSupportRuntimePermissions(voiceRecoPackage)) {
+            grantRuntimePermissions(voiceRecoPackage, MICROPHONE_PERMISSIONS, userId);
+        }
+
+        // Location
+        if (locationPackageNames != null) {
+            for (String packageName : locationPackageNames) {
+                PackageParser.Package locationPackage = getSystemPackage(packageName);
+                if (locationPackage != null
+                        && doesPackageSupportRuntimePermissions(locationPackage)) {
+                    grantRuntimePermissions(locationPackage, CONTACTS_PERMISSIONS, userId);
+                    grantRuntimePermissions(locationPackage, CALENDAR_PERMISSIONS, userId);
+                    grantRuntimePermissions(locationPackage, MICROPHONE_PERMISSIONS, userId);
+                    grantRuntimePermissions(locationPackage, PHONE_PERMISSIONS, userId);
+                    grantRuntimePermissions(locationPackage, SMS_PERMISSIONS, userId);
+                    grantRuntimePermissions(locationPackage, LOCATION_PERMISSIONS,
+                            true, userId);
+                    grantRuntimePermissions(locationPackage, CAMERA_PERMISSIONS, userId);
+                    grantRuntimePermissions(locationPackage, SENSORS_PERMISSIONS, userId);
+                    grantRuntimePermissions(locationPackage, STORAGE_PERMISSIONS, userId);
+                }
+            }
+        }
+
+        // Music
+        Intent musicIntent = new Intent(Intent.ACTION_VIEW);
+        musicIntent.addCategory(Intent.CATEGORY_DEFAULT);
+        musicIntent.setDataAndType(Uri.fromFile(new File("foo.mp3")),
+                AUDIO_MIME_TYPE);
+        PackageParser.Package musicPackage = getDefaultSystemHandlerActivityPackage(
+                musicIntent, userId);
+        if (musicPackage != null
+                && doesPackageSupportRuntimePermissions(musicPackage)) {
+            grantRuntimePermissions(musicPackage, STORAGE_PERMISSIONS, userId);
+        }
+
+        // Home
+        Intent homeIntent = new Intent(Intent.ACTION_MAIN);
+        homeIntent.addCategory(Intent.CATEGORY_HOME);
+        homeIntent.addCategory(Intent.CATEGORY_LAUNCHER_APP);
+        PackageParser.Package homePackage = getDefaultSystemHandlerActivityPackage(
+                homeIntent, userId);
+        if (homePackage != null
+                && doesPackageSupportRuntimePermissions(homePackage)) {
+            grantRuntimePermissions(homePackage, LOCATION_PERMISSIONS, false, userId);
+        }
+
+        // Watches
+        if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH, 0)) {
+            // Home application on watches
+            Intent wearHomeIntent = new Intent(Intent.ACTION_MAIN);
+            wearHomeIntent.addCategory(Intent.CATEGORY_HOME_MAIN);
+
+            PackageParser.Package wearHomePackage = getDefaultSystemHandlerActivityPackage(
+                    wearHomeIntent, userId);
+
+            if (wearHomePackage != null
+                    && doesPackageSupportRuntimePermissions(wearHomePackage)) {
+                grantRuntimePermissions(wearHomePackage, CONTACTS_PERMISSIONS, false,
+                        userId);
+                grantRuntimePermissions(wearHomePackage, PHONE_PERMISSIONS, true, userId);
+                grantRuntimePermissions(wearHomePackage, MICROPHONE_PERMISSIONS, false,
+                        userId);
+                grantRuntimePermissions(wearHomePackage, LOCATION_PERMISSIONS, false,
+                        userId);
+            }
+
+            // Fitness tracking on watches
+            Intent trackIntent = new Intent(ACTION_TRACK);
+            PackageParser.Package trackPackage = getDefaultSystemHandlerActivityPackage(
+                    trackIntent, userId);
+            if (trackPackage != null
+                    && doesPackageSupportRuntimePermissions(trackPackage)) {
+                grantRuntimePermissions(trackPackage, SENSORS_PERMISSIONS, false, userId);
+                grantRuntimePermissions(trackPackage, LOCATION_PERMISSIONS, false, userId);
+            }
+        }
+
+        // Print Spooler
+        PackageParser.Package printSpoolerPackage = getSystemPackage(
+                PrintManager.PRINT_SPOOLER_PACKAGE_NAME);
+        if (printSpoolerPackage != null
+                && doesPackageSupportRuntimePermissions(printSpoolerPackage)) {
+            grantRuntimePermissions(printSpoolerPackage, LOCATION_PERMISSIONS, true, userId);
+        }
+
+        // EmergencyInfo
+        Intent emergencyInfoIntent = new Intent(TelephonyManager.ACTION_EMERGENCY_ASSISTANCE);
+        PackageParser.Package emergencyInfoPckg = getDefaultSystemHandlerActivityPackage(
+                emergencyInfoIntent, userId);
+        if (emergencyInfoPckg != null
+                && doesPackageSupportRuntimePermissions(emergencyInfoPckg)) {
+            grantRuntimePermissions(emergencyInfoPckg, CONTACTS_PERMISSIONS, true, userId);
+            grantRuntimePermissions(emergencyInfoPckg, PHONE_PERMISSIONS, true, userId);
+        }
+
+        // NFC Tag viewer
+        Intent nfcTagIntent = new Intent(Intent.ACTION_VIEW);
+        nfcTagIntent.setType("vnd.android.cursor.item/ndef_msg");
+        PackageParser.Package nfcTagPkg = getDefaultSystemHandlerActivityPackage(
+                nfcTagIntent, userId);
+        if (nfcTagPkg != null
+                && doesPackageSupportRuntimePermissions(nfcTagPkg)) {
+            grantRuntimePermissions(nfcTagPkg, CONTACTS_PERMISSIONS, false, userId);
+            grantRuntimePermissions(nfcTagPkg, PHONE_PERMISSIONS, false, userId);
+        }
+
+        // Storage Manager
+        Intent storageManagerIntent = new Intent(StorageManager.ACTION_MANAGE_STORAGE);
+        PackageParser.Package storageManagerPckg = getDefaultSystemHandlerActivityPackage(
+                storageManagerIntent, userId);
+        if (storageManagerPckg != null
+                && doesPackageSupportRuntimePermissions(storageManagerPckg)) {
+            grantRuntimePermissions(storageManagerPckg, STORAGE_PERMISSIONS, true, userId);
+        }
+
+        // Companion devices
+        PackageParser.Package companionDeviceDiscoveryPackage = getSystemPackage(
+                CompanionDeviceManager.COMPANION_DEVICE_DISCOVERY_PACKAGE_NAME);
+        if (companionDeviceDiscoveryPackage != null
+                && doesPackageSupportRuntimePermissions(companionDeviceDiscoveryPackage)) {
+            grantRuntimePermissions(companionDeviceDiscoveryPackage,
+                    LOCATION_PERMISSIONS, true, userId);
+        }
+
+        // Ringtone Picker
+        Intent ringtonePickerIntent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER);
+        PackageParser.Package ringtonePickerPackage =
+                getDefaultSystemHandlerActivityPackage(ringtonePickerIntent, userId);
+        if (ringtonePickerPackage != null
+                && doesPackageSupportRuntimePermissions(ringtonePickerPackage)) {
+            grantRuntimePermissions(ringtonePickerPackage,
+                    STORAGE_PERMISSIONS, true, userId);
+        }
+
+        if (mPermissionGrantedCallback != null) {
+            mPermissionGrantedCallback.onDefaultRuntimePermissionsGranted(userId);
+        }
+    }
+
+    private void grantDefaultPermissionsToDefaultSystemDialerApp(
+            PackageParser.Package dialerPackage, int userId) {
+        if (doesPackageSupportRuntimePermissions(dialerPackage)) {
+            boolean isPhonePermFixed =
+                    mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH, 0);
+            grantRuntimePermissions(
+                    dialerPackage, PHONE_PERMISSIONS, isPhonePermFixed, userId);
+            grantRuntimePermissions(dialerPackage, CONTACTS_PERMISSIONS, userId);
+            grantRuntimePermissions(dialerPackage, SMS_PERMISSIONS, userId);
+            grantRuntimePermissions(dialerPackage, MICROPHONE_PERMISSIONS, userId);
+            grantRuntimePermissions(dialerPackage, CAMERA_PERMISSIONS, userId);
+        }
+    }
+
+    private void grantDefaultPermissionsToDefaultSystemSmsApp(
+            PackageParser.Package smsPackage, int userId) {
+        if (doesPackageSupportRuntimePermissions(smsPackage)) {
+            grantRuntimePermissions(smsPackage, PHONE_PERMISSIONS, userId);
+            grantRuntimePermissions(smsPackage, CONTACTS_PERMISSIONS, userId);
+            grantRuntimePermissions(smsPackage, SMS_PERMISSIONS, userId);
+            grantRuntimePermissions(smsPackage, STORAGE_PERMISSIONS, userId);
+            grantRuntimePermissions(smsPackage, MICROPHONE_PERMISSIONS, userId);
+            grantRuntimePermissions(smsPackage, CAMERA_PERMISSIONS, userId);
+        }
+    }
+
+    public void grantDefaultPermissionsToDefaultSmsApp(String packageName, int userId) {
+        Log.i(TAG, "Granting permissions to default sms app for user:" + userId);
+        if (packageName == null) {
+            return;
+        }
+        PackageParser.Package smsPackage = getPackage(packageName);
+        if (smsPackage != null && doesPackageSupportRuntimePermissions(smsPackage)) {
+            grantRuntimePermissions(smsPackage, PHONE_PERMISSIONS, false, true, userId);
+            grantRuntimePermissions(smsPackage, CONTACTS_PERMISSIONS, false, true, userId);
+            grantRuntimePermissions(smsPackage, SMS_PERMISSIONS, false, true, userId);
+            grantRuntimePermissions(smsPackage, STORAGE_PERMISSIONS, false, true, userId);
+            grantRuntimePermissions(smsPackage, MICROPHONE_PERMISSIONS, false, true, userId);
+            grantRuntimePermissions(smsPackage, CAMERA_PERMISSIONS, false, true, userId);
+        }
+    }
+
+    public void grantDefaultPermissionsToDefaultDialerApp(String packageName, int userId) {
+        Log.i(TAG, "Granting permissions to default dialer app for user:" + userId);
+        if (packageName == null) {
+            return;
+        }
+        PackageParser.Package dialerPackage = getPackage(packageName);
+        if (dialerPackage != null
+                && doesPackageSupportRuntimePermissions(dialerPackage)) {
+            grantRuntimePermissions(dialerPackage, PHONE_PERMISSIONS, false, true, userId);
+            grantRuntimePermissions(dialerPackage, CONTACTS_PERMISSIONS, false, true, userId);
+            grantRuntimePermissions(dialerPackage, SMS_PERMISSIONS, false, true, userId);
+            grantRuntimePermissions(dialerPackage, MICROPHONE_PERMISSIONS, false, true, userId);
+            grantRuntimePermissions(dialerPackage, CAMERA_PERMISSIONS, false, true, userId);
+        }
+    }
+
+    private void grantDefaultPermissionsToDefaultSimCallManager(
+            PackageParser.Package simCallManagerPackage, int userId) {
+        Log.i(TAG, "Granting permissions to sim call manager for user:" + userId);
+        if (doesPackageSupportRuntimePermissions(simCallManagerPackage)) {
+            grantRuntimePermissions(simCallManagerPackage, PHONE_PERMISSIONS, userId);
+            grantRuntimePermissions(simCallManagerPackage, MICROPHONE_PERMISSIONS, userId);
+        }
+    }
+
+    public void grantDefaultPermissionsToDefaultSimCallManager(String packageName, int userId) {
+        if (packageName == null) {
+            return;
+        }
+        PackageParser.Package simCallManagerPackage = getPackage(packageName);
+        if (simCallManagerPackage != null) {
+            grantDefaultPermissionsToDefaultSimCallManager(simCallManagerPackage, userId);
+        }
+    }
+
+    public void grantDefaultPermissionsToEnabledCarrierApps(String[] packageNames, int userId) {
+        Log.i(TAG, "Granting permissions to enabled carrier apps for user:" + userId);
+        if (packageNames == null) {
+            return;
+        }
+        for (String packageName : packageNames) {
+            PackageParser.Package carrierPackage = getSystemPackage(packageName);
+            if (carrierPackage != null
+                    && doesPackageSupportRuntimePermissions(carrierPackage)) {
+                grantRuntimePermissions(carrierPackage, PHONE_PERMISSIONS, userId);
+                grantRuntimePermissions(carrierPackage, LOCATION_PERMISSIONS, userId);
+                grantRuntimePermissions(carrierPackage, SMS_PERMISSIONS, userId);
+            }
+        }
+    }
+
+    public void grantDefaultPermissionsToEnabledImsServices(String[] packageNames, int userId) {
+        Log.i(TAG, "Granting permissions to enabled ImsServices for user:" + userId);
+        if (packageNames == null) {
+            return;
+        }
+        for (String packageName : packageNames) {
+            PackageParser.Package imsServicePackage = getSystemPackage(packageName);
+            if (imsServicePackage != null
+                    && doesPackageSupportRuntimePermissions(imsServicePackage)) {
+                grantRuntimePermissions(imsServicePackage, PHONE_PERMISSIONS, userId);
+                grantRuntimePermissions(imsServicePackage, MICROPHONE_PERMISSIONS, userId);
+                grantRuntimePermissions(imsServicePackage, LOCATION_PERMISSIONS, userId);
+                grantRuntimePermissions(imsServicePackage, CAMERA_PERMISSIONS, userId);
+            }
+        }
+    }
+
+    public void grantDefaultPermissionsToDefaultBrowser(String packageName, int userId) {
+        Log.i(TAG, "Granting permissions to default browser for user:" + userId);
+        if (packageName == null) {
+            return;
+        }
+        PackageParser.Package browserPackage = getSystemPackage(packageName);
+        if (browserPackage != null
+                && doesPackageSupportRuntimePermissions(browserPackage)) {
+            grantRuntimePermissions(browserPackage, LOCATION_PERMISSIONS, false, false, userId);
+        }
+    }
+
+    private PackageParser.Package getDefaultSystemHandlerActivityPackage(
+            Intent intent, int userId) {
+        ResolveInfo handler = mServiceInternal.resolveIntent(intent,
+                intent.resolveType(mContext.getContentResolver()), DEFAULT_FLAGS, userId, false);
+        if (handler == null || handler.activityInfo == null) {
+            return null;
+        }
+        if (mServiceInternal.isResolveActivityComponent(handler.activityInfo)) {
+            return null;
+        }
+        return getSystemPackage(handler.activityInfo.packageName);
+    }
+
+    private PackageParser.Package getDefaultSystemHandlerServicePackage(
+            Intent intent, int userId) {
+        List<ResolveInfo> handlers = mServiceInternal.queryIntentServices(
+                intent, DEFAULT_FLAGS, Binder.getCallingUid(), userId);
+        if (handlers == null) {
+            return null;
+        }
+        final int handlerCount = handlers.size();
+        for (int i = 0; i < handlerCount; i++) {
+            ResolveInfo handler = handlers.get(i);
+            PackageParser.Package handlerPackage = getSystemPackage(
+                    handler.serviceInfo.packageName);
+            if (handlerPackage != null) {
+                return handlerPackage;
+            }
+        }
+        return null;
+    }
+
+    private List<PackageParser.Package> getHeadlessSyncAdapterPackages(
+            String[] syncAdapterPackageNames, int userId) {
+        List<PackageParser.Package> syncAdapterPackages = new ArrayList<>();
+
+        Intent homeIntent = new Intent(Intent.ACTION_MAIN);
+        homeIntent.addCategory(Intent.CATEGORY_LAUNCHER);
+
+        for (String syncAdapterPackageName : syncAdapterPackageNames) {
+            homeIntent.setPackage(syncAdapterPackageName);
+
+            ResolveInfo homeActivity = mServiceInternal.resolveIntent(homeIntent,
+                    homeIntent.resolveType(mContext.getContentResolver()), DEFAULT_FLAGS,
+                    userId, false);
+            if (homeActivity != null) {
+                continue;
+            }
+
+            PackageParser.Package syncAdapterPackage = getSystemPackage(syncAdapterPackageName);
+            if (syncAdapterPackage != null) {
+                syncAdapterPackages.add(syncAdapterPackage);
+            }
+        }
+
+        return syncAdapterPackages;
+    }
+
+    private PackageParser.Package getDefaultProviderAuthorityPackage(
+            String authority, int userId) {
+        ProviderInfo provider =
+                mServiceInternal.resolveContentProvider(authority, DEFAULT_FLAGS, userId);
+        if (provider != null) {
+            return getSystemPackage(provider.packageName);
+        }
+        return null;
+    }
+
+    private PackageParser.Package getPackage(String packageName) {
+        return mServiceInternal.getPackage(packageName);
+    }
+
+    private PackageParser.Package getSystemPackage(String packageName) {
+        PackageParser.Package pkg = getPackage(packageName);
+        if (pkg != null && pkg.isSystemApp()) {
+            return !isSysComponentOrPersistentPlatformSignedPrivApp(pkg) ? pkg : null;
+        }
+        return null;
+    }
+
+    private void grantRuntimePermissions(PackageParser.Package pkg, Set<String> permissions,
+            int userId) {
+        grantRuntimePermissions(pkg, permissions, false, false, userId);
+    }
+
+    private void grantRuntimePermissions(PackageParser.Package pkg, Set<String> permissions,
+            boolean systemFixed, int userId) {
+        grantRuntimePermissions(pkg, permissions, systemFixed, false, userId);
+    }
+
+    private void grantRuntimePermissions(PackageParser.Package pkg, Set<String> permissions,
+            boolean systemFixed, boolean isDefaultPhoneOrSms, int userId) {
+        if (pkg.requestedPermissions.isEmpty()) {
+            return;
+        }
+
+        List<String> requestedPermissions = pkg.requestedPermissions;
+        Set<String> grantablePermissions = null;
+
+        // If this is the default Phone or SMS app we grant permissions regardless
+        // whether the version on the system image declares the permission as used since
+        // selecting the app as the default Phone or SMS the user makes a deliberate
+        // choice to grant this app the permissions needed to function. For all other
+        // apps, (default grants on first boot and user creation) we don't grant default
+        // permissions if the version on the system image does not declare them.
+        if (!isDefaultPhoneOrSms && pkg.isUpdatedSystemApp()) {
+            final PackageParser.Package disabledPkg =
+                    mServiceInternal.getDisabledPackage(pkg.packageName);
+            if (disabledPkg != null) {
+                if (disabledPkg.requestedPermissions.isEmpty()) {
+                    return;
+                }
+                if (!requestedPermissions.equals(disabledPkg.requestedPermissions)) {
+                    grantablePermissions = new ArraySet<>(requestedPermissions);
+                    requestedPermissions = disabledPkg.requestedPermissions;
+                }
+            }
+        }
+
+        final int grantablePermissionCount = requestedPermissions.size();
+        for (int i = 0; i < grantablePermissionCount; i++) {
+            String permission = requestedPermissions.get(i);
+
+            // If there is a disabled system app it may request a permission the updated
+            // version ot the data partition doesn't, In this case skip the permission.
+            if (grantablePermissions != null && !grantablePermissions.contains(permission)) {
+                continue;
+            }
+
+            if (permissions.contains(permission)) {
+                final int flags = mServiceInternal.getPermissionFlagsTEMP(
+                        permission, pkg.packageName, userId);
+
+                // If any flags are set to the permission, then it is either set in
+                // its current state by the system or device/profile owner or the user.
+                // In all these cases we do not want to clobber the current state.
+                // Unless the caller wants to override user choices. The override is
+                // to make sure we can grant the needed permission to the default
+                // sms and phone apps after the user chooses this in the UI.
+                if (flags == 0 || isDefaultPhoneOrSms) {
+                    // Never clobber policy or system.
+                    final int fixedFlags = PackageManager.FLAG_PERMISSION_SYSTEM_FIXED
+                            | PackageManager.FLAG_PERMISSION_POLICY_FIXED;
+                    if ((flags & fixedFlags) != 0) {
+                        continue;
+                    }
+
+                    mServiceInternal.grantRuntimePermission(
+                            pkg.packageName, permission, userId, false);
+                    if (DEBUG) {
+                        Log.i(TAG, "Granted " + (systemFixed ? "fixed " : "not fixed ")
+                                + permission + " to default handler " + pkg.packageName);
+                    }
+
+                    int newFlags = PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
+                    if (systemFixed) {
+                        newFlags |= PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
+                    }
+
+                    mServiceInternal.updatePermissionFlagsTEMP(permission, pkg.packageName,
+                            newFlags, newFlags, userId);
+                }
+
+                // If a component gets a permission for being the default handler A
+                // and also default handler B, we grant the weaker grant form.
+                if ((flags & PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT) != 0
+                        && (flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0
+                        && !systemFixed) {
+                    if (DEBUG) {
+                        Log.i(TAG, "Granted not fixed " + permission + " to default handler "
+                                + pkg.packageName);
+                    }
+                    mServiceInternal.updatePermissionFlagsTEMP(permission, pkg.packageName,
+                            PackageManager.FLAG_PERMISSION_SYSTEM_FIXED, 0, userId);
+                }
+            }
+        }
+    }
+
+    private boolean isSysComponentOrPersistentPlatformSignedPrivApp(PackageParser.Package pkg) {
+        if (UserHandle.getAppId(pkg.applicationInfo.uid) < FIRST_APPLICATION_UID) {
+            return true;
+        }
+        if (!pkg.isPrivilegedApp()) {
+            return false;
+        }
+        final PackageParser.Package disabledPkg =
+                mServiceInternal.getDisabledPackage(pkg.packageName);
+        if (disabledPkg != null) {
+            if ((disabledPkg.applicationInfo.flags & ApplicationInfo.FLAG_PERSISTENT) == 0) {
+                return false;
+            }
+        } else if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_PERSISTENT) == 0) {
+            return false;
+        }
+        final String systemPackageName = mServiceInternal.getKnownPackageName(
+                PackageManagerInternal.PACKAGE_SYSTEM, UserHandle.USER_SYSTEM);
+        final PackageParser.Package systemPackage = getPackage(systemPackageName);
+        return PackageManagerService.compareSignatures(systemPackage.mSignatures,
+                pkg.mSignatures) == PackageManager.SIGNATURE_MATCH;
+    }
+
+    private void grantDefaultPermissionExceptions(int userId) {
+        mHandler.removeMessages(MSG_READ_DEFAULT_PERMISSION_EXCEPTIONS);
+
+        synchronized (mLock) {
+            // mGrantExceptions is null only before the first read and then
+            // it serves as a cache of the default grants that should be
+            // performed for every user. If there is an entry then the app
+            // is on the system image and supports runtime permissions.
+            if (mGrantExceptions == null) {
+                mGrantExceptions = readDefaultPermissionExceptionsLocked();
+            }
+        }
+
+        Set<String> permissions = null;
+        final int exceptionCount = mGrantExceptions.size();
+        for (int i = 0; i < exceptionCount; i++) {
+            String packageName = mGrantExceptions.keyAt(i);
+            PackageParser.Package pkg = getSystemPackage(packageName);
+            List<DefaultPermissionGrant> permissionGrants = mGrantExceptions.valueAt(i);
+            final int permissionGrantCount = permissionGrants.size();
+            for (int j = 0; j < permissionGrantCount; j++) {
+                DefaultPermissionGrant permissionGrant = permissionGrants.get(j);
+                if (permissions == null) {
+                    permissions = new ArraySet<>();
+                } else {
+                    permissions.clear();
+                }
+                permissions.add(permissionGrant.name);
+                grantRuntimePermissions(pkg, permissions,
+                        permissionGrant.fixed, userId);
+            }
+        }
+    }
+
+    private File[] getDefaultPermissionFiles() {
+        ArrayList<File> ret = new ArrayList<File>();
+        File dir = new File(Environment.getRootDirectory(), "etc/default-permissions");
+        if (dir.isDirectory() && dir.canRead()) {
+            Collections.addAll(ret, dir.listFiles());
+        }
+        dir = new File(Environment.getVendorDirectory(), "etc/default-permissions");
+        if (dir.isDirectory() && dir.canRead()) {
+            Collections.addAll(ret, dir.listFiles());
+        }
+        return ret.isEmpty() ? null : ret.toArray(new File[0]);
+    }
+
+    private @NonNull ArrayMap<String, List<DefaultPermissionGrant>>
+            readDefaultPermissionExceptionsLocked() {
+        File[] files = getDefaultPermissionFiles();
+        if (files == null) {
+            return new ArrayMap<>(0);
+        }
+
+        ArrayMap<String, List<DefaultPermissionGrant>> grantExceptions = new ArrayMap<>();
+
+        // Iterate over the files in the directory and scan .xml files
+        for (File file : files) {
+            if (!file.getPath().endsWith(".xml")) {
+                Slog.i(TAG, "Non-xml file " + file
+                        + " in " + file.getParent() + " directory, ignoring");
+                continue;
+            }
+            if (!file.canRead()) {
+                Slog.w(TAG, "Default permissions file " + file + " cannot be read");
+                continue;
+            }
+            try (
+                InputStream str = new BufferedInputStream(new FileInputStream(file))
+            ) {
+                XmlPullParser parser = Xml.newPullParser();
+                parser.setInput(str, null);
+                parse(parser, grantExceptions);
+            } catch (XmlPullParserException | IOException e) {
+                Slog.w(TAG, "Error reading default permissions file " + file, e);
+            }
+        }
+
+        return grantExceptions;
+    }
+
+    private void parse(XmlPullParser parser, Map<String, List<DefaultPermissionGrant>>
+            outGrantExceptions) throws IOException, XmlPullParserException {
+        final int outerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+            if (TAG_EXCEPTIONS.equals(parser.getName())) {
+                parseExceptions(parser, outGrantExceptions);
+            } else {
+                Log.e(TAG, "Unknown tag " + parser.getName());
+            }
+        }
+    }
+
+    private void parseExceptions(XmlPullParser parser, Map<String, List<DefaultPermissionGrant>>
+            outGrantExceptions) throws IOException, XmlPullParserException {
+        final int outerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+            if (TAG_EXCEPTION.equals(parser.getName())) {
+                String packageName = parser.getAttributeValue(null, ATTR_PACKAGE);
+
+                List<DefaultPermissionGrant> packageExceptions =
+                        outGrantExceptions.get(packageName);
+                if (packageExceptions == null) {
+                    // The package must be on the system image
+                    PackageParser.Package pkg = getSystemPackage(packageName);
+                    if (pkg == null) {
+                        Log.w(TAG, "Unknown package:" + packageName);
+                        XmlUtils.skipCurrentTag(parser);
+                        continue;
+                    }
+
+                    // The package must support runtime permissions
+                    if (!doesPackageSupportRuntimePermissions(pkg)) {
+                        Log.w(TAG, "Skipping non supporting runtime permissions package:"
+                                + packageName);
+                        XmlUtils.skipCurrentTag(parser);
+                        continue;
+                    }
+                    packageExceptions = new ArrayList<>();
+                    outGrantExceptions.put(packageName, packageExceptions);
+                }
+
+                parsePermission(parser, packageExceptions);
+            } else {
+                Log.e(TAG, "Unknown tag " + parser.getName() + "under <exceptions>");
+            }
+        }
+    }
+
+    private void parsePermission(XmlPullParser parser, List<DefaultPermissionGrant>
+            outPackageExceptions) throws IOException, XmlPullParserException {
+        final int outerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+
+            if (TAG_PERMISSION.contains(parser.getName())) {
+                String name = parser.getAttributeValue(null, ATTR_NAME);
+                if (name == null) {
+                    Log.w(TAG, "Mandatory name attribute missing for permission tag");
+                    XmlUtils.skipCurrentTag(parser);
+                    continue;
+                }
+
+                final boolean fixed = XmlUtils.readBooleanAttribute(parser, ATTR_FIXED);
+
+                DefaultPermissionGrant exception = new DefaultPermissionGrant(name, fixed);
+                outPackageExceptions.add(exception);
+            } else {
+                Log.e(TAG, "Unknown tag " + parser.getName() + "under <exception>");
+            }
+        }
+    }
+
+    private static boolean doesPackageSupportRuntimePermissions(PackageParser.Package pkg) {
+        return pkg.applicationInfo.targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1;
+    }
+
+    private static final class DefaultPermissionGrant {
+        final String name;
+        final boolean fixed;
+
+        public DefaultPermissionGrant(String name, boolean fixed) {
+            this.name = name;
+            this.fixed = fixed;
+        }
+    }
+}
diff --git a/com/android/server/pm/permission/PermissionManagerInternal.java b/com/android/server/pm/permission/PermissionManagerInternal.java
new file mode 100644
index 0000000..3b20b42
--- /dev/null
+++ b/com/android/server/pm/permission/PermissionManagerInternal.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.permission;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.PackageParser;
+import android.content.pm.PermissionInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.PackageManager.PermissionInfoFlags;
+import android.content.pm.PackageParser.Permission;
+
+import com.android.server.pm.SharedUserSetting;
+import com.android.server.pm.permission.PermissionManagerInternal.PermissionCallback;
+
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Internal interfaces to be used by other components within the system server.
+ */
+public abstract class PermissionManagerInternal {
+    /**
+     * Callbacks invoked when interesting actions have been taken on a permission.
+     * <p>
+     * NOTE: The current arguments are merely to support the existing use cases. This
+     * needs to be properly thought out with appropriate arguments for each of the
+     * callback methods.
+     */
+    public static class PermissionCallback {
+        public void onGidsChanged(int appId, int userId) {
+        }
+        public void onPermissionChanged() {
+        }
+        public void onPermissionGranted(int uid, int userId) {
+        }
+        public void onInstallPermissionGranted() {
+        }
+        public void onPermissionRevoked(int uid, int userId) {
+        }
+        public void onInstallPermissionRevoked() {
+        }
+        public void onPermissionUpdated(int userId) {
+        }
+        public void onPermissionRemoved() {
+        }
+        public void onInstallPermissionUpdated() {
+        }
+    }
+
+    public abstract void grantRuntimePermission(
+            @NonNull String permName, @NonNull String packageName, boolean overridePolicy,
+            int callingUid, int userId, @Nullable PermissionCallback callback);
+    public abstract void grantRuntimePermissionsGrantedToDisabledPackage(
+            @NonNull PackageParser.Package pkg, int callingUid,
+            @Nullable PermissionCallback callback);
+    public abstract void grantRequestedRuntimePermissions(
+            @NonNull PackageParser.Package pkg, @NonNull int[] userIds,
+            @NonNull String[] grantedPermissions, int callingUid,
+            @Nullable PermissionCallback callback);
+    public abstract void revokeRuntimePermission(@NonNull String permName,
+            @NonNull String packageName, boolean overridePolicy, int callingUid, int userId,
+            @Nullable PermissionCallback callback);
+    public abstract int[] revokeUnusedSharedUserPermissions(@NonNull SharedUserSetting suSetting,
+            @NonNull int[] allUserIds);
+
+
+    public abstract boolean addPermission(@NonNull PermissionInfo info, boolean async,
+            int callingUid, @Nullable PermissionCallback callback);
+    public abstract void removePermission(@NonNull String permName, int callingUid,
+            @Nullable PermissionCallback callback);
+
+    public abstract int getPermissionFlags(@NonNull String permName,
+            @NonNull String packageName, int callingUid, int userId);
+    /**
+     * Retrieve all of the information we know about a particular permission.
+     */
+    public abstract @Nullable PermissionInfo getPermissionInfo(@NonNull String permName,
+            @NonNull String packageName, @PermissionInfoFlags int flags, int callingUid);
+    /**
+     * Retrieve all of the permissions associated with a particular group.
+     */
+    public abstract @Nullable List<PermissionInfo> getPermissionInfoByGroup(@NonNull String group,
+            @PermissionInfoFlags int flags, int callingUid);
+    public abstract boolean isPermissionAppOp(@NonNull String permName);
+    public abstract boolean isPermissionInstant(@NonNull String permName);
+
+    /**
+     * Updates the flags associated with a permission by replacing the flags in
+     * the specified mask with the provided flag values.
+     */
+    public abstract void updatePermissionFlags(@NonNull String permName,
+            @NonNull String packageName, int flagMask, int flagValues, int callingUid, int userId,
+            @Nullable PermissionCallback callback);
+    /**
+     * Updates the flags for all applications by replacing the flags in the specified mask
+     * with the provided flag values.
+     */
+    public abstract boolean updatePermissionFlagsForAllApps(int flagMask, int flagValues,
+            int callingUid, int userId, @NonNull Collection<PackageParser.Package> packages,
+            @Nullable PermissionCallback callback);
+
+    public abstract int checkPermission(@NonNull String permName, @NonNull String packageName,
+            int callingUid, int userId);
+
+    /**
+     * Enforces the request is from the system or an app that has INTERACT_ACROSS_USERS
+     * or INTERACT_ACROSS_USERS_FULL permissions, if the {@code userid} is not for the caller.
+     * @param checkShell whether to prevent shell from access if there's a debugging restriction
+     * @param message the message to log on security exception
+     */
+    public abstract void enforceCrossUserPermission(int callingUid, int userId,
+            boolean requireFullPermission, boolean checkShell, @NonNull String message);
+    public abstract void enforceGrantRevokeRuntimePermissionPermissions(@NonNull String message);
+
+    public abstract @NonNull PermissionSettings getPermissionSettings();
+    public abstract @NonNull DefaultPermissionGrantPolicy getDefaultPermissionGrantPolicy();
+
+    /** HACK HACK methods to allow for partial migration of data to the PermissionManager class */
+    public abstract Iterator<BasePermission> getPermissionIteratorTEMP();
+    public abstract @Nullable BasePermission getPermissionTEMP(@NonNull String permName);
+    public abstract void putPermissionTEMP(@NonNull String permName,
+            @NonNull BasePermission permission);
+}
\ No newline at end of file
diff --git a/com/android/server/pm/permission/PermissionManagerService.java b/com/android/server/pm/permission/PermissionManagerService.java
new file mode 100644
index 0000000..6c031a6
--- /dev/null
+++ b/com/android/server/pm/permission/PermissionManagerService.java
@@ -0,0 +1,1081 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.permission;
+
+import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
+import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.PackageParser;
+import android.content.pm.ParceledListSlice;
+import android.content.pm.PermissionInfo;
+import android.content.pm.PackageParser.Package;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Process;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.os.UserManagerInternal;
+import android.os.storage.StorageManagerInternal;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.R;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+import com.android.internal.util.ArrayUtils;
+import com.android.server.FgThread;
+import com.android.server.LocalServices;
+import com.android.server.ServiceThread;
+import com.android.server.SystemConfig;
+import com.android.server.Watchdog;
+import com.android.server.pm.PackageManagerService;
+import com.android.server.pm.PackageManagerServiceUtils;
+import com.android.server.pm.PackageSetting;
+import com.android.server.pm.ProcessLoggingHandler;
+import com.android.server.pm.SharedUserSetting;
+import com.android.server.pm.permission.DefaultPermissionGrantPolicy.DefaultPermissionGrantedCallback;
+import com.android.server.pm.permission.PermissionManagerInternal.PermissionCallback;
+import com.android.server.pm.permission.PermissionsState.PermissionState;
+
+import libcore.util.EmptyArray;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Manages all permissions and handles permissions related tasks.
+ */
+public class PermissionManagerService {
+    private static final String TAG = "PackageManager";
+
+    /** All dangerous permission names in the same order as the events in MetricsEvent */
+    private static final List<String> ALL_DANGEROUS_PERMISSIONS = Arrays.asList(
+            Manifest.permission.READ_CALENDAR,
+            Manifest.permission.WRITE_CALENDAR,
+            Manifest.permission.CAMERA,
+            Manifest.permission.READ_CONTACTS,
+            Manifest.permission.WRITE_CONTACTS,
+            Manifest.permission.GET_ACCOUNTS,
+            Manifest.permission.ACCESS_FINE_LOCATION,
+            Manifest.permission.ACCESS_COARSE_LOCATION,
+            Manifest.permission.RECORD_AUDIO,
+            Manifest.permission.READ_PHONE_STATE,
+            Manifest.permission.CALL_PHONE,
+            Manifest.permission.READ_CALL_LOG,
+            Manifest.permission.WRITE_CALL_LOG,
+            Manifest.permission.ADD_VOICEMAIL,
+            Manifest.permission.USE_SIP,
+            Manifest.permission.PROCESS_OUTGOING_CALLS,
+            Manifest.permission.READ_CELL_BROADCASTS,
+            Manifest.permission.BODY_SENSORS,
+            Manifest.permission.SEND_SMS,
+            Manifest.permission.RECEIVE_SMS,
+            Manifest.permission.READ_SMS,
+            Manifest.permission.RECEIVE_WAP_PUSH,
+            Manifest.permission.RECEIVE_MMS,
+            Manifest.permission.READ_EXTERNAL_STORAGE,
+            Manifest.permission.WRITE_EXTERNAL_STORAGE,
+            Manifest.permission.READ_PHONE_NUMBERS,
+            Manifest.permission.ANSWER_PHONE_CALLS);
+
+    /** Cap the size of permission trees that 3rd party apps can define */
+    private static final int MAX_PERMISSION_TREE_FOOTPRINT = 32768;     // characters of text
+
+    /** Lock to protect internal data access */
+    private final Object mLock;
+
+    /** Internal connection to the package manager */
+    private final PackageManagerInternal mPackageManagerInt;
+
+    /** Internal connection to the user manager */
+    private final UserManagerInternal mUserManagerInt;
+
+    /** Default permission policy to provide proper behaviour out-of-the-box */
+    private final DefaultPermissionGrantPolicy mDefaultPermissionGrantPolicy;
+
+    /** Internal storage for permissions and related settings */
+    private final PermissionSettings mSettings;
+
+    private final HandlerThread mHandlerThread;
+    private final Handler mHandler;
+    private final Context mContext;
+
+    PermissionManagerService(Context context,
+            @Nullable DefaultPermissionGrantedCallback defaultGrantCallback,
+            @NonNull Object externalLock) {
+        mContext = context;
+        mLock = externalLock;
+        mPackageManagerInt = LocalServices.getService(PackageManagerInternal.class);
+        mUserManagerInt = LocalServices.getService(UserManagerInternal.class);
+        mSettings = new PermissionSettings(context, mLock);
+
+        mHandlerThread = new ServiceThread(TAG,
+                Process.THREAD_PRIORITY_BACKGROUND, true /*allowIo*/);
+        mHandlerThread.start();
+        mHandler = new Handler(mHandlerThread.getLooper());
+        Watchdog.getInstance().addThread(mHandler);
+
+        mDefaultPermissionGrantPolicy = new DefaultPermissionGrantPolicy(
+                context, mHandlerThread.getLooper(), defaultGrantCallback, this);
+
+        // propagate permission configuration
+        final ArrayMap<String, SystemConfig.PermissionEntry> permConfig =
+                SystemConfig.getInstance().getPermissions();
+        synchronized (mLock) {
+            for (int i=0; i<permConfig.size(); i++) {
+                final SystemConfig.PermissionEntry perm = permConfig.valueAt(i);
+                BasePermission bp = mSettings.getPermissionLocked(perm.name);
+                if (bp == null) {
+                    bp = new BasePermission(perm.name, "android", BasePermission.TYPE_BUILTIN);
+                    mSettings.putPermissionLocked(perm.name, bp);
+                }
+                if (perm.gids != null) {
+                    bp.setGids(perm.gids, perm.perUser);
+                }
+            }
+        }
+
+        LocalServices.addService(
+                PermissionManagerInternal.class, new PermissionManagerInternalImpl());
+    }
+
+    /**
+     * Creates and returns an initialized, internal service for use by other components.
+     * <p>
+     * The object returned is identical to the one returned by the LocalServices class using:
+     * {@code LocalServices.getService(PermissionManagerInternal.class);}
+     * <p>
+     * NOTE: The external lock is temporary and should be removed. This needs to be a
+     * lock created by the permission manager itself.
+     */
+    public static PermissionManagerInternal create(Context context,
+            @Nullable DefaultPermissionGrantedCallback defaultGrantCallback,
+            @NonNull Object externalLock) {
+        final PermissionManagerInternal permMgrInt =
+                LocalServices.getService(PermissionManagerInternal.class);
+        if (permMgrInt != null) {
+            return permMgrInt;
+        }
+        new PermissionManagerService(context, defaultGrantCallback, externalLock);
+        return LocalServices.getService(PermissionManagerInternal.class);
+    }
+
+    @Nullable BasePermission getPermission(String permName) {
+        synchronized (mLock) {
+            return mSettings.getPermissionLocked(permName);
+        }
+    }
+
+    private int checkPermission(String permName, String pkgName, int callingUid, int userId) {
+        if (!mUserManagerInt.exists(userId)) {
+            return PackageManager.PERMISSION_DENIED;
+        }
+
+        final PackageParser.Package pkg = mPackageManagerInt.getPackage(pkgName);
+        if (pkg != null && pkg.mExtras != null) {
+            if (mPackageManagerInt.filterAppAccess(pkg, callingUid, userId)) {
+                return PackageManager.PERMISSION_DENIED;
+            }
+            final PackageSetting ps = (PackageSetting) pkg.mExtras;
+            final boolean instantApp = ps.getInstantApp(userId);
+            final PermissionsState permissionsState = ps.getPermissionsState();
+            if (permissionsState.hasPermission(permName, userId)) {
+                if (instantApp) {
+                    synchronized (mLock) {
+                        BasePermission bp = mSettings.getPermissionLocked(permName);
+                        if (bp != null && bp.isInstant()) {
+                            return PackageManager.PERMISSION_GRANTED;
+                        }
+                    }
+                } else {
+                    return PackageManager.PERMISSION_GRANTED;
+                }
+            }
+            // Special case: ACCESS_FINE_LOCATION permission includes ACCESS_COARSE_LOCATION
+            if (Manifest.permission.ACCESS_COARSE_LOCATION.equals(permName) && permissionsState
+                    .hasPermission(Manifest.permission.ACCESS_FINE_LOCATION, userId)) {
+                return PackageManager.PERMISSION_GRANTED;
+            }
+        }
+
+        return PackageManager.PERMISSION_DENIED;
+    }
+
+    private PermissionInfo getPermissionInfo(String name, String packageName, int flags,
+            int callingUid) {
+        if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) {
+            return null;
+        }
+        // reader
+        synchronized (mLock) {
+            final BasePermission bp = mSettings.getPermissionLocked(name);
+            if (bp == null) {
+                return null;
+            }
+            final int adjustedProtectionLevel = adjustPermissionProtectionFlagsLocked(
+                    bp.getProtectionLevel(), packageName, callingUid);
+            return bp.generatePermissionInfo(adjustedProtectionLevel, flags);
+        }
+    }
+
+    private List<PermissionInfo> getPermissionInfoByGroup(
+            String groupName, int flags, int callingUid) {
+        if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) {
+            return null;
+        }
+        // reader
+        synchronized (mLock) {
+            // TODO Uncomment when mPermissionGroups moves to this class
+//            if (groupName != null && !mPermissionGroups.containsKey(groupName)) {
+//                // This is thrown as NameNotFoundException
+//                return null;
+//            }
+
+            final ArrayList<PermissionInfo> out = new ArrayList<PermissionInfo>(10);
+            for (BasePermission bp : mSettings.getAllPermissionsLocked()) {
+                final PermissionInfo pi = bp.generatePermissionInfo(groupName, flags);
+                if (pi != null) {
+                    out.add(pi);
+                }
+            }
+            return out;
+        }
+    }
+
+    private int adjustPermissionProtectionFlagsLocked(
+            int protectionLevel, String packageName, int uid) {
+        // Signature permission flags area always reported
+        final int protectionLevelMasked = protectionLevel
+                & (PermissionInfo.PROTECTION_NORMAL
+                | PermissionInfo.PROTECTION_DANGEROUS
+                | PermissionInfo.PROTECTION_SIGNATURE);
+        if (protectionLevelMasked == PermissionInfo.PROTECTION_SIGNATURE) {
+            return protectionLevel;
+        }
+        // System sees all flags.
+        final int appId = UserHandle.getAppId(uid);
+        if (appId == Process.SYSTEM_UID || appId == Process.ROOT_UID
+                || appId == Process.SHELL_UID) {
+            return protectionLevel;
+        }
+        // Normalize package name to handle renamed packages and static libs
+        final PackageParser.Package pkg = mPackageManagerInt.getPackage(packageName);
+        if (pkg == null) {
+            return protectionLevel;
+        }
+        if (pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.O) {
+            return protectionLevelMasked;
+        }
+        // Apps that target O see flags for all protection levels.
+        final PackageSetting ps = (PackageSetting) pkg.mExtras;
+        if (ps == null) {
+            return protectionLevel;
+        }
+        if (ps.getAppId() != appId) {
+            return protectionLevel;
+        }
+        return protectionLevel;
+    }
+
+    private boolean addPermission(
+            PermissionInfo info, int callingUid, PermissionCallback callback) {
+        if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) {
+            throw new SecurityException("Instant apps can't add permissions");
+        }
+        if (info.labelRes == 0 && info.nonLocalizedLabel == null) {
+            throw new SecurityException("Label must be specified in permission");
+        }
+        final BasePermission tree = (BasePermission) mPackageManagerInt.enforcePermissionTreeTEMP(
+                info.name, callingUid);
+        final boolean added;
+        final boolean changed;
+        synchronized (mLock) {
+            BasePermission bp = mSettings.getPermissionLocked(info.name);
+            added = bp == null;
+            int fixedLevel = PermissionInfo.fixProtectionLevel(info.protectionLevel);
+            if (added) {
+                enforcePermissionCapLocked(info, tree);
+                bp = new BasePermission(info.name, tree.getSourcePackageName(),
+                        BasePermission.TYPE_DYNAMIC);
+            } else if (bp.isDynamic()) {
+                throw new SecurityException(
+                        "Not allowed to modify non-dynamic permission "
+                        + info.name);
+            }
+            changed = bp.addToTree(fixedLevel, info, tree);
+            if (added) {
+                mSettings.putPermissionLocked(info.name, bp);
+            }
+        }
+        if (changed && callback != null) {
+            callback.onPermissionChanged();
+        }
+        return added;
+    }
+
+    private void removePermission(
+            String permName, int callingUid, PermissionCallback callback) {
+        if (mPackageManagerInt.getInstantAppPackageName(callingUid) != null) {
+            throw new SecurityException("Instant applications don't have access to this method");
+        }
+        final BasePermission tree = (BasePermission) mPackageManagerInt.enforcePermissionTreeTEMP(
+                permName, callingUid);
+        synchronized (mLock) {
+            final BasePermission bp = mSettings.getPermissionLocked(permName);
+            if (bp == null) {
+                return;
+            }
+            if (bp.isDynamic()) {
+                throw new SecurityException(
+                        "Not allowed to modify non-dynamic permission "
+                        + permName);
+            }
+            mSettings.removePermissionLocked(permName);
+            if (callback != null) {
+                callback.onPermissionRemoved();
+            }
+        }
+    }
+
+    private void grantRuntimePermissionsGrantedToDisabledPackageLocked(
+            PackageParser.Package pkg, int callingUid, PermissionCallback callback) {
+        if (pkg.parentPackage == null) {
+            return;
+        }
+        if (pkg.requestedPermissions == null) {
+            return;
+        }
+        final PackageParser.Package disabledPkg =
+                mPackageManagerInt.getDisabledPackage(pkg.parentPackage.packageName);
+        if (disabledPkg == null || disabledPkg.mExtras == null) {
+            return;
+        }
+        final PackageSetting disabledPs = (PackageSetting) disabledPkg.mExtras;
+        if (!disabledPs.isPrivileged() || disabledPs.hasChildPackages()) {
+            return;
+        }
+        final int permCount = pkg.requestedPermissions.size();
+        for (int i = 0; i < permCount; i++) {
+            String permission = pkg.requestedPermissions.get(i);
+            BasePermission bp = mSettings.getPermissionLocked(permission);
+            if (bp == null || !(bp.isRuntime() || bp.isDevelopment())) {
+                continue;
+            }
+            for (int userId : mUserManagerInt.getUserIds()) {
+                if (disabledPs.getPermissionsState().hasRuntimePermission(permission, userId)) {
+                    grantRuntimePermission(
+                            permission, pkg.packageName, false, callingUid, userId, callback);
+                }
+            }
+        }
+    }
+
+    private void grantRequestedRuntimePermissions(PackageParser.Package pkg, int[] userIds,
+            String[] grantedPermissions, int callingUid, PermissionCallback callback) {
+        for (int userId : userIds) {
+            grantRequestedRuntimePermissionsForUser(pkg, userId, grantedPermissions, callingUid,
+                    callback);
+        }
+    }
+
+    private void grantRequestedRuntimePermissionsForUser(PackageParser.Package pkg, int userId,
+            String[] grantedPermissions, int callingUid, PermissionCallback callback) {
+        PackageSetting ps = (PackageSetting) pkg.mExtras;
+        if (ps == null) {
+            return;
+        }
+
+        PermissionsState permissionsState = ps.getPermissionsState();
+
+        final int immutableFlags = PackageManager.FLAG_PERMISSION_SYSTEM_FIXED
+                | PackageManager.FLAG_PERMISSION_POLICY_FIXED;
+
+        final boolean supportsRuntimePermissions = pkg.applicationInfo.targetSdkVersion
+                >= Build.VERSION_CODES.M;
+
+        final boolean instantApp = mPackageManagerInt.isInstantApp(pkg.packageName, userId);
+
+        for (String permission : pkg.requestedPermissions) {
+            final BasePermission bp;
+            synchronized (mLock) {
+                bp = mSettings.getPermissionLocked(permission);
+            }
+            if (bp != null && (bp.isRuntime() || bp.isDevelopment())
+                    && (!instantApp || bp.isInstant())
+                    && (supportsRuntimePermissions || !bp.isRuntimeOnly())
+                    && (grantedPermissions == null
+                           || ArrayUtils.contains(grantedPermissions, permission))) {
+                final int flags = permissionsState.getPermissionFlags(permission, userId);
+                if (supportsRuntimePermissions) {
+                    // Installer cannot change immutable permissions.
+                    if ((flags & immutableFlags) == 0) {
+                        grantRuntimePermission(permission, pkg.packageName, false, callingUid,
+                                userId, callback);
+                    }
+                } else if (mSettings.mPermissionReviewRequired) {
+                    // In permission review mode we clear the review flag when we
+                    // are asked to install the app with all permissions granted.
+                    if ((flags & PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED) != 0) {
+                        updatePermissionFlags(permission, pkg.packageName,
+                                PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED, 0, callingUid,
+                                userId, callback);
+                    }
+                }
+            }
+        }
+    }
+
+    private void grantRuntimePermission(String permName, String packageName, boolean overridePolicy,
+            int callingUid, final int userId, PermissionCallback callback) {
+        if (!mUserManagerInt.exists(userId)) {
+            Log.e(TAG, "No such user:" + userId);
+            return;
+        }
+
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
+                "grantRuntimePermission");
+
+        enforceCrossUserPermission(callingUid, userId,
+                true /* requireFullPermission */, true /* checkShell */,
+                "grantRuntimePermission");
+
+        final PackageParser.Package pkg = mPackageManagerInt.getPackage(packageName);
+        if (pkg == null || pkg.mExtras == null) {
+            throw new IllegalArgumentException("Unknown package: " + packageName);
+        }
+        final BasePermission bp;
+        synchronized(mLock) {
+            bp = mSettings.getPermissionLocked(permName);
+        }
+        if (bp == null) {
+            throw new IllegalArgumentException("Unknown permission: " + permName);
+        }
+        if (mPackageManagerInt.filterAppAccess(pkg, callingUid, userId)) {
+            throw new IllegalArgumentException("Unknown package: " + packageName);
+        }
+
+        bp.enforceDeclaredUsedAndRuntimeOrDevelopment(pkg);
+
+        // If a permission review is required for legacy apps we represent
+        // their permissions as always granted runtime ones since we need
+        // to keep the review required permission flag per user while an
+        // install permission's state is shared across all users.
+        if (mSettings.mPermissionReviewRequired
+                && pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M
+                && bp.isRuntime()) {
+            return;
+        }
+
+        final int uid = UserHandle.getUid(userId, pkg.applicationInfo.uid);
+
+        final PackageSetting ps = (PackageSetting) pkg.mExtras;
+        final PermissionsState permissionsState = ps.getPermissionsState();
+
+        final int flags = permissionsState.getPermissionFlags(permName, userId);
+        if ((flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0) {
+            throw new SecurityException("Cannot grant system fixed permission "
+                    + permName + " for package " + packageName);
+        }
+        if (!overridePolicy && (flags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0) {
+            throw new SecurityException("Cannot grant policy fixed permission "
+                    + permName + " for package " + packageName);
+        }
+
+        if (bp.isDevelopment()) {
+            // Development permissions must be handled specially, since they are not
+            // normal runtime permissions.  For now they apply to all users.
+            if (permissionsState.grantInstallPermission(bp) !=
+                    PermissionsState.PERMISSION_OPERATION_FAILURE) {
+                if (callback != null) {
+                    callback.onInstallPermissionGranted();
+                }
+            }
+            return;
+        }
+
+        if (ps.getInstantApp(userId) && !bp.isInstant()) {
+            throw new SecurityException("Cannot grant non-ephemeral permission"
+                    + permName + " for package " + packageName);
+        }
+
+        if (pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M) {
+            Slog.w(TAG, "Cannot grant runtime permission to a legacy app");
+            return;
+        }
+
+        final int result = permissionsState.grantRuntimePermission(bp, userId);
+        switch (result) {
+            case PermissionsState.PERMISSION_OPERATION_FAILURE: {
+                return;
+            }
+
+            case PermissionsState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED: {
+                if (callback != null) {
+                    callback.onGidsChanged(UserHandle.getAppId(pkg.applicationInfo.uid), userId);
+                }
+            }
+            break;
+        }
+
+        if (bp.isRuntime()) {
+            logPermissionGranted(mContext, permName, packageName);
+        }
+
+        if (callback != null) {
+            callback.onPermissionGranted(uid, userId);
+        }
+
+        // Only need to do this if user is initialized. Otherwise it's a new user
+        // and there are no processes running as the user yet and there's no need
+        // to make an expensive call to remount processes for the changed permissions.
+        if (READ_EXTERNAL_STORAGE.equals(permName)
+                || WRITE_EXTERNAL_STORAGE.equals(permName)) {
+            final long token = Binder.clearCallingIdentity();
+            try {
+                if (mUserManagerInt.isUserInitialized(userId)) {
+                    StorageManagerInternal storageManagerInternal = LocalServices.getService(
+                            StorageManagerInternal.class);
+                    storageManagerInternal.onExternalStoragePolicyChanged(uid, packageName);
+                }
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
+    }
+
+    private void revokeRuntimePermission(String permName, String packageName,
+            boolean overridePolicy, int callingUid, int userId, PermissionCallback callback) {
+        if (!mUserManagerInt.exists(userId)) {
+            Log.e(TAG, "No such user:" + userId);
+            return;
+        }
+
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS,
+                "revokeRuntimePermission");
+
+        enforceCrossUserPermission(Binder.getCallingUid(), userId,
+                true /* requireFullPermission */, true /* checkShell */,
+                "revokeRuntimePermission");
+
+        final int appId;
+
+        final PackageParser.Package pkg = mPackageManagerInt.getPackage(packageName);
+        if (pkg == null || pkg.mExtras == null) {
+            throw new IllegalArgumentException("Unknown package: " + packageName);
+        }
+        if (mPackageManagerInt.filterAppAccess(pkg, Binder.getCallingUid(), userId)) {
+            throw new IllegalArgumentException("Unknown package: " + packageName);
+        }
+        final BasePermission bp = mSettings.getPermissionLocked(permName);
+        if (bp == null) {
+            throw new IllegalArgumentException("Unknown permission: " + permName);
+        }
+
+        bp.enforceDeclaredUsedAndRuntimeOrDevelopment(pkg);
+
+        // If a permission review is required for legacy apps we represent
+        // their permissions as always granted runtime ones since we need
+        // to keep the review required permission flag per user while an
+        // install permission's state is shared across all users.
+        if (mSettings.mPermissionReviewRequired
+                && pkg.applicationInfo.targetSdkVersion < Build.VERSION_CODES.M
+                && bp.isRuntime()) {
+            return;
+        }
+
+        final PackageSetting ps = (PackageSetting) pkg.mExtras;
+        final PermissionsState permissionsState = ps.getPermissionsState();
+
+        final int flags = permissionsState.getPermissionFlags(permName, userId);
+        if ((flags & PackageManager.FLAG_PERMISSION_SYSTEM_FIXED) != 0) {
+            throw new SecurityException("Cannot revoke system fixed permission "
+                    + permName + " for package " + packageName);
+        }
+        if (!overridePolicy && (flags & PackageManager.FLAG_PERMISSION_POLICY_FIXED) != 0) {
+            throw new SecurityException("Cannot revoke policy fixed permission "
+                    + permName + " for package " + packageName);
+        }
+
+        if (bp.isDevelopment()) {
+            // Development permissions must be handled specially, since they are not
+            // normal runtime permissions.  For now they apply to all users.
+            if (permissionsState.revokeInstallPermission(bp) !=
+                    PermissionsState.PERMISSION_OPERATION_FAILURE) {
+                if (callback != null) {
+                    callback.onInstallPermissionRevoked();
+                }
+            }
+            return;
+        }
+
+        if (permissionsState.revokeRuntimePermission(bp, userId) ==
+                PermissionsState.PERMISSION_OPERATION_FAILURE) {
+            return;
+        }
+
+        if (bp.isRuntime()) {
+            logPermissionRevoked(mContext, permName, packageName);
+        }
+
+        if (callback != null) {
+            final int uid = UserHandle.getUid(userId, pkg.applicationInfo.uid);
+            callback.onPermissionRevoked(pkg.applicationInfo.uid, userId);
+        }
+    }
+
+    private int[] revokeUnusedSharedUserPermissions(SharedUserSetting suSetting, int[] allUserIds) {
+        // Collect all used permissions in the UID
+        final ArraySet<String> usedPermissions = new ArraySet<>();
+        final List<PackageParser.Package> pkgList = suSetting.getPackages();
+        if (pkgList == null || pkgList.size() == 0) {
+            return EmptyArray.INT;
+        }
+        for (PackageParser.Package pkg : pkgList) {
+            final int requestedPermCount = pkg.requestedPermissions.size();
+            for (int j = 0; j < requestedPermCount; j++) {
+                String permission = pkg.requestedPermissions.get(j);
+                BasePermission bp = mSettings.getPermissionLocked(permission);
+                if (bp != null) {
+                    usedPermissions.add(permission);
+                }
+            }
+        }
+
+        PermissionsState permissionsState = suSetting.getPermissionsState();
+        // Prune install permissions
+        List<PermissionState> installPermStates = permissionsState.getInstallPermissionStates();
+        final int installPermCount = installPermStates.size();
+        for (int i = installPermCount - 1; i >= 0;  i--) {
+            PermissionState permissionState = installPermStates.get(i);
+            if (!usedPermissions.contains(permissionState.getName())) {
+                BasePermission bp = mSettings.getPermissionLocked(permissionState.getName());
+                if (bp != null) {
+                    permissionsState.revokeInstallPermission(bp);
+                    permissionsState.updatePermissionFlags(bp, UserHandle.USER_ALL,
+                            PackageManager.MASK_PERMISSION_FLAGS, 0);
+                }
+            }
+        }
+
+        int[] runtimePermissionChangedUserIds = EmptyArray.INT;
+
+        // Prune runtime permissions
+        for (int userId : allUserIds) {
+            List<PermissionState> runtimePermStates = permissionsState
+                    .getRuntimePermissionStates(userId);
+            final int runtimePermCount = runtimePermStates.size();
+            for (int i = runtimePermCount - 1; i >= 0; i--) {
+                PermissionState permissionState = runtimePermStates.get(i);
+                if (!usedPermissions.contains(permissionState.getName())) {
+                    BasePermission bp = mSettings.getPermissionLocked(permissionState.getName());
+                    if (bp != null) {
+                        permissionsState.revokeRuntimePermission(bp, userId);
+                        permissionsState.updatePermissionFlags(bp, userId,
+                                PackageManager.MASK_PERMISSION_FLAGS, 0);
+                        runtimePermissionChangedUserIds = ArrayUtils.appendInt(
+                                runtimePermissionChangedUserIds, userId);
+                    }
+                }
+            }
+        }
+
+        return runtimePermissionChangedUserIds;
+    }
+
+    private int getPermissionFlags(String permName, String packageName, int callingUid, int userId) {
+        if (!mUserManagerInt.exists(userId)) {
+            return 0;
+        }
+
+        enforceGrantRevokeRuntimePermissionPermissions("getPermissionFlags");
+
+        enforceCrossUserPermission(callingUid, userId,
+                true /* requireFullPermission */, false /* checkShell */,
+                "getPermissionFlags");
+
+        final PackageParser.Package pkg = mPackageManagerInt.getPackage(packageName);
+        if (pkg == null || pkg.mExtras == null) {
+            return 0;
+        }
+        synchronized (mLock) {
+            if (mSettings.getPermissionLocked(permName) == null) {
+                return 0;
+            }
+        }
+        if (mPackageManagerInt.filterAppAccess(pkg, callingUid, userId)) {
+            return 0;
+        }
+        final PackageSetting ps = (PackageSetting) pkg.mExtras;
+        PermissionsState permissionsState = ps.getPermissionsState();
+        return permissionsState.getPermissionFlags(permName, userId);
+    }
+
+    private void updatePermissionFlags(String permName, String packageName, int flagMask,
+            int flagValues, int callingUid, int userId, PermissionCallback callback) {
+        if (!mUserManagerInt.exists(userId)) {
+            return;
+        }
+
+        enforceGrantRevokeRuntimePermissionPermissions("updatePermissionFlags");
+
+        enforceCrossUserPermission(callingUid, userId,
+                true /* requireFullPermission */, true /* checkShell */,
+                "updatePermissionFlags");
+
+        // Only the system can change these flags and nothing else.
+        if (callingUid != Process.SYSTEM_UID) {
+            flagMask &= ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
+            flagValues &= ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
+            flagMask &= ~PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
+            flagValues &= ~PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT;
+            flagValues &= ~PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED;
+        }
+
+        final PackageParser.Package pkg = mPackageManagerInt.getPackage(packageName);
+        if (pkg == null || pkg.mExtras == null) {
+            throw new IllegalArgumentException("Unknown package: " + packageName);
+        }
+        if (mPackageManagerInt.filterAppAccess(pkg, callingUid, userId)) {
+            throw new IllegalArgumentException("Unknown package: " + packageName);
+        }
+
+        final BasePermission bp;
+        synchronized (mLock) {
+            bp = mSettings.getPermissionLocked(permName);
+        }
+        if (bp == null) {
+            throw new IllegalArgumentException("Unknown permission: " + permName);
+        }
+
+        final PackageSetting ps = (PackageSetting) pkg.mExtras;
+        final PermissionsState permissionsState = ps.getPermissionsState();
+        final boolean hadState =
+                permissionsState.getRuntimePermissionState(permName, userId) != null;
+        final boolean permissionUpdated =
+                permissionsState.updatePermissionFlags(bp, userId, flagMask, flagValues);
+        if (permissionUpdated && callback != null) {
+            // Install and runtime permissions are stored in different places,
+            // so figure out what permission changed and persist the change.
+            if (permissionsState.getInstallPermissionState(permName) != null) {
+                callback.onInstallPermissionUpdated();
+            } else if (permissionsState.getRuntimePermissionState(permName, userId) != null
+                    || hadState) {
+                callback.onPermissionUpdated(userId);
+            }
+        }
+    }
+
+    private boolean updatePermissionFlagsForAllApps(int flagMask, int flagValues, int callingUid,
+            int userId, Collection<Package> packages, PermissionCallback callback) {
+        if (!mUserManagerInt.exists(userId)) {
+            return false;
+        }
+
+        enforceGrantRevokeRuntimePermissionPermissions(
+                "updatePermissionFlagsForAllApps");
+        enforceCrossUserPermission(callingUid, userId,
+                true /* requireFullPermission */, true /* checkShell */,
+                "updatePermissionFlagsForAllApps");
+
+        // Only the system can change system fixed flags.
+        if (callingUid != Process.SYSTEM_UID) {
+            flagMask &= ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
+            flagValues &= ~PackageManager.FLAG_PERMISSION_SYSTEM_FIXED;
+        }
+
+        boolean changed = false;
+        for (PackageParser.Package pkg : packages) {
+            final PackageSetting ps = (PackageSetting) pkg.mExtras;
+            if (ps == null) {
+                continue;
+            }
+            PermissionsState permissionsState = ps.getPermissionsState();
+            changed |= permissionsState.updatePermissionFlagsForAllPermissions(
+                    userId, flagMask, flagValues);
+        }
+        return changed;
+    }
+
+    private void enforceGrantRevokeRuntimePermissionPermissions(String message) {
+        if (mContext.checkCallingOrSelfPermission(Manifest.permission.GRANT_RUNTIME_PERMISSIONS)
+                != PackageManager.PERMISSION_GRANTED
+            && mContext.checkCallingOrSelfPermission(Manifest.permission.REVOKE_RUNTIME_PERMISSIONS)
+                != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException(message + " requires "
+                    + Manifest.permission.GRANT_RUNTIME_PERMISSIONS + " or "
+                    + Manifest.permission.REVOKE_RUNTIME_PERMISSIONS);
+        }
+    }
+
+    /**
+     * Checks if the request is from the system or an app that has INTERACT_ACROSS_USERS
+     * or INTERACT_ACROSS_USERS_FULL permissions, if the userid is not for the caller.
+     * @param checkShell whether to prevent shell from access if there's a debugging restriction
+     * @param message the message to log on security exception
+     */
+    private void enforceCrossUserPermission(int callingUid, int userId,
+            boolean requireFullPermission, boolean checkShell, String message) {
+        if (userId < 0) {
+            throw new IllegalArgumentException("Invalid userId " + userId);
+        }
+        if (checkShell) {
+            PackageManagerServiceUtils.enforceShellRestriction(
+                    UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, userId);
+        }
+        if (userId == UserHandle.getUserId(callingUid)) return;
+        if (callingUid != Process.SYSTEM_UID && callingUid != 0) {
+            if (requireFullPermission) {
+                mContext.enforceCallingOrSelfPermission(
+                        android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, message);
+            } else {
+                try {
+                    mContext.enforceCallingOrSelfPermission(
+                            android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, message);
+                } catch (SecurityException se) {
+                    mContext.enforceCallingOrSelfPermission(
+                            android.Manifest.permission.INTERACT_ACROSS_USERS, message);
+                }
+            }
+        }
+    }
+
+    private int calculateCurrentPermissionFootprintLocked(BasePermission tree) {
+        int size = 0;
+        for (BasePermission perm : mSettings.getAllPermissionsLocked()) {
+            size += tree.calculateFootprint(perm);
+        }
+        return size;
+    }
+
+    private void enforcePermissionCapLocked(PermissionInfo info, BasePermission tree) {
+        // We calculate the max size of permissions defined by this uid and throw
+        // if that plus the size of 'info' would exceed our stated maximum.
+        if (tree.getUid() != Process.SYSTEM_UID) {
+            final int curTreeSize = calculateCurrentPermissionFootprintLocked(tree);
+            if (curTreeSize + info.calculateFootprint() > MAX_PERMISSION_TREE_FOOTPRINT) {
+                throw new SecurityException("Permission tree size cap exceeded");
+            }
+        }
+    }
+
+    /**
+     * Get the first event id for the permission.
+     *
+     * <p>There are four events for each permission: <ul>
+     *     <li>Request permission: first id + 0</li>
+     *     <li>Grant permission: first id + 1</li>
+     *     <li>Request for permission denied: first id + 2</li>
+     *     <li>Revoke permission: first id + 3</li>
+     * </ul></p>
+     *
+     * @param name name of the permission
+     *
+     * @return The first event id for the permission
+     */
+    private static int getBaseEventId(@NonNull String name) {
+        int eventIdIndex = ALL_DANGEROUS_PERMISSIONS.indexOf(name);
+
+        if (eventIdIndex == -1) {
+            if (AppOpsManager.permissionToOpCode(name) == AppOpsManager.OP_NONE
+                    || Build.IS_USER) {
+                Log.i(TAG, "Unknown permission " + name);
+
+                return MetricsEvent.ACTION_PERMISSION_REQUEST_UNKNOWN;
+            } else {
+                // Most likely #ALL_DANGEROUS_PERMISSIONS needs to be updated.
+                //
+                // Also update
+                // - EventLogger#ALL_DANGEROUS_PERMISSIONS
+                // - metrics_constants.proto
+                throw new IllegalStateException("Unknown permission " + name);
+            }
+        }
+
+        return MetricsEvent.ACTION_PERMISSION_REQUEST_READ_CALENDAR + eventIdIndex * 4;
+    }
+
+    /**
+     * Log that a permission was revoked.
+     *
+     * @param context Context of the caller
+     * @param name name of the permission
+     * @param packageName package permission if for
+     */
+    private static void logPermissionRevoked(@NonNull Context context, @NonNull String name,
+            @NonNull String packageName) {
+        MetricsLogger.action(context, getBaseEventId(name) + 3, packageName);
+    }
+
+    /**
+     * Log that a permission request was granted.
+     *
+     * @param context Context of the caller
+     * @param name name of the permission
+     * @param packageName package permission if for
+     */
+    private static void logPermissionGranted(@NonNull Context context, @NonNull String name,
+            @NonNull String packageName) {
+        MetricsLogger.action(context, getBaseEventId(name) + 1, packageName);
+    }
+
+    private class PermissionManagerInternalImpl extends PermissionManagerInternal {
+        @Override
+        public boolean addPermission(PermissionInfo info, boolean async, int callingUid,
+                PermissionCallback callback) {
+            return PermissionManagerService.this.addPermission(info, callingUid, callback);
+        }
+        @Override
+        public void removePermission(String permName, int callingUid,
+                PermissionCallback callback) {
+            PermissionManagerService.this.removePermission(permName, callingUid, callback);
+        }
+        @Override
+        public void grantRuntimePermission(String permName, String packageName,
+                boolean overridePolicy, int callingUid, int userId,
+                PermissionCallback callback) {
+            PermissionManagerService.this.grantRuntimePermission(
+                    permName, packageName, overridePolicy, callingUid, userId, callback);
+        }
+        @Override
+        public void grantRequestedRuntimePermissions(PackageParser.Package pkg, int[] userIds,
+                String[] grantedPermissions, int callingUid, PermissionCallback callback) {
+            PermissionManagerService.this.grantRequestedRuntimePermissions(
+                    pkg, userIds, grantedPermissions, callingUid, callback);
+        }
+        @Override
+        public void grantRuntimePermissionsGrantedToDisabledPackage(PackageParser.Package pkg,
+                int callingUid, PermissionCallback callback) {
+            PermissionManagerService.this.grantRuntimePermissionsGrantedToDisabledPackageLocked(
+                    pkg, callingUid, callback);
+        }
+        @Override
+        public void revokeRuntimePermission(String permName, String packageName,
+                boolean overridePolicy, int callingUid, int userId,
+                PermissionCallback callback) {
+            PermissionManagerService.this.revokeRuntimePermission(permName, packageName,
+                    overridePolicy, callingUid, userId, callback);
+        }
+        @Override
+        public int[] revokeUnusedSharedUserPermissions(SharedUserSetting suSetting,
+                int[] allUserIds) {
+            return PermissionManagerService.this.revokeUnusedSharedUserPermissions(
+                    (SharedUserSetting) suSetting, allUserIds);
+        }
+        @Override
+        public int getPermissionFlags(String permName, String packageName, int callingUid,
+                int userId) {
+            return PermissionManagerService.this.getPermissionFlags(permName, packageName,
+                    callingUid, userId);
+        }
+        @Override
+        public void updatePermissionFlags(String permName, String packageName, int flagMask,
+                int flagValues, int callingUid, int userId, PermissionCallback callback) {
+            PermissionManagerService.this.updatePermissionFlags(
+                    permName, packageName, flagMask, flagValues, callingUid, userId, callback);
+        }
+        @Override
+        public boolean updatePermissionFlagsForAllApps(int flagMask, int flagValues, int callingUid,
+                int userId, Collection<Package> packages, PermissionCallback callback) {
+            return PermissionManagerService.this.updatePermissionFlagsForAllApps(
+                    flagMask, flagValues, callingUid, userId, packages, callback);
+        }
+        @Override
+        public void enforceCrossUserPermission(int callingUid, int userId,
+                boolean requireFullPermission, boolean checkShell, String message) {
+            PermissionManagerService.this.enforceCrossUserPermission(callingUid, userId,
+                    requireFullPermission, checkShell, message);
+        }
+        @Override
+        public void enforceGrantRevokeRuntimePermissionPermissions(String message) {
+            PermissionManagerService.this.enforceGrantRevokeRuntimePermissionPermissions(message);
+        }
+        @Override
+        public int checkPermission(String permName, String packageName, int callingUid,
+                int userId) {
+            return PermissionManagerService.this.checkPermission(
+                    permName, packageName, callingUid, userId);
+        }
+        @Override
+        public PermissionInfo getPermissionInfo(String permName, String packageName, int flags,
+                int callingUid) {
+            return PermissionManagerService.this.getPermissionInfo(
+                    permName, packageName, flags, callingUid);
+        }
+        @Override
+        public List<PermissionInfo> getPermissionInfoByGroup(String group, int flags,
+                int callingUid) {
+            return PermissionManagerService.this.getPermissionInfoByGroup(group, flags, callingUid);
+        }
+        @Override
+        public boolean isPermissionInstant(String permName) {
+            synchronized (PermissionManagerService.this.mLock) {
+                final BasePermission bp = mSettings.getPermissionLocked(permName);
+                return (bp != null && bp.isInstant());
+            }
+        }
+        @Override
+        public boolean isPermissionAppOp(String permName) {
+            synchronized (PermissionManagerService.this.mLock) {
+                final BasePermission bp = mSettings.getPermissionLocked(permName);
+                return (bp != null && bp.isAppOp());
+            }
+        }
+        @Override
+        public PermissionSettings getPermissionSettings() {
+            return mSettings;
+        }
+        @Override
+        public DefaultPermissionGrantPolicy getDefaultPermissionGrantPolicy() {
+            return mDefaultPermissionGrantPolicy;
+        }
+        @Override
+        public BasePermission getPermissionTEMP(String permName) {
+            synchronized (PermissionManagerService.this.mLock) {
+                return mSettings.getPermissionLocked(permName);
+            }
+        }
+        @Override
+        public void putPermissionTEMP(String permName, BasePermission permission) {
+            synchronized (PermissionManagerService.this.mLock) {
+                mSettings.putPermissionLocked(permName, (BasePermission) permission);
+            }
+        }
+        @Override
+        public Iterator<BasePermission> getPermissionIteratorTEMP() {
+            synchronized (PermissionManagerService.this.mLock) {
+                return mSettings.getAllPermissionsLocked().iterator();
+            }
+        }
+    }
+}
diff --git a/com/android/server/pm/permission/PermissionSettings.java b/com/android/server/pm/permission/PermissionSettings.java
new file mode 100644
index 0000000..7a2e5ec
--- /dev/null
+++ b/com/android/server/pm/permission/PermissionSettings.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.permission;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+
+import com.android.internal.R;
+import com.android.internal.util.XmlUtils;
+import com.android.server.pm.DumpState;
+import com.android.server.pm.PackageManagerService;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Collection;
+
+/**
+ * Permissions and other related data. This class is not meant for
+ * direct access outside of the permission package with the sole exception
+ * of package settings. Instead, it should be reference either from the
+ * permission manager or package settings.
+ */
+public class PermissionSettings {
+
+    final boolean mPermissionReviewRequired;
+    /**
+     * All of the permissions known to the system. The mapping is from permission
+     * name to permission object.
+     */
+    private final ArrayMap<String, BasePermission> mPermissions =
+            new ArrayMap<String, BasePermission>();
+    private final Object mLock;
+
+    PermissionSettings(@NonNull Context context, @NonNull Object lock) {
+        mPermissionReviewRequired =
+                context.getResources().getBoolean(R.bool.config_permissionReviewRequired);
+        mLock = lock;
+    }
+
+    public @Nullable BasePermission getPermission(@NonNull String permName) {
+        synchronized (mLock) {
+            return getPermissionLocked(permName);
+        }
+    }
+
+    /**
+     * Transfers ownership of permissions from one package to another.
+     */
+    public void transferPermissions(String origPackageName, String newPackageName,
+            ArrayMap<String, BasePermission> permissionTrees) {
+        synchronized (mLock) {
+            for (int i=0; i<2; i++) {
+                ArrayMap<String, BasePermission> permissions =
+                        i == 0 ? permissionTrees : mPermissions;
+                for (BasePermission bp : permissions.values()) {
+                    bp.transfer(origPackageName, newPackageName);
+                }
+            }
+        }
+    }
+
+    public boolean canPropagatePermissionToInstantApp(String permName) {
+        synchronized (mLock) {
+            final BasePermission bp = mPermissions.get(permName);
+            return (bp != null && (bp.isRuntime() || bp.isDevelopment()) && bp.isInstant());
+        }
+    }
+
+    public void readPermissions(XmlPullParser parser) throws IOException, XmlPullParserException {
+        synchronized (mLock) {
+            readPermissions(mPermissions, parser);
+        }
+    }
+
+    public void writePermissions(XmlSerializer serializer) throws IOException {
+        for (BasePermission bp : mPermissions.values()) {
+            bp.writeLPr(serializer);
+        }
+    }
+
+    public static void readPermissions(ArrayMap<String, BasePermission> out, XmlPullParser parser)
+            throws IOException, XmlPullParserException {
+        int outerDepth = parser.getDepth();
+        int type;
+        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+
+            if (!BasePermission.readLPw(out, parser)) {
+                PackageManagerService.reportSettingsProblem(Log.WARN,
+                        "Unknown element reading permissions: " + parser.getName() + " at "
+                                + parser.getPositionDescription());
+            }
+            XmlUtils.skipCurrentTag(parser);
+        }
+    }
+
+    public void dumpPermissions(PrintWriter pw, String packageName,
+            ArraySet<String> permissionNames, boolean externalStorageEnforced,
+            DumpState dumpState) {
+        synchronized (mLock) {
+            boolean printedSomething = false;
+            for (BasePermission bp : mPermissions.values()) {
+                printedSomething = bp.dumpPermissionsLPr(pw, packageName, permissionNames,
+                        externalStorageEnforced, printedSomething, dumpState);
+            }
+        }
+    }
+
+    @Nullable BasePermission getPermissionLocked(@NonNull String permName) {
+        return mPermissions.get(permName);
+    }
+
+    void putPermissionLocked(@NonNull String permName, @NonNull BasePermission permission) {
+        mPermissions.put(permName, permission);
+    }
+
+    void removePermissionLocked(@NonNull String permName) {
+        mPermissions.remove(permName);
+    }
+
+    Collection<BasePermission> getAllPermissionsLocked() {
+        return mPermissions.values();
+    }
+}
diff --git a/com/android/server/pm/PermissionsState.java b/com/android/server/pm/permission/PermissionsState.java
similarity index 97%
rename from com/android/server/pm/PermissionsState.java
rename to com/android/server/pm/permission/PermissionsState.java
index f4d2ad2..11df380 100644
--- a/com/android/server/pm/PermissionsState.java
+++ b/com/android/server/pm/permission/PermissionsState.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.pm;
+package com.android.server.pm.permission;
 
 import android.content.pm.PackageManager;
 import android.os.UserHandle;
@@ -406,7 +406,7 @@
             ensurePermissionData(permission);
         }
 
-        PermissionData permissionData = mPermissions.get(permission.name);
+        PermissionData permissionData = mPermissions.get(permission.getName());
         if (permissionData == null) {
             if (!mayChangeFlags) {
                 return false;
@@ -557,7 +557,7 @@
     }
 
     private int grantPermission(BasePermission permission, int userId) {
-        if (hasPermission(permission.name, userId)) {
+        if (hasPermission(permission.getName(), userId)) {
             return PERMISSION_OPERATION_FAILURE;
         }
 
@@ -581,21 +581,22 @@
     }
 
     private int revokePermission(BasePermission permission, int userId) {
-        if (!hasPermission(permission.name, userId)) {
+        final String permName = permission.getName();
+        if (!hasPermission(permName, userId)) {
             return PERMISSION_OPERATION_FAILURE;
         }
 
         final boolean hasGids = !ArrayUtils.isEmpty(permission.computeGids(userId));
         final int[] oldGids = hasGids ? computeGids(userId) : NO_GIDS;
 
-        PermissionData permissionData = mPermissions.get(permission.name);
+        PermissionData permissionData = mPermissions.get(permName);
 
         if (!permissionData.revoke(userId)) {
             return PERMISSION_OPERATION_FAILURE;
         }
 
         if (permissionData.isDefault()) {
-            ensureNoPermissionData(permission.name);
+            ensureNoPermissionData(permName);
         }
 
         if (hasGids) {
@@ -625,13 +626,14 @@
     }
 
     private PermissionData ensurePermissionData(BasePermission permission) {
+        final String permName = permission.getName();
         if (mPermissions == null) {
             mPermissions = new ArrayMap<>();
         }
-        PermissionData permissionData = mPermissions.get(permission.name);
+        PermissionData permissionData = mPermissions.get(permName);
         if (permissionData == null) {
             permissionData = new PermissionData(permission);
-            mPermissions.put(permission.name, permissionData);
+            mPermissions.put(permName, permissionData);
         }
         return permissionData;
     }
@@ -692,7 +694,7 @@
 
             PermissionState userState = mUserStates.get(userId);
             if (userState == null) {
-                userState = new PermissionState(mPerm.name);
+                userState = new PermissionState(mPerm.getName());
                 mUserStates.put(userId, userState);
             }
 
@@ -760,7 +762,7 @@
                 }
                 return userState.mFlags != oldFlags;
             } else if (newFlags != 0) {
-                userState = new PermissionState(mPerm.name);
+                userState = new PermissionState(mPerm.getName());
                 userState.mFlags = newFlags;
                 mUserStates.put(userId, userState);
                 return true;
diff --git a/com/android/server/policy/PhoneWindowManager.java b/com/android/server/policy/PhoneWindowManager.java
index a806af4..db7817e 100644
--- a/com/android/server/policy/PhoneWindowManager.java
+++ b/com/android/server/policy/PhoneWindowManager.java
@@ -20,9 +20,12 @@
 import static android.Manifest.permission.SYSTEM_ALERT_WINDOW;
 import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
 import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.HOME_STACK_ID;
 import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
 import static android.app.AppOpsManager.OP_TOAST_WINDOW;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.content.Context.CONTEXT_RESTRICTED;
 import static android.content.Context.DISPLAY_SERVICE;
 import static android.content.Context.WINDOW_SERVICE;
@@ -198,6 +201,7 @@
 import android.util.Log;
 import android.util.LongSparseArray;
 import android.util.MutableBoolean;
+import android.util.PrintWriterPrinter;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.proto.ProtoOutputStream;
@@ -296,13 +300,13 @@
     static final int LONG_PRESS_POWER_SHUT_OFF = 2;
     static final int LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM = 3;
 
-    static final int LONG_PRESS_BACK_NOTHING = 0;
-    static final int LONG_PRESS_BACK_GO_TO_VOICE_ASSIST = 1;
-
     static final int MULTI_PRESS_POWER_NOTHING = 0;
     static final int MULTI_PRESS_POWER_THEATER_MODE = 1;
     static final int MULTI_PRESS_POWER_BRIGHTNESS_BOOST = 2;
 
+    static final int LONG_PRESS_BACK_NOTHING = 0;
+    static final int LONG_PRESS_BACK_GO_TO_VOICE_ASSIST = 1;
+
     // Number of presses needed before we induce panic press behavior on the back button
     static final int PANIC_PRESS_BACK_COUNT = 4;
     static final int PANIC_PRESS_BACK_NOTHING = 0;
@@ -565,7 +569,7 @@
     int mLongPressOnBackBehavior;
     int mPanicPressOnBackBehavior;
     int mShortPressOnSleepBehavior;
-    int mShortPressWindowBehavior;
+    int mShortPressOnWindowBehavior;
     volatile boolean mAwake;
     boolean mScreenOnEarly;
     boolean mScreenOnFully;
@@ -2180,9 +2184,9 @@
             mDoubleTapOnHomeBehavior = LONG_PRESS_HOME_NOTHING;
         }
 
-        mShortPressWindowBehavior = SHORT_PRESS_WINDOW_NOTHING;
+        mShortPressOnWindowBehavior = SHORT_PRESS_WINDOW_NOTHING;
         if (mContext.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
-            mShortPressWindowBehavior = SHORT_PRESS_WINDOW_PICTURE_IN_PICTURE;
+            mShortPressOnWindowBehavior = SHORT_PRESS_WINDOW_PICTURE_IN_PICTURE;
         }
 
         mNavBarOpacityMode = res.getInteger(
@@ -2626,18 +2630,15 @@
             attrs.privateFlags &= ~WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
         }
 
-        if (ActivityManager.isHighEndGfx()) {
-            if ((attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0) {
-                attrs.subtreeSystemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
-            }
-            final boolean forceWindowDrawsStatusBarBackground =
-                    (attrs.privateFlags & PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND)
-                            != 0;
-            if ((attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0
-                    || forceWindowDrawsStatusBarBackground
-                            && attrs.height == MATCH_PARENT && attrs.width == MATCH_PARENT) {
-                attrs.subtreeSystemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
-            }
+        if ((attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0) {
+            attrs.subtreeSystemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
+        }
+        final boolean forceWindowDrawsStatusBarBackground =
+                (attrs.privateFlags & PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND) != 0;
+        if ((attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0
+                || forceWindowDrawsStatusBarBackground
+                        && attrs.height == MATCH_PARENT && attrs.width == MATCH_PARENT) {
+            attrs.subtreeSystemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
         }
     }
 
@@ -3627,10 +3628,14 @@
                 return -1;
             }
 
-            // If the device is in Vr mode, drop the volume keys and don't
-            // forward it to the application/dispatch the audio event.
+            // If the device is in VR mode and keys are "internal" (e.g. on the side of the
+            // device), then drop the volume keys and don't forward it to the application/dispatch
+            // the audio event.
             if (mPersistentVrModeEnabled) {
-                return -1;
+                final InputDevice d = event.getDevice();
+                if (d != null && !d.isExternal()) {
+                    return -1;
+                }
             }
         } else if (keyCode == KeyEvent.KEYCODE_TAB && event.isMetaPressed()) {
             // Pass through keyboard navigation keys.
@@ -6272,7 +6277,7 @@
                 break;
             }
             case KeyEvent.KEYCODE_WINDOW: {
-                if (mShortPressWindowBehavior == SHORT_PRESS_WINDOW_PICTURE_IN_PICTURE) {
+                if (mShortPressOnWindowBehavior == SHORT_PRESS_WINDOW_PICTURE_IN_PICTURE) {
                     if (mPictureInPictureVisible) {
                         // Consumes the key only if picture-in-picture is visible to show
                         // picture-in-picture control menu. This gives a chance to the foreground
@@ -7915,8 +7920,10 @@
                 mTopFullscreenOpaqueWindowState, mTopFullscreenOpaqueOrDimmingWindowState);
         final int dockedVisibility = updateLightStatusBarLw(0 /* vis */,
                 mTopDockedOpaqueWindowState, mTopDockedOpaqueOrDimmingWindowState);
-        mWindowManagerFuncs.getStackBounds(HOME_STACK_ID, mNonDockedStackBounds);
-        mWindowManagerFuncs.getStackBounds(DOCKED_STACK_ID, mDockedStackBounds);
+        mWindowManagerFuncs.getStackBounds(
+                WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME, mNonDockedStackBounds);
+        mWindowManagerFuncs.getStackBounds(
+                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, mDockedStackBounds);
         final int visibility = updateSystemBarsLw(win, mLastSystemUiFlags, tmpVisibility);
         final int diff = visibility ^ mLastSystemUiFlags;
         final int fullscreenDiff = fullscreenVisibility ^ mLastFullscreenStackSysUiFlags;
@@ -8319,9 +8326,12 @@
         pw.print(prefix); pw.print("mSafeMode="); pw.print(mSafeMode);
                 pw.print(" mSystemReady="); pw.print(mSystemReady);
                 pw.print(" mSystemBooted="); pw.println(mSystemBooted);
-        pw.print(prefix); pw.print("mLidState="); pw.print(mLidState);
-                pw.print(" mLidOpenRotation="); pw.print(mLidOpenRotation);
-                pw.print(" mCameraLensCoverState="); pw.print(mCameraLensCoverState);
+        pw.print(prefix); pw.print("mLidState=");
+                pw.print(WindowManagerFuncs.lidStateToString(mLidState));
+                pw.print(" mLidOpenRotation=");
+                pw.println(Surface.rotationToString(mLidOpenRotation));
+        pw.print(prefix); pw.print("mCameraLensCoverState=");
+                pw.print(WindowManagerFuncs.cameraLensStateToString(mCameraLensCoverState));
                 pw.print(" mHdmiPlugged="); pw.println(mHdmiPlugged);
         if (mLastSystemUiFlags != 0 || mResettingSystemUiFlags != 0
                 || mForceClearedSystemUiFlags != 0) {
@@ -8339,16 +8349,24 @@
         pw.print(prefix); pw.print("mWakeGestureEnabledSetting=");
                 pw.println(mWakeGestureEnabledSetting);
 
-        pw.print(prefix); pw.print("mSupportAutoRotation="); pw.println(mSupportAutoRotation);
-        pw.print(prefix); pw.print("mUiMode="); pw.print(mUiMode);
-                pw.print(" mDockMode="); pw.print(mDockMode);
-                pw.print(" mEnableCarDockHomeCapture="); pw.print(mEnableCarDockHomeCapture);
-                pw.print(" mCarDockRotation="); pw.print(mCarDockRotation);
-                pw.print(" mDeskDockRotation="); pw.println(mDeskDockRotation);
-        pw.print(prefix); pw.print("mUserRotationMode="); pw.print(mUserRotationMode);
-                pw.print(" mUserRotation="); pw.print(mUserRotation);
-                pw.print(" mAllowAllRotations="); pw.println(mAllowAllRotations);
-        pw.print(prefix); pw.print("mCurrentAppOrientation="); pw.println(mCurrentAppOrientation);
+        pw.print(prefix);
+                pw.print("mSupportAutoRotation="); pw.print(mSupportAutoRotation);
+                pw.print(" mOrientationSensorEnabled="); pw.println(mOrientationSensorEnabled);
+        pw.print(prefix); pw.print("mUiMode="); pw.print(Configuration.uiModeToString(mUiMode));
+                pw.print(" mDockMode="); pw.println(Intent.dockStateToString(mDockMode));
+        pw.print(prefix); pw.print("mEnableCarDockHomeCapture=");
+                pw.print(mEnableCarDockHomeCapture);
+                pw.print(" mCarDockRotation=");
+                pw.print(Surface.rotationToString(mCarDockRotation));
+                pw.print(" mDeskDockRotation=");
+                pw.println(Surface.rotationToString(mDeskDockRotation));
+        pw.print(prefix); pw.print("mUserRotationMode=");
+                pw.print(WindowManagerPolicy.userRotationModeToString(mUserRotationMode));
+                pw.print(" mUserRotation="); pw.print(Surface.rotationToString(mUserRotation));
+                pw.print(" mAllowAllRotations=");
+                pw.println(allowAllRotationsToString(mAllowAllRotations));
+        pw.print(prefix); pw.print("mCurrentAppOrientation=");
+                pw.println(ActivityInfo.screenOrientationToString(mCurrentAppOrientation));
         pw.print(prefix); pw.print("mCarDockEnablesAccelerometer=");
                 pw.print(mCarDockEnablesAccelerometer);
                 pw.print(" mDeskDockEnablesAccelerometer=");
@@ -8357,23 +8375,54 @@
                 pw.print(mLidKeyboardAccessibility);
                 pw.print(" mLidNavigationAccessibility="); pw.print(mLidNavigationAccessibility);
                 pw.print(" mLidControlsScreenLock="); pw.println(mLidControlsScreenLock);
-                pw.print(" mLidControlsSleep="); pw.println(mLidControlsSleep);
+        pw.print(prefix); pw.print("mLidControlsSleep="); pw.println(mLidControlsSleep);
         pw.print(prefix);
-                pw.print(" mLongPressOnBackBehavior="); pw.println(mLongPressOnBackBehavior);
+                pw.print("mLongPressOnBackBehavior=");
+                pw.println(longPressOnBackBehaviorToString(mLongPressOnBackBehavior));
         pw.print(prefix);
-                pw.print("mShortPressOnPowerBehavior="); pw.print(mShortPressOnPowerBehavior);
-                pw.print(" mLongPressOnPowerBehavior="); pw.println(mLongPressOnPowerBehavior);
+                pw.print("mPanicPressOnBackBehavior=");
+                pw.println(panicPressOnBackBehaviorToString(mPanicPressOnBackBehavior));
         pw.print(prefix);
-                pw.print("mDoublePressOnPowerBehavior="); pw.print(mDoublePressOnPowerBehavior);
-                pw.print(" mTriplePressOnPowerBehavior="); pw.println(mTriplePressOnPowerBehavior);
-        pw.print(prefix); pw.print("mHasSoftInput="); pw.println(mHasSoftInput);
-        pw.print(prefix); pw.print("mAwake="); pw.println(mAwake);
-        pw.print(prefix); pw.print("mScreenOnEarly="); pw.print(mScreenOnEarly);
+                pw.print("mLongPressOnHomeBehavior=");
+                pw.println(longPressOnHomeBehaviorToString(mLongPressOnHomeBehavior));
+        pw.print(prefix);
+                pw.print("mDoubleTapOnHomeBehavior=");
+                pw.println(doubleTapOnHomeBehaviorToString(mDoubleTapOnHomeBehavior));
+        pw.print(prefix);
+                pw.print("mShortPressOnPowerBehavior=");
+                pw.println(shortPressOnPowerBehaviorToString(mShortPressOnPowerBehavior));
+        pw.print(prefix);
+                pw.print("mLongPressOnPowerBehavior=");
+                pw.println(longPressOnPowerBehaviorToString(mLongPressOnPowerBehavior));
+        pw.print(prefix);
+                pw.print("mDoublePressOnPowerBehavior=");
+                pw.println(multiPressOnPowerBehaviorToString(mDoublePressOnPowerBehavior));
+        pw.print(prefix);
+                pw.print("mTriplePressOnPowerBehavior=");
+                pw.println(multiPressOnPowerBehaviorToString(mTriplePressOnPowerBehavior));
+        pw.print(prefix);
+                pw.print("mShortPressOnSleepBehavior=");
+                pw.println(shortPressOnSleepBehaviorToString(mShortPressOnSleepBehavior));
+        pw.print(prefix);
+                pw.print("mShortPressOnWindowBehavior=");
+                pw.println(shortPressOnWindowBehaviorToString(mShortPressOnWindowBehavior));
+        pw.print(prefix);
+                pw.print("mHasSoftInput="); pw.print(mHasSoftInput);
+                pw.print(" mDismissImeOnBackKeyPressed="); pw.println(mDismissImeOnBackKeyPressed);
+        pw.print(prefix);
+                pw.print("mIncallPowerBehavior=");
+                pw.print(incallPowerBehaviorToString(mIncallPowerBehavior));
+                pw.print(" mIncallBackBehavior=");
+                pw.print(incallBackBehaviorToString(mIncallBackBehavior));
+                pw.print(" mEndcallBehavior=");
+                pw.println(endcallBehaviorToString(mEndcallBehavior));
+        pw.print(prefix); pw.print("mHomePressed="); pw.println(mHomePressed);
+        pw.print(prefix);
+                pw.print("mAwake="); pw.print(mAwake);
+                pw.print("mScreenOnEarly="); pw.print(mScreenOnEarly);
                 pw.print(" mScreenOnFully="); pw.println(mScreenOnFully);
         pw.print(prefix); pw.print("mKeyguardDrawComplete="); pw.print(mKeyguardDrawComplete);
                 pw.print(" mWindowManagerDrawComplete="); pw.println(mWindowManagerDrawComplete);
-        pw.print(prefix); pw.print("mOrientationSensorEnabled=");
-                pw.println(mOrientationSensorEnabled);
         pw.print(prefix); pw.print("mOverscanScreen=("); pw.print(mOverscanScreenLeft);
                 pw.print(","); pw.print(mOverscanScreenTop);
                 pw.print(") "); pw.print(mOverscanScreenWidth);
@@ -8439,8 +8488,6 @@
             pw.print(prefix); pw.print("mLastInputMethodTargetWindow=");
                     pw.println(mLastInputMethodTargetWindow);
         }
-        pw.print(prefix); pw.print("mDismissImeOnBackKeyPressed=");
-                pw.println(mDismissImeOnBackKeyPressed);
         if (mStatusBar != null) {
             pw.print(prefix); pw.print("mStatusBar=");
                     pw.print(mStatusBar); pw.print(" isStatusBarKeyguard=");
@@ -8473,26 +8520,28 @@
         }
         pw.print(prefix); pw.print("mTopIsFullscreen="); pw.print(mTopIsFullscreen);
                 pw.print(" mKeyguardOccluded="); pw.println(mKeyguardOccluded);
-                pw.print(" mKeyguardOccludedChanged="); pw.println(mKeyguardOccludedChanged);
+        pw.print(prefix);
+                pw.print("mKeyguardOccludedChanged="); pw.print(mKeyguardOccludedChanged);
                 pw.print(" mPendingKeyguardOccluded="); pw.println(mPendingKeyguardOccluded);
         pw.print(prefix); pw.print("mForceStatusBar="); pw.print(mForceStatusBar);
                 pw.print(" mForceStatusBarFromKeyguard=");
                 pw.println(mForceStatusBarFromKeyguard);
-        pw.print(prefix); pw.print("mHomePressed="); pw.println(mHomePressed);
         pw.print(prefix); pw.print("mAllowLockscreenWhenOn="); pw.print(mAllowLockscreenWhenOn);
                 pw.print(" mLockScreenTimeout="); pw.print(mLockScreenTimeout);
                 pw.print(" mLockScreenTimerActive="); pw.println(mLockScreenTimerActive);
-        pw.print(prefix); pw.print("mEndcallBehavior="); pw.print(mEndcallBehavior);
-                pw.print(" mIncallPowerBehavior="); pw.print(mIncallPowerBehavior);
-                pw.print(" mIncallBackBehavior="); pw.print(mIncallBackBehavior);
-                pw.print(" mLongPressOnHomeBehavior="); pw.println(mLongPressOnHomeBehavior);
-        pw.print(prefix); pw.print("mLandscapeRotation="); pw.print(mLandscapeRotation);
-                pw.print(" mSeascapeRotation="); pw.println(mSeascapeRotation);
-        pw.print(prefix); pw.print("mPortraitRotation="); pw.print(mPortraitRotation);
-                pw.print(" mUpsideDownRotation="); pw.println(mUpsideDownRotation);
-        pw.print(prefix); pw.print("mDemoHdmiRotation="); pw.print(mDemoHdmiRotation);
+        pw.print(prefix); pw.print("mLandscapeRotation=");
+                pw.print(Surface.rotationToString(mLandscapeRotation));
+                pw.print(" mSeascapeRotation=");
+                pw.println(Surface.rotationToString(mSeascapeRotation));
+        pw.print(prefix); pw.print("mPortraitRotation=");
+                pw.print(Surface.rotationToString(mPortraitRotation));
+                pw.print(" mUpsideDownRotation=");
+                pw.println(Surface.rotationToString(mUpsideDownRotation));
+        pw.print(prefix); pw.print("mDemoHdmiRotation=");
+                pw.print(Surface.rotationToString(mDemoHdmiRotation));
                 pw.print(" mDemoHdmiRotationLock="); pw.println(mDemoHdmiRotationLock);
-        pw.print(prefix); pw.print("mUndockedHdmiRotation="); pw.println(mUndockedHdmiRotation);
+        pw.print(prefix); pw.print("mUndockedHdmiRotation=");
+                pw.println(Surface.rotationToString(mUndockedHdmiRotation));
         if (mHasFeatureLeanback) {
             pw.print(prefix);
             pw.print("mAccessibilityTvKey1Pressed="); pw.println(mAccessibilityTvKey1Pressed);
@@ -8519,5 +8568,169 @@
         if (mKeyguardDelegate != null) {
             mKeyguardDelegate.dump(prefix, pw);
         }
+
+        pw.print(prefix); pw.println("Looper state:");
+        mHandler.getLooper().dump(new PrintWriterPrinter(pw), prefix + "  ");
+    }
+
+    private static String allowAllRotationsToString(int allowAll) {
+        switch (allowAll) {
+            case -1:
+                return "unknown";
+            case 0:
+                return "false";
+            case 1:
+                return "true";
+            default:
+                return Integer.toString(allowAll);
+        }
+    }
+
+    private static String endcallBehaviorToString(int behavior) {
+        StringBuilder sb = new StringBuilder();
+        if ((behavior & Settings.System.END_BUTTON_BEHAVIOR_HOME) != 0 ) {
+            sb.append("home|");
+        }
+        if ((behavior & Settings.System.END_BUTTON_BEHAVIOR_SLEEP) != 0) {
+            sb.append("sleep|");
+        }
+
+        final int N = sb.length();
+        if (N == 0) {
+            return "<nothing>";
+        } else {
+            // Chop off the trailing '|'
+            return sb.substring(0, N - 1);
+        }
+    }
+
+    private static String incallPowerBehaviorToString(int behavior) {
+        if ((behavior & Settings.Secure.INCALL_POWER_BUTTON_BEHAVIOR_HANGUP) != 0) {
+            return "hangup";
+        } else {
+            return "sleep";
+        }
+    }
+
+    private static String incallBackBehaviorToString(int behavior) {
+        if ((behavior & Settings.Secure.INCALL_BACK_BUTTON_BEHAVIOR_HANGUP) != 0) {
+            return "hangup";
+        } else {
+            return "<nothing>";
+        }
+    }
+
+    private static String longPressOnBackBehaviorToString(int behavior) {
+        switch (behavior) {
+            case LONG_PRESS_BACK_NOTHING:
+                return "LONG_PRESS_BACK_NOTHING";
+            case LONG_PRESS_BACK_GO_TO_VOICE_ASSIST:
+                return "LONG_PRESS_BACK_GO_TO_VOICE_ASSIST";
+            default:
+                return Integer.toString(behavior);
+        }
+    }
+
+    private static String panicPressOnBackBehaviorToString(int behavior) {
+        switch (behavior) {
+            case PANIC_PRESS_BACK_NOTHING:
+                return "PANIC_PRESS_BACK_NOTHING";
+            case PANIC_PRESS_BACK_HOME:
+                return "PANIC_PRESS_BACK_HOME";
+            default:
+                return Integer.toString(behavior);
+        }
+    }
+
+    private static String longPressOnHomeBehaviorToString(int behavior) {
+        switch (behavior) {
+            case LONG_PRESS_HOME_NOTHING:
+                return "LONG_PRESS_HOME_NOTHING";
+            case LONG_PRESS_HOME_ALL_APPS:
+                return "LONG_PRESS_HOME_ALL_APPS";
+            case LONG_PRESS_HOME_ASSIST:
+                return "LONG_PRESS_HOME_ASSIST";
+            default:
+                return Integer.toString(behavior);
+        }
+    }
+
+    private static String doubleTapOnHomeBehaviorToString(int behavior) {
+        switch (behavior) {
+            case DOUBLE_TAP_HOME_NOTHING:
+                return "DOUBLE_TAP_HOME_NOTHING";
+            case DOUBLE_TAP_HOME_RECENT_SYSTEM_UI:
+                return "DOUBLE_TAP_HOME_RECENT_SYSTEM_UI";
+            default:
+                return Integer.toString(behavior);
+        }
+    }
+
+    private static String shortPressOnPowerBehaviorToString(int behavior) {
+        switch (behavior) {
+            case SHORT_PRESS_POWER_NOTHING:
+                return "SHORT_PRESS_POWER_NOTHING";
+            case SHORT_PRESS_POWER_GO_TO_SLEEP:
+                return "SHORT_PRESS_POWER_GO_TO_SLEEP";
+            case SHORT_PRESS_POWER_REALLY_GO_TO_SLEEP:
+                return "SHORT_PRESS_POWER_REALLY_GO_TO_SLEEP";
+            case SHORT_PRESS_POWER_REALLY_GO_TO_SLEEP_AND_GO_HOME:
+                return "SHORT_PRESS_POWER_REALLY_GO_TO_SLEEP_AND_GO_HOME";
+            case SHORT_PRESS_POWER_GO_HOME:
+                return "SHORT_PRESS_POWER_GO_HOME";
+            case SHORT_PRESS_POWER_CLOSE_IME_OR_GO_HOME:
+                return "SHORT_PRESS_POWER_CLOSE_IME_OR_GO_HOME";
+            default:
+                return Integer.toString(behavior);
+        }
+    }
+
+    private static String longPressOnPowerBehaviorToString(int behavior) {
+        switch (behavior) {
+            case LONG_PRESS_POWER_NOTHING:
+                return "LONG_PRESS_POWER_NOTHING";
+            case LONG_PRESS_POWER_GLOBAL_ACTIONS:
+                return "LONG_PRESS_POWER_GLOBAL_ACTIONS";
+            case LONG_PRESS_POWER_SHUT_OFF:
+                return "LONG_PRESS_POWER_SHUT_OFF";
+            case LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM:
+                return "LONG_PRESS_POWER_SHUT_OFF_NO_CONFIRM";
+            default:
+                return Integer.toString(behavior);
+        }
+    }
+    private static String multiPressOnPowerBehaviorToString(int behavior) {
+        switch (behavior) {
+            case MULTI_PRESS_POWER_NOTHING:
+                return "MULTI_PRESS_POWER_NOTHING";
+            case MULTI_PRESS_POWER_THEATER_MODE:
+                return "MULTI_PRESS_POWER_THEATER_MODE";
+            case MULTI_PRESS_POWER_BRIGHTNESS_BOOST:
+                return "MULTI_PRESS_POWER_BRIGHTNESS_BOOST";
+            default:
+                return Integer.toString(behavior);
+        }
+    }
+
+    private static String shortPressOnSleepBehaviorToString(int behavior) {
+        switch (behavior) {
+            case SHORT_PRESS_SLEEP_GO_TO_SLEEP:
+                return "SHORT_PRESS_SLEEP_GO_TO_SLEEP";
+            case SHORT_PRESS_SLEEP_GO_TO_SLEEP_AND_GO_HOME:
+                return "SHORT_PRESS_SLEEP_GO_TO_SLEEP_AND_GO_HOME";
+            default:
+                return Integer.toString(behavior);
+        }
+    }
+
+    private static String shortPressOnWindowBehaviorToString(int behavior) {
+        switch (behavior) {
+            case SHORT_PRESS_WINDOW_NOTHING:
+                return "SHORT_PRESS_WINDOW_NOTHING";
+            case SHORT_PRESS_WINDOW_PICTURE_IN_PICTURE:
+                return "SHORT_PRESS_WINDOW_PICTURE_IN_PICTURE";
+            default:
+                return Integer.toString(behavior);
+        }
     }
 }
diff --git a/com/android/server/policy/WindowOrientationListener.java b/com/android/server/policy/WindowOrientationListener.java
index 64f64c0..169fd27 100644
--- a/com/android/server/policy/WindowOrientationListener.java
+++ b/com/android/server/policy/WindowOrientationListener.java
@@ -26,6 +26,7 @@
 import android.os.SystemProperties;
 import android.text.TextUtils;
 import android.util.Slog;
+import android.view.Surface;
 
 import java.io.PrintWriter;
 import java.util.Arrays;
@@ -236,7 +237,7 @@
             pw.println(prefix + TAG);
             prefix += "  ";
             pw.println(prefix + "mEnabled=" + mEnabled);
-            pw.println(prefix + "mCurrentRotation=" + mCurrentRotation);
+            pw.println(prefix + "mCurrentRotation=" + Surface.rotationToString(mCurrentRotation));
             pw.println(prefix + "mSensorType=" + mSensorType);
             pw.println(prefix + "mSensor=" + mSensor);
             pw.println(prefix + "mRate=" + mRate);
@@ -1026,8 +1027,9 @@
         public void dumpLocked(PrintWriter pw, String prefix) {
             pw.println(prefix + "OrientationSensorJudge");
             prefix += "  ";
-            pw.println(prefix + "mDesiredRotation=" + mDesiredRotation);
-            pw.println(prefix + "mProposedRotation=" + mProposedRotation);
+            pw.println(prefix + "mDesiredRotation=" + Surface.rotationToString(mDesiredRotation));
+            pw.println(prefix + "mProposedRotation="
+                    + Surface.rotationToString(mProposedRotation));
             pw.println(prefix + "mTouching=" + mTouching);
             pw.println(prefix + "mTouchEndedTimestampNanos=" + mTouchEndedTimestampNanos);
         }
diff --git a/com/android/server/policy/keyguard/KeyguardServiceDelegate.java b/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
index 50e5e7b..70cd54f 100644
--- a/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
+++ b/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
@@ -1,5 +1,7 @@
 package com.android.server.policy.keyguard;
 
+import static android.view.Display.INVALID_DISPLAY;
+
 import android.app.ActivityManager;
 import android.content.ComponentName;
 import android.content.Context;
@@ -13,6 +15,7 @@
 import android.os.UserHandle;
 import android.util.Log;
 import android.util.Slog;
+import android.view.WindowManagerPolicy;
 import android.view.WindowManagerPolicy.OnKeyguardExitResult;
 
 import com.android.internal.policy.IKeyguardDismissCallback;
@@ -201,7 +204,10 @@
             mKeyguardState.reset();
             mHandler.post(() -> {
                 try {
-                    ActivityManager.getService().setLockScreenShown(true);
+                    // There are no longer any keyguard windows on secondary displays, so pass
+                    // INVALID_DISPLAY. All that means is that showWhenLocked activities on
+                    // secondary displays now get to show.
+                    ActivityManager.getService().setLockScreenShown(true, INVALID_DISPLAY);
                 } catch (RemoteException e) {
                     // Local call.
                 }
@@ -412,13 +418,45 @@
         pw.println(prefix + "systemIsReady=" + mKeyguardState.systemIsReady);
         pw.println(prefix + "deviceHasKeyguard=" + mKeyguardState.deviceHasKeyguard);
         pw.println(prefix + "enabled=" + mKeyguardState.enabled);
-        pw.println(prefix + "offReason=" + mKeyguardState.offReason);
+        pw.println(prefix + "offReason=" +
+                WindowManagerPolicy.offReasonToString(mKeyguardState.offReason));
         pw.println(prefix + "currentUser=" + mKeyguardState.currentUser);
         pw.println(prefix + "bootCompleted=" + mKeyguardState.bootCompleted);
-        pw.println(prefix + "screenState=" + mKeyguardState.screenState);
-        pw.println(prefix + "interactiveState=" + mKeyguardState.interactiveState);
+        pw.println(prefix + "screenState=" + screenStateToString(mKeyguardState.screenState));
+        pw.println(prefix + "interactiveState=" +
+                interactiveStateToString(mKeyguardState.interactiveState));
         if (mKeyguardService != null) {
             mKeyguardService.dump(prefix, pw);
         }
     }
+
+    private static String screenStateToString(int screen) {
+        switch (screen) {
+            case SCREEN_STATE_OFF:
+                return "SCREEN_STATE_OFF";
+            case SCREEN_STATE_TURNING_ON:
+                return "SCREEN_STATE_TURNING_ON";
+            case SCREEN_STATE_ON:
+                return "SCREEN_STATE_ON";
+            case SCREEN_STATE_TURNING_OFF:
+                return "SCREEN_STATE_TURNING_OFF";
+            default:
+                return Integer.toString(screen);
+        }
+    }
+
+    private static String interactiveStateToString(int interactive) {
+        switch (interactive) {
+            case INTERACTIVE_STATE_SLEEP:
+                return "INTERACTIVE_STATE_SLEEP";
+            case INTERACTIVE_STATE_WAKING:
+                return "INTERACTIVE_STATE_WAKING";
+            case INTERACTIVE_STATE_AWAKE:
+                return "INTERACTIVE_STATE_AWAKE";
+            case INTERACTIVE_STATE_GOING_TO_SLEEP:
+                return "INTERACTIVE_STATE_GOING_TO_SLEEP";
+            default:
+                return Integer.toString(interactive);
+        }
+    }
 }
diff --git a/com/android/server/power/PowerManagerService.java b/com/android/server/power/PowerManagerService.java
index 3b70130..b917dae 100644
--- a/com/android/server/power/PowerManagerService.java
+++ b/com/android/server/power/PowerManagerService.java
@@ -206,6 +206,8 @@
     private static final String REASON_REBOOT = "reboot";
     private static final String REASON_USERREQUESTED = "shutdown,userrequested";
     private static final String REASON_THERMAL_SHUTDOWN = "shutdown,thermal";
+    private static final String REASON_LOW_BATTERY = "shutdown,battery";
+    private static final String REASON_BATTERY_THERMAL_STATE = "shutdown,thermal,battery";
 
     private static final String TRACE_SCREEN_ON = "Screen turning on";
 
@@ -1569,12 +1571,15 @@
         return true;
     }
 
-    private void setWakefulnessLocked(int wakefulness, int reason) {
+    @VisibleForTesting
+    void setWakefulnessLocked(int wakefulness, int reason) {
         if (mWakefulness != wakefulness) {
             mWakefulness = wakefulness;
             mWakefulnessChanging = true;
             mDirty |= DIRTY_WAKEFULNESS;
-            mNotifier.onWakefulnessChangeStarted(wakefulness, reason);
+            if (mNotifier != null) {
+                mNotifier.onWakefulnessChangeStarted(wakefulness, reason);
+            }
         }
     }
 
@@ -2432,11 +2437,8 @@
         return value >= -1.0f && value <= 1.0f;
     }
 
-    private int getDesiredScreenPolicyLocked() {
-        if (mIsVrModeEnabled) {
-            return DisplayPowerRequest.POLICY_VR;
-        }
-
+    @VisibleForTesting
+    int getDesiredScreenPolicyLocked() {
         if (mWakefulness == WAKEFULNESS_ASLEEP || sQuiescent) {
             return DisplayPowerRequest.POLICY_OFF;
         }
@@ -2452,6 +2454,13 @@
             // doze after screen off.  This causes the screen off transition to be skipped.
         }
 
+        // It is important that POLICY_VR check happens after the wakefulness checks above so
+        // that VR-mode does not prevent displays from transitioning to the correct state when
+        // dozing or sleeping.
+        if (mIsVrModeEnabled) {
+            return DisplayPowerRequest.POLICY_VR;
+        }
+
         if ((mWakeLockSummary & WAKE_LOCK_SCREEN_BRIGHT) != 0
                 || (mUserActivitySummary & USER_ACTIVITY_SCREEN_BRIGHT) != 0
                 || !mBootCompleted
@@ -3113,6 +3122,11 @@
         }
     }
 
+    @VisibleForTesting
+    void setVrModeEnabled(boolean enabled) {
+        mIsVrModeEnabled = enabled;
+    }
+
     private void powerHintInternal(int hintId, int data) {
         nativeSendPowerHint(hintId, data);
     }
@@ -3810,7 +3824,7 @@
 
             synchronized (mLock) {
                 if (mIsVrModeEnabled != enabled) {
-                    mIsVrModeEnabled = enabled;
+                    setVrModeEnabled(enabled);
                     mDirty |= DIRTY_VR_MODE_CHANGED;
                     updatePowerStateLocked();
                 }
@@ -4639,6 +4653,10 @@
                 return PowerManager.SHUTDOWN_REASON_USER_REQUESTED;
             case REASON_THERMAL_SHUTDOWN:
                 return PowerManager.SHUTDOWN_REASON_THERMAL_SHUTDOWN;
+            case REASON_LOW_BATTERY:
+                return PowerManager.SHUTDOWN_REASON_LOW_BATTERY;
+            case REASON_BATTERY_THERMAL_STATE:
+                return PowerManager.SHUTDOWN_REASON_BATTERY_THERMAL;
             default:
                 return PowerManager.SHUTDOWN_REASON_UNKNOWN;
         }
diff --git a/com/android/server/power/ShutdownThread.java b/com/android/server/power/ShutdownThread.java
index 853e1b2..515fa39 100644
--- a/com/android/server/power/ShutdownThread.java
+++ b/com/android/server/power/ShutdownThread.java
@@ -457,8 +457,7 @@
         // First send the high-level shut down broadcast.
         mActionDone = false;
         Intent intent = new Intent(Intent.ACTION_SHUTDOWN);
-        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND
-                | Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
+        intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
         mContext.sendOrderedBroadcastAsUser(intent,
                 UserHandle.ALL, null, br, mHandler, 0, null, null);
 
diff --git a/com/android/server/stats/StatsCompanionService.java b/com/android/server/stats/StatsCompanionService.java
new file mode 100644
index 0000000..f1fb3e7
--- /dev/null
+++ b/com/android/server/stats/StatsCompanionService.java
@@ -0,0 +1,301 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.stats;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.IStatsCompanionService;
+import android.os.IStatsManager;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.os.KernelWakelockReader;
+import com.android.internal.os.KernelWakelockStats;
+import com.android.server.SystemService;
+
+import java.util.Map;
+
+/**
+ * Helper service for statsd (the native stats management service in cmds/statsd/).
+ * Used for registering and receiving alarms on behalf of statsd.
+ * @hide
+ */
+public class StatsCompanionService extends IStatsCompanionService.Stub {
+    static final String TAG = "StatsCompanionService";
+    static final boolean DEBUG = true;
+
+    private final Context mContext;
+    private final AlarmManager mAlarmManager;
+    @GuardedBy("sStatsdLock")
+    private static IStatsManager sStatsd;
+    private static final Object sStatsdLock = new Object();
+
+    private final PendingIntent mAnomalyAlarmIntent;
+    private final PendingIntent mPollingAlarmIntent;
+
+    public StatsCompanionService(Context context) {
+        super();
+        mContext = context;
+        mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
+
+        mAnomalyAlarmIntent = PendingIntent.getBroadcast(mContext, 0,
+                new Intent(mContext, AnomalyAlarmReceiver.class), 0);
+        mPollingAlarmIntent = PendingIntent.getBroadcast(mContext, 0,
+                new Intent(mContext, PollingAlarmReceiver.class), 0);
+    }
+
+    public final static class AnomalyAlarmReceiver extends BroadcastReceiver  {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            Slog.i(TAG, "StatsCompanionService believes an anomaly has occurred.");
+            synchronized (sStatsdLock) {
+                if (sStatsd == null) {
+                    Slog.w(TAG, "Could not access statsd to inform it of anomaly alarm firing");
+                    return;
+                }
+                try {
+                    // Two-way call to statsd to retain AlarmManager wakelock
+                    sStatsd.informAnomalyAlarmFired();
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "Failed to inform statsd of anomaly alarm firing", e);
+                }
+            }
+            // AlarmManager releases its own wakelock here.
+        }
+    };
+
+    public final static class PollingAlarmReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (DEBUG) Slog.d(TAG, "Time to poll something.");
+            synchronized (sStatsdLock) {
+                if (sStatsd == null) {
+                    Slog.w(TAG, "Could not access statsd to inform it of polling alarm firing");
+                    return;
+                }
+                try {
+                    // Two-way call to statsd to retain AlarmManager wakelock
+                    sStatsd.informPollAlarmFired();
+                } catch (RemoteException e) {
+                    Slog.w(TAG, "Failed to inform statsd of polling alarm firing", e);
+                }
+            }
+            // AlarmManager releases its own wakelock here.
+        }
+    };
+
+    @Override // Binder call
+    public void setAnomalyAlarm(long timestampMs) {
+        enforceCallingPermission();
+        if (DEBUG) Slog.d(TAG, "Setting anomaly alarm for " + timestampMs);
+        final long callingToken = Binder.clearCallingIdentity();
+        try {
+            // using RTC, not RTC_WAKEUP, so if device is asleep, will only fire when it awakens.
+            // This alarm is inexact, leaving its exactness completely up to the OS optimizations.
+            // AlarmManager will automatically cancel any previous mAnomalyAlarmIntent alarm.
+            mAlarmManager.set(AlarmManager.RTC, timestampMs, mAnomalyAlarmIntent);
+        } finally {
+            Binder.restoreCallingIdentity(callingToken);
+        }
+    }
+
+    @Override // Binder call
+    public void cancelAnomalyAlarm() {
+        enforceCallingPermission();
+        if (DEBUG) Slog.d(TAG, "Cancelling anomaly alarm");
+        final long callingToken = Binder.clearCallingIdentity();
+        try {
+            mAlarmManager.cancel(mAnomalyAlarmIntent);
+        } finally {
+            Binder.restoreCallingIdentity(callingToken);
+        }
+    }
+
+    @Override // Binder call
+    public void setPollingAlarms(long timestampMs, long intervalMs) {
+        enforceCallingPermission();
+        if (DEBUG) Slog.d(TAG, "Setting polling alarm for " + timestampMs
+                + " every " + intervalMs + "ms");
+        final long callingToken = Binder.clearCallingIdentity();
+        try {
+            // using RTC, not RTC_WAKEUP, so if device is asleep, will only fire when it awakens.
+            // This alarm is inexact, leaving its exactness completely up to the OS optimizations.
+            // TODO: totally inexact means that stats per bucket could be quite off. Is this okay?
+            mAlarmManager.setRepeating(AlarmManager.RTC, timestampMs, intervalMs,
+                    mPollingAlarmIntent);
+        } finally {
+            Binder.restoreCallingIdentity(callingToken);
+        }
+    }
+
+    @Override // Binder call
+    public void cancelPollingAlarms() {
+        enforceCallingPermission();
+        if (DEBUG) Slog.d(TAG, "Cancelling polling alarm");
+        final long callingToken = Binder.clearCallingIdentity();
+        try {
+            mAlarmManager.cancel(mPollingAlarmIntent);
+        } finally {
+            Binder.restoreCallingIdentity(callingToken);
+        }
+    }
+
+    // These values must be kept in sync with cmd/statsd/StatsPullerManager.h.
+    // TODO: pull the constant from stats_events.proto instead
+    private static final int PULL_CODE_KERNEL_WAKELOCKS = 20;
+
+    private final KernelWakelockReader mKernelWakelockReader = new KernelWakelockReader();
+    private final KernelWakelockStats mTmpWakelockStats = new KernelWakelockStats();
+
+    @Override // Binder call
+    public String pullData(int pullCode) {
+        enforceCallingPermission();
+        if (DEBUG) Slog.d(TAG, "Fetching " + pullCode);
+
+        StringBuilder s = new StringBuilder(); // TODO: use and return a Parcel instead of a string
+        switch (pullCode) {
+            case PULL_CODE_KERNEL_WAKELOCKS:
+                final KernelWakelockStats wakelockStats =
+                        mKernelWakelockReader.readKernelWakelockStats(mTmpWakelockStats);
+
+                for (Map.Entry<String, KernelWakelockStats.Entry> ent : wakelockStats.entrySet()) {
+                    String name = ent.getKey();
+                    KernelWakelockStats.Entry kws = ent.getValue();
+                    s.append("Wakelock ")
+                            .append(name)
+                            .append(", time=")
+                            .append(kws.mTotalTime)
+                            .append(", count=")
+                            .append(kws.mCount)
+                            .append('\n');
+                }
+                break;
+            default:
+                Slog.w(TAG, "No such pollable data as " + pullCode);
+                return null;
+        }
+        return s.toString();
+    }
+
+    @Override // Binder call
+    public void statsdReady() {
+        enforceCallingPermission();
+        if (DEBUG) Slog.d(TAG, "learned that statsdReady");
+        sayHiToStatsd(); // tell statsd that we're ready too and link to it
+    }
+
+    private void enforceCallingPermission() {
+        if (Binder.getCallingPid() == Process.myPid()) {
+            return;
+        }
+        mContext.enforceCallingPermission(android.Manifest.permission.STATSCOMPANION, null);
+    }
+
+    // Lifecycle and related code
+
+    /** Fetches the statsd IBinder service */
+    private static IStatsManager fetchStatsdService() {
+        return IStatsManager.Stub.asInterface(ServiceManager.getService("stats"));
+    }
+
+    public static final class Lifecycle extends SystemService {
+        private StatsCompanionService mStatsCompanionService;
+
+        public Lifecycle(Context context) {
+            super(context);
+        }
+
+        @Override
+        public void onStart() {
+            mStatsCompanionService = new StatsCompanionService(getContext());
+            try {
+                publishBinderService(Context.STATS_COMPANION_SERVICE, mStatsCompanionService);
+                if (DEBUG) Slog.d(TAG, "Published " + Context.STATS_COMPANION_SERVICE);
+            } catch (Exception e) {
+                Slog.e(TAG, "Failed to publishBinderService", e);
+            }
+        }
+
+        @Override
+        public void onBootPhase(int phase) {
+            super.onBootPhase(phase);
+            if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
+                mStatsCompanionService.systemReady();
+            }
+        }
+    }
+
+    /** Now that the android system is ready, StatsCompanion is ready too, so inform statsd. */
+    private void systemReady() {
+        if (DEBUG) Slog.d(TAG, "Learned that systemReady");
+        sayHiToStatsd();
+    }
+
+    /** Tells statsd that statscompanion is ready. If the binder call returns, link to statsd. */
+    private void sayHiToStatsd() {
+        synchronized (sStatsdLock) {
+            if (sStatsd != null) {
+                Slog.e(TAG, "Trying to fetch statsd, but it was already fetched",
+                        new IllegalStateException("sStatsd is not null when being fetched"));
+                return;
+            }
+            sStatsd = fetchStatsdService();
+            if (sStatsd == null) {
+                Slog.w(TAG, "Could not access statsd");
+                return;
+            }
+            if (DEBUG) Slog.d(TAG, "Saying hi to statsd");
+            try {
+                sStatsd.statsCompanionReady();
+                // If the statsCompanionReady two-way binder call returns, link to statsd.
+                try {
+                    sStatsd.asBinder().linkToDeath(new StatsdDeathRecipient(), 0);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "linkToDeath(StatsdDeathRecipient) failed", e);
+                    forgetEverything();
+                }
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Failed to inform statsd that statscompanion is ready", e);
+                forgetEverything();
+            }
+        }
+    }
+
+    private class StatsdDeathRecipient implements IBinder.DeathRecipient {
+        @Override
+        public void binderDied() {
+            Slog.i(TAG, "Statsd is dead - erase all my knowledge.");
+            forgetEverything();
+        }
+    }
+
+    private void forgetEverything() {
+        synchronized (sStatsdLock) {
+            sStatsd = null;
+            cancelAnomalyAlarm();
+            cancelPollingAlarms();
+        }
+    }
+
+}
diff --git a/com/android/server/statusbar/StatusBarManagerService.java b/com/android/server/statusbar/StatusBarManagerService.java
index 38dc33f..bdfbe48 100644
--- a/com/android/server/statusbar/StatusBarManagerService.java
+++ b/com/android/server/statusbar/StatusBarManagerService.java
@@ -31,6 +31,7 @@
 import android.os.ResultReceiver;
 import android.os.ShellCallback;
 import android.os.UserHandle;
+import android.service.notification.NotificationStats;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.Slog;
@@ -97,12 +98,58 @@
         int what2;
         IBinder token;
 
+        public DisableRecord(int userId, IBinder token) {
+            this.userId = userId;
+            this.token = token;
+            try {
+                token.linkToDeath(this, 0);
+            } catch (RemoteException re) {
+                // Give up
+            }
+        }
+
+        @Override
         public void binderDied() {
             Slog.i(TAG, "binder died for pkg=" + pkg);
             disableForUser(0, token, pkg, userId);
             disable2ForUser(0, token, pkg, userId);
             token.unlinkToDeath(this, 0);
         }
+
+        public void setFlags(int what, int which, String pkg) {
+            switch (which) {
+                case 1:
+                    what1 = what;
+                    return;
+                case 2:
+                    what2 = what;
+                    return;
+                default:
+                    Slog.w(TAG, "Can't set unsupported disable flag " + which
+                            + ": 0x" + Integer.toHexString(what));
+            }
+            this.pkg = pkg;
+        }
+
+        public int getFlags(int which) {
+            switch (which) {
+                case 1: return what1;
+                case 2: return what2;
+                default:
+                    Slog.w(TAG, "Can't get unsupported disable flag " + which);
+                    return 0;
+            }
+        }
+
+        public boolean isEmpty() {
+            return what1 == 0 && what2 == 0;
+        }
+
+        @Override
+        public String toString() {
+            return String.format("userId=%d what1=0x%08X what2=0x%08X pkg=%s token=%s",
+                    userId, what1, what2, pkg, token);
+        }
     }
 
     /**
@@ -483,7 +530,7 @@
      */
     @Override
     public void disable2ForUser(int what, IBinder token, String pkg, int userId) {
-        enforceStatusBar();
+        enforceStatusBarService();
 
         synchronized (mLock) {
             disableLocked(userId, what, token, pkg, 2);
@@ -897,13 +944,15 @@
     }
 
     @Override
-    public void onNotificationClear(String pkg, String tag, int id, int userId) {
+    public void onNotificationClear(String pkg, String tag, int id, int userId, String key,
+            @NotificationStats.DismissalSurface int dismissalSurface) {
         enforceStatusBarService();
         final int callingUid = Binder.getCallingUid();
         final int callingPid = Binder.getCallingPid();
         long identity = Binder.clearCallingIdentity();
         try {
-            mNotificationDelegate.onNotificationClear(callingUid, callingPid, pkg, tag, id, userId);
+            mNotificationDelegate.onNotificationClear(
+                    callingUid, callingPid, pkg, tag, id, userId, key, dismissalSurface);
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
@@ -937,6 +986,28 @@
     }
 
     @Override
+    public void onNotificationDirectReplied(String key) throws RemoteException {
+        enforceStatusBarService();
+        long identity = Binder.clearCallingIdentity();
+        try {
+            mNotificationDelegate.onNotificationDirectReplied(key);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    @Override
+    public void onNotificationSettingsViewed(String key) throws RemoteException {
+        enforceStatusBarService();
+        long identity = Binder.clearCallingIdentity();
+        try {
+            mNotificationDelegate.onNotificationSettingsViewed(key);
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    @Override
     public void onClearAllNotifications(int userId) {
         enforceStatusBarService();
         final int callingUid = Binder.getCallingUid();
@@ -970,42 +1041,42 @@
             Slog.d(TAG, "manageDisableList userId=" + userId
                     + " what=0x" + Integer.toHexString(what) + " pkg=" + pkg);
         }
-        // update the list
+
+        // Find matching record, if any
         final int N = mDisableRecords.size();
-        DisableRecord tok = null;
+        DisableRecord record = null;
         int i;
-        for (i=0; i<N; i++) {
-            DisableRecord t = mDisableRecords.get(i);
-            if (t.token == token && t.userId == userId) {
-                tok = t;
+        for (i = 0; i < N; i++) {
+            DisableRecord r = mDisableRecords.get(i);
+            if (r.token == token && r.userId == userId) {
+                record = r;
                 break;
             }
         }
-        if (what == 0 || !token.isBinderAlive()) {
-            if (tok != null) {
+
+        // Remove record if binder is already dead
+        if (!token.isBinderAlive()) {
+            if (record != null) {
                 mDisableRecords.remove(i);
-                tok.token.unlinkToDeath(tok, 0);
+                record.token.unlinkToDeath(record, 0);
             }
-        } else {
-            if (tok == null) {
-                tok = new DisableRecord();
-                tok.userId = userId;
-                try {
-                    token.linkToDeath(tok, 0);
-                }
-                catch (RemoteException ex) {
-                    return; // give up
-                }
-                mDisableRecords.add(tok);
-            }
-            if (which == 1) {
-                tok.what1 = what;
-            } else {
-                tok.what2 = what;
-            }
-            tok.token = token;
-            tok.pkg = pkg;
+            return;
         }
+
+        // Update existing record
+        if (record != null) {
+            record.setFlags(what, which, pkg);
+            if (record.isEmpty()) {
+                mDisableRecords.remove(i);
+                record.token.unlinkToDeath(record, 0);
+            }
+            return;
+        }
+
+        // Record doesn't exist, so we create a new one
+        record = new DisableRecord(userId, token);
+        record.setFlags(what, which, pkg);
+        mDisableRecords.add(record);
     }
 
     // lock on mDisableRecords
@@ -1016,7 +1087,7 @@
         for (int i=0; i<N; i++) {
             final DisableRecord rec = mDisableRecords.get(i);
             if (rec.userId == userId) {
-                net |= (which == 1) ? rec.what1 : rec.what2;
+                net |= rec.getFlags(which);
             }
         }
         return net;
@@ -1036,11 +1107,7 @@
             pw.println("  mDisableRecords.size=" + N);
             for (int i=0; i<N; i++) {
                 DisableRecord tok = mDisableRecords.get(i);
-                pw.println("    [" + i + "] userId=" + tok.userId
-                                + " what1=0x" + Integer.toHexString(tok.what1)
-                                + " what2=0x" + Integer.toHexString(tok.what2)
-                                + " pkg=" + tok.pkg
-                                + " token=" + tok.token);
+                pw.println("    [" + i + "] " + tok);
             }
             pw.println("  mCurrentUserId=" + mCurrentUserId);
             pw.println("  mIcons=");
diff --git a/com/android/server/storage/AppCollector.java b/com/android/server/storage/AppCollector.java
index 03b754f..0b51f9c 100644
--- a/com/android/server/storage/AppCollector.java
+++ b/com/android/server/storage/AppCollector.java
@@ -135,7 +135,7 @@
                                 PackageStats packageStats = new PackageStats(app.packageName,
                                         user.id);
                                 packageStats.cacheSize = storageStats.getCacheBytes();
-                                packageStats.codeSize = storageStats.getCodeBytes();
+                                packageStats.codeSize = storageStats.getAppBytes();
                                 packageStats.dataSize = storageStats.getDataBytes();
                                 stats.add(packageStats);
                             } catch (NameNotFoundException | IOException e) {
diff --git a/com/android/server/storage/DiskStatsFileLogger.java b/com/android/server/storage/DiskStatsFileLogger.java
index 0094ab5..1db3ec4 100644
--- a/com/android/server/storage/DiskStatsFileLogger.java
+++ b/com/android/server/storage/DiskStatsFileLogger.java
@@ -56,10 +56,12 @@
     public static final String SYSTEM_KEY = "systemSize";
     public static final String MISC_KEY = "otherSize";
     public static final String APP_SIZE_AGG_KEY = "appSize";
+    public static final String APP_DATA_SIZE_AGG_KEY = "appDataSize";
     public static final String APP_CACHE_AGG_KEY = "cacheSize";
     public static final String PACKAGE_NAMES_KEY = "packageNames";
     public static final String APP_SIZES_KEY = "appSizes";
     public static final String APP_CACHES_KEY = "cacheSizes";
+    public static final String APP_DATA_KEY = "appDataSizes";
     public static final String LAST_QUERY_TIMESTAMP_KEY = "queryTime";
 
     private MeasurementResult mResult;
@@ -114,31 +116,39 @@
     private void addAppsToJson(JSONObject json) throws JSONException {
         JSONArray names = new JSONArray();
         JSONArray appSizeList = new JSONArray();
+        JSONArray appDataSizeList = new JSONArray();
         JSONArray cacheSizeList = new JSONArray();
 
         long appSizeSum = 0L;
+        long appDataSizeSum = 0L;
         long cacheSizeSum = 0L;
         boolean isExternal = Environment.isExternalStorageEmulated();
         for (Map.Entry<String, PackageStats> entry : filterOnlyPrimaryUser().entrySet()) {
             PackageStats stat = entry.getValue();
-            long appSize = stat.codeSize + stat.dataSize;
+            long appSize = stat.codeSize;
+            long appDataSize = stat.dataSize;
             long cacheSize = stat.cacheSize;
             if (isExternal) {
-                appSize += stat.externalCodeSize + stat.externalDataSize;
+                appSize += stat.externalCodeSize;
+                appDataSize += stat.externalDataSize;
                 cacheSize += stat.externalCacheSize;
             }
             appSizeSum += appSize;
+            appDataSizeSum += appDataSize;
             cacheSizeSum += cacheSize;
 
             names.put(stat.packageName);
             appSizeList.put(appSize);
+            appDataSizeList.put(appDataSize);
             cacheSizeList.put(cacheSize);
         }
         json.put(PACKAGE_NAMES_KEY, names);
         json.put(APP_SIZES_KEY, appSizeList);
         json.put(APP_CACHES_KEY, cacheSizeList);
+        json.put(APP_DATA_KEY, appDataSizeList);
         json.put(APP_SIZE_AGG_KEY, appSizeSum);
         json.put(APP_CACHE_AGG_KEY, cacheSizeSum);
+        json.put(APP_DATA_SIZE_AGG_KEY, appDataSizeSum);
     }
 
     /**
diff --git a/com/android/server/timezone/IntentHelper.java b/com/android/server/timezone/IntentHelper.java
index 0cb9065..5de5432 100644
--- a/com/android/server/timezone/IntentHelper.java
+++ b/com/android/server/timezone/IntentHelper.java
@@ -23,15 +23,22 @@
  */
 interface IntentHelper {
 
-    void initialize(String updateAppPackageName, String dataAppPackageName, Listener listener);
+    void initialize(String updateAppPackageName, String dataAppPackageName,
+            PackageTracker packageTracker);
 
     void sendTriggerUpdateCheck(CheckToken checkToken);
 
-    void enableReliabilityTriggering();
+    /**
+     * Schedule a "reliability trigger" after at least minimumDelayMillis, replacing any existing
+     * scheduled one. A reliability trigger ensures that the {@link PackageTracker} can pick up
+     * reliably if a previous update check did not complete for some reason. It can happen when
+     * the device is idle. The trigger is expected to call
+     * {@link PackageTracker#triggerUpdateIfNeeded(boolean)} with a {@code false} value.
+     */
+    void scheduleReliabilityTrigger(long minimumDelayMillis);
 
-    void disableReliabilityTriggering();
-
-    interface Listener {
-        void triggerUpdateIfNeeded(boolean packageUpdated);
-    }
+    /**
+     * Make sure there is no reliability trigger scheduled. No-op if there wasn't one.
+     */
+    void unscheduleReliabilityTrigger();
 }
diff --git a/com/android/server/timezone/IntentHelperImpl.java b/com/android/server/timezone/IntentHelperImpl.java
index 6db70cd..6e6259d 100644
--- a/com/android/server/timezone/IntentHelperImpl.java
+++ b/com/android/server/timezone/IntentHelperImpl.java
@@ -24,6 +24,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.os.PatternMatcher;
+import android.os.UserHandle;
 import android.util.Slog;
 
 /**
@@ -36,16 +37,13 @@
     private final Context mContext;
     private String mUpdaterAppPackageName;
 
-    private boolean mReliabilityReceiverEnabled;
-    private Receiver mReliabilityReceiver;
-
     IntentHelperImpl(Context context) {
         mContext = context;
     }
 
     @Override
-    public void initialize(
-            String updaterAppPackageName, String dataAppPackageName, Listener listener) {
+    public void initialize(String updaterAppPackageName, String dataAppPackageName,
+            PackageTracker packageTracker) {
         mUpdaterAppPackageName = updaterAppPackageName;
 
         // Register for events of interest.
@@ -78,10 +76,10 @@
         // We do not register for ACTION_PACKAGE_DATA_CLEARED because the updater / data apps are
         // not expected to need local data.
 
-        Receiver packageUpdateReceiver = new Receiver(listener, true /* packageUpdated */);
-        mContext.registerReceiver(packageUpdateReceiver, packageIntentFilter);
-
-        mReliabilityReceiver = new Receiver(listener, false /* packageUpdated */);
+        Receiver packageUpdateReceiver = new Receiver(packageTracker);
+        mContext.registerReceiverAsUser(
+                packageUpdateReceiver, UserHandle.SYSTEM, packageIntentFilter,
+                null /* broadcastPermission */, null /* default handler */);
     }
 
     /** Sends an intent to trigger an update check. */
@@ -93,39 +91,26 @@
     }
 
     @Override
-    public synchronized void enableReliabilityTriggering() {
-        if (!mReliabilityReceiverEnabled) {
-            // The intent filter that exists to make updates reliable in the event of failures /
-            // reboots.
-            IntentFilter reliabilityIntentFilter = new IntentFilter();
-            reliabilityIntentFilter.addAction(Intent.ACTION_IDLE_MAINTENANCE_START);
-            mContext.registerReceiver(mReliabilityReceiver, reliabilityIntentFilter);
-            mReliabilityReceiverEnabled = true;
-        }
+    public synchronized void scheduleReliabilityTrigger(long minimumDelayMillis) {
+        TimeZoneUpdateIdler.schedule(mContext, minimumDelayMillis);
     }
 
     @Override
-    public synchronized void disableReliabilityTriggering() {
-        if (mReliabilityReceiverEnabled) {
-            mContext.unregisterReceiver(mReliabilityReceiver);
-            mReliabilityReceiverEnabled = false;
-        }
+    public synchronized void unscheduleReliabilityTrigger() {
+        TimeZoneUpdateIdler.unschedule(mContext);
     }
 
     private static class Receiver extends BroadcastReceiver {
-        private final Listener mListener;
-        private final boolean mPackageUpdated;
+        private final PackageTracker mPackageTracker;
 
-        private Receiver(Listener listener, boolean packageUpdated) {
-            mListener = listener;
-            mPackageUpdated = packageUpdated;
+        private Receiver(PackageTracker packageTracker) {
+            mPackageTracker = packageTracker;
         }
 
         @Override
         public void onReceive(Context context, Intent intent) {
             Slog.d(TAG, "Received intent: " + intent.toString());
-            mListener.triggerUpdateIfNeeded(mPackageUpdated);
+            mPackageTracker.triggerUpdateIfNeeded(true /* packageChanged */);
         }
     }
-
 }
diff --git a/com/android/server/timezone/PackageTracker.java b/com/android/server/timezone/PackageTracker.java
index 24e0fe4..f0306b9 100644
--- a/com/android/server/timezone/PackageTracker.java
+++ b/com/android/server/timezone/PackageTracker.java
@@ -51,7 +51,7 @@
  */
 // Also made non-final so it can be mocked.
 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
-public class PackageTracker implements IntentHelper.Listener {
+public class PackageTracker {
     private static final String TAG = "timezone.PackageTracker";
 
     private final PackageManagerHelper mPackageManagerHelper;
@@ -72,6 +72,13 @@
     // The number of failed checks in a row before reliability checks should stop happening.
     private long mFailedCheckRetryCount;
 
+    /*
+     * The minimum delay between a successive reliability triggers / other operations. Should to be
+     * larger than mCheckTimeAllowedMillis to avoid reliability triggers happening during package
+     * update checks.
+     */
+    private int mDelayBeforeReliabilityCheckMillis;
+
     // Reliability check state: If a check was triggered but not acknowledged within
     // mCheckTimeAllowedMillis then another one can be triggered.
     private Long mLastTriggerTimestamp = null;
@@ -122,6 +129,7 @@
         mDataAppPackageName = mConfigHelper.getDataAppPackageName();
         mCheckTimeAllowedMillis = mConfigHelper.getCheckTimeAllowedMillis();
         mFailedCheckRetryCount = mConfigHelper.getFailedCheckRetryCount();
+        mDelayBeforeReliabilityCheckMillis = mCheckTimeAllowedMillis + (60 * 1000);
 
         // Validate the device configuration including the application packages.
         // The manifest entries in the apps themselves are not validated until use as they can
@@ -135,9 +143,10 @@
         // Initialize the intent helper.
         mIntentHelper.initialize(mUpdateAppPackageName, mDataAppPackageName, this);
 
-        // Enable the reliability triggering so we will have at least one reliability trigger if
-        // a package isn't updated.
-        mIntentHelper.enableReliabilityTriggering();
+        // Schedule a reliability trigger so we will have at least one after boot. This will allow
+        // us to catch if a package updated wasn't handled to completion. There's no hurry: it's ok
+        // to delay for a while before doing this even if idle.
+        mIntentHelper.scheduleReliabilityTrigger(mDelayBeforeReliabilityCheckMillis);
 
         Slog.i(TAG, "Time zone updater / data package tracking enabled");
     }
@@ -195,7 +204,6 @@
      * @param packageChanged true if this method was called because a known packaged definitely
      *     changed, false if the cause is a reliability trigger
      */
-    @Override
     public synchronized void triggerUpdateIfNeeded(boolean packageChanged) {
         if (!mTrackingEnabled) {
             throw new IllegalStateException("Unexpected call. Tracking is disabled.");
@@ -212,8 +220,8 @@
                     + " updaterApp=" + updaterAppManifestValid
                     + ", dataApp=" + dataAppManifestValid);
 
-            // There's no point in doing reliability checks if the current packages are bad.
-            mIntentHelper.disableReliabilityTriggering();
+            // There's no point in doing any reliability triggers if the current packages are bad.
+            mIntentHelper.unscheduleReliabilityTrigger();
             return;
         }
 
@@ -238,7 +246,8 @@
                     Slog.d(TAG,
                             "triggerUpdateIfNeeded: checkComplete call is not yet overdue."
                                     + " Not triggering.");
-                    // Not doing any work, but also not disabling future reliability triggers.
+                    // Don't do any work now but we do schedule a future reliability trigger.
+                    mIntentHelper.scheduleReliabilityTrigger(mDelayBeforeReliabilityCheckMillis);
                     return;
                 }
             } else if (mCheckFailureCount > mFailedCheckRetryCount) {
@@ -247,13 +256,13 @@
                 Slog.i(TAG, "triggerUpdateIfNeeded: number of allowed consecutive check failures"
                         + " exceeded. Stopping reliability triggers until next reboot or package"
                         + " update.");
-                mIntentHelper.disableReliabilityTriggering();
+                mIntentHelper.unscheduleReliabilityTrigger();
                 return;
             } else if (mCheckFailureCount == 0) {
                 // Case 4.
                 Slog.i(TAG, "triggerUpdateIfNeeded: No reliability check required. Last check was"
                         + " successful.");
-                mIntentHelper.disableReliabilityTriggering();
+                mIntentHelper.unscheduleReliabilityTrigger();
                 return;
             }
         }
@@ -263,7 +272,7 @@
         if (currentInstalledVersions == null) {
             // This should not happen if the device is configured in a valid way.
             Slog.e(TAG, "triggerUpdateIfNeeded: currentInstalledVersions was null");
-            mIntentHelper.disableReliabilityTriggering();
+            mIntentHelper.unscheduleReliabilityTrigger();
             return;
         }
 
@@ -288,7 +297,7 @@
                 // The last check succeeded and nothing has changed. Do nothing and disable
                 // reliability checks.
                 Slog.i(TAG, "triggerUpdateIfNeeded: Prior check succeeded. No need to trigger.");
-                mIntentHelper.disableReliabilityTriggering();
+                mIntentHelper.unscheduleReliabilityTrigger();
                 return;
             }
         }
@@ -299,6 +308,8 @@
         if (checkToken == null) {
             Slog.w(TAG, "triggerUpdateIfNeeded: Unable to generate check token."
                     + " Not sending check request.");
+            // Trigger again later: perhaps we'll have better luck.
+            mIntentHelper.scheduleReliabilityTrigger(mDelayBeforeReliabilityCheckMillis);
             return;
         }
 
@@ -309,9 +320,9 @@
         // Update the reliability check state in case the update fails.
         setCheckInProgress();
 
-        // Enable reliability triggering in case the check doesn't succeed and there is no
-        // response at all. Enabling reliability triggering is idempotent.
-        mIntentHelper.enableReliabilityTriggering();
+        // Schedule a reliability trigger in case the update check doesn't succeed and there is no
+        // response at all. It will be cancelled if the check is successful in recordCheckResult.
+        mIntentHelper.scheduleReliabilityTrigger(mDelayBeforeReliabilityCheckMillis);
     }
 
     /**
@@ -370,9 +381,9 @@
                     + " storage state.");
             mPackageStatusStorage.resetCheckState();
 
-            // Enable reliability triggering and reset the failure count so we know that the
+            // Schedule a reliability trigger and reset the failure count so we know that the
             // next reliability trigger will do something.
-            mIntentHelper.enableReliabilityTriggering();
+            mIntentHelper.scheduleReliabilityTrigger(mDelayBeforeReliabilityCheckMillis);
             mCheckFailureCount = 0;
         } else {
             // This is the expected case when tracking is enabled: a check was triggered and it has
@@ -385,13 +396,13 @@
                 setCheckComplete();
 
                 if (success) {
-                    // Since the check was successful, no more reliability checks are required until
+                    // Since the check was successful, no reliability trigger is required until
                     // there is a package change.
-                    mIntentHelper.disableReliabilityTriggering();
+                    mIntentHelper.unscheduleReliabilityTrigger();
                     mCheckFailureCount = 0;
                 } else {
-                    // Enable reliability triggering to potentially check again in future.
-                    mIntentHelper.enableReliabilityTriggering();
+                    // Enable schedule a reliability trigger to check again in future.
+                    mIntentHelper.scheduleReliabilityTrigger(mDelayBeforeReliabilityCheckMillis);
                     mCheckFailureCount++;
                 }
             } else {
@@ -400,8 +411,8 @@
                 Slog.i(TAG, "recordCheckResult: could not update token=" + checkToken
                         + " with success=" + success + ". Optimistic lock failure");
 
-                // Enable reliability triggering to potentially try again in future.
-                mIntentHelper.enableReliabilityTriggering();
+                // Schedule a reliability trigger to potentially try again in future.
+                mIntentHelper.scheduleReliabilityTrigger(mDelayBeforeReliabilityCheckMillis);
                 mCheckFailureCount++;
             }
         }
@@ -515,6 +526,7 @@
                 ", mUpdateAppPackageName='" + mUpdateAppPackageName + '\'' +
                 ", mDataAppPackageName='" + mDataAppPackageName + '\'' +
                 ", mCheckTimeAllowedMillis=" + mCheckTimeAllowedMillis +
+                ", mDelayBeforeReliabilityCheckMillis=" + mDelayBeforeReliabilityCheckMillis +
                 ", mFailedCheckRetryCount=" + mFailedCheckRetryCount +
                 ", mLastTriggerTimestamp=" + mLastTriggerTimestamp +
                 ", mCheckTriggered=" + mCheckTriggered +
diff --git a/com/android/server/timezone/PackageTrackerHelperImpl.java b/com/android/server/timezone/PackageTrackerHelperImpl.java
index 2e0c21b..b89dd38 100644
--- a/com/android/server/timezone/PackageTrackerHelperImpl.java
+++ b/com/android/server/timezone/PackageTrackerHelperImpl.java
@@ -26,6 +26,7 @@
 import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
 import android.os.SystemClock;
+import android.os.UserHandle;
 import android.util.Slog;
 
 import java.util.List;
@@ -114,8 +115,8 @@
     @Override
     public boolean contentProviderRegistered(String authority, String requiredPackageName) {
         int flags = PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS;
-        ProviderInfo providerInfo =
-                mPackageManager.resolveContentProvider(authority, flags);
+        ProviderInfo providerInfo = mPackageManager.resolveContentProviderAsUser(
+                authority, flags, UserHandle.SYSTEM.getIdentifier());
         if (providerInfo == null) {
             Slog.i(TAG, "contentProviderRegistered: No content provider registered with authority="
                     + authority);
@@ -136,7 +137,8 @@
             throws PackageManager.NameNotFoundException {
 
         int flags = PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS;
-        List<ResolveInfo> resolveInfo = mPackageManager.queryBroadcastReceivers(intent, flags);
+        List<ResolveInfo> resolveInfo = mPackageManager.queryBroadcastReceiversAsUser(
+                intent, flags, UserHandle.SYSTEM);
         if (resolveInfo.size() != 1) {
             Slog.i(TAG, "receiverRegistered: Zero or multiple broadcast receiver registered for"
                     + " intent=" + intent + ", found=" + resolveInfo);
diff --git a/com/android/server/timezone/RulesManagerService.java b/com/android/server/timezone/RulesManagerService.java
index 3ad4419..52b49ba 100644
--- a/com/android/server/timezone/RulesManagerService.java
+++ b/com/android/server/timezone/RulesManagerService.java
@@ -47,6 +47,7 @@
 import java.util.concurrent.Executor;
 import java.util.concurrent.atomic.AtomicBoolean;
 import libcore.icu.ICU;
+import libcore.util.TimeZoneFinder;
 import libcore.util.ZoneInfoDB;
 
 import static android.app.timezone.RulesState.DISTRO_STATUS_INSTALLED;
@@ -69,18 +70,22 @@
                     DistroVersion.CURRENT_FORMAT_MINOR_VERSION);
 
     public static class Lifecycle extends SystemService {
-        private RulesManagerService mService;
-
         public Lifecycle(Context context) {
             super(context);
         }
 
         @Override
         public void onStart() {
-            mService = RulesManagerService.create(getContext());
-            mService.start();
+            RulesManagerService service = RulesManagerService.create(getContext());
+            service.start();
 
-            publishBinderService(Context.TIME_ZONE_RULES_MANAGER_SERVICE, mService);
+            // Publish the binder service so it can be accessed from other (appropriately
+            // permissioned) processes.
+            publishBinderService(Context.TIME_ZONE_RULES_MANAGER_SERVICE, service);
+
+            // Publish the service instance locally so we can use it directly from within the system
+            // server from TimeZoneUpdateIdler.
+            publishLocalService(RulesManagerService.class, service);
         }
     }
 
@@ -475,9 +480,10 @@
                         case 'a': {
                             // Report the active rules version (i.e. the rules in use by the current
                             // process).
-                            pw.println("Active rules version (ICU, libcore): "
+                            pw.println("Active rules version (ICU, ZoneInfoDB, TimeZoneFinder): "
                                     + ICU.getTZDataVersion() + ","
-                                    + ZoneInfoDB.getInstance().getVersion());
+                                    + ZoneInfoDB.getInstance().getVersion() + ","
+                                    + TimeZoneFinder.getInstance().getIanaVersion());
                             break;
                         }
                         default: {
@@ -490,12 +496,24 @@
         }
 
         pw.println("RulesManagerService state: " + toString());
-        pw.println("Active rules version (ICU, libcore): " + ICU.getTZDataVersion() + ","
-                + ZoneInfoDB.getInstance().getVersion());
+        pw.println("Active rules version (ICU, ZoneInfoDB, TimeZoneFinder): "
+                + ICU.getTZDataVersion() + ","
+                + ZoneInfoDB.getInstance().getVersion() + ","
+                + TimeZoneFinder.getInstance().getIanaVersion());
         pw.println("Distro state: " + rulesState.toString());
         mPackageTracker.dump(pw);
     }
 
+    /**
+     * Called when the device is considered idle.
+     */
+    void notifyIdle() {
+        // No package has changed: we are just triggering because the device is idle and there
+        // *might* be work to do.
+        final boolean packageChanged = false;
+        mPackageTracker.triggerUpdateIfNeeded(packageChanged);
+    }
+
     @Override
     public String toString() {
         return "RulesManagerService{" +
diff --git a/com/android/server/timezone/TimeZoneUpdateIdler.java b/com/android/server/timezone/TimeZoneUpdateIdler.java
new file mode 100644
index 0000000..a7767a4
--- /dev/null
+++ b/com/android/server/timezone/TimeZoneUpdateIdler.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.timezone;
+
+import com.android.server.LocalServices;
+
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobScheduler;
+import android.app.job.JobService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.util.Slog;
+
+/**
+ * A JobService used to trigger time zone rules update work when a device falls idle.
+ */
+public final class TimeZoneUpdateIdler extends JobService {
+
+    private static final String TAG = "timezone.TimeZoneUpdateIdler";
+
+    /** The static job ID used to handle on-idle work. */
+    // Must be unique within UID (system service)
+    private static final int TIME_ZONE_UPDATE_IDLE_JOB_ID = 27042305;
+
+    @Override
+    public boolean onStartJob(JobParameters params) {
+        RulesManagerService rulesManagerService =
+                LocalServices.getService(RulesManagerService.class);
+
+        Slog.d(TAG, "onStartJob() called");
+
+        // Note: notifyIdle() explicitly handles canceling / re-scheduling so no need to reschedule
+        // here.
+        rulesManagerService.notifyIdle();
+
+        // Everything is handled synchronously. We are done.
+        return false;
+    }
+
+    @Override
+    public boolean onStopJob(JobParameters params) {
+        // Reschedule if stopped unless it was cancelled due to unschedule().
+        boolean reschedule = params.getStopReason() != JobParameters.REASON_CANCELED;
+        Slog.d(TAG, "onStopJob() called: Reschedule=" + reschedule);
+        return reschedule;
+    }
+
+    /**
+     * Schedules the TimeZoneUpdateIdler job service to run once.
+     *
+     * @param context Context to use to get a job scheduler.
+     */
+    public static void schedule(Context context, long minimumDelayMillis) {
+        // Request that the JobScheduler tell us when the device falls idle.
+        JobScheduler jobScheduler =
+                (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
+
+        // The TimeZoneUpdateIdler will send an intent that will trigger the Receiver.
+        ComponentName idlerJobServiceName =
+                new ComponentName(context, TimeZoneUpdateIdler.class);
+
+        // We require the device is idle, but also that it is charging to be as non-invasive as
+        // we can.
+        JobInfo.Builder jobInfoBuilder =
+                new JobInfo.Builder(TIME_ZONE_UPDATE_IDLE_JOB_ID, idlerJobServiceName)
+                        .setRequiresDeviceIdle(true)
+                        .setRequiresCharging(true)
+                        .setMinimumLatency(minimumDelayMillis);
+
+        Slog.d(TAG, "schedule() called: minimumDelayMillis=" + minimumDelayMillis);
+        jobScheduler.schedule(jobInfoBuilder.build());
+    }
+
+    /**
+     * Unschedules the TimeZoneUpdateIdler job service.
+     *
+     * @param context Context to use to get a job scheduler.
+     */
+    public static void unschedule(Context context) {
+        JobScheduler jobScheduler =
+                (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE);
+        Slog.d(TAG, "unschedule() called");
+        jobScheduler.cancel(TIME_ZONE_UPDATE_IDLE_JOB_ID);
+    }
+}
diff --git a/com/android/server/twilight/TwilightState.java b/com/android/server/twilight/TwilightState.java
index 30a8ccc..71304a7 100644
--- a/com/android/server/twilight/TwilightState.java
+++ b/com/android/server/twilight/TwilightState.java
@@ -18,7 +18,10 @@
 
 import android.text.format.DateFormat;
 
-import java.util.Calendar;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.TimeZone;
 
 /**
  * The twilight state, consisting of the sunrise and sunset times (in millis) for the current
@@ -45,12 +48,11 @@
     }
 
     /**
-     * Returns a new {@link Calendar} instance initialized to {@link #sunriseTimeMillis()}.
+     * Returns a new {@link LocalDateTime} instance initialized to {@link #sunriseTimeMillis()}.
      */
-    public Calendar sunrise() {
-        final Calendar sunrise = Calendar.getInstance();
-        sunrise.setTimeInMillis(mSunriseTimeMillis);
-        return sunrise;
+    public LocalDateTime sunrise() {
+        final ZoneId zoneId = TimeZone.getDefault().toZoneId();
+        return LocalDateTime.ofInstant(Instant.ofEpochMilli(mSunriseTimeMillis), zoneId);
     }
 
     /**
@@ -62,12 +64,11 @@
     }
 
     /**
-     * Returns a new {@link Calendar} instance initialized to {@link #sunsetTimeMillis()}.
+     * Returns a new {@link LocalDateTime} instance initialized to {@link #sunsetTimeMillis()}.
      */
-    public Calendar sunset() {
-        final Calendar sunset = Calendar.getInstance();
-        sunset.setTimeInMillis(mSunsetTimeMillis);
-        return sunset;
+    public LocalDateTime sunset() {
+        final ZoneId zoneId = TimeZone.getDefault().toZoneId();
+        return LocalDateTime.ofInstant(Instant.ofEpochMilli(mSunsetTimeMillis), zoneId);
     }
 
     /**
diff --git a/com/android/server/usage/AppStandbyController.java b/com/android/server/usage/AppStandbyController.java
new file mode 100644
index 0000000..b2446ba
--- /dev/null
+++ b/com/android/server/usage/AppStandbyController.java
@@ -0,0 +1,987 @@
+/**
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy
+ * of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.android.server.usage;
+
+import static com.android.server.SystemService.PHASE_BOOT_COMPLETED;
+import static com.android.server.SystemService.PHASE_SYSTEM_SERVICES_READY;
+import static com.android.server.usage.UsageStatsService.MSG_REPORT_EVENT;
+
+import android.app.ActivityManager;
+import android.app.AppGlobals;
+import android.app.admin.DevicePolicyManager;
+import android.app.usage.UsageEvents;
+import android.app.usage.UsageStatsManagerInternal;
+import android.appwidget.AppWidgetManager;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
+import android.content.pm.ParceledListSlice;
+import android.database.ContentObserver;
+import android.hardware.display.DisplayManager;
+import android.net.NetworkScoreManager;
+import android.os.BatteryManager;
+import android.os.BatteryStats;
+import android.os.Handler;
+import android.os.IDeviceIdleController;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.telephony.TelephonyManager;
+import android.util.KeyValueListParser;
+import android.util.Slog;
+import android.util.SparseIntArray;
+import android.util.TimeUtils;
+import android.view.Display;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.app.IBatteryStats;
+import com.android.internal.os.SomeArgs;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.server.LocalServices;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Manages the standby state of an app, listening to various events.
+ */
+public class AppStandbyController {
+
+    private static final String TAG = "AppStandbyController";
+    private static final boolean DEBUG = false;
+
+    static final boolean COMPRESS_TIME = false;
+    private static final long ONE_MINUTE = 60 * 1000;
+
+    // To name the lock for stack traces
+    static class Lock {}
+
+    /** Lock to protect the app's standby state. Required for calls into AppIdleHistory */
+    private final Object mAppIdleLock = new Lock();
+
+    /** Keeps the history and state for each app. */
+    @GuardedBy("mAppIdleLock")
+    private AppIdleHistory mAppIdleHistory;
+
+    @GuardedBy("mAppIdleLock")
+    private ArrayList<UsageStatsManagerInternal.AppIdleStateChangeListener>
+            mPackageAccessListeners = new ArrayList<>();
+
+    /** Whether we've queried the list of carrier privileged apps. */
+    @GuardedBy("mAppIdleLock")
+    private boolean mHaveCarrierPrivilegedApps;
+
+    /** List of carrier-privileged apps that should be excluded from standby */
+    @GuardedBy("mAppIdleLock")
+    private List<String> mCarrierPrivilegedApps;
+
+    // Messages for the handler
+    static final int MSG_INFORM_LISTENERS = 3;
+    static final int MSG_FORCE_IDLE_STATE = 4;
+    static final int MSG_CHECK_IDLE_STATES = 5;
+    static final int MSG_CHECK_PAROLE_TIMEOUT = 6;
+    static final int MSG_PAROLE_END_TIMEOUT = 7;
+    static final int MSG_REPORT_CONTENT_PROVIDER_USAGE = 8;
+    static final int MSG_PAROLE_STATE_CHANGED = 9;
+    static final int MSG_ONE_TIME_CHECK_IDLE_STATES = 10;
+
+    long mAppIdleScreenThresholdMillis;
+    long mCheckIdleIntervalMillis;
+    long mAppIdleWallclockThresholdMillis;
+    long mAppIdleParoleIntervalMillis;
+    long mAppIdleParoleDurationMillis;
+    boolean mAppIdleEnabled;
+    boolean mAppIdleTempParoled;
+    boolean mCharging;
+    private long mLastAppIdleParoledTime;
+    private boolean mSystemServicesReady = false;
+
+    private volatile boolean mPendingOneTimeCheckIdleStates;
+
+    private final Handler mHandler;
+    private final Context mContext;
+
+    private DisplayManager mDisplayManager;
+    private IDeviceIdleController mDeviceIdleController;
+    private AppWidgetManager mAppWidgetManager;
+    private IBatteryStats mBatteryStats;
+    private PowerManager mPowerManager;
+    private PackageManager mPackageManager;
+    private PackageManagerInternal mPackageManagerInternal;
+
+    AppStandbyController(Context context, Looper looper) {
+        mContext = context;
+        mHandler = new AppStandbyHandler(looper);
+        mPackageManager = mContext.getPackageManager();
+        mAppIdleEnabled = mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_enableAutoPowerModes);
+        if (mAppIdleEnabled) {
+            IntentFilter deviceStates = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
+            deviceStates.addAction(BatteryManager.ACTION_DISCHARGING);
+            deviceStates.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
+            mContext.registerReceiver(new DeviceStateReceiver(), deviceStates);
+        }
+        synchronized (mAppIdleLock) {
+            mAppIdleHistory = new AppIdleHistory(SystemClock.elapsedRealtime());
+        }
+
+        IntentFilter packageFilter = new IntentFilter();
+        packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
+        packageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
+        packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
+        packageFilter.addDataScheme("package");
+
+        mContext.registerReceiverAsUser(new PackageReceiver(), UserHandle.ALL, packageFilter,
+                null, mHandler);
+    }
+
+    public void onBootPhase(int phase) {
+        if (phase == PHASE_SYSTEM_SERVICES_READY) {
+            // Observe changes to the threshold
+            SettingsObserver settingsObserver = new SettingsObserver(mHandler);
+            settingsObserver.registerObserver();
+            settingsObserver.updateSettings();
+
+            mAppWidgetManager = mContext.getSystemService(AppWidgetManager.class);
+            mDeviceIdleController = IDeviceIdleController.Stub.asInterface(
+                    ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
+            mBatteryStats = IBatteryStats.Stub.asInterface(
+                    ServiceManager.getService(BatteryStats.SERVICE_NAME));
+            mDisplayManager = (DisplayManager) mContext.getSystemService(
+                    Context.DISPLAY_SERVICE);
+            mPowerManager = mContext.getSystemService(PowerManager.class);
+            mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
+
+            mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);
+            synchronized (mAppIdleLock) {
+                mAppIdleHistory.updateDisplay(isDisplayOn(), SystemClock.elapsedRealtime());
+            }
+
+            if (mPendingOneTimeCheckIdleStates) {
+                postOneTimeCheckIdleStates();
+            }
+
+            mSystemServicesReady = true;
+        } else if (phase == PHASE_BOOT_COMPLETED) {
+            setChargingState(mContext.getSystemService(BatteryManager.class).isCharging());
+        }
+    }
+
+    void reportContentProviderUsage(String authority, String providerPkgName, int userId) {
+        // Get sync adapters for the authority
+        String[] packages = ContentResolver.getSyncAdapterPackagesForAuthorityAsUser(
+                authority, userId);
+        for (String packageName: packages) {
+            // Only force the sync adapters to active if the provider is not in the same package and
+            // the sync adapter is a system package.
+            try {
+                PackageInfo pi = mPackageManager.getPackageInfoAsUser(
+                        packageName, PackageManager.MATCH_SYSTEM_ONLY, userId);
+                if (pi == null || pi.applicationInfo == null) {
+                    continue;
+                }
+                if (!packageName.equals(providerPkgName)) {
+                    setAppIdleAsync(packageName, false, userId);
+                }
+            } catch (PackageManager.NameNotFoundException e) {
+                // Shouldn't happen
+            }
+        }
+    }
+
+    void setChargingState(boolean charging) {
+        synchronized (mAppIdleLock) {
+            if (mCharging != charging) {
+                mCharging = charging;
+                postParoleStateChanged();
+            }
+        }
+    }
+
+    /** Paroled here means temporary pardon from being inactive */
+    void setAppIdleParoled(boolean paroled) {
+        synchronized (mAppIdleLock) {
+            final long now = System.currentTimeMillis();
+            if (mAppIdleTempParoled != paroled) {
+                mAppIdleTempParoled = paroled;
+                if (DEBUG) Slog.d(TAG, "Changing paroled to " + mAppIdleTempParoled);
+                if (paroled) {
+                    postParoleEndTimeout();
+                } else {
+                    mLastAppIdleParoledTime = now;
+                    postNextParoleTimeout(now);
+                }
+                postParoleStateChanged();
+            }
+        }
+    }
+
+    boolean isParoledOrCharging() {
+        synchronized (mAppIdleLock) {
+            return mAppIdleTempParoled || mCharging;
+        }
+    }
+
+    private void postNextParoleTimeout(long now) {
+        if (DEBUG) Slog.d(TAG, "Posting MSG_CHECK_PAROLE_TIMEOUT");
+        mHandler.removeMessages(MSG_CHECK_PAROLE_TIMEOUT);
+        // Compute when the next parole needs to happen. We check more frequently than necessary
+        // since the message handler delays are based on elapsedRealTime and not wallclock time.
+        // The comparison is done in wallclock time.
+        long timeLeft = (mLastAppIdleParoledTime + mAppIdleParoleIntervalMillis) - now;
+        if (timeLeft < 0) {
+            timeLeft = 0;
+        }
+        mHandler.sendEmptyMessageDelayed(MSG_CHECK_PAROLE_TIMEOUT, timeLeft);
+    }
+
+    private void postParoleEndTimeout() {
+        if (DEBUG) Slog.d(TAG, "Posting MSG_PAROLE_END_TIMEOUT");
+        mHandler.removeMessages(MSG_PAROLE_END_TIMEOUT);
+        mHandler.sendEmptyMessageDelayed(MSG_PAROLE_END_TIMEOUT, mAppIdleParoleDurationMillis);
+    }
+
+    private void postParoleStateChanged() {
+        if (DEBUG) Slog.d(TAG, "Posting MSG_PAROLE_STATE_CHANGED");
+        mHandler.removeMessages(MSG_PAROLE_STATE_CHANGED);
+        mHandler.sendEmptyMessage(MSG_PAROLE_STATE_CHANGED);
+    }
+
+    void postCheckIdleStates(int userId) {
+        mHandler.sendMessage(mHandler.obtainMessage(MSG_CHECK_IDLE_STATES, userId, 0));
+    }
+
+    /**
+     * We send a different message to check idle states once, otherwise we would end up
+     * scheduling a series of repeating checkIdleStates each time we fired off one.
+     */
+    void postOneTimeCheckIdleStates() {
+        if (mDeviceIdleController == null) {
+            // Not booted yet; wait for it!
+            mPendingOneTimeCheckIdleStates = true;
+        } else {
+            mHandler.sendEmptyMessage(MSG_ONE_TIME_CHECK_IDLE_STATES);
+            mPendingOneTimeCheckIdleStates = false;
+        }
+    }
+
+    /**
+     * Check all running users' or specified user's apps to see if they enter an idle state.
+     * @return Returns whether checking should continue periodically.
+     */
+    boolean checkIdleStates(int checkUserId) {
+        if (!mAppIdleEnabled) {
+            return false;
+        }
+
+        final int[] runningUserIds;
+        try {
+            runningUserIds = ActivityManager.getService().getRunningUserIds();
+            if (checkUserId != UserHandle.USER_ALL
+                    && !ArrayUtils.contains(runningUserIds, checkUserId)) {
+                return false;
+            }
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+
+        final long elapsedRealtime = SystemClock.elapsedRealtime();
+        for (int i = 0; i < runningUserIds.length; i++) {
+            final int userId = runningUserIds[i];
+            if (checkUserId != UserHandle.USER_ALL && checkUserId != userId) {
+                continue;
+            }
+            if (DEBUG) {
+                Slog.d(TAG, "Checking idle state for user " + userId);
+            }
+            List<PackageInfo> packages = mPackageManager.getInstalledPackagesAsUser(
+                    PackageManager.MATCH_DISABLED_COMPONENTS,
+                    userId);
+            final int packageCount = packages.size();
+            for (int p = 0; p < packageCount; p++) {
+                final PackageInfo pi = packages.get(p);
+                final String packageName = pi.packageName;
+                final boolean isIdle = isAppIdleFiltered(packageName,
+                        UserHandle.getAppId(pi.applicationInfo.uid),
+                        userId, elapsedRealtime);
+                mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS,
+                        userId, isIdle ? 1 : 0, packageName));
+                if (isIdle) {
+                    synchronized (mAppIdleLock) {
+                        mAppIdleHistory.setIdle(packageName, userId, elapsedRealtime);
+                    }
+                }
+            }
+        }
+        if (DEBUG) {
+            Slog.d(TAG, "checkIdleStates took "
+                    + (SystemClock.elapsedRealtime() - elapsedRealtime));
+        }
+        return true;
+    }
+
+    /** Check if it's been a while since last parole and let idle apps do some work */
+    void checkParoleTimeout() {
+        boolean setParoled = false;
+        synchronized (mAppIdleLock) {
+            final long now = System.currentTimeMillis();
+            if (!mAppIdleTempParoled) {
+                final long timeSinceLastParole = now - mLastAppIdleParoledTime;
+                if (timeSinceLastParole > mAppIdleParoleIntervalMillis) {
+                    if (DEBUG) Slog.d(TAG, "Crossed default parole interval");
+                    setParoled = true;
+                } else {
+                    if (DEBUG) Slog.d(TAG, "Not long enough to go to parole");
+                    postNextParoleTimeout(now);
+                }
+            }
+        }
+        if (setParoled) {
+            setAppIdleParoled(true);
+        }
+    }
+
+    private void notifyBatteryStats(String packageName, int userId, boolean idle) {
+        try {
+            final int uid = mPackageManager.getPackageUidAsUser(packageName,
+                    PackageManager.MATCH_UNINSTALLED_PACKAGES, userId);
+            if (idle) {
+                mBatteryStats.noteEvent(BatteryStats.HistoryItem.EVENT_PACKAGE_INACTIVE,
+                        packageName, uid);
+            } else {
+                mBatteryStats.noteEvent(BatteryStats.HistoryItem.EVENT_PACKAGE_ACTIVE,
+                        packageName, uid);
+            }
+        } catch (PackageManager.NameNotFoundException | RemoteException e) {
+        }
+    }
+
+    void onDeviceIdleModeChanged() {
+        final boolean deviceIdle = mPowerManager.isDeviceIdleMode();
+        if (DEBUG) Slog.i(TAG, "DeviceIdleMode changed to " + deviceIdle);
+        boolean paroled = false;
+        synchronized (mAppIdleLock) {
+            final long timeSinceLastParole = System.currentTimeMillis() - mLastAppIdleParoledTime;
+            if (!deviceIdle
+                    && timeSinceLastParole >= mAppIdleParoleIntervalMillis) {
+                if (DEBUG) {
+                    Slog.i(TAG, "Bringing idle apps out of inactive state due to deviceIdleMode=false");
+                }
+                paroled = true;
+            } else if (deviceIdle) {
+                if (DEBUG) Slog.i(TAG, "Device idle, back to prison");
+                paroled = false;
+            } else {
+                return;
+            }
+        }
+        setAppIdleParoled(paroled);
+    }
+
+    void reportEvent(UsageEvents.Event event, long elapsedRealtime, int userId) {
+        synchronized (mAppIdleLock) {
+            // TODO: Ideally this should call isAppIdleFiltered() to avoid calling back
+            // about apps that are on some kind of whitelist anyway.
+            final boolean previouslyIdle = mAppIdleHistory.isIdle(
+                    event.mPackage, userId, elapsedRealtime);
+            // Inform listeners if necessary
+            if ((event.mEventType == UsageEvents.Event.MOVE_TO_FOREGROUND
+                    || event.mEventType == UsageEvents.Event.MOVE_TO_BACKGROUND
+                    || event.mEventType == UsageEvents.Event.SYSTEM_INTERACTION
+                    || event.mEventType == UsageEvents.Event.USER_INTERACTION)) {
+                mAppIdleHistory.reportUsage(event.mPackage, userId, elapsedRealtime);
+                if (previouslyIdle) {
+                    mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS, userId,
+                                /* idle = */ 0, event.mPackage));
+                    notifyBatteryStats(event.mPackage, userId, false);
+                }
+            }
+        }
+
+    }
+
+    /**
+     * Forces the app's beginIdleTime and lastUsedTime to reflect idle or active. If idle,
+     * then it rolls back the beginIdleTime and lastUsedTime to a point in time that's behind
+     * the threshold for idle.
+     *
+     * This method is always called from the handler thread, so not much synchronization is
+     * required.
+     */
+    void forceIdleState(String packageName, int userId, boolean idle) {
+        final int appId = getAppId(packageName);
+        if (appId < 0) return;
+        final long elapsedRealtime = SystemClock.elapsedRealtime();
+
+        final boolean previouslyIdle = isAppIdleFiltered(packageName, appId,
+                userId, elapsedRealtime);
+        synchronized (mAppIdleLock) {
+            mAppIdleHistory.setIdle(packageName, userId, idle, elapsedRealtime);
+        }
+        final boolean stillIdle = isAppIdleFiltered(packageName, appId,
+                userId, elapsedRealtime);
+        // Inform listeners if necessary
+        if (previouslyIdle != stillIdle) {
+            mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS, userId,
+                    /* idle = */ stillIdle ? 1 : 0, packageName));
+            if (!stillIdle) {
+                notifyBatteryStats(packageName, userId, idle);
+            }
+        }
+    }
+
+    public void onUserRemoved(int userId) {
+        synchronized (mAppIdleLock) {
+            mAppIdleHistory.onUserRemoved(userId);
+        }
+    }
+
+    private boolean isAppIdleUnfiltered(String packageName, int userId, long elapsedRealtime) {
+        synchronized (mAppIdleLock) {
+            return mAppIdleHistory.isIdle(packageName, userId, elapsedRealtime);
+        }
+    }
+
+    void addListener(UsageStatsManagerInternal.AppIdleStateChangeListener listener) {
+        synchronized (mAppIdleLock) {
+            if (!mPackageAccessListeners.contains(listener)) {
+                mPackageAccessListeners.add(listener);
+            }
+        }
+    }
+
+    void removeListener(UsageStatsManagerInternal.AppIdleStateChangeListener listener) {
+        synchronized (mAppIdleLock) {
+            mPackageAccessListeners.remove(listener);
+        }
+    }
+
+    int getAppId(String packageName) {
+        try {
+            ApplicationInfo ai = mPackageManager.getApplicationInfo(packageName,
+                    PackageManager.MATCH_ANY_USER
+                            | PackageManager.MATCH_DISABLED_COMPONENTS);
+            return ai.uid;
+        } catch (PackageManager.NameNotFoundException re) {
+            return -1;
+        }
+    }
+
+    boolean isAppIdleFilteredOrParoled(String packageName, int userId, long elapsedRealtime,
+            boolean shouldObfuscateInstantApps) {
+        if (isParoledOrCharging()) {
+            return false;
+        }
+        if (shouldObfuscateInstantApps &&
+                mPackageManagerInternal.isPackageEphemeral(userId, packageName)) {
+            return false;
+        }
+        return isAppIdleFiltered(packageName, getAppId(packageName), userId, elapsedRealtime);
+    }
+
+    /**
+     * Checks if an app has been idle for a while and filters out apps that are excluded.
+     * It returns false if the current system state allows all apps to be considered active.
+     * This happens if the device is plugged in or temporarily allowed to make exceptions.
+     * Called by interface impls.
+     */
+    boolean isAppIdleFiltered(String packageName, int appId, int userId,
+            long elapsedRealtime) {
+        if (packageName == null) return false;
+        // If not enabled at all, of course nobody is ever idle.
+        if (!mAppIdleEnabled) {
+            return false;
+        }
+        if (appId < Process.FIRST_APPLICATION_UID) {
+            // System uids never go idle.
+            return false;
+        }
+        if (packageName.equals("android")) {
+            // Nor does the framework (which should be redundant with the above, but for MR1 we will
+            // retain this for safety).
+            return false;
+        }
+        if (mSystemServicesReady) {
+            try {
+                // We allow all whitelisted apps, including those that don't want to be whitelisted
+                // for idle mode, because app idle (aka app standby) is really not as big an issue
+                // for controlling who participates vs. doze mode.
+                if (mDeviceIdleController.isPowerSaveWhitelistExceptIdleApp(packageName)) {
+                    return false;
+                }
+            } catch (RemoteException re) {
+                throw re.rethrowFromSystemServer();
+            }
+
+            if (isActiveDeviceAdmin(packageName, userId)) {
+                return false;
+            }
+
+            if (isActiveNetworkScorer(packageName)) {
+                return false;
+            }
+
+            if (mAppWidgetManager != null
+                    && mAppWidgetManager.isBoundWidgetPackage(packageName, userId)) {
+                return false;
+            }
+
+            if (isDeviceProvisioningPackage(packageName)) {
+                return false;
+            }
+        }
+
+        if (!isAppIdleUnfiltered(packageName, userId, elapsedRealtime)) {
+            return false;
+        }
+
+        // Check this last, as it is the most expensive check
+        // TODO: Optimize this by fetching the carrier privileged apps ahead of time
+        if (isCarrierApp(packageName)) {
+            return false;
+        }
+
+        return true;
+    }
+
+    int[] getIdleUidsForUser(int userId) {
+        if (!mAppIdleEnabled) {
+            return new int[0];
+        }
+
+        final long elapsedRealtime = SystemClock.elapsedRealtime();
+
+        List<ApplicationInfo> apps;
+        try {
+            ParceledListSlice<ApplicationInfo> slice = AppGlobals.getPackageManager()
+                    .getInstalledApplications(/* flags= */ 0, userId);
+            if (slice == null) {
+                return new int[0];
+            }
+            apps = slice.getList();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+
+        // State of each uid.  Key is the uid.  Value lower 16 bits is the number of apps
+        // associated with that uid, upper 16 bits is the number of those apps that is idle.
+        SparseIntArray uidStates = new SparseIntArray();
+
+        // Now resolve all app state.  Iterating over all apps, keeping track of how many
+        // we find for each uid and how many of those are idle.
+        for (int i = apps.size() - 1; i >= 0; i--) {
+            ApplicationInfo ai = apps.get(i);
+
+            // Check whether this app is idle.
+            boolean idle = isAppIdleFiltered(ai.packageName, UserHandle.getAppId(ai.uid),
+                    userId, elapsedRealtime);
+
+            int index = uidStates.indexOfKey(ai.uid);
+            if (index < 0) {
+                uidStates.put(ai.uid, 1 + (idle ? 1<<16 : 0));
+            } else {
+                int value = uidStates.valueAt(index);
+                uidStates.setValueAt(index, value + 1 + (idle ? 1<<16 : 0));
+            }
+        }
+        if (DEBUG) {
+            Slog.d(TAG, "getIdleUids took " + (SystemClock.elapsedRealtime() - elapsedRealtime));
+        }
+        int numIdle = 0;
+        for (int i = uidStates.size() - 1; i >= 0; i--) {
+            int value = uidStates.valueAt(i);
+            if ((value&0x7fff) == (value>>16)) {
+                numIdle++;
+            }
+        }
+
+        int[] res = new int[numIdle];
+        numIdle = 0;
+        for (int i = uidStates.size() - 1; i >= 0; i--) {
+            int value = uidStates.valueAt(i);
+            if ((value&0x7fff) == (value>>16)) {
+                res[numIdle] = uidStates.keyAt(i);
+                numIdle++;
+            }
+        }
+
+        return res;
+    }
+
+    void setAppIdleAsync(String packageName, boolean idle, int userId) {
+        if (packageName == null) return;
+
+        mHandler.obtainMessage(MSG_FORCE_IDLE_STATE, userId, idle ? 1 : 0, packageName)
+                .sendToTarget();
+    }
+
+    private boolean isActiveDeviceAdmin(String packageName, int userId) {
+        DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class);
+        if (dpm == null) return false;
+        return dpm.packageHasActiveAdmins(packageName, userId);
+    }
+
+    /**
+     * Returns {@code true} if the supplied package is the device provisioning app. Otherwise,
+     * returns {@code false}.
+     */
+    private boolean isDeviceProvisioningPackage(String packageName) {
+        String deviceProvisioningPackage = mContext.getResources().getString(
+                com.android.internal.R.string.config_deviceProvisioningPackage);
+        return deviceProvisioningPackage != null && deviceProvisioningPackage.equals(packageName);
+    }
+
+    private boolean isCarrierApp(String packageName) {
+        synchronized (mAppIdleLock) {
+            if (!mHaveCarrierPrivilegedApps) {
+                fetchCarrierPrivilegedAppsLA();
+            }
+            if (mCarrierPrivilegedApps != null) {
+                return mCarrierPrivilegedApps.contains(packageName);
+            }
+            return false;
+        }
+    }
+
+    void clearCarrierPrivilegedApps() {
+        if (DEBUG) {
+            Slog.i(TAG, "Clearing carrier privileged apps list");
+        }
+        synchronized (mAppIdleLock) {
+            mHaveCarrierPrivilegedApps = false;
+            mCarrierPrivilegedApps = null; // Need to be refetched.
+        }
+    }
+
+    @GuardedBy("mAppIdleLock")
+    private void fetchCarrierPrivilegedAppsLA() {
+        TelephonyManager telephonyManager =
+                mContext.getSystemService(TelephonyManager.class);
+        mCarrierPrivilegedApps = telephonyManager.getPackagesWithCarrierPrivileges();
+        mHaveCarrierPrivilegedApps = true;
+        if (DEBUG) {
+            Slog.d(TAG, "apps with carrier privilege " + mCarrierPrivilegedApps);
+        }
+    }
+
+    private boolean isActiveNetworkScorer(String packageName) {
+        NetworkScoreManager nsm = (NetworkScoreManager) mContext.getSystemService(
+                Context.NETWORK_SCORE_SERVICE);
+        return packageName != null && packageName.equals(nsm.getActiveScorerPackage());
+    }
+
+    void informListeners(String packageName, int userId, boolean isIdle) {
+        for (UsageStatsManagerInternal.AppIdleStateChangeListener listener : mPackageAccessListeners) {
+            listener.onAppIdleStateChanged(packageName, userId, isIdle);
+        }
+    }
+
+    void informParoleStateChanged() {
+        final boolean paroled = isParoledOrCharging();
+        for (UsageStatsManagerInternal.AppIdleStateChangeListener listener : mPackageAccessListeners) {
+            listener.onParoleStateChanged(paroled);
+        }
+    }
+
+    void flushToDisk(int userId) {
+        synchronized (mAppIdleLock) {
+            mAppIdleHistory.writeAppIdleTimes(userId);
+        }
+    }
+
+    void flushDurationsToDisk() {
+        // Persist elapsed and screen on time. If this fails for whatever reason, the apps will be
+        // considered not-idle, which is the safest outcome in such an event.
+        synchronized (mAppIdleLock) {
+            mAppIdleHistory.writeAppIdleDurations();
+        }
+    }
+
+    boolean isDisplayOn() {
+        return mDisplayManager
+                .getDisplay(Display.DEFAULT_DISPLAY).getState() == Display.STATE_ON;
+    }
+
+    void clearAppIdleForPackage(String packageName, int userId) {
+        synchronized (mAppIdleLock) {
+            mAppIdleHistory.clearUsage(packageName, userId);
+        }
+    }
+
+    private class PackageReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final String action = intent.getAction();
+            if (Intent.ACTION_PACKAGE_ADDED.equals(action)
+                    || Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
+                clearCarrierPrivilegedApps();
+            }
+            if ((Intent.ACTION_PACKAGE_REMOVED.equals(action) ||
+                    Intent.ACTION_PACKAGE_ADDED.equals(action))
+                    && !intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
+                clearAppIdleForPackage(intent.getData().getSchemeSpecificPart(),
+                        getSendingUserId());
+            }
+        }
+    }
+
+    void initializeDefaultsForSystemApps(int userId) {
+        Slog.d(TAG, "Initializing defaults for system apps on user " + userId);
+        final long elapsedRealtime = SystemClock.elapsedRealtime();
+        List<PackageInfo> packages = mPackageManager.getInstalledPackagesAsUser(
+                PackageManager.MATCH_DISABLED_COMPONENTS,
+                userId);
+        final int packageCount = packages.size();
+        synchronized (mAppIdleLock) {
+            for (int i = 0; i < packageCount; i++) {
+                final PackageInfo pi = packages.get(i);
+                String packageName = pi.packageName;
+                if (pi.applicationInfo != null && pi.applicationInfo.isSystemApp()) {
+                    mAppIdleHistory.reportUsage(packageName, userId, elapsedRealtime);
+                }
+            }
+        }
+    }
+
+    void postReportContentProviderUsage(String name, String packageName, int userId) {
+        SomeArgs args = SomeArgs.obtain();
+        args.arg1 = name;
+        args.arg2 = packageName;
+        args.arg3 = userId;
+        mHandler.obtainMessage(MSG_REPORT_CONTENT_PROVIDER_USAGE, args)
+                .sendToTarget();
+    }
+
+    void dumpHistory(IndentingPrintWriter idpw, int userId) {
+        synchronized (mAppIdleLock) {
+            mAppIdleHistory.dumpHistory(idpw, userId);
+        }
+    }
+
+    void dumpUser(IndentingPrintWriter idpw, int userId) {
+        synchronized (mAppIdleLock) {
+            mAppIdleHistory.dump(idpw, userId);
+        }
+    }
+
+    void dumpState(String[] args, PrintWriter pw) {
+        synchronized (mAppIdleLock) {
+            pw.println("Carrier privileged apps (have=" + mHaveCarrierPrivilegedApps
+                    + "): " + mCarrierPrivilegedApps);
+        }
+
+        pw.println();
+        pw.println("Settings:");
+
+        pw.print("  mAppIdleDurationMillis=");
+        TimeUtils.formatDuration(mAppIdleScreenThresholdMillis, pw);
+        pw.println();
+
+        pw.print("  mAppIdleWallclockThresholdMillis=");
+        TimeUtils.formatDuration(mAppIdleWallclockThresholdMillis, pw);
+        pw.println();
+
+        pw.print("  mCheckIdleIntervalMillis=");
+        TimeUtils.formatDuration(mCheckIdleIntervalMillis, pw);
+        pw.println();
+
+        pw.print("  mAppIdleParoleIntervalMillis=");
+        TimeUtils.formatDuration(mAppIdleParoleIntervalMillis, pw);
+        pw.println();
+
+        pw.print("  mAppIdleParoleDurationMillis=");
+        TimeUtils.formatDuration(mAppIdleParoleDurationMillis, pw);
+        pw.println();
+
+        pw.println();
+        pw.print("mAppIdleEnabled="); pw.print(mAppIdleEnabled);
+        pw.print(" mAppIdleTempParoled="); pw.print(mAppIdleTempParoled);
+        pw.print(" mCharging="); pw.print(mCharging);
+        pw.print(" mLastAppIdleParoledTime=");
+        TimeUtils.formatDuration(mLastAppIdleParoledTime, pw);
+        pw.println();
+    }
+
+    class AppStandbyHandler extends Handler {
+
+        AppStandbyHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_FORCE_IDLE_STATE:
+                    forceIdleState((String) msg.obj, msg.arg1, msg.arg2 == 1);
+                    break;
+
+                case MSG_CHECK_IDLE_STATES:
+                    if (checkIdleStates(msg.arg1)) {
+                        mHandler.sendMessageDelayed(mHandler.obtainMessage(
+                                MSG_CHECK_IDLE_STATES, msg.arg1, 0),
+                                mCheckIdleIntervalMillis);
+                    }
+                    break;
+
+                case MSG_ONE_TIME_CHECK_IDLE_STATES:
+                    mHandler.removeMessages(MSG_ONE_TIME_CHECK_IDLE_STATES);
+                    checkIdleStates(UserHandle.USER_ALL);
+                    break;
+
+                case MSG_CHECK_PAROLE_TIMEOUT:
+                    checkParoleTimeout();
+                    break;
+
+                case MSG_PAROLE_END_TIMEOUT:
+                    if (DEBUG) Slog.d(TAG, "Ending parole");
+                    setAppIdleParoled(false);
+                    break;
+
+                case MSG_REPORT_CONTENT_PROVIDER_USAGE:
+                    SomeArgs args = (SomeArgs) msg.obj;
+                    reportContentProviderUsage((String) args.arg1, // authority name
+                            (String) args.arg2, // package name
+                            (int) args.arg3); // userId
+                    args.recycle();
+                    break;
+
+                case MSG_PAROLE_STATE_CHANGED:
+                    if (DEBUG) Slog.d(TAG, "Parole state: " + mAppIdleTempParoled
+                            + ", Charging state:" + mCharging);
+                    informParoleStateChanged();
+                    break;
+                default:
+                    super.handleMessage(msg);
+                    break;
+
+            }
+        }
+    };
+
+    private class DeviceStateReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final String action = intent.getAction();
+            if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
+                setChargingState(intent.getIntExtra("plugged", 0) != 0);
+            } else if (PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED.equals(action)) {
+                onDeviceIdleModeChanged();
+            }
+        }
+    }
+
+    private final DisplayManager.DisplayListener mDisplayListener
+            = new DisplayManager.DisplayListener() {
+
+        @Override public void onDisplayAdded(int displayId) {
+        }
+
+        @Override public void onDisplayRemoved(int displayId) {
+        }
+
+        @Override public void onDisplayChanged(int displayId) {
+            if (displayId == Display.DEFAULT_DISPLAY) {
+                final boolean displayOn = isDisplayOn();
+                synchronized (mAppIdleLock) {
+                    mAppIdleHistory.updateDisplay(displayOn, SystemClock.elapsedRealtime());
+                }
+            }
+        }
+    };
+
+    /**
+     * Observe settings changes for {@link Settings.Global#APP_IDLE_CONSTANTS}.
+     */
+    private class SettingsObserver extends ContentObserver {
+        /**
+         * This flag has been used to disable app idle on older builds with bug b/26355386.
+         */
+        @Deprecated
+        private static final String KEY_IDLE_DURATION_OLD = "idle_duration";
+
+        private static final String KEY_IDLE_DURATION = "idle_duration2";
+        private static final String KEY_WALLCLOCK_THRESHOLD = "wallclock_threshold";
+        private static final String KEY_PAROLE_INTERVAL = "parole_interval";
+        private static final String KEY_PAROLE_DURATION = "parole_duration";
+
+        private final KeyValueListParser mParser = new KeyValueListParser(',');
+
+        SettingsObserver(Handler handler) {
+            super(handler);
+        }
+
+        void registerObserver() {
+            mContext.getContentResolver().registerContentObserver(Settings.Global.getUriFor(
+                    Settings.Global.APP_IDLE_CONSTANTS), false, this);
+        }
+
+        @Override
+        public void onChange(boolean selfChange) {
+            updateSettings();
+            postOneTimeCheckIdleStates();
+        }
+
+        void updateSettings() {
+            synchronized (mAppIdleLock) {
+                // Look at global settings for this.
+                // TODO: Maybe apply different thresholds for different users.
+                try {
+                    mParser.setString(Settings.Global.getString(mContext.getContentResolver(),
+                            Settings.Global.APP_IDLE_CONSTANTS));
+                } catch (IllegalArgumentException e) {
+                    Slog.e(TAG, "Bad value for app idle settings: " + e.getMessage());
+                    // fallthrough, mParser is empty and all defaults will be returned.
+                }
+
+                // Default: 12 hours of screen-on time sans dream-time
+                mAppIdleScreenThresholdMillis = mParser.getLong(KEY_IDLE_DURATION,
+                        COMPRESS_TIME ? ONE_MINUTE * 4 : 12 * 60 * ONE_MINUTE);
+
+                mAppIdleWallclockThresholdMillis = mParser.getLong(KEY_WALLCLOCK_THRESHOLD,
+                        COMPRESS_TIME ? ONE_MINUTE * 8 : 2L * 24 * 60 * ONE_MINUTE); // 2 days
+
+                mCheckIdleIntervalMillis = Math.min(mAppIdleScreenThresholdMillis / 4,
+                        COMPRESS_TIME ? ONE_MINUTE : 8 * 60 * ONE_MINUTE); // 8 hours
+
+                // Default: 24 hours between paroles
+                mAppIdleParoleIntervalMillis = mParser.getLong(KEY_PAROLE_INTERVAL,
+                        COMPRESS_TIME ? ONE_MINUTE * 10 : 24 * 60 * ONE_MINUTE);
+
+                mAppIdleParoleDurationMillis = mParser.getLong(KEY_PAROLE_DURATION,
+                        COMPRESS_TIME ? ONE_MINUTE : 10 * ONE_MINUTE); // 10 minutes
+                mAppIdleHistory.setThresholds(mAppIdleWallclockThresholdMillis,
+                        mAppIdleScreenThresholdMillis);
+            }
+        }
+    }
+
+}
+
diff --git a/com/android/server/usage/UsageStatsService.java b/com/android/server/usage/UsageStatsService.java
index 25e471c..afafea1 100644
--- a/com/android/server/usage/UsageStatsService.java
+++ b/com/android/server/usage/UsageStatsService.java
@@ -18,37 +18,24 @@
 
 import android.Manifest;
 import android.app.ActivityManager;
-import android.app.AppGlobals;
 import android.app.AppOpsManager;
 import android.app.IUidObserver;
-import android.app.admin.DevicePolicyManager;
 import android.app.usage.ConfigurationStats;
 import android.app.usage.IUsageStatsManager;
 import android.app.usage.UsageEvents;
 import android.app.usage.UsageEvents.Event;
 import android.app.usage.UsageStats;
 import android.app.usage.UsageStatsManagerInternal;
-import android.app.usage.UsageStatsManagerInternal.AppIdleStateChangeListener;
-import android.appwidget.AppWidgetManager;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
-import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
-import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.UserInfo;
 import android.content.res.Configuration;
-import android.database.ContentObserver;
-import android.hardware.display.DisplayManager;
-import android.net.NetworkScoreManager;
-import android.os.BatteryManager;
-import android.os.BatteryStats;
 import android.os.Binder;
 import android.os.Environment;
 import android.os.FileUtils;
@@ -56,7 +43,6 @@
 import android.os.IDeviceIdleController;
 import android.os.Looper;
 import android.os.Message;
-import android.os.PowerManager;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -64,21 +50,12 @@
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
-import android.provider.Settings;
-import android.telephony.TelephonyManager;
 import android.util.ArraySet;
-import android.util.KeyValueListParser;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
-import android.util.TimeUtils;
-import android.view.Display;
 
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.app.IBatteryStats;
 import com.android.internal.os.BackgroundThread;
-import com.android.internal.os.SomeArgs;
-import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.LocalServices;
@@ -88,7 +65,6 @@
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.PrintWriter;
-import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 
@@ -107,7 +83,6 @@
     static final boolean COMPRESS_TIME = false;
 
     private static final long TEN_SECONDS = 10 * 1000;
-    private static final long ONE_MINUTE = 60 * 1000;
     private static final long TWENTY_MINUTES = 20 * 60 * 1000;
     private static final long FLUSH_INTERVAL = COMPRESS_TIME ? TEN_SECONDS : TWENTY_MINUTES;
     private static final long TIME_CHANGE_THRESHOLD_MILLIS = 2 * 1000; // Two seconds.
@@ -115,24 +90,10 @@
     private static final boolean ENABLE_KERNEL_UPDATES = true;
     private static final File KERNEL_COUNTER_FILE = new File("/proc/uid_procstat/set");
 
-    long mAppIdleScreenThresholdMillis;
-    long mCheckIdleIntervalMillis;
-    long mAppIdleWallclockThresholdMillis;
-    long mAppIdleParoleIntervalMillis;
-    long mAppIdleParoleDurationMillis;
-
     // Handler message types.
     static final int MSG_REPORT_EVENT = 0;
     static final int MSG_FLUSH_TO_DISK = 1;
     static final int MSG_REMOVE_USER = 2;
-    static final int MSG_INFORM_LISTENERS = 3;
-    static final int MSG_FORCE_IDLE_STATE = 4;
-    static final int MSG_CHECK_IDLE_STATES = 5;
-    static final int MSG_CHECK_PAROLE_TIMEOUT = 6;
-    static final int MSG_PAROLE_END_TIMEOUT = 7;
-    static final int MSG_REPORT_CONTENT_PROVIDER_USAGE = 8;
-    static final int MSG_PAROLE_STATE_CHANGED = 9;
-    static final int MSG_ONE_TIME_CHECK_IDLE_STATES = 10;
 
     private final Object mLock = new Object();
     Handler mHandler;
@@ -140,11 +101,7 @@
     UserManager mUserManager;
     PackageManager mPackageManager;
     PackageManagerInternal mPackageManagerInternal;
-    AppWidgetManager mAppWidgetManager;
     IDeviceIdleController mDeviceIdleController;
-    private DisplayManager mDisplayManager;
-    private PowerManager mPowerManager;
-    private IBatteryStats mBatteryStats;
 
     private final SparseArray<UserUsageStatsService> mUserState = new SparseArray<>();
     private final SparseIntArray mUidToKernelCounter = new SparseIntArray();
@@ -152,26 +109,7 @@
     long mRealTimeSnapshot;
     long mSystemTimeSnapshot;
 
-    boolean mAppIdleEnabled;
-    boolean mAppIdleTempParoled;
-    boolean mCharging;
-    private long mLastAppIdleParoledTime;
-
-    private volatile boolean mPendingOneTimeCheckIdleStates;
-    private boolean mSystemServicesReady = false;
-
-    private final Object mAppIdleLock = new Object();
-    @GuardedBy("mAppIdleLock")
-    private AppIdleHistory mAppIdleHistory;
-
-    @GuardedBy("mAppIdleLock")
-    private ArrayList<UsageStatsManagerInternal.AppIdleStateChangeListener>
-            mPackageAccessListeners = new ArrayList<>();
-
-    @GuardedBy("mAppIdleLock")
-    private boolean mHaveCarrierPrivilegedApps;
-    @GuardedBy("mAppIdleLock")
-    private List<String> mCarrierPrivilegedApps;
+    AppStandbyController mAppStandby;
 
     public UsageStatsService(Context context) {
         super(context);
@@ -185,6 +123,8 @@
         mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
         mHandler = new H(BackgroundThread.get().getLooper());
 
+        mAppStandby = new AppStandbyController(getContext(), BackgroundThread.get().getLooper());
+
         File systemDataDir = new File(Environment.getDataDirectory(), "system");
         mUsageStatsDir = new File(systemDataDir, "usagestats");
         mUsageStatsDir.mkdirs();
@@ -198,30 +138,9 @@
         getContext().registerReceiverAsUser(new UserActionsReceiver(), UserHandle.ALL, filter,
                 null, mHandler);
 
-        IntentFilter packageFilter = new IntentFilter();
-        packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
-        packageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
-        packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
-        packageFilter.addDataScheme("package");
-
-        getContext().registerReceiverAsUser(new PackageReceiver(), UserHandle.ALL, packageFilter,
-                null, mHandler);
-
-        mAppIdleEnabled = getContext().getResources().getBoolean(
-                com.android.internal.R.bool.config_enableAutoPowerModes);
-        if (mAppIdleEnabled) {
-            IntentFilter deviceStates = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
-            deviceStates.addAction(BatteryManager.ACTION_DISCHARGING);
-            deviceStates.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
-            getContext().registerReceiver(new DeviceStateReceiver(), deviceStates);
-        }
-
         synchronized (mLock) {
             cleanUpRemovedUsersLocked();
         }
-        synchronized (mAppIdleLock) {
-            mAppIdleHistory = new AppIdleHistory(SystemClock.elapsedRealtime());
-        }
 
         mRealTimeSnapshot = SystemClock.elapsedRealtime();
         mSystemTimeSnapshot = System.currentTimeMillis();
@@ -233,28 +152,10 @@
     @Override
     public void onBootPhase(int phase) {
         if (phase == PHASE_SYSTEM_SERVICES_READY) {
-            // Observe changes to the threshold
-            SettingsObserver settingsObserver = new SettingsObserver(mHandler);
-            settingsObserver.registerObserver();
-            settingsObserver.updateSettings();
+            mAppStandby.onBootPhase(phase);
 
-            mAppWidgetManager = getContext().getSystemService(AppWidgetManager.class);
             mDeviceIdleController = IDeviceIdleController.Stub.asInterface(
                     ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
-            mBatteryStats = IBatteryStats.Stub.asInterface(
-                    ServiceManager.getService(BatteryStats.SERVICE_NAME));
-            mDisplayManager = (DisplayManager) getContext().getSystemService(
-                    Context.DISPLAY_SERVICE);
-            mPowerManager = getContext().getSystemService(PowerManager.class);
-
-            mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);
-            synchronized (mAppIdleLock) {
-                mAppIdleHistory.updateDisplay(isDisplayOn(), SystemClock.elapsedRealtime());
-            }
-
-            if (mPendingOneTimeCheckIdleStates) {
-                postOneTimeCheckIdleStates();
-            }
 
             if (ENABLE_KERNEL_UPDATES && KERNEL_COUNTER_FILE.exists()) {
                 try {
@@ -268,18 +169,9 @@
             } else {
                 Slog.w(TAG, "Missing procfs interface: " + KERNEL_COUNTER_FILE);
             }
-
-            mSystemServicesReady = true;
-        } else if (phase == PHASE_BOOT_COMPLETED) {
-            setChargingState(getContext().getSystemService(BatteryManager.class).isCharging());
         }
     }
 
-    private boolean isDisplayOn() {
-        return mDisplayManager
-                .getDisplay(Display.DEFAULT_DISPLAY).getState() == Display.STATE_ON;
-    }
-
     private class UserActionsReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -291,60 +183,12 @@
                 }
             } else if (Intent.ACTION_USER_STARTED.equals(action)) {
                 if (userId >=0) {
-                    postCheckIdleStates(userId);
+                    mAppStandby.postCheckIdleStates(userId);
                 }
             }
         }
     }
 
-    private class PackageReceiver extends BroadcastReceiver {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            final String action = intent.getAction();
-            if (Intent.ACTION_PACKAGE_ADDED.equals(action)
-                    || Intent.ACTION_PACKAGE_CHANGED.equals(action)) {
-                clearCarrierPrivilegedApps();
-            }
-            if ((Intent.ACTION_PACKAGE_REMOVED.equals(action) ||
-                    Intent.ACTION_PACKAGE_ADDED.equals(action))
-                    && !intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
-                clearAppIdleForPackage(intent.getData().getSchemeSpecificPart(),
-                        getSendingUserId());
-            }
-        }
-    }
-
-    private class DeviceStateReceiver extends BroadcastReceiver {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            final String action = intent.getAction();
-            if (Intent.ACTION_BATTERY_CHANGED.equals(action)) {
-                setChargingState(intent.getIntExtra("plugged", 0) != 0);
-            } else if (PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED.equals(action)) {
-                onDeviceIdleModeChanged();
-            }
-        }
-    }
-
-    private final DisplayManager.DisplayListener mDisplayListener
-            = new DisplayManager.DisplayListener() {
-
-        @Override public void onDisplayAdded(int displayId) {
-        }
-
-        @Override public void onDisplayRemoved(int displayId) {
-        }
-
-        @Override public void onDisplayChanged(int displayId) {
-            if (displayId == Display.DEFAULT_DISPLAY) {
-                final boolean displayOn = isDisplayOn();
-                synchronized (UsageStatsService.this.mAppIdleLock) {
-                    mAppIdleHistory.updateDisplay(displayOn, SystemClock.elapsedRealtime());
-                }
-            }
-        }
-    };
-
     private final IUidObserver mUidObserver = new IUidObserver.Stub() {
         @Override
         public void onUidStateChanged(int uid, int procState, long procStateSeq) {
@@ -388,42 +232,18 @@
 
     @Override
     public void onStatsReloaded() {
-        postOneTimeCheckIdleStates();
+        mAppStandby.postOneTimeCheckIdleStates();
     }
 
     @Override
     public void onNewUpdate(int userId) {
-        initializeDefaultsForSystemApps(userId);
-    }
-
-    private void initializeDefaultsForSystemApps(int userId) {
-        Slog.d(TAG, "Initializing defaults for system apps on user " + userId);
-        final long elapsedRealtime = SystemClock.elapsedRealtime();
-        List<PackageInfo> packages = mPackageManager.getInstalledPackagesAsUser(
-                PackageManager.MATCH_DISABLED_COMPONENTS,
-                userId);
-        final int packageCount = packages.size();
-        synchronized (mAppIdleLock) {
-            for (int i = 0; i < packageCount; i++) {
-                final PackageInfo pi = packages.get(i);
-                String packageName = pi.packageName;
-                if (pi.applicationInfo != null && pi.applicationInfo.isSystemApp()) {
-                    mAppIdleHistory.reportUsage(packageName, userId, elapsedRealtime);
-                }
-            }
-        }
+        mAppStandby.initializeDefaultsForSystemApps(userId);
     }
 
     private boolean shouldObfuscateInstantAppsForCaller(int callingUid, int userId) {
         return !mPackageManagerInternal.canAccessInstantApps(callingUid, userId);
     }
 
-    void clearAppIdleForPackage(String packageName, int userId) {
-        synchronized (mAppIdleLock) {
-            mAppIdleHistory.clearUsage(packageName, userId);
-        }
-    }
-
     private void cleanUpRemovedUsersLocked() {
         final List<UserInfo> users = mUserManager.getUsers(true);
         if (users == null || users.size() == 0) {
@@ -451,195 +271,6 @@
         }
     }
 
-    void setChargingState(boolean charging) {
-        synchronized (mAppIdleLock) {
-            if (mCharging != charging) {
-                mCharging = charging;
-                postParoleStateChanged();
-            }
-        }
-    }
-
-    /** Paroled here means temporary pardon from being inactive */
-    void setAppIdleParoled(boolean paroled) {
-        synchronized (mAppIdleLock) {
-            final long now = System.currentTimeMillis();
-            if (mAppIdleTempParoled != paroled) {
-                mAppIdleTempParoled = paroled;
-                if (DEBUG) Slog.d(TAG, "Changing paroled to " + mAppIdleTempParoled);
-                if (paroled) {
-                    postParoleEndTimeout();
-                } else {
-                    mLastAppIdleParoledTime = now;
-                    postNextParoleTimeout(now);
-                }
-                postParoleStateChanged();
-            }
-        }
-    }
-
-    boolean isParoledOrCharging() {
-        synchronized (mAppIdleLock) {
-            return mAppIdleTempParoled || mCharging;
-        }
-    }
-
-    private void postNextParoleTimeout(long now) {
-        if (DEBUG) Slog.d(TAG, "Posting MSG_CHECK_PAROLE_TIMEOUT");
-        mHandler.removeMessages(MSG_CHECK_PAROLE_TIMEOUT);
-        // Compute when the next parole needs to happen. We check more frequently than necessary
-        // since the message handler delays are based on elapsedRealTime and not wallclock time.
-        // The comparison is done in wallclock time.
-        long timeLeft = (mLastAppIdleParoledTime + mAppIdleParoleIntervalMillis) - now;
-        if (timeLeft < 0) {
-            timeLeft = 0;
-        }
-        mHandler.sendEmptyMessageDelayed(MSG_CHECK_PAROLE_TIMEOUT, timeLeft);
-    }
-
-    private void postParoleEndTimeout() {
-        if (DEBUG) Slog.d(TAG, "Posting MSG_PAROLE_END_TIMEOUT");
-        mHandler.removeMessages(MSG_PAROLE_END_TIMEOUT);
-        mHandler.sendEmptyMessageDelayed(MSG_PAROLE_END_TIMEOUT, mAppIdleParoleDurationMillis);
-    }
-
-    private void postParoleStateChanged() {
-        if (DEBUG) Slog.d(TAG, "Posting MSG_PAROLE_STATE_CHANGED");
-        mHandler.removeMessages(MSG_PAROLE_STATE_CHANGED);
-        mHandler.sendEmptyMessage(MSG_PAROLE_STATE_CHANGED);
-    }
-
-    void postCheckIdleStates(int userId) {
-        mHandler.sendMessage(mHandler.obtainMessage(MSG_CHECK_IDLE_STATES, userId, 0));
-    }
-
-    /**
-     * We send a different message to check idle states once, otherwise we would end up
-     * scheduling a series of repeating checkIdleStates each time we fired off one.
-     */
-    void postOneTimeCheckIdleStates() {
-        if (mDeviceIdleController == null) {
-            // Not booted yet; wait for it!
-            mPendingOneTimeCheckIdleStates = true;
-        } else {
-            mHandler.sendEmptyMessage(MSG_ONE_TIME_CHECK_IDLE_STATES);
-            mPendingOneTimeCheckIdleStates = false;
-        }
-    }
-
-    /**
-     * Check all running users' or specified user's apps to see if they enter an idle state.
-     * @return Returns whether checking should continue periodically.
-     */
-    boolean checkIdleStates(int checkUserId) {
-        if (!mAppIdleEnabled) {
-            return false;
-        }
-
-        final int[] runningUserIds;
-        try {
-            runningUserIds = ActivityManager.getService().getRunningUserIds();
-            if (checkUserId != UserHandle.USER_ALL
-                    && !ArrayUtils.contains(runningUserIds, checkUserId)) {
-                return false;
-            }
-        } catch (RemoteException re) {
-            throw re.rethrowFromSystemServer();
-        }
-
-        final long elapsedRealtime = SystemClock.elapsedRealtime();
-        for (int i = 0; i < runningUserIds.length; i++) {
-            final int userId = runningUserIds[i];
-            if (checkUserId != UserHandle.USER_ALL && checkUserId != userId) {
-                continue;
-            }
-            if (DEBUG) {
-                Slog.d(TAG, "Checking idle state for user " + userId);
-            }
-            List<PackageInfo> packages = mPackageManager.getInstalledPackagesAsUser(
-                    PackageManager.MATCH_DISABLED_COMPONENTS,
-                    userId);
-            final int packageCount = packages.size();
-            for (int p = 0; p < packageCount; p++) {
-                final PackageInfo pi = packages.get(p);
-                final String packageName = pi.packageName;
-                final boolean isIdle = isAppIdleFiltered(packageName,
-                        UserHandle.getAppId(pi.applicationInfo.uid),
-                        userId, elapsedRealtime);
-                mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS,
-                        userId, isIdle ? 1 : 0, packageName));
-                if (isIdle) {
-                    synchronized (mAppIdleLock) {
-                        mAppIdleHistory.setIdle(packageName, userId, elapsedRealtime);
-                    }
-                }
-            }
-        }
-        if (DEBUG) {
-            Slog.d(TAG, "checkIdleStates took "
-                    + (SystemClock.elapsedRealtime() - elapsedRealtime));
-        }
-        return true;
-    }
-
-    /** Check if it's been a while since last parole and let idle apps do some work */
-    void checkParoleTimeout() {
-        boolean setParoled = false;
-        synchronized (mAppIdleLock) {
-            final long now = System.currentTimeMillis();
-            if (!mAppIdleTempParoled) {
-                final long timeSinceLastParole = now - mLastAppIdleParoledTime;
-                if (timeSinceLastParole > mAppIdleParoleIntervalMillis) {
-                    if (DEBUG) Slog.d(TAG, "Crossed default parole interval");
-                    setParoled = true;
-                } else {
-                    if (DEBUG) Slog.d(TAG, "Not long enough to go to parole");
-                    postNextParoleTimeout(now);
-                }
-            }
-        }
-        if (setParoled) {
-            setAppIdleParoled(true);
-        }
-    }
-
-    private void notifyBatteryStats(String packageName, int userId, boolean idle) {
-        try {
-            final int uid = mPackageManager.getPackageUidAsUser(packageName,
-                    PackageManager.MATCH_UNINSTALLED_PACKAGES, userId);
-            if (idle) {
-                mBatteryStats.noteEvent(BatteryStats.HistoryItem.EVENT_PACKAGE_INACTIVE,
-                        packageName, uid);
-            } else {
-                mBatteryStats.noteEvent(BatteryStats.HistoryItem.EVENT_PACKAGE_ACTIVE,
-                        packageName, uid);
-            }
-        } catch (NameNotFoundException | RemoteException e) {
-        }
-    }
-
-    void onDeviceIdleModeChanged() {
-        final boolean deviceIdle = mPowerManager.isDeviceIdleMode();
-        if (DEBUG) Slog.i(TAG, "DeviceIdleMode changed to " + deviceIdle);
-        boolean paroled = false;
-        synchronized (mAppIdleLock) {
-            final long timeSinceLastParole = System.currentTimeMillis() - mLastAppIdleParoledTime;
-            if (!deviceIdle
-                    && timeSinceLastParole >= mAppIdleParoleIntervalMillis) {
-                if (DEBUG) {
-                    Slog.i(TAG, "Bringing idle apps out of inactive state due to deviceIdleMode=false");
-                }
-                paroled = true;
-            } else if (deviceIdle) {
-                if (DEBUG) Slog.i(TAG, "Device idle, back to prison");
-                paroled = false;
-            } else {
-                return;
-            }
-        }
-        setAppIdleParoled(paroled);
-    }
-
     private static void deleteRecursively(File f) {
         File[] files = f.listFiles();
         if (files != null) {
@@ -724,76 +355,7 @@
                     getUserDataAndInitializeIfNeededLocked(userId, timeNow);
             service.reportEvent(event);
 
-            synchronized (mAppIdleLock) {
-                // TODO: Ideally this should call isAppIdleFiltered() to avoid calling back
-                // about apps that are on some kind of whitelist anyway.
-                final boolean previouslyIdle = mAppIdleHistory.isIdle(
-                        event.mPackage, userId, elapsedRealtime);
-                // Inform listeners if necessary
-                if ((event.mEventType == Event.MOVE_TO_FOREGROUND
-                        || event.mEventType == Event.MOVE_TO_BACKGROUND
-                        || event.mEventType == Event.SYSTEM_INTERACTION
-                        || event.mEventType == Event.USER_INTERACTION)) {
-                    mAppIdleHistory.reportUsage(event.mPackage, userId, elapsedRealtime);
-                    if (previouslyIdle) {
-                        mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS, userId,
-                                /* idle = */ 0, event.mPackage));
-                        notifyBatteryStats(event.mPackage, userId, false);
-                    }
-                }
-            }
-        }
-    }
-
-    void reportContentProviderUsage(String authority, String providerPkgName, int userId) {
-        // Get sync adapters for the authority
-        String[] packages = ContentResolver.getSyncAdapterPackagesForAuthorityAsUser(
-                authority, userId);
-        for (String packageName: packages) {
-            // Only force the sync adapters to active if the provider is not in the same package and
-            // the sync adapter is a system package.
-            try {
-                PackageInfo pi = mPackageManager.getPackageInfoAsUser(
-                        packageName, PackageManager.MATCH_SYSTEM_ONLY, userId);
-                if (pi == null || pi.applicationInfo == null) {
-                    continue;
-                }
-                if (!packageName.equals(providerPkgName)) {
-                    setAppIdleAsync(packageName, false, userId);
-                }
-            } catch (NameNotFoundException e) {
-                // Shouldn't happen
-            }
-        }
-    }
-
-    /**
-     * Forces the app's beginIdleTime and lastUsedTime to reflect idle or active. If idle,
-     * then it rolls back the beginIdleTime and lastUsedTime to a point in time that's behind
-     * the threshold for idle.
-     *
-     * This method is always called from the handler thread, so not much synchronization is
-     * required.
-     */
-    void forceIdleState(String packageName, int userId, boolean idle) {
-        final int appId = getAppId(packageName);
-        if (appId < 0) return;
-        final long elapsedRealtime = SystemClock.elapsedRealtime();
-
-        final boolean previouslyIdle = isAppIdleFiltered(packageName, appId,
-                userId, elapsedRealtime);
-        synchronized (mAppIdleLock) {
-            mAppIdleHistory.setIdle(packageName, userId, idle, elapsedRealtime);
-        }
-        final boolean stillIdle = isAppIdleFiltered(packageName, appId,
-                userId, elapsedRealtime);
-        // Inform listeners if necessary
-        if (previouslyIdle != stillIdle) {
-            mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS, userId,
-                    /* idle = */ stillIdle ? 1 : 0, packageName));
-            if (!stillIdle) {
-                notifyBatteryStats(packageName, userId, idle);
-            }
+            mAppStandby.reportEvent(event, elapsedRealtime, userId);
         }
     }
 
@@ -813,9 +375,7 @@
         synchronized (mLock) {
             Slog.i(TAG, "Removing user " + userId + " and all data.");
             mUserState.remove(userId);
-            synchronized (mAppIdleLock) {
-                mAppIdleHistory.onUserRemoved(userId);
-            }
+            mAppStandby.onUserRemoved(userId);
             cleanUpRemovedUsersLocked();
         }
     }
@@ -887,253 +447,6 @@
         }
     }
 
-    private boolean isAppIdleUnfiltered(String packageName, int userId, long elapsedRealtime) {
-        synchronized (mAppIdleLock) {
-            return mAppIdleHistory.isIdle(packageName, userId, elapsedRealtime);
-        }
-    }
-
-    void addListener(AppIdleStateChangeListener listener) {
-        synchronized (mAppIdleLock) {
-            if (!mPackageAccessListeners.contains(listener)) {
-                mPackageAccessListeners.add(listener);
-            }
-        }
-    }
-
-    void removeListener(AppIdleStateChangeListener listener) {
-        synchronized (mAppIdleLock) {
-            mPackageAccessListeners.remove(listener);
-        }
-    }
-
-    int getAppId(String packageName) {
-        try {
-            ApplicationInfo ai = mPackageManager.getApplicationInfo(packageName,
-                    PackageManager.MATCH_ANY_USER
-                            | PackageManager.MATCH_DISABLED_COMPONENTS);
-            return ai.uid;
-        } catch (NameNotFoundException re) {
-            return -1;
-        }
-    }
-
-    boolean isAppIdleFilteredOrParoled(String packageName, int userId, long elapsedRealtime,
-            boolean shouldObfuscateInstantApps) {
-        if (isParoledOrCharging()) {
-            return false;
-        }
-        if (shouldObfuscateInstantApps &&
-                mPackageManagerInternal.isPackageEphemeral(userId, packageName)) {
-            return false;
-        }
-        return isAppIdleFiltered(packageName, getAppId(packageName), userId, elapsedRealtime);
-    }
-
-    /**
-     * Checks if an app has been idle for a while and filters out apps that are excluded.
-     * It returns false if the current system state allows all apps to be considered active.
-     * This happens if the device is plugged in or temporarily allowed to make exceptions.
-     * Called by interface impls.
-     */
-    private boolean isAppIdleFiltered(String packageName, int appId, int userId,
-            long elapsedRealtime) {
-        if (packageName == null) return false;
-        // If not enabled at all, of course nobody is ever idle.
-        if (!mAppIdleEnabled) {
-            return false;
-        }
-        if (appId < Process.FIRST_APPLICATION_UID) {
-            // System uids never go idle.
-            return false;
-        }
-        if (packageName.equals("android")) {
-            // Nor does the framework (which should be redundant with the above, but for MR1 we will
-            // retain this for safety).
-            return false;
-        }
-        if (mSystemServicesReady) {
-            try {
-                // We allow all whitelisted apps, including those that don't want to be whitelisted
-                // for idle mode, because app idle (aka app standby) is really not as big an issue
-                // for controlling who participates vs. doze mode.
-                if (mDeviceIdleController.isPowerSaveWhitelistExceptIdleApp(packageName)) {
-                    return false;
-                }
-            } catch (RemoteException re) {
-                throw re.rethrowFromSystemServer();
-            }
-
-            if (isActiveDeviceAdmin(packageName, userId)) {
-                return false;
-            }
-
-            if (isActiveNetworkScorer(packageName)) {
-                return false;
-            }
-
-            if (mAppWidgetManager != null
-                    && mAppWidgetManager.isBoundWidgetPackage(packageName, userId)) {
-                return false;
-            }
-
-            if (isDeviceProvisioningPackage(packageName)) {
-                return false;
-            }
-        }
-
-        if (!isAppIdleUnfiltered(packageName, userId, elapsedRealtime)) {
-            return false;
-        }
-
-        // Check this last, as it is the most expensive check
-        // TODO: Optimize this by fetching the carrier privileged apps ahead of time
-        if (isCarrierApp(packageName)) {
-            return false;
-        }
-
-        return true;
-    }
-
-    int[] getIdleUidsForUser(int userId) {
-        if (!mAppIdleEnabled) {
-            return new int[0];
-        }
-
-        final long elapsedRealtime = SystemClock.elapsedRealtime();
-
-        List<ApplicationInfo> apps;
-        try {
-            ParceledListSlice<ApplicationInfo> slice = AppGlobals.getPackageManager()
-                    .getInstalledApplications(/* flags= */ 0, userId);
-            if (slice == null) {
-                return new int[0];
-            }
-            apps = slice.getList();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-
-        // State of each uid.  Key is the uid.  Value lower 16 bits is the number of apps
-        // associated with that uid, upper 16 bits is the number of those apps that is idle.
-        SparseIntArray uidStates = new SparseIntArray();
-
-        // Now resolve all app state.  Iterating over all apps, keeping track of how many
-        // we find for each uid and how many of those are idle.
-        for (int i = apps.size() - 1; i >= 0; i--) {
-            ApplicationInfo ai = apps.get(i);
-
-            // Check whether this app is idle.
-            boolean idle = isAppIdleFiltered(ai.packageName, UserHandle.getAppId(ai.uid),
-                    userId, elapsedRealtime);
-
-            int index = uidStates.indexOfKey(ai.uid);
-            if (index < 0) {
-                uidStates.put(ai.uid, 1 + (idle ? 1<<16 : 0));
-            } else {
-                int value = uidStates.valueAt(index);
-                uidStates.setValueAt(index, value + 1 + (idle ? 1<<16 : 0));
-            }
-        }
-        if (DEBUG) {
-            Slog.d(TAG, "getIdleUids took " + (SystemClock.elapsedRealtime() - elapsedRealtime));
-        }
-        int numIdle = 0;
-        for (int i = uidStates.size() - 1; i >= 0; i--) {
-            int value = uidStates.valueAt(i);
-            if ((value&0x7fff) == (value>>16)) {
-                numIdle++;
-            }
-        }
-
-        int[] res = new int[numIdle];
-        numIdle = 0;
-        for (int i = uidStates.size() - 1; i >= 0; i--) {
-            int value = uidStates.valueAt(i);
-            if ((value&0x7fff) == (value>>16)) {
-                res[numIdle] = uidStates.keyAt(i);
-                numIdle++;
-            }
-        }
-
-        return res;
-    }
-
-    void setAppIdleAsync(String packageName, boolean idle, int userId) {
-        if (packageName == null) return;
-
-        mHandler.obtainMessage(MSG_FORCE_IDLE_STATE, userId, idle ? 1 : 0, packageName)
-                .sendToTarget();
-    }
-
-    private boolean isActiveDeviceAdmin(String packageName, int userId) {
-        DevicePolicyManager dpm = getContext().getSystemService(DevicePolicyManager.class);
-        if (dpm == null) return false;
-        return dpm.packageHasActiveAdmins(packageName, userId);
-    }
-
-    /**
-     * Returns {@code true} if the supplied package is the device provisioning app. Otherwise,
-     * returns {@code false}.
-     */
-    private boolean isDeviceProvisioningPackage(String packageName) {
-        String deviceProvisioningPackage = getContext().getResources().getString(
-                com.android.internal.R.string.config_deviceProvisioningPackage);
-        return deviceProvisioningPackage != null && deviceProvisioningPackage.equals(packageName);
-    }
-
-    private boolean isCarrierApp(String packageName) {
-        synchronized (mAppIdleLock) {
-            if (!mHaveCarrierPrivilegedApps) {
-                fetchCarrierPrivilegedAppsLA();
-            }
-            if (mCarrierPrivilegedApps != null) {
-                return mCarrierPrivilegedApps.contains(packageName);
-            }
-            return false;
-        }
-    }
-
-    void clearCarrierPrivilegedApps() {
-        if (DEBUG) {
-            Slog.i(TAG, "Clearing carrier privileged apps list");
-        }
-        synchronized (mAppIdleLock) {
-            mHaveCarrierPrivilegedApps = false;
-            mCarrierPrivilegedApps = null; // Need to be refetched.
-        }
-    }
-
-    @GuardedBy("mAppIdleLock")
-    private void fetchCarrierPrivilegedAppsLA() {
-        TelephonyManager telephonyManager =
-                getContext().getSystemService(TelephonyManager.class);
-        mCarrierPrivilegedApps = telephonyManager.getPackagesWithCarrierPrivileges();
-        mHaveCarrierPrivilegedApps = true;
-        if (DEBUG) {
-            Slog.d(TAG, "apps with carrier privilege " + mCarrierPrivilegedApps);
-        }
-    }
-
-    private boolean isActiveNetworkScorer(String packageName) {
-        NetworkScoreManager nsm = (NetworkScoreManager) getContext().getSystemService(
-                Context.NETWORK_SCORE_SERVICE);
-        return packageName != null && packageName.equals(nsm.getActiveScorerPackage());
-    }
-
-    void informListeners(String packageName, int userId, boolean isIdle) {
-        for (AppIdleStateChangeListener listener : mPackageAccessListeners) {
-            listener.onAppIdleStateChanged(packageName, userId, isIdle);
-        }
-    }
-
-    void informParoleStateChanged() {
-        final boolean paroled = isParoledOrCharging();
-        for (AppIdleStateChangeListener listener : mPackageAccessListeners) {
-            listener.onParoleStateChanged(paroled);
-        }
-    }
-
     private static boolean validRange(long currentTime, long beginTime, long endTime) {
         return beginTime <= currentTime && beginTime < endTime;
     }
@@ -1143,15 +456,10 @@
         for (int i = 0; i < userCount; i++) {
             UserUsageStatsService service = mUserState.valueAt(i);
             service.persistActiveStats();
-            synchronized (mAppIdleLock) {
-                mAppIdleHistory.writeAppIdleTimes(mUserState.keyAt(i));
-            }
+            mAppStandby.flushToDisk(mUserState.keyAt(i));
         }
-        // Persist elapsed and screen on time. If this fails for whatever reason, the apps will be
-        // considered not-idle, which is the safest outcome in such an event.
-        synchronized (mAppIdleLock) {
-            mAppIdleHistory.writeAppIdleDurations();
-        }
+        mAppStandby.flushDurationsToDisk();
+
         mHandler.removeMessages(MSG_FLUSH_TO_DISK);
     }
 
@@ -1166,7 +474,8 @@
 
             final int userCount = mUserState.size();
             for (int i = 0; i < userCount; i++) {
-                idpw.printPair("user", mUserState.keyAt(i));
+                int userId = mUserState.keyAt(i);
+                idpw.printPair("user", userId);
                 idpw.println();
                 idpw.increaseIndent();
                 if (argSet.contains("--checkin")) {
@@ -1176,57 +485,19 @@
                     idpw.println();
                     if (args.length > 0) {
                         if ("history".equals(args[0])) {
-                            synchronized (mAppIdleLock) {
-                                mAppIdleHistory.dumpHistory(idpw, mUserState.keyAt(i));
-                            }
+                            mAppStandby.dumpHistory(idpw, userId);
                         } else if ("flush".equals(args[0])) {
-                            UsageStatsService.this.flushToDiskLocked();
+                            flushToDiskLocked();
                             pw.println("Flushed stats to disk");
                         }
                     }
                 }
-                synchronized (mAppIdleLock) {
-                    mAppIdleHistory.dump(idpw, mUserState.keyAt(i));
-                }
+                mAppStandby.dumpUser(idpw, userId);
                 idpw.decreaseIndent();
             }
 
             pw.println();
-            synchronized (mAppIdleLock) {
-                pw.println("Carrier privileged apps (have=" + mHaveCarrierPrivilegedApps
-                        + "): " + mCarrierPrivilegedApps);
-            }
-
-            pw.println();
-            pw.println("Settings:");
-
-            pw.print("  mAppIdleDurationMillis=");
-            TimeUtils.formatDuration(mAppIdleScreenThresholdMillis, pw);
-            pw.println();
-
-            pw.print("  mAppIdleWallclockThresholdMillis=");
-            TimeUtils.formatDuration(mAppIdleWallclockThresholdMillis, pw);
-            pw.println();
-
-            pw.print("  mCheckIdleIntervalMillis=");
-            TimeUtils.formatDuration(mCheckIdleIntervalMillis, pw);
-            pw.println();
-
-            pw.print("  mAppIdleParoleIntervalMillis=");
-            TimeUtils.formatDuration(mAppIdleParoleIntervalMillis, pw);
-            pw.println();
-
-            pw.print("  mAppIdleParoleDurationMillis=");
-            TimeUtils.formatDuration(mAppIdleParoleDurationMillis, pw);
-            pw.println();
-
-            pw.println();
-            pw.print("mAppIdleEnabled="); pw.print(mAppIdleEnabled);
-            pw.print(" mAppIdleTempParoled="); pw.print(mAppIdleTempParoled);
-            pw.print(" mCharging="); pw.print(mCharging);
-            pw.print(" mLastAppIdleParoledTime=");
-            TimeUtils.formatDuration(mLastAppIdleParoledTime, pw);
-            pw.println();
+            mAppStandby.dumpState(args, pw);
         }
     }
 
@@ -1250,50 +521,6 @@
                     onUserRemoved(msg.arg1);
                     break;
 
-                case MSG_INFORM_LISTENERS:
-                    informListeners((String) msg.obj, msg.arg1, msg.arg2 == 1);
-                    break;
-
-                case MSG_FORCE_IDLE_STATE:
-                    forceIdleState((String) msg.obj, msg.arg1, msg.arg2 == 1);
-                    break;
-
-                case MSG_CHECK_IDLE_STATES:
-                    if (checkIdleStates(msg.arg1)) {
-                        mHandler.sendMessageDelayed(mHandler.obtainMessage(
-                                MSG_CHECK_IDLE_STATES, msg.arg1, 0),
-                                mCheckIdleIntervalMillis);
-                    }
-                    break;
-
-                case MSG_ONE_TIME_CHECK_IDLE_STATES:
-                    mHandler.removeMessages(MSG_ONE_TIME_CHECK_IDLE_STATES);
-                    checkIdleStates(UserHandle.USER_ALL);
-                    break;
-
-                case MSG_CHECK_PAROLE_TIMEOUT:
-                    checkParoleTimeout();
-                    break;
-
-                case MSG_PAROLE_END_TIMEOUT:
-                    if (DEBUG) Slog.d(TAG, "Ending parole");
-                    setAppIdleParoled(false);
-                    break;
-
-                case MSG_REPORT_CONTENT_PROVIDER_USAGE:
-                    SomeArgs args = (SomeArgs) msg.obj;
-                    reportContentProviderUsage((String) args.arg1, // authority name
-                            (String) args.arg2, // package name
-                            (int) args.arg3); // userId
-                    args.recycle();
-                    break;
-
-                case MSG_PAROLE_STATE_CHANGED:
-                    if (DEBUG) Slog.d(TAG, "Parole state: " + mAppIdleTempParoled
-                            + ", Charging state:" + mCharging);
-                    informParoleStateChanged();
-                    break;
-
                 default:
                     super.handleMessage(msg);
                     break;
@@ -1301,72 +528,6 @@
         }
     }
 
-    /**
-     * Observe settings changes for {@link Settings.Global#APP_IDLE_CONSTANTS}.
-     */
-    private class SettingsObserver extends ContentObserver {
-        /**
-         * This flag has been used to disable app idle on older builds with bug b/26355386.
-         */
-        @Deprecated
-        private static final String KEY_IDLE_DURATION_OLD = "idle_duration";
-
-        private static final String KEY_IDLE_DURATION = "idle_duration2";
-        private static final String KEY_WALLCLOCK_THRESHOLD = "wallclock_threshold";
-        private static final String KEY_PAROLE_INTERVAL = "parole_interval";
-        private static final String KEY_PAROLE_DURATION = "parole_duration";
-
-        private final KeyValueListParser mParser = new KeyValueListParser(',');
-
-        SettingsObserver(Handler handler) {
-            super(handler);
-        }
-
-        void registerObserver() {
-            getContext().getContentResolver().registerContentObserver(Settings.Global.getUriFor(
-                    Settings.Global.APP_IDLE_CONSTANTS), false, this);
-        }
-
-        @Override
-        public void onChange(boolean selfChange) {
-            updateSettings();
-            postOneTimeCheckIdleStates();
-        }
-
-        void updateSettings() {
-            synchronized (mAppIdleLock) {
-                // Look at global settings for this.
-                // TODO: Maybe apply different thresholds for different users.
-                try {
-                    mParser.setString(Settings.Global.getString(getContext().getContentResolver(),
-                            Settings.Global.APP_IDLE_CONSTANTS));
-                } catch (IllegalArgumentException e) {
-                    Slog.e(TAG, "Bad value for app idle settings: " + e.getMessage());
-                    // fallthrough, mParser is empty and all defaults will be returned.
-                }
-
-                // Default: 12 hours of screen-on time sans dream-time
-                mAppIdleScreenThresholdMillis = mParser.getLong(KEY_IDLE_DURATION,
-                       COMPRESS_TIME ? ONE_MINUTE * 4 : 12 * 60 * ONE_MINUTE);
-
-                mAppIdleWallclockThresholdMillis = mParser.getLong(KEY_WALLCLOCK_THRESHOLD,
-                        COMPRESS_TIME ? ONE_MINUTE * 8 : 2L * 24 * 60 * ONE_MINUTE); // 2 days
-
-                mCheckIdleIntervalMillis = Math.min(mAppIdleScreenThresholdMillis / 4,
-                        COMPRESS_TIME ? ONE_MINUTE : 8 * 60 * ONE_MINUTE); // 8 hours
-
-                // Default: 24 hours between paroles
-                mAppIdleParoleIntervalMillis = mParser.getLong(KEY_PAROLE_INTERVAL,
-                        COMPRESS_TIME ? ONE_MINUTE * 10 : 24 * 60 * ONE_MINUTE);
-
-                mAppIdleParoleDurationMillis = mParser.getLong(KEY_PAROLE_DURATION,
-                        COMPRESS_TIME ? ONE_MINUTE : 10 * ONE_MINUTE); // 10 minutes
-                mAppIdleHistory.setThresholds(mAppIdleWallclockThresholdMillis,
-                        mAppIdleScreenThresholdMillis);
-            }
-        }
-    }
-
     private final class BinderService extends IUsageStatsManager.Stub {
 
         private boolean hasPermission(String callingPackage) {
@@ -1462,7 +623,8 @@
                     Binder.getCallingUid(), userId);
             final long token = Binder.clearCallingIdentity();
             try {
-                return UsageStatsService.this.isAppIdleFilteredOrParoled(packageName, userId,
+                return mAppStandby.isAppIdleFilteredOrParoled(
+                        packageName, userId,
                         SystemClock.elapsedRealtime(), obfuscateInstantApps);
             } finally {
                 Binder.restoreCallingIdentity(token);
@@ -1483,9 +645,9 @@
                     "No permission to change app idle state");
             final long token = Binder.clearCallingIdentity();
             try {
-                final int appId = getAppId(packageName);
+                final int appId = mAppStandby.getAppId(packageName);
                 if (appId < 0) return;
-                UsageStatsService.this.setAppIdleAsync(packageName, idle, userId);
+                mAppStandby.setAppIdleAsync(packageName, idle, userId);
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
@@ -1509,7 +671,7 @@
             getContext().enforceCallingOrSelfPermission(
                     android.Manifest.permission.BIND_CARRIER_SERVICES,
                     "onCarrierPrivilegedAppsChanged can only be called by privileged apps.");
-            UsageStatsService.this.clearCarrierPrivilegedApps();
+            mAppStandby.clearCarrierPrivilegedApps();
         }
 
         @Override
@@ -1624,28 +786,23 @@
 
         @Override
         public void reportContentProviderUsage(String name, String packageName, int userId) {
-            SomeArgs args = SomeArgs.obtain();
-            args.arg1 = name;
-            args.arg2 = packageName;
-            args.arg3 = userId;
-            mHandler.obtainMessage(MSG_REPORT_CONTENT_PROVIDER_USAGE, args)
-                    .sendToTarget();
+            mAppStandby.postReportContentProviderUsage(name, packageName, userId);
         }
 
         @Override
         public boolean isAppIdle(String packageName, int uidForAppId, int userId) {
-            return UsageStatsService.this.isAppIdleFiltered(packageName, uidForAppId, userId,
-                    SystemClock.elapsedRealtime());
+            return mAppStandby.isAppIdleFiltered(packageName, uidForAppId,
+                    userId, SystemClock.elapsedRealtime());
         }
 
         @Override
         public int[] getIdleUidsForUser(int userId) {
-            return UsageStatsService.this.getIdleUidsForUser(userId);
+            return mAppStandby.getIdleUidsForUser(userId);
         }
 
         @Override
         public boolean isAppIdleParoleOn() {
-            return isParoledOrCharging();
+            return mAppStandby.isParoledOrCharging();
         }
 
         @Override
@@ -1658,20 +815,20 @@
 
         @Override
         public void addAppIdleStateChangeListener(AppIdleStateChangeListener listener) {
-            UsageStatsService.this.addListener(listener);
+            mAppStandby.addListener(listener);
             listener.onParoleStateChanged(isAppIdleParoleOn());
         }
 
         @Override
         public void removeAppIdleStateChangeListener(
                 AppIdleStateChangeListener listener) {
-            UsageStatsService.this.removeListener(listener);
+            mAppStandby.removeListener(listener);
         }
 
         @Override
         public byte[] getBackupPayload(int user, String key) {
             // Check to ensure that only user 0's data is b/r for now
-            synchronized (UsageStatsService.this.mLock) {
+            synchronized (mLock) {
                 if (user == UserHandle.USER_SYSTEM) {
                     final UserUsageStatsService userStats =
                             getUserDataAndInitializeIfNeededLocked(user, checkAndGetTimeLocked());
@@ -1684,7 +841,7 @@
 
         @Override
         public void applyRestoredPayload(int user, String key, byte[] payload) {
-            synchronized (UsageStatsService.this.mLock) {
+            synchronized (mLock) {
                 if (user == UserHandle.USER_SYSTEM) {
                     final UserUsageStatsService userStats =
                             getUserDataAndInitializeIfNeededLocked(user, checkAndGetTimeLocked());
diff --git a/com/android/server/usb/UsbAlsaManager.java b/com/android/server/usb/UsbAlsaManager.java
index 68c1d5f..acc27be 100644
--- a/com/android/server/usb/UsbAlsaManager.java
+++ b/com/android/server/usb/UsbAlsaManager.java
@@ -314,7 +314,11 @@
             return null;
         }
 
-        mDevicesParser.scan();
+        if (!mDevicesParser.scan()) {
+            Slog.e(TAG, "Error parsing ALSA devices file.");
+            return null;
+        }
+
         int device = mDevicesParser.getDefaultDeviceNum(card);
 
         boolean hasPlayback = mDevicesParser.hasPlaybackDevices(card);
diff --git a/com/android/server/utils/ManagedApplicationService.java b/com/android/server/utils/ManagedApplicationService.java
index 0f251fd..c555388 100644
--- a/com/android/server/utils/ManagedApplicationService.java
+++ b/com/android/server/utils/ManagedApplicationService.java
@@ -16,19 +16,24 @@
 package com.android.server.utils;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.PendingIntent;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.ServiceConnection;
+import android.os.Handler;
 import android.os.IBinder;
 import android.os.IBinder.DeathRecipient;
 import android.os.IInterface;
 import android.os.RemoteException;
+import android.os.SystemClock;
 import android.os.UserHandle;
 import android.util.Slog;
 
+import java.text.SimpleDateFormat;
 import java.util.Objects;
+import java.util.Date;
 
 /**
  * Manages the lifecycle of an application-provided service bound from system server.
@@ -38,39 +43,126 @@
 public class ManagedApplicationService {
     private final String TAG = getClass().getSimpleName();
 
+    /**
+     * Attempt to reconnect service forever if an onBindingDied or onServiceDisconnected event
+     * is received.
+     */
+    public static final int RETRY_FOREVER = 1;
+
+    /**
+     * Never attempt to reconnect the service - a single onBindingDied or onServiceDisconnected
+     * event will cause this to fully unbind the service and never attempt to reconnect.
+     */
+    public static final int RETRY_NEVER = 2;
+
+    /**
+     * Attempt to reconnect the service until the maximum number of retries is reached, then stop.
+     *
+     * The first retry will occur MIN_RETRY_DURATION_MS after the disconnection, and each
+     * subsequent retry will occur after 2x the duration used for the previous retry up to the
+     * MAX_RETRY_DURATION_MS duration.
+     *
+     * In this case, retries mean a full unbindService/bindService pair to handle cases when the
+     * usual service re-connection logic in ActiveServices has very high backoff times or when the
+     * serviceconnection has fully died due to a package update or similar.
+     */
+    public static final int RETRY_BEST_EFFORT = 3;
+
+    // Maximum number of retries before giving up (for RETRY_BEST_EFFORT).
+    private static final int MAX_RETRY_COUNT = 4;
+    // Max time between retry attempts.
+    private static final long MAX_RETRY_DURATION_MS = 16000;
+    // Min time between retry attempts.
+    private static final long MIN_RETRY_DURATION_MS = 2000;
+    // Time since the last retry attempt after which to clear the retry attempt counter.
+    private static final long RETRY_RESET_TIME_MS = MAX_RETRY_DURATION_MS * 4;
+
     private final Context mContext;
     private final int mUserId;
     private final ComponentName mComponent;
     private final int mClientLabel;
     private final String mSettingsAction;
     private final BinderChecker mChecker;
-
-    private final DeathRecipient mDeathRecipient = new DeathRecipient() {
-        @Override
-        public void binderDied() {
-            synchronized (mLock) {
-                mBoundInterface = null;
-            }
-        }
-    };
+    private final boolean mIsImportant;
+    private final int mRetryType;
+    private final Handler mHandler;
+    private final Runnable mRetryRunnable = this::doRetry;
+    private final EventCallback mEventCb;
 
     private final Object mLock = new Object();
 
     // State protected by mLock
-    private ServiceConnection mPendingConnection;
     private ServiceConnection mConnection;
     private IInterface mBoundInterface;
     private PendingEvent mPendingEvent;
+    private int mRetryCount;
+    private long mLastRetryTimeMs;
+    private long mNextRetryDurationMs = MIN_RETRY_DURATION_MS;
+    private boolean mRetrying;
+
+    public static interface LogFormattable {
+       String toLogString(SimpleDateFormat dateFormat);
+    }
+
+    /**
+     * Lifecycle event of this managed service.
+     */
+    public static class LogEvent implements LogFormattable {
+        public static final int EVENT_CONNECTED = 1;
+        public static final int EVENT_DISCONNECTED = 2;
+        public static final int EVENT_BINDING_DIED = 3;
+        public static final int EVENT_STOPPED_PERMANENTLY = 4;
+
+        // Time of the events in "current time ms" timebase.
+        public final long timestamp;
+        // Name of the component for this system service.
+        public final ComponentName component;
+        // ID of the event that occurred.
+        public final int event;
+
+        public LogEvent(long timestamp, ComponentName component, int event) {
+            this.timestamp = timestamp;
+            this.component = component;
+            this.event = event;
+        }
+
+        @Override
+        public String toLogString(SimpleDateFormat dateFormat) {
+            return dateFormat.format(new Date(timestamp)) + "   " + eventToString(event)
+                    + " Managed Service: "
+                    + ((component == null) ? "None" : component.flattenToString());
+        }
+
+        public static String eventToString(int event) {
+            switch (event) {
+                case EVENT_CONNECTED:
+                    return "Connected";
+                case EVENT_DISCONNECTED:
+                    return "Disconnected";
+                case EVENT_BINDING_DIED:
+                    return "Binding Died For";
+                case EVENT_STOPPED_PERMANENTLY:
+                    return "Permanently Stopped";
+                default:
+                    return "Unknown Event Occurred";
+            }
+        }
+    }
 
     private ManagedApplicationService(final Context context, final ComponentName component,
             final int userId, int clientLabel, String settingsAction,
-            BinderChecker binderChecker) {
+            BinderChecker binderChecker, boolean isImportant, int retryType, Handler handler,
+            EventCallback eventCallback) {
         mContext = context;
         mComponent = component;
         mUserId = userId;
         mClientLabel = clientLabel;
         mSettingsAction = settingsAction;
         mChecker = binderChecker;
+        mIsImportant = isImportant;
+        mRetryType = retryType;
+        mHandler = handler;
+        mEventCb = eventCallback;
     }
 
     /**
@@ -85,7 +177,17 @@
      * Implement to call IInterface methods after service is connected.
      */
     public interface PendingEvent {
-         void runEvent(IInterface service) throws RemoteException;
+        void runEvent(IInterface service) throws RemoteException;
+    }
+
+    /**
+     * Implement to be notified about any problems with remote service.
+     */
+    public interface EventCallback {
+        /**
+         * Called when an sevice lifecycle event occurs.
+         */
+        void onServiceEvent(LogEvent event);
     }
 
     /**
@@ -95,19 +197,28 @@
      * @param component the {@link ComponentName} of the application service to bind.
      * @param userId the user ID of user to bind the application service as.
      * @param clientLabel the resource ID of a label displayed to the user indicating the
-     *      binding service.
+     *      binding service, or 0 if none is desired.
      * @param settingsAction an action that can be used to open the Settings UI to enable/disable
-     *      binding to these services.
-     * @param binderChecker an interface used to validate the returned binder object.
+     *      binding to these services, or null if none is desired.
+     * @param binderChecker an interface used to validate the returned binder object, or null if
+     *      this interface is unchecked.
+     * @param isImportant bind the user service with BIND_IMPORTANT.
+     * @param retryType reconnect behavior to have when bound service is disconnected.
+     * @param handler the Handler to use for retries and delivering EventCallbacks.
+     * @param eventCallback a callback used to deliver disconnection events, or null if you
+     *      don't care.
      * @return a ManagedApplicationService instance.
      */
     public static ManagedApplicationService build(@NonNull final Context context,
-        @NonNull final ComponentName component, final int userId, @NonNull int clientLabel,
-        @NonNull String settingsAction, @NonNull BinderChecker binderChecker) {
+            @NonNull final ComponentName component, final int userId, int clientLabel,
+            @Nullable String settingsAction, @Nullable BinderChecker binderChecker,
+            boolean isImportant, int retryType, @NonNull Handler handler,
+            @Nullable EventCallback eventCallback) {
         return new ManagedApplicationService(context, component, userId, clientLabel,
-            settingsAction, binderChecker);
+            settingsAction, binderChecker, isImportant, retryType, handler, eventCallback);
     }
 
+
     /**
      * @return the user ID of the user that owns the bound service.
      */
@@ -138,13 +249,12 @@
         return true;
     }
 
-
-  /**
-   * Send an event to run as soon as the binder interface is available.
-   *
-   * @param event a {@link PendingEvent} to send.
-   */
-  public void sendEvent(@NonNull PendingEvent event) {
+    /**
+     * Send an event to run as soon as the binder interface is available.
+     *
+     * @param event a {@link PendingEvent} to send.
+     */
+    public void sendEvent(@NonNull PendingEvent event) {
         IInterface iface;
         synchronized (mLock) {
             iface = mBoundInterface;
@@ -167,15 +277,13 @@
      */
     public void disconnect() {
         synchronized (mLock) {
-            // Wipe out pending connections
-            mPendingConnection = null;
-
             // Unbind existing connection, if it exists
-            if (mConnection != null) {
-                mContext.unbindService(mConnection);
-                mConnection = null;
+            if (mConnection == null) {
+                return;
             }
 
+            mContext.unbindService(mConnection);
+            mConnection = null;
             mBoundInterface = null;
         }
     }
@@ -185,48 +293,70 @@
      */
     public void connect() {
         synchronized (mLock) {
-            if (mConnection != null || mPendingConnection != null) {
+            if (mConnection != null) {
                 // We're already connected or are trying to connect
                 return;
             }
 
-            final PendingIntent pendingIntent = PendingIntent.getActivity(
-                    mContext, 0, new Intent(mSettingsAction), 0);
-            final Intent intent = new Intent().setComponent(mComponent).
-                    putExtra(Intent.EXTRA_CLIENT_LABEL, mClientLabel).
-                    putExtra(Intent.EXTRA_CLIENT_INTENT, pendingIntent);
+            Intent intent  = new Intent().setComponent(mComponent);
+            if (mClientLabel != 0) {
+                intent.putExtra(Intent.EXTRA_CLIENT_LABEL, mClientLabel);
+            }
+            if (mSettingsAction != null) {
+                intent.putExtra(Intent.EXTRA_CLIENT_INTENT,
+                        PendingIntent.getActivity(mContext, 0, new Intent(mSettingsAction), 0));
+            }
 
-            final ServiceConnection serviceConnection = new ServiceConnection() {
+            mConnection = new ServiceConnection() {
+                @Override
+                public void onBindingDied(ComponentName componentName) {
+                    final long timestamp = System.currentTimeMillis();
+                    Slog.w(TAG, "Service binding died: " + componentName);
+                    synchronized (mLock) {
+                        if (mConnection != this) {
+                            return;
+                        }
+                        mHandler.post(() -> {
+                            mEventCb.onServiceEvent(new LogEvent(timestamp, mComponent,
+                                  LogEvent.EVENT_BINDING_DIED));
+                        });
+
+                        mBoundInterface = null;
+                        startRetriesLocked();
+                    }
+                }
+
                 @Override
                 public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
+                    final long timestamp = System.currentTimeMillis();
+                    Slog.i(TAG, "Service connected: " + componentName);
                     IInterface iface = null;
                     PendingEvent pendingEvent = null;
                     synchronized (mLock) {
-                        if (mPendingConnection == this) {
-                            // No longer pending, remove from pending connection
-                            mPendingConnection = null;
-                            mConnection = this;
-                        } else {
-                            // Service connection wasn't pending, must have been disconnected
-                            mContext.unbindService(this);
+                        if (mConnection != this) {
+                            // Must've been unbound.
                             return;
                         }
+                        mHandler.post(() -> {
+                            mEventCb.onServiceEvent(new LogEvent(timestamp, mComponent,
+                                  LogEvent.EVENT_CONNECTED));
+                        });
 
-                        try {
-                            iBinder.linkToDeath(mDeathRecipient, 0);
+                        stopRetriesLocked();
+
+                        mBoundInterface = null;
+                        if (mChecker != null) {
                             mBoundInterface = mChecker.asInterface(iBinder);
                             if (!mChecker.checkType(mBoundInterface)) {
-                                // Received an invalid binder, disconnect
-                                mContext.unbindService(this);
+                                // Received an invalid binder, disconnect.
                                 mBoundInterface = null;
+                                Slog.w(TAG, "Invalid binder from " + componentName);
+                                startRetriesLocked();
+                                return;
                             }
                             iface = mBoundInterface;
                             pendingEvent = mPendingEvent;
                             mPendingEvent = null;
-                        } catch (RemoteException e) {
-                            // DOA
-                            Slog.w(TAG, "Unable to bind service: " + intent, e);
-                            mBoundInterface = null;
                         }
                     }
                     if (iface != null && pendingEvent != null) {
@@ -234,28 +364,44 @@
                             pendingEvent.runEvent(iface);
                         } catch (RuntimeException | RemoteException ex) {
                             Slog.e(TAG, "Received exception from user service: ", ex);
+                            startRetriesLocked();
                         }
                     }
                 }
 
                 @Override
                 public void onServiceDisconnected(ComponentName componentName) {
-                    Slog.w(TAG, "Service disconnected: " + intent);
-                    mConnection = null;
-                    mBoundInterface = null;
+                    final long timestamp = System.currentTimeMillis();
+                    Slog.w(TAG, "Service disconnected: " + componentName);
+                    synchronized (mLock) {
+                        if (mConnection != this) {
+                            return;
+                        }
+
+                        mHandler.post(() -> {
+                            mEventCb.onServiceEvent(new LogEvent(timestamp, mComponent,
+                                  LogEvent.EVENT_DISCONNECTED));
+                        });
+
+                        mBoundInterface = null;
+                        startRetriesLocked();
+                    }
                 }
             };
 
-            mPendingConnection = serviceConnection;
-
+            int flags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE;
+            if (mIsImportant) {
+                flags |= Context.BIND_IMPORTANT;
+            }
             try {
-                if (!mContext.bindServiceAsUser(intent, serviceConnection,
-                        Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
+                if (!mContext.bindServiceAsUser(intent, mConnection, flags,
                         new UserHandle(mUserId))) {
                     Slog.w(TAG, "Unable to bind service: " + intent);
+                    startRetriesLocked();
                 }
             } catch (SecurityException e) {
                 Slog.w(TAG, "Unable to bind service: " + intent, e);
+                startRetriesLocked();
             }
         }
     }
@@ -263,4 +409,81 @@
     private boolean matches(final ComponentName component, final int userId) {
         return Objects.equals(mComponent, component) && mUserId == userId;
     }
+
+    private void startRetriesLocked() {
+        if (checkAndDeliverServiceDiedCbLocked()) {
+            // If we delivered the service callback, disconnect and stop retrying.
+            disconnect();
+            return;
+        }
+
+        if (mRetrying) {
+            // Retry already queued, don't queue a new one.
+            return;
+        }
+        mRetrying = true;
+        queueRetryLocked();
+    }
+
+    private void stopRetriesLocked() {
+        mRetrying = false;
+        mHandler.removeCallbacks(mRetryRunnable);
+    }
+
+    private void queueRetryLocked() {
+        long now = SystemClock.uptimeMillis();
+        if ((now - mLastRetryTimeMs) > RETRY_RESET_TIME_MS) {
+            // It's been longer than the reset time since we last had to retry.  Re-initialize.
+            mNextRetryDurationMs = MIN_RETRY_DURATION_MS;
+            mRetryCount = 0;
+        }
+        mLastRetryTimeMs = now;
+        mHandler.postDelayed(mRetryRunnable, mNextRetryDurationMs);
+        mNextRetryDurationMs = Math.min(2 * mNextRetryDurationMs, MAX_RETRY_DURATION_MS);
+        mRetryCount++;
+    }
+
+    private boolean checkAndDeliverServiceDiedCbLocked() {
+
+       if (mRetryType == RETRY_NEVER || (mRetryType == RETRY_BEST_EFFORT
+                && mRetryCount >= MAX_RETRY_COUNT)) {
+            // If we never retry, or we've exhausted our retries, post the onServiceDied callback.
+            Slog.e(TAG, "Service " + mComponent + " has died too much, not retrying.");
+            if (mEventCb != null) {
+                final long timestamp = System.currentTimeMillis();
+                mHandler.post(() -> {
+                  mEventCb.onServiceEvent(new LogEvent(timestamp, mComponent,
+                        LogEvent.EVENT_STOPPED_PERMANENTLY));
+                });
+            }
+            return true;
+        }
+        return false;
+    }
+
+    private void doRetry() {
+        synchronized (mLock) {
+            if (mConnection == null) {
+                // We disconnected for good.  Don't attempt to retry.
+                return;
+            }
+            if (!mRetrying) {
+                // We successfully connected.  Don't attempt to retry.
+                return;
+            }
+            Slog.i(TAG, "Attempting to reconnect " + mComponent + "...");
+            // While frameworks may restart the remote Service if we stay bound, we have little
+            // control of the backoff timing for reconnecting the service.  In the event of a
+            // process crash, the backoff time can be very large (1-30 min), which is not
+            // acceptable for the types of services this is used for.  Instead force an unbind/bind
+            // sequence to cause a more immediate retry.
+            disconnect();
+            if (checkAndDeliverServiceDiedCbLocked()) {
+                // No more retries.
+                return;
+            }
+            queueRetryLocked();
+            connect();
+        }
+    }
 }
diff --git a/com/android/server/utils/PriorityDump.java b/com/android/server/utils/PriorityDump.java
index c05cc3f..054f156 100644
--- a/com/android/server/utils/PriorityDump.java
+++ b/com/android/server/utils/PriorityDump.java
@@ -59,10 +59,10 @@
     Donuts in the box: 1
     Nuclear reactor status: DANGER - MELTDOWN IMMINENT
 
-    $ adb shell dumpsys snpp --dump_priority CRITICAL
+    $ adb shell dumpsys snpp --dump-priority CRITICAL
     Donuts in the box: 1
 
-    $ adb shell dumpsys snpp --dump_priority NORMAL
+    $ adb shell dumpsys snpp --dump-priority NORMAL
     Nuclear reactor status: DANGER - MELTDOWN IMMINENT
 
  * </code></pre>
@@ -84,7 +84,7 @@
  */
 public final class PriorityDump {
 
-    public static final String PRIORITY_ARG = "--dump_priority";
+    public static final String PRIORITY_ARG = "--dump-priority";
 
     private PriorityDump() {
         throw new UnsupportedOperationException();
@@ -92,12 +92,12 @@
 
     /**
      * Parses {@code} and call the proper {@link PriorityDumper} method when the first argument is
-     * {@code --dump_priority}, stripping the priority and its type.
+     * {@code --dump-priority}, stripping the priority and its type.
      * <p>
-     * For example, if called as {@code --dump_priority HIGH arg1 arg2 arg3}, it will call
+     * For example, if called as {@code --dump-priority HIGH arg1 arg2 arg3}, it will call
      * <code>dumper.dumpHigh(fd, pw, {"arg1", "arg2", "arg3"}) </code>
      * <p>
-     * If the {@code --dump_priority} is not set, it calls
+     * If the {@code --dump-priority} is not set, it calls
      * {@link PriorityDumper#dump(FileDescriptor, PrintWriter, String[])} passing the whole
      * {@code args} instead.
      */
@@ -124,7 +124,7 @@
     }
 
     /**
-     * Gets an array without the {@code --dump_priority PRIORITY} prefix.
+     * Gets an array without the {@code --dump-priority PRIORITY} prefix.
      */
     private static String[] getStrippedArgs(String[] args) {
         final String[] stripped = new String[args.length - 2];
diff --git a/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index 3788cf3..b040a63 100644
--- a/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -20,6 +20,7 @@
 import static android.app.ActivityManager.START_ASSISTANT_NOT_ACTIVE_SESSION;
 import static android.app.ActivityManager.START_VOICE_HIDDEN_SESSION;
 import static android.app.ActivityManager.START_VOICE_NOT_ACTIVE_SESSION;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
 
 import android.app.ActivityManager;
 import android.app.ActivityManager.StackId;
@@ -222,8 +223,8 @@
             }
             intent = new Intent(intent);
             intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-            ActivityOptions options = ActivityOptions.makeBasic();
-            options.setLaunchStackId(StackId.ASSISTANT_STACK_ID);
+            final ActivityOptions options = ActivityOptions.makeBasic();
+            options.setLaunchActivityType(ACTIVITY_TYPE_ASSISTANT);
             return mAm.startAssistantActivity(mComponent.getPackageName(), callingPid, callingUid,
                     intent, resolvedType, options.toBundle(), mUser);
         } catch (RemoteException e) {
diff --git a/com/android/server/vr/Vr2dDisplay.java b/com/android/server/vr/Vr2dDisplay.java
index 8f50a39..95d03d4 100644
--- a/com/android/server/vr/Vr2dDisplay.java
+++ b/com/android/server/vr/Vr2dDisplay.java
@@ -294,6 +294,9 @@
 
             int flags = DisplayManager.VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH;
             flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT;
+            flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
+            flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
+            flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL;
             mVirtualDisplay = mDisplayManager.createVirtualDisplay(null /* projection */,
                     DISPLAY_NAME, mVirtualDisplayWidth, mVirtualDisplayHeight, mVirtualDisplayDpi,
                     null /* surface */, flags, null /* callback */, null /* handler */,
diff --git a/com/android/server/vr/VrManagerInternal.java b/com/android/server/vr/VrManagerInternal.java
index bdd9de0..7b1e12e 100644
--- a/com/android/server/vr/VrManagerInternal.java
+++ b/com/android/server/vr/VrManagerInternal.java
@@ -74,6 +74,13 @@
     public abstract void onScreenStateChanged(boolean isScreenOn);
 
     /**
+     * Set whether the keyguard is currently active/showing.
+     *
+     * @param isShowing is {@code true} if the keyguard is active/showing.
+     */
+    public abstract void onKeyguardStateChanged(boolean isShowing);
+
+    /**
      * Return NO_ERROR if the given package is installed on the device and enabled as a
      * VrListenerService for the given current user, or a negative error code indicating a failure.
      *
diff --git a/com/android/server/vr/VrManagerService.java b/com/android/server/vr/VrManagerService.java
index 1f0b2f0..e7e4efc 100644
--- a/com/android/server/vr/VrManagerService.java
+++ b/com/android/server/vr/VrManagerService.java
@@ -66,6 +66,8 @@
 import com.android.server.SystemConfig;
 import com.android.server.SystemService;
 import com.android.server.utils.ManagedApplicationService.PendingEvent;
+import com.android.server.utils.ManagedApplicationService.LogEvent;
+import com.android.server.utils.ManagedApplicationService.LogFormattable;
 import com.android.server.vr.EnabledComponentsObserver.EnabledComponentChangeListener;
 import com.android.server.utils.ManagedApplicationService;
 import com.android.server.utils.ManagedApplicationService.BinderChecker;
@@ -108,16 +110,18 @@
     static final boolean DBG = false;
 
     private static final int PENDING_STATE_DELAY_MS = 300;
-    private static final int EVENT_LOG_SIZE = 32;
+    private static final int EVENT_LOG_SIZE = 64;
     private static final int INVALID_APPOPS_MODE = -1;
     /** Null set of sleep sleep flags. */
     private static final int FLAG_NONE = 0;
     /** Flag set when the device is not sleeping. */
-    private static final int FLAG_AWAKE = 1;
+    private static final int FLAG_AWAKE = 1 << 0;
     /** Flag set when the screen has been turned on. */
-    private static final int FLAG_SCREEN_ON = 2;
+    private static final int FLAG_SCREEN_ON = 1 << 1;
+    /** Flag set when the keyguard is not active. */
+    private static final int FLAG_KEYGUARD_UNLOCKED = 1 << 2;
     /** Flag indicating that all system sleep flags have been set.*/
-    private static final int FLAG_ALL = FLAG_AWAKE | FLAG_SCREEN_ON;
+    private static final int FLAG_ALL = FLAG_AWAKE | FLAG_SCREEN_ON | FLAG_KEYGUARD_UNLOCKED;
 
     private static native void initializeNative();
     private static native void setVrModeNative(boolean enabled);
@@ -134,6 +138,7 @@
     private int mVrAppProcessId;
     private EnabledComponentsObserver mComponentObserver;
     private ManagedApplicationService mCurrentVrService;
+    private ManagedApplicationService mCurrentVrCompositorService;
     private ComponentName mDefaultVrService;
     private Context mContext;
     private ComponentName mCurrentVrModeComponent;
@@ -147,19 +152,45 @@
     private int mPreviousCoarseLocationMode = INVALID_APPOPS_MODE;
     private int mPreviousManageOverlayMode = INVALID_APPOPS_MODE;
     private VrState mPendingState;
-    private final ArrayDeque<VrState> mLoggingDeque = new ArrayDeque<>(EVENT_LOG_SIZE);
+    private boolean mLogLimitHit;
+    private final ArrayDeque<LogFormattable> mLoggingDeque = new ArrayDeque<>(EVENT_LOG_SIZE);
     private final NotificationAccessManager mNotifAccessManager = new NotificationAccessManager();
     private INotificationManager mNotificationManager;
     /** Tracks the state of the screen and keyguard UI.*/
-    private int mSystemSleepFlags = FLAG_AWAKE;
+    private int mSystemSleepFlags = FLAG_AWAKE | FLAG_KEYGUARD_UNLOCKED;
     /**
      * Set when ACTION_USER_UNLOCKED is fired. We shouldn't try to bind to the
-     * vr service before then.
+     * vr service before then. This gets set only once the first time the user unlocks the device
+     * and stays true thereafter.
      */
     private boolean mUserUnlocked;
     private Vr2dDisplay mVr2dDisplay;
     private boolean mBootsToVr;
 
+    // Handles events from the managed services (e.g. VrListenerService and any bound VR compositor
+    // service).
+    private final ManagedApplicationService.EventCallback mEventCallback
+                = new ManagedApplicationService.EventCallback() {
+        @Override
+        public void onServiceEvent(LogEvent event) {
+            logEvent(event);
+
+            ComponentName component = null;
+            synchronized (mLock) {
+                component = ((mCurrentVrService == null) ? null : mCurrentVrService.getComponent());
+            }
+
+            // If not on an AIO device and we permanently stopped trying to connect to the
+            // VrListenerService (or don't have one bound), leave persistent VR mode and VR mode.
+            if (!mBootsToVr && event.event == LogEvent.EVENT_STOPPED_PERMANENTLY &&
+                    (component == null || component.equals(event.component))) {
+                Slog.e(TAG, "VrListenerSevice has died permanently, leaving system VR mode.");
+                // We're not a native VR device.  Leave VR + persistent mode.
+                setPersistentVrModeEnabled(false);
+            }
+        }
+    };
+
     private static final int MSG_VR_STATE_CHANGE = 0;
     private static final int MSG_PENDING_VR_STATE_CHANGE = 1;
     private static final int MSG_PERSISTENT_VR_MODE_STATE_CHANGE = 2;
@@ -180,7 +211,6 @@
                 if (mBootsToVr) {
                     setPersistentVrModeEnabled(true);
                 }
-                consumeAndApplyPendingStateLocked();
                 if (mBootsToVr && !mVrModeEnabled) {
                   setVrMode(true, mDefaultVrService, 0, -1, null);
                 }
@@ -202,29 +232,40 @@
     }
 
     private void setSleepState(boolean isAsleep) {
-        synchronized(mLock) {
-
-            if (!isAsleep) {
-                mSystemSleepFlags |= FLAG_AWAKE;
-            } else {
-                mSystemSleepFlags &= ~FLAG_AWAKE;
-            }
-
-            updateVrModeAllowedLocked();
-        }
+        setSystemState(FLAG_AWAKE, !isAsleep);
     }
 
     private void setScreenOn(boolean isScreenOn) {
+        setSystemState(FLAG_SCREEN_ON, isScreenOn);
+    }
+
+    private void setKeyguardShowing(boolean isShowing) {
+        setSystemState(FLAG_KEYGUARD_UNLOCKED, !isShowing);
+    }
+
+    private void setSystemState(int flags, boolean isOn) {
         synchronized(mLock) {
-            if (isScreenOn) {
-                mSystemSleepFlags |= FLAG_SCREEN_ON;
+            int oldState = mSystemSleepFlags;
+            if (isOn) {
+                mSystemSleepFlags |= flags;
             } else {
-                mSystemSleepFlags &= ~FLAG_SCREEN_ON;
+                mSystemSleepFlags &= ~flags;
             }
-            updateVrModeAllowedLocked();
+            if (oldState != mSystemSleepFlags) {
+                if (DBG) Slog.d(TAG, "System state: " + getStateAsString());
+                updateVrModeAllowedLocked();
+            }
         }
     }
 
+    private String getStateAsString() {
+        return new StringBuilder()
+                .append((mSystemSleepFlags & FLAG_AWAKE) != 0 ? "awake, " : "")
+                .append((mSystemSleepFlags & FLAG_SCREEN_ON) != 0 ? "screen_on, " : "")
+                .append((mSystemSleepFlags & FLAG_KEYGUARD_UNLOCKED) != 0 ? "keyguard_off" : "")
+                .toString();
+    }
+
     private void setUserUnlocked() {
         synchronized(mLock) {
             mUserUnlocked = true;
@@ -276,7 +317,24 @@
         }
     };
 
-    private static class VrState {
+    // Event used to log when settings are changed for dumpsys logs.
+    private static class SettingEvent implements LogFormattable {
+        public final long timestamp;
+        public final String what;
+
+        SettingEvent(String what) {
+            this.timestamp = System.currentTimeMillis();
+            this.what = what;
+        }
+
+        @Override
+        public String toLogString(SimpleDateFormat dateFormat) {
+            return dateFormat.format(new Date(timestamp)) + "   " + what;
+        }
+    }
+
+    // Event used to track changes of the primary on-screen VR activity.
+    private static class VrState implements LogFormattable {
         final boolean enabled;
         final boolean running2dInVr;
         final int userId;
@@ -286,7 +344,6 @@
         final long timestamp;
         final boolean defaultPermissionsGranted;
 
-
         VrState(boolean enabled, boolean running2dInVr, ComponentName targetPackageName, int userId,
                 int processId, ComponentName callingPackage) {
             this.enabled = enabled;
@@ -310,6 +367,39 @@
             this.defaultPermissionsGranted = defaultPermissionsGranted;
             this.timestamp = System.currentTimeMillis();
         }
+
+        @Override
+        public String toLogString(SimpleDateFormat dateFormat) {
+            String tab = "  ";
+            String newLine = "\n";
+            StringBuilder sb = new StringBuilder(dateFormat.format(new Date(timestamp)));
+            sb.append(tab);
+            sb.append("State changed to:");
+            sb.append(tab);
+            sb.append((enabled) ? "ENABLED" : "DISABLED");
+            sb.append(newLine);
+            if (enabled) {
+                sb.append(tab);
+                sb.append("User=");
+                sb.append(userId);
+                sb.append(newLine);
+                sb.append(tab);
+                sb.append("Current VR Activity=");
+                sb.append((callingPackage == null) ? "None" : callingPackage.flattenToString());
+                sb.append(newLine);
+                sb.append(tab);
+                sb.append("Bound VrListenerService=");
+                sb.append((targetPackageName == null) ? "None"
+                        : targetPackageName.flattenToString());
+                sb.append(newLine);
+                if (defaultPermissionsGranted) {
+                    sb.append(tab);
+                    sb.append("Default permissions granted to the bound VrListenerService.");
+                    sb.append(newLine);
+                }
+            }
+            return sb.toString();
+        }
     }
 
     private static final BinderChecker sBinderChecker = new BinderChecker() {
@@ -490,6 +580,13 @@
         }
 
         @Override
+        public void setAndBindCompositor(String componentName) {
+            enforceCallerPermissionAnyOf(Manifest.permission.RESTRICTED_VR_ACCESS);
+            VrManagerService.this.setAndBindCompositor(
+                (componentName == null) ? null : ComponentName.unflattenFromString(componentName));
+        }
+
+        @Override
         protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
             if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
 
@@ -497,6 +594,12 @@
             pw.println("VR mode is currently: " + ((mVrModeAllowed) ? "allowed" : "disallowed"));
             pw.println("Persistent VR mode is currently: " +
                     ((mPersistentVrModeEnabled) ? "enabled" : "disabled"));
+            pw.println("Currently bound VR listener service: "
+                    + ((mCurrentVrService == null)
+                    ? "None" : mCurrentVrService.getComponent().flattenToString()));
+            pw.println("Currently bound VR compositor service: "
+                    + ((mCurrentVrCompositorService == null)
+                    ? "None" : mCurrentVrCompositorService.getComponent().flattenToString()));
             pw.println("Previous state transitions:\n");
             String tab = "  ";
             dumpStateTransitions(pw);
@@ -582,6 +685,11 @@
         }
 
         @Override
+        public void onKeyguardStateChanged(boolean isShowing) {
+            VrManagerService.this.setKeyguardShowing(isShowing);
+        }
+
+        @Override
         public boolean isCurrentVrListener(String packageName, int userId) {
             return VrManagerService.this.isCurrentVrListener(packageName, userId);
         }
@@ -785,6 +893,7 @@
                         + mCurrentVrService.getComponent() + " for user "
                         + mCurrentVrService.getUserId());
                     mCurrentVrService.disconnect();
+                    updateCompositorServiceLocked(UserHandle.USER_NULL, null);
                     mCurrentVrService = null;
                 } else {
                     nothingChanged = true;
@@ -798,6 +907,7 @@
                         Slog.i(TAG, "VR mode component changed to " + component
                             + ", disconnecting " + mCurrentVrService.getComponent()
                             + " for user " + mCurrentVrService.getUserId());
+                        updateCompositorServiceLocked(UserHandle.USER_NULL, null);
                         createAndConnectService(component, userId);
                         sendUpdatedCaller = true;
                     } else {
@@ -985,7 +1095,7 @@
 
 
     private void createAndConnectService(@NonNull ComponentName component, int userId) {
-        mCurrentVrService = VrManagerService.create(mContext, component, userId);
+        mCurrentVrService = createVrListenerService(component, userId);
         mCurrentVrService.connect();
         Slog.i(TAG, "Connecting " + component + " for user " + userId);
     }
@@ -1020,13 +1130,27 @@
     }
 
     /**
-     * Helper function for making ManagedApplicationService instances.
+     * Helper function for making ManagedApplicationService for VrListenerService instances.
      */
-    private static ManagedApplicationService create(@NonNull Context context,
-            @NonNull ComponentName component, int userId) {
-        return ManagedApplicationService.build(context, component, userId,
+    private ManagedApplicationService createVrListenerService(@NonNull ComponentName component,
+            int userId) {
+        int retryType = (mBootsToVr) ? ManagedApplicationService.RETRY_FOREVER
+                : ManagedApplicationService.RETRY_NEVER;
+        return ManagedApplicationService.build(mContext, component, userId,
                 R.string.vr_listener_binding_label, Settings.ACTION_VR_LISTENER_SETTINGS,
-                sBinderChecker);
+                sBinderChecker, /*isImportant*/true, retryType, mHandler, mEventCallback);
+    }
+
+    /**
+     * Helper function for making ManagedApplicationService for VR Compositor instances.
+     */
+    private ManagedApplicationService createVrCompositorService(@NonNull ComponentName component,
+            int userId) {
+        int retryType = (mBootsToVr) ? ManagedApplicationService.RETRY_FOREVER
+                : ManagedApplicationService.RETRY_BEST_EFFORT;
+        return ManagedApplicationService.build(mContext, component, userId, /*clientLabel*/0,
+                /*settingsAction*/null, /*binderChecker*/null, /*isImportant*/true, retryType,
+                mHandler, /*disconnectCallback*/mEventCallback);
     }
 
     /**
@@ -1057,44 +1181,35 @@
 
     private void logStateLocked() {
         ComponentName currentBoundService = (mCurrentVrService == null) ? null :
-            mCurrentVrService.getComponent();
-        VrState current = new VrState(mVrModeEnabled, mRunning2dInVr, currentBoundService,
-            mCurrentVrModeUser, mVrAppProcessId, mCurrentVrModeComponent, mWasDefaultGranted);
-        if (mLoggingDeque.size() == EVENT_LOG_SIZE) {
-            mLoggingDeque.removeFirst();
+                mCurrentVrService.getComponent();
+        logEvent(new VrState(mVrModeEnabled, mRunning2dInVr, currentBoundService,
+                mCurrentVrModeUser, mVrAppProcessId, mCurrentVrModeComponent, mWasDefaultGranted));
+    }
+
+    private void logEvent(LogFormattable event) {
+        synchronized (mLoggingDeque) {
+            if (mLoggingDeque.size() == EVENT_LOG_SIZE) {
+                mLoggingDeque.removeFirst();
+                mLogLimitHit = true;
+            }
+            mLoggingDeque.add(event);
         }
-        mLoggingDeque.add(current);
     }
 
     private void dumpStateTransitions(PrintWriter pw) {
         SimpleDateFormat d = new SimpleDateFormat("MM-dd HH:mm:ss.SSS");
-        String tab = "  ";
-        if (mLoggingDeque.size() == 0) {
-            pw.print(tab);
-            pw.println("None");
-        }
-        for (VrState state : mLoggingDeque) {
-            pw.print(d.format(new Date(state.timestamp)));
-            pw.print(tab);
-            pw.print("State changed to:");
-            pw.print(tab);
-            pw.println((state.enabled) ? "ENABLED" : "DISABLED");
-            if (state.enabled) {
-                pw.print(tab);
-                pw.print("User=");
-                pw.println(state.userId);
-                pw.print(tab);
-                pw.print("Current VR Activity=");
-                pw.println((state.callingPackage == null) ?
-                    "None" : state.callingPackage.flattenToString());
-                pw.print(tab);
-                pw.print("Bound VrListenerService=");
-                pw.println((state.targetPackageName == null) ?
-                    "None" : state.targetPackageName.flattenToString());
-                if (state.defaultPermissionsGranted) {
-                    pw.print(tab);
-                    pw.println("Default permissions granted to the bound VrListenerService.");
-                }
+        synchronized (mLoggingDeque) {
+            if (mLoggingDeque.size() == 0) {
+                pw.print("  ");
+                pw.println("None");
+            }
+
+            if (mLogLimitHit) {
+                pw.println("..."); // Indicates log overflow
+            }
+
+            for (LogFormattable event : mLoggingDeque) {
+                pw.println(event.toLogString(d));
             }
         }
     }
@@ -1177,10 +1292,41 @@
         return INVALID_DISPLAY;
     }
 
+    private void setAndBindCompositor(ComponentName componentName) {
+        final int userId = UserHandle.getCallingUserId();
+        final long token = Binder.clearCallingIdentity();
+        synchronized (mLock) {
+            updateCompositorServiceLocked(userId, componentName);
+        }
+        Binder.restoreCallingIdentity(token);
+    }
+
+    private void updateCompositorServiceLocked(int userId, ComponentName componentName) {
+        if (mCurrentVrCompositorService != null
+                && mCurrentVrCompositorService.disconnectIfNotMatching(componentName, userId)) {
+            Slog.i(TAG, "Disconnecting compositor service: "
+                    + mCurrentVrCompositorService.getComponent());
+            // Check if existing service matches the requested one, if not (or if the requested
+            // component is null) disconnect it.
+            mCurrentVrCompositorService = null;
+        }
+
+        if (componentName != null && mCurrentVrCompositorService == null) {
+            // We don't have an existing service matching the requested component, so attempt to
+            // connect one.
+            Slog.i(TAG, "Connecting compositor service: " + componentName);
+            mCurrentVrCompositorService = createVrCompositorService(componentName, userId);
+            mCurrentVrCompositorService.connect();
+        }
+    }
+
     private void setPersistentModeAndNotifyListenersLocked(boolean enabled) {
         if (mPersistentVrModeEnabled == enabled) {
             return;
         }
+        String eventName = "Persistent VR mode " + ((enabled) ? "enabled" : "disabled");
+        Slog.i(TAG, eventName);
+        logEvent(new SettingEvent(eventName));
         mPersistentVrModeEnabled = enabled;
 
         mHandler.sendMessage(mHandler.obtainMessage(MSG_PERSISTENT_VR_MODE_STATE_CHANGE,
diff --git a/com/android/server/webkit/SystemImpl.java b/com/android/server/webkit/SystemImpl.java
index bf769ed..1e334b8 100644
--- a/com/android/server/webkit/SystemImpl.java
+++ b/com/android/server/webkit/SystemImpl.java
@@ -304,6 +304,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.MATCH_ANY_USER;
+            | PackageManager.GET_SIGNATURES | PackageManager.GET_SHARED_LIBRARY_FILES
+            | PackageManager.MATCH_DEBUG_TRIAGED_MISSING | PackageManager.MATCH_ANY_USER;
 }
diff --git a/com/android/server/wifi/NetworkListStoreData.java b/com/android/server/wifi/NetworkListStoreData.java
index 5ddfd4d..f287d4b 100644
--- a/com/android/server/wifi/NetworkListStoreData.java
+++ b/com/android/server/wifi/NetworkListStoreData.java
@@ -16,10 +16,12 @@
 
 package com.android.server.wifi;
 
+import android.content.Context;
 import android.net.IpConfiguration;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiConfiguration.NetworkSelectionStatus;
 import android.net.wifi.WifiEnterpriseConfig;
+import android.os.Process;
 import android.util.Log;
 import android.util.Pair;
 
@@ -52,6 +54,8 @@
     private static final String XML_TAG_SECTION_HEADER_WIFI_ENTERPRISE_CONFIGURATION =
             "WifiEnterpriseConfiguration";
 
+    private final Context mContext;
+
     /**
      * List of saved shared networks visible to all the users to be stored in the shared store file.
      */
@@ -62,7 +66,9 @@
      */
     private List<WifiConfiguration> mUserConfigurations;
 
-    NetworkListStoreData() {}
+    NetworkListStoreData(Context context) {
+        mContext = context;
+    }
 
     @Override
     public void serializeData(XmlSerializer out, boolean shared)
@@ -282,6 +288,19 @@
                     "Configuration key does not match. Retrieved: " + configKeyParsed
                             + ", Calculated: " + configKeyCalculated);
         }
+        // Set creatorUid/creatorName for networks which don't have it set to valid value.
+        String creatorName = mContext.getPackageManager().getNameForUid(configuration.creatorUid);
+        if (creatorName == null) {
+            Log.e(TAG, "Invalid creatorUid for saved network " + configuration.configKey()
+                    + ", creatorUid=" + configuration.creatorUid);
+            configuration.creatorUid = Process.SYSTEM_UID;
+            configuration.creatorName = creatorName;
+        } else if (!creatorName.equals(configuration.creatorName)) {
+            Log.w(TAG, "Invalid creatorName for saved network " + configuration.configKey()
+                    + ", creatorUid=" + configuration.creatorUid
+                    + ", creatorName=" + configuration.creatorName);
+            configuration.creatorName = creatorName;
+        }
 
         configuration.setNetworkSelectionStatus(status);
         configuration.setIpConfiguration(ipConfiguration);
diff --git a/com/android/server/wifi/OpenNetworkNotifier.java b/com/android/server/wifi/OpenNetworkNotifier.java
index 31ff44b..eee4ac5 100644
--- a/com/android/server/wifi/OpenNetworkNotifier.java
+++ b/com/android/server/wifi/OpenNetworkNotifier.java
@@ -40,11 +40,13 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
+import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
+import com.android.server.wifi.nano.WifiMetricsProto.ConnectToNetworkNotificationAndActionCount;
 import com.android.server.wifi.util.ScanResultUtil;
 
 import java.io.FileDescriptor;
@@ -124,6 +126,7 @@
     private final Context mContext;
     private final Handler mHandler;
     private final FrameworkFacade mFrameworkFacade;
+    private final WifiMetrics mWifiMetrics;
     private final Clock mClock;
     private final WifiConfigManager mConfigManager;
     private final WifiStateMachine mWifiStateMachine;
@@ -138,6 +141,7 @@
             Looper looper,
             FrameworkFacade framework,
             Clock clock,
+            WifiMetrics wifiMetrics,
             WifiConfigManager wifiConfigManager,
             WifiConfigStore wifiConfigStore,
             WifiStateMachine wifiStateMachine,
@@ -146,6 +150,7 @@
         mContext = context;
         mHandler = new Handler(looper);
         mFrameworkFacade = framework;
+        mWifiMetrics = wifiMetrics;
         mClock = clock;
         mConfigManager = wifiConfigManager;
         mWifiStateMachine = wifiStateMachine;
@@ -206,7 +211,7 @@
             case WifiManager.CONNECT_NETWORK_SUCCEEDED:
                 break;
             case WifiManager.CONNECT_NETWORK_FAILED:
-                handleConnectionFailure();
+                handleConnectionAttemptFailedToSend();
                 break;
             default:
                 Log.e(TAG, "Unknown message " + msg.what);
@@ -226,6 +231,13 @@
 
         if (mState != STATE_NO_NOTIFICATION) {
             getNotificationManager().cancel(SystemMessage.NOTE_NETWORK_AVAILABLE);
+
+            if (mRecommendedNetwork != null) {
+                Log.d(TAG, "Notification with state="
+                        + mState
+                        + " was cleared for recommended network: "
+                        + mRecommendedNetwork.SSID);
+            }
             mState = STATE_NO_NOTIFICATION;
             mRecommendedNetwork = null;
         }
@@ -295,6 +307,10 @@
 
         postNotification(mNotificationBuilder.createNetworkConnectedNotification(
                 mRecommendedNetwork));
+
+        Log.d(TAG, "User connected to recommended network: " + mRecommendedNetwork.SSID);
+        mWifiMetrics.incrementConnectToNetworkNotification(
+                ConnectToNetworkNotificationAndActionCount.NOTIFICATION_CONNECTED_TO_NETWORK);
         mState = STATE_CONNECTED_NOTIFICATION;
         mHandler.postDelayed(
                 () -> {
@@ -313,6 +329,10 @@
             return;
         }
         postNotification(mNotificationBuilder.createNetworkFailedNotification());
+
+        Log.d(TAG, "User failed to connect to recommended network: " + mRecommendedNetwork.SSID);
+        mWifiMetrics.incrementConnectToNetworkNotification(
+                ConnectToNetworkNotificationAndActionCount.NOTIFICATION_FAILED_TO_CONNECT);
         mState = STATE_CONNECT_FAILED_NOTIFICATION;
         mHandler.postDelayed(
                 () -> {
@@ -328,8 +348,18 @@
     }
 
     private void postInitialNotification(ScanResult recommendedNetwork) {
+        if (mRecommendedNetwork != null
+                && TextUtils.equals(mRecommendedNetwork.SSID, recommendedNetwork.SSID)) {
+            return;
+        }
         postNotification(mNotificationBuilder.createConnectToNetworkNotification(
                 recommendedNetwork));
+        if (mState == STATE_NO_NOTIFICATION) {
+            mWifiMetrics.incrementConnectToNetworkNotification(
+                    ConnectToNetworkNotificationAndActionCount.NOTIFICATION_RECOMMEND_NETWORK);
+        } else {
+            mWifiMetrics.incrementNumOpenNetworkRecommendationUpdates();
+        }
         mState = STATE_SHOWING_RECOMMENDATION_NOTIFICATION;
         mRecommendedNetwork = recommendedNetwork;
         mNotificationRepeatTime = mClock.getWallClockMillis() + mNotificationRepeatDelay;
@@ -340,11 +370,15 @@
     }
 
     private void handleConnectToNetworkAction() {
+        mWifiMetrics.incrementConnectToNetworkNotificationAction(mState,
+                ConnectToNetworkNotificationAndActionCount.ACTION_CONNECT_TO_NETWORK);
         if (mState != STATE_SHOWING_RECOMMENDATION_NOTIFICATION) {
             return;
         }
         postNotification(mNotificationBuilder.createNetworkConnectingNotification(
                 mRecommendedNetwork));
+        mWifiMetrics.incrementConnectToNetworkNotification(
+                ConnectToNetworkNotificationAndActionCount.NOTIFICATION_CONNECTING_TO_NETWORK);
 
         Log.d(TAG, "User initiated connection to recommended network: " + mRecommendedNetwork.SSID);
         WifiConfiguration network = ScanResultUtil.createNetworkFromScanResult(mRecommendedNetwork);
@@ -366,6 +400,8 @@
     }
 
     private void handleSeeAllNetworksAction() {
+        mWifiMetrics.incrementConnectToNetworkNotificationAction(mState,
+                ConnectToNetworkNotificationAndActionCount.ACTION_PICK_WIFI_NETWORK);
         startWifiSettings();
     }
 
@@ -378,14 +414,26 @@
         clearPendingNotification(false /* resetRepeatTime */);
     }
 
+    private void handleConnectionAttemptFailedToSend() {
+        handleConnectionFailure();
+        mWifiMetrics.incrementNumOpenNetworkConnectMessageFailedToSend();
+    }
+
     private void handlePickWifiNetworkAfterConnectFailure() {
+        mWifiMetrics.incrementConnectToNetworkNotificationAction(mState,
+                ConnectToNetworkNotificationAndActionCount
+                        .ACTION_PICK_WIFI_NETWORK_AFTER_CONNECT_FAILURE);
         startWifiSettings();
     }
 
     private void handleUserDismissedAction() {
+        Log.d(TAG, "User dismissed notification with state=" + mState);
+        mWifiMetrics.incrementConnectToNetworkNotificationAction(mState,
+                ConnectToNetworkNotificationAndActionCount.ACTION_USER_DISMISSED_NOTIFICATION);
         if (mState == STATE_SHOWING_RECOMMENDATION_NOTIFICATION) {
             // blacklist dismissed network
             mBlacklistedSsids.add(mRecommendedNetwork.SSID);
+            mWifiMetrics.setOpenNetworkRecommenderBlacklistSize(mBlacklistedSsids.size());
             mConfigManager.saveToStore(false /* forceWrite */);
             Log.d(TAG, "Network is added to the open network notification blacklist: "
                     + mRecommendedNetwork.SSID);
@@ -418,6 +466,7 @@
         @Override
         public void setSsids(Set<String> ssidList) {
             mBlacklistedSsids.addAll(ssidList);
+            mWifiMetrics.setOpenNetworkRecommenderBlacklistSize(mBlacklistedSsids.size());
         }
     }
 
@@ -440,8 +489,10 @@
         }
 
         private boolean getValue() {
-            return mFrameworkFacade.getIntegerSetting(mContext,
+            boolean enabled = mFrameworkFacade.getIntegerSetting(mContext,
                     Settings.Global.WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON, 1) == 1;
+            mWifiMetrics.setIsWifiNetworksAvailableNotificationEnabled(enabled);
+            return enabled;
         }
     }
 }
diff --git a/com/android/server/wifi/VelocityBasedConnectedScore.java b/com/android/server/wifi/VelocityBasedConnectedScore.java
new file mode 100644
index 0000000..9d90332
--- /dev/null
+++ b/com/android/server/wifi/VelocityBasedConnectedScore.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi;
+
+import android.content.Context;
+import android.net.wifi.WifiInfo;
+
+import com.android.internal.R;
+import com.android.server.wifi.util.KalmanFilter;
+import com.android.server.wifi.util.Matrix;
+
+/**
+ * Class used to calculate scores for connected wifi networks and report it to the associated
+ * network agent.
+ */
+public class VelocityBasedConnectedScore extends ConnectedScore {
+
+    // Device configs. The values are examples.
+    private final int mThresholdMinimumRssi5;      // -82
+    private final int mThresholdMinimumRssi24;     // -85
+
+    private int mFrequency = 5000;
+    private int mRssi = 0;
+    private final KalmanFilter mFilter;
+    private long mLastMillis;
+
+    public VelocityBasedConnectedScore(Context context, Clock clock) {
+        super(clock);
+        mThresholdMinimumRssi5 = context.getResources().getInteger(
+                R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_5GHz);
+        mThresholdMinimumRssi24 = context.getResources().getInteger(
+                R.integer.config_wifi_framework_wifi_score_bad_rssi_threshold_24GHz);
+        mFilter = new KalmanFilter();
+        mFilter.mH = new Matrix(2, new double[]{1.0, 0.0});
+        mFilter.mR = new Matrix(1, new double[]{1.0});
+    }
+
+    /**
+     * Set the Kalman filter's state transition matrix F and process noise covariance Q given
+     * a time step.
+     *
+     * @param dt delta time, in seconds
+     */
+    private void setDeltaTimeSeconds(double dt) {
+        mFilter.mF = new Matrix(2, new double[]{1.0, dt, 0.0, 1.0});
+        Matrix tG = new Matrix(1, new double[]{0.5 * dt * dt, dt});
+        double stda = 0.02; // standard deviation of modelled acceleration
+        mFilter.mQ = tG.dotTranspose(tG).dot(new Matrix(2, new double[]{
+                stda * stda, 0.0,
+                0.0, stda * stda}));
+    }
+    /**
+     * Reset the filter state.
+     */
+    @Override
+    public void reset() {
+        mLastMillis = 0;
+    }
+
+    /**
+     * Updates scoring state using RSSI and measurement noise estimate
+     * <p>
+     * This is useful if an RSSI comes from another source (e.g. scan results) and the
+     * expected noise varies by source.
+     *
+     * @param rssi              signal strength (dB).
+     * @param millis            millisecond-resolution time.
+     * @param standardDeviation of the RSSI.
+     */
+    @Override
+    public void updateUsingRssi(int rssi, long millis, double standardDeviation) {
+        if (millis <= 0) return;
+        if (mLastMillis <= 0 || millis < mLastMillis) {
+            double initialVariance = 9.0 * standardDeviation * standardDeviation;
+            mFilter.mx = new Matrix(1, new double[]{rssi, 0.0});
+            mFilter.mP = new Matrix(2, new double[]{initialVariance, 0.0, 0.0, 0.0});
+            mLastMillis = millis;
+            return;
+        }
+        double dt = (millis - mLastMillis) * 0.001;
+        mFilter.mR.put(0, 0, standardDeviation * standardDeviation);
+        setDeltaTimeSeconds(dt);
+        mFilter.predict();
+        mLastMillis = millis;
+        mFilter.update(new Matrix(1, new double[]{rssi}));
+    }
+
+    /**
+     * Updates the state.
+     */
+    @Override
+    public void updateUsingWifiInfo(WifiInfo wifiInfo, long millis) {
+        int frequency = wifiInfo.getFrequency();
+        if (frequency != mFrequency) {
+            reset(); // Probably roamed
+            mFrequency = frequency;
+        }
+        updateUsingRssi(wifiInfo.getRssi(), millis, mDefaultRssiStandardDeviation);
+    }
+
+    /**
+     * Velocity scorer - predict the rssi a few seconds from now
+     */
+    @Override
+    public int generateScore() {
+        int badRssi = mFrequency >= 5000 ? mThresholdMinimumRssi5 : mThresholdMinimumRssi24;
+        double horizonSeconds = 15.0;
+        Matrix x = new Matrix(mFilter.mx);
+        double filteredRssi = x.get(0, 0);
+        setDeltaTimeSeconds(horizonSeconds);
+        x = mFilter.mF.dot(x);
+        double forecastRssi = x.get(0, 0);
+        if (forecastRssi > filteredRssi) {
+            forecastRssi = filteredRssi; // Be pessimistic about predicting an actual increase
+        }
+        int score = (int) (Math.round(forecastRssi) - badRssi) + WIFI_TRANSITION_SCORE;
+        return score;
+    }
+}
diff --git a/com/android/server/wifi/WifiBackupRestore.java b/com/android/server/wifi/WifiBackupRestore.java
index 60c3b48..ae5e411 100644
--- a/com/android/server/wifi/WifiBackupRestore.java
+++ b/com/android/server/wifi/WifiBackupRestore.java
@@ -228,9 +228,8 @@
             }
 
             return parseNetworkConfigurationsFromXml(in, rootTagDepth, version);
-        } catch (XmlPullParserException e) {
-            Log.e(TAG, "Error parsing the backup data: " + e);
-        } catch (IOException e) {
+        } catch (XmlPullParserException | IOException | ClassCastException
+                | IllegalArgumentException e) {
             Log.e(TAG, "Error parsing the backup data: " + e);
         }
         return null;
diff --git a/com/android/server/wifi/WifiConfigManager.java b/com/android/server/wifi/WifiConfigManager.java
index f8b33cb..ba1695a 100644
--- a/com/android/server/wifi/WifiConfigManager.java
+++ b/com/android/server/wifi/WifiConfigManager.java
@@ -2333,12 +2333,13 @@
     public List<WifiScanner.PnoSettings.PnoNetwork> retrievePnoNetworkList() {
         List<WifiScanner.PnoSettings.PnoNetwork> pnoList = new ArrayList<>();
         List<WifiConfiguration> networks = new ArrayList<>(getInternalConfiguredNetworks());
-        // Remove any permanently disabled networks.
+        // Remove any permanently or temporarily disabled networks.
         Iterator<WifiConfiguration> iter = networks.iterator();
         while (iter.hasNext()) {
             WifiConfiguration config = iter.next();
             if (config.ephemeral || config.isPasspoint()
-                    || config.getNetworkSelectionStatus().isNetworkPermanentlyDisabled()) {
+                    || config.getNetworkSelectionStatus().isNetworkPermanentlyDisabled()
+                    || config.getNetworkSelectionStatus().isNetworkTemporaryDisabled()) {
                 iter.remove();
             }
         }
@@ -2574,10 +2575,12 @@
      * @param userId The identifier of the user that stopped.
      */
     public void handleUserStop(int userId) {
+        if (mVerboseLoggingEnabled) {
+            Log.v(TAG, "Handling user stop for " + userId);
+        }
         if (userId == mCurrentUserId && mUserManager.isUserUnlockingOrUnlocked(mCurrentUserId)) {
             saveToStore(true);
-            clearInternalData();
-            mCurrentUserId = UserHandle.USER_SYSTEM;
+            clearInternalUserData(mCurrentUserId);
         }
     }
 
@@ -2589,6 +2592,7 @@
      *  - List of deleted ephemeral networks.
      */
     private void clearInternalData() {
+        localLog("clearInternalData: Clearing all internal data");
         mConfiguredNetworks.clear();
         mDeletedEphemeralSSIDs.clear();
         mScanDetailCaches.clear();
@@ -2607,12 +2611,16 @@
      * removed from memory.
      */
     private Set<Integer> clearInternalUserData(int userId) {
+        localLog("clearInternalUserData: Clearing user internal data for " + userId);
         Set<Integer> removedNetworkIds = new HashSet<>();
         // Remove any private networks of the old user before switching the userId.
         for (WifiConfiguration config : getInternalConfiguredNetworks()) {
             if (!config.shared && WifiConfigurationUtil.doesUidBelongToAnyProfile(
                     config.creatorUid, mUserManager.getProfiles(userId))) {
                 removedNetworkIds.add(config.networkId);
+                localLog("clearInternalUserData: removed config."
+                        + " netId=" + config.networkId
+                        + " configKey=" + config.configKey());
                 mConfiguredNetworks.remove(config.networkId);
             }
         }
diff --git a/com/android/server/wifi/WifiConnectivityManager.java b/com/android/server/wifi/WifiConnectivityManager.java
index 7e730c8..458f73a 100644
--- a/com/android/server/wifi/WifiConnectivityManager.java
+++ b/com/android/server/wifi/WifiConnectivityManager.java
@@ -30,6 +30,8 @@
 import android.net.wifi.WifiScanner.ScanSettings;
 import android.os.Handler;
 import android.os.Looper;
+import android.os.Process;
+import android.os.WorkSource;
 import android.util.LocalLog;
 import android.util.Log;
 
@@ -214,7 +216,7 @@
 
         @Override
         public void onAlarm() {
-            startSingleScan(mIsFullBandScan);
+            startSingleScan(mIsFullBandScan, WIFI_WORK_SOURCE);
         }
     }
 
@@ -741,7 +743,7 @@
                 localLog("connectToNetwork: Connect to " + targetAssociationId + " from "
                         + currentAssociationId);
             }
-            mStateMachine.startConnectToNetwork(candidate.networkId, targetBssid);
+            mStateMachine.startConnectToNetwork(candidate.networkId, Process.WIFI_UID, targetBssid);
         }
     }
 
@@ -794,7 +796,7 @@
             localLog("start a single scan from watchdogHandler");
 
             scheduleWatchdogTimer();
-            startSingleScan(true);
+            startSingleScan(true, WIFI_WORK_SOURCE);
         }
     }
 
@@ -823,7 +825,7 @@
         }
 
         mLastPeriodicSingleScanTimeStamp = currentTimeStamp;
-        startSingleScan(isFullBandScan);
+        startSingleScan(isFullBandScan, WIFI_WORK_SOURCE);
         schedulePeriodicScanTimer(mPeriodicSingleScanInterval);
 
         // Set up the next scan interval in an exponential backoff fashion.
@@ -850,7 +852,7 @@
     }
 
     // Start a single scan
-    private void startSingleScan(boolean isFullBandScan) {
+    private void startSingleScan(boolean isFullBandScan, WorkSource workSource) {
         if (!mWifiEnabled || !mWifiConnectivityManagerEnabled) {
             return;
         }
@@ -875,7 +877,7 @@
 
         SingleScanListener singleScanListener =
                 new SingleScanListener(isFullBandScan);
-        mScanner.startScan(settings, singleScanListener, WIFI_WORK_SOURCE);
+        mScanner.startScan(settings, singleScanListener, workSource);
     }
 
     // Start a periodic scan when screen is on
@@ -1132,11 +1134,11 @@
     /**
      * Handler for on-demand connectivity scan
      */
-    public void forceConnectivityScan() {
-        localLog("forceConnectivityScan");
+    public void forceConnectivityScan(WorkSource workSource) {
+        localLog("forceConnectivityScan in request of " + workSource);
 
         mWaitForFullBandScanResults = true;
-        startSingleScan(true);
+        startSingleScan(true, workSource);
     }
 
     /**
diff --git a/com/android/server/wifi/WifiCountryCode.java b/com/android/server/wifi/WifiCountryCode.java
index e69fb8e..66a035f 100644
--- a/com/android/server/wifi/WifiCountryCode.java
+++ b/com/android/server/wifi/WifiCountryCode.java
@@ -83,11 +83,9 @@
     public synchronized void simCardRemoved() {
         if (DBG) Log.d(TAG, "SIM Card Removed");
         // SIM card is removed, we need to reset the country code to phone default.
-        if (mRevertCountryCodeOnCellularLoss) {
-            mTelephonyCountryCode = null;
-            if (mReady) {
-                updateCountryCode();
-            }
+        mTelephonyCountryCode = null;
+        if (mReady) {
+            updateCountryCode();
         }
     }
 
@@ -98,12 +96,9 @@
      */
     public synchronized void airplaneModeEnabled() {
         if (DBG) Log.d(TAG, "Airplane Mode Enabled");
-        mTelephonyCountryCode = null;
         // Airplane mode is enabled, we need to reset the country code to phone default.
-        if (mRevertCountryCodeOnCellularLoss) {
-            mTelephonyCountryCode = null;
-            // Country code will be set upon when wpa_supplicant starts next time.
-        }
+        // Country code will be set upon when wpa_supplicant starts next time.
+        mTelephonyCountryCode = null;
     }
 
     /**
@@ -133,8 +128,10 @@
         if (DBG) Log.d(TAG, "Receive set country code request: " + countryCode);
         // Empty country code.
         if (TextUtils.isEmpty(countryCode)) {
-            if (DBG) Log.d(TAG, "Received empty country code, reset to default country code");
-            mTelephonyCountryCode = null;
+            if (mRevertCountryCodeOnCellularLoss) {
+                if (DBG) Log.d(TAG, "Received empty country code, reset to default country code");
+                mTelephonyCountryCode = null;
+            }
         } else {
             mTelephonyCountryCode = countryCode.toUpperCase();
         }
diff --git a/com/android/server/wifi/WifiInjector.java b/com/android/server/wifi/WifiInjector.java
index fc3af83..1cbb29e 100644
--- a/com/android/server/wifi/WifiInjector.java
+++ b/com/android/server/wifi/WifiInjector.java
@@ -116,6 +116,7 @@
     private final PasspointManager mPasspointManager;
     private final SIMAccessor mSimAccessor;
     private HandlerThread mWifiAwareHandlerThread;
+    private HandlerThread mRttHandlerThread;
     private HalDeviceManager mHalDeviceManager;
     private final IBatteryStats mBatteryStats;
     private final WifiStateTracker mWifiStateTracker;
@@ -197,7 +198,7 @@
         mWifiConfigManager = new WifiConfigManager(mContext, mClock,
                 UserManager.get(mContext), TelephonyManager.from(mContext),
                 mWifiKeyStore, mWifiConfigStore, mWifiPermissionsUtil,
-                mWifiPermissionsWrapper, new NetworkListStoreData(),
+                mWifiPermissionsWrapper, new NetworkListStoreData(mContext),
                 new DeletedEphemeralSsidsStoreData());
         mWifiMetrics.setWifiConfigManager(mWifiConfigManager);
         mWifiConnectivityHelper = new WifiConnectivityHelper(mWifiNative);
@@ -225,7 +226,7 @@
                 new WrongPasswordNotifier(mContext, mFrameworkFacade));
         mCertManager = new WifiCertManager(mContext);
         mOpenNetworkNotifier = new OpenNetworkNotifier(mContext,
-                mWifiStateMachineHandlerThread.getLooper(), mFrameworkFacade, mClock,
+                mWifiStateMachineHandlerThread.getLooper(), mFrameworkFacade, mClock, mWifiMetrics,
                 mWifiConfigManager, mWifiConfigStore, mWifiStateMachine,
                 new OpenNetworkRecommender(),
                 new ConnectToNetworkNotificationBuilder(mContext, mFrameworkFacade));
@@ -455,6 +456,19 @@
     }
 
     /**
+     * Returns a singleton instance of a HandlerThread for injection. Uses lazy initialization.
+     *
+     * TODO: share worker thread with other Wi-Fi handlers (b/27924886)
+     */
+    public HandlerThread getRttHandlerThread() {
+        if (mRttHandlerThread == null) { // lazy initialization
+            mRttHandlerThread = new HandlerThread("wifiRttService");
+            mRttHandlerThread.start();
+        }
+        return mRttHandlerThread;
+    }
+
+    /**
      * Returns a single instance of HalDeviceManager for injection.
      */
     public HalDeviceManager getHalDeviceManager() {
diff --git a/com/android/server/wifi/WifiMetrics.java b/com/android/server/wifi/WifiMetrics.java
index 5db5ee6..071b4f8 100644
--- a/com/android/server/wifi/WifiMetrics.java
+++ b/com/android/server/wifi/WifiMetrics.java
@@ -37,6 +37,7 @@
 import com.android.server.wifi.hotspot2.PasspointMatch;
 import com.android.server.wifi.hotspot2.PasspointProvider;
 import com.android.server.wifi.nano.WifiMetricsProto;
+import com.android.server.wifi.nano.WifiMetricsProto.ConnectToNetworkNotificationAndActionCount;
 import com.android.server.wifi.nano.WifiMetricsProto.PnoScanMetrics;
 import com.android.server.wifi.nano.WifiMetricsProto.StaEvent;
 import com.android.server.wifi.nano.WifiMetricsProto.StaEvent.ConfigInfo;
@@ -83,6 +84,7 @@
     public static final int MAX_CONNECTABLE_BSSID_NETWORK_BUCKET = 50;
     public static final int MAX_TOTAL_SCAN_RESULT_SSIDS_BUCKET = 100;
     public static final int MAX_TOTAL_SCAN_RESULTS_BUCKET = 250;
+    private static final int CONNECT_TO_NETWORK_NOTIFICATION_ACTION_KEY_MULTIPLIER = 1000;
     private Clock mClock;
     private boolean mScreenOn;
     private int mWifiState;
@@ -150,6 +152,15 @@
     private final SparseIntArray mAvailableSavedPasspointProviderBssidsInScanHistogram =
             new SparseIntArray();
 
+    /** Mapping of "Connect to Network" notifications to counts. */
+    private final SparseIntArray mConnectToNetworkNotificationCount = new SparseIntArray();
+    /** Mapping of "Connect to Network" notification user actions to counts. */
+    private final SparseIntArray mConnectToNetworkNotificationActionCount = new SparseIntArray();
+    private int mOpenNetworkRecommenderBlacklistSize = 0;
+    private boolean mIsWifiNetworksAvailableNotificationOn = false;
+    private int mNumOpenNetworkConnectMessageFailedToSend = 0;
+    private int mNumOpenNetworkRecommendationUpdates = 0;
+
     class RouterFingerPrint {
         private WifiMetricsProto.RouterFingerPrint mRouterFingerPrintProto;
         RouterFingerPrint() {
@@ -1237,6 +1248,55 @@
         }
     }
 
+    /** Increments the occurence of a "Connect to Network" notification. */
+    public void incrementConnectToNetworkNotification(int notificationType) {
+        synchronized (mLock) {
+            int count = mConnectToNetworkNotificationCount.get(notificationType);
+            mConnectToNetworkNotificationCount.put(notificationType, count + 1);
+        }
+    }
+
+    /** Increments the occurence of an "Connect to Network" notification user action. */
+    public void incrementConnectToNetworkNotificationAction(int notificationType, int actionType) {
+        synchronized (mLock) {
+            int key = notificationType * CONNECT_TO_NETWORK_NOTIFICATION_ACTION_KEY_MULTIPLIER
+                    + actionType;
+            int count = mConnectToNetworkNotificationActionCount.get(key);
+            mConnectToNetworkNotificationActionCount.put(key, count + 1);
+        }
+    }
+
+    /**
+     * Sets the number of SSIDs blacklisted from recommendation by the open network notification
+     * recommender.
+     */
+    public void setOpenNetworkRecommenderBlacklistSize(int size) {
+        synchronized (mLock) {
+            mOpenNetworkRecommenderBlacklistSize = size;
+        }
+    }
+
+    /** Sets if the available network notification feature is enabled. */
+    public void setIsWifiNetworksAvailableNotificationEnabled(boolean enabled) {
+        synchronized (mLock) {
+            mIsWifiNetworksAvailableNotificationOn = enabled;
+        }
+    }
+
+    /** Increments the occurence of connection attempts that were initiated unsuccessfully */
+    public void incrementNumOpenNetworkRecommendationUpdates() {
+        synchronized (mLock) {
+            mNumOpenNetworkRecommendationUpdates++;
+        }
+    }
+
+    /** Increments the occurence of connection attempts that were initiated unsuccessfully */
+    public void incrementNumOpenNetworkConnectMessageFailedToSend() {
+        synchronized (mLock) {
+            mNumOpenNetworkConnectMessageFailedToSend++;
+        }
+    }
+
     public static final String PROTO_DUMP_ARG = "wifiMetricsProto";
     public static final String CLEAN_DUMP_ARG = "clean";
 
@@ -1488,6 +1548,19 @@
                         + mPnoScanMetrics.numPnoScanFailedOverOffload);
                 pw.println("mPnoScanMetrics.numPnoFoundNetworkEvents="
                         + mPnoScanMetrics.numPnoFoundNetworkEvents);
+
+                pw.println("mWifiLogProto.connectToNetworkNotificationCount="
+                        + mConnectToNetworkNotificationCount.toString());
+                pw.println("mWifiLogProto.connectToNetworkNotificationActionCount="
+                        + mConnectToNetworkNotificationActionCount.toString());
+                pw.println("mWifiLogProto.openNetworkRecommenderBlacklistSize="
+                        + mOpenNetworkRecommenderBlacklistSize);
+                pw.println("mWifiLogProto.isWifiNetworksAvailableNotificationOn="
+                        + mIsWifiNetworksAvailableNotificationOn);
+                pw.println("mWifiLogProto.numOpenNetworkRecommendationUpdates="
+                        + mNumOpenNetworkRecommendationUpdates);
+                pw.println("mWifiLogProto.numOpenNetworkConnectMessageFailedToSend="
+                        + mNumOpenNetworkConnectMessageFailedToSend);
             }
         }
     }
@@ -1698,6 +1771,53 @@
             mWifiLogProto.wifiAwareLog = mWifiAwareMetrics.consolidateProto();
 
             mWifiLogProto.pnoScanMetrics = mPnoScanMetrics;
+
+            /**
+             * Convert the SparseIntArray of "Connect to Network" notification types and counts to
+             * proto's repeated IntKeyVal array.
+             */
+            ConnectToNetworkNotificationAndActionCount[] notificationCountArray =
+                    new ConnectToNetworkNotificationAndActionCount[
+                            mConnectToNetworkNotificationCount.size()];
+            for (int i = 0; i < mConnectToNetworkNotificationCount.size(); i++) {
+                ConnectToNetworkNotificationAndActionCount keyVal =
+                        new ConnectToNetworkNotificationAndActionCount();
+                keyVal.notification = mConnectToNetworkNotificationCount.keyAt(i);
+                keyVal.recommender =
+                        ConnectToNetworkNotificationAndActionCount.RECOMMENDER_OPEN;
+                keyVal.count = mConnectToNetworkNotificationCount.valueAt(i);
+                notificationCountArray[i] = keyVal;
+            }
+            mWifiLogProto.connectToNetworkNotificationCount = notificationCountArray;
+
+            /**
+             * Convert the SparseIntArray of "Connect to Network" notification types and counts to
+             * proto's repeated IntKeyVal array.
+             */
+            ConnectToNetworkNotificationAndActionCount[] notificationActionCountArray =
+                    new ConnectToNetworkNotificationAndActionCount[
+                            mConnectToNetworkNotificationActionCount.size()];
+            for (int i = 0; i < mConnectToNetworkNotificationActionCount.size(); i++) {
+                ConnectToNetworkNotificationAndActionCount keyVal =
+                        new ConnectToNetworkNotificationAndActionCount();
+                int key = mConnectToNetworkNotificationActionCount.keyAt(i);
+                keyVal.notification = key / CONNECT_TO_NETWORK_NOTIFICATION_ACTION_KEY_MULTIPLIER;
+                keyVal.action = key % CONNECT_TO_NETWORK_NOTIFICATION_ACTION_KEY_MULTIPLIER;
+                keyVal.recommender =
+                        ConnectToNetworkNotificationAndActionCount.RECOMMENDER_OPEN;
+                keyVal.count = mConnectToNetworkNotificationActionCount.valueAt(i);
+                notificationActionCountArray[i] = keyVal;
+            }
+            mWifiLogProto.connectToNetworkNotificationActionCount = notificationActionCountArray;
+
+            mWifiLogProto.openNetworkRecommenderBlacklistSize =
+                    mOpenNetworkRecommenderBlacklistSize;
+            mWifiLogProto.isWifiNetworksAvailableNotificationOn =
+                    mIsWifiNetworksAvailableNotificationOn;
+            mWifiLogProto.numOpenNetworkRecommendationUpdates =
+                    mNumOpenNetworkRecommendationUpdates;
+            mWifiLogProto.numOpenNetworkConnectMessageFailedToSend =
+                    mNumOpenNetworkConnectMessageFailedToSend;
         }
     }
 
@@ -1716,7 +1836,8 @@
     }
 
     /**
-     * Clear all WifiMetrics, except for currentConnectionEvent.
+     * Clear all WifiMetrics, except for currentConnectionEvent and Open Network Notification
+     * feature enabled state, blacklist size.
      */
     private void clear() {
         synchronized (mLock) {
@@ -1747,6 +1868,10 @@
             mAvailableSavedPasspointProviderProfilesInScanHistogram.clear();
             mAvailableSavedPasspointProviderBssidsInScanHistogram.clear();
             mPnoScanMetrics.clear();
+            mConnectToNetworkNotificationCount.clear();
+            mConnectToNetworkNotificationActionCount.clear();
+            mNumOpenNetworkRecommendationUpdates = 0;
+            mNumOpenNetworkConnectMessageFailedToSend = 0;
         }
     }
 
diff --git a/com/android/server/wifi/WifiNative.java b/com/android/server/wifi/WifiNative.java
index 5b12a36..0b1719d 100644
--- a/com/android/server/wifi/WifiNative.java
+++ b/com/android/server/wifi/WifiNative.java
@@ -79,6 +79,10 @@
         return mInterfaceName;
     }
 
+    public WifiVendorHal getVendorHal() {
+        return mWifiVendorHal;
+    }
+
     /**
      * Enable verbose logging for all sub modules.
      */
diff --git a/com/android/server/wifi/WifiScoreReport.java b/com/android/server/wifi/WifiScoreReport.java
index 894d57c..324cdba 100644
--- a/com/android/server/wifi/WifiScoreReport.java
+++ b/com/android/server/wifi/WifiScoreReport.java
@@ -49,11 +49,13 @@
 
     ConnectedScore mConnectedScore;
     ConnectedScore mAggressiveConnectedScore;
+    ConnectedScore mFancyConnectedScore;
 
     WifiScoreReport(Context context, WifiConfigManager wifiConfigManager, Clock clock) {
         mClock = clock;
         mConnectedScore = new LegacyConnectedScore(context, wifiConfigManager, clock);
         mAggressiveConnectedScore = new AggressiveConnectedScore(context, clock);
+        mFancyConnectedScore = new VelocityBasedConnectedScore(context, clock);
     }
 
     /**
@@ -76,6 +78,7 @@
         }
         mConnectedScore.reset();
         mAggressiveConnectedScore.reset();
+        mFancyConnectedScore.reset();
         if (mVerboseLoggingEnabled) Log.d(TAG, "reset");
     }
 
@@ -113,18 +116,20 @@
                                         int aggressiveHandover, WifiMetrics wifiMetrics) {
         int score;
 
-        long millis = mConnectedScore.getMillis();
+        long millis = mClock.getWallClockMillis();
 
         mConnectedScore.updateUsingWifiInfo(wifiInfo, millis);
         mAggressiveConnectedScore.updateUsingWifiInfo(wifiInfo, millis);
+        mFancyConnectedScore.updateUsingWifiInfo(wifiInfo, millis);
 
         int s0 = mConnectedScore.generateScore();
         int s1 = mAggressiveConnectedScore.generateScore();
+        int s2 = mFancyConnectedScore.generateScore();
 
         if (aggressiveHandover == 0) {
             score = s0;
         } else {
-            score = s1;
+            score = s2;
         }
 
         //sanitize boundaries
@@ -135,7 +140,7 @@
             score = 0;
         }
 
-        logLinkMetrics(wifiInfo, s0, s1);
+        logLinkMetrics(wifiInfo, millis, s0, s1, s2);
 
         //report score
         if (score != wifiInfo.score) {
@@ -163,8 +168,7 @@
     /**
      * Data logging for dumpsys
      */
-    private void logLinkMetrics(WifiInfo wifiInfo, int s0, int s1) {
-        long now = mClock.getWallClockMillis();
+    private void logLinkMetrics(WifiInfo wifiInfo, long now, int s0, int s1, int s2) {
         if (now < FIRST_REASONABLE_WALL_CLOCK) return;
         double rssi = wifiInfo.getRssi();
         int freq = wifiInfo.getFrequency();
@@ -176,10 +180,10 @@
         try {
             String timestamp = new SimpleDateFormat("MM-dd HH:mm:ss.SSS").format(new Date(now));
             String s = String.format(Locale.US, // Use US to avoid comma/decimal confusion
-                    "%s,%d,%.1f,%d,%d,%.2f,%.2f,%.2f,%.2f,%d,%d",
+                    "%s,%d,%.1f,%d,%d,%.2f,%.2f,%.2f,%.2f,%d,%d,%d",
                     timestamp, mSessionNumber, rssi, freq, linkSpeed,
                     txSuccessRate, txRetriesRate, txBadRate, rxSuccessRate,
-                    s0, s1);
+                    s0, s1, s2);
             mLinkMetricsHistory.add(s);
         } catch (Exception e) {
             Log.e(TAG, "format problem", e);
@@ -201,7 +205,7 @@
      * @param args unused
      */
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        pw.println("time,session,rssi,freq,linkspeed,tx_good,tx_retry,tx_bad,rx,s0,s1");
+        pw.println("time,session,rssi,freq,linkspeed,tx_good,tx_retry,tx_bad,rx,s0,s1,s2");
         for (String line : mLinkMetricsHistory) {
             pw.println(line);
         }
diff --git a/com/android/server/wifi/WifiServiceImpl.java b/com/android/server/wifi/WifiServiceImpl.java
index bb995b7..c83f6c6 100644
--- a/com/android/server/wifi/WifiServiceImpl.java
+++ b/com/android/server/wifi/WifiServiceImpl.java
@@ -1421,7 +1421,7 @@
     public void reconnect() {
         enforceChangePermission();
         mLog.info("reconnect uid=%").c(Binder.getCallingUid()).flush();
-        mWifiStateMachine.reconnectCommand();
+        mWifiStateMachine.reconnectCommand(new WorkSource(Binder.getCallingUid()));
     }
 
     /**
diff --git a/com/android/server/wifi/WifiStateMachine.java b/com/android/server/wifi/WifiStateMachine.java
index faad0e2..1f6cada 100644
--- a/com/android/server/wifi/WifiStateMachine.java
+++ b/com/android/server/wifi/WifiStateMachine.java
@@ -1321,7 +1321,7 @@
 
     /**
      * Initiates connection to a network specified by the user/app. This method checks if the
-     * requesting app holds the WIFI_CONFIG_OVERRIDE permission.
+     * requesting app holds the NETWORK_SETTINGS permission.
      *
      * @param netId Id network to initiate connection.
      * @param uid UID of the app requesting the connection.
@@ -1350,7 +1350,7 @@
             logi("connectToUserSelectNetwork already connecting/connected=" + netId);
         } else {
             mWifiConnectivityManager.prepareForForcedConnection(netId);
-            startConnectToNetwork(netId, SUPPLICANT_BSSID_ANY);
+            startConnectToNetwork(netId, uid, SUPPLICANT_BSSID_ANY);
         }
         return true;
     }
@@ -1873,8 +1873,8 @@
     /**
      * Initiate a reconnection to AP
      */
-    public void reconnectCommand() {
-        sendMessage(CMD_RECONNECT);
+    public void reconnectCommand(WorkSource workSource) {
+        sendMessage(CMD_RECONNECT, workSource);
     }
 
     /**
@@ -3980,9 +3980,7 @@
                     deleteNetworkConfigAndSendReply(message, true);
                     break;
                 case WifiManager.SAVE_NETWORK:
-                    messageHandlingStatus = MESSAGE_HANDLING_STATUS_FAIL;
-                    replyToMessage(message, WifiManager.SAVE_NETWORK_FAILED,
-                            WifiManager.BUSY);
+                    saveNetworkConfigAndSendReply(message);
                     break;
                 case WifiManager.START_WPS:
                     replyToMessage(message, WifiManager.WPS_FAILED,
@@ -4537,7 +4535,7 @@
                     mEnableAutoJoinWhenAssociated = allowed;
                     if (!old_state && allowed && mScreenOn
                             && getCurrentState() == mConnectedState) {
-                        mWifiConnectivityManager.forceConnectivityScan();
+                        mWifiConnectivityManager.forceConnectivityScan(WIFI_WORK_SOURCE);
                     }
                     break;
                 case CMD_SELECT_TX_POWER_SCENARIO:
@@ -4824,7 +4822,7 @@
 
             // Notify PasspointManager of Passpoint network connected event.
             WifiConfiguration currentNetwork = getCurrentWifiConfiguration();
-            if (currentNetwork.isPasspoint()) {
+            if (currentNetwork != null && currentNetwork.isPasspoint()) {
                 mPasspointManager.onPasspointNetworkConnected(currentNetwork.FQDN);
             }
        }
@@ -5160,7 +5158,8 @@
                             mPasspointManager.getMatchingOsuProviders((ScanResult) message.obj));
                     break;
                 case CMD_RECONNECT:
-                    mWifiConnectivityManager.forceConnectivityScan();
+                    WorkSource workSource = (WorkSource) message.obj;
+                    mWifiConnectivityManager.forceConnectivityScan(workSource);
                     break;
                 case CMD_REASSOCIATE:
                     lastConnectAttemptTimestamp = mClock.getWallClockMillis();
@@ -5180,7 +5179,23 @@
                 case CMD_START_CONNECT:
                     /* connect command coming from auto-join */
                     netId = message.arg1;
+                    int uid = message.arg2;
                     bssid = (String) message.obj;
+
+                    synchronized (mWifiReqCountLock) {
+                        if (!hasConnectionRequests()) {
+                            if (mNetworkAgent == null) {
+                                loge("CMD_START_CONNECT but no requests and not connected,"
+                                        + " bailing");
+                                break;
+                            } else if (!mWifiPermissionsUtil.checkNetworkSettingsPermission(uid)) {
+                                loge("CMD_START_CONNECT but no requests and connected, but app "
+                                        + "does not have sufficient permissions, bailing");
+                                break;
+                            }
+                        }
+                    }
+
                     config = mWifiConfigManager.getConfiguredNetworkWithPassword(netId);
                     logd("CMD_START_CONNECT sup state "
                             + mSupplicantStateTracker.getSupplicantStateName()
@@ -5270,41 +5285,17 @@
                     replyToMessage(message, WifiManager.CONNECT_NETWORK_SUCCEEDED);
                     break;
                 case WifiManager.SAVE_NETWORK:
-                    config = (WifiConfiguration) message.obj;
-                    mWifiConnectionStatistics.numWifiManagerJoinAttempt++;
-                    if (config == null) {
-                        loge("SAVE_NETWORK with null configuration"
-                                + mSupplicantStateTracker.getSupplicantStateName()
-                                + " my state " + getCurrentState().getName());
-                        messageHandlingStatus = MESSAGE_HANDLING_STATUS_FAIL;
-                        replyToMessage(message, WifiManager.SAVE_NETWORK_FAILED,
-                                WifiManager.ERROR);
-                        break;
-                    }
-                    result = mWifiConfigManager.addOrUpdateNetwork(config, message.sendingUid);
-                    if (!result.isSuccess()) {
-                        loge("SAVE_NETWORK adding/updating config=" + config + " failed");
-                        messageHandlingStatus = MESSAGE_HANDLING_STATUS_FAIL;
-                        replyToMessage(message, WifiManager.SAVE_NETWORK_FAILED,
-                                WifiManager.ERROR);
-                        break;
-                    }
-                    if (!mWifiConfigManager.enableNetwork(
-                            result.getNetworkId(), false, message.sendingUid)) {
-                        loge("SAVE_NETWORK enabling config=" + config + " failed");
-                        messageHandlingStatus = MESSAGE_HANDLING_STATUS_FAIL;
-                        replyToMessage(message, WifiManager.SAVE_NETWORK_FAILED,
-                                WifiManager.ERROR);
-                        break;
-                    }
+                    result = saveNetworkConfigAndSendReply(message);
                     netId = result.getNetworkId();
-                    if (mWifiInfo.getNetworkId() == netId) {
+                    if (result.isSuccess() && mWifiInfo.getNetworkId() == netId) {
+                        mWifiConnectionStatistics.numWifiManagerJoinAttempt++;
                         if (result.hasCredentialChanged()) {
+                            config = (WifiConfiguration) message.obj;
                             // The network credentials changed and we're connected to this network,
                             // start a new connection with the updated credentials.
                             logi("SAVE_NETWORK credential changed for config=" + config.configKey()
                                     + ", Reconnecting.");
-                            startConnectToNetwork(netId, SUPPLICANT_BSSID_ANY);
+                            startConnectToNetwork(netId, message.sendingUid, SUPPLICANT_BSSID_ANY);
                         } else {
                             if (result.hasProxyChanged()) {
                                 log("Reconfiguring proxy on connection");
@@ -5322,8 +5313,6 @@
                             }
                         }
                     }
-                    broadcastWifiCredentialChanged(WifiManager.WIFI_CREDENTIAL_SAVED, config);
-                    replyToMessage(message, WifiManager.SAVE_NETWORK_SUCCEEDED);
                     break;
                 case WifiManager.FORGET_NETWORK:
                     if (!deleteNetworkConfigAndSendReply(message, true)) {
@@ -5831,8 +5820,15 @@
                     reportConnectionAttemptEnd(
                             WifiMetrics.ConnectionEvent.FAILURE_NONE,
                             WifiMetricsProto.ConnectionEvent.HLF_NONE);
-                    sendConnectedState();
-                    transitionTo(mConnectedState);
+                    if (getCurrentWifiConfiguration() == null) {
+                        // The current config may have been removed while we were connecting,
+                        // trigger a disconnect to clear up state.
+                        mWifiNative.disconnect();
+                        transitionTo(mDisconnectingState);
+                    } else {
+                        sendConnectedState();
+                        transitionTo(mConnectedState);
+                    }
                     break;
                 case CMD_IP_CONFIGURATION_LOST:
                     // Get Link layer stats so that we get fresh tx packet counters.
@@ -5999,6 +5995,9 @@
                 case CMD_STOP_RSSI_MONITORING_OFFLOAD:
                     stopRssiMonitoringOffload();
                     break;
+                case CMD_RECONNECT:
+                    log(" Ignore CMD_RECONNECT request because wifi is already connected");
+                    break;
                 case CMD_RESET_SIM_NETWORKS:
                     if (message.arg1 == 0 // sim was removed
                             && mLastNetworkId != WifiConfiguration.INVALID_NETWORK_ID) {
@@ -6142,7 +6141,7 @@
         WifiConfiguration config = getCurrentWifiConfiguration();
         if (shouldEvaluateWhetherToSendExplicitlySelected(config)) {
             boolean prompt =
-                    mWifiPermissionsUtil.checkConfigOverridePermission(config.lastConnectUid);
+                    mWifiPermissionsUtil.checkNetworkSettingsPermission(config.lastConnectUid);
             if (mVerboseLoggingEnabled) {
                 log("Network selected by UID " + config.lastConnectUid + " prompt=" + prompt);
             }
@@ -6851,6 +6850,8 @@
                 case WifiManager.CONNECT_NETWORK:
                 case CMD_ENABLE_NETWORK:
                 case CMD_RECONNECT:
+                    log(" Ignore CMD_RECONNECT request because wps is running");
+                    return HANDLED;
                 case CMD_REASSOCIATE:
                     deferMessage(message);
                     break;
@@ -7118,14 +7119,11 @@
      * Automatically connect to the network specified
      *
      * @param networkId ID of the network to connect to
+     * @param uid UID of the app triggering the connection.
      * @param bssid BSSID of the network
      */
-    public void startConnectToNetwork(int networkId, String bssid) {
-        synchronized (mWifiReqCountLock) {
-            if (hasConnectionRequests()) {
-                sendMessage(CMD_START_CONNECT, networkId, 0, bssid);
-            }
-        }
+    public void startConnectToNetwork(int networkId, int uid, String bssid) {
+        sendMessage(CMD_START_CONNECT, networkId, uid, bssid);
     }
 
     /**
@@ -7210,6 +7208,43 @@
         }
     }
 
+    /**
+     * Private method to handle calling WifiConfigManager to add & enable network configs and reply
+     * to the message from the sender of the outcome.
+     *
+     * @return NetworkUpdateResult with networkId of the added/updated configuration. Will return
+     * {@link WifiConfiguration#INVALID_NETWORK_ID} in case of error.
+     */
+    private NetworkUpdateResult saveNetworkConfigAndSendReply(Message message) {
+        WifiConfiguration config = (WifiConfiguration) message.obj;
+        if (config == null) {
+            loge("SAVE_NETWORK with null configuration "
+                    + mSupplicantStateTracker.getSupplicantStateName()
+                    + " my state " + getCurrentState().getName());
+            messageHandlingStatus = MESSAGE_HANDLING_STATUS_FAIL;
+            replyToMessage(message, WifiManager.SAVE_NETWORK_FAILED, WifiManager.ERROR);
+            return new NetworkUpdateResult(WifiConfiguration.INVALID_NETWORK_ID);
+        }
+        NetworkUpdateResult result =
+                mWifiConfigManager.addOrUpdateNetwork(config, message.sendingUid);
+        if (!result.isSuccess()) {
+            loge("SAVE_NETWORK adding/updating config=" + config + " failed");
+            messageHandlingStatus = MESSAGE_HANDLING_STATUS_FAIL;
+            replyToMessage(message, WifiManager.SAVE_NETWORK_FAILED, WifiManager.ERROR);
+            return result;
+        }
+        if (!mWifiConfigManager.enableNetwork(
+                result.getNetworkId(), false, message.sendingUid)) {
+            loge("SAVE_NETWORK enabling config=" + config + " failed");
+            messageHandlingStatus = MESSAGE_HANDLING_STATUS_FAIL;
+            replyToMessage(message, WifiManager.SAVE_NETWORK_FAILED, WifiManager.ERROR);
+            return new NetworkUpdateResult(WifiConfiguration.INVALID_NETWORK_ID);
+        }
+        broadcastWifiCredentialChanged(WifiManager.WIFI_CREDENTIAL_SAVED, config);
+        replyToMessage(message, WifiManager.SAVE_NETWORK_SUCCEEDED);
+        return result;
+    }
+
     private static String getLinkPropertiesSummary(LinkProperties lp) {
         List<String> attributes = new ArrayList<>(6);
         if (lp.hasIPv4Address()) {
diff --git a/com/android/server/wifi/WifiVendorHal.java b/com/android/server/wifi/WifiVendorHal.java
index 12674aa..e0467d7 100644
--- a/com/android/server/wifi/WifiVendorHal.java
+++ b/com/android/server/wifi/WifiVendorHal.java
@@ -67,6 +67,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.RemoteException;
+import android.util.Log;
 import android.util.MutableBoolean;
 import android.util.MutableInt;
 
@@ -237,6 +238,11 @@
         clearState();
     }
 
+    // TODO: b/65014872 remove - have RttService (RTT2) interact directly with HalDeviceManager
+    public IWifiRttController getRttController() {
+        return mIWifiRttController;
+    }
+
     private WifiNative.VendorHalDeathEventHandler mDeathEventHandler;
 
     /**
@@ -2582,8 +2588,25 @@
                 // The problem here is that the two threads acquire the locks in opposite order.
                 // If, for example, T2.2 executes between T1.2 and 1.4, then T1 and T2
                 // will be deadlocked.
-                eventHandler.onRingBufferData(
-                        ringBufferStatus(status), NativeUtil.byteArrayFromArrayList(data));
+                int sizeBefore = data.size();
+                boolean conversionFailure = false;
+                try {
+                    eventHandler.onRingBufferData(
+                            ringBufferStatus(status), NativeUtil.byteArrayFromArrayList(data));
+                    int sizeAfter = data.size();
+                    if (sizeAfter != sizeBefore) {
+                        conversionFailure = true;
+                    }
+                } catch (ArrayIndexOutOfBoundsException e) {
+                    conversionFailure = true;
+                }
+                if (conversionFailure) {
+                    Log.wtf("WifiVendorHal", "Conversion failure detected in "
+                            + "onDebugRingBufferDataAvailable. "
+                            + "The input ArrayList |data| is potentially corrupted. "
+                            + "Starting size=" + sizeBefore + ", "
+                            + "final size=" + data.size());
+                }
             });
         }
 
diff --git a/com/android/server/wifi/WificondControl.java b/com/android/server/wifi/WificondControl.java
index 056777f..b6104a8 100644
--- a/com/android/server/wifi/WificondControl.java
+++ b/com/android/server/wifi/WificondControl.java
@@ -374,8 +374,13 @@
                         new InformationElementUtil.Capabilities();
                 capabilities.from(ies, result.capability);
                 String flags = capabilities.generateCapabilitiesString();
-                NetworkDetail networkDetail =
-                        new NetworkDetail(bssid, ies, null, result.frequency);
+                NetworkDetail networkDetail;
+                try {
+                    networkDetail = new NetworkDetail(bssid, ies, null, result.frequency);
+                } catch (IllegalArgumentException e) {
+                    Log.e(TAG, "Illegal argument for scan result with bssid: " + bssid, e);
+                    continue;
+                }
 
                 ScanDetail scanDetail = new ScanDetail(networkDetail, wifiSsid, bssid, flags,
                         result.signalMbm / 100, result.frequency, result.tsf, ies, null);
diff --git a/com/android/server/wifi/aware/WifiAwareClientState.java b/com/android/server/wifi/aware/WifiAwareClientState.java
index 3570f1d..987d49e 100644
--- a/com/android/server/wifi/aware/WifiAwareClientState.java
+++ b/com/android/server/wifi/aware/WifiAwareClientState.java
@@ -20,7 +20,6 @@
 import android.app.AppOpsManager;
 import android.content.Context;
 import android.content.pm.PackageManager;
-import android.net.wifi.RttManager;
 import android.net.wifi.aware.ConfigRequest;
 import android.net.wifi.aware.IWifiAwareEventCallback;
 import android.os.RemoteException;
@@ -271,47 +270,6 @@
     }
 
     /**
-     * Called on RTT success - forwards call to client.
-     */
-    public void onRangingSuccess(int rangingId, RttManager.ParcelableRttResults results) {
-        if (VDBG) {
-            Log.v(TAG, "onRangingSuccess: rangingId=" + rangingId + ", results=" + results);
-        }
-        try {
-            mCallback.onRangingSuccess(rangingId, results);
-        } catch (RemoteException e) {
-            Log.w(TAG, "onRangingSuccess: RemoteException - ignored: " + e);
-        }
-    }
-
-    /**
-     * Called on RTT failure - forwards call to client.
-     */
-    public void onRangingFailure(int rangingId, int reason, String description) {
-        if (VDBG) {
-            Log.v(TAG, "onRangingSuccess: rangingId=" + rangingId + ", reason=" + reason
-                    + ", description=" + description);
-        }
-        try {
-            mCallback.onRangingFailure(rangingId, reason, description);
-        } catch (RemoteException e) {
-            Log.w(TAG, "onRangingFailure: RemoteException - ignored: " + e);
-        }
-    }
-
-    /**
-     * Called on RTT operation aborted - forwards call to client.
-     */
-    public void onRangingAborted(int rangingId) {
-        if (VDBG) Log.v(TAG, "onRangingSuccess: rangingId=" + rangingId);
-        try {
-            mCallback.onRangingAborted(rangingId);
-        } catch (RemoteException e) {
-            Log.w(TAG, "onRangingAborted: RemoteException - ignored: " + e);
-        }
-    }
-
-    /**
      * Dump the internal state of the class.
      */
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
diff --git a/com/android/server/wifi/aware/WifiAwareRttStateManager.java b/com/android/server/wifi/aware/WifiAwareRttStateManager.java
deleted file mode 100644
index 9d0441f..0000000
--- a/com/android/server/wifi/aware/WifiAwareRttStateManager.java
+++ /dev/null
@@ -1,206 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.wifi.aware;
-
-import android.content.Context;
-import android.net.wifi.IRttManager;
-import android.net.wifi.RttManager;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.os.Messenger;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-import android.util.Log;
-import android.util.SparseArray;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.AsyncChannel;
-
-import java.io.FileDescriptor;
-import java.io.PrintWriter;
-import java.util.Arrays;
-
-
-/**
- * Manages interactions between the Aware and the RTT service. Duplicates some of the functionality
- * of the RttManager.
- */
-public class WifiAwareRttStateManager {
-    private static final String TAG = "WifiAwareRttStateMgr";
-
-    private static final boolean DBG = false;
-    private static final boolean VDBG = false; // STOPSHIP if true
-
-    private final SparseArray<WifiAwareClientState> mPendingOperations = new SparseArray<>();
-    private AsyncChannel mAsyncChannel;
-    private Context mContext;
-
-    /**
-     * Initializes the connection to the RTT service.
-     */
-    public void start(Context context, Looper looper) {
-        if (VDBG) Log.v(TAG, "start()");
-
-        IBinder b = ServiceManager.getService(Context.WIFI_RTT_SERVICE);
-        IRttManager service = IRttManager.Stub.asInterface(b);
-        if (service == null) {
-            Log.e(TAG, "start(): not able to get WIFI_RTT_SERVICE");
-            return;
-        }
-
-        startWithRttService(context, looper, service);
-    }
-
-    /**
-     * Initializes the connection to the RTT service.
-     */
-    @VisibleForTesting
-    public void startWithRttService(Context context, Looper looper, IRttManager service) {
-        Messenger messenger;
-        try {
-            messenger = service.getMessenger(null, new int[1]);
-        } catch (RemoteException e) {
-            Log.e(TAG, "start(): not able to getMessenger() of WIFI_RTT_SERVICE");
-            return;
-        }
-
-        mAsyncChannel = new AsyncChannel();
-        mAsyncChannel.connect(context, new AwareRttHandler(looper), messenger);
-        mContext = context;
-    }
-
-    private WifiAwareClientState getAndRemovePendingOperationClient(int rangingId) {
-        WifiAwareClientState client = mPendingOperations.get(rangingId);
-        mPendingOperations.delete(rangingId);
-        return client;
-    }
-
-    /**
-     * Start a ranging operation for the client + peer MAC.
-     */
-    public void startRanging(int rangingId, WifiAwareClientState client,
-                             RttManager.RttParams[] params) {
-        if (VDBG) {
-            Log.v(TAG, "startRanging: rangingId=" + rangingId + ", parms="
-                    + Arrays.toString(params));
-        }
-
-        if (mAsyncChannel == null) {
-            Log.d(TAG, "startRanging(): AsyncChannel to RTT service not configured - failing");
-            client.onRangingFailure(rangingId, RttManager.REASON_NOT_AVAILABLE,
-                    "Aware service not able to configure connection to RTT service");
-            return;
-        }
-
-        mPendingOperations.put(rangingId, client);
-        RttManager.ParcelableRttParams pparams = new RttManager.ParcelableRttParams(params);
-        mAsyncChannel.sendMessage(RttManager.CMD_OP_START_RANGING, 0, rangingId, pparams);
-    }
-
-    private class AwareRttHandler extends Handler {
-        AwareRttHandler(Looper looper) {
-            super(looper);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            if (VDBG) Log.v(TAG, "handleMessage(): " + msg.what);
-
-            // channel configuration messages
-            switch (msg.what) {
-                case AsyncChannel.CMD_CHANNEL_HALF_CONNECTED:
-                    if (msg.arg1 == AsyncChannel.STATUS_SUCCESSFUL) {
-                        mAsyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION,
-                                new RttManager.RttClient(mContext.getPackageName()));
-                    } else {
-                        Log.e(TAG, "Failed to set up channel connection to RTT service");
-                        mAsyncChannel = null;
-                    }
-                    return;
-                case AsyncChannel.CMD_CHANNEL_FULLY_CONNECTED:
-                    /* NOP */
-                    return;
-                case AsyncChannel.CMD_CHANNEL_DISCONNECTED:
-                    Log.e(TAG, "Channel connection to RTT service lost");
-                    mAsyncChannel = null;
-                    return;
-            }
-
-            // RTT-specific messages
-            WifiAwareClientState client = getAndRemovePendingOperationClient(msg.arg2);
-            if (client == null) {
-                Log.e(TAG, "handleMessage(): RTT message (" + msg.what
-                        + ") -- cannot find registered pending operation client for ID "
-                        + msg.arg2);
-                return;
-            }
-
-            switch (msg.what) {
-                case RttManager.CMD_OP_SUCCEEDED: {
-                    int rangingId = msg.arg2;
-                    RttManager.ParcelableRttResults results = (RttManager.ParcelableRttResults)
-                            msg.obj;
-                    if (VDBG) {
-                        Log.v(TAG, "CMD_OP_SUCCEEDED: rangingId=" + rangingId + ", results="
-                                + results);
-                    }
-                    for (int i = 0; i < results.mResults.length; ++i) {
-                        /*
-                         * TODO: store peer ID rather than null in the return result.
-                         */
-                        results.mResults[i].bssid = null;
-                    }
-                    client.onRangingSuccess(rangingId, results);
-                    break;
-                }
-                case RttManager.CMD_OP_FAILED: {
-                    int rangingId = msg.arg2;
-                    int reason = msg.arg1;
-                    String description = ((Bundle) msg.obj).getString(RttManager.DESCRIPTION_KEY);
-                    if (VDBG) {
-                        Log.v(TAG, "CMD_OP_FAILED: rangingId=" + rangingId + ", reason=" + reason
-                                + ", description=" + description);
-                    }
-                    client.onRangingFailure(rangingId, reason, description);
-                    break;
-                }
-                case RttManager.CMD_OP_ABORTED: {
-                    int rangingId = msg.arg2;
-                    if (VDBG) {
-                        Log.v(TAG, "CMD_OP_ABORTED: rangingId=" + rangingId);
-                    }
-                    client.onRangingAborted(rangingId);
-                    break;
-                }
-                default:
-                    Log.e(TAG, "handleMessage(): ignoring message " + msg.what);
-                    break;
-            }
-        }
-    }
-
-    /**
-     * Dump the internal state of the class.
-     */
-    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        pw.println("WifiAwareRttStateManager:");
-        pw.println("  mPendingOperations: [" + mPendingOperations + "]");
-    }
-}
diff --git a/com/android/server/wifi/aware/WifiAwareServiceImpl.java b/com/android/server/wifi/aware/WifiAwareServiceImpl.java
index b77ae63..421d9ac 100644
--- a/com/android/server/wifi/aware/WifiAwareServiceImpl.java
+++ b/com/android/server/wifi/aware/WifiAwareServiceImpl.java
@@ -16,15 +16,16 @@
 
 package com.android.server.wifi.aware;
 
+import android.Manifest;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.hardware.wifi.V1_0.NanStatusType;
-import android.net.wifi.RttManager;
 import android.net.wifi.aware.Characteristics;
 import android.net.wifi.aware.ConfigRequest;
 import android.net.wifi.aware.DiscoverySession;
 import android.net.wifi.aware.IWifiAwareDiscoverySessionCallback;
 import android.net.wifi.aware.IWifiAwareEventCallback;
+import android.net.wifi.aware.IWifiAwareMacAddressProvider;
 import android.net.wifi.aware.IWifiAwareManager;
 import android.net.wifi.aware.PublishConfig;
 import android.net.wifi.aware.SubscribeConfig;
@@ -42,7 +43,7 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.util.Arrays;
+import java.util.List;
 
 /**
  * Implementation of the IWifiAwareManager AIDL interface. Performs validity
@@ -63,7 +64,6 @@
     private final SparseArray<IBinder.DeathRecipient> mDeathRecipientsByClientId =
             new SparseArray<>();
     private int mNextClientId = 1;
-    private int mNextRangingId = 1;
     private final SparseIntArray mUidByClientId = new SparseIntArray();
 
     public WifiAwareServiceImpl(Context context) {
@@ -134,7 +134,7 @@
         }
 
         if (configRequest != null) {
-            enforceConnectivityInternalPermission();
+            enforceNetworkStackPermission();
         } else {
             configRequest = new ConfigRequest.Builder().build();
         }
@@ -326,7 +326,7 @@
         enforceChangePermission();
 
         if (retryCount != 0) {
-            enforceConnectivityInternalPermission();
+            enforceNetworkStackPermission();
         }
 
         if (message != null
@@ -352,30 +352,10 @@
     }
 
     @Override
-    public int startRanging(int clientId, int sessionId, RttManager.ParcelableRttParams params) {
-        enforceAccessPermission();
-        enforceLocationPermission();
+    public void requestMacAddresses(int uid, List peerIds, IWifiAwareMacAddressProvider callback) {
+        enforceNetworkStackPermission();
 
-        // TODO: b/35676064 restricts access to this API until decide if will open.
-        enforceConnectivityInternalPermission();
-
-        int uid = getMockableCallingUid();
-        enforceClientValidity(uid, clientId);
-        if (VDBG) {
-            Log.v(TAG, "startRanging: clientId=" + clientId + ", sessionId=" + sessionId + ", "
-                    + ", parms=" + Arrays.toString(params.mParams));
-        }
-
-        if (params.mParams.length == 0) {
-            throw new IllegalArgumentException("Empty ranging parameters");
-        }
-
-        int rangingId;
-        synchronized (mLock) {
-            rangingId = mNextRangingId++;
-        }
-        mStateManager.startRanging(clientId, sessionId, params.mParams, rangingId);
-        return rangingId;
+        mStateManager.requestMacAddresses(uid, peerIds, callback);
     }
 
     @Override
@@ -424,8 +404,7 @@
                 TAG);
     }
 
-    private void enforceConnectivityInternalPermission() {
-        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CONNECTIVITY_INTERNAL,
-                TAG);
+    private void enforceNetworkStackPermission() {
+        mContext.enforceCallingOrSelfPermission(Manifest.permission.NETWORK_STACK, TAG);
     }
 }
diff --git a/com/android/server/wifi/aware/WifiAwareStateManager.java b/com/android/server/wifi/aware/WifiAwareStateManager.java
index 6ced948..31bdff8 100644
--- a/com/android/server/wifi/aware/WifiAwareStateManager.java
+++ b/com/android/server/wifi/aware/WifiAwareStateManager.java
@@ -21,11 +21,11 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.hardware.wifi.V1_0.NanStatusType;
-import android.net.wifi.RttManager;
 import android.net.wifi.aware.Characteristics;
 import android.net.wifi.aware.ConfigRequest;
 import android.net.wifi.aware.IWifiAwareDiscoverySessionCallback;
 import android.net.wifi.aware.IWifiAwareEventCallback;
+import android.net.wifi.aware.IWifiAwareMacAddressProvider;
 import android.net.wifi.aware.PublishConfig;
 import android.net.wifi.aware.SubscribeConfig;
 import android.net.wifi.aware.WifiAwareManager;
@@ -48,7 +48,6 @@
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
 import com.android.internal.util.WakeupMessage;
-import com.android.server.wifi.util.NativeUtil;
 import com.android.server.wifi.util.WifiPermissionsWrapper;
 
 import libcore.util.HexEncoding;
@@ -62,6 +61,7 @@
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.LinkedHashMap;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -108,7 +108,6 @@
     private static final int COMMAND_TYPE_ENQUEUE_SEND_MESSAGE = 107;
     private static final int COMMAND_TYPE_ENABLE_USAGE = 108;
     private static final int COMMAND_TYPE_DISABLE_USAGE = 109;
-    private static final int COMMAND_TYPE_START_RANGING = 110;
     private static final int COMMAND_TYPE_GET_CAPABILITIES = 111;
     private static final int COMMAND_TYPE_CREATE_ALL_DATA_PATH_INTERFACES = 112;
     private static final int COMMAND_TYPE_DELETE_ALL_DATA_PATH_INTERFACES = 113;
@@ -167,7 +166,6 @@
     private static final String MESSAGE_BUNDLE_KEY_MAC_ADDRESS = "mac_address";
     private static final String MESSAGE_BUNDLE_KEY_MESSAGE_DATA = "message_data";
     private static final String MESSAGE_BUNDLE_KEY_REQ_INSTANCE_ID = "req_instance_id";
-    private static final String MESSAGE_BUNDLE_KEY_RANGING_ID = "ranging_id";
     private static final String MESSAGE_BUNDLE_KEY_SEND_MESSAGE_ENQUEUE_TIME = "message_queue_time";
     private static final String MESSAGE_BUNDLE_KEY_RETRY_COUNT = "retry_count";
     private static final String MESSAGE_BUNDLE_KEY_SUCCESS_FLAG = "success_flag";
@@ -203,7 +201,6 @@
     private volatile Capabilities mCapabilities;
     private volatile Characteristics mCharacteristics = null;
     private WifiAwareStateMachine mSm;
-    private WifiAwareRttStateManager mRtt;
     public WifiAwareDataPathStateManager mDataPathMgr;
     private PowerManager mPowerManager;
 
@@ -348,7 +345,6 @@
         mSm.setDbg(DBG);
         mSm.start();
 
-        mRtt = new WifiAwareRttStateManager();
         mDataPathMgr = new WifiAwareDataPathStateManager(this);
         mDataPathMgr.start(mContext, mSm.getHandler().getLooper(), awareMetrics,
                 permissionsWrapper);
@@ -417,6 +413,48 @@
     }
 
     /*
+     * Cross-service API: synchronized but independent of state machine
+     */
+
+    /**
+     * Translate (and return in the callback) the peerId to its MAC address representation.
+     */
+    public void requestMacAddresses(int uid, List<Integer> peerIds,
+            IWifiAwareMacAddressProvider callback) {
+        mSm.getHandler().post(() -> {
+            if (VDBG) Log.v(TAG, "requestMacAddresses: uid=" + uid + ", peerIds=" + peerIds);
+            Map<Integer, byte[]> peerIdToMacMap = new HashMap<>();
+            for (int i = 0; i < mClients.size(); ++i) {
+                WifiAwareClientState client = mClients.valueAt(i);
+                if (client.getUid() != uid) {
+                    continue;
+                }
+
+                SparseArray<WifiAwareDiscoverySessionState> sessions = client.getSessions();
+                for (int j = 0; j < sessions.size(); ++j) {
+                    WifiAwareDiscoverySessionState session = sessions.valueAt(j);
+
+                    for (int peerId : peerIds) {
+                        WifiAwareDiscoverySessionState.PeerInfo peerInfo = session.getPeerInfo(
+                                peerId);
+                        if (peerInfo != null) {
+                            peerIdToMacMap.put(peerId, peerInfo.mMac);
+                        }
+                    }
+                }
+            }
+
+            try {
+                if (VDBG) Log.v(TAG, "requestMacAddresses: peerIdToMacMap=" + peerIdToMacMap);
+                callback.macAddress(peerIdToMacMap);
+            } catch (RemoteException e) {
+                Log.e(TAG, "requestMacAddress (sync): exception on callback -- " + e);
+
+            }
+        });
+    }
+
+    /*
      * COMMANDS
      */
 
@@ -553,20 +591,6 @@
     }
 
     /**
-     * Place a request to range a peer on the discovery session on the state machine queue.
-     */
-    public void startRanging(int clientId, int sessionId, RttManager.RttParams[] params,
-                             int rangingId) {
-        Message msg = mSm.obtainMessage(MESSAGE_TYPE_COMMAND);
-        msg.arg1 = COMMAND_TYPE_START_RANGING;
-        msg.arg2 = clientId;
-        msg.obj = params;
-        msg.getData().putInt(MESSAGE_BUNDLE_KEY_SESSION_ID, sessionId);
-        msg.getData().putInt(MESSAGE_BUNDLE_KEY_RANGING_ID, rangingId);
-        mSm.sendMessage(msg);
-    }
-
-    /**
      * Enable usage of Aware. Doesn't actually turn on Aware (form clusters) - that
      * only happens when a connection is created.
      */
@@ -1525,18 +1549,6 @@
                 case COMMAND_TYPE_DISABLE_USAGE:
                     waitForResponse = disableUsageLocal(mCurrentTransactionId);
                     break;
-                case COMMAND_TYPE_START_RANGING: {
-                    Bundle data = msg.getData();
-
-                    int clientId = msg.arg2;
-                    RttManager.RttParams[] params = (RttManager.RttParams[]) msg.obj;
-                    int sessionId = data.getInt(MESSAGE_BUNDLE_KEY_SESSION_ID);
-                    int rangingId = data.getInt(MESSAGE_BUNDLE_KEY_RANGING_ID);
-
-                    startRangingLocal(clientId, sessionId, params, rangingId);
-                    waitForResponse = false;
-                    break;
-                }
                 case COMMAND_TYPE_GET_CAPABILITIES:
                     if (mCapabilities == null) {
                         waitForResponse = mWifiAwareNativeApi.getCapabilities(
@@ -1614,7 +1626,6 @@
                     break;
                 case COMMAND_TYPE_DELAYED_INITIALIZATION:
                     mWifiAwareNativeManager.start();
-                    mRtt.start(mContext, mSm.getHandler().getLooper());
                     waitForResponse = false;
                     break;
                 default:
@@ -1827,9 +1838,6 @@
                 case COMMAND_TYPE_DISABLE_USAGE:
                     Log.wtf(TAG, "processTimeout: DISABLE_USAGE - shouldn't be waiting!");
                     break;
-                case COMMAND_TYPE_START_RANGING:
-                    Log.wtf(TAG, "processTimeout: START_RANGING - shouldn't be waiting!");
-                    break;
                 case COMMAND_TYPE_GET_CAPABILITIES:
                     Log.e(TAG,
                             "processTimeout: GET_CAPABILITIES timed-out - strange, will try again"
@@ -2308,49 +2316,6 @@
         return callDispatched;
     }
 
-    private void startRangingLocal(int clientId, int sessionId, RttManager.RttParams[] params,
-                                   int rangingId) {
-        if (VDBG) {
-            Log.v(TAG, "startRangingLocal: clientId=" + clientId + ", sessionId=" + sessionId
-                    + ", parms=" + Arrays.toString(params) + ", rangingId=" + rangingId);
-        }
-
-        WifiAwareClientState client = mClients.get(clientId);
-        if (client == null) {
-            Log.e(TAG, "startRangingLocal: no client exists for clientId=" + clientId);
-            return;
-        }
-
-        WifiAwareDiscoverySessionState session = client.getSession(sessionId);
-        if (session == null) {
-            Log.e(TAG, "startRangingLocal: no session exists for clientId=" + clientId
-                    + ", sessionId=" + sessionId);
-            client.onRangingFailure(rangingId, RttManager.REASON_INVALID_REQUEST,
-                    "Invalid session ID");
-            return;
-        }
-
-        for (RttManager.RttParams param : params) {
-            String peerIdStr = param.bssid;
-            try {
-                WifiAwareDiscoverySessionState.PeerInfo peerInfo = session.getPeerInfo(
-                        Integer.parseInt(peerIdStr));
-                if (peerInfo == null || peerInfo.mMac == null) {
-                    Log.d(TAG, "startRangingLocal: no MAC address for peer ID=" + peerIdStr);
-                    param.bssid = "";
-                } else {
-                    param.bssid = NativeUtil.macAddressFromByteArray(peerInfo.mMac);
-                }
-            } catch (NumberFormatException e) {
-                Log.e(TAG, "startRangingLocal: invalid peer ID specification (in bssid field): '"
-                        + peerIdStr + "'");
-                param.bssid = "";
-            }
-        }
-
-        mRtt.startRanging(rangingId, client, params);
-    }
-
     private boolean initiateDataPathSetupLocal(short transactionId,
             WifiAwareNetworkSpecifier networkSpecifier, int peerId, int channelRequestType,
             int channel, byte[] peer, String interfaceName, byte[] pmk, String passphrase,
@@ -3061,7 +3026,6 @@
         }
         pw.println("  mSettableParameters: " + mSettableParameters);
         mSm.dump(fd, pw, args);
-        mRtt.dump(fd, pw, args);
         mDataPathMgr.dump(fd, pw, args);
         mWifiAwareNativeApi.dump(fd, pw, args);
     }
diff --git a/com/android/server/wifi/rtt/RttNative.java b/com/android/server/wifi/rtt/RttNative.java
new file mode 100644
index 0000000..d30fe91
--- /dev/null
+++ b/com/android/server/wifi/rtt/RttNative.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi.rtt;
+
+import android.hardware.wifi.V1_0.IWifiRttController;
+import android.hardware.wifi.V1_0.IWifiRttControllerEventCallback;
+import android.hardware.wifi.V1_0.RttBw;
+import android.hardware.wifi.V1_0.RttConfig;
+import android.hardware.wifi.V1_0.RttPeerType;
+import android.hardware.wifi.V1_0.RttPreamble;
+import android.hardware.wifi.V1_0.RttResult;
+import android.hardware.wifi.V1_0.RttStatus;
+import android.hardware.wifi.V1_0.RttType;
+import android.hardware.wifi.V1_0.WifiChannelWidthInMhz;
+import android.hardware.wifi.V1_0.WifiStatus;
+import android.hardware.wifi.V1_0.WifiStatusCode;
+import android.net.wifi.ScanResult;
+import android.net.wifi.rtt.RangingRequest;
+import android.net.wifi.rtt.RangingResult;
+import android.net.wifi.rtt.RangingResultCallback;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.server.wifi.HalDeviceManager;
+import com.android.server.wifi.WifiNative;
+import com.android.server.wifi.WifiVendorHal;
+import com.android.server.wifi.util.NativeUtil;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * TBD
+ */
+public class RttNative extends IWifiRttControllerEventCallback.Stub {
+    private static final String TAG = "RttNative";
+    private static final boolean VDBG = true; // STOPSHIP if true
+
+    private final RttServiceImpl mRttService;
+    private final HalDeviceManager mHalDeviceManager;
+    private final WifiVendorHal mWifiVendorHal;
+
+    private boolean mIsInitialized = false;
+
+    public RttNative(RttServiceImpl rttService, HalDeviceManager halDeviceManager,
+            WifiNative wifiNative) {
+        mRttService = rttService;
+        mHalDeviceManager = halDeviceManager;
+        mWifiVendorHal = wifiNative.getVendorHal();
+    }
+
+    /**
+     * Issue a range request to the HAL.
+     *
+     * @param cmdId Command ID for the request. Will be used in the corresponding
+     * {@link #onResults(int, ArrayList)}.
+     * @param request Range request.
+     * @return Success status: true for success, false for failure.
+     */
+    public boolean rangeRequest(int cmdId, RangingRequest request) {
+        if (VDBG) Log.v(TAG, "rangeRequest: cmdId=" + cmdId + ", request=" + request);
+        // TODO: b/65014872 replace by direct access to HalDeviceManager
+        IWifiRttController rttController = mWifiVendorHal.getRttController();
+        if (rttController == null) {
+            Log.e(TAG, "rangeRequest: RttController is null");
+            return false;
+        }
+        if (!mIsInitialized) {
+            try {
+                WifiStatus status = rttController.registerEventCallback(this);
+                if (status.code != WifiStatusCode.SUCCESS) {
+                    Log.e(TAG,
+                            "rangeRequest: cannot register event callback -- code=" + status.code);
+                    return false;
+                }
+            } catch (RemoteException e) {
+                Log.e(TAG, "rangeRequest: exception registering callback: " + e);
+                return false;
+            }
+            mIsInitialized = true;
+        }
+
+        ArrayList<RttConfig> rttConfig = convertRangingRequestToRttConfigs(request);
+        if (rttConfig == null) {
+            Log.e(TAG, "rangeRequest: invalid request parameters");
+            return false;
+        }
+
+        try {
+            WifiStatus status = rttController.rangeRequest(cmdId, rttConfig);
+            if (status.code != WifiStatusCode.SUCCESS) {
+                Log.e(TAG, "rangeRequest: cannot issue range request -- code=" + status.code);
+                return false;
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "rangeRequest: exception issuing range request: " + e);
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Callback from HAL with range results.
+     *
+     * @param cmdId Command ID specified in the original request
+     * {@link #rangeRequest(int, RangingRequest)}.
+     * @param halResults A list of range results.
+     */
+    @Override
+    public void onResults(int cmdId, ArrayList<RttResult> halResults) {
+        if (VDBG) Log.v(TAG, "onResults: cmdId=" + cmdId + ", # of results=" + halResults.size());
+        List<RangingResult> results = new ArrayList<>(halResults.size());
+
+        for (RttResult halResult: halResults) {
+            results.add(new RangingResult(
+                    halResult.status == RttStatus.SUCCESS ? RangingResultCallback.STATUS_SUCCESS
+                            : RangingResultCallback.STATUS_FAIL, halResult.addr,
+                    halResult.distanceInMm / 10, halResult.distanceSdInMm / 10, halResult.rssi,
+                    halResult.timeStampInUs));
+        }
+
+        mRttService.onRangingResults(cmdId, results);
+    }
+
+    private static ArrayList<RttConfig> convertRangingRequestToRttConfigs(RangingRequest request) {
+        ArrayList<RttConfig> rttConfigs = new ArrayList<>(request.mRttPeers.size());
+
+        // Skipping any configurations which have an error (printing out a message).
+        // The caller will only get results for valid configurations.
+        for (RangingRequest.RttPeer peer: request.mRttPeers) {
+            if (peer instanceof RangingRequest.RttPeerAp) {
+                ScanResult scanResult = ((RangingRequest.RttPeerAp) peer).scanResult;
+                RttConfig config = new RttConfig();
+
+                byte[] addr = NativeUtil.macAddressToByteArray(scanResult.BSSID);
+                if (addr.length != config.addr.length) {
+                    Log.e(TAG, "Invalid configuration: unexpected BSSID length -- " + scanResult);
+                    continue;
+                }
+                for (int i = 0; i < config.addr.length; ++i) {
+                    config.addr[i] = addr[i];
+                }
+
+                try {
+                    config.type =
+                            scanResult.is80211mcResponder() ? RttType.TWO_SIDED : RttType.ONE_SIDED;
+                    config.peer = RttPeerType.AP;
+                    config.channel.width = halChannelWidthFromScanResult(
+                            scanResult.channelWidth);
+                    config.channel.centerFreq = scanResult.frequency;
+                    if (scanResult.centerFreq0 > 0) {
+                        config.channel.centerFreq0 = scanResult.centerFreq0;
+                    }
+                    if (scanResult.centerFreq1 > 0) {
+                        config.channel.centerFreq1 = scanResult.centerFreq1;
+                    }
+                    config.burstPeriod = 0;
+                    config.numBurst = 0;
+                    config.numFramesPerBurst = 8;
+                    config.numRetriesPerRttFrame = 0;
+                    config.numRetriesPerFtmr = 0;
+                    config.mustRequestLci = false;
+                    config.mustRequestLcr = false;
+                    config.burstDuration = 15;
+                    if (config.channel.centerFreq > 5000) {
+                        config.preamble = RttPreamble.VHT;
+                    } else {
+                        config.preamble = RttPreamble.HT;
+                    }
+                    config.bw = halChannelBandwidthFromScanResult(scanResult.channelWidth);
+                } catch (IllegalArgumentException e) {
+                    Log.e(TAG, "Invalid configuration: " + e.getMessage());
+                    continue;
+                }
+
+                rttConfigs.add(config);
+            } else {
+                Log.e(TAG, "convertRangingRequestToRttConfigs: unknown request type -- "
+                        + peer.getClass().getCanonicalName());
+                return null;
+            }
+        }
+
+
+        return rttConfigs;
+    }
+
+    static int halChannelWidthFromScanResult(int scanResultChannelWidth) {
+        switch (scanResultChannelWidth) {
+            case ScanResult.CHANNEL_WIDTH_20MHZ:
+                return WifiChannelWidthInMhz.WIDTH_20;
+            case ScanResult.CHANNEL_WIDTH_40MHZ:
+                return WifiChannelWidthInMhz.WIDTH_40;
+            case ScanResult.CHANNEL_WIDTH_80MHZ:
+                return WifiChannelWidthInMhz.WIDTH_80;
+            case ScanResult.CHANNEL_WIDTH_160MHZ:
+                return WifiChannelWidthInMhz.WIDTH_160;
+            case ScanResult.CHANNEL_WIDTH_80MHZ_PLUS_MHZ:
+                return WifiChannelWidthInMhz.WIDTH_80P80;
+            default:
+                throw new IllegalArgumentException(
+                        "halChannelWidthFromScanResult: bad " + scanResultChannelWidth);
+        }
+    }
+
+    static int halChannelBandwidthFromScanResult(int scanResultChannelWidth) {
+        switch (scanResultChannelWidth) {
+            case ScanResult.CHANNEL_WIDTH_20MHZ:
+                return RttBw.BW_20MHZ;
+            case ScanResult.CHANNEL_WIDTH_40MHZ:
+                return RttBw.BW_40MHZ;
+            case ScanResult.CHANNEL_WIDTH_80MHZ:
+                return RttBw.BW_80MHZ;
+            case ScanResult.CHANNEL_WIDTH_160MHZ:
+                return RttBw.BW_160MHZ;
+            case ScanResult.CHANNEL_WIDTH_80MHZ_PLUS_MHZ:
+                return RttBw.BW_160MHZ;
+            default:
+                throw new IllegalArgumentException(
+                        "halChannelBandwidthFromScanResult: bad " + scanResultChannelWidth);
+        }
+    }
+
+    /**
+     * Dump the internal state of the class.
+     */
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println("RttNative:");
+        pw.println("  mIsInitialized: " + mIsInitialized);
+    }
+}
diff --git a/com/android/server/wifi/rtt/RttService.java b/com/android/server/wifi/rtt/RttService.java
new file mode 100644
index 0000000..71e8a0a
--- /dev/null
+++ b/com/android/server/wifi/rtt/RttService.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi.rtt;
+
+import android.content.Context;
+import android.os.HandlerThread;
+import android.util.Log;
+
+import com.android.server.SystemService;
+import com.android.server.wifi.HalDeviceManager;
+import com.android.server.wifi.WifiInjector;
+import com.android.server.wifi.WifiNative;
+import com.android.server.wifi.util.WifiPermissionsUtil;
+
+/**
+ * TBD.
+ */
+public class RttService extends SystemService {
+    private static final String TAG = "RttService";
+    private RttServiceImpl mImpl;
+
+    public RttService(Context context) {
+        super(context);
+        mImpl = new RttServiceImpl(context);
+    }
+
+    @Override
+    public void onStart() {
+        Log.i(TAG, "Registering " + Context.WIFI_RTT2_SERVICE);
+        publishBinderService(Context.WIFI_RTT2_SERVICE, mImpl);
+    }
+
+    @Override
+    public void onBootPhase(int phase) {
+        if (phase == SystemService.PHASE_SYSTEM_SERVICES_READY) {
+            Log.i(TAG, "Starting " + Context.WIFI_RTT2_SERVICE);
+
+            WifiInjector wifiInjector = WifiInjector.getInstance();
+            if (wifiInjector == null) {
+                Log.e(TAG, "onBootPhase(PHASE_SYSTEM_SERVICES_READY): NULL injector!");
+                return;
+            }
+
+            HalDeviceManager halDeviceManager = wifiInjector.getHalDeviceManager();
+            HandlerThread handlerThread = wifiInjector.getRttHandlerThread();
+            WifiNative wifiNative = wifiInjector.getWifiNative();
+            WifiPermissionsUtil wifiPermissionsUtil = wifiInjector.getWifiPermissionsUtil();
+
+            RttNative rttNative = new RttNative(mImpl, halDeviceManager, wifiNative);
+            mImpl.start(handlerThread.getLooper(), rttNative, wifiPermissionsUtil);
+        }
+    }
+}
diff --git a/com/android/server/wifi/rtt/RttServiceImpl.java b/com/android/server/wifi/rtt/RttServiceImpl.java
new file mode 100644
index 0000000..d248768
--- /dev/null
+++ b/com/android/server/wifi/rtt/RttServiceImpl.java
@@ -0,0 +1,399 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi.rtt;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.net.wifi.rtt.IRttCallback;
+import android.net.wifi.rtt.IWifiRttManager;
+import android.net.wifi.rtt.RangingRequest;
+import android.net.wifi.rtt.RangingResult;
+import android.net.wifi.rtt.RangingResultCallback;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.android.server.wifi.util.NativeUtil;
+import com.android.server.wifi.util.WifiPermissionsUtil;
+
+import libcore.util.HexEncoding;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Set;
+
+/**
+ * Implementation of the IWifiRttManager AIDL interface and of the RttService state manager.
+ */
+public class RttServiceImpl extends IWifiRttManager.Stub {
+    private static final String TAG = "RttServiceImpl";
+    private static final boolean VDBG = true; // STOPSHIP if true
+
+    private final Context mContext;
+    private WifiPermissionsUtil mWifiPermissionsUtil;
+
+    private RttServiceSynchronized mRttServiceSynchronized;
+
+
+    public RttServiceImpl(Context context) {
+        mContext = context;
+    }
+
+    /*
+     * INITIALIZATION
+     */
+
+    /**
+     * Initializes the RTT service (usually with objects from an injector).
+     *
+     * @param looper The looper on which to synchronize operations.
+     * @param rttNative The Native interface to the HAL.
+     * @param wifiPermissionsUtil Utility for permission checks.
+     */
+    public void start(Looper looper, RttNative rttNative, WifiPermissionsUtil wifiPermissionsUtil) {
+        mWifiPermissionsUtil = wifiPermissionsUtil;
+        mRttServiceSynchronized = new RttServiceSynchronized(looper, rttNative);
+    }
+
+    /*
+     * ASYNCHRONOUS DOMAIN - can be called from different threads!
+     */
+
+    /**
+     * Proxy for the final native call of the parent class. Enables mocking of
+     * the function.
+     */
+    public int getMockableCallingUid() {
+        return getCallingUid();
+    }
+
+    /**
+     * Binder interface API to start a ranging operation. Called on binder thread, operations needs
+     * to be posted to handler thread.
+     */
+    @Override
+    public void startRanging(IBinder binder, String callingPackage, RangingRequest request,
+            IRttCallback callback) throws RemoteException {
+        if (VDBG) {
+            Log.v(TAG, "startRanging: binder=" + binder + ", callingPackage=" + callingPackage
+                    + ", request=" + request + ", callback=" + callback);
+        }
+        // verify arguments
+        if (binder == null) {
+            throw new IllegalArgumentException("Binder must not be null");
+        }
+        if (request == null || request.mRttPeers == null || request.mRttPeers.size() == 0) {
+            throw new IllegalArgumentException("Request must not be null or empty");
+        }
+        for (RangingRequest.RttPeer peer: request.mRttPeers) {
+            if (peer == null) {
+                throw new IllegalArgumentException(
+                        "Request must not contain empty peer specifications");
+            }
+            if (!(peer instanceof RangingRequest.RttPeerAp)) {
+                throw new IllegalArgumentException(
+                        "Request contains unknown peer specification types");
+            }
+        }
+        if (callback == null) {
+            throw new IllegalArgumentException("Callback must not be null");
+        }
+        request.enforceValidity();
+
+        final int uid = getMockableCallingUid();
+
+        // permission check
+        enforceAccessPermission();
+        enforceChangePermission();
+        enforceLocationPermission(callingPackage, uid);
+
+        // register for binder death
+        IBinder.DeathRecipient dr = new IBinder.DeathRecipient() {
+            @Override
+            public void binderDied() {
+                if (VDBG) Log.v(TAG, "binderDied: uid=" + uid);
+                binder.unlinkToDeath(this, 0);
+
+                mRttServiceSynchronized.mHandler.post(() -> {
+                    mRttServiceSynchronized.cleanUpOnClientDeath(uid);
+                });
+            }
+        };
+
+        try {
+            binder.linkToDeath(dr, 0);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error on linkToDeath - " + e);
+        }
+
+        mRttServiceSynchronized.mHandler.post(() -> {
+            mRttServiceSynchronized.queueRangingRequest(uid, binder, dr, callingPackage, request,
+                    callback);
+        });
+    }
+
+    /**
+     * Called by HAL to report ranging results. Called on HAL thread - needs to post to local
+     * thread.
+     */
+    public void onRangingResults(int cmdId, List<RangingResult> results) {
+        if (VDBG) Log.v(TAG, "onRangingResults: cmdId=" + cmdId);
+        mRttServiceSynchronized.mHandler.post(() -> {
+            mRttServiceSynchronized.onRangingResults(cmdId, results);
+        });
+    }
+
+    private void enforceAccessPermission() {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_WIFI_STATE, TAG);
+    }
+
+    private void enforceChangePermission() {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CHANGE_WIFI_STATE, TAG);
+    }
+
+    private void enforceLocationPermission(String callingPackage, int uid) {
+        mContext.enforceCallingOrSelfPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION,
+                TAG);
+    }
+
+    @Override
+    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        if (mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.DUMP) != PackageManager.PERMISSION_GRANTED) {
+            pw.println("Permission Denial: can't dump RttService from pid="
+                    + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
+            return;
+        }
+        pw.println("Wi-Fi RTT Service");
+        mRttServiceSynchronized.dump(fd, pw, args);
+    }
+
+    /*
+     * SYNCHRONIZED DOMAIN
+     */
+
+    /**
+     * RTT service implementation - synchronized on a single thread. All commands should be posted
+     * to the exposed handler.
+     */
+    private class RttServiceSynchronized {
+        public Handler mHandler;
+
+        private RttNative mRttNative;
+        private int mNextCommandId = 1000;
+        private List<RttRequestInfo> mRttRequestQueue = new LinkedList<>();
+
+        RttServiceSynchronized(Looper looper, RttNative rttNative) {
+            mRttNative = rttNative;
+
+            mHandler = new Handler(looper);
+        }
+
+        private void cleanUpOnClientDeath(int uid) {
+            if (VDBG) {
+                Log.v(TAG, "RttServiceSynchronized.cleanUpOnClientDeath: uid=" + uid
+                        + ", mRttRequestQueue=" + mRttRequestQueue);
+            }
+            ListIterator<RttRequestInfo> it = mRttRequestQueue.listIterator();
+            while (it.hasNext()) {
+                RttRequestInfo rri = it.next();
+                if (rri.uid == uid) {
+                    // TODO: actually abort operation - though API is not clear or clean
+                    if (rri.cmdId == 0) {
+                        // Until that happens we will get results for the last operation: which is
+                        // why we don't dispatch a new range request off the queue and keep the
+                        // currently running operation in the queue
+                        it.remove();
+                    }
+                }
+            }
+
+            if (VDBG) {
+                Log.v(TAG, "RttServiceSynchronized.cleanUpOnClientDeath: uid=" + uid
+                        + ", after cleanup - mRttRequestQueue=" + mRttRequestQueue);
+            }
+        }
+
+        private void queueRangingRequest(int uid, IBinder binder, IBinder.DeathRecipient dr,
+                String callingPackage, RangingRequest request, IRttCallback callback) {
+            RttRequestInfo newRequest = new RttRequestInfo();
+            newRequest.uid = uid;
+            newRequest.binder = binder;
+            newRequest.dr = dr;
+            newRequest.callingPackage = callingPackage;
+            newRequest.request = request;
+            newRequest.callback = callback;
+            mRttRequestQueue.add(newRequest);
+
+            if (VDBG) {
+                Log.v(TAG, "RttServiceSynchronized.queueRangingRequest: newRequest=" + newRequest);
+            }
+
+            executeNextRangingRequestIfPossible();
+        }
+
+        private void executeNextRangingRequestIfPossible() {
+            if (mRttRequestQueue.size() == 0) {
+                if (VDBG) Log.v(TAG, "executeNextRangingRequestIfPossible: no requests pending");
+                return;
+            }
+
+            RttRequestInfo nextRequest = mRttRequestQueue.get(0);
+            if (nextRequest.cmdId != 0) {
+                if (VDBG) {
+                    Log.v(TAG, "executeNextRangingRequestIfPossible: called but a command is "
+                            + "executing. topOfQueue=" + nextRequest);
+                }
+                return;
+            }
+
+            nextRequest.cmdId = mNextCommandId++;
+            startRanging(nextRequest);
+        }
+
+        private void startRanging(RttRequestInfo nextRequest) {
+            if (VDBG) {
+                Log.v(TAG, "RttServiceSynchronized.startRanging: nextRequest=" + nextRequest);
+            }
+
+            if (!mRttNative.rangeRequest(nextRequest.cmdId, nextRequest.request)) {
+                Log.w(TAG, "RttServiceSynchronized.startRanging: native rangeRequest call failed");
+                try {
+                    nextRequest.callback.onRangingResults(RangingResultCallback.STATUS_FAIL, null);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "RttServiceSynchronized.startRanging: HAL request failed, callback "
+                            + "failed -- " + e);
+                }
+
+                mRttRequestQueue.remove(0);
+                executeNextRangingRequestIfPossible();
+            }
+        }
+
+        private void onRangingResults(int cmdId, List<RangingResult> results) {
+            if (mRttRequestQueue.size() == 0) {
+                Log.e(TAG, "RttServiceSynchronized.onRangingResults: no current RTT request "
+                        + "pending!?");
+                return;
+            }
+            RttRequestInfo topOfQueueRequest = mRttRequestQueue.get(0);
+
+            if (VDBG) {
+                Log.v(TAG, "RttServiceSynchronized.onRangingResults: cmdId=" + cmdId
+                        + ", topOfQueueRequest=" + topOfQueueRequest + ", results="
+                        + Arrays.toString(results.toArray()));
+            }
+
+            if (topOfQueueRequest.cmdId != cmdId) {
+                Log.e(TAG, "RttServiceSynchronized.onRangingResults: cmdId=" + cmdId
+                        + ", does not match pending RTT request cmdId=" + topOfQueueRequest.cmdId);
+                return;
+            }
+
+            boolean permissionGranted = mWifiPermissionsUtil.checkCallersLocationPermission(
+                    topOfQueueRequest.callingPackage, topOfQueueRequest.uid);
+            try {
+                if (permissionGranted) {
+                    addMissingEntries(topOfQueueRequest.request, results);
+                    topOfQueueRequest.callback.onRangingResults(
+                            RangingResultCallback.STATUS_SUCCESS, results);
+                } else {
+                    Log.w(TAG, "RttServiceSynchronized.onRangingResults: location permission "
+                            + "revoked - not forwarding results");
+                    topOfQueueRequest.callback.onRangingResults(RangingResultCallback.STATUS_FAIL,
+                            null);
+                }
+            } catch (RemoteException e) {
+                Log.e(TAG,
+                        "RttServiceSynchronized.onRangingResults: callback exception -- " + e);
+            }
+
+            // clean-up binder death listener: the callback for results is a onetime event - now
+            // done with the binder.
+            topOfQueueRequest.binder.unlinkToDeath(topOfQueueRequest.dr, 0);
+
+            mRttRequestQueue.remove(0);
+            executeNextRangingRequestIfPossible();
+        }
+
+        /*
+         * Make sure the results contain an entry for each request. Add results with FAIL status
+         * if missing.
+         */
+        private void addMissingEntries(RangingRequest request,
+                List<RangingResult> results) {
+            Set<String> resultEntries = new HashSet<>(results.size());
+            for (RangingResult result: results) {
+                resultEntries.add(new String(HexEncoding.encode(result.getMacAddress())));
+            }
+
+            for (RangingRequest.RttPeer peer: request.mRttPeers) {
+                byte[] addr;
+                if (peer instanceof RangingRequest.RttPeerAp) {
+                    addr = NativeUtil.macAddressToByteArray(
+                            ((RangingRequest.RttPeerAp) peer).scanResult.BSSID);
+                } else {
+                    continue;
+                }
+                String canonicString = new String(HexEncoding.encode(addr));
+
+                if (!resultEntries.contains(canonicString)) {
+                    if (VDBG) {
+                        Log.v(TAG, "padRangingResultsWithMissingResults: missing=" + canonicString);
+                    }
+                    results.add(new RangingResult(RangingResultCallback.STATUS_FAIL, addr, 0, 0, 0,
+                            0));
+                }
+            }
+        }
+
+        // dump call (asynchronous most likely)
+        protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+            pw.println("  mNextCommandId: " + mNextCommandId);
+            pw.println("  mRttRequestQueue: " + mRttRequestQueue);
+            mRttNative.dump(fd, pw, args);
+        }
+    }
+
+    private static class RttRequestInfo {
+        public int uid;
+        public IBinder binder;
+        public IBinder.DeathRecipient dr;
+        public String callingPackage;
+        public RangingRequest request;
+        public byte[] mac;
+        public IRttCallback callback;
+
+        public int cmdId = 0; // uninitialized cmdId value
+
+        @Override
+        public String toString() {
+            return new StringBuilder("RttRequestInfo: uid=").append(uid).append(", binder=").append(
+                    binder).append(", dr=").append(dr).append(", callingPackage=").append(
+                    callingPackage).append(", request=").append(request.toString()).append(
+                    ", callback=").append(callback).append(", cmdId=").append(cmdId).toString();
+        }
+    }
+}
diff --git a/com/android/server/wifi/scanner/ChannelHelper.java b/com/android/server/wifi/scanner/ChannelHelper.java
index d87df07..6a01f0c 100644
--- a/com/android/server/wifi/scanner/ChannelHelper.java
+++ b/com/android/server/wifi/scanner/ChannelHelper.java
@@ -242,7 +242,7 @@
         if (scanSettings.band == WifiScanner.WIFI_BAND_UNSPECIFIED) {
             return toString(scanSettings.channels);
         } else {
-            return toString(scanSettings.band);
+            return bandToString(scanSettings.band);
         }
     }
 
@@ -255,7 +255,7 @@
         if (bucketSettings.band == WifiScanner.WIFI_BAND_UNSPECIFIED) {
             return toString(bucketSettings.channels, bucketSettings.num_channels);
         } else {
-            return toString(bucketSettings.band);
+            return bandToString(bucketSettings.band);
         }
     }
 
@@ -293,7 +293,10 @@
         return sb.toString();
     }
 
-    private static String toString(int band) {
+    /**
+     * Converts a WifiScanner.WIFI_BAND_* constant to a meaningful String
+     */
+    public static String bandToString(int band) {
         switch (band) {
             case WifiScanner.WIFI_BAND_UNSPECIFIED:
                 return "unspecified";
@@ -310,7 +313,6 @@
             case WifiScanner.WIFI_BAND_BOTH_WITH_DFS:
                 return "24Ghz & 5Ghz (DFS incl)";
         }
-
         return "invalid band";
     }
 }
diff --git a/com/android/server/wifi/scanner/HalWifiScannerImpl.java b/com/android/server/wifi/scanner/HalWifiScannerImpl.java
index 7d0ccba..890f72e 100644
--- a/com/android/server/wifi/scanner/HalWifiScannerImpl.java
+++ b/com/android/server/wifi/scanner/HalWifiScannerImpl.java
@@ -131,11 +131,6 @@
     }
 
     @Override
-    public boolean shouldScheduleBackgroundScanForHwPno() {
-        return mWificondScannerDelegate.shouldScheduleBackgroundScanForHwPno();
-    }
-
-    @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         mWificondScannerDelegate.dump(fd, pw, args);
     }
diff --git a/com/android/server/wifi/scanner/WifiScannerImpl.java b/com/android/server/wifi/scanner/WifiScannerImpl.java
index 5281b3a..dacb007 100644
--- a/com/android/server/wifi/scanner/WifiScannerImpl.java
+++ b/com/android/server/wifi/scanner/WifiScannerImpl.java
@@ -155,11 +155,5 @@
      */
     public abstract boolean isHwPnoSupported(boolean isConnectedPno);
 
-    /**
-     * This returns whether a background scan should be running for HW PNO scan or not.
-     * @return true if background scan needs to be started, false otherwise.
-     */
-    public abstract boolean shouldScheduleBackgroundScanForHwPno();
-
     protected abstract void dump(FileDescriptor fd, PrintWriter pw, String[] args);
 }
diff --git a/com/android/server/wifi/scanner/WifiScanningServiceImpl.java b/com/android/server/wifi/scanner/WifiScanningServiceImpl.java
index 4b8e284..02462f6 100644
--- a/com/android/server/wifi/scanner/WifiScanningServiceImpl.java
+++ b/com/android/server/wifi/scanner/WifiScanningServiceImpl.java
@@ -75,7 +75,6 @@
     private static final String TAG = WifiScanningService.TAG;
     private static final boolean DBG = false;
 
-    private static final int MIN_PERIOD_PER_CHANNEL_MS = 200;               // DFS needs 120 ms
     private static final int UNKNOWN_PID = -1;
 
     private final LocalLog mLocalLog = new LocalLog(512);
@@ -240,10 +239,6 @@
 
     private static final int CMD_SCAN_RESULTS_AVAILABLE              = BASE + 0;
     private static final int CMD_FULL_SCAN_RESULTS                   = BASE + 1;
-    private static final int CMD_HOTLIST_AP_FOUND                    = BASE + 2;
-    private static final int CMD_HOTLIST_AP_LOST                     = BASE + 3;
-    private static final int CMD_WIFI_CHANGE_DETECTED                = BASE + 4;
-    private static final int CMD_WIFI_CHANGE_TIMEOUT                 = BASE + 5;
     private static final int CMD_DRIVER_LOADED                       = BASE + 6;
     private static final int CMD_DRIVER_UNLOADED                     = BASE + 7;
     private static final int CMD_SCAN_PAUSED                         = BASE + 8;
@@ -1724,10 +1719,7 @@
             }
             logScanRequest("addHwPnoScanRequest", ci, handler, null, scanSettings, pnoSettings);
             addPnoScanRequest(ci, handler, scanSettings, pnoSettings);
-            // HW PNO is supported, check if we need a background scan running for this.
-            if (mScannerImpl.shouldScheduleBackgroundScanForHwPno()) {
-                addBackgroundScanRequest(scanSettings);
-            }
+
             return true;
         }
 
@@ -2213,7 +2205,7 @@
 
     static String describeTo(StringBuilder sb, ScanSettings scanSettings) {
         sb.append("ScanSettings { ")
-          .append(" band:").append(scanSettings.band)
+          .append(" band:").append(ChannelHelper.bandToString(scanSettings.band))
           .append(" period:").append(scanSettings.periodInMs)
           .append(" reportEvents:").append(scanSettings.reportEvents)
           .append(" numBssidsPerScan:").append(scanSettings.numBssidsPerScan)
diff --git a/com/android/server/wifi/scanner/WificondScannerImpl.java b/com/android/server/wifi/scanner/WificondScannerImpl.java
index 12a0bde..fb878e6 100644
--- a/com/android/server/wifi/scanner/WificondScannerImpl.java
+++ b/com/android/server/wifi/scanner/WificondScannerImpl.java
@@ -34,7 +34,6 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
-import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -50,7 +49,6 @@
     private static final String TAG = "WificondScannerImpl";
     private static final boolean DBG = false;
 
-    public static final String BACKGROUND_PERIOD_ALARM_TAG = TAG + " Background Scan Period";
     public static final String TIMEOUT_ALARM_TAG = TAG + " Scan Timeout";
     // Max number of networks that can be specified to wificond per scan request
     public static final int MAX_HIDDEN_NETWORK_IDS_PER_SCAN = 16;
@@ -69,20 +67,9 @@
     private final Object mSettingsLock = new Object();
 
     // Next scan settings to apply when the previous scan completes
-    private WifiNative.ScanSettings mPendingBackgroundScanSettings = null;
-    private WifiNative.ScanEventHandler mPendingBackgroundScanEventHandler = null;
     private WifiNative.ScanSettings mPendingSingleScanSettings = null;
     private WifiNative.ScanEventHandler mPendingSingleScanEventHandler = null;
 
-    // Active background scan settings/state
-    private WifiNative.ScanSettings mBackgroundScanSettings = null;
-    private WifiNative.ScanEventHandler mBackgroundScanEventHandler = null;
-    private int mNextBackgroundScanPeriod = 0;
-    private int mNextBackgroundScanId = 0;
-    private boolean mBackgroundScanPeriodPending = false;
-    private boolean mBackgroundScanPaused = false;
-    private ScanBuffer mBackgroundScanBuffer = new ScanBuffer(SCAN_BUFFER_CAPACITY);
-
     private ArrayList<ScanDetail> mNativeScanResults;
     private WifiScanner.ScanData mLatestSingleScanResult =
             new WifiScanner.ScanData(0, 0, new ScanResult[0]);
@@ -110,14 +97,6 @@
      */
     private static final long SCAN_TIMEOUT_MS = 15000;
 
-    AlarmManager.OnAlarmListener mScanPeriodListener = new AlarmManager.OnAlarmListener() {
-            public void onAlarm() {
-                synchronized (mSettingsLock) {
-                    handleScanPeriod();
-                }
-            }
-        };
-
     AlarmManager.OnAlarmListener mScanTimeoutListener = new AlarmManager.OnAlarmListener() {
             public void onAlarm() {
                 synchronized (mSettingsLock) {
@@ -161,7 +140,6 @@
             mPendingSingleScanSettings = null;
             mPendingSingleScanEventHandler = null;
             stopHwPnoScan();
-            stopBatchedScan();
             mLastScanSettings = null; // finally clear any active scan
         }
     }
@@ -210,111 +188,23 @@
     @Override
     public boolean startBatchedScan(WifiNative.ScanSettings settings,
             WifiNative.ScanEventHandler eventHandler) {
-        if (settings == null || eventHandler == null) {
-            Log.w(TAG, "Invalid arguments for startBatched: settings=" + settings
-                    + ",eventHandler=" + eventHandler);
-            return false;
-        }
-
-        if (settings.max_ap_per_scan < 0 || settings.max_ap_per_scan > MAX_APS_PER_SCAN) {
-            return false;
-        }
-        if (settings.num_buckets < 0 || settings.num_buckets > MAX_SCAN_BUCKETS) {
-            return false;
-        }
-        if (settings.report_threshold_num_scans < 0
-                || settings.report_threshold_num_scans > SCAN_BUFFER_CAPACITY) {
-            return false;
-        }
-        if (settings.report_threshold_percent < 0 || settings.report_threshold_percent > 100) {
-            return false;
-        }
-        if (settings.base_period_ms <= 0) {
-            return false;
-        }
-        for (int i = 0; i < settings.num_buckets; ++i) {
-            WifiNative.BucketSettings bucket = settings.buckets[i];
-            if (bucket.period_ms % settings.base_period_ms != 0) {
-                return false;
-            }
-        }
-
-        synchronized (mSettingsLock) {
-            stopBatchedScan();
-            if (DBG) {
-                Log.d(TAG, "Starting scan num_buckets=" + settings.num_buckets + ", base_period="
-                        + settings.base_period_ms + " ms");
-            }
-            mPendingBackgroundScanSettings = settings;
-            mPendingBackgroundScanEventHandler = eventHandler;
-            handleScanPeriod(); // Try to start scan immediately
-            return true;
-        }
+        Log.w(TAG, "startBatchedScan() is not supported");
+        return false;
     }
 
     @Override
     public void stopBatchedScan() {
-        synchronized (mSettingsLock) {
-            if (DBG) Log.d(TAG, "Stopping scan");
-            mBackgroundScanSettings = null;
-            mBackgroundScanEventHandler = null;
-            mPendingBackgroundScanSettings = null;
-            mPendingBackgroundScanEventHandler = null;
-            mBackgroundScanPaused = false;
-            mBackgroundScanPeriodPending = false;
-            unscheduleScansLocked();
-        }
-        processPendingScans();
+        Log.w(TAG, "stopBatchedScan() is not supported");
     }
 
     @Override
     public void pauseBatchedScan() {
-        synchronized (mSettingsLock) {
-            if (DBG) Log.d(TAG, "Pausing scan");
-            // if there isn't a pending scan then make the current scan pending
-            if (mPendingBackgroundScanSettings == null) {
-                mPendingBackgroundScanSettings = mBackgroundScanSettings;
-                mPendingBackgroundScanEventHandler = mBackgroundScanEventHandler;
-            }
-            mBackgroundScanSettings = null;
-            mBackgroundScanEventHandler = null;
-            mBackgroundScanPeriodPending = false;
-            mBackgroundScanPaused = true;
-
-            unscheduleScansLocked();
-
-            WifiScanner.ScanData[] results = getLatestBatchedScanResults(/* flush = */ true);
-            if (mPendingBackgroundScanEventHandler != null) {
-                mPendingBackgroundScanEventHandler.onScanPaused(results);
-            }
-        }
-        processPendingScans();
+        Log.w(TAG, "pauseBatchedScan() is not supported");
     }
 
     @Override
     public void restartBatchedScan() {
-        synchronized (mSettingsLock) {
-            if (DBG) Log.d(TAG, "Restarting scan");
-            if (mPendingBackgroundScanEventHandler != null) {
-                mPendingBackgroundScanEventHandler.onScanRestarted();
-            }
-            mBackgroundScanPaused = false;
-            handleScanPeriod();
-        }
-    }
-
-    private void unscheduleScansLocked() {
-        mAlarmManager.cancel(mScanPeriodListener);
-        if (mLastScanSettings != null) {
-            mLastScanSettings.backgroundScanActive = false;
-        }
-    }
-
-    private void handleScanPeriod() {
-        synchronized (mSettingsLock) {
-            mBackgroundScanPeriodPending = true;
-            processPendingScans();
-        }
+        Log.w(TAG, "restartBatchedScan() is not supported");
     }
 
     private void handleScanTimeout() {
@@ -342,56 +232,6 @@
             final LastScanSettings newScanSettings =
                     new LastScanSettings(mClock.getElapsedSinceBootMillis());
 
-            // Update scan settings if there is a pending scan
-            if (!mBackgroundScanPaused) {
-                if (mPendingBackgroundScanSettings != null) {
-                    mBackgroundScanSettings = mPendingBackgroundScanSettings;
-                    mBackgroundScanEventHandler = mPendingBackgroundScanEventHandler;
-                    mNextBackgroundScanPeriod = 0;
-                    mPendingBackgroundScanSettings = null;
-                    mPendingBackgroundScanEventHandler = null;
-                    mBackgroundScanPeriodPending = true;
-                }
-                if (mBackgroundScanPeriodPending && mBackgroundScanSettings != null) {
-                    int reportEvents = WifiScanner.REPORT_EVENT_NO_BATCH; // default to no batch
-                    for (int bucket_id = 0; bucket_id < mBackgroundScanSettings.num_buckets;
-                            ++bucket_id) {
-                        WifiNative.BucketSettings bucket =
-                                mBackgroundScanSettings.buckets[bucket_id];
-                        if (mNextBackgroundScanPeriod % (bucket.period_ms
-                                        / mBackgroundScanSettings.base_period_ms) == 0) {
-                            if ((bucket.report_events
-                                            & WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN) != 0) {
-                                reportEvents |= WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN;
-                            }
-                            if ((bucket.report_events
-                                            & WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT) != 0) {
-                                reportEvents |= WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT;
-                            }
-                            // only no batch if all buckets specify it
-                            if ((bucket.report_events
-                                            & WifiScanner.REPORT_EVENT_NO_BATCH) == 0) {
-                                reportEvents &= ~WifiScanner.REPORT_EVENT_NO_BATCH;
-                            }
-
-                            allFreqs.addChannels(bucket);
-                        }
-                    }
-                    if (!allFreqs.isEmpty()) {
-                        newScanSettings.setBackgroundScan(mNextBackgroundScanId++,
-                                mBackgroundScanSettings.max_ap_per_scan, reportEvents,
-                                mBackgroundScanSettings.report_threshold_num_scans,
-                                mBackgroundScanSettings.report_threshold_percent);
-                    }
-                    mNextBackgroundScanPeriod++;
-                    mBackgroundScanPeriodPending = false;
-                    mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP,
-                            mClock.getElapsedSinceBootMillis()
-                                    + mBackgroundScanSettings.base_period_ms,
-                            BACKGROUND_PERIOD_ALARM_TAG, mScanPeriodListener, mEventHandler);
-                }
-            }
-
             if (mPendingSingleScanSettings != null) {
                 boolean reportFullResults = false;
                 ChannelCollection singleScanFreqs = mChannelHelper.createChannelCollection();
@@ -422,16 +262,26 @@
                 mPendingSingleScanEventHandler = null;
             }
 
-            if ((newScanSettings.backgroundScanActive || newScanSettings.singleScanActive)
-                    && !allFreqs.isEmpty()) {
-                pauseHwPnoScan();
-                Set<Integer> freqs = allFreqs.getScanFreqs();
-                boolean success = mWifiNative.scan(freqs, hiddenNetworkSSIDSet);
+            if (newScanSettings.singleScanActive) {
+                boolean success = false;
+                Set<Integer> freqs;
+                if (!allFreqs.isEmpty()) {
+                    pauseHwPnoScan();
+                    freqs = allFreqs.getScanFreqs();
+                    success = mWifiNative.scan(freqs, hiddenNetworkSSIDSet);
+                    if (!success) {
+                        Log.e(TAG, "Failed to start scan, freqs=" + freqs);
+                    }
+                } else {
+                    // There is a scan request but no available channels could be scanned for.
+                    // We regard it as a scan failure in this case.
+                    Log.e(TAG, "Failed to start scan because there is "
+                            + "no available channel to scan for");
+                }
                 if (success) {
                     // TODO handle scan timeout
                     if (DBG) {
                         Log.d(TAG, "Starting wifi scan for freqs=" + freqs
-                                + ", background=" + newScanSettings.backgroundScanActive
                                 + ", single=" + newScanSettings.singleScanActive);
                     }
                     mLastScanSettings = newScanSettings;
@@ -439,7 +289,6 @@
                             mClock.getElapsedSinceBootMillis() + SCAN_TIMEOUT_MS,
                             TIMEOUT_ALARM_TAG, mScanTimeoutListener, mEventHandler);
                 } else {
-                    Log.e(TAG, "Failed to start scan, freqs=" + freqs);
                     // indicate scan failure async
                     mEventHandler.post(new Runnable() {
                             public void run() {
@@ -449,7 +298,6 @@
                                 }
                             }
                         });
-                    // TODO(b/27769665) background scans should be failed too if scans fail enough
                 }
             } else if (isHwPnoScanRequired()) {
                 newScanSettings.setHwPnoScan(mPnoSettings.networkList, mPnoEventHandler);
@@ -512,7 +360,6 @@
                     mLastScanSettings.singleScanEventHandler
                             .onScanStatus(WifiNative.WIFI_SCAN_FAILED);
                 }
-                // TODO(b/27769665) background scans should be failed too if scans fail enough
                 mLastScanSettings = null;
             }
         }
@@ -597,18 +444,13 @@
                 return;
             }
 
-            if (DBG) Log.d(TAG, "Polling scan data for scan: " + mLastScanSettings.scanId);
             mNativeScanResults = mWifiNative.getScanResults();
             List<ScanResult> singleScanResults = new ArrayList<>();
-            List<ScanResult> backgroundScanResults = new ArrayList<>();
             int numFilteredScanResults = 0;
             for (int i = 0; i < mNativeScanResults.size(); ++i) {
                 ScanResult result = mNativeScanResults.get(i).getScanResult();
                 long timestamp_ms = result.timestamp / 1000; // convert us -> ms
                 if (timestamp_ms > mLastScanSettings.startTime) {
-                    if (mLastScanSettings.backgroundScanActive) {
-                        backgroundScanResults.add(result);
-                    }
                     if (mLastScanSettings.singleScanActive
                             && mLastScanSettings.singleScanFreqs.containsChannel(
                                     result.frequency)) {
@@ -622,49 +464,6 @@
                 Log.d(TAG, "Filtering out " + numFilteredScanResults + " scan results.");
             }
 
-            if (mLastScanSettings.backgroundScanActive) {
-                if (mBackgroundScanEventHandler != null) {
-                    if ((mLastScanSettings.reportEvents
-                                    & WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT) != 0) {
-                        for (ScanResult scanResult : backgroundScanResults) {
-                            // TODO(b/27506257): Fill in correct bucketsScanned value
-                            mBackgroundScanEventHandler.onFullScanResult(scanResult, 0);
-                        }
-                    }
-                }
-
-                Collections.sort(backgroundScanResults, SCAN_RESULT_SORT_COMPARATOR);
-                ScanResult[] scanResultsArray = new ScanResult[Math.min(mLastScanSettings.maxAps,
-                            backgroundScanResults.size())];
-                for (int i = 0; i < scanResultsArray.length; ++i) {
-                    scanResultsArray[i] = backgroundScanResults.get(i);
-                }
-
-                if ((mLastScanSettings.reportEvents & WifiScanner.REPORT_EVENT_NO_BATCH) == 0) {
-                    // TODO(b/27506257): Fill in correct bucketsScanned value
-                    mBackgroundScanBuffer.add(new WifiScanner.ScanData(mLastScanSettings.scanId, 0,
-                                    scanResultsArray));
-                }
-
-                if (mBackgroundScanEventHandler != null) {
-                    if ((mLastScanSettings.reportEvents
-                                    & WifiScanner.REPORT_EVENT_FULL_SCAN_RESULT) != 0
-                            || (mLastScanSettings.reportEvents
-                                    & WifiScanner.REPORT_EVENT_AFTER_EACH_SCAN) != 0
-                            || (mLastScanSettings.reportEvents
-                                    == WifiScanner.REPORT_EVENT_AFTER_BUFFER_FULL
-                                    && (mBackgroundScanBuffer.size()
-                                            >= (mBackgroundScanBuffer.capacity()
-                                                    * mLastScanSettings.reportPercentThreshold
-                                                    / 100)
-                                            || mBackgroundScanBuffer.size()
-                                            >= mLastScanSettings.reportNumScansThreshold))) {
-                        mBackgroundScanEventHandler
-                                .onScanStatus(WifiNative.WIFI_SCAN_RESULTS_AVAILABLE);
-                    }
-                }
-            }
-
             if (mLastScanSettings.singleScanActive
                     && mLastScanSettings.singleScanEventHandler != null) {
                 if (mLastScanSettings.reportSingleScanFullResults) {
@@ -675,7 +474,7 @@
                     }
                 }
                 Collections.sort(singleScanResults, SCAN_RESULT_SORT_COMPARATOR);
-                mLatestSingleScanResult = new WifiScanner.ScanData(mLastScanSettings.scanId, 0, 0,
+                mLatestSingleScanResult = new WifiScanner.ScanData(0, 0, 0,
                         isAllChannelsScanned(mLastScanSettings.singleScanFreqs),
                         singleScanResults.toArray(new ScanResult[singleScanResults.size()]));
                 mLastScanSettings.singleScanEventHandler
@@ -689,13 +488,7 @@
 
     @Override
     public WifiScanner.ScanData[] getLatestBatchedScanResults(boolean flush) {
-        synchronized (mSettingsLock) {
-            WifiScanner.ScanData[] results = mBackgroundScanBuffer.get();
-            if (flush) {
-                mBackgroundScanBuffer.clear();
-            }
-            return results;
-        }
+        return null;
     }
 
     private boolean startHwPnoScan(WifiNative.PnoSettings pnoSettings) {
@@ -768,11 +561,6 @@
     }
 
     @Override
-    public boolean shouldScheduleBackgroundScanForHwPno() {
-        return false;
-    }
-
-    @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         synchronized (mSettingsLock) {
             pw.println("Latest native scan results:");
@@ -813,24 +601,6 @@
             this.startTime = startTime;
         }
 
-        // Background settings
-        public boolean backgroundScanActive = false;
-        public int scanId;
-        public int maxAps;
-        public int reportEvents;
-        public int reportNumScansThreshold;
-        public int reportPercentThreshold;
-
-        public void setBackgroundScan(int scanId, int maxAps, int reportEvents,
-                int reportNumScansThreshold, int reportPercentThreshold) {
-            this.backgroundScanActive = true;
-            this.scanId = scanId;
-            this.maxAps = maxAps;
-            this.reportEvents = reportEvents;
-            this.reportNumScansThreshold = reportNumScansThreshold;
-            this.reportPercentThreshold = reportPercentThreshold;
-        }
-
         // Single scan settings
         public boolean singleScanActive = false;
         public boolean reportSingleScanFullResults;
@@ -859,44 +629,6 @@
         }
     }
 
-
-    private static class ScanBuffer {
-        private final ArrayDeque<WifiScanner.ScanData> mBuffer;
-        private int mCapacity;
-
-        ScanBuffer(int capacity) {
-            mCapacity = capacity;
-            mBuffer = new ArrayDeque<>(mCapacity);
-        }
-
-        public int size() {
-            return mBuffer.size();
-        }
-
-        public int capacity() {
-            return mCapacity;
-        }
-
-        public boolean isFull() {
-            return size() == mCapacity;
-        }
-
-        public void add(WifiScanner.ScanData scanData) {
-            if (isFull()) {
-                mBuffer.pollFirst();
-            }
-            mBuffer.offerLast(scanData);
-        }
-
-        public void clear() {
-            mBuffer.clear();
-        }
-
-        public WifiScanner.ScanData[] get() {
-            return mBuffer.toArray(new WifiScanner.ScanData[mBuffer.size()]);
-        }
-    }
-
     /**
      * HW PNO Debouncer is used to debounce PNO requests. This guards against toggling the PNO
      * state too often which is not handled very well by some drivers.
diff --git a/com/android/server/wifi/util/KalmanFilter.java b/com/android/server/wifi/util/KalmanFilter.java
new file mode 100644
index 0000000..b961ed8
--- /dev/null
+++ b/com/android/server/wifi/util/KalmanFilter.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wifi.util;
+
+/**
+ * Utility providiing a basic Kalman filter
+ *
+ * For background, see https://en.wikipedia.org/wiki/Kalman_filter
+ */
+public class KalmanFilter {
+    public Matrix mF; // stateTransition
+    public Matrix mQ; // processNoiseCovariance
+    public Matrix mH; // observationModel
+    public Matrix mR; // observationNoiseCovariance
+    public Matrix mP; // aPosterioriErrorCovariance
+    public Matrix mx; // stateEstimate
+
+    /**
+     * Performs the prediction phase of the filter, using the state estimate to produce
+     * a new estimate for the current timestep.
+     */
+    public void predict() {
+        mx = mF.dot(mx);
+        mP = mF.dot(mP).dotTranspose(mF).plus(mQ);
+    }
+
+    /**
+     * Updates the state estimate to incorporate the new observation z.
+     */
+    public void update(Matrix z) {
+        Matrix y = z.minus(mH.dot(mx));
+        Matrix tS = mH.dot(mP).dotTranspose(mH).plus(mR);
+        Matrix tK = mP.dotTranspose(mH).dot(tS.inverse());
+        mx = mx.plus(tK.dot(y));
+        mP = mP.minus(tK.dot(mH).dot(mP));
+    }
+
+    @Override
+    public String toString() {
+        return "{F: " + mF
+                + " Q: " + mQ
+                + " H: " + mH
+                + " R: " + mR
+                + " P: " + mP
+                + " x: " + mx
+                + "}";
+    }
+}
diff --git a/com/android/server/wm/AppWindowAnimator.java b/com/android/server/wm/AppWindowAnimator.java
index c76b905..5365e27 100644
--- a/com/android/server/wm/AppWindowAnimator.java
+++ b/com/android/server/wm/AppWindowAnimator.java
@@ -16,10 +16,10 @@
 
 package com.android.server.wm;
 
-import static android.view.Display.INVALID_DISPLAY;
 import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
 import static com.android.server.wm.AppTransition.TRANSIT_UNSET;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYERS;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_VISIBILITY;
 import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -78,11 +78,8 @@
     // requires that the duration of the two animations are the same.
     SurfaceControl thumbnail;
     int thumbnailTransactionSeq;
-    // TODO(b/62029108): combine both members into a private one. Create a member function to set
-    // the thumbnail layer to +1 to the highest layer position and replace all setter instances
-    // with this function. Remove all unnecessary calls to both variables in other classes.
-    int thumbnailLayer;
-    int thumbnailForceAboveLayer;
+    private int mThumbnailLayer;
+
     Animation thumbnailAnimation;
     final Transformation thumbnailTransformation = new Transformation();
     // This flag indicates that the destruction of the thumbnail surface is synchronized with
@@ -256,7 +253,7 @@
 
     private void updateLayers() {
         mAppToken.getDisplayContent().assignWindowLayers(false /* relayoutNeeded */);
-        thumbnailLayer = mAppToken.getHighestAnimLayer();
+        updateThumbnailLayer();
     }
 
     private void stepThumbnailAnimation(long currentTime) {
@@ -280,26 +277,35 @@
         thumbnail.setPosition(tmpFloats[Matrix.MTRANS_X], tmpFloats[Matrix.MTRANS_Y]);
         if (SHOW_TRANSACTIONS) WindowManagerService.logSurface(thumbnail,
                 "thumbnail", "alpha=" + thumbnailTransformation.getAlpha()
-                + " layer=" + thumbnailLayer
+                + " layer=" + mThumbnailLayer
                 + " matrix=[" + tmpFloats[Matrix.MSCALE_X]
                 + "," + tmpFloats[Matrix.MSKEW_Y]
                 + "][" + tmpFloats[Matrix.MSKEW_X]
                 + "," + tmpFloats[Matrix.MSCALE_Y] + "]");
         thumbnail.setAlpha(thumbnailTransformation.getAlpha());
-        if (thumbnailForceAboveLayer > 0) {
-            thumbnail.setLayer(thumbnailForceAboveLayer + 1);
-        } else {
-            // The thumbnail is layered below the window immediately above this
-            // token's anim layer.
-            thumbnail.setLayer(thumbnailLayer + WindowManagerService.WINDOW_LAYER_MULTIPLIER
-                    - WindowManagerService.LAYER_OFFSET_THUMBNAIL);
-        }
+        updateThumbnailLayer();
         thumbnail.setMatrix(tmpFloats[Matrix.MSCALE_X], tmpFloats[Matrix.MSKEW_Y],
                 tmpFloats[Matrix.MSKEW_X], tmpFloats[Matrix.MSCALE_Y]);
         thumbnail.setWindowCrop(thumbnailTransformation.getClipRect());
     }
 
     /**
+     * Updates the thumbnail layer z order to just above the highest animation layer if changed
+     */
+    void updateThumbnailLayer() {
+        if (thumbnail != null) {
+            final int layer = mAppToken.getHighestAnimLayer();
+            if (layer != mThumbnailLayer) {
+                if (DEBUG_LAYERS) Slog.v(TAG,
+                        "Setting thumbnail layer " + mAppToken + ": layer=" + layer);
+                thumbnail.setLayer(layer + WindowManagerService.WINDOW_LAYER_MULTIPLIER
+                        - WindowManagerService.LAYER_OFFSET_THUMBNAIL);
+                mThumbnailLayer = layer;
+            }
+        }
+    }
+
+    /**
      * Sometimes we need to synchronize the first frame of animation with some external event, e.g.
      * Recents hiding some of its content. To achieve this, we prolong the start of the animaiton
      * and keep producing the first frame of the animation.
@@ -473,7 +479,7 @@
         }
         if (thumbnail != null) {
             pw.print(prefix); pw.print("thumbnail="); pw.print(thumbnail);
-                    pw.print(" layer="); pw.println(thumbnailLayer);
+                    pw.print(" layer="); pw.println(mThumbnailLayer);
             pw.print(prefix); pw.print("thumbnailAnimation="); pw.println(thumbnailAnimation);
             pw.print(prefix); pw.print("thumbnailTransformation=");
                     pw.println(thumbnailTransformation.toShortString());
diff --git a/com/android/server/wm/AppWindowToken.java b/com/android/server/wm/AppWindowToken.java
index ea2f305..a1eeff8 100644
--- a/com/android/server/wm/AppWindowToken.java
+++ b/com/android/server/wm/AppWindowToken.java
@@ -16,8 +16,7 @@
 
 package com.android.server.wm;
 
-import static android.app.ActivityManager.StackId;
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.content.pm.ActivityInfo.CONFIG_ORIENTATION;
 import static android.content.pm.ActivityInfo.CONFIG_SCREEN_SIZE;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
@@ -53,6 +52,7 @@
 import static com.android.server.wm.proto.AppWindowTokenProto.NAME;
 import static com.android.server.wm.proto.AppWindowTokenProto.WINDOW_TOKEN;
 
+import android.annotation.CallSuper;
 import android.annotation.NonNull;
 import android.app.Activity;
 import android.content.res.Configuration;
@@ -1170,7 +1170,6 @@
                 wAppAnimator.thumbnail.destroy();
             }
             wAppAnimator.thumbnail = tAppAnimator.thumbnail;
-            wAppAnimator.thumbnailLayer = tAppAnimator.thumbnailLayer;
             wAppAnimator.thumbnailAnimation = tAppAnimator.thumbnailAnimation;
             tAppAnimator.thumbnail = null;
         }
@@ -1311,7 +1310,8 @@
 
                 // Notify the pinned stack upon all windows drawn. If there was an animation in
                 // progress then this signal will resume that animation.
-                final TaskStack pinnedStack = mDisplayContent.getStackById(PINNED_STACK_ID);
+                final TaskStack pinnedStack =
+                        mDisplayContent.getStack(WINDOWING_MODE_PINNED);
                 if (pinnedStack != null) {
                     pinnedStack.onAllWindowsDrawn();
                 }
@@ -1620,8 +1620,9 @@
         }
     }
 
+    @CallSuper
     @Override
-    void writeToProto(ProtoOutputStream proto, long fieldId) {
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
         final long token = proto.start(fieldId);
         writeNameToProto(proto, NAME);
         super.writeToProto(proto, WINDOW_TOKEN);
diff --git a/com/android/server/wm/ConfigurationContainer.java b/com/android/server/wm/ConfigurationContainer.java
index 28ba9b3..9e028d3 100644
--- a/com/android/server/wm/ConfigurationContainer.java
+++ b/com/android/server/wm/ConfigurationContainer.java
@@ -21,12 +21,19 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.app.WindowConfiguration.activityTypeToString;
+import static com.android.server.wm.proto.ConfigurationContainerProto.FULL_CONFIGURATION;
+import static com.android.server.wm.proto.ConfigurationContainerProto.MERGED_OVERRIDE_CONFIGURATION;
+import static com.android.server.wm.proto.ConfigurationContainerProto.OVERRIDE_CONFIGURATION;
 
+import android.annotation.CallSuper;
 import android.app.WindowConfiguration;
 import android.content.res.Configuration;
+import android.util.proto.ProtoOutputStream;
 
 import java.util.ArrayList;
 
@@ -147,6 +154,17 @@
         onOverrideConfigurationChanged(mTmpConfig);
     }
 
+    /**
+     * Returns true if this container is currently in multi-window mode. I.e. sharing the screen
+     * with another activity.
+     */
+    public boolean inMultiWindowMode() {
+        /*@WindowConfiguration.WindowingMode*/ int windowingMode =
+                mFullConfiguration.windowConfiguration.getWindowingMode();
+        return windowingMode != WINDOWING_MODE_FULLSCREEN
+                && windowingMode != WINDOWING_MODE_UNDEFINED;
+    }
+
     /** Returns true if this container is currently in split-screen windowing mode. */
     public boolean inSplitScreenWindowingMode() {
         /*@WindowConfiguration.WindowingMode*/ int windowingMode =
@@ -170,7 +188,7 @@
      * {@link WindowConfiguration##WINDOWING_MODE_SPLIT_SCREEN_SECONDARY} windowing modes based on
      * its current state.
      */
-    public boolean supportSplitScreenWindowingMode() {
+    public boolean supportsSplitScreenWindowingMode() {
         return mFullConfiguration.windowConfiguration.supportSplitScreenWindowingMode();
     }
 
@@ -220,9 +238,48 @@
         /*@WindowConfiguration.ActivityType*/ int thisType = getActivityType();
         /*@WindowConfiguration.ActivityType*/ int otherType = other.getActivityType();
 
-        return thisType == otherType
-                || thisType == ACTIVITY_TYPE_UNDEFINED
-                || otherType == ACTIVITY_TYPE_UNDEFINED;
+        if (thisType == otherType) {
+            return true;
+        }
+        if (thisType == ACTIVITY_TYPE_ASSISTANT) {
+            // Assistant activities are only compatible with themselves...
+            return false;
+        }
+        // Otherwise we are compatible if us or other is not currently defined.
+        return thisType == ACTIVITY_TYPE_UNDEFINED || otherType == ACTIVITY_TYPE_UNDEFINED;
+    }
+
+    /**
+     * Returns true if this container is compatible with the input windowing mode and activity type.
+     * The container is compatible:
+     * - If {@param activityType} and {@param windowingMode} match this container activity type and
+     * windowing mode.
+     * - If {@param activityType} is {@link WindowConfiguration#ACTIVITY_TYPE_UNDEFINED} or
+     * {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD} and this containers activity type is also
+     * standard or undefined and its windowing mode matches {@param windowingMode}.
+     * - If {@param activityType} isn't {@link WindowConfiguration#ACTIVITY_TYPE_UNDEFINED} or
+     * {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD} or this containers activity type isn't
+     * also standard or undefined and its activity type matches {@param activityType} regardless of
+     * if {@param windowingMode} matches the containers windowing mode.
+     */
+    public boolean isCompatible(int windowingMode, int activityType) {
+        final int thisActivityType = getActivityType();
+        final int thisWindowingMode = getWindowingMode();
+        final boolean sameActivityType = thisActivityType == activityType;
+        final boolean sameWindowingMode = thisWindowingMode == windowingMode;
+
+        if (sameActivityType && sameWindowingMode) {
+            return true;
+        }
+
+        if ((activityType != ACTIVITY_TYPE_UNDEFINED && activityType != ACTIVITY_TYPE_STANDARD)
+                || !isActivityTypeStandardOrUndefined()) {
+            // Only activity type need to match for non-standard activity types that are defined.
+            return sameActivityType;
+        }
+
+        // Otherwise we are compatible if the windowing mode is the same.
+        return sameWindowingMode;
     }
 
     public void registerConfigurationChangeListener(ConfigurationContainerListener listener) {
@@ -252,6 +309,24 @@
         }
     }
 
+    /**
+     * Write to a protocol buffer output stream. Protocol buffer message definition is at
+     * {@link com.android.server.wm.proto.ConfigurationContainerProto}.
+     *
+     * @param protoOutputStream Stream to write the ConfigurationContainer object to.
+     * @param fieldId           Field Id of the ConfigurationContainer as defined in the parent
+     *                          message.
+     * @hide
+     */
+    @CallSuper
+    public void writeToProto(ProtoOutputStream protoOutputStream, long fieldId) {
+        final long token = protoOutputStream.start(fieldId);
+        mOverrideConfiguration.writeToProto(protoOutputStream, OVERRIDE_CONFIGURATION);
+        mFullConfiguration.writeToProto(protoOutputStream, FULL_CONFIGURATION);
+        mMergedOverrideConfiguration.writeToProto(protoOutputStream, MERGED_OVERRIDE_CONFIGURATION);
+        protoOutputStream.end(token);
+    }
+
     abstract protected int getChildCount();
 
     abstract protected E getChildAt(int index);
diff --git a/com/android/server/wm/DisplayContent.java b/com/android/server/wm/DisplayContent.java
index 6cf608a..0e68a8f 100644
--- a/com/android/server/wm/DisplayContent.java
+++ b/com/android/server/wm/DisplayContent.java
@@ -18,9 +18,11 @@
 
 import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
 import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.HOME_STACK_ID;
 import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
@@ -112,10 +114,11 @@
 import static com.android.server.wm.proto.DisplayProto.ROTATION;
 import static com.android.server.wm.proto.DisplayProto.SCREEN_ROTATION_ANIMATION;
 import static com.android.server.wm.proto.DisplayProto.STACKS;
+import static com.android.server.wm.proto.DisplayProto.WINDOW_CONTAINER;
 
+import android.annotation.CallSuper;
 import android.annotation.NonNull;
 import android.app.ActivityManager.StackId;
-import android.app.WindowConfiguration;
 import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
 import android.graphics.Bitmap;
@@ -389,10 +392,6 @@
                     != mTmpWindowAnimator.mAnimTransactionSequence) {
                 appAnimator.thumbnailTransactionSeq =
                         mTmpWindowAnimator.mAnimTransactionSequence;
-                appAnimator.thumbnailLayer = 0;
-            }
-            if (appAnimator.thumbnailLayer < winAnimator.mAnimLayer) {
-                appAnimator.thumbnailLayer = winAnimator.mAnimLayer;
             }
         }
     };
@@ -966,10 +965,10 @@
         final int lastOrientation = mLastOrientation;
         final boolean oldAltOrientation = mAltOrientation;
         int rotation = mService.mPolicy.rotationForOrientationLw(lastOrientation, oldRotation);
-        final boolean rotateSeamlessly = mService.mPolicy.shouldRotateSeamlessly(oldRotation,
+        boolean mayRotateSeamlessly = mService.mPolicy.shouldRotateSeamlessly(oldRotation,
                 rotation);
 
-        if (rotateSeamlessly) {
+        if (mayRotateSeamlessly) {
             final WindowState seamlessRotated = getWindow((w) -> w.mSeamlesslyRotated);
             if (seamlessRotated != null) {
                 // We can't rotate (seamlessly or not) while waiting for the last seamless rotation
@@ -978,7 +977,20 @@
                 // window-removal.
                 return false;
             }
+
+            // In the presence of the PINNED stack or System Alert
+            // windows we unforuntately can not seamlessly rotate.
+            if (getStackById(PINNED_STACK_ID) != null) {
+                mayRotateSeamlessly = false;
+            }
+            for (int i = 0; i < mService.mSessions.size(); i++) {
+                if (mService.mSessions.valueAt(i).hasAlertWindowSurfaces()) {
+                    mayRotateSeamlessly = false;
+                    break;
+                }
+            }
         }
+        final boolean rotateSeamlessly = mayRotateSeamlessly;
 
         // TODO: Implement forced rotation changes.
         //       Set mAltOrientation to indicate that the application is receiving
@@ -1455,16 +1467,38 @@
         return null;
     }
 
+    /**
+     * Returns the topmost stack on the display that is compatible with the input windowing mode.
+     * Null is no compatible stack on the display.
+     */
+    TaskStack getStack(int windowingMode) {
+        return getStack(windowingMode, ACTIVITY_TYPE_UNDEFINED);
+    }
+
+    /**
+     * Returns the topmost stack on the display that is compatible with the input windowing mode and
+     * activity type. Null is no compatible stack on the display.
+     */
+    TaskStack getStack(int windowingMode, int activityType) {
+        for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) {
+            final TaskStack stack = mTaskStackContainers.get(i);
+            if (stack.isCompatible(windowingMode, activityType)) {
+                return stack;
+            }
+        }
+        return null;
+    }
+
     @VisibleForTesting
     int getStackCount() {
         return mTaskStackContainers.size();
     }
 
     @VisibleForTesting
-    int getStackPosById(int stackId) {
+    int getStackPosition(int windowingMode, int activityType) {
         for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) {
             final TaskStack stack = mTaskStackContainers.get(i);
-            if (stack.mStackId == stackId) {
+            if (stack.isCompatible(windowingMode, activityType)) {
                 return i;
             }
         }
@@ -1499,7 +1533,7 @@
         // If there was no pinned stack, we still need to notify the controller of the display info
         // update as a result of the config change.  We do this here to consolidate the flow between
         // changes when there is and is not a stack.
-        if (getStackById(PINNED_STACK_ID) == null) {
+        if (getStack(WINDOWING_MODE_PINNED) == null) {
             mPinnedStackControllerLocked.onDisplayInfoChanged();
         }
     }
@@ -1720,21 +1754,22 @@
         out.set(mContentRect);
     }
 
-    TaskStack addStackToDisplay(int stackId, boolean onTop) {
+    TaskStack addStackToDisplay(int stackId, boolean onTop, StackWindowController controller) {
         if (DEBUG_STACK) Slog.d(TAG_WM, "Create new stackId=" + stackId + " on displayId="
                 + mDisplayId);
 
         TaskStack stack = getStackById(stackId);
         if (stack != null) {
-            // It's already attached to the display...clear mDeferRemoval and move stack to
-            // appropriate z-order on display as needed.
+            // It's already attached to the display...clear mDeferRemoval, set controller, and move
+            // stack to appropriate z-order on display as needed.
             stack.mDeferRemoval = false;
+            stack.setController(controller);
             // We're not moving the display to front when we're adding stacks, only when
             // requested to change the position of stack explicitly.
             mTaskStackContainers.positionChildAt(onTop ? POSITION_TOP : POSITION_BOTTOM, stack,
                     false /* includingParents */);
         } else {
-            stack = new TaskStack(mService, stackId);
+            stack = new TaskStack(mService, stackId, controller);
             mTaskStackContainers.addStackToDisplay(stack, onTop);
         }
 
@@ -2023,8 +2058,8 @@
             for (int i = mTaskStackContainers.size() - 1; i >= 0; --i) {
                 final TaskStack stack = mTaskStackContainers.get(i);
                 final boolean isDockedOnBottom = stack.getDockSide() == DOCKED_BOTTOM;
-                if (stack.isVisible() && (imeOnBottom || isDockedOnBottom) &&
-                        StackId.isStackAffectedByDragResizing(stack.mStackId)) {
+                if (stack.isVisible() && (imeOnBottom || isDockedOnBottom)
+                        && stack.inSplitScreenWindowingMode()) {
                     stack.setAdjustedForIme(imeWin, imeOnBottom && imeHeightChanged);
                 } else {
                     stack.resetAdjustedForIme(false);
@@ -2119,8 +2154,11 @@
         }
     }
 
-    void writeToProto(ProtoOutputStream proto, long fieldId) {
+    @CallSuper
+    @Override
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
         final long token = proto.start(fieldId);
+        super.writeToProto(proto, WINDOW_CONTAINER);
         proto.write(ID, mDisplayId);
         for (int stackNdx = mTaskStackContainers.size() - 1; stackNdx >= 0; --stackNdx) {
             final TaskStack stack = mTaskStackContainers.get(stackNdx);
@@ -2232,7 +2270,7 @@
      * @return The docked stack, but only if it is visible, and {@code null} otherwise.
      */
     TaskStack getDockedStackLocked() {
-        final TaskStack stack = getStackById(DOCKED_STACK_ID);
+        final TaskStack stack = getStack(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
         return (stack != null && stack.isVisible()) ? stack : null;
     }
 
@@ -2241,7 +2279,7 @@
      * visible.
      */
     TaskStack getDockedStackIgnoringVisibility() {
-        return getStackById(DOCKED_STACK_ID);
+        return getStack(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
     }
 
     /** Find the visible, touch-deliverable window under the given point */
@@ -3350,7 +3388,7 @@
          * @see WindowManagerService#addStackToDisplay(int, int, boolean)
          */
         void addStackToDisplay(TaskStack stack, boolean onTop) {
-            if (stack.mStackId == HOME_STACK_ID) {
+            if (stack.isActivityTypeHome()) {
                 if (mHomeStack != null) {
                     throw new IllegalArgumentException("attachStack: HOME_STACK_ID (0) not first.");
                 }
@@ -3415,11 +3453,11 @@
                     : requestedPosition >= topChildPosition;
             int targetPosition = requestedPosition;
 
-            if (toTop && stack.mStackId != PINNED_STACK_ID
-                    && getStackById(PINNED_STACK_ID) != null) {
+            if (toTop && stack.getWindowingMode() != WINDOWING_MODE_PINNED
+                    && getStack(WINDOWING_MODE_PINNED) != null) {
                 // The pinned stack is always the top most stack (always-on-top) when it is present.
                 TaskStack topStack = mChildren.get(topChildPosition);
-                if (topStack.mStackId != PINNED_STACK_ID) {
+                if (topStack.getWindowingMode() != WINDOWING_MODE_PINNED) {
                     throw new IllegalStateException("Pinned stack isn't top stack??? " + mChildren);
                 }
 
diff --git a/com/android/server/wm/DockedStackDividerController.java b/com/android/server/wm/DockedStackDividerController.java
index 030b986..6f441b9 100644
--- a/com/android/server/wm/DockedStackDividerController.java
+++ b/com/android/server/wm/DockedStackDividerController.java
@@ -17,8 +17,10 @@
 package com.android.server.wm;
 
 import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
-import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
 import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 import static android.view.Surface.ROTATION_270;
@@ -330,7 +332,7 @@
         mLastVisibility = visible;
         notifyDockedDividerVisibilityChanged(visible);
         if (!visible) {
-            setResizeDimLayer(false, INVALID_STACK_ID, 0f);
+            setResizeDimLayer(false, WINDOWING_MODE_UNDEFINED, 0f);
         }
     }
 
@@ -456,7 +458,8 @@
             boolean isHomeStackResizable) {
         long animDuration = 0;
         if (animate) {
-            final TaskStack stack = mDisplayContent.getStackById(DOCKED_STACK_ID);
+            final TaskStack stack =
+                    mDisplayContent.getStack(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
             final long transitionDuration = isAnimationMaximizing()
                     ? mService.mAppTransition.getLastClipRevealTransitionDuration()
                     : DEFAULT_APP_TRANSITION_DURATION;
@@ -517,9 +520,18 @@
 
     }
 
-    void setResizeDimLayer(boolean visible, int targetStackId, float alpha) {
+    /**
+     * Shows a dim layer with {@param alpha} if {@param visible} is true and
+     * {@param targetWindowingMode} isn't
+     * {@link android.app.WindowConfiguration#WINDOWING_MODE_UNDEFINED} and there is a stack on the
+     * display in that windowing mode.
+     */
+    void setResizeDimLayer(boolean visible, int targetWindowingMode, float alpha) {
         mService.openSurfaceTransaction();
-        final TaskStack stack = mDisplayContent.getStackById(targetStackId);
+        // TODO: Maybe only allow split-screen windowing modes?
+        final TaskStack stack = targetWindowingMode != WINDOWING_MODE_UNDEFINED
+                ? mDisplayContent.getStack(targetWindowingMode)
+                : null;
         final TaskStack dockedStack = mDisplayContent.getDockedStackLocked();
         boolean visibleAndValid = visible && stack != null && dockedStack != null;
         if (visibleAndValid) {
@@ -605,8 +617,8 @@
         if (mMinimizedDock && mService.mPolicy.isKeyguardShowingAndNotOccluded()) {
             return;
         }
-        final TaskStack fullscreenStack =
-                mDisplayContent.getStackById(FULLSCREEN_WORKSPACE_STACK_ID);
+        final TaskStack fullscreenStack = mDisplayContent.getStack(
+                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
         final boolean homeVisible = homeTask.getTopVisibleAppToken() != null;
         final boolean homeBehind = fullscreenStack != null && fullscreenStack.isVisible();
         setMinimizedDockedStack(homeVisible && !homeBehind, animate);
@@ -801,7 +813,8 @@
     }
 
     private boolean animateForMinimizedDockedStack(long now) {
-        final TaskStack stack = mDisplayContent.getStackById(DOCKED_STACK_ID);
+        final TaskStack stack =
+                mDisplayContent.getStack(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
         if (!mAnimationStarted) {
             mAnimationStarted = true;
             mAnimationStartTime = now;
diff --git a/com/android/server/wm/DragResizeMode.java b/com/android/server/wm/DragResizeMode.java
index 8ab0406..c0bf1e8 100644
--- a/com/android/server/wm/DragResizeMode.java
+++ b/com/android/server/wm/DragResizeMode.java
@@ -16,11 +16,7 @@
 
 package com.android.server.wm;
 
-import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
-import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.HOME_STACK_ID;
-import static android.app.ActivityManager.StackId.RECENTS_STACK_ID;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 
 /**
  * Describes the mode in which a window is drag resizing.
@@ -39,15 +35,12 @@
      */
     static final int DRAG_RESIZE_MODE_DOCKED_DIVIDER = 1;
 
-    static boolean isModeAllowedForStack(int stackId, int mode) {
+    static boolean isModeAllowedForStack(TaskStack stack, int mode) {
         switch (mode) {
             case DRAG_RESIZE_MODE_FREEFORM:
-                return stackId == FREEFORM_WORKSPACE_STACK_ID;
+                return stack.getWindowingMode() == WINDOWING_MODE_FREEFORM;
             case DRAG_RESIZE_MODE_DOCKED_DIVIDER:
-                return stackId == DOCKED_STACK_ID
-                        || stackId == FULLSCREEN_WORKSPACE_STACK_ID
-                        || stackId == HOME_STACK_ID
-                        || stackId == RECENTS_STACK_ID;
+                return stack.inSplitScreenWindowingMode();
             default:
                 return false;
         }
diff --git a/com/android/server/wm/PinnedStackController.java b/com/android/server/wm/PinnedStackController.java
index 1e7140a..ef31598 100644
--- a/com/android/server/wm/PinnedStackController.java
+++ b/com/android/server/wm/PinnedStackController.java
@@ -16,7 +16,8 @@
 
 package com.android.server.wm;
 
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.util.TypedValue.COMPLEX_UNIT_DIP;
 
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -404,27 +405,29 @@
      */
     private void notifyMovementBoundsChanged(boolean fromImeAdjustement) {
         synchronized (mService.mWindowMap) {
-            if (mPinnedStackListener != null) {
-                try {
-                    final Rect insetBounds = new Rect();
-                    getInsetBounds(insetBounds);
-                    final Rect normalBounds = getDefaultBounds();
-                    if (isValidPictureInPictureAspectRatio(mAspectRatio)) {
-                        transformBoundsToAspectRatio(normalBounds, mAspectRatio,
-                                false /* useCurrentMinEdgeSize */);
-                    }
-                    final Rect animatingBounds = mTmpAnimatingBoundsRect;
-                    final TaskStack pinnedStack = mDisplayContent.getStackById(PINNED_STACK_ID);
-                    if (pinnedStack != null) {
-                        pinnedStack.getAnimationOrCurrentBounds(animatingBounds);
-                    } else {
-                        animatingBounds.set(normalBounds);
-                    }
-                    mPinnedStackListener.onMovementBoundsChanged(insetBounds, normalBounds,
-                            animatingBounds, fromImeAdjustement, mDisplayInfo.rotation);
-                } catch (RemoteException e) {
-                    Slog.e(TAG_WM, "Error delivering actions changed event.", e);
+            if (mPinnedStackListener == null) {
+                return;
+            }
+            try {
+                final Rect insetBounds = new Rect();
+                getInsetBounds(insetBounds);
+                final Rect normalBounds = getDefaultBounds();
+                if (isValidPictureInPictureAspectRatio(mAspectRatio)) {
+                    transformBoundsToAspectRatio(normalBounds, mAspectRatio,
+                            false /* useCurrentMinEdgeSize */);
                 }
+                final Rect animatingBounds = mTmpAnimatingBoundsRect;
+                final TaskStack pinnedStack =
+                        mDisplayContent.getStack(WINDOWING_MODE_PINNED);
+                if (pinnedStack != null) {
+                    pinnedStack.getAnimationOrCurrentBounds(animatingBounds);
+                } else {
+                    animatingBounds.set(normalBounds);
+                }
+                mPinnedStackListener.onMovementBoundsChanged(insetBounds, normalBounds,
+                        animatingBounds, fromImeAdjustement, mDisplayInfo.rotation);
+            } catch (RemoteException e) {
+                Slog.e(TAG_WM, "Error delivering actions changed event.", e);
             }
         }
     }
@@ -491,7 +494,7 @@
         pw.println(prefix + "PinnedStackController");
         pw.print(prefix + "  defaultBounds="); getDefaultBounds().printShortString(pw);
         pw.println();
-        mService.getStackBounds(PINNED_STACK_ID, mTmpRect);
+        mService.getStackBounds(WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, mTmpRect);
         pw.print(prefix + "  movementBounds="); getMovementBounds(mTmpRect).printShortString(pw);
         pw.println();
         pw.println(prefix + "  mIsImeShowing=" + mIsImeShowing);
@@ -512,7 +515,7 @@
     void writeToProto(ProtoOutputStream proto, long fieldId) {
         final long token = proto.start(fieldId);
         getDefaultBounds().writeToProto(proto, DEFAULT_BOUNDS);
-        mService.getStackBounds(PINNED_STACK_ID, mTmpRect);
+        mService.getStackBounds(WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD, mTmpRect);
         getMovementBounds(mTmpRect).writeToProto(proto, MOVEMENT_BOUNDS);
         proto.end(token);
     }
diff --git a/com/android/server/wm/PinnedStackWindowController.java b/com/android/server/wm/PinnedStackWindowController.java
index 590ac6e..41f076d 100644
--- a/com/android/server/wm/PinnedStackWindowController.java
+++ b/com/android/server/wm/PinnedStackWindowController.java
@@ -16,19 +16,16 @@
 
 package com.android.server.wm;
 
-import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
-
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static com.android.server.wm.BoundsAnimationController.NO_PIP_MODE_CHANGED_CALLBACKS;
 import static com.android.server.wm.BoundsAnimationController.SCHEDULE_PIP_MODE_CHANGED_ON_END;
 import static com.android.server.wm.BoundsAnimationController.SCHEDULE_PIP_MODE_CHANGED_ON_START;
 import static com.android.server.wm.BoundsAnimationController.SchedulePipModeChangedState;
 
 import android.app.RemoteAction;
-import android.content.res.Configuration;
 import android.graphics.Rect;
 
-import com.android.server.UiThread;
-
 import java.util.List;
 
 /**
@@ -40,8 +37,8 @@
     private Rect mTmpToBounds = new Rect();
 
     public PinnedStackWindowController(int stackId, PinnedStackWindowListener listener,
-            int displayId, boolean onTop, Rect outBounds) {
-        super(stackId, listener, displayId, onTop, outBounds, WindowManagerService.getInstance());
+            int displayId, boolean onTop, Rect outBounds, WindowManagerService service) {
+        super(stackId, listener, displayId, onTop, outBounds, service);
     }
 
     /**
@@ -101,7 +98,8 @@
                 }
                 schedulePipModeChangedState = SCHEDULE_PIP_MODE_CHANGED_ON_START;
 
-                mService.getStackBounds(FULLSCREEN_WORKSPACE_STACK_ID, mTmpToBounds);
+                mService.getStackBounds(
+                        WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD, mTmpToBounds);
                 if (!mTmpToBounds.isEmpty()) {
                     // If there is a fullscreen bounds, use that
                     toBounds = new Rect(mTmpToBounds);
diff --git a/com/android/server/wm/RootWindowContainer.java b/com/android/server/wm/RootWindowContainer.java
index 8a74976..7832f5d 100644
--- a/com/android/server/wm/RootWindowContainer.java
+++ b/com/android/server/wm/RootWindowContainer.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import android.annotation.CallSuper;
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.hardware.power.V1_0.PowerHint;
@@ -89,8 +90,9 @@
 import static com.android.server.wm.WindowSurfacePlacer.SET_UPDATE_ROTATION;
 import static com.android.server.wm.WindowSurfacePlacer.SET_WALLPAPER_ACTION_PENDING;
 import static com.android.server.wm.WindowSurfacePlacer.SET_WALLPAPER_MAY_CHANGE;
-import static com.android.server.wm.proto.WindowManagerServiceProto.DISPLAYS;
-import static com.android.server.wm.proto.WindowManagerServiceProto.WINDOWS;
+import static com.android.server.wm.proto.RootWindowContainerProto.DISPLAYS;
+import static com.android.server.wm.proto.RootWindowContainerProto.WINDOWS;
+import static com.android.server.wm.proto.RootWindowContainerProto.WINDOW_CONTAINER;
 
 /** Root {@link WindowContainer} for the device. */
 class RootWindowContainer extends WindowContainer<DisplayContent> {
@@ -420,6 +422,17 @@
         return null;
     }
 
+    TaskStack getStack(int windowingMode, int activityType) {
+        for (int i = mChildren.size() - 1; i >= 0; i--) {
+            final DisplayContent dc = mChildren.get(i);
+            final TaskStack stack = dc.getStack(windowingMode, activityType);
+            if (stack != null) {
+                return stack;
+            }
+        }
+        return null;
+    }
+
     void setSecureSurfaceState(int userId, boolean disabled) {
         forAllWindows((w) -> {
             if (w.mHasSurface && userId == UserHandle.getUserId(w.mOwnerUid)) {
@@ -1077,7 +1090,11 @@
         }
     }
 
-    void writeToProto(ProtoOutputStream proto) {
+    @CallSuper
+    @Override
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+        super.writeToProto(proto, WINDOW_CONTAINER);
         if (mService.mDisplayReady) {
             final int count = mChildren.size();
             for (int i = 0; i < count; ++i) {
@@ -1088,6 +1105,7 @@
         forAllWindows((w) -> {
             w.writeIdentifierToProto(proto, WINDOWS);
         }, true);
+        proto.end(token);
     }
 
     @Override
diff --git a/com/android/server/wm/Session.java b/com/android/server/wm/Session.java
index 1781247..4dd147e 100644
--- a/com/android/server/wm/Session.java
+++ b/com/android/server/wm/Session.java
@@ -719,4 +719,8 @@
     public String toString() {
         return mStringName;
     }
+
+    boolean hasAlertWindowSurfaces() {
+        return !mAlertWindowSurfaces.isEmpty();
+    }
 }
diff --git a/com/android/server/wm/StackWindowController.java b/com/android/server/wm/StackWindowController.java
index a50ed71..c0a4cb7 100644
--- a/com/android/server/wm/StackWindowController.java
+++ b/com/android/server/wm/StackWindowController.java
@@ -18,8 +18,6 @@
 
 import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
 
-import android.app.ActivityManager.StackId;
-import android.app.WindowConfiguration;
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.Handler;
@@ -76,8 +74,7 @@
                         + " to unknown displayId=" + displayId);
             }
 
-            final TaskStack stack = dc.addStackToDisplay(stackId, onTop);
-            stack.setController(this);
+            dc.addStackToDisplay(stackId, onTop, this);
             getRawBounds(outBounds);
         }
     }
diff --git a/com/android/server/wm/Task.java b/com/android/server/wm/Task.java
index 55b6c91..7e8d130 100644
--- a/com/android/server/wm/Task.java
+++ b/com/android/server/wm/Task.java
@@ -23,6 +23,7 @@
 import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
+import static android.content.res.Configuration.EMPTY;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
 
 import static com.android.server.EventLogTags.WM_TASK_REMOVED;
@@ -34,8 +35,9 @@
 import static com.android.server.wm.proto.TaskProto.FILLS_PARENT;
 import static com.android.server.wm.proto.TaskProto.ID;
 import static com.android.server.wm.proto.TaskProto.TEMP_INSET_BOUNDS;
+import static com.android.server.wm.proto.TaskProto.WINDOW_CONTAINER;
 
-import android.app.ActivityManager.StackId;
+import android.annotation.CallSuper;
 import android.app.ActivityManager.TaskDescription;
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
@@ -278,15 +280,9 @@
     // WindowConfiguration long term.
     private int setBounds(Rect bounds, Configuration overrideConfig) {
         if (overrideConfig == null) {
-            overrideConfig = Configuration.EMPTY;
+            overrideConfig = EMPTY;
         }
-        if (bounds == null && !Configuration.EMPTY.equals(overrideConfig)) {
-            throw new IllegalArgumentException("null bounds but non empty configuration: "
-                    + overrideConfig);
-        }
-        if (bounds != null && Configuration.EMPTY.equals(overrideConfig)) {
-            throw new IllegalArgumentException("non null bounds, but empty configuration");
-        }
+
         boolean oldFullscreen = mFillsParent;
         int rotation = Surface.ROTATION_0;
         final DisplayContent displayContent = mStack.getDisplayContent();
@@ -321,7 +317,7 @@
         if (displayContent != null) {
             displayContent.mDimLayerController.updateDimLayer(this);
         }
-        onOverrideConfigurationChanged(mFillsParent ? Configuration.EMPTY : overrideConfig);
+        onOverrideConfigurationChanged(overrideConfig);
         return boundsChange;
     }
 
@@ -404,7 +400,7 @@
      *                    the adjusted bounds's top.
      */
     void alignToAdjustedBounds(Rect adjustedBounds, Rect tempInsetBounds, boolean alignBottom) {
-        if (!isResizeable() || Configuration.EMPTY.equals(getOverrideConfiguration())) {
+        if (!isResizeable() || EMPTY.equals(getOverrideConfiguration())) {
             return;
         }
 
@@ -529,7 +525,7 @@
 
     void setDragResizing(boolean dragResizing, int dragResizeMode) {
         if (mDragResizing != dragResizing) {
-            if (!DragResizeMode.isModeAllowedForStack(mStack.mStackId, dragResizeMode)) {
+            if (!DragResizeMode.isModeAllowedForStack(mStack, dragResizeMode)) {
                 throw new IllegalArgumentException("Drag resize mode not allow for stack stackId="
                         + mStack.mStackId + " dragResizeMode=" + dragResizeMode);
             }
@@ -552,7 +548,9 @@
             return;
         }
         if (mFillsParent) {
-            setBounds(null, Configuration.EMPTY);
+            // TODO: Yeah...not sure if this works with WindowConfiguration, but shouldn't be a
+            // problem once we move mBounds into WindowConfiguration.
+            setBounds(null, getOverrideConfiguration());
             return;
         }
         final int newRotation = displayContent.getDisplayInfo().rotation;
@@ -735,8 +733,11 @@
         return "Task=" + mTaskId;
     }
 
-    void writeToProto(ProtoOutputStream proto, long fieldId) {
+    @CallSuper
+    @Override
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
         final long token = proto.start(fieldId);
+        super.writeToProto(proto, WINDOW_CONTAINER);
         proto.write(ID, mTaskId);
         for (int i = mChildren.size() - 1; i >= 0; i--) {
             final AppWindowToken appWindowToken = mChildren.get(i);
diff --git a/com/android/server/wm/TaskSnapshotController.java b/com/android/server/wm/TaskSnapshotController.java
index 4632402..bff24f6 100644
--- a/com/android/server/wm/TaskSnapshotController.java
+++ b/com/android/server/wm/TaskSnapshotController.java
@@ -254,7 +254,7 @@
     @VisibleForTesting
     int getSnapshotMode(Task task) {
         final AppWindowToken topChild = task.getTopChild();
-        if (StackId.isHomeOrRecentsStack(task.mStack.mStackId)) {
+        if (!task.isActivityTypeStandardOrUndefined()) {
             return SNAPSHOT_MODE_NONE;
         } else if (topChild != null && topChild.shouldUseAppThemeSnapshot()) {
             return SNAPSHOT_MODE_APP_THEME;
@@ -297,7 +297,7 @@
 
         return new TaskSnapshot(hwBitmap.createGraphicBufferHandle(),
                 topChild.getConfiguration().orientation, mainWindow.mStableInsets,
-                false /* reduced */, 1.0f /* scale */);
+                ActivityManager.isLowRamDeviceStatic() /* reduced */, 1.0f /* scale */);
     }
 
     /**
diff --git a/com/android/server/wm/TaskStack.java b/com/android/server/wm/TaskStack.java
index 4664dcb..6527883 100644
--- a/com/android/server/wm/TaskStack.java
+++ b/com/android/server/wm/TaskStack.java
@@ -19,8 +19,11 @@
 import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
 import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
 import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
-import static android.app.ActivityManager.StackId.HOME_STACK_ID;
 import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
 import static android.content.res.Configuration.DENSITY_DPI_UNDEFINED;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
@@ -41,8 +44,9 @@
 import static com.android.server.wm.proto.StackProto.FILLS_PARENT;
 import static com.android.server.wm.proto.StackProto.ID;
 import static com.android.server.wm.proto.StackProto.TASKS;
+import static com.android.server.wm.proto.StackProto.WINDOW_CONTAINER;
 
-import android.app.ActivityManager.StackId;
+import android.annotation.CallSuper;
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.graphics.Region;
@@ -147,9 +151,10 @@
 
     Rect mPreAnimationBounds = new Rect();
 
-    TaskStack(WindowManagerService service, int stackId) {
+    TaskStack(WindowManagerService service, int stackId, StackWindowController controller) {
         mService = service;
         mStackId = stackId;
+        setController(controller);
         mDockedStackMinimizeThickness = service.mContext.getResources().getDimensionPixelSize(
                 com.android.internal.R.dimen.docked_stack_minimize_thickness);
         EventLog.writeEvent(EventLogTags.WM_STACK_CREATED, stackId);
@@ -733,7 +738,7 @@
         outTempTaskBounds.setEmpty();
 
         // When the home stack is resizable, should always have the same stack and task bounds
-        if (mStackId == HOME_STACK_ID) {
+        if (isActivityTypeHome()) {
             final Task homeTask = findHomeTask();
             if (homeTask != null && homeTask.isResizeable()) {
                 // Calculate the home stack bounds when in docked mode and the home stack is
@@ -918,7 +923,9 @@
 
     void resetAnimationBackgroundAnimator() {
         mAnimationBackgroundAnimator = null;
-        mAnimationBackgroundSurface.hide();
+        if (mAnimationBackgroundSurface != null) {
+            mAnimationBackgroundSurface.hide();
+        }
     }
 
     void setAnimationBackground(WindowStateAnimator winAnimator, int color) {
@@ -1005,7 +1012,7 @@
             mAdjustImeAmount = 0f;
             mAdjustDividerAmount = 0f;
             updateAdjustedBounds();
-            mService.setResizeDimLayer(false, mStackId, 1.0f);
+            mService.setResizeDimLayer(false, getWindowingMode(), 1.0f);
         } else {
             mImeGoingAway |= mAdjustedForIme;
         }
@@ -1201,7 +1208,7 @@
         if (mAdjustedForIme && adjust && !isImeTarget) {
             final float alpha = Math.max(mAdjustImeAmount, mAdjustDividerAmount)
                     * IME_ADJUST_DIM_AMOUNT;
-            mService.setResizeDimLayer(true, mStackId, alpha);
+            mService.setResizeDimLayer(true, getWindowingMode(), alpha);
         }
     }
 
@@ -1219,8 +1226,11 @@
         return mMinimizeAmount != 0f;
     }
 
-    void writeToProto(ProtoOutputStream proto, long fieldId) {
+    @CallSuper
+    @Override
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
         final long token = proto.start(fieldId);
+        super.writeToProto(proto, WINDOW_CONTAINER);
         proto.write(ID, mStackId);
         for (int taskNdx = mChildren.size() - 1; taskNdx >= 0; taskNdx--) {
             mChildren.get(taskNdx).writeToProto(proto, TASKS);
@@ -1277,7 +1287,7 @@
 
     @Override
     public boolean dimFullscreen() {
-        return StackId.isHomeOrRecentsStack(mStackId) || fillsParent();
+        return !isActivityTypeStandard() || fillsParent();
     }
 
     @Override
@@ -1657,7 +1667,15 @@
 
     @Override
     int getOrientation() {
-        return (StackId.canSpecifyOrientation(mStackId))
-                ? super.getOrientation() : SCREEN_ORIENTATION_UNSET;
+        return (canSpecifyOrientation()) ? super.getOrientation() : SCREEN_ORIENTATION_UNSET;
+    }
+
+    private boolean canSpecifyOrientation() {
+        final int windowingMode = getWindowingMode();
+        final int activityType = getActivityType();
+        return windowingMode == WINDOWING_MODE_FULLSCREEN
+                || activityType == ACTIVITY_TYPE_HOME
+                || activityType == ACTIVITY_TYPE_RECENTS
+                || activityType == ACTIVITY_TYPE_ASSISTANT;
     }
 }
diff --git a/com/android/server/wm/WindowContainer.java b/com/android/server/wm/WindowContainer.java
index 926719d..40923c8 100644
--- a/com/android/server/wm/WindowContainer.java
+++ b/com/android/server/wm/WindowContainer.java
@@ -19,12 +19,14 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
-import static android.content.res.Configuration.EMPTY;
+import static com.android.server.wm.proto.WindowContainerProto.CONFIGURATION_CONTAINER;
+import static com.android.server.wm.proto.WindowContainerProto.ORIENTATION;
 
 import android.annotation.CallSuper;
 import android.content.res.Configuration;
 import android.util.Pools;
 
+import android.util.proto.ProtoOutputStream;
 import com.android.internal.util.ToBooleanFunction;
 
 import java.util.Comparator;
@@ -685,6 +687,23 @@
         }
     }
 
+    /**
+     * Write to a protocol buffer output stream. Protocol buffer message definition is at
+     * {@link com.android.server.wm.proto.WindowContainerProto}.
+     *
+     * @param protoOutputStream Stream to write the WindowContainer object to.
+     * @param fieldId           Field Id of the WindowContainer as defined in the parent message.
+     * @hide
+     */
+    @CallSuper
+    @Override
+    public void writeToProto(ProtoOutputStream protoOutputStream, long fieldId) {
+        final long token = protoOutputStream.start(fieldId);
+        super.writeToProto(protoOutputStream, CONFIGURATION_CONTAINER);
+        protoOutputStream.write(ORIENTATION, mOrientation);
+        protoOutputStream.end(token);
+    }
+
     String getName() {
         return toString();
     }
diff --git a/com/android/server/wm/WindowLayersController.java b/com/android/server/wm/WindowLayersController.java
index 857b13d..7caf2fe 100644
--- a/com/android/server/wm/WindowLayersController.java
+++ b/com/android/server/wm/WindowLayersController.java
@@ -21,11 +21,8 @@
 import java.util.ArrayDeque;
 import java.util.function.Consumer;
 
-import static android.app.ActivityManager.StackId;
-import static android.app.ActivityManager.StackId.ASSISTANT_STACK_ID;
-import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYERS;
@@ -187,12 +184,13 @@
             }
         }
 
-        final int stackId = w.getAppToken() != null ? w.getStackId() : INVALID_STACK_ID;
-        if (stackId == PINNED_STACK_ID) {
+        final int windowingMode = w.getWindowingMode();
+        if (windowingMode == WINDOWING_MODE_PINNED) {
             mPinnedWindows.add(w);
-        } else if (stackId == DOCKED_STACK_ID) {
+        } else if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
             mDockedWindows.add(w);
-        } else if (stackId == ASSISTANT_STACK_ID) {
+        }
+        if (w.isActivityTypeAssistant()) {
             mAssistantWindows.add(w);
         }
     }
@@ -268,20 +266,8 @@
         w.mLayer = layer;
         w.mWinAnimator.mAnimLayer = w.getAnimLayerAdjustment()
                 + w.getSpecialWindowAnimLayerAdjustment();
-        if (w.mAppToken != null && w.mAppToken.mAppAnimator.thumbnailForceAboveLayer > 0) {
-            if (w.mWinAnimator.mAnimLayer > w.mAppToken.mAppAnimator.thumbnailForceAboveLayer) {
-                w.mAppToken.mAppAnimator.thumbnailForceAboveLayer = w.mWinAnimator.mAnimLayer;
-            }
-            // TODO(b/62029108): the entire contents of the if statement should call the refactored
-            // function to set the thumbnail layer for w.AppToken
-            int highestLayer = w.mAppToken.getHighestAnimLayer();
-            if (highestLayer > 0) {
-                if (w.mAppToken.mAppAnimator.thumbnail != null
-                        && w.mAppToken.mAppAnimator.thumbnailForceAboveLayer != highestLayer) {
-                    w.mAppToken.mAppAnimator.thumbnailForceAboveLayer = highestLayer;
-                    w.mAppToken.mAppAnimator.thumbnail.setLayer(highestLayer + 1);
-                }
-            }
+        if (w.mAppToken != null) {
+            w.mAppToken.mAppAnimator.updateThumbnailLayer();
         }
     }
 }
diff --git a/com/android/server/wm/WindowManagerService.java b/com/android/server/wm/WindowManagerService.java
index 32ee51c..1fb2188 100644
--- a/com/android/server/wm/WindowManagerService.java
+++ b/com/android/server/wm/WindowManagerService.java
@@ -109,6 +109,7 @@
 import static com.android.server.wm.proto.WindowManagerServiceProto.INPUT_METHOD_WINDOW;
 import static com.android.server.wm.proto.WindowManagerServiceProto.LAST_ORIENTATION;
 import static com.android.server.wm.proto.WindowManagerServiceProto.POLICY;
+import static com.android.server.wm.proto.WindowManagerServiceProto.ROOT_WINDOW_CONTAINER;
 import static com.android.server.wm.proto.WindowManagerServiceProto.ROTATION;
 
 import android.Manifest;
@@ -246,6 +247,7 @@
 import com.android.server.input.InputManagerService;
 import com.android.server.power.BatterySaverPolicy.ServiceType;
 import com.android.server.power.ShutdownThread;
+import com.android.server.utils.PriorityDump;
 
 import java.io.BufferedWriter;
 import java.io.DataInputStream;
@@ -390,6 +392,18 @@
     };
     final WindowSurfacePlacer mWindowPlacerLocked;
 
+    private final PriorityDump.PriorityDumper mPriorityDumper = new PriorityDump.PriorityDumper() {
+        @Override
+        public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args) {
+            doDump(fd, pw, new String[] {"-a"});
+        }
+
+        @Override
+        public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+            doDump(fd, pw, args);
+        }
+    };
+
     /**
      * Current user when multi-user is enabled. Don't show windows of
      * non-current user. Also see mCurrentProfileIds.
@@ -2029,10 +2043,14 @@
                 Slog.i(TAG_WM, "Relayout " + win + ": oldVis=" + oldVisibility
                         + " newVis=" + viewVisibility, stack);
             }
-            if (viewVisibility == View.VISIBLE &&
-                    (win.mAppToken == null || win.mAttrs.type == TYPE_APPLICATION_STARTING
-                            || !win.mAppToken.isClientHidden())) {
 
+            // We should only relayout if the view is visible, it is a starting window, or the
+            // associated appToken is not hidden.
+            final boolean shouldRelayout = viewVisibility == View.VISIBLE &&
+                (win.mAppToken == null || win.mAttrs.type == TYPE_APPLICATION_STARTING
+                    || !win.mAppToken.isClientHidden());
+
+            if (shouldRelayout) {
                 Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "relayoutWindow: viewVisibility_1");
 
                 // We are about to create a surface, but we didn't run a layout yet. So better run
@@ -2179,8 +2197,18 @@
             // and needs process it before handling the corresponding window frame. the variable
             // {@code mergedConfiguration} is an out parameter that will be passed back to the
             // client over IPC and checked there.
-            win.getMergedConfiguration(mergedConfiguration);
-            win.setReportedConfiguration(mergedConfiguration);
+            // Note: in the cases where the window is tied to an activity, we should not send a
+            // configuration update when the window has requested to be hidden. Doing so can lead
+            // to the client erroneously accepting a configuration that would have otherwise caused
+            // an activity restart. We instead hand back the last reported
+            // {@link MergedConfiguration}.
+            if (shouldRelayout) {
+                win.getMergedConfiguration(mergedConfiguration);
+            } else {
+                win.getLastReportedMergedConfiguration(mergedConfiguration);
+            }
+
+            win.setLastReportedMergedConfiguration(mergedConfiguration);
 
             outFrame.set(win.mCompatFrame);
             outOverscanInsets.set(win.mOverscanInsets);
@@ -2881,9 +2909,9 @@
     }
 
     @Override
-    public void getStackBounds(int stackId, Rect bounds) {
+    public void getStackBounds(int windowingMode, int activityType, Rect bounds) {
         synchronized (mWindowMap) {
-            final TaskStack stack = mRoot.getStackById(stackId);
+            final TaskStack stack = mRoot.getStack(windowingMode, activityType);
             if (stack != null) {
                 stack.getBounds(bounds);
                 return;
@@ -6505,7 +6533,7 @@
 
     private void writeToProtoLocked(ProtoOutputStream proto) {
         mPolicy.writeToProto(proto, POLICY);
-        mRoot.writeToProto(proto);
+        mRoot.writeToProto(proto, ROOT_WINDOW_CONTAINER);
         if (mCurrentFocus != null) {
             mCurrentFocus.writeIdentifierToProto(proto, FOCUSED_WINDOW);
         }
@@ -6793,8 +6821,11 @@
 
     @Override
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
-        if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
+        PriorityDump.dump(mPriorityDumper, fd, pw, args);
+    }
 
+    private void doDump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
         boolean dumpAll = false;
         boolean useProto = false;
 
@@ -7124,10 +7155,10 @@
     }
 
     @Override
-    public void setResizeDimLayer(boolean visible, int targetStackId, float alpha) {
+    public void setResizeDimLayer(boolean visible, int targetWindowingMode, float alpha) {
         synchronized (mWindowMap) {
             getDefaultDisplayContentLocked().getDockedDividerController().setResizeDimLayer(
-                    visible, targetStackId, alpha);
+                    visible, targetWindowingMode, alpha);
         }
     }
 
diff --git a/com/android/server/wm/WindowState.java b/com/android/server/wm/WindowState.java
index 1b05566..4ff0f39 100644
--- a/com/android/server/wm/WindowState.java
+++ b/com/android/server/wm/WindowState.java
@@ -116,7 +116,9 @@
 import static com.android.server.wm.proto.WindowStateProto.PARENT_FRAME;
 import static com.android.server.wm.proto.WindowStateProto.STACK_ID;
 import static com.android.server.wm.proto.WindowStateProto.SURFACE_INSETS;
+import static com.android.server.wm.proto.WindowStateProto.WINDOW_CONTAINER;
 
+import android.annotation.CallSuper;
 import android.app.AppOpsManager;
 import android.content.Context;
 import android.content.res.Configuration;
@@ -255,7 +257,7 @@
      * We'll send configuration to client only if it is different from the last applied one and
      * client won't perform unnecessary updates.
      */
-    private final Configuration mLastReportedConfiguration = new Configuration();
+    private final MergedConfiguration mLastReportedConfiguration = new MergedConfiguration();
 
     /**
      * Actual position of the surface shown on-screen (may be modified by animation). These are
@@ -1242,7 +1244,7 @@
         //                   this is not necessarily what the client has processed yet. Find a
         //                   better indicator consistent with the client.
         return (mOrientationChanging || (isVisible()
-                && getConfiguration().orientation != mLastReportedConfiguration.orientation))
+                && getConfiguration().orientation != getLastReportedConfiguration().orientation))
                 && !mSeamlesslyRotated
                 && !mOrientationChangeTimedOut;
     }
@@ -1756,7 +1758,7 @@
 
     /** Returns true if last applied config was not yet requested by client. */
     boolean isConfigChanged() {
-        return !mLastReportedConfiguration.equals(getConfiguration());
+        return !getLastReportedConfiguration().equals(getConfiguration());
     }
 
     void onWindowReplacementTimeout() {
@@ -2310,8 +2312,16 @@
         outConfiguration.setConfiguration(globalConfig, overrideConfig);
     }
 
-    void setReportedConfiguration(MergedConfiguration config) {
-        mLastReportedConfiguration.setTo(config.getMergedConfiguration());
+    void setLastReportedMergedConfiguration(MergedConfiguration config) {
+        mLastReportedConfiguration.setTo(config);
+    }
+
+    void getLastReportedMergedConfiguration(MergedConfiguration config) {
+        config.setTo(mLastReportedConfiguration);
+    }
+
+    private Configuration getLastReportedConfiguration() {
+        return mLastReportedConfiguration.getMergedConfiguration();
     }
 
     void adjustStartingWindowFlags() {
@@ -2851,7 +2861,7 @@
                     new MergedConfiguration(mService.mRoot.getConfiguration(),
                     getMergedOverrideConfiguration());
 
-            setReportedConfiguration(mergedConfiguration);
+            setLastReportedMergedConfiguration(mergedConfiguration);
 
             if (DEBUG_ORIENTATION && mWinAnimator.mDrawState == DRAW_PENDING)
                 Slog.i(TAG, "Resizing " + this + " WITH DRAW PENDING");
@@ -3078,7 +3088,7 @@
         if (task == null) {
             return false;
         }
-        if (!StackId.isStackAffectedByDragResizing(getStackId())) {
+        if (!inSplitScreenWindowingMode()) {
             return false;
         }
         if (mAttrs.width != MATCH_PARENT || mAttrs.height != MATCH_PARENT) {
@@ -3124,8 +3134,11 @@
                 || (isChildWindow() && getParentWindow().isDockedResizing());
     }
 
-    void writeToProto(ProtoOutputStream proto, long fieldId) {
+    @CallSuper
+    @Override
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
         final long token = proto.start(fieldId);
+        super.writeToProto(proto, WINDOW_CONTAINER);
         writeIdentifierToProto(proto, IDENTIFIER);
         proto.write(DISPLAY_ID, getDisplayId());
         proto.write(STACK_ID, getStackId());
@@ -3168,7 +3181,7 @@
                 pw.print(" mShowToOwnerOnly="); pw.print(mShowToOwnerOnly);
                 pw.print(" package="); pw.print(mAttrs.packageName);
                 pw.print(" appop="); pw.println(AppOpsManager.opToName(mAppOp));
-        pw.print(prefix); pw.print("mAttrs="); pw.println(mAttrs);
+        pw.print(prefix); pw.print("mAttrs="); pw.println(mAttrs.toString(prefix));
         pw.print(prefix); pw.print("Requested w="); pw.print(mRequestedWidth);
                 pw.print(" h="); pw.print(mRequestedHeight);
                 pw.print(" mLayoutSeq="); pw.println(mLayoutSeq);
@@ -3249,7 +3262,7 @@
             }
             pw.print(prefix); pw.print("mFullConfiguration="); pw.println(getConfiguration());
             pw.print(prefix); pw.print("mLastReportedConfiguration=");
-                    pw.println(mLastReportedConfiguration);
+                    pw.println(getLastReportedConfiguration());
         }
         pw.print(prefix); pw.print("mHasSurface="); pw.print(mHasSurface);
                 pw.print(" mShownPosition="); mShownPosition.printShortString(pw);
@@ -3309,7 +3322,7 @@
             pw.print(prefix); pw.print("mOrientationChanging=");
                     pw.print(mOrientationChanging);
                     pw.print(" configOrientationChanging=");
-                    pw.print(mLastReportedConfiguration.orientation
+                    pw.print(getLastReportedConfiguration().orientation
                             != getConfiguration().orientation);
                     pw.print(" mAppFreezing="); pw.print(mAppFreezing);
                     pw.print(" mTurnOnScreen="); pw.print(mTurnOnScreen);
diff --git a/com/android/server/wm/WindowSurfacePlacer.java b/com/android/server/wm/WindowSurfacePlacer.java
index 88625d3..af1fa2f 100644
--- a/com/android/server/wm/WindowSurfacePlacer.java
+++ b/com/android/server/wm/WindowSurfacePlacer.java
@@ -342,10 +342,7 @@
         mTmpLayerAndToken.token = null;
         handleClosingApps(transit, animLp, voiceInteraction, mTmpLayerAndToken);
         final AppWindowToken topClosingApp = mTmpLayerAndToken.token;
-        final int topClosingLayer = mTmpLayerAndToken.layer;
-
-        final AppWindowToken topOpeningApp = handleOpeningApps(transit,
-                animLp, voiceInteraction, topClosingLayer);
+        final AppWindowToken topOpeningApp = handleOpeningApps(transit, animLp, voiceInteraction);
 
         mService.mAppTransition.setLastAppTransition(transit, topOpeningApp, topClosingApp);
 
@@ -387,8 +384,9 @@
     }
 
     private AppWindowToken handleOpeningApps(int transit, LayoutParams animLp,
-            boolean voiceInteraction, int topClosingLayer) {
+            boolean voiceInteraction) {
         AppWindowToken topOpeningApp = null;
+        int topOpeningLayer = Integer.MIN_VALUE;
         final int appsCount = mService.mOpeningApps.size();
         for (int i = 0; i < appsCount; i++) {
             AppWindowToken wtoken = mService.mOpeningApps.valueAt(i);
@@ -422,7 +420,6 @@
             }
             mService.mAnimator.mAppWindowAnimating |= appAnimator.isAnimating();
 
-            int topOpeningLayer = 0;
             if (animLp != null) {
                 final int layer = wtoken.getHighestAnimLayer();
                 if (topOpeningApp == null || layer > topOpeningLayer) {
@@ -431,7 +428,7 @@
                 }
             }
             if (mService.mAppTransition.isNextAppTransitionThumbnailUp()) {
-                createThumbnailAppAnimator(transit, wtoken, topOpeningLayer, topClosingLayer);
+                createThumbnailAppAnimator(transit, wtoken);
             }
         }
         return topOpeningApp;
@@ -473,7 +470,7 @@
                 }
             }
             if (mService.mAppTransition.isNextAppTransitionThumbnailDown()) {
-                createThumbnailAppAnimator(transit, wtoken, 0, layerAndToken.layer);
+                createThumbnailAppAnimator(transit, wtoken);
             }
         }
     }
@@ -666,8 +663,7 @@
         }
     }
 
-    private void createThumbnailAppAnimator(int transit, AppWindowToken appToken,
-            int openingLayer, int closingLayer) {
+    private void createThumbnailAppAnimator(int transit, AppWindowToken appToken) {
         AppWindowAnimator openingAppAnimator = (appToken == null) ? null : appToken.mAppAnimator;
         if (openingAppAnimator == null || openingAppAnimator.animation == null) {
             return;
@@ -724,7 +720,6 @@
                 anim = mService.mAppTransition.createThumbnailAspectScaleAnimationLocked(appRect,
                         insets, thumbnailHeader, taskId, displayConfig.uiMode,
                         displayConfig.orientation);
-                openingAppAnimator.thumbnailForceAboveLayer = Math.max(openingLayer, closingLayer);
                 openingAppAnimator.deferThumbnailDestruction =
                         !mService.mAppTransition.isNextThumbnailTransitionScaleUp();
             } else {
@@ -734,8 +729,8 @@
             anim.restrictDuration(MAX_ANIMATION_DURATION);
             anim.scaleCurrentDuration(mService.getTransitionAnimationScaleLocked());
 
+            openingAppAnimator.updateThumbnailLayer();
             openingAppAnimator.thumbnail = surfaceControl;
-            openingAppAnimator.thumbnailLayer = openingLayer;
             openingAppAnimator.thumbnailAnimation = anim;
             mService.mAppTransition.getNextAppTransitionStartRect(taskId, mTmpStartRect);
         } catch (Surface.OutOfResourcesException e) {
diff --git a/com/android/server/wm/WindowToken.java b/com/android/server/wm/WindowToken.java
index 422615b..943448e 100644
--- a/com/android/server/wm/WindowToken.java
+++ b/com/android/server/wm/WindowToken.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import android.annotation.CallSuper;
 import android.util.proto.ProtoOutputStream;
 import java.util.Comparator;
 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
@@ -28,6 +29,7 @@
 import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
 import static com.android.server.wm.proto.WindowTokenProto.HASH_CODE;
 import static com.android.server.wm.proto.WindowTokenProto.WINDOWS;
+import static com.android.server.wm.proto.WindowTokenProto.WINDOW_CONTAINER;
 
 import android.os.Debug;
 import android.os.IBinder;
@@ -263,8 +265,11 @@
         super.onDisplayChanged(dc);
     }
 
-    void writeToProto(ProtoOutputStream proto, long fieldId) {
+    @CallSuper
+    @Override
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
         final long token = proto.start(fieldId);
+        super.writeToProto(proto, WINDOW_CONTAINER);
         proto.write(HASH_CODE, System.identityHashCode(this));
         for (int i = 0; i < mChildren.size(); i++) {
             final WindowState w = mChildren.get(i);
diff --git a/com/android/settingslib/applications/ApplicationsState.java b/com/android/settingslib/applications/ApplicationsState.java
index 40c2b1f..fa2499f 100644
--- a/com/android/settingslib/applications/ApplicationsState.java
+++ b/com/android/settingslib/applications/ApplicationsState.java
@@ -52,6 +52,11 @@
 
 import com.android.internal.R;
 import com.android.internal.util.ArrayUtils;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+import com.android.settingslib.core.lifecycle.LifecycleObserver;
+import com.android.settingslib.core.lifecycle.events.OnDestroy;
+import com.android.settingslib.core.lifecycle.events.OnPause;
+import com.android.settingslib.core.lifecycle.events.OnResume;
 
 import java.io.File;
 import java.io.IOException;
@@ -180,7 +185,11 @@
     }
 
     public Session newSession(Callbacks callbacks) {
-        Session s = new Session(callbacks);
+        return newSession(callbacks, null);
+    }
+
+    public Session newSession(Callbacks callbacks, Lifecycle lifecycle) {
+        Session s = new Session(callbacks, lifecycle);
         synchronized (mEntriesMap) {
             mSessions.add(s);
         }
@@ -586,7 +595,7 @@
                 .replaceAll("").toLowerCase();
     }
 
-    public class Session {
+    public class Session implements LifecycleObserver, OnPause, OnResume, OnDestroy {
         final Callbacks mCallbacks;
         boolean mResumed;
 
@@ -600,11 +609,19 @@
         ArrayList<AppEntry> mLastAppList;
         boolean mRebuildForeground;
 
-        Session(Callbacks callbacks) {
+        private final boolean mHasLifecycle;
+
+        Session(Callbacks callbacks, Lifecycle lifecycle) {
             mCallbacks = callbacks;
+            if (lifecycle != null) {
+                lifecycle.addObserver(this);
+                mHasLifecycle = true;
+            } else {
+                mHasLifecycle = false;
+            }
         }
 
-        public void resume() {
+        public void onResume() {
             if (DEBUG_LOCKING) Log.v(TAG, "resume about to acquire lock...");
             synchronized (mEntriesMap) {
                 if (!mResumed) {
@@ -616,7 +633,7 @@
             if (DEBUG_LOCKING) Log.v(TAG, "...resume releasing lock");
         }
 
-        public void pause() {
+        public void onPause() {
             if (DEBUG_LOCKING) Log.v(TAG, "pause about to acquire lock...");
             synchronized (mEntriesMap) {
                 if (mResumed) {
@@ -735,8 +752,11 @@
             Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
         }
 
-        public void release() {
-            pause();
+        public void onDestroy() {
+            if (!mHasLifecycle) {
+                // TODO: Legacy, remove this later once all usages are switched to Lifecycle
+                onPause();
+            }
             synchronized (mEntriesMap) {
                 mSessions.remove(this);
             }
diff --git a/com/android/settingslib/applications/StorageStatsSource.java b/com/android/settingslib/applications/StorageStatsSource.java
index 8fc9fa6..9fbadee 100644
--- a/com/android/settingslib/applications/StorageStatsSource.java
+++ b/com/android/settingslib/applications/StorageStatsSource.java
@@ -131,7 +131,7 @@
         }
 
         public long getTotalBytes() {
-            return mStats.getCacheBytes() + mStats.getCodeBytes() + mStats.getDataBytes();
+            return mStats.getAppBytes() + mStats.getDataBytes();
         }
     }
 }
\ No newline at end of file
diff --git a/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java b/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java
index 47cbb77..6aae226 100644
--- a/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java
+++ b/com/android/settingslib/development/AbstractEnableAdbPreferenceController.java
@@ -28,17 +28,20 @@
 import android.support.v7.preference.TwoStatePreference;
 import android.text.TextUtils;
 
-import com.android.settingslib.core.AbstractPreferenceController;
 import com.android.settingslib.core.ConfirmationDialogController;
 
-public abstract class AbstractEnableAdbPreferenceController extends AbstractPreferenceController
-        implements ConfirmationDialogController {
+public abstract class AbstractEnableAdbPreferenceController extends
+        DeveloperOptionsPreferenceController implements ConfirmationDialogController {
     private static final String KEY_ENABLE_ADB = "enable_adb";
     public static final String ACTION_ENABLE_ADB_STATE_CHANGED =
             "com.android.settingslib.development.AbstractEnableAdbController."
                     + "ENABLE_ADB_STATE_CHANGED";
 
-    private SwitchPreference mPreference;
+    public static final int ADB_SETTING_ON = 1;
+    public static final int ADB_SETTING_OFF = 0;
+
+
+    protected SwitchPreference mPreference;
 
     public AbstractEnableAdbPreferenceController(Context context) {
         super(context);
@@ -64,12 +67,13 @@
 
     private boolean isAdbEnabled() {
         final ContentResolver cr = mContext.getContentResolver();
-        return Settings.Global.getInt(cr, Settings.Global.ADB_ENABLED, 0) != 0;
+        return Settings.Global.getInt(cr, Settings.Global.ADB_ENABLED, ADB_SETTING_OFF)
+                != ADB_SETTING_OFF;
     }
 
     @Override
     public void updateState(Preference preference) {
-        ((TwoStatePreference)preference).setChecked(isAdbEnabled());
+        ((TwoStatePreference) preference).setChecked(isAdbEnabled());
     }
 
     public void enablePreference(boolean enabled) {
@@ -105,7 +109,7 @@
 
     protected void writeAdbSetting(boolean enabled) {
         Settings.Global.putInt(mContext.getContentResolver(),
-                Settings.Global.ADB_ENABLED, enabled ? 1 : 0);
+                Settings.Global.ADB_ENABLED, enabled ? ADB_SETTING_ON : ADB_SETTING_OFF);
         notifyStateChanged();
     }
 
diff --git a/com/android/settingslib/development/AbstractLogdSizePreferenceController.java b/com/android/settingslib/development/AbstractLogdSizePreferenceController.java
index c167723..7998b2e 100644
--- a/com/android/settingslib/development/AbstractLogdSizePreferenceController.java
+++ b/com/android/settingslib/development/AbstractLogdSizePreferenceController.java
@@ -26,10 +26,9 @@
 import android.support.v7.preference.PreferenceScreen;
 
 import com.android.settingslib.R;
-import com.android.settingslib.core.AbstractPreferenceController;
 
-public abstract class AbstractLogdSizePreferenceController extends AbstractPreferenceController
-        implements Preference.OnPreferenceChangeListener {
+public abstract class AbstractLogdSizePreferenceController extends
+        DeveloperOptionsPreferenceController implements Preference.OnPreferenceChangeListener {
     public static final String ACTION_LOGD_SIZE_UPDATED = "com.android.settingslib.development."
             + "AbstractLogdSizePreferenceController.LOGD_SIZE_UPDATED";
     public static final String EXTRA_CURRENT_LOGD_VALUE = "CURRENT_LOGD_VALUE";
@@ -57,11 +56,6 @@
     }
 
     @Override
-    public boolean isAvailable() {
-        return true;
-    }
-
-    @Override
     public String getPreferenceKey() {
         return SELECT_LOGD_SIZE_KEY;
     }
diff --git a/com/android/settingslib/development/AbstractLogpersistPreferenceController.java b/com/android/settingslib/development/AbstractLogpersistPreferenceController.java
index 502fb17..67553ad 100644
--- a/com/android/settingslib/development/AbstractLogpersistPreferenceController.java
+++ b/com/android/settingslib/development/AbstractLogpersistPreferenceController.java
@@ -30,16 +30,15 @@
 import android.text.TextUtils;
 
 import com.android.settingslib.R;
-import com.android.settingslib.core.AbstractPreferenceController;
 import com.android.settingslib.core.ConfirmationDialogController;
 import com.android.settingslib.core.lifecycle.Lifecycle;
 import com.android.settingslib.core.lifecycle.LifecycleObserver;
 import com.android.settingslib.core.lifecycle.events.OnCreate;
 import com.android.settingslib.core.lifecycle.events.OnDestroy;
 
-public abstract class AbstractLogpersistPreferenceController extends AbstractPreferenceController
-        implements Preference.OnPreferenceChangeListener, LifecycleObserver, OnCreate, OnDestroy,
-        ConfirmationDialogController {
+public abstract class AbstractLogpersistPreferenceController extends
+        DeveloperOptionsPreferenceController implements Preference.OnPreferenceChangeListener,
+        LifecycleObserver, OnCreate, OnDestroy, ConfirmationDialogController {
 
     private static final String SELECT_LOGPERSIST_KEY = "select_logpersist";
     private static final String SELECT_LOGPERSIST_PROPERTY = "persist.logd.logpersistd";
diff --git a/com/android/settingslib/development/DeveloperOptionsPreferenceController.java b/com/android/settingslib/development/DeveloperOptionsPreferenceController.java
new file mode 100644
index 0000000..f68c04f
--- /dev/null
+++ b/com/android/settingslib/development/DeveloperOptionsPreferenceController.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.development;
+
+import android.content.Context;
+
+import com.android.settingslib.core.AbstractPreferenceController;
+
+/**
+ * This controller is used handle changes for the master switch in the developer options page.
+ *
+ * All Preference Controllers that are a part of the developer options page should inherit this
+ * class.
+ */
+public abstract class DeveloperOptionsPreferenceController extends
+        AbstractPreferenceController {
+
+    public DeveloperOptionsPreferenceController(Context context) {
+        super(context);
+    }
+
+    /**
+     * Child classes should override this method to create custom logic for hiding preferences.
+     *
+     * @return true if the preference is to be displayed.
+     */
+    @Override
+    public boolean isAvailable() {
+        return true;
+    }
+
+    /**
+     * Called when developer options is enabled
+     */
+    public void onDeveloperOptionsEnabled() {
+        if (isAvailable()) {
+            onDeveloperOptionsSwitchEnabled();
+        }
+    }
+
+    /**
+     * Called when developer options is disabled
+     */
+    public void onDeveloperOptionsDisabled() {
+        if (isAvailable()) {
+            onDeveloperOptionsSwitchDisabled();
+        }
+    }
+
+    /**
+     * Called when developer options is enabled and the preference is available
+     */
+    protected void onDeveloperOptionsSwitchEnabled() {
+    }
+
+    /**
+     * Called when developer options is disabled and the preference is available
+     */
+    protected void onDeveloperOptionsSwitchDisabled() {
+    }
+
+}
diff --git a/com/android/settingslib/deviceinfo/AbstractSerialNumberPreferenceController.java b/com/android/settingslib/deviceinfo/AbstractSerialNumberPreferenceController.java
new file mode 100644
index 0000000..ff7536a
--- /dev/null
+++ b/com/android/settingslib/deviceinfo/AbstractSerialNumberPreferenceController.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.deviceinfo;
+
+import android.content.Context;
+import android.os.Build;
+import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceScreen;
+import android.text.TextUtils;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.settingslib.core.AbstractPreferenceController;
+
+/**
+ * Preference controller for displaying device serial number. Wraps {@link Build#getSerial()}.
+ */
+public class AbstractSerialNumberPreferenceController extends AbstractPreferenceController {
+    private static final String KEY_SERIAL_NUMBER = "serial_number";
+
+    private final String mSerialNumber;
+
+    public AbstractSerialNumberPreferenceController(Context context) {
+        this(context, Build.getSerial());
+    }
+
+    @VisibleForTesting
+    AbstractSerialNumberPreferenceController(Context context, String serialNumber) {
+        super(context);
+        mSerialNumber = serialNumber;
+    }
+
+    @Override
+    public boolean isAvailable() {
+        return !TextUtils.isEmpty(mSerialNumber);
+    }
+
+    @Override
+    public void displayPreference(PreferenceScreen screen) {
+        super.displayPreference(screen);
+        final Preference pref = screen.findPreference(KEY_SERIAL_NUMBER);
+        if (pref != null) {
+            pref.setSummary(mSerialNumber);
+        }
+    }
+
+    @Override
+    public String getPreferenceKey() {
+        return KEY_SERIAL_NUMBER;
+    }
+}
diff --git a/com/android/settingslib/drawer/TileUtils.java b/com/android/settingslib/drawer/TileUtils.java
index 9b75c00..35ba6ae 100644
--- a/com/android/settingslib/drawer/TileUtils.java
+++ b/com/android/settingslib/drawer/TileUtils.java
@@ -26,7 +26,6 @@
 import android.content.res.Resources;
 import android.graphics.drawable.Icon;
 import android.net.Uri;
-import android.os.AsyncTask;
 import android.os.Bundle;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -320,6 +319,15 @@
             Context context, UserHandle user, Intent intent,
             Map<Pair<String, String>, Tile> addedCache, String defaultCategory, List<Tile> outTiles,
             boolean usePriority, boolean checkCategory, boolean forceTintExternalIcon) {
+        getTilesForIntent(context, user, intent, addedCache, defaultCategory, outTiles,
+                usePriority, checkCategory, forceTintExternalIcon, false /* shouldUpdateTiles */);
+    }
+
+    public static void getTilesForIntent(
+            Context context, UserHandle user, Intent intent,
+            Map<Pair<String, String>, Tile> addedCache, String defaultCategory, List<Tile> outTiles,
+            boolean usePriority, boolean checkCategory, boolean forceTintExternalIcon,
+            boolean shouldUpdateTiles) {
         PackageManager pm = context.getPackageManager();
         List<ResolveInfo> results = pm.queryIntentActivitiesAsUser(intent,
                 PackageManager.GET_META_DATA, user.getIdentifier());
@@ -357,9 +365,11 @@
                 updateTileData(context, tile, activityInfo, activityInfo.applicationInfo,
                         pm, providerMap, forceTintExternalIcon);
                 if (DEBUG) Log.d(LOG_TAG, "Adding tile " + tile.title);
-
                 addedCache.put(key, tile);
+            } else if (shouldUpdateTiles) {
+                updateSummaryAndTitle(context, providerMap, tile);
             }
+
             if (!tile.userHandle.contains(user)) {
                 tile.userHandle.add(user);
             }
@@ -380,7 +390,6 @@
             String summary = null;
             String keyHint = null;
             boolean isIconTintable = false;
-            RemoteViews remoteViews = null;
 
             // Get the activity's meta-data
             try {
@@ -428,7 +437,8 @@
                     }
                     if (metaData.containsKey(META_DATA_PREFERENCE_CUSTOM_VIEW)) {
                         int layoutId = metaData.getInt(META_DATA_PREFERENCE_CUSTOM_VIEW);
-                        remoteViews = new RemoteViews(applicationInfo.packageName, layoutId);
+                        tile.remoteViews = new RemoteViews(applicationInfo.packageName, layoutId);
+                        updateSummaryAndTitle(context, providerMap, tile);
                     }
                 }
             } catch (PackageManager.NameNotFoundException | Resources.NotFoundException e) {
@@ -462,7 +472,6 @@
             // Suggest a key for this tile
             tile.key = keyHint;
             tile.isIconTintable = isIconTintable;
-            tile.remoteViews = remoteViews;
 
             return true;
         }
@@ -470,6 +479,26 @@
         return false;
     }
 
+    private static void updateSummaryAndTitle(
+            Context context, Map<String, IContentProvider> providerMap, Tile tile) {
+        if (tile == null || tile.metaData == null
+                || !tile.metaData.containsKey(META_DATA_PREFERENCE_SUMMARY_URI)) {
+            return;
+        }
+
+        String uriString = tile.metaData.getString(META_DATA_PREFERENCE_SUMMARY_URI);
+        Bundle bundle = getBundleFromUri(context, uriString, providerMap);
+        String overrideSummary = getString(bundle, META_DATA_PREFERENCE_SUMMARY);
+        String overrideTitle = getString(bundle, META_DATA_PREFERENCE_TITLE);
+        if (overrideSummary != null) {
+            tile.remoteViews.setTextViewText(android.R.id.summary, overrideSummary);
+        }
+
+        if (overrideTitle != null) {
+            tile.remoteViews.setTextViewText(android.R.id.title, overrideTitle);
+        }
+    }
+
     /**
      * Gets the icon package name and resource id from content provider.
      * @param context context
@@ -535,37 +564,6 @@
         }
     }
 
-    public static void updateTileUsingSummaryUri(Context context, final Tile tile) {
-        if (tile == null || tile.metaData == null ||
-                !tile.metaData.containsKey(META_DATA_PREFERENCE_SUMMARY_URI)) {
-            return;
-        }
-
-        new AsyncTask<Void, Void, Bundle>() {
-            @Override
-            protected Bundle doInBackground(Void... params) {
-                return getBundleFromUri(context,
-                        tile.metaData.getString(META_DATA_PREFERENCE_SUMMARY_URI), new HashMap<>());
-            }
-
-            @Override
-            protected void onPostExecute(Bundle bundle) {
-                if (bundle == null) {
-                    return;
-                }
-                final String overrideSummary = getString(bundle, META_DATA_PREFERENCE_SUMMARY);
-                final String overrideTitle = getString(bundle, META_DATA_PREFERENCE_TITLE);
-
-                if (overrideSummary != null) {
-                    tile.remoteViews.setTextViewText(android.R.id.summary, overrideSummary);
-                }
-                if (overrideTitle != null) {
-                    tile.remoteViews.setTextViewText(android.R.id.title, overrideTitle);
-                }
-            }
-        }.execute();
-    }
-
     private static String getString(Bundle bundle, String key) {
         return bundle == null ? null : bundle.getString(key);
     }
diff --git a/com/android/settingslib/graph/BluetoothDeviceLayerDrawable.java b/com/android/settingslib/graph/BluetoothDeviceLayerDrawable.java
index b7fd404..3c5ac8d 100644
--- a/com/android/settingslib/graph/BluetoothDeviceLayerDrawable.java
+++ b/com/android/settingslib/graph/BluetoothDeviceLayerDrawable.java
@@ -73,7 +73,7 @@
         final Drawable deviceDrawable = context.getDrawable(resId);
 
         final BatteryMeterDrawable batteryDrawable = new BatteryMeterDrawable(context,
-                R.color.meter_background_color, batteryLevel);
+                context.getColor(R.color.meter_background_color), batteryLevel);
         final int pad = context.getResources().getDimensionPixelSize(R.dimen.bt_battery_padding);
         batteryDrawable.setPadding(pad, pad, pad, pad);
 
@@ -107,6 +107,8 @@
     @VisibleForTesting
     static class BatteryMeterDrawable extends BatteryMeterDrawableBase {
         private final float mAspectRatio;
+        @VisibleForTesting
+        int mFrameColor;
 
         public BatteryMeterDrawable(Context context, int frameColor, int batteryLevel) {
             super(context, frameColor);
@@ -118,6 +120,7 @@
             final int tintColor = Utils.getColorAttr(context, android.R.attr.colorControlNormal);
             setColorFilter(new PorterDuffColorFilter(tintColor, PorterDuff.Mode.SRC_IN));
             setBatteryLevel(batteryLevel);
+            mFrameColor = frameColor;
         }
 
         @Override
diff --git a/com/android/settingslib/suggestions/SuggestionParser.java b/com/android/settingslib/suggestions/SuggestionParser.java
index 00f32b2..56b8441 100644
--- a/com/android/settingslib/suggestions/SuggestionParser.java
+++ b/com/android/settingslib/suggestions/SuggestionParser.java
@@ -195,7 +195,7 @@
             intent.setPackage(category.pkg);
         }
         TileUtils.getTilesForIntent(mContext, new UserHandle(UserHandle.myUserId()), intent,
-                mAddCache, null, suggestions, true, false, false);
+                mAddCache, null, suggestions, true, false, false, true /* shouldUpdateTiles */);
         filterSuggestions(suggestions, countBefore, isSmartSuggestionEnabled);
         if (!category.multiple && suggestions.size() > (countBefore + 1)) {
             // If there are too many, remove them all and only re-add the one with the highest
diff --git a/com/android/settingslib/wifi/WifiTracker.java b/com/android/settingslib/wifi/WifiTracker.java
index 664dcfc..12455d8 100644
--- a/com/android/settingslib/wifi/WifiTracker.java
+++ b/com/android/settingslib/wifi/WifiTracker.java
@@ -24,7 +24,6 @@
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
-import android.net.NetworkInfo.DetailedState;
 import android.net.NetworkKey;
 import android.net.NetworkRequest;
 import android.net.NetworkScoreManager;
@@ -36,10 +35,14 @@
 import android.net.wifi.WifiNetworkScoreCache;
 import android.net.wifi.WifiNetworkScoreCache.CacheListener;
 import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Message;
+import android.os.Process;
 import android.provider.Settings;
 import android.support.annotation.GuardedBy;
+import android.support.annotation.NonNull;
+import android.support.annotation.VisibleForTesting;
 import android.text.format.DateUtils;
 import android.util.ArraySet;
 import android.util.Log;
@@ -47,8 +50,12 @@
 import android.util.SparseIntArray;
 import android.widget.Toast;
 
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.settingslib.R;
+import com.android.settingslib.core.lifecycle.Lifecycle;
+import com.android.settingslib.core.lifecycle.LifecycleObserver;
+import com.android.settingslib.core.lifecycle.events.OnDestroy;
+import com.android.settingslib.core.lifecycle.events.OnStart;
+import com.android.settingslib.core.lifecycle.events.OnStop;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -64,7 +71,7 @@
 /**
  * Tracks saved or available wifi networks and their state.
  */
-public class WifiTracker {
+public class WifiTracker implements LifecycleObserver, OnStart, OnStop, OnDestroy {
     /**
      * Default maximum age in millis of cached scored networks in
      * {@link AccessPoint#mScoredNetworkCache} to be used for speed label generation.
@@ -80,7 +87,7 @@
      * and used so as to assist with in-the-field WiFi connectivity debugging  */
     public static boolean sVerboseLogging;
 
-    // TODO(b/36733768): Remove flag includeSaved and includePasspoints.
+    // TODO(b/36733768): Remove flag includeSaved
 
     // TODO: Allow control of this?
     // Combo scans can take 5-6s to complete - set to 10s.
@@ -96,9 +103,9 @@
     private final WifiListener mListener;
     private final boolean mIncludeSaved;
     private final boolean mIncludeScans;
-    private final boolean mIncludePasspoints;
-    @VisibleForTesting final MainHandler mMainHandler;
-    @VisibleForTesting final WorkHandler mWorkHandler;
+    @VisibleForTesting MainHandler mMainHandler;
+    @VisibleForTesting WorkHandler mWorkHandler;
+    private HandlerThread mWorkThread;
 
     private WifiTrackerNetworkCallback mNetworkCallback;
 
@@ -142,7 +149,7 @@
     private WifiInfo mLastInfo;
 
     private final NetworkScoreManager mNetworkScoreManager;
-    private final WifiNetworkScoreCache mScoreCache;
+    private WifiNetworkScoreCache mScoreCache;
     private boolean mNetworkScoringUiEnabled;
     private long mMaxSpeedLabelScoreCacheAge;
 
@@ -169,51 +176,43 @@
         return filter;
     }
 
+    /**
+     * Use the lifecycle constructor below whenever possible
+     */
+    @Deprecated
     public WifiTracker(Context context, WifiListener wifiListener,
             boolean includeSaved, boolean includeScans) {
-        this(context, wifiListener, null, includeSaved, includeScans);
-    }
-
-    public WifiTracker(Context context, WifiListener wifiListener, Looper workerLooper,
-            boolean includeSaved, boolean includeScans) {
-        this(context, wifiListener, workerLooper, includeSaved, includeScans, false);
-    }
-
-    public WifiTracker(Context context, WifiListener wifiListener,
-            boolean includeSaved, boolean includeScans, boolean includePasspoints) {
-        this(context, wifiListener, null, includeSaved, includeScans, includePasspoints);
-    }
-
-    public WifiTracker(Context context, WifiListener wifiListener, Looper workerLooper,
-                       boolean includeSaved, boolean includeScans, boolean includePasspoints) {
-        this(context, wifiListener, workerLooper, includeSaved, includeScans, includePasspoints,
+        this(context, wifiListener, includeSaved, includeScans,
                 context.getSystemService(WifiManager.class),
                 context.getSystemService(ConnectivityManager.class),
                 context.getSystemService(NetworkScoreManager.class),
-                Looper.myLooper(), newIntentFilter());
+                newIntentFilter());
+    }
+
+    public WifiTracker(Context context, WifiListener wifiListener,
+            @NonNull Lifecycle lifecycle, boolean includeSaved, boolean includeScans) {
+        this(context, wifiListener, includeSaved, includeScans,
+                context.getSystemService(WifiManager.class),
+                context.getSystemService(ConnectivityManager.class),
+                context.getSystemService(NetworkScoreManager.class),
+                newIntentFilter());
+        lifecycle.addObserver(this);
     }
 
     @VisibleForTesting
-    WifiTracker(Context context, WifiListener wifiListener, Looper workerLooper,
-                boolean includeSaved, boolean includeScans, boolean includePasspoints,
-                WifiManager wifiManager, ConnectivityManager connectivityManager,
-                NetworkScoreManager networkScoreManager, Looper currentLooper,
-                IntentFilter filter) {
+    WifiTracker(Context context, WifiListener wifiListener,
+            boolean includeSaved, boolean includeScans,
+            WifiManager wifiManager, ConnectivityManager connectivityManager,
+            NetworkScoreManager networkScoreManager,
+            IntentFilter filter) {
         if (!includeSaved && !includeScans) {
             throw new IllegalArgumentException("Must include either saved or scans");
         }
         mContext = context;
-        if (currentLooper == null) {
-            // When we aren't on a looper thread, default to the main.
-            currentLooper = Looper.getMainLooper();
-        }
-        mMainHandler = new MainHandler(currentLooper);
-        mWorkHandler = new WorkHandler(
-                workerLooper != null ? workerLooper : currentLooper);
+        mMainHandler = new MainHandler(Looper.getMainLooper());
         mWifiManager = wifiManager;
         mIncludeSaved = includeSaved;
         mIncludeScans = includeScans;
-        mIncludePasspoints = includePasspoints;
         mListener = wifiListener;
         mConnectivityManager = connectivityManager;
 
@@ -229,7 +228,22 @@
 
         mNetworkScoreManager = networkScoreManager;
 
-        mScoreCache = new WifiNetworkScoreCache(context, new CacheListener(mWorkHandler) {
+        final HandlerThread workThread = new HandlerThread(TAG
+                + "{" + Integer.toHexString(System.identityHashCode(this)) + "}",
+                Process.THREAD_PRIORITY_BACKGROUND);
+        workThread.start();
+        setWorkThread(workThread);
+    }
+
+    /**
+     * Sanity warning: this wipes out mScoreCache, so use with extreme caution
+     * @param workThread substitute Handler thread, for testing purposes only
+     */
+    @VisibleForTesting
+    void setWorkThread(HandlerThread workThread) {
+        mWorkThread = workThread;
+        mWorkHandler = new WorkHandler(workThread.getLooper());
+        mScoreCache = new WifiNetworkScoreCache(mContext, new CacheListener(mWorkHandler) {
             @Override
             public void networkCacheUpdated(List<ScoredNetwork> networks) {
                 synchronized (mLock) {
@@ -244,6 +258,11 @@
         });
     }
 
+    @Override
+    public void onDestroy() {
+        mWorkThread.quit();
+    }
+
     /** Synchronously update the list of access points with the latest information. */
     @MainThread
     public void forceUpdate() {
@@ -312,8 +331,9 @@
      * <p>Registers listeners and starts scanning for wifi networks. If this is not called
      * then forceUpdate() must be called to populate getAccessPoints().
      */
+    @Override
     @MainThread
-    public void startTracking() {
+    public void onStart() {
         synchronized (mLock) {
             registerScoreCache();
 
@@ -361,15 +381,16 @@
     /**
      * Stop tracking wifi networks and scores.
      *
-     * <p>This should always be called when done with a WifiTracker (if startTracking was called) to
+     * <p>This should always be called when done with a WifiTracker (if onStart was called) to
      * ensure proper cleanup and prevent any further callbacks from occurring.
      *
      * <p>Calling this method will set the {@link #mStaleScanResults} bit, which prevents
      * {@link WifiListener#onAccessPointsChanged()} callbacks from being invoked (until the bit
      * is unset on the next SCAN_RESULTS_AVAILABLE_ACTION).
      */
+    @Override
     @MainThread
-    public void stopTracking() {
+    public void onStop() {
         synchronized (mLock) {
             if (mRegistered) {
                 mContext.unregisterReceiver(mReceiver);
@@ -769,9 +790,8 @@
     }
 
     public static List<AccessPoint> getCurrentAccessPoints(Context context, boolean includeSaved,
-            boolean includeScans, boolean includePasspoints) {
-        WifiTracker tracker = new WifiTracker(context,
-                null, null, includeSaved, includeScans, includePasspoints);
+            boolean includeScans) {
+        WifiTracker tracker = new WifiTracker(context, null, includeSaved, includeScans);
         tracker.forceUpdate();
         tracker.copyAndNotifyListeners(false /*notifyListeners*/);
         return tracker.getAccessPoints();
diff --git a/com/android/settingslib/wifi/WifiTrackerFactory.java b/com/android/settingslib/wifi/WifiTrackerFactory.java
index 79cee04..8b5863a 100644
--- a/com/android/settingslib/wifi/WifiTrackerFactory.java
+++ b/com/android/settingslib/wifi/WifiTrackerFactory.java
@@ -16,8 +16,10 @@
 package com.android.settingslib.wifi;
 
 import android.content.Context;
-import android.os.Looper;
 import android.support.annotation.Keep;
+import android.support.annotation.NonNull;
+
+import com.android.settingslib.core.lifecycle.Lifecycle;
 
 /**
  * Factory method used to inject WifiTracker instances.
@@ -31,12 +33,11 @@
     }
 
     public static WifiTracker create(
-            Context context, WifiTracker.WifiListener wifiListener, Looper workerLooper,
-            boolean includeSaved, boolean includeScans, boolean includePasspoints) {
+            Context context, WifiTracker.WifiListener wifiListener, @NonNull Lifecycle lifecycle,
+            boolean includeSaved, boolean includeScans) {
         if(sTestingWifiTracker != null) {
             return sTestingWifiTracker;
         }
-        return new WifiTracker(
-                context, wifiListener, workerLooper, includeSaved, includeScans, includePasspoints);
+        return new WifiTracker(context, wifiListener, lifecycle, includeSaved, includeScans);
     }
 }
diff --git a/com/android/settingslib/wrapper/PackageManagerWrapper.java b/com/android/settingslib/wrapper/PackageManagerWrapper.java
index cd62bc3..b1f3f3c 100644
--- a/com/android/settingslib/wrapper/PackageManagerWrapper.java
+++ b/com/android/settingslib/wrapper/PackageManagerWrapper.java
@@ -60,6 +60,13 @@
     }
 
     /**
+     * Calls {@code PackageManager.getInstalledPackagesAsUser}
+     */
+    public List<PackageInfo> getInstalledPackagesAsUser(int flags, int userId) {
+        return mPm.getInstalledPackagesAsUser(flags, userId);
+    }
+
+    /**
      * Calls {@code PackageManager.hasSystemFeature()}.
      *
      * @see android.content.pm.PackageManager#hasSystemFeature
@@ -132,11 +139,11 @@
 
     /**
      * Gets information about a particular package from the package manager.
+     *
      * @param packageName The name of the package we would like information about.
-     * @param i additional options flags. see javadoc for
-     * {@link PackageManager#getPackageInfo(String, int)}
+     * @param i           additional options flags. see javadoc for
+     *                    {@link PackageManager#getPackageInfo(String, int)}
      * @return The PackageInfo for the requested package
-     * @throws NameNotFoundException
      */
     public PackageInfo getPackageInfo(String packageName, int i) throws NameNotFoundException {
         return mPm.getPackageInfo(packageName, i);
@@ -144,6 +151,7 @@
 
     /**
      * Retrieves the icon associated with this particular set of ApplicationInfo
+     *
      * @param info The ApplicationInfo to retrieve the icon for
      * @return The icon as a drawable.
      */
@@ -154,6 +162,7 @@
 
     /**
      * Retrieves the label associated with the particular set of ApplicationInfo
+     *
      * @param app The ApplicationInfo to retrieve the label for
      * @return the label as a CharSequence
      */
@@ -190,4 +199,48 @@
             throws PackageManager.NameNotFoundException {
         return mPm.getPackageUidAsUser(pkg, userId);
     }
+
+    /**
+     * Calls {@code PackageManager.setApplicationEnabledSetting}
+     */
+    public void setApplicationEnabledSetting(String packageName, int newState, int flags) {
+        mPm.setApplicationEnabledSetting(packageName, newState, flags);
+    }
+
+    /**
+     * Calls {@code PackageManager.getApplicationEnabledSetting}
+     */
+    public int getApplicationEnabledSetting(String packageName) {
+        return mPm.getApplicationEnabledSetting(packageName);
+    }
+
+    /**
+     * Calls {@code PackageManager.setComponentEnabledSetting}
+     */
+    public void setComponentEnabledSetting(ComponentName componentName, int newState, int flags) {
+        mPm.setComponentEnabledSetting(componentName, newState, flags);
+    }
+
+    /**
+     * Calls {@code PackageManager.getApplicationInfo}
+     */
+    public ApplicationInfo getApplicationInfo(String packageName, int flags)
+            throws NameNotFoundException {
+        return mPm.getApplicationInfo(packageName, flags);
+    }
+
+    /**
+     * Calls {@code PackageManager.getApplicationLabel}
+     */
+    public CharSequence getApplicationLabel(ApplicationInfo info) {
+        return mPm.getApplicationLabel(info);
+    }
+
+    /**
+     * Calls {@code PackageManager.queryBroadcastReceivers}
+     */
+    public List<ResolveInfo> queryBroadcastReceivers(Intent intent, int flags) {
+        return mPm.queryBroadcastReceivers(intent, flags);
+    }
 }
+
diff --git a/com/android/setupwizardlib/test/util/DrawingTestActivity.java b/com/android/setupwizardlib/test/util/DrawingTestActivity.java
index 154339a..3d11e12 100644
--- a/com/android/setupwizardlib/test/util/DrawingTestActivity.java
+++ b/com/android/setupwizardlib/test/util/DrawingTestActivity.java
@@ -16,15 +16,14 @@
 
 package com.android.setupwizardlib.test.util;
 
-import android.support.v7.app.AppCompatActivity;
+import android.app.Activity;
 
 /**
  * Activity to test view and drawable drawing behaviors. This is used to make sure that the drawing
- * behavior tested is the same as it would when inflated as part of an {@link AppCompatActivity},
- * including custom layout inflaters and theme values that the support library injects to the
- * activity.
+ * behavior tested is the same as it would when inflated as part of an activity, including any
+ * injected layout inflater factories and custom themes etc.
  *
  * @see DrawingTestHelper
  */
-public class DrawingTestActivity extends AppCompatActivity {
+public class DrawingTestActivity extends Activity {
 }
diff --git a/com/android/setupwizardlib/util/LinkAccessibilityHelper.java b/com/android/setupwizardlib/util/LinkAccessibilityHelper.java
index 1e663d6..1fb3a37 100644
--- a/com/android/setupwizardlib/util/LinkAccessibilityHelper.java
+++ b/com/android/setupwizardlib/util/LinkAccessibilityHelper.java
@@ -19,6 +19,8 @@
 import android.graphics.Rect;
 import android.os.Build;
 import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.VisibleForTesting;
 import android.support.v4.view.AccessibilityDelegateCompat;
 import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
 import android.support.v4.view.accessibility.AccessibilityNodeProviderCompat;
@@ -38,12 +40,11 @@
 /**
  * An accessibility delegate that allows {@link android.text.style.ClickableSpan} to be focused and
  * clicked by accessibility services.
- * <p>
- * <strong>Note: </strong> From Android O on, there is native support for ClickableSpan
- * accessibility, so this class is not needed (and indeed has no effect.)
- * </p>
  *
- * <p />Sample usage:
+ * <p><strong>Note:</strong> This class is a no-op on Android O or above since there is native
+ * support for ClickableSpan accessibility.
+ *
+ * <p>Sample usage:
  * <pre>
  * LinkAccessibilityHelper mAccessibilityHelper;
  *
@@ -68,294 +69,255 @@
 
     private static final String TAG = "LinkAccessibilityHelper";
 
-    private final TextView mView;
-    private final Rect mTempRect = new Rect();
-    private final ExploreByTouchHelper mExploreByTouchHelper;
+    private final AccessibilityDelegateCompat mDelegate;
 
     public LinkAccessibilityHelper(TextView view) {
-        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N_MR1) {
-            // Pre-O, we essentially extend ExploreByTouchHelper to expose a virtual view hierarchy
-            mExploreByTouchHelper = new ExploreByTouchHelper(view) {
-                @Override
-                protected int getVirtualViewAt(float x, float y) {
-                    return LinkAccessibilityHelper.this.getVirtualViewAt(x, y);
-                }
+        this(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
+                // Platform support was added in O. This helper will be no-op
+                ? new AccessibilityDelegateCompat()
+                // Pre-O, we extend ExploreByTouchHelper to expose a virtual view hierarchy
+                : new PreOLinkAccessibilityHelper(view));
+    }
 
-                @Override
-                protected void getVisibleVirtualViews(List<Integer> virtualViewIds) {
-                    LinkAccessibilityHelper.this.getVisibleVirtualViews(virtualViewIds);
-                }
-
-                @Override
-                protected void onPopulateEventForVirtualView(int virtualViewId,
-                        AccessibilityEvent event) {
-                    LinkAccessibilityHelper
-                            .this.onPopulateEventForVirtualView(virtualViewId, event);
-                }
-
-                @Override
-                protected void onPopulateNodeForVirtualView(int virtualViewId,
-                        AccessibilityNodeInfoCompat infoCompat) {
-                    LinkAccessibilityHelper
-                            .this.onPopulateNodeForVirtualView(virtualViewId, infoCompat);
-
-                }
-
-                @Override
-                protected boolean onPerformActionForVirtualView(int virtualViewId, int action,
-                        Bundle arguments) {
-                    return LinkAccessibilityHelper.this
-                            .onPerformActionForVirtualView(virtualViewId, action, arguments);
-                }
-            };
-        } else {
-            mExploreByTouchHelper = null;
-        }
-        mView = view;
+    @VisibleForTesting
+    LinkAccessibilityHelper(@NonNull AccessibilityDelegateCompat delegate) {
+        mDelegate = delegate;
     }
 
     @Override
     public void sendAccessibilityEvent(View host, int eventType) {
-        if (mExploreByTouchHelper != null) {
-            mExploreByTouchHelper.sendAccessibilityEvent(host, eventType);
-        } else {
-            super.sendAccessibilityEvent(host, eventType);
-        }
+        mDelegate.sendAccessibilityEvent(host, eventType);
     }
 
     @Override
     public void sendAccessibilityEventUnchecked(View host, AccessibilityEvent event) {
-        if (mExploreByTouchHelper != null) {
-            mExploreByTouchHelper.sendAccessibilityEventUnchecked(host, event);
-        } else {
-            super.sendAccessibilityEventUnchecked(host, event);
-        }
+        mDelegate.sendAccessibilityEventUnchecked(host, event);
     }
 
     @Override
     public boolean dispatchPopulateAccessibilityEvent(View host, AccessibilityEvent event) {
-        return (mExploreByTouchHelper != null)
-                ? mExploreByTouchHelper.dispatchPopulateAccessibilityEvent(host, event)
-                : super.dispatchPopulateAccessibilityEvent(host, event);
+        return mDelegate.dispatchPopulateAccessibilityEvent(host, event);
     }
 
     @Override
     public void onPopulateAccessibilityEvent(View host, AccessibilityEvent event) {
-        if (mExploreByTouchHelper != null) {
-            mExploreByTouchHelper.onPopulateAccessibilityEvent(host, event);
-        } else {
-            super.onPopulateAccessibilityEvent(host, event);
-        }
+        mDelegate.onPopulateAccessibilityEvent(host, event);
     }
 
     @Override
     public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
-        if (mExploreByTouchHelper != null) {
-            mExploreByTouchHelper.onInitializeAccessibilityEvent(host, event);
-        } else {
-            super.onInitializeAccessibilityEvent(host, event);
-        }
+        mDelegate.onInitializeAccessibilityEvent(host, event);
     }
 
     @Override
     public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
-        if (mExploreByTouchHelper != null) {
-            mExploreByTouchHelper.onInitializeAccessibilityNodeInfo(host, info);
-        } else {
-            super.onInitializeAccessibilityNodeInfo(host, info);
-        }
+        mDelegate.onInitializeAccessibilityNodeInfo(host, info);
     }
 
     @Override
     public boolean onRequestSendAccessibilityEvent(ViewGroup host, View child,
             AccessibilityEvent event) {
-        return (mExploreByTouchHelper != null)
-                ? mExploreByTouchHelper.onRequestSendAccessibilityEvent(host, child, event)
-                : super.onRequestSendAccessibilityEvent(host, child, event);
+        return mDelegate.onRequestSendAccessibilityEvent(host, child, event);
     }
 
     @Override
     public AccessibilityNodeProviderCompat getAccessibilityNodeProvider(View host) {
-        return (mExploreByTouchHelper != null)
-                ? mExploreByTouchHelper.getAccessibilityNodeProvider(host)
-                : super.getAccessibilityNodeProvider(host);
+        return mDelegate.getAccessibilityNodeProvider(host);
     }
 
     @Override
     public boolean performAccessibilityAction(View host, int action, Bundle args) {
-        return (mExploreByTouchHelper != null)
-                ? mExploreByTouchHelper.performAccessibilityAction(host, action, args)
-                : super.performAccessibilityAction(host, action, args);
+        return mDelegate.performAccessibilityAction(host, action, args);
     }
 
     /**
-     * Delegated to {@link ExploreByTouchHelper}
+     * Dispatches hover event to the virtual view hierarchy. This method should be called in
+     * {@link View#dispatchHoverEvent(MotionEvent)}.
+     *
+     * @see ExploreByTouchHelper#dispatchHoverEvent(MotionEvent)
      */
     public final boolean dispatchHoverEvent(MotionEvent event) {
-        return (mExploreByTouchHelper != null) ? mExploreByTouchHelper.dispatchHoverEvent(event)
-                : false;
+        return mDelegate instanceof ExploreByTouchHelper
+                && ((ExploreByTouchHelper) mDelegate).dispatchHoverEvent(event);
     }
 
-    protected int getVirtualViewAt(float x, float y) {
-        final CharSequence text = mView.getText();
-        if (text instanceof Spanned) {
-            final Spanned spannedText = (Spanned) text;
-            final int offset = getOffsetForPosition(mView, x, y);
-            ClickableSpan[] linkSpans = spannedText.getSpans(offset, offset, ClickableSpan.class);
-            if (linkSpans.length == 1) {
-                ClickableSpan linkSpan = linkSpans[0];
-                return spannedText.getSpanStart(linkSpan);
+    @VisibleForTesting
+    static class PreOLinkAccessibilityHelper extends ExploreByTouchHelper {
+
+        private final Rect mTempRect = new Rect();
+        private final TextView mView;
+
+        PreOLinkAccessibilityHelper(TextView view) {
+            super(view);
+            mView = view;
+        }
+
+        protected int getVirtualViewAt(float x, float y) {
+            final CharSequence text = mView.getText();
+            if (text instanceof Spanned) {
+                final Spanned spannedText = (Spanned) text;
+                final int offset = getOffsetForPosition(mView, x, y);
+                ClickableSpan[] linkSpans =
+                        spannedText.getSpans(offset, offset, ClickableSpan.class);
+                if (linkSpans.length == 1) {
+                    ClickableSpan linkSpan = linkSpans[0];
+                    return spannedText.getSpanStart(linkSpan);
+                }
+            }
+            return ExploreByTouchHelper.INVALID_ID;
+        }
+
+        protected void getVisibleVirtualViews(List<Integer> virtualViewIds) {
+            final CharSequence text = mView.getText();
+            if (text instanceof Spanned) {
+                final Spanned spannedText = (Spanned) text;
+                ClickableSpan[] linkSpans = spannedText.getSpans(0, spannedText.length(),
+                        ClickableSpan.class);
+                for (ClickableSpan span : linkSpans) {
+                    virtualViewIds.add(spannedText.getSpanStart(span));
+                }
             }
         }
-        return ExploreByTouchHelper.INVALID_ID;
-    }
 
-    protected void getVisibleVirtualViews(List<Integer> virtualViewIds) {
-        final CharSequence text = mView.getText();
-        if (text instanceof Spanned) {
-            final Spanned spannedText = (Spanned) text;
-            ClickableSpan[] linkSpans = spannedText.getSpans(0, spannedText.length(),
-                    ClickableSpan.class);
-            for (ClickableSpan span : linkSpans) {
-                virtualViewIds.add(spannedText.getSpanStart(span));
-            }
-        }
-    }
-
-    protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) {
-        final ClickableSpan span = getSpanForOffset(virtualViewId);
-        if (span != null) {
-            event.setContentDescription(getTextForSpan(span));
-        } else {
-            Log.e(TAG, "LinkSpan is null for offset: " + virtualViewId);
-            event.setContentDescription(mView.getText());
-        }
-    }
-
-    protected void onPopulateNodeForVirtualView(int virtualViewId,
-            AccessibilityNodeInfoCompat info) {
-        final ClickableSpan span = getSpanForOffset(virtualViewId);
-        if (span != null) {
-            info.setContentDescription(getTextForSpan(span));
-        } else {
-            Log.e(TAG, "LinkSpan is null for offset: " + virtualViewId);
-            info.setContentDescription(mView.getText());
-        }
-        info.setFocusable(true);
-        info.setClickable(true);
-        getBoundsForSpan(span, mTempRect);
-        if (mTempRect.isEmpty()) {
-            Log.e(TAG, "LinkSpan bounds is empty for: " + virtualViewId);
-            mTempRect.set(0, 0, 1, 1);
-        }
-        info.setBoundsInParent(mTempRect);
-        info.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK);
-    }
-
-    protected boolean onPerformActionForVirtualView(int virtualViewId, int action,
-            Bundle arguments) {
-        if (action == AccessibilityNodeInfoCompat.ACTION_CLICK) {
-            ClickableSpan span = getSpanForOffset(virtualViewId);
+        protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) {
+            final ClickableSpan span = getSpanForOffset(virtualViewId);
             if (span != null) {
-                span.onClick(mView);
-                return true;
+                event.setContentDescription(getTextForSpan(span));
             } else {
                 Log.e(TAG, "LinkSpan is null for offset: " + virtualViewId);
+                event.setContentDescription(mView.getText());
             }
         }
-        return false;
-    }
 
-    private ClickableSpan getSpanForOffset(int offset) {
-        CharSequence text = mView.getText();
-        if (text instanceof Spanned) {
-            Spanned spannedText = (Spanned) text;
-            ClickableSpan[] spans = spannedText.getSpans(offset, offset, ClickableSpan.class);
-            if (spans.length == 1) {
-                return spans[0];
+        protected void onPopulateNodeForVirtualView(
+                int virtualViewId,
+                AccessibilityNodeInfoCompat info) {
+            final ClickableSpan span = getSpanForOffset(virtualViewId);
+            if (span != null) {
+                info.setContentDescription(getTextForSpan(span));
+            } else {
+                Log.e(TAG, "LinkSpan is null for offset: " + virtualViewId);
+                info.setContentDescription(mView.getText());
             }
+            info.setFocusable(true);
+            info.setClickable(true);
+            getBoundsForSpan(span, mTempRect);
+            if (mTempRect.isEmpty()) {
+                Log.e(TAG, "LinkSpan bounds is empty for: " + virtualViewId);
+                mTempRect.set(0, 0, 1, 1);
+            }
+            info.setBoundsInParent(mTempRect);
+            info.addAction(AccessibilityNodeInfoCompat.ACTION_CLICK);
         }
-        return null;
-    }
 
-    private CharSequence getTextForSpan(ClickableSpan span) {
-        CharSequence text = mView.getText();
-        if (text instanceof Spanned) {
-            Spanned spannedText = (Spanned) text;
-            return spannedText.subSequence(spannedText.getSpanStart(span),
-                    spannedText.getSpanEnd(span));
-        }
-        return text;
-    }
-
-    // Find the bounds of a span. If it spans multiple lines, it will only return the bounds for the
-    // section on the first line.
-    private Rect getBoundsForSpan(ClickableSpan span, Rect outRect) {
-        CharSequence text = mView.getText();
-        outRect.setEmpty();
-        if (text instanceof Spanned) {
-            final Layout layout = mView.getLayout();
-            if (layout != null) {
-                Spanned spannedText = (Spanned) text;
-                final int spanStart = spannedText.getSpanStart(span);
-                final int spanEnd = spannedText.getSpanEnd(span);
-                final float xStart = layout.getPrimaryHorizontal(spanStart);
-                final float xEnd = layout.getPrimaryHorizontal(spanEnd);
-                final int lineStart = layout.getLineForOffset(spanStart);
-                final int lineEnd = layout.getLineForOffset(spanEnd);
-                layout.getLineBounds(lineStart, outRect);
-                if (lineEnd == lineStart) {
-                    // If the span is on a single line, adjust both the left and right bounds
-                    // so outrect is exactly bounding the span.
-                    outRect.left = (int) Math.min(xStart, xEnd);
-                    outRect.right = (int) Math.max(xStart, xEnd);
+        protected boolean onPerformActionForVirtualView(
+                int virtualViewId,
+                int action,
+                Bundle arguments) {
+            if (action == AccessibilityNodeInfoCompat.ACTION_CLICK) {
+                ClickableSpan span = getSpanForOffset(virtualViewId);
+                if (span != null) {
+                    span.onClick(mView);
+                    return true;
                 } else {
-                    // If the span wraps across multiple lines, only use the first line (as returned
-                    // by layout.getLineBounds above), and adjust the "start" of outrect to where
-                    // the span starts, leaving the "end" of outrect at the end of the line.
-                    // ("start" being left for LTR, and right for RTL)
-                    if (layout.getParagraphDirection(lineStart) == Layout.DIR_RIGHT_TO_LEFT) {
-                        outRect.right = (int) xStart;
-                    } else {
-                        outRect.left = (int) xStart;
-                    }
+                    Log.e(TAG, "LinkSpan is null for offset: " + virtualViewId);
                 }
-
-                // Offset for padding
-                outRect.offset(mView.getTotalPaddingLeft(), mView.getTotalPaddingTop());
             }
+            return false;
         }
-        return outRect;
-    }
 
-    // Compat implementation of TextView#getOffsetForPosition().
+        private ClickableSpan getSpanForOffset(int offset) {
+            CharSequence text = mView.getText();
+            if (text instanceof Spanned) {
+                Spanned spannedText = (Spanned) text;
+                ClickableSpan[] spans = spannedText.getSpans(offset, offset, ClickableSpan.class);
+                if (spans.length == 1) {
+                    return spans[0];
+                }
+            }
+            return null;
+        }
 
-    private static int getOffsetForPosition(TextView view, float x, float y) {
-        if (view.getLayout() == null) return -1;
-        final int line = getLineAtCoordinate(view, y);
-        return getOffsetAtCoordinate(view, line, x);
-    }
+        private CharSequence getTextForSpan(ClickableSpan span) {
+            CharSequence text = mView.getText();
+            if (text instanceof Spanned) {
+                Spanned spannedText = (Spanned) text;
+                return spannedText.subSequence(
+                        spannedText.getSpanStart(span),
+                        spannedText.getSpanEnd(span));
+            }
+            return text;
+        }
 
-    private static float convertToLocalHorizontalCoordinate(TextView view, float x) {
-        x -= view.getTotalPaddingLeft();
-        // Clamp the position to inside of the view.
-        x = Math.max(0.0f, x);
-        x = Math.min(view.getWidth() - view.getTotalPaddingRight() - 1, x);
-        x += view.getScrollX();
-        return x;
-    }
+        // Find the bounds of a span. If it spans multiple lines, it will only return the bounds for
+        // the section on the first line.
+        private Rect getBoundsForSpan(ClickableSpan span, Rect outRect) {
+            CharSequence text = mView.getText();
+            outRect.setEmpty();
+            if (text instanceof Spanned) {
+                final Layout layout = mView.getLayout();
+                if (layout != null) {
+                    Spanned spannedText = (Spanned) text;
+                    final int spanStart = spannedText.getSpanStart(span);
+                    final int spanEnd = spannedText.getSpanEnd(span);
+                    final float xStart = layout.getPrimaryHorizontal(spanStart);
+                    final float xEnd = layout.getPrimaryHorizontal(spanEnd);
+                    final int lineStart = layout.getLineForOffset(spanStart);
+                    final int lineEnd = layout.getLineForOffset(spanEnd);
+                    layout.getLineBounds(lineStart, outRect);
+                    if (lineEnd == lineStart) {
+                        // If the span is on a single line, adjust both the left and right bounds
+                        // so outrect is exactly bounding the span.
+                        outRect.left = (int) Math.min(xStart, xEnd);
+                        outRect.right = (int) Math.max(xStart, xEnd);
+                    } else {
+                        // If the span wraps across multiple lines, only use the first line (as
+                        // returned by layout.getLineBounds above), and adjust the "start" of
+                        // outrect to where the span starts, leaving the "end" of outrect at the end
+                        // of the line. ("start" being left for LTR, and right for RTL)
+                        if (layout.getParagraphDirection(lineStart) == Layout.DIR_RIGHT_TO_LEFT) {
+                            outRect.right = (int) xStart;
+                        } else {
+                            outRect.left = (int) xStart;
+                        }
+                    }
 
-    private static int getLineAtCoordinate(TextView view, float y) {
-        y -= view.getTotalPaddingTop();
-        // Clamp the position to inside of the view.
-        y = Math.max(0.0f, y);
-        y = Math.min(view.getHeight() - view.getTotalPaddingBottom() - 1, y);
-        y += view.getScrollY();
-        return view.getLayout().getLineForVertical((int) y);
-    }
+                    // Offset for padding
+                    outRect.offset(mView.getTotalPaddingLeft(), mView.getTotalPaddingTop());
+                }
+            }
+            return outRect;
+        }
 
-    private static int getOffsetAtCoordinate(TextView view, int line, float x) {
-        x = convertToLocalHorizontalCoordinate(view, x);
-        return view.getLayout().getOffsetForHorizontal(line, x);
+        // Compat implementation of TextView#getOffsetForPosition().
+
+        private static int getOffsetForPosition(TextView view, float x, float y) {
+            if (view.getLayout() == null) return -1;
+            final int line = getLineAtCoordinate(view, y);
+            return getOffsetAtCoordinate(view, line, x);
+        }
+
+        private static float convertToLocalHorizontalCoordinate(TextView view, float x) {
+            x -= view.getTotalPaddingLeft();
+            // Clamp the position to inside of the view.
+            x = Math.max(0.0f, x);
+            x = Math.min(view.getWidth() - view.getTotalPaddingRight() - 1, x);
+            x += view.getScrollX();
+            return x;
+        }
+
+        private static int getLineAtCoordinate(TextView view, float y) {
+            y -= view.getTotalPaddingTop();
+            // Clamp the position to inside of the view.
+            y = Math.max(0.0f, y);
+            y = Math.min(view.getHeight() - view.getTotalPaddingBottom() - 1, y);
+            y += view.getScrollY();
+            return view.getLayout().getLineForVertical((int) y);
+        }
+
+        private static int getOffsetAtCoordinate(TextView view, int line, float x) {
+            x = convertToLocalHorizontalCoordinate(view, x);
+            return view.getLayout().getOffsetForHorizontal(line, x);
+        }
     }
 }
diff --git a/com/android/setupwizardlib/test/LinkAccessibilityHelperTest.java b/com/android/setupwizardlib/util/LinkAccessibilityHelperTest.java
similarity index 75%
rename from com/android/setupwizardlib/test/LinkAccessibilityHelperTest.java
rename to com/android/setupwizardlib/util/LinkAccessibilityHelperTest.java
index 844e73e..6228e6f 100644
--- a/com/android/setupwizardlib/test/LinkAccessibilityHelperTest.java
+++ b/com/android/setupwizardlib/util/LinkAccessibilityHelperTest.java
@@ -14,29 +14,35 @@
  * limitations under the License.
  */
 
-package com.android.setupwizardlib.test;
+package com.android.setupwizardlib.util;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Matchers.same;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
 
 import android.graphics.Rect;
-import android.os.Build;
 import android.os.Bundle;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.support.v4.text.BidiFormatter;
 import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
+import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat;
 import android.support.v4.widget.ExploreByTouchHelper;
 import android.text.SpannableStringBuilder;
 import android.util.DisplayMetrics;
 import android.util.TypedValue;
+import android.view.MotionEvent;
 import android.view.View;
 import android.view.accessibility.AccessibilityEvent;
+import android.widget.FrameLayout;
 import android.widget.TextView;
 
 import com.android.setupwizardlib.span.LinkSpan;
-import com.android.setupwizardlib.util.LinkAccessibilityHelper;
+import com.android.setupwizardlib.util.LinkAccessibilityHelper.PreOLinkAccessibilityHelper;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -52,13 +58,12 @@
     private static final LinkSpan LINK_SPAN = new LinkSpan("foobar");
 
     private TextView mTextView;
-    private TestLinkAccessibilityHelper mHelper;
+    private TestPreOLinkAccessibilityHelper mHelper;
 
     private DisplayMetrics mDisplayMetrics;
 
     @Test
     public void testGetVirtualViewAt() {
-        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) return;
         initTextView();
         final int virtualViewId = mHelper.getVirtualViewAt(dp2Px(15), dp2Px(10));
         assertEquals("Virtual view ID should be 1", 1, virtualViewId);
@@ -66,7 +71,6 @@
 
     @Test
     public void testGetVirtualViewAtHost() {
-        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) return;
         initTextView();
         final int virtualViewId = mHelper.getVirtualViewAt(dp2Px(100), dp2Px(100));
         assertEquals("Virtual view ID should be INVALID_ID",
@@ -75,7 +79,6 @@
 
     @Test
     public void testGetVisibleVirtualViews() {
-        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) return;
         initTextView();
         List<Integer> virtualViewIds = new ArrayList<>();
         mHelper.getVisibleVirtualViews(virtualViewIds);
@@ -86,7 +89,6 @@
 
     @Test
     public void testOnPopulateEventForVirtualView() {
-        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) return;
         initTextView();
         AccessibilityEvent event = AccessibilityEvent.obtain();
         mHelper.onPopulateEventForVirtualView(1, event);
@@ -100,7 +102,6 @@
 
     @Test
     public void testOnPopulateEventForVirtualViewHost() {
-        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) return;
         initTextView();
         AccessibilityEvent event = AccessibilityEvent.obtain();
         mHelper.onPopulateEventForVirtualView(ExploreByTouchHelper.INVALID_ID, event);
@@ -113,7 +114,6 @@
 
     @Test
     public void testOnPopulateNodeForVirtualView() {
-        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) return;
         initTextView();
         AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain();
         mHelper.onPopulateNodeForVirtualView(1, info);
@@ -132,7 +132,6 @@
 
     @Test
     public void testNullLayout() {
-        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) return;
         initTextView();
         // Setting the padding will cause the layout to be null-ed out.
         mTextView.setPadding(1, 1, 1, 1);
@@ -150,7 +149,6 @@
 
     @Test
     public void testRtlLayout() {
-        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) return;
         SpannableStringBuilder ssb = new SpannableStringBuilder("מכונה בתרגום");
         ssb.setSpan(LINK_SPAN, 1, 2, 0 /* flags */);
         initTextView(ssb);
@@ -170,7 +168,6 @@
 
     @Test
     public void testMultilineLink() {
-        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) return;
         SpannableStringBuilder ssb = new SpannableStringBuilder(
                 "Lorem ipsum dolor sit amet, consectetur adipiscing elit. "
                 + "Praesent accumsan efficitur eros eu porttitor.");
@@ -192,7 +189,6 @@
 
     @Test
     public void testRtlMultilineLink() {
-        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) return;
         String iwLoremIpsum = "אחר על רביעי אקטואליה. לוח דת אחרות המקובל רומנית, מיזמים מועמדים "
                 + "האנציקלופדיה בה צ'ט. מתן מה שנורו לערוך ייִדיש, בקר או החול אנתרופולוגיה, עוד "
                 + "דפים המחשב מיזמים ב.";
@@ -216,7 +212,6 @@
 
     @Test
     public void testBidiMultilineLink() {
-        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) return;
         String iwLoremIpsum = "אחר על רביעי אקטואליה. לוח דת אחרות המקובל רומנית, מיזמים מועמדים "
                 + "האנציקלופדיה בה צ'ט. מתן מה שנורו לערוך ייִדיש, בקר או החול אנתרופולוגיה, עוד "
                 + "דפים המחשב מיזמים ב.";
@@ -243,6 +238,70 @@
         info.recycle();
     }
 
+    @Test
+    public void testMethodDelegation() {
+        initTextView();
+        ExploreByTouchHelper delegate = mock(TestPreOLinkAccessibilityHelper.class);
+        LinkAccessibilityHelper helper = new LinkAccessibilityHelper(delegate);
+
+        AccessibilityEvent accessibilityEvent =
+                AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_CLICKED);
+
+        helper.sendAccessibilityEvent(mTextView, AccessibilityEvent.TYPE_VIEW_CLICKED);
+        verify(delegate).sendAccessibilityEvent(
+                same(mTextView),
+                eq(AccessibilityEvent.TYPE_VIEW_CLICKED));
+
+        helper.sendAccessibilityEventUnchecked(mTextView, accessibilityEvent);
+        verify(delegate).sendAccessibilityEventUnchecked(same(mTextView), same(accessibilityEvent));
+
+        helper.performAccessibilityAction(
+                mTextView,
+                AccessibilityActionCompat.ACTION_CLICK.getId(),
+                Bundle.EMPTY);
+        verify(delegate).performAccessibilityAction(
+                same(mTextView),
+                eq(AccessibilityActionCompat.ACTION_CLICK.getId()),
+                eq(Bundle.EMPTY));
+
+        helper.dispatchPopulateAccessibilityEvent(
+                mTextView,
+                accessibilityEvent);
+        verify(delegate).dispatchPopulateAccessibilityEvent(
+                same(mTextView),
+                same(accessibilityEvent));
+
+        MotionEvent motionEvent = MotionEvent.obtain(0, 0, 0, 0, 0, 0);
+        helper.dispatchHoverEvent(motionEvent);
+        verify(delegate).dispatchHoverEvent(eq(motionEvent));
+
+        helper.getAccessibilityNodeProvider(mTextView);
+        verify(delegate).getAccessibilityNodeProvider(same(mTextView));
+
+        helper.onInitializeAccessibilityEvent(mTextView, accessibilityEvent);
+        verify(delegate).onInitializeAccessibilityEvent(
+                same(mTextView),
+                eq(accessibilityEvent));
+
+        AccessibilityNodeInfoCompat accessibilityNodeInfo = AccessibilityNodeInfoCompat.obtain();
+        helper.onInitializeAccessibilityNodeInfo(mTextView, accessibilityNodeInfo);
+        verify(delegate).onInitializeAccessibilityNodeInfo(
+                same(mTextView),
+                same(accessibilityNodeInfo));
+
+        helper.onPopulateAccessibilityEvent(mTextView, accessibilityEvent);
+        verify(delegate).onPopulateAccessibilityEvent(
+                same(mTextView),
+                same(accessibilityEvent));
+
+        FrameLayout parent = new FrameLayout(InstrumentationRegistry.getTargetContext());
+        helper.onRequestSendAccessibilityEvent(parent, mTextView, accessibilityEvent);
+        verify(delegate).onRequestSendAccessibilityEvent(
+                same(parent),
+                same(mTextView),
+                same(accessibilityEvent));
+    }
+
     private void initTextView() {
         SpannableStringBuilder ssb = new SpannableStringBuilder("Hello world");
         ssb.setSpan(LINK_SPAN, 1, 2, 0 /* flags */);
@@ -254,7 +313,7 @@
         mTextView.setSingleLine(false);
         mTextView.setText(text);
         mTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 15);
-        mHelper = new TestLinkAccessibilityHelper(mTextView);
+        mHelper = new TestPreOLinkAccessibilityHelper(mTextView);
 
         int measureExactly500dp = View.MeasureSpec.makeMeasureSpec(dp2Px(500),
                 View.MeasureSpec.EXACTLY);
@@ -270,9 +329,9 @@
         return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, mDisplayMetrics);
     }
 
-    private static class TestLinkAccessibilityHelper extends LinkAccessibilityHelper {
+    public static class TestPreOLinkAccessibilityHelper extends PreOLinkAccessibilityHelper {
 
-        TestLinkAccessibilityHelper(TextView view) {
+        TestPreOLinkAccessibilityHelper(TextView view) {
             super(view);
         }
 
diff --git a/com/android/setupwizardlib/util/PartnerTest.java b/com/android/setupwizardlib/util/PartnerTest.java
index f47eef1..aeb678f 100644
--- a/com/android/setupwizardlib/util/PartnerTest.java
+++ b/com/android/setupwizardlib/util/PartnerTest.java
@@ -31,6 +31,7 @@
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ResolveInfo;
 import android.content.res.Resources;
 import android.os.Build.VERSION;
@@ -40,20 +41,25 @@
 import com.android.setupwizardlib.R;
 import com.android.setupwizardlib.robolectric.SuwLibRobolectricTestRunner;
 import com.android.setupwizardlib.util.Partner.ResourceEntry;
+import com.android.setupwizardlib.util.PartnerTest.ShadowApplicationPackageManager;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.robolectric.RuntimeEnvironment;
+import org.robolectric.Shadows;
 import org.robolectric.annotation.Config;
-import org.robolectric.res.builder.DefaultPackageManager;
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
 import org.robolectric.shadows.ShadowResources;
 
 import java.util.Arrays;
 import java.util.Collections;
 
 @RunWith(SuwLibRobolectricTestRunner.class)
-@Config(constants = BuildConfig.class, sdk = { Config.OLDEST_SDK, Config.NEWEST_SDK })
+@Config(
+        constants = BuildConfig.class,
+        sdk = { Config.OLDEST_SDK, Config.NEWEST_SDK },
+        shadows = ShadowApplicationPackageManager.class)
 public class PartnerTest {
 
     private static final String ACTION_PARTNER_CUSTOMIZATION =
@@ -62,7 +68,7 @@
     private Context mContext;
     private Resources mPartnerResources;
 
-    private TestPackageManager mPackageManager;
+    private ShadowApplicationPackageManager mPackageManager;
 
     @Before
     public void setUp() throws Exception {
@@ -71,8 +77,9 @@
         mContext = spy(application);
         mPartnerResources = spy(ShadowResources.getSystem());
 
-        mPackageManager = new TestPackageManager();
-        RuntimeEnvironment.setRobolectricPackageManager(mPackageManager);
+        mPackageManager =
+                (ShadowApplicationPackageManager) Shadows.shadowOf(application.getPackageManager());
+        mPackageManager.partnerResources = mPartnerResources;
     }
 
     @Test
@@ -173,13 +180,18 @@
         return info;
     }
 
-    private class TestPackageManager extends DefaultPackageManager {
+    @Implements(className = "android.app.ApplicationPackageManager")
+    public static class ShadowApplicationPackageManager extends
+            org.robolectric.shadows.ShadowApplicationPackageManager {
 
+        public Resources partnerResources;
+
+        @Implementation
         @Override
         public Resources getResourcesForApplication(ApplicationInfo app)
                 throws NameNotFoundException {
             if (app != null && "test.partner.package".equals(app.packageName)) {
-                return mPartnerResources;
+                return partnerResources;
             } else {
                 return super.getResourcesForApplication(app);
             }
diff --git a/com/android/setupwizardlib/view/IllustrationVideoViewTest.java b/com/android/setupwizardlib/view/IllustrationVideoViewTest.java
index ffa228d..ddf59ca 100644
--- a/com/android/setupwizardlib/view/IllustrationVideoViewTest.java
+++ b/com/android/setupwizardlib/view/IllustrationVideoViewTest.java
@@ -48,7 +48,7 @@
 import org.robolectric.annotation.Implementation;
 import org.robolectric.annotation.Implements;
 import org.robolectric.annotation.RealObject;
-import org.robolectric.internal.Shadow;
+import org.robolectric.shadow.api.Shadow;
 import org.robolectric.shadows.ShadowMediaPlayer;
 import org.robolectric.util.ReflectionHelpers;
 
diff --git a/com/android/setupwizardlib/view/NavigationBarButton.java b/com/android/setupwizardlib/view/NavigationBarButton.java
index 5172c47..45d3737 100644
--- a/com/android/setupwizardlib/view/NavigationBarButton.java
+++ b/com/android/setupwizardlib/view/NavigationBarButton.java
@@ -17,143 +17,16 @@
 package com.android.setupwizardlib.view;
 
 import android.content.Context;
-import android.content.res.ColorStateList;
-import android.graphics.PorterDuff;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.LayerDrawable;
-import android.os.Build;
-import android.support.annotation.NonNull;
 import android.util.AttributeSet;
 import android.widget.Button;
 
-/**
- * Button for navigation bar, which includes tinting of its compound drawables to be used for dark
- * and light themes.
- */
 public class NavigationBarButton extends Button {
 
     public NavigationBarButton(Context context) {
         super(context);
-        init();
     }
 
     public NavigationBarButton(Context context, AttributeSet attrs) {
         super(context, attrs);
-        init();
-    }
-
-    private void init() {
-        // Unfortunately, drawableStart and drawableEnd set through XML does not call the setter,
-        // so manually getting it and wrapping it in the compat drawable.
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
-            Drawable[] drawables = getCompoundDrawablesRelative();
-            for (int i = 0; i < drawables.length; i++) {
-                if (drawables[i] != null) {
-                    drawables[i] = TintedDrawable.wrap(drawables[i]);
-                }
-            }
-            setCompoundDrawablesRelativeWithIntrinsicBounds(drawables[0], drawables[1],
-                    drawables[2], drawables[3]);
-        }
-    }
-
-    @Override
-    public void setCompoundDrawables(Drawable left, Drawable top, Drawable right, Drawable bottom) {
-        if (left != null) left = TintedDrawable.wrap(left);
-        if (top != null) top = TintedDrawable.wrap(top);
-        if (right != null) right = TintedDrawable.wrap(right);
-        if (bottom != null) bottom = TintedDrawable.wrap(bottom);
-        super.setCompoundDrawables(left, top, right, bottom);
-        tintDrawables();
-    }
-
-    @Override
-    public void setCompoundDrawablesRelative(Drawable start, Drawable top, Drawable end,
-            Drawable bottom) {
-        if (start != null) start = TintedDrawable.wrap(start);
-        if (top != null) top = TintedDrawable.wrap(top);
-        if (end != null) end = TintedDrawable.wrap(end);
-        if (bottom != null) bottom = TintedDrawable.wrap(bottom);
-        super.setCompoundDrawablesRelative(start, top, end, bottom);
-        tintDrawables();
-    }
-
-    @Override
-    public void setTextColor(ColorStateList colors) {
-        super.setTextColor(colors);
-        tintDrawables();
-    }
-
-    private void tintDrawables() {
-        final ColorStateList textColors = getTextColors();
-        if (textColors != null) {
-            for (Drawable drawable : getAllCompoundDrawables()) {
-                if (drawable instanceof TintedDrawable) {
-                    ((TintedDrawable) drawable).setTintListCompat(textColors);
-                }
-            }
-            invalidate();
-        }
-    }
-
-    private Drawable[] getAllCompoundDrawables() {
-        Drawable[] drawables = new Drawable[6];
-        Drawable[] compoundDrawables = getCompoundDrawables();
-        drawables[0] = compoundDrawables[0];  // left
-        drawables[1] = compoundDrawables[1];  // top
-        drawables[2] = compoundDrawables[2];  // right
-        drawables[3] = compoundDrawables[3];  // bottom
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
-            Drawable[] compoundDrawablesRelative = getCompoundDrawablesRelative();
-            drawables[4] = compoundDrawablesRelative[0];  // start
-            drawables[5] = compoundDrawablesRelative[2];  // end
-        }
-        return drawables;
-    }
-
-    // TODO: Remove this class and use DrawableCompat.wrap() once we can use support library 22.1.0
-    // or above
-    private static class TintedDrawable extends LayerDrawable {
-
-        public static TintedDrawable wrap(Drawable drawable) {
-            if (drawable instanceof TintedDrawable) {
-                return (TintedDrawable) drawable;
-            }
-            return new TintedDrawable(drawable.mutate());
-        }
-
-        private ColorStateList mTintList = null;
-
-        TintedDrawable(Drawable wrapped) {
-            super(new Drawable[] { wrapped });
-        }
-
-        @Override
-        public boolean isStateful() {
-            return true;
-        }
-
-        @Override
-        public boolean setState(@NonNull int[] stateSet) {
-            boolean needsInvalidate = super.setState(stateSet);
-            boolean needsInvalidateForState = updateState();
-            return needsInvalidate || needsInvalidateForState;
-        }
-
-        public void setTintListCompat(ColorStateList colors) {
-            mTintList = colors;
-            if (updateState()) {
-                invalidateSelf();
-            }
-        }
-
-        private boolean updateState() {
-            if (mTintList != null) {
-                final int color = mTintList.getColorForState(getState(), 0);
-                setColorFilter(color, PorterDuff.Mode.SRC_IN);
-                return true;  // Needs invalidate
-            }
-            return false;
-        }
     }
 }
diff --git a/com/android/setupwizardlib/view/RichTextView.java b/com/android/setupwizardlib/view/RichTextView.java
index e6bc9da..5a78561 100644
--- a/com/android/setupwizardlib/view/RichTextView.java
+++ b/com/android/setupwizardlib/view/RichTextView.java
@@ -17,11 +17,6 @@
 package com.android.setupwizardlib.view;
 
 import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.os.Build.VERSION;
-import android.os.Build.VERSION_CODES;
-import android.support.v4.view.ViewCompat;
-import android.support.v7.widget.AppCompatTextView;
 import android.text.Annotation;
 import android.text.SpannableString;
 import android.text.Spanned;
@@ -30,18 +25,22 @@
 import android.text.style.TextAppearanceSpan;
 import android.util.AttributeSet;
 import android.util.Log;
-import android.view.MotionEvent;
+import android.widget.TextView;
 
 import com.android.setupwizardlib.span.LinkSpan;
 import com.android.setupwizardlib.span.LinkSpan.OnLinkClickListener;
 import com.android.setupwizardlib.span.SpanHelper;
-import com.android.setupwizardlib.util.LinkAccessibilityHelper;
 
 /**
  * An extension of TextView that automatically replaces the annotation tags as specified in
  * {@link SpanHelper#replaceSpan(android.text.Spannable, Object, Object)}
+ *
+ * <p>Note: The accessibility interaction for ClickableSpans (and therefore LinkSpans) are built
+ * into platform in O, although the interaction paradigm is different. (See b/17726921). In this
+ * platform version, the links are exposed in the Local Context Menu of TalkBack instead of
+ * accessible directly through swiping.
  */
-public class RichTextView extends AppCompatTextView implements OnLinkClickListener {
+public class RichTextView extends TextView implements OnLinkClickListener {
 
     /* static section */
 
@@ -89,22 +88,14 @@
 
     /* non-static section */
 
-    private LinkAccessibilityHelper mAccessibilityHelper;
     private OnLinkClickListener mOnLinkClickListener;
 
     public RichTextView(Context context) {
         super(context);
-        init();
     }
 
     public RichTextView(Context context, AttributeSet attrs) {
         super(context, attrs);
-        init();
-    }
-
-    private void init() {
-        mAccessibilityHelper = new LinkAccessibilityHelper(this);
-        ViewCompat.setAccessibilityDelegate(this, mAccessibilityHelper);
     }
 
     @Override
@@ -141,32 +132,6 @@
         return false;
     }
 
-    @Override
-    protected boolean dispatchHoverEvent(MotionEvent event) {
-        if (mAccessibilityHelper != null && mAccessibilityHelper.dispatchHoverEvent(event)) {
-            return true;
-        }
-        return super.dispatchHoverEvent(event);
-    }
-
-    @Override
-    protected void drawableStateChanged() {
-        super.drawableStateChanged();
-
-        if (VERSION.SDK_INT >= VERSION_CODES.JELLY_BEAN_MR1) {
-            // b/26765507 causes drawableStart and drawableEnd to not get the right state on M. As a
-            // workaround, set the state on those drawables directly.
-            final int[] state = getDrawableState();
-            for (Drawable drawable : getCompoundDrawablesRelative()) {
-                if (drawable != null) {
-                    if (drawable.setState(state)) {
-                        invalidateDrawable(drawable);
-                    }
-                }
-            }
-        }
-    }
-
     public void setOnLinkClickListener(OnLinkClickListener listener) {
         mOnLinkClickListener = listener;
     }
diff --git a/com/android/systemui/BatteryMeterView.java b/com/android/systemui/BatteryMeterView.java
index 2b31967..2fe66a1 100644
--- a/com/android/systemui/BatteryMeterView.java
+++ b/com/android/systemui/BatteryMeterView.java
@@ -15,6 +15,8 @@
  */
 package com.android.systemui;
 
+import static android.app.StatusBarManager.DISABLE2_SYSTEM_ICONS;
+import static android.app.StatusBarManager.DISABLE_NONE;
 import static android.provider.Settings.System.SHOW_BATTERY_PERCENT;
 
 import android.animation.ArgbEvaluator;
@@ -52,6 +54,7 @@
 import com.android.systemui.statusbar.policy.IconLogger;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.tuner.TunerService.Tunable;
+import com.android.systemui.util.Utils.DisableStateTracker;
 
 import java.text.NumberFormat;
 
@@ -101,6 +104,9 @@
 
         mSettingObserver = new SettingObserver(new Handler(context.getMainLooper()));
 
+        addOnAttachStateChangeListener(
+                new DisableStateTracker(DISABLE_NONE, DISABLE2_SYSTEM_ICONS));
+
         mSlotBattery = context.getString(
                 com.android.internal.R.string.status_bar_battery);
         mBatteryIconView = new ImageView(context);
diff --git a/com/android/systemui/assist/AssistManager.java b/com/android/systemui/assist/AssistManager.java
index c5eebcc..8a8bafa 100644
--- a/com/android/systemui/assist/AssistManager.java
+++ b/com/android/systemui/assist/AssistManager.java
@@ -61,6 +61,7 @@
     private AssistOrbContainer mView;
     private final DeviceProvisionedController mDeviceProvisionedController;
     protected final AssistUtils mAssistUtils;
+    private final boolean mShouldEnableOrb;
 
     private IVoiceInteractionSessionShowCallback mShowCallback =
             new IVoiceInteractionSessionShowCallback.Stub() {
@@ -96,6 +97,7 @@
                 | ActivityInfo.CONFIG_LOCALE | ActivityInfo.CONFIG_UI_MODE
                 | ActivityInfo.CONFIG_SCREEN_LAYOUT | ActivityInfo.CONFIG_ASSETS_PATHS);
         onConfigurationChanged(context.getResources().getConfiguration());
+        mShouldEnableOrb = !ActivityManager.isLowRamDeviceStatic();
     }
 
     protected void registerVoiceInteractionSessionListener() {
@@ -179,7 +181,9 @@
 
     private void showOrb(@NonNull ComponentName assistComponent, boolean isService) {
         maybeSwapSearchIcon(assistComponent, isService);
-        mView.show(true /* show */, true /* animate */);
+        if (mShouldEnableOrb) {
+            mView.show(true /* show */, true /* animate */);
+        }
     }
 
     private void startAssistInternal(Bundle args, @NonNull ComponentName assistComponent,
diff --git a/com/android/systemui/doze/AlwaysOnDisplayPolicy.java b/com/android/systemui/doze/AlwaysOnDisplayPolicy.java
index 5c99961..debda21 100644
--- a/com/android/systemui/doze/AlwaysOnDisplayPolicy.java
+++ b/com/android/systemui/doze/AlwaysOnDisplayPolicy.java
@@ -16,8 +16,13 @@
 
 package com.android.systemui.doze;
 
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.res.Resources;
+import android.database.ContentObserver;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.UserHandle;
 import android.provider.Settings;
 import android.text.format.DateUtils;
 import android.util.KeyValueListParser;
@@ -34,6 +39,10 @@
 public class AlwaysOnDisplayPolicy {
     public static final String TAG = "AlwaysOnDisplayPolicy";
 
+    private static final long DEFAULT_PROX_SCREEN_OFF_DELAY_MS = 10 * DateUtils.SECOND_IN_MILLIS;
+    private static final long DEFAULT_PROX_COOLDOWN_TRIGGER_MS = 2 * DateUtils.SECOND_IN_MILLIS;
+    private static final long DEFAULT_PROX_COOLDOWN_PERIOD_MS = 5 * DateUtils.SECOND_IN_MILLIS;
+
     static final String KEY_SCREEN_BRIGHTNESS_ARRAY = "screen_brightness_array";
     static final String KEY_DIMMING_SCRIM_ARRAY = "dimming_scrim_array";
     static final String KEY_PROX_SCREEN_OFF_DELAY_MS = "prox_screen_off_delay";
@@ -46,7 +55,7 @@
      * @see Settings.Global#ALWAYS_ON_DISPLAY_CONSTANTS
      * @see #KEY_SCREEN_BRIGHTNESS_ARRAY
      */
-    public final int[] screenBrightnessArray;
+    public int[] screenBrightnessArray;
 
     /**
      * Integer array to map ambient brightness type to dimming scrim.
@@ -54,7 +63,7 @@
      * @see Settings.Global#ALWAYS_ON_DISPLAY_CONSTANTS
      * @see #KEY_DIMMING_SCRIM_ARRAY
      */
-    public final int[] dimmingScrimArray;
+    public int[] dimmingScrimArray;
 
     /**
      * Delay time(ms) from covering the prox to turning off the screen.
@@ -62,7 +71,7 @@
      * @see Settings.Global#ALWAYS_ON_DISPLAY_CONSTANTS
      * @see #KEY_PROX_SCREEN_OFF_DELAY_MS
      */
-    public final long proxScreenOffDelayMs;
+    public long proxScreenOffDelayMs;
 
     /**
      * The threshold time(ms) to trigger the cooldown timer, which will
@@ -71,7 +80,7 @@
      * @see Settings.Global#ALWAYS_ON_DISPLAY_CONSTANTS
      * @see #KEY_PROX_COOLDOWN_TRIGGER_MS
      */
-    public final long proxCooldownTriggerMs;
+    public long proxCooldownTriggerMs;
 
     /**
      * The period(ms) to turning off the prox sensor if
@@ -80,43 +89,78 @@
      * @see Settings.Global#ALWAYS_ON_DISPLAY_CONSTANTS
      * @see #KEY_PROX_COOLDOWN_PERIOD_MS
      */
-    public final long proxCooldownPeriodMs;
+    public long proxCooldownPeriodMs;
 
     private final KeyValueListParser mParser;
+    private final Context mContext;
+    private SettingsObserver mSettingsObserver;
 
     public AlwaysOnDisplayPolicy(Context context) {
-        final Resources resources = context.getResources();
+        mContext = context;
         mParser = new KeyValueListParser(',');
-
-        final String value = Settings.Global.getString(context.getContentResolver(),
-                Settings.Global.ALWAYS_ON_DISPLAY_CONSTANTS);
-
-        try {
-            mParser.setString(value);
-        } catch (IllegalArgumentException e) {
-            Log.e(TAG, "Bad AOD constants");
-        }
-
-        proxScreenOffDelayMs = mParser.getLong(KEY_PROX_SCREEN_OFF_DELAY_MS,
-                10 * DateUtils.SECOND_IN_MILLIS);
-        proxCooldownTriggerMs = mParser.getLong(KEY_PROX_COOLDOWN_TRIGGER_MS,
-                2 * DateUtils.SECOND_IN_MILLIS);
-        proxCooldownPeriodMs = mParser.getLong(KEY_PROX_COOLDOWN_PERIOD_MS,
-                5 * DateUtils.SECOND_IN_MILLIS);
-        screenBrightnessArray = parseIntArray(KEY_SCREEN_BRIGHTNESS_ARRAY,
-                resources.getIntArray(R.array.config_doze_brightness_sensor_to_brightness));
-        dimmingScrimArray = parseIntArray(KEY_DIMMING_SCRIM_ARRAY,
-                resources.getIntArray(R.array.config_doze_brightness_sensor_to_scrim_opacity));
+        mSettingsObserver = new SettingsObserver(context.getMainThreadHandler());
+        mSettingsObserver.observe();
     }
 
     private int[] parseIntArray(final String key, final int[] defaultArray) {
         final String value = mParser.getString(key, null);
         if (value != null) {
-            return Arrays.stream(value.split(":")).map(String::trim).mapToInt(
-                    Integer::parseInt).toArray();
+            try {
+                return Arrays.stream(value.split(":")).map(String::trim).mapToInt(
+                        Integer::parseInt).toArray();
+            } catch (NumberFormatException e) {
+                return defaultArray;
+            }
         } else {
             return defaultArray;
         }
     }
 
+    private final class SettingsObserver extends ContentObserver {
+        private final Uri ALWAYS_ON_DISPLAY_CONSTANTS_URI
+                = Settings.Global.getUriFor(Settings.Global.ALWAYS_ON_DISPLAY_CONSTANTS);
+
+        SettingsObserver(Handler handler) {
+            super(handler);
+        }
+
+        void observe() {
+            ContentResolver resolver = mContext.getContentResolver();
+            resolver.registerContentObserver(ALWAYS_ON_DISPLAY_CONSTANTS_URI,
+                    false, this, UserHandle.USER_ALL);
+            update(null);
+        }
+
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            update(uri);
+        }
+
+        public void update(Uri uri) {
+            if (uri == null || ALWAYS_ON_DISPLAY_CONSTANTS_URI.equals(uri)) {
+                final Resources resources = mContext.getResources();
+                final String value = Settings.Global.getString(mContext.getContentResolver(),
+                        Settings.Global.ALWAYS_ON_DISPLAY_CONSTANTS);
+
+                try {
+                    mParser.setString(value);
+                } catch (IllegalArgumentException e) {
+                    Log.e(TAG, "Bad AOD constants");
+                }
+
+                proxScreenOffDelayMs = mParser.getLong(KEY_PROX_SCREEN_OFF_DELAY_MS,
+                        DEFAULT_PROX_SCREEN_OFF_DELAY_MS);
+                proxCooldownTriggerMs = mParser.getLong(KEY_PROX_COOLDOWN_TRIGGER_MS,
+                        DEFAULT_PROX_COOLDOWN_TRIGGER_MS);
+                proxCooldownPeriodMs = mParser.getLong(KEY_PROX_COOLDOWN_PERIOD_MS,
+                        DEFAULT_PROX_COOLDOWN_PERIOD_MS);
+                screenBrightnessArray = parseIntArray(KEY_SCREEN_BRIGHTNESS_ARRAY,
+                        resources.getIntArray(
+                                R.array.config_doze_brightness_sensor_to_brightness));
+                dimmingScrimArray = parseIntArray(KEY_DIMMING_SCRIM_ARRAY,
+                        resources.getIntArray(
+                                R.array.config_doze_brightness_sensor_to_scrim_opacity));
+            }
+        }
+    }
 }
diff --git a/com/android/systemui/doze/DozePauser.java b/com/android/systemui/doze/DozePauser.java
index 76a1902..58f1448 100644
--- a/com/android/systemui/doze/DozePauser.java
+++ b/com/android/systemui/doze/DozePauser.java
@@ -28,20 +28,21 @@
     public static final String TAG = DozePauser.class.getSimpleName();
     private final AlarmTimeout mPauseTimeout;
     private final DozeMachine mMachine;
-    private final long mTimeoutMs;
+    private final AlwaysOnDisplayPolicy mPolicy;
 
     public DozePauser(Handler handler, DozeMachine machine, AlarmManager alarmManager,
             AlwaysOnDisplayPolicy policy) {
         mMachine = machine;
         mPauseTimeout = new AlarmTimeout(alarmManager, this::onTimeout, TAG, handler);
-        mTimeoutMs = policy.proxScreenOffDelayMs;
+        mPolicy = policy;
     }
 
     @Override
     public void transitionTo(DozeMachine.State oldState, DozeMachine.State newState) {
         switch (newState) {
             case DOZE_AOD_PAUSING:
-                mPauseTimeout.schedule(mTimeoutMs, AlarmTimeout.MODE_IGNORE_IF_SCHEDULED);
+                mPauseTimeout.schedule(mPolicy.proxScreenOffDelayMs,
+                        AlarmTimeout.MODE_IGNORE_IF_SCHEDULED);
                 break;
             default:
                 mPauseTimeout.cancel();
diff --git a/com/android/systemui/doze/DozeScreenBrightness.java b/com/android/systemui/doze/DozeScreenBrightness.java
index 03407e2..4bb4e79 100644
--- a/com/android/systemui/doze/DozeScreenBrightness.java
+++ b/com/android/systemui/doze/DozeScreenBrightness.java
@@ -22,6 +22,7 @@
 import android.hardware.SensorEventListener;
 import android.hardware.SensorManager;
 import android.os.Handler;
+import android.os.Trace;
 
 import com.android.internal.annotations.VisibleForTesting;
 
@@ -94,9 +95,14 @@
 
     @Override
     public void onSensorChanged(SensorEvent event) {
-        if (mRegistered) {
-            mLastSensorValue = (int) event.values[0];
-            updateBrightnessAndReady();
+        Trace.beginSection("DozeScreenBrightness.onSensorChanged" + event.values[0]);
+        try {
+            if (mRegistered) {
+                mLastSensorValue = (int) event.values[0];
+                updateBrightnessAndReady();
+            }
+        } finally {
+            Trace.endSection();
         }
     }
 
diff --git a/com/android/systemui/globalactions/GlobalActionsDialog.java b/com/android/systemui/globalactions/GlobalActionsDialog.java
index 4cbbbd6..189badf 100644
--- a/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -1280,7 +1280,23 @@
             mGradientDrawable.setScreenSize(displaySize.x, displaySize.y);
             GradientColors colors = mColorExtractor.getColors(mKeyguardShowing ?
                     WallpaperManager.FLAG_LOCK : WallpaperManager.FLAG_SYSTEM);
-            mGradientDrawable.setColors(colors, false);
+            updateColors(colors, false /* animate */);
+        }
+
+        /**
+         * Updates background and system bars according to current GradientColors.
+         * @param colors Colors and hints to use.
+         * @param animate Interpolates gradient if true, just sets otherwise.
+         */
+        private void updateColors(GradientColors colors, boolean animate) {
+            mGradientDrawable.setColors(colors, animate);
+            View decorView = getWindow().getDecorView();
+            if (colors.supportsDarkText()) {
+                decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR |
+                    View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
+            } else {
+                decorView.setSystemUiVisibility(0);
+            }
         }
 
         @Override
@@ -1350,11 +1366,13 @@
         public void onColorsChanged(ColorExtractor extractor, int which) {
             if (mKeyguardShowing) {
                 if ((WallpaperManager.FLAG_LOCK & which) != 0) {
-                    mGradientDrawable.setColors(extractor.getColors(WallpaperManager.FLAG_LOCK));
+                    updateColors(extractor.getColors(WallpaperManager.FLAG_LOCK),
+                            true /* animate */);
                 }
             } else {
                 if ((WallpaperManager.FLAG_SYSTEM & which) != 0) {
-                    mGradientDrawable.setColors(extractor.getColors(WallpaperManager.FLAG_SYSTEM));
+                    updateColors(extractor.getColors(WallpaperManager.FLAG_SYSTEM),
+                            true /* animate */);
                 }
             }
         }
diff --git a/com/android/systemui/keyguard/KeyguardViewMediator.java b/com/android/systemui/keyguard/KeyguardViewMediator.java
index 3eb68f5..28adca9 100644
--- a/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -17,6 +17,7 @@
 package com.android.systemui.keyguard;
 
 import static android.provider.Settings.System.SCREEN_OFF_TIMEOUT;
+import static android.view.Display.INVALID_DISPLAY;
 
 import static com.android.internal.telephony.IccCardConstants.State.ABSENT;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST;
@@ -239,6 +240,9 @@
     // answer whether the input should be restricted)
     private boolean mShowing;
 
+    // display id of the secondary display on which we have put a keyguard window
+    private int mSecondaryDisplayShowing = INVALID_DISPLAY;
+
     /** Cached value of #isInputRestricted */
     private boolean mInputRestricted;
 
@@ -646,6 +650,13 @@
             }
             return KeyguardSecurityView.PROMPT_REASON_NONE;
         }
+
+        @Override
+        public void onSecondaryDisplayShowingChanged(int displayId) {
+            synchronized (KeyguardViewMediator.this) {
+                setShowingLocked(mShowing, displayId, false);
+            }
+        }
     };
 
     public void userActivity() {
@@ -670,7 +681,7 @@
         filter.addAction(Intent.ACTION_SHUTDOWN);
         mContext.registerReceiver(mBroadcastReceiver, filter);
 
-        mKeyguardDisplayManager = new KeyguardDisplayManager(mContext);
+        mKeyguardDisplayManager = new KeyguardDisplayManager(mContext, mViewMediatorCallback);
 
         mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
 
@@ -685,7 +696,8 @@
                 com.android.keyguard.R.bool.config_enableKeyguardService)) {
             setShowingLocked(!shouldWaitForProvisioning()
                     && !mLockPatternUtils.isLockScreenDisabled(
-                            KeyguardUpdateMonitor.getCurrentUser()), true /* forceCallbacks */);
+                            KeyguardUpdateMonitor.getCurrentUser()),
+                    mSecondaryDisplayShowing, true /* forceCallbacks */);
         }
 
         mStatusBarKeyguardViewManager =
@@ -1694,10 +1706,10 @@
         playSound(mTrustedSoundId);
     }
 
-    private void updateActivityLockScreenState(boolean showing) {
+    private void updateActivityLockScreenState(boolean showing, int secondaryDisplayShowing) {
         mUiOffloadThread.submit(() -> {
             try {
-                ActivityManager.getService().setLockScreenShown(showing);
+                ActivityManager.getService().setLockScreenShown(showing, secondaryDisplayShowing);
             } catch (RemoteException e) {
             }
         });
@@ -2060,30 +2072,39 @@
     }
 
     private void setShowingLocked(boolean showing) {
-        setShowingLocked(showing, false /* forceCallbacks */);
+        setShowingLocked(showing, mSecondaryDisplayShowing, false /* forceCallbacks */);
     }
 
-    private void setShowingLocked(boolean showing, boolean forceCallbacks) {
-        if (showing != mShowing || forceCallbacks) {
+    private void setShowingLocked(
+            boolean showing, int secondaryDisplayShowing, boolean forceCallbacks) {
+        final boolean notifyDefaultDisplayCallbacks = showing != mShowing || forceCallbacks;
+        if (notifyDefaultDisplayCallbacks || secondaryDisplayShowing != mSecondaryDisplayShowing) {
             mShowing = showing;
-            int size = mKeyguardStateCallbacks.size();
-            for (int i = size - 1; i >= 0; i--) {
-                IKeyguardStateCallback callback = mKeyguardStateCallbacks.get(i);
-                try {
-                    callback.onShowingStateChanged(showing);
-                } catch (RemoteException e) {
-                    Slog.w(TAG, "Failed to call onShowingStateChanged", e);
-                    if (e instanceof DeadObjectException) {
-                        mKeyguardStateCallbacks.remove(callback);
-                    }
+            mSecondaryDisplayShowing = secondaryDisplayShowing;
+            if (notifyDefaultDisplayCallbacks) {
+                notifyDefaultDisplayCallbacks(showing);
+            }
+            updateActivityLockScreenState(showing, secondaryDisplayShowing);
+        }
+    }
+
+    private void notifyDefaultDisplayCallbacks(boolean showing) {
+        int size = mKeyguardStateCallbacks.size();
+        for (int i = size - 1; i >= 0; i--) {
+            IKeyguardStateCallback callback = mKeyguardStateCallbacks.get(i);
+            try {
+                callback.onShowingStateChanged(showing);
+            } catch (RemoteException e) {
+                Slog.w(TAG, "Failed to call onShowingStateChanged", e);
+                if (e instanceof DeadObjectException) {
+                    mKeyguardStateCallbacks.remove(callback);
                 }
             }
-            updateInputRestrictedLocked();
-            mUiOffloadThread.submit(() -> {
-                mTrustManager.reportKeyguardShowingChanged();
-            });
-            updateActivityLockScreenState(showing);
         }
+        updateInputRestrictedLocked();
+        mUiOffloadThread.submit(() -> {
+            mTrustManager.reportKeyguardShowingChanged();
+        });
     }
 
     private void notifyTrustedChangedLocked(boolean trusted) {
diff --git a/com/android/systemui/media/NotificationPlayer.java b/com/android/systemui/media/NotificationPlayer.java
index 50720e9..b5c0d53 100644
--- a/com/android/systemui/media/NotificationPlayer.java
+++ b/com/android/systemui/media/NotificationPlayer.java
@@ -29,6 +29,8 @@
 import android.os.SystemClock;
 import android.util.Log;
 
+import com.android.internal.annotations.GuardedBy;
+
 import java.util.LinkedList;
 
 /**
@@ -57,8 +59,12 @@
         }
     }
 
-    private LinkedList<Command> mCmdQueue = new LinkedList();
+    private final LinkedList<Command> mCmdQueue = new LinkedList<Command>();
 
+    private final Object mCompletionHandlingLock = new Object();
+    @GuardedBy("mCompletionHandlingLock")
+    private CreationAndCompletionThread mCompletionThread;
+    @GuardedBy("mCompletionHandlingLock")
     private Looper mLooper;
 
     /*
@@ -76,7 +82,10 @@
 
         public void run() {
             Looper.prepare();
+            // ok to modify mLooper as here we are
+            // synchronized on mCompletionHandlingLock due to the Object.wait() in startSound(cmd)
             mLooper = Looper.myLooper();
+            if (DEBUG) Log.d(mTag, "in run: new looper " + mLooper);
             synchronized(this) {
                 AudioManager audioManager =
                     (AudioManager) mCmd.context.getSystemService(Context.AUDIO_SERVICE);
@@ -97,7 +106,7 @@
                     if ((mCmd.uri != null) && (mCmd.uri.getEncodedPath() != null)
                             && (mCmd.uri.getEncodedPath().length() > 0)) {
                         if (!audioManager.isMusicActiveRemotely()) {
-                            synchronized(mQueueAudioFocusLock) {
+                            synchronized (mQueueAudioFocusLock) {
                                 if (mAudioManagerWithAudioFocus == null) {
                                     if (DEBUG) Log.d(mTag, "requesting AudioFocus");
                                     int focusGain = AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK;
@@ -129,7 +138,9 @@
                         Log.e(mTag, "Exception while sleeping to sync notification playback"
                                 + " with ducking", e);
                     }
+                    if (DEBUG) { Log.d(mTag, "player.start"); }
                     if (mPlayer != null) {
+                        if (DEBUG) { Log.d(mTag, "mPlayer.release"); }
                         mPlayer.release();
                     }
                     mPlayer = player;
@@ -148,7 +159,7 @@
         // is playing, let it continue until we're done, so there
         // is less of a glitch.
         try {
-            if (DEBUG) Log.d(mTag, "Starting playback");
+            if (DEBUG) { Log.d(mTag, "startSound()"); }
             //-----------------------------------
             // This is were we deviate from the AsyncPlayer implementation and create the
             // MediaPlayer in a new thread with which we're synchronized
@@ -158,10 +169,11 @@
                 // matters
                 if((mLooper != null)
                         && (mLooper.getThread().getState() != Thread.State.TERMINATED)) {
+                    if (DEBUG) { Log.d(mTag, "in startSound quitting looper " + mLooper); }
                     mLooper.quit();
                 }
                 mCompletionThread = new CreationAndCompletionThread(cmd);
-                synchronized(mCompletionThread) {
+                synchronized (mCompletionThread) {
                     mCompletionThread.start();
                     mCompletionThread.wait();
                 }
@@ -209,13 +221,18 @@
                         mPlayer = null;
                         synchronized(mQueueAudioFocusLock) {
                             if (mAudioManagerWithAudioFocus != null) {
+                                if (DEBUG) { Log.d(mTag, "in STOP: abandonning AudioFocus"); }
                                 mAudioManagerWithAudioFocus.abandonAudioFocus(null);
                                 mAudioManagerWithAudioFocus = null;
                             }
                         }
-                        if((mLooper != null)
-                                && (mLooper.getThread().getState() != Thread.State.TERMINATED)) {
-                            mLooper.quit();
+                        synchronized (mCompletionHandlingLock) {
+                            if ((mLooper != null) &&
+                                    (mLooper.getThread().getState() != Thread.State.TERMINATED))
+                            {
+                                if (DEBUG) { Log.d(mTag, "in STOP: quitting looper "+ mLooper); }
+                                mLooper.quit();
+                            }
                         }
                     } else {
                         Log.w(mTag, "STOP command without a player");
@@ -250,9 +267,11 @@
         }
         // if there are no more sounds to play, end the Looper to listen for media completion
         synchronized (mCmdQueue) {
-            if (mCmdQueue.size() == 0) {
-                synchronized(mCompletionHandlingLock) {
-                    if(mLooper != null) {
+            synchronized(mCompletionHandlingLock) {
+                if (DEBUG) { Log.d(mTag, "onCompletion queue size=" + mCmdQueue.size()); }
+                if ((mCmdQueue.size() == 0)) {
+                    if (mLooper != null) {
+                        if (DEBUG) { Log.d(mTag, "in onCompletion quitting looper " + mLooper); }
                         mLooper.quit();
                     }
                     mCompletionThread = null;
@@ -269,13 +288,20 @@
     }
 
     private String mTag;
+
+    @GuardedBy("mCmdQueue")
     private CmdThread mThread;
-    private CreationAndCompletionThread mCompletionThread;
-    private final Object mCompletionHandlingLock = new Object();
+
     private MediaPlayer mPlayer;
+
+
+    @GuardedBy("mCmdQueue")
     private PowerManager.WakeLock mWakeLock;
+
     private final Object mQueueAudioFocusLock = new Object();
-    private AudioManager mAudioManagerWithAudioFocus; // synchronized on mQueueAudioFocusLock
+    @GuardedBy("mQueueAudioFocusLock")
+    private AudioManager mAudioManagerWithAudioFocus;
+
     private int mNotificationRampTimeMs = 0;
 
     // The current state according to the caller.  Reality lags behind
@@ -311,6 +337,7 @@
      */
     @Deprecated
     public void play(Context context, Uri uri, boolean looping, int stream) {
+        if (DEBUG) { Log.d(mTag, "play uri=" + uri.toString()); }
         PlayerBase.deprecateStreamTypeForPlayback(stream, "NotificationPlayer", "play");
         Command cmd = new Command();
         cmd.requestTime = SystemClock.uptimeMillis();
@@ -339,6 +366,7 @@
      *          (see {@link MediaPlayer#setAudioAttributes(AudioAttributes)})
      */
     public void play(Context context, Uri uri, boolean looping, AudioAttributes attributes) {
+        if (DEBUG) { Log.d(mTag, "play uri=" + uri.toString()); }
         Command cmd = new Command();
         cmd.requestTime = SystemClock.uptimeMillis();
         cmd.code = PLAY;
@@ -357,6 +385,7 @@
      * at this point.  Calling this multiple times has no ill effects.
      */
     public void stop() {
+        if (DEBUG) { Log.d(mTag, "stop"); }
         synchronized (mCmdQueue) {
             // This check allows stop to be called multiple times without starting
             // a thread that ends up doing nothing.
@@ -370,6 +399,7 @@
         }
     }
 
+    @GuardedBy("mCmdQueue")
     private void enqueueLocked(Command cmd) {
         mCmdQueue.add(cmd);
         if (mThread == null) {
@@ -393,22 +423,26 @@
      * @hide
      */
     public void setUsesWakeLock(Context context) {
-        if (mWakeLock != null || mThread != null) {
-            // if either of these has happened, we've already played something.
-            // and our releases will be out of sync.
-            throw new RuntimeException("assertion failed mWakeLock=" + mWakeLock
-                    + " mThread=" + mThread);
+        synchronized (mCmdQueue) {
+            if (mWakeLock != null || mThread != null) {
+                // if either of these has happened, we've already played something.
+                // and our releases will be out of sync.
+                throw new RuntimeException("assertion failed mWakeLock=" + mWakeLock
+                        + " mThread=" + mThread);
+            }
+            PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
+            mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, mTag);
         }
-        PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
-        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, mTag);
     }
 
+    @GuardedBy("mCmdQueue")
     private void acquireWakeLock() {
         if (mWakeLock != null) {
             mWakeLock.acquire();
         }
     }
 
+    @GuardedBy("mCmdQueue")
     private void releaseWakeLock() {
         if (mWakeLock != null) {
             mWakeLock.release();
diff --git a/com/android/systemui/pip/phone/PipManager.java b/com/android/systemui/pip/phone/PipManager.java
index b3f992d..f8996aa 100644
--- a/com/android/systemui/pip/phone/PipManager.java
+++ b/com/android/systemui/pip/phone/PipManager.java
@@ -16,7 +16,8 @@
 
 package com.android.systemui.pip.phone;
 
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.view.Display.DEFAULT_DISPLAY;
 
 import android.app.ActivityManager;
@@ -30,6 +31,7 @@
 import android.os.Handler;
 import android.os.RemoteException;
 import android.util.Log;
+import android.util.Pair;
 import android.view.IPinnedStackController;
 import android.view.IPinnedStackListener;
 import android.view.IWindowManager;
@@ -70,11 +72,11 @@
      */
     TaskStackListener mTaskStackListener = new TaskStackListener() {
         @Override
-        public void onActivityPinned(String packageName, int taskId) {
+        public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
             mTouchHandler.onActivityPinned();
             mMediaController.onActivityPinned();
             mMenuController.onActivityPinned();
-            mNotificationController.onActivityPinned(packageName,
+            mNotificationController.onActivityPinned(packageName, userId,
                     true /* deferUntilAnimationEnds */);
 
             SystemServicesProxy.getInstance(mContext).setPipVisibility(true);
@@ -82,13 +84,15 @@
 
         @Override
         public void onActivityUnpinned() {
-            ComponentName topPipActivity = PipUtils.getTopPinnedActivity(mContext,
-                    mActivityManager);
-            mMenuController.onActivityUnpinned(topPipActivity);
-            mTouchHandler.onActivityUnpinned(topPipActivity);
-            mNotificationController.onActivityUnpinned(topPipActivity);
+            final Pair<ComponentName, Integer> topPipActivityInfo = PipUtils.getTopPinnedActivity(
+                    mContext, mActivityManager);
+            final ComponentName topActivity = topPipActivityInfo.first;
+            final int userId = topActivity != null ? topPipActivityInfo.second : 0;
+            mMenuController.onActivityUnpinned();
+            mTouchHandler.onActivityUnpinned(topActivity);
+            mNotificationController.onActivityUnpinned(topActivity, userId);
 
-            SystemServicesProxy.getInstance(mContext).setPipVisibility(topPipActivity != null);
+            SystemServicesProxy.getInstance(mContext).setPipVisibility(topActivity != null);
         }
 
         @Override
@@ -196,7 +200,8 @@
     public final void onBusEvent(ExpandPipEvent event) {
         if (event.clearThumbnailWindows) {
             try {
-                StackInfo stackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
+                StackInfo stackInfo = mActivityManager.getStackInfo(
+                        WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
                 if (stackInfo != null && stackInfo.taskIds != null) {
                     SystemServicesProxy ssp = SystemServicesProxy.getInstance(mContext);
                     for (int taskId : stackInfo.taskIds) {
diff --git a/com/android/systemui/pip/phone/PipMediaController.java b/com/android/systemui/pip/phone/PipMediaController.java
index b3a0794..174a7ef 100644
--- a/com/android/systemui/pip/phone/PipMediaController.java
+++ b/com/android/systemui/pip/phone/PipMediaController.java
@@ -230,7 +230,7 @@
     private void resolveActiveMediaController(List<MediaController> controllers) {
         if (controllers != null) {
             final ComponentName topActivity = PipUtils.getTopPinnedActivity(mContext,
-                    mActivityManager);
+                    mActivityManager).first;
             if (topActivity != null) {
                 for (int i = 0; i < controllers.size(); i++) {
                     final MediaController controller = controllers.get(i);
diff --git a/com/android/systemui/pip/phone/PipMenuActivityController.java b/com/android/systemui/pip/phone/PipMenuActivityController.java
index 34666fb..9fb201b 100644
--- a/com/android/systemui/pip/phone/PipMenuActivityController.java
+++ b/com/android/systemui/pip/phone/PipMenuActivityController.java
@@ -16,7 +16,8 @@
 
 package com.android.systemui.pip.phone;
 
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 
 import android.app.ActivityManager.StackInfo;
 import android.app.ActivityOptions;
@@ -223,7 +224,7 @@
         }
     }
 
-    public void onActivityUnpinned(ComponentName topPipActivity) {
+    public void onActivityUnpinned() {
         hideMenu();
         setStartActivityRequested(false);
     }
@@ -383,7 +384,8 @@
     private void startMenuActivity(int menuState, Rect stackBounds, Rect movementBounds,
             boolean allowMenuTimeout, boolean willResizeMenu) {
         try {
-            StackInfo pinnedStackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
+            StackInfo pinnedStackInfo = mActivityManager.getStackInfo(
+                    WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
             if (pinnedStackInfo != null && pinnedStackInfo.taskIds != null &&
                     pinnedStackInfo.taskIds.length > 0) {
                 Intent intent = new Intent(mContext, PipMenuActivity.class);
@@ -421,7 +423,8 @@
             // Fetch the pinned stack bounds
             Rect stackBounds = null;
             try {
-                StackInfo pinnedStackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
+                StackInfo pinnedStackInfo = mActivityManager.getStackInfo(
+                        WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
                 if (pinnedStackInfo != null) {
                     stackBounds = pinnedStackInfo.bounds;
                 }
diff --git a/com/android/systemui/pip/phone/PipMotionHelper.java b/com/android/systemui/pip/phone/PipMotionHelper.java
index cebb22f..21a836c 100644
--- a/com/android/systemui/pip/phone/PipMotionHelper.java
+++ b/com/android/systemui/pip/phone/PipMotionHelper.java
@@ -16,8 +16,10 @@
 
 package com.android.systemui.pip.phone;
 
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
 
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static com.android.systemui.Interpolators.FAST_OUT_LINEAR_IN;
 import static com.android.systemui.Interpolators.FAST_OUT_SLOW_IN;
 import static com.android.systemui.Interpolators.LINEAR_OUT_SLOW_IN;
@@ -121,7 +123,8 @@
     void synchronizePinnedStackBounds() {
         cancelAnimations();
         try {
-            StackInfo stackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
+            StackInfo stackInfo =
+                    mActivityManager.getStackInfo(WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
             if (stackInfo != null) {
                 mBounds.set(stackInfo.bounds);
             }
@@ -158,13 +161,7 @@
         mMenuController.hideMenuWithoutResize();
         mHandler.post(() -> {
             try {
-                if (skipAnimation) {
-                    mActivityManager.moveTasksToFullscreenStack(PINNED_STACK_ID, true /* onTop */);
-                } else {
-                    mActivityManager.resizeStack(PINNED_STACK_ID, null /* bounds */,
-                            true /* allowResizeInDockedMode */, true /* preserveWindows */,
-                            true /* animate */, EXPAND_STACK_TO_FULLSCREEN_DURATION);
-                }
+                mActivityManager.dismissPip(!skipAnimation, EXPAND_STACK_TO_FULLSCREEN_DURATION);
             } catch (RemoteException e) {
                 Log.e(TAG, "Error expanding PiP activity", e);
             }
@@ -182,7 +179,7 @@
         mMenuController.hideMenuWithoutResize();
         mHandler.post(() -> {
             try {
-                mActivityManager.removeStack(PINNED_STACK_ID);
+                mActivityManager.removeStacksInWindowingModes(new int[]{ WINDOWING_MODE_PINNED });
             } catch (RemoteException e) {
                 Log.e(TAG, "Failed to remove PiP", e);
             }
@@ -529,14 +526,15 @@
                 Rect toBounds = (Rect) args.arg1;
                 int duration = args.argi1;
                 try {
-                    StackInfo stackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
+                    StackInfo stackInfo = mActivityManager.getStackInfo(
+                            WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
                     if (stackInfo == null) {
                         // In the case where we've already re-expanded or dismissed the PiP, then
                         // just skip the resize
                         return true;
                     }
 
-                    mActivityManager.resizeStack(PINNED_STACK_ID, toBounds,
+                    mActivityManager.resizeStack(stackInfo.stackId, toBounds,
                             false /* allowResizeInDockedMode */, true /* preserveWindows */,
                             true /* animate */, duration);
                     mBounds.set(toBounds);
diff --git a/com/android/systemui/pip/phone/PipNotificationController.java b/com/android/systemui/pip/phone/PipNotificationController.java
index 696fdbc..6d083e9 100644
--- a/com/android/systemui/pip/phone/PipNotificationController.java
+++ b/com/android/systemui/pip/phone/PipNotificationController.java
@@ -35,10 +35,15 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.res.Resources;
-import android.graphics.drawable.Icon;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
 import android.net.Uri;
+import android.os.UserHandle;
+import android.util.IconDrawableFactory;
 import android.util.Log;
+import android.util.Pair;
 
 import com.android.systemui.R;
 import com.android.systemui.SystemUI;
@@ -57,22 +62,29 @@
     private IActivityManager mActivityManager;
     private AppOpsManager mAppOpsManager;
     private NotificationManager mNotificationManager;
+    private IconDrawableFactory mIconDrawableFactory;
 
     private PipMotionHelper mMotionHelper;
 
     // Used when building a deferred notification
     private String mDeferredNotificationPackageName;
+    private int mDeferredNotificationUserId;
 
     private AppOpsManager.OnOpChangedListener mAppOpsChangedListener = new OnOpChangedListener() {
         @Override
         public void onOpChanged(String op, String packageName) {
             try {
                 // Dismiss the PiP once the user disables the app ops setting for that package
-                final ApplicationInfo appInfo = mContext.getPackageManager().getApplicationInfo(
-                        packageName, 0);
-                if (mAppOpsManager.checkOpNoThrow(OP_PICTURE_IN_PICTURE, appInfo.uid, packageName)
-                        != MODE_ALLOWED) {
-                    mMotionHelper.dismissPip();
+                final Pair<ComponentName, Integer> topPipActivityInfo =
+                        PipUtils.getTopPinnedActivity(mContext, mActivityManager);
+                if (topPipActivityInfo.first != null) {
+                    final ApplicationInfo appInfo = mContext.getPackageManager()
+                            .getApplicationInfoAsUser(packageName, 0, topPipActivityInfo.second);
+                    if (appInfo.packageName.equals(topPipActivityInfo.first.getPackageName()) &&
+                                mAppOpsManager.checkOpNoThrow(OP_PICTURE_IN_PICTURE, appInfo.uid,
+                                        packageName) != MODE_ALLOWED) {
+                        mMotionHelper.dismissPip();
+                    }
                 }
             } catch (NameNotFoundException e) {
                 // Unregister the listener if the package can't be found
@@ -88,16 +100,18 @@
         mAppOpsManager = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
         mNotificationManager = NotificationManager.from(context);
         mMotionHelper = motionHelper;
+        mIconDrawableFactory = IconDrawableFactory.newInstance(context);
     }
 
-    public void onActivityPinned(String packageName, boolean deferUntilAnimationEnds) {
+    public void onActivityPinned(String packageName, int userId, boolean deferUntilAnimationEnds) {
         // Clear any existing notification
         mNotificationManager.cancel(NOTIFICATION_TAG, NOTIFICATION_ID);
 
         if (deferUntilAnimationEnds) {
             mDeferredNotificationPackageName = packageName;
+            mDeferredNotificationUserId = userId;
         } else {
-            showNotificationForApp(mDeferredNotificationPackageName);
+            showNotificationForApp(packageName, userId);
         }
 
         // Register for changes to the app ops setting for this package while it is in PiP
@@ -106,22 +120,25 @@
 
     public void onPinnedStackAnimationEnded() {
         if (mDeferredNotificationPackageName != null) {
-            showNotificationForApp(mDeferredNotificationPackageName);
+            showNotificationForApp(mDeferredNotificationPackageName, mDeferredNotificationUserId);
             mDeferredNotificationPackageName = null;
+            mDeferredNotificationUserId = 0;
         }
     }
 
-    public void onActivityUnpinned(ComponentName topPipActivity) {
+    public void onActivityUnpinned(ComponentName topPipActivity, int userId) {
         // Unregister for changes to the previously PiP'ed package
         unregisterAppOpsListener();
 
         // Reset the deferred notification package
         mDeferredNotificationPackageName = null;
+        mDeferredNotificationUserId = 0;
 
         if (topPipActivity != null) {
             // onActivityUnpinned() is only called after the transition is complete, so we don't
             // need to defer until the animation ends to update the notification
-            onActivityPinned(topPipActivity.getPackageName(), false /* deferUntilAnimationEnds */);
+            onActivityPinned(topPipActivity.getPackageName(), userId,
+                    false /* deferUntilAnimationEnds */);
         } else {
             mNotificationManager.cancel(NOTIFICATION_TAG, NOTIFICATION_ID);
         }
@@ -130,20 +147,27 @@
     /**
      * Builds and shows the notification for the given app.
      */
-    private void showNotificationForApp(String packageName) {
+    private void showNotificationForApp(String packageName, int userId) {
         // Build a new notification
-        final Notification.Builder builder =
-                new Notification.Builder(mContext, NotificationChannels.GENERAL)
-                        .setLocalOnly(true)
-                        .setOngoing(true)
-                        .setSmallIcon(R.drawable.pip_notification_icon)
-                        .setColor(mContext.getColor(
-                                com.android.internal.R.color.system_notification_accent_color));
-        if (updateNotificationForApp(builder, packageName)) {
-            SystemUI.overrideNotificationAppName(mContext, builder);
+        try {
+            final UserHandle user = UserHandle.of(userId);
+            final Context userContext = mContext.createPackageContextAsUser(
+                    mContext.getPackageName(), 0, user);
+            final Notification.Builder builder =
+                    new Notification.Builder(userContext, NotificationChannels.GENERAL)
+                            .setLocalOnly(true)
+                            .setOngoing(true)
+                            .setSmallIcon(R.drawable.pip_notification_icon)
+                            .setColor(mContext.getColor(
+                                    com.android.internal.R.color.system_notification_accent_color));
+            if (updateNotificationForApp(builder, packageName, user)) {
+                SystemUI.overrideNotificationAppName(mContext, builder);
 
-            // Show the new notification
-            mNotificationManager.notify(NOTIFICATION_TAG, NOTIFICATION_ID, builder.build());
+                // Show the new notification
+                mNotificationManager.notify(NOTIFICATION_TAG, NOTIFICATION_ID, builder.build());
+            }
+        } catch (NameNotFoundException e) {
+            Log.e(TAG, "Could not show notification for application", e);
         }
     }
 
@@ -151,33 +175,33 @@
      * Updates the notification builder with app-specific information, returning whether it was
      * successful.
      */
-    private boolean updateNotificationForApp(Notification.Builder builder, String packageName) {
+    private boolean updateNotificationForApp(Notification.Builder builder, String packageName,
+            UserHandle user) throws NameNotFoundException {
         final PackageManager pm = mContext.getPackageManager();
         final ApplicationInfo appInfo;
         try {
-            appInfo = pm.getApplicationInfo(packageName, 0);
+            appInfo = pm.getApplicationInfoAsUser(packageName, 0, user.getIdentifier());
         } catch (NameNotFoundException e) {
             Log.e(TAG, "Could not update notification for application", e);
             return false;
         }
 
         if (appInfo != null) {
-            final String appName = pm.getApplicationLabel(appInfo).toString();
+            final String appName = pm.getUserBadgedLabel(pm.getApplicationLabel(appInfo), user)
+                    .toString();
             final String message = mContext.getString(R.string.pip_notification_message, appName);
             final Intent settingsIntent = new Intent(ACTION_PICTURE_IN_PICTURE_SETTINGS,
                     Uri.fromParts("package", packageName, null));
+            settingsIntent.putExtra(Intent.EXTRA_USER_HANDLE, user);
             settingsIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
-            final Icon appIcon = appInfo.icon != 0
-                    ? Icon.createWithResource(packageName, appInfo.icon)
-                    : Icon.createWithResource(Resources.getSystem(),
-                            com.android.internal.R.drawable.sym_def_app_icon);
 
+            final Drawable iconDrawable = mIconDrawableFactory.getBadgedIcon(appInfo);
             builder.setContentTitle(mContext.getString(R.string.pip_notification_title, appName))
                     .setContentText(message)
-                    .setContentIntent(PendingIntent.getActivity(mContext, packageName.hashCode(),
-                            settingsIntent, FLAG_CANCEL_CURRENT))
+                    .setContentIntent(PendingIntent.getActivityAsUser(mContext, packageName.hashCode(),
+                            settingsIntent, FLAG_CANCEL_CURRENT, null, user))
                     .setStyle(new Notification.BigTextStyle().bigText(message))
-                    .setLargeIcon(appIcon);
+                    .setLargeIcon(createBitmap(iconDrawable).createAshmemBitmap());
             return true;
         }
         return false;
@@ -191,4 +215,17 @@
     private void unregisterAppOpsListener() {
         mAppOpsManager.stopWatchingMode(mAppOpsChangedListener);
     }
+
+    /**
+     * Bakes a drawable into a bitmap.
+     */
+    private Bitmap createBitmap(Drawable d) {
+        Bitmap bitmap = Bitmap.createBitmap(d.getIntrinsicWidth(), d.getIntrinsicHeight(),
+                Config.ARGB_8888);
+        Canvas c = new Canvas(bitmap);
+        d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight());
+        d.draw(c);
+        c.setBitmap(null);
+        return bitmap;
+    }
 }
diff --git a/com/android/systemui/pip/phone/PipUtils.java b/com/android/systemui/pip/phone/PipUtils.java
index a8cdd1b..2f53de9 100644
--- a/com/android/systemui/pip/phone/PipUtils.java
+++ b/com/android/systemui/pip/phone/PipUtils.java
@@ -16,7 +16,8 @@
 
 package com.android.systemui.pip.phone;
 
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 
 import android.app.ActivityManager.StackInfo;
 import android.app.IActivityManager;
@@ -24,33 +25,35 @@
 import android.content.Context;
 import android.os.RemoteException;
 import android.util.Log;
+import android.util.Pair;
 
 public class PipUtils {
 
     private static final String TAG = "PipUtils";
 
     /**
-     * @return the ComponentName of the top non-SystemUI activity in the pinned stack, or null if
-     *         none exists.
+     * @return the ComponentName and user id of the top non-SystemUI activity in the pinned stack.
+     *         The component name may be null if no such activity exists.
      */
-    public static ComponentName getTopPinnedActivity(Context context,
+    public static Pair<ComponentName, Integer> getTopPinnedActivity(Context context,
             IActivityManager activityManager) {
         try {
             final String sysUiPackageName = context.getPackageName();
-            final StackInfo pinnedStackInfo = activityManager.getStackInfo(PINNED_STACK_ID);
+            final StackInfo pinnedStackInfo =
+                    activityManager.getStackInfo(WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
             if (pinnedStackInfo != null && pinnedStackInfo.taskIds != null &&
                     pinnedStackInfo.taskIds.length > 0) {
                 for (int i = pinnedStackInfo.taskNames.length - 1; i >= 0; i--) {
                     ComponentName cn = ComponentName.unflattenFromString(
                             pinnedStackInfo.taskNames[i]);
                     if (cn != null && !cn.getPackageName().equals(sysUiPackageName)) {
-                        return cn;
+                        return new Pair<>(cn, pinnedStackInfo.taskUserIds[i]);
                     }
                 }
             }
         } catch (RemoteException e) {
             Log.w(TAG, "Unable to get pinned stack.");
         }
-        return null;
+        return new Pair<>(null, 0);
     }
 }
diff --git a/com/android/systemui/pip/tv/PipManager.java b/com/android/systemui/pip/tv/PipManager.java
index e8c1295..e0445c1 100644
--- a/com/android/systemui/pip/tv/PipManager.java
+++ b/com/android/systemui/pip/tv/PipManager.java
@@ -53,7 +53,9 @@
 import java.util.ArrayList;
 import java.util.List;
 
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
+import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.view.Display.DEFAULT_DISPLAY;
 
 /**
@@ -121,6 +123,7 @@
     private int mLastOrientation = Configuration.ORIENTATION_UNDEFINED;
     private boolean mInitialized;
     private int mPipTaskId = TASK_ID_NO_PIP;
+    private int mPinnedStackId = INVALID_STACK_ID;
     private ComponentName mPipComponentName;
     private MediaController mPipMediaController;
     private String[] mLastPackagesResourceGranted;
@@ -336,9 +339,11 @@
         mMediaSessionManager.removeOnActiveSessionsChangedListener(mActiveMediaSessionListener);
         if (removePipStack) {
             try {
-                mActivityManager.removeStack(PINNED_STACK_ID);
+                mActivityManager.removeStack(mPinnedStackId);
             } catch (RemoteException e) {
                 Log.e(TAG, "removeStack failed", e);
+            } finally {
+                mPinnedStackId = INVALID_STACK_ID;
             }
         }
         for (int i = mListeners.size() - 1; i >= 0; --i) {
@@ -424,7 +429,7 @@
         }
         try {
             int animationDurationMs = -1;
-            mActivityManager.resizeStack(PINNED_STACK_ID, mCurrentPipBounds,
+            mActivityManager.resizeStack(mPinnedStackId, mCurrentPipBounds,
                     true, true, true, animationDurationMs);
         } catch (RemoteException e) {
             Log.e(TAG, "resizeStack failed", e);
@@ -502,7 +507,8 @@
     private StackInfo getPinnedStackInfo() {
         StackInfo stackInfo = null;
         try {
-            stackInfo = mActivityManager.getStackInfo(PINNED_STACK_ID);
+            stackInfo = mActivityManager.getStackInfo(
+                    WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
         } catch (RemoteException e) {
             Log.e(TAG, "getStackInfo failed", e);
         }
@@ -654,7 +660,7 @@
         }
 
         @Override
-        public void onActivityPinned(String packageName, int taskId) {
+        public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
             if (DEBUG) Log.d(TAG, "onActivityPinned()");
             if (!checkCurrentUserId(mContext, DEBUG)) {
                 return;
@@ -665,6 +671,7 @@
                 return;
             }
             if (DEBUG) Log.d(TAG, "PINNED_STACK:" + stackInfo);
+            mPinnedStackId = stackInfo.stackId;
             mPipTaskId = stackInfo.taskIds[stackInfo.taskIds.length - 1];
             mPipComponentName = ComponentName.unflattenFromString(
                     stackInfo.taskNames[stackInfo.taskNames.length - 1]);
diff --git a/com/android/systemui/qs/AlphaControlledSignalTileView.java b/com/android/systemui/qs/AlphaControlledSignalTileView.java
new file mode 100644
index 0000000..2c7ec70
--- /dev/null
+++ b/com/android/systemui/qs/AlphaControlledSignalTileView.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs;
+
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.graphics.drawable.Drawable;
+import com.android.systemui.qs.tileimpl.SlashImageView;
+
+
+/**
+ * Creates AlphaControlledSlashImageView instead of SlashImageView
+ */
+public class AlphaControlledSignalTileView extends SignalTileView {
+    public AlphaControlledSignalTileView(Context context) {
+        super(context);
+    }
+
+    @Override
+    protected SlashImageView createSlashImageView(Context context) {
+        return new AlphaControlledSlashImageView(context);
+    }
+
+    /**
+     * Creates AlphaControlledSlashDrawable instead of regular SlashDrawables
+     */
+    public static class AlphaControlledSlashImageView extends SlashImageView {
+        public AlphaControlledSlashImageView(Context context) {
+            super(context);
+        }
+
+        public void setFinalImageTintList(ColorStateList tint) {
+            super.setImageTintList(tint);
+            final SlashDrawable slash = getSlash();
+            if (slash != null) {
+                ((AlphaControlledSlashDrawable)slash).setFinalTintList(tint);
+            }
+        }
+
+        @Override
+        protected void ensureSlashDrawable() {
+            if (getSlash() == null) {
+                final SlashDrawable slash = new AlphaControlledSlashDrawable(getDrawable());
+                setSlash(slash);
+                slash.setAnimationEnabled(getAnimationEnabled());
+                setImageViewDrawable(slash);
+            }
+        }
+    }
+
+    /**
+     * SlashDrawable that disobeys orders to change its drawable's tint except when you tell
+     * it not to disobey. The slash still will animate its alpha.
+     */
+    public static class AlphaControlledSlashDrawable extends SlashDrawable {
+        AlphaControlledSlashDrawable(Drawable d) {
+            super(d);
+        }
+
+        @Override
+        protected void setDrawableTintList(ColorStateList tint) {
+        }
+
+        /**
+         * Set a target tint list instead of
+         */
+        public void setFinalTintList(ColorStateList tint) {
+            super.setDrawableTintList(tint);
+        }
+    }
+}
+
diff --git a/com/android/systemui/qs/SignalTileView.java b/com/android/systemui/qs/SignalTileView.java
index b300e4a..9ee40cc 100644
--- a/com/android/systemui/qs/SignalTileView.java
+++ b/com/android/systemui/qs/SignalTileView.java
@@ -63,13 +63,17 @@
     @Override
     protected View createIcon() {
         mIconFrame = new FrameLayout(mContext);
-        mSignal = new SlashImageView(mContext);
+        mSignal = createSlashImageView(mContext);
         mIconFrame.addView(mSignal);
         mOverlay = new ImageView(mContext);
         mIconFrame.addView(mOverlay, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
         return mIconFrame;
     }
 
+    protected SlashImageView createSlashImageView(Context context) {
+        return new SlashImageView(context);
+    }
+
     @Override
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
diff --git a/com/android/systemui/qs/SlashDrawable.java b/com/android/systemui/qs/SlashDrawable.java
index c356148..a9b2376 100644
--- a/com/android/systemui/qs/SlashDrawable.java
+++ b/com/android/systemui/qs/SlashDrawable.java
@@ -197,11 +197,15 @@
     public void setTintList(@Nullable ColorStateList tint) {
         mTintList = tint;
         super.setTintList(tint);
-        mDrawable.setTintList(tint);
+        setDrawableTintList(tint);
         mPaint.setColor(tint.getDefaultColor());
         invalidateSelf();
     }
 
+    protected void setDrawableTintList(@Nullable ColorStateList tint) {
+        mDrawable.setTintList(tint);
+    }
+
     @Override
     public void setTintMode(@NonNull Mode tintMode) {
         mTintMode = tintMode;
diff --git a/com/android/systemui/qs/tileimpl/QSIconViewImpl.java b/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
index 8074cb9..e8c8b90 100644
--- a/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
+++ b/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
@@ -33,6 +33,7 @@
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.qs.QSTile.State;
 
+import com.android.systemui.qs.AlphaControlledSignalTileView.AlphaControlledSlashImageView;
 import java.util.Objects;
 
 public class QSIconViewImpl extends QSIconView {
@@ -138,7 +139,12 @@
                 animateGrayScale(mTint, color, iv);
                 mTint = color;
             } else {
-                setTint(iv, color);
+                if (iv instanceof AlphaControlledSlashImageView) {
+                    ((AlphaControlledSlashImageView)iv)
+                            .setFinalImageTintList(ColorStateList.valueOf(color));
+                } else {
+                    setTint(iv, color);
+                }
                 mTint = color;
             }
         }
@@ -149,6 +155,10 @@
     }
 
     public static void animateGrayScale(int fromColor, int toColor, ImageView iv) {
+        if (iv instanceof AlphaControlledSlashImageView) {
+            ((AlphaControlledSlashImageView)iv)
+                    .setFinalImageTintList(ColorStateList.valueOf(toColor));
+        }
         if (ValueAnimator.areAnimatorsEnabled()) {
             final float fromAlpha = Color.alpha(fromColor);
             final float toAlpha = Color.alpha(toColor);
diff --git a/com/android/systemui/qs/tileimpl/SlashImageView.java b/com/android/systemui/qs/tileimpl/SlashImageView.java
index 97e9c3d..63d6f82 100644
--- a/com/android/systemui/qs/tileimpl/SlashImageView.java
+++ b/com/android/systemui/qs/tileimpl/SlashImageView.java
@@ -34,7 +34,15 @@
         super(context);
     }
 
-    private void ensureSlashDrawable() {
+    protected SlashDrawable getSlash() {
+        return mSlash;
+    }
+
+    protected void setSlash(SlashDrawable slash) {
+        mSlash = slash;
+    }
+
+    protected void ensureSlashDrawable() {
         if (mSlash == null) {
             mSlash = new SlashDrawable(getDrawable());
             mSlash.setAnimationEnabled(mAnimationEnabled);
@@ -56,10 +64,18 @@
         }
     }
 
+    protected void setImageViewDrawable(SlashDrawable slash) {
+        super.setImageDrawable(slash);
+    }
+
     public void setAnimationEnabled(boolean enabled) {
         mAnimationEnabled = enabled;
     }
 
+    public boolean getAnimationEnabled() {
+        return mAnimationEnabled;
+    }
+
     private void setSlashState(@NonNull SlashState slashState) {
         ensureSlashDrawable();
         mSlash.setRotation(slashState.rotation);
diff --git a/com/android/systemui/qs/tiles/BluetoothTile.java b/com/android/systemui/qs/tiles/BluetoothTile.java
index 8d62f2a..81b8622 100644
--- a/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -23,6 +23,7 @@
 import android.bluetooth.BluetoothProfile;
 import android.content.Context;
 import android.content.Intent;
+import android.graphics.drawable.Drawable;
 import android.provider.Settings;
 import android.service.quicksettings.Tile;
 import android.text.TextUtils;
@@ -34,10 +35,8 @@
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.settingslib.Utils;
 import com.android.settingslib.bluetooth.CachedBluetoothDevice;
-import com.android.settingslib.graph.BluetoothDeviceLayerDrawable;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
-import com.android.systemui.R.drawable;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.qs.DetailAdapter;
 import com.android.systemui.plugins.qs.QSTile.BooleanState;
@@ -135,11 +134,7 @@
                 if (lastDevice != null) {
                     int batteryLevel = lastDevice.getBatteryLevel();
                     if (batteryLevel != BluetoothDevice.BATTERY_LEVEL_UNKNOWN) {
-                        BluetoothDeviceLayerDrawable drawable = createLayerDrawable(mContext,
-                                R.drawable.ic_qs_bluetooth_connected, batteryLevel,
-                                mContext.getResources().getFraction(
-                                        R.fraction.bt_battery_scale_fraction, 1, 1));
-                        state.icon = new DrawableIcon(drawable);
+                        state.icon = new BluetoothBatteryDrawable(batteryLevel);
                     }
                 }
                 state.contentDescription = mContext.getString(
@@ -215,6 +210,22 @@
         return new BluetoothDetailAdapter();
     }
 
+    private class BluetoothBatteryDrawable extends Icon {
+        private int mLevel;
+
+        BluetoothBatteryDrawable(int level) {
+            mLevel = level;
+        }
+
+        @Override
+        public Drawable getDrawable(Context context) {
+            return createLayerDrawable(context,
+                    R.drawable.ic_qs_bluetooth_connected, mLevel,
+                    context.getResources().getFraction(
+                            R.fraction.bt_battery_scale_fraction, 1, 1));
+        }
+    }
+
     protected class BluetoothDetailAdapter implements DetailAdapter, QSDetailItems.Callback {
         // We probably won't ever have space in the UI for more than 20 devices, so don't
         // get info for them.
diff --git a/com/android/systemui/qs/tiles/CellularTile.java b/com/android/systemui/qs/tiles/CellularTile.java
index 2e389ba..0ce3e6a 100644
--- a/com/android/systemui/qs/tiles/CellularTile.java
+++ b/com/android/systemui/qs/tiles/CellularTile.java
@@ -266,7 +266,7 @@
         }
 
         @Override
-        public void setNoSims(boolean show) {
+        public void setNoSims(boolean show, boolean simDetected) {
             mInfo.noSim = show;
             if (mInfo.noSim) {
                 // Make sure signal gets cleared out when no sims.
diff --git a/com/android/systemui/qs/tiles/WifiTile.java b/com/android/systemui/qs/tiles/WifiTile.java
index 33b1512..2370273 100644
--- a/com/android/systemui/qs/tiles/WifiTile.java
+++ b/com/android/systemui/qs/tiles/WifiTile.java
@@ -37,10 +37,10 @@
 import com.android.systemui.plugins.qs.QSIconView;
 import com.android.systemui.plugins.qs.QSTile;
 import com.android.systemui.plugins.qs.QSTile.SignalState;
+import com.android.systemui.qs.AlphaControlledSignalTileView;
 import com.android.systemui.qs.QSDetailItems;
 import com.android.systemui.qs.QSDetailItems.Item;
 import com.android.systemui.qs.QSHost;
-import com.android.systemui.qs.SignalTileView;
 import com.android.systemui.qs.tileimpl.QSTileImpl;
 import com.android.systemui.statusbar.policy.NetworkController;
 import com.android.systemui.statusbar.policy.NetworkController.AccessPointController;
@@ -104,7 +104,7 @@
 
     @Override
     public QSIconView createTileView(Context context) {
-        return new SignalTileView(context);
+        return new AlphaControlledSignalTileView(context);
     }
 
     @Override
diff --git a/com/android/systemui/recents/Recents.java b/com/android/systemui/recents/Recents.java
index 406bcac..283ac0c 100644
--- a/com/android/systemui/recents/Recents.java
+++ b/com/android/systemui/recents/Recents.java
@@ -16,6 +16,9 @@
 
 package com.android.systemui.recents;
 
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_RECENT_APPS;
 
 import android.app.ActivityManager;
@@ -437,9 +440,12 @@
         int currentUser = sSystemServicesProxy.getCurrentUser();
         SystemServicesProxy ssp = Recents.getSystemServices();
         ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
+        final int activityType = runningTask != null
+                ? runningTask.configuration.windowConfiguration.getActivityType()
+                : ACTIVITY_TYPE_UNDEFINED;
         boolean screenPinningActive = ssp.isScreenPinningActive();
-        boolean isRunningTaskInHomeOrRecentsStack = runningTask != null &&
-                ActivityManager.StackId.isHomeOrRecentsStack(runningTask.stackId);
+        boolean isRunningTaskInHomeOrRecentsStack =
+                activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS;
         if (runningTask != null && !isRunningTaskInHomeOrRecentsStack && !screenPinningActive) {
             logDockAttempt(mContext, runningTask.topActivity, runningTask.resizeMode);
             if (runningTask.supportsSplitScreenMultiWindow) {
diff --git a/com/android/systemui/recents/RecentsActivity.java b/com/android/systemui/recents/RecentsActivity.java
index f545556..86b7790 100644
--- a/com/android/systemui/recents/RecentsActivity.java
+++ b/com/android/systemui/recents/RecentsActivity.java
@@ -358,6 +358,9 @@
         mScrimViews = new SystemBarScrimViews(this);
         getWindow().getAttributes().privateFlags |=
                 WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY;
+        if (Recents.getConfiguration().isLowRamDevice) {
+            getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
+        }
 
         mLastConfig = new Configuration(Utilities.getAppConfiguration(this));
         mFocusTimerDuration = getResources().getInteger(R.integer.recents_auto_advance_duration);
diff --git a/com/android/systemui/recents/RecentsImpl.java b/com/android/systemui/recents/RecentsImpl.java
index aecf95f..3e2a5f3 100644
--- a/com/android/systemui/recents/RecentsImpl.java
+++ b/com/android/systemui/recents/RecentsImpl.java
@@ -16,9 +16,9 @@
 
 package com.android.systemui.recents;
 
-import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
-import static android.app.ActivityManager.StackId.isHomeOrRecentsStack;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.view.View.MeasureSpec;
 
 import android.app.ActivityManager;
@@ -173,7 +173,7 @@
         }
 
         @Override
-        public void onActivityPinned(String packageName, int taskId) {
+        public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
             // Check this is for the right user
             if (!checkCurrentUserId(mContext, false /* debug */)) {
                 return;
@@ -533,7 +533,9 @@
         if (runningTask == null) return;
 
         // Find the task in the recents list
-        boolean isRunningTaskInHomeStack = SystemServicesProxy.isHomeStack(runningTask.stackId);
+        boolean isRunningTaskInHomeStack =
+                runningTask.configuration.windowConfiguration.getActivityType()
+                        == ACTIVITY_TYPE_HOME;
         ArrayList<Task> tasks = focusedStack.getStackTasks();
         Task toTask = null;
         ActivityOptions launchOpts = null;
@@ -565,8 +567,7 @@
 
         // Launch the task
         ssp.startActivityFromRecents(
-                mContext, toTask.key, toTask.title, launchOpts, INVALID_STACK_ID,
-                null /* resultListener */);
+                mContext, toTask.key, toTask.title, launchOpts, null /* resultListener */);
     }
 
     /**
@@ -584,9 +585,10 @@
 
         // Return early if there is no running task (can't determine affiliated tasks in this case)
         ActivityManager.RunningTaskInfo runningTask = ssp.getRunningTask();
+        final int activityType = runningTask.configuration.windowConfiguration.getActivityType();
         if (runningTask == null) return;
         // Return early if the running task is in the home/recents stack (optimization)
-        if (isHomeOrRecentsStack(runningTask.stackId)) return;
+        if (activityType == ACTIVITY_TYPE_HOME || activityType == ACTIVITY_TYPE_RECENTS) return;
 
         // Find the task in the recents list
         ArrayList<Task> tasks = focusedStack.getStackTasks();
@@ -639,8 +641,7 @@
 
         // Launch the task
         ssp.startActivityFromRecents(
-                mContext, toTask.key, toTask.title, launchOpts, INVALID_STACK_ID,
-                null /* resultListener */);
+                mContext, toTask.key, toTask.title, launchOpts, null /* resultListener */);
     }
 
     public void showNextAffiliatedTask() {
@@ -872,7 +873,9 @@
             getThumbnailTransitionActivityOptions(ActivityManager.RunningTaskInfo runningTask,
                     Rect windowOverrideRect) {
         final boolean isLowRamDevice = Recents.getConfiguration().isLowRamDevice;
-        if (runningTask != null && runningTask.stackId == FREEFORM_WORKSPACE_STACK_ID) {
+        if (runningTask != null
+                && runningTask.configuration.windowConfiguration.getWindowingMode()
+                == WINDOWING_MODE_FREEFORM) {
             ArrayList<AppTransitionAnimationSpec> specs = new ArrayList<>();
             ArrayList<Task> tasks = mDummyStackView.getStack().getStackTasks();
             TaskStackLayoutAlgorithm stackLayout = mDummyStackView.getStackAlgorithm();
diff --git a/com/android/systemui/recents/events/activity/HideStackActionButtonEvent.java b/com/android/systemui/recents/events/activity/HideStackActionButtonEvent.java
index e02fb14..e4a4f59 100644
--- a/com/android/systemui/recents/events/activity/HideStackActionButtonEvent.java
+++ b/com/android/systemui/recents/events/activity/HideStackActionButtonEvent.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2017 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -22,5 +22,15 @@
  * This is sent when the stack action button should be hidden.
  */
 public class HideStackActionButtonEvent extends EventBus.Event {
-    // Simple event
+
+    // Whether or not to translate the stack action button when hiding it
+    public final boolean translate;
+
+    public HideStackActionButtonEvent() {
+        this(true);
+    }
+
+    public HideStackActionButtonEvent(boolean translate) {
+        this.translate = translate;
+    }
 }
diff --git a/com/android/systemui/recents/events/activity/LaunchTaskEvent.java b/com/android/systemui/recents/events/activity/LaunchTaskEvent.java
index 3db106e..862a1ee 100644
--- a/com/android/systemui/recents/events/activity/LaunchTaskEvent.java
+++ b/com/android/systemui/recents/events/activity/LaunchTaskEvent.java
@@ -16,6 +16,9 @@
 
 package com.android.systemui.recents.events.activity;
 
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+
 import android.graphics.Rect;
 
 import com.android.systemui.recents.events.EventBus;
@@ -30,15 +33,23 @@
     public final TaskView taskView;
     public final Task task;
     public final Rect targetTaskBounds;
-    public final int targetTaskStack;
+    public final int targetWindowingMode;
+    public final int targetActivityType;
     public final boolean screenPinningRequested;
 
-    public LaunchTaskEvent(TaskView taskView, Task task, Rect targetTaskBounds, int targetTaskStack,
+    public LaunchTaskEvent(TaskView taskView, Task task, Rect targetTaskBounds,
             boolean screenPinningRequested) {
+        this(taskView, task, targetTaskBounds, screenPinningRequested,
+                WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_UNDEFINED);
+    }
+
+    public LaunchTaskEvent(TaskView taskView, Task task, Rect targetTaskBounds,
+            boolean screenPinningRequested, int windowingMode, int activityType) {
         this.taskView = taskView;
         this.task = task;
         this.targetTaskBounds = targetTaskBounds;
-        this.targetTaskStack = targetTaskStack;
+        this.targetWindowingMode = windowingMode;
+        this.targetActivityType = activityType;
         this.screenPinningRequested = screenPinningRequested;
     }
 
diff --git a/com/android/systemui/recents/misc/SystemServicesProxy.java b/com/android/systemui/recents/misc/SystemServicesProxy.java
index 7177782..bddf9a5 100644
--- a/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -16,13 +16,15 @@
 
 package com.android.systemui.recents.misc;
 
-import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
-import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.HOME_STACK_ID;
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
-import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
-import static android.app.ActivityManager.StackId.RECENTS_STACK_ID;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.provider.Settings.Global.DEVELOPMENT_ENABLE_FREEFORM_WINDOWS_SUPPORT;
 
 import android.annotation.NonNull;
@@ -34,6 +36,7 @@
 import android.app.AppGlobals;
 import android.app.IActivityManager;
 import android.app.KeyguardManager;
+import android.app.WindowConfiguration;
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -172,7 +175,7 @@
         public void onTaskStackChangedBackground() { }
         public void onTaskStackChanged() { }
         public void onTaskSnapshotChanged(int taskId, TaskSnapshot snapshot) { }
-        public void onActivityPinned(String packageName, int taskId) { }
+        public void onActivityPinned(String packageName, int userId, int taskId, int stackId) { }
         public void onActivityUnpinned() { }
         public void onPinnedActivityRestartAttempt(boolean clearedTask) { }
         public void onPinnedStackAnimationStarted() { }
@@ -227,9 +230,11 @@
         }
 
         @Override
-        public void onActivityPinned(String packageName, int taskId) throws RemoteException {
+        public void onActivityPinned(String packageName, int userId, int taskId, int stackId)
+                throws RemoteException {
             mHandler.removeMessages(H.ON_ACTIVITY_PINNED);
-            mHandler.obtainMessage(H.ON_ACTIVITY_PINNED, taskId, 0, packageName).sendToTarget();
+            mHandler.obtainMessage(H.ON_ACTIVITY_PINNED,
+                    new PinnedActivityInfo(packageName, userId, taskId, stackId)).sendToTarget();
         }
 
         @Override
@@ -481,16 +486,22 @@
     public ActivityManager.RunningTaskInfo getRunningTask() {
         // Note: The set of running tasks from the system is ordered by recency
         List<ActivityManager.RunningTaskInfo> tasks = mAm.getRunningTasks(10);
-        if (tasks != null && !tasks.isEmpty()) {
-            // Find the first task in a valid stack, we ignore everything from the Recents and PiP
-            // stacks
-            for (int i = 0; i < tasks.size(); i++) {
-                ActivityManager.RunningTaskInfo task = tasks.get(i);
-                int stackId = task.stackId;
-                if (stackId != RECENTS_STACK_ID && stackId != PINNED_STACK_ID) {
-                    return task;
-                }
+        if (tasks == null || tasks.isEmpty()) {
+            return null;
+        }
+
+        // Find the first task in a valid stack, we ignore everything from the Recents and PiP
+        // stacks
+        for (int i = 0; i < tasks.size(); i++) {
+            final ActivityManager.RunningTaskInfo task = tasks.get(i);
+            final WindowConfiguration winConfig = task.configuration.windowConfiguration;
+            if (winConfig.getActivityType() == ACTIVITY_TYPE_RECENTS) {
+                continue;
             }
+            if (winConfig.getWindowingMode() == WINDOWING_MODE_PINNED) {
+                continue;
+            }
+            return task;
         }
         return null;
     }
@@ -517,12 +528,17 @@
             ActivityManager.StackInfo fullscreenStackInfo = null;
             ActivityManager.StackInfo recentsStackInfo = null;
             for (int i = 0; i < stackInfos.size(); i++) {
-                StackInfo stackInfo = stackInfos.get(i);
-                if (stackInfo.stackId == HOME_STACK_ID) {
+                final StackInfo stackInfo = stackInfos.get(i);
+                final WindowConfiguration winConfig = stackInfo.configuration.windowConfiguration;
+                final int activityType = winConfig.getActivityType();
+                final int windowingMode = winConfig.getWindowingMode();
+                if (activityType == ACTIVITY_TYPE_HOME) {
                     homeStackInfo = stackInfo;
-                } else if (stackInfo.stackId == FULLSCREEN_WORKSPACE_STACK_ID) {
+                } else if (activityType == ACTIVITY_TYPE_STANDARD
+                        && (windowingMode == WINDOWING_MODE_FULLSCREEN
+                            || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY)) {
                     fullscreenStackInfo = stackInfo;
-                } else if (stackInfo.stackId == RECENTS_STACK_ID) {
+                } else if (activityType == ACTIVITY_TYPE_RECENTS) {
                     recentsStackInfo = stackInfo;
                 }
             }
@@ -576,7 +592,7 @@
         try {
             final ActivityOptions options = ActivityOptions.makeBasic();
             options.setDockCreateMode(createMode);
-            options.setLaunchStackId(DOCKED_STACK_ID);
+            options.setLaunchWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
             mIam.startActivityFromRecents(taskId, options.toBundle());
             return true;
         } catch (Exception e) {
@@ -601,34 +617,6 @@
     }
 
     /**
-     * Returns whether the given stack id is the home stack id.
-     */
-    public static boolean isHomeStack(int stackId) {
-        return stackId == HOME_STACK_ID;
-    }
-
-    /**
-     * Returns whether the given stack id is the pinned stack id.
-     */
-    public static boolean isPinnedStack(int stackId){
-        return stackId == PINNED_STACK_ID;
-    }
-
-    /**
-     * Returns whether the given stack id is the docked stack id.
-     */
-    public static boolean isDockedStack(int stackId) {
-        return stackId == DOCKED_STACK_ID;
-    }
-
-    /**
-     * Returns whether the given stack id is the freeform workspace stack id.
-     */
-    public static boolean isFreeformStack(int stackId) {
-        return stackId == FREEFORM_WORKSPACE_STACK_ID;
-    }
-
-    /**
      * @return whether there are any docked tasks for the current user.
      */
     public boolean hasDockedTask() {
@@ -636,7 +624,8 @@
 
         ActivityManager.StackInfo stackInfo = null;
         try {
-            stackInfo = mIam.getStackInfo(DOCKED_STACK_ID);
+            stackInfo =
+                    mIam.getStackInfo(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_UNDEFINED);
         } catch (RemoteException e) {
             e.printStackTrace();
         }
@@ -737,14 +726,12 @@
         }
     }
 
-    /**
-     * Moves a task into another stack.
-     */
-    public void moveTaskToStack(int taskId, int stackId) {
+    /** Set the task's windowing mode. */
+    public void setTaskWindowingMode(int taskId, int windowingMode) {
         if (mIam == null) return;
 
         try {
-            mIam.positionTaskInStack(taskId, stackId, 0);
+            mIam.setTaskWindowingMode(taskId, windowingMode, false /* onTop */);
         } catch (RemoteException | IllegalArgumentException e) {
             e.printStackTrace();
         }
@@ -1101,9 +1088,10 @@
 
         try {
             // Use the recents stack bounds, fallback to fullscreen stack if it is null
-            ActivityManager.StackInfo stackInfo = mIam.getStackInfo(RECENTS_STACK_ID);
+            ActivityManager.StackInfo stackInfo =
+                    mIam.getStackInfo(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_RECENTS);
             if (stackInfo == null) {
-                stackInfo = mIam.getStackInfo(FULLSCREEN_WORKSPACE_STACK_ID);
+                stackInfo = mIam.getStackInfo(WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD);
             }
             if (stackInfo != null) {
                 windowRect.set(stackInfo.bounds);
@@ -1120,25 +1108,34 @@
                 opts != null ? opts.toBundle() : null, UserHandle.CURRENT));
     }
 
+    public void startActivityFromRecents(Context context, Task.TaskKey taskKey, String taskName,
+            ActivityOptions options,
+            @Nullable final StartActivityFromRecentsResultListener resultListener) {
+        startActivityFromRecents(context, taskKey, taskName, options,
+                WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_UNDEFINED, resultListener);
+    }
+
     /** Starts an activity from recents. */
     public void startActivityFromRecents(Context context, Task.TaskKey taskKey, String taskName,
-            ActivityOptions options, int stackId,
+            ActivityOptions options, int windowingMode, int activityType,
             @Nullable final StartActivityFromRecentsResultListener resultListener) {
         if (mIam == null) {
             return;
         }
-        if (taskKey.stackId == DOCKED_STACK_ID) {
+        if (taskKey.windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
             // We show non-visible docked tasks in Recents, but we always want to launch
             // them in the fullscreen stack.
             if (options == null) {
                 options = ActivityOptions.makeBasic();
             }
-            options.setLaunchStackId(FULLSCREEN_WORKSPACE_STACK_ID);
-        } else if (stackId != INVALID_STACK_ID) {
+            options.setLaunchWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+        } else if (windowingMode != WINDOWING_MODE_UNDEFINED
+                || activityType != ACTIVITY_TYPE_UNDEFINED) {
             if (options == null) {
                 options = ActivityOptions.makeBasic();
             }
-            options.setLaunchStackId(stackId);
+            options.setLaunchWindowingMode(windowingMode);
+            options.setLaunchActivityType(activityType);
         }
         final ActivityOptions finalOptions = options;
 
@@ -1307,6 +1304,20 @@
         void onStartActivityResult(boolean succeeded);
     }
 
+    private class PinnedActivityInfo {
+        final String mPackageName;
+        final int mUserId;
+        final int mTaskId;
+        final int mStackId;
+
+        PinnedActivityInfo(String packageName, int userId, int taskId, int stackId) {
+            mPackageName = packageName;
+            mUserId = userId;
+            mTaskId = taskId;
+            mStackId = stackId;
+        }
+    }
+
     private final class H extends Handler {
         private static final int ON_TASK_STACK_CHANGED = 1;
         private static final int ON_TASK_SNAPSHOT_CHANGED = 2;
@@ -1342,8 +1353,10 @@
                         break;
                     }
                     case ON_ACTIVITY_PINNED: {
+                        final PinnedActivityInfo info = (PinnedActivityInfo) msg.obj;
                         for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
-                            mTaskStackListeners.get(i).onActivityPinned((String) msg.obj, msg.arg1);
+                            mTaskStackListeners.get(i).onActivityPinned(
+                                    info.mPackageName, info.mUserId, info.mTaskId, info.mStackId);
                         }
                         break;
                     }
diff --git a/com/android/systemui/recents/model/HighResThumbnailLoader.java b/com/android/systemui/recents/model/HighResThumbnailLoader.java
index 48fa6c3..6414ea1 100644
--- a/com/android/systemui/recents/model/HighResThumbnailLoader.java
+++ b/com/android/systemui/recents/model/HighResThumbnailLoader.java
@@ -187,7 +187,7 @@
     }
 
     @Override
-    public void onTaskStackIdChanged() {
+    public void onTaskWindowingModeChanged() {
     }
 
     private final Runnable mLoader = new Runnable() {
diff --git a/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
index 8d31730..d5e0313 100644
--- a/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
+++ b/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.recents.model;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+
 import android.app.ActivityManager;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
@@ -155,12 +157,13 @@
             ActivityManager.RecentTaskInfo t = mRawTasks.get(i);
 
             // Compose the task key
-            Task.TaskKey taskKey = new Task.TaskKey(t.persistentId, t.stackId, t.baseIntent,
+            final int windowingMode = t.configuration.windowConfiguration.getWindowingMode();
+            Task.TaskKey taskKey = new Task.TaskKey(t.persistentId, windowingMode, t.baseIntent,
                     t.userId, t.firstActiveTime, t.lastActiveTime);
 
             // This task is only shown in the stack if it satisfies the historical time or min
             // number of tasks constraints. Freeform tasks are also always shown.
-            boolean isFreeformTask = SystemServicesProxy.isFreeformStack(t.stackId);
+            boolean isFreeformTask = windowingMode == WINDOWING_MODE_FREEFORM;
             boolean isStackTask;
             if (Recents.getConfiguration().isGridEnabled) {
                 // When grid layout is enabled, we only show the first
diff --git a/com/android/systemui/recents/model/Task.java b/com/android/systemui/recents/model/Task.java
index 9e6bf85..abdb5cb 100644
--- a/com/android/systemui/recents/model/Task.java
+++ b/com/android/systemui/recents/model/Task.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.recents.model;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+
 import android.app.ActivityManager;
 import android.content.ComponentName;
 import android.content.Intent;
@@ -46,8 +48,8 @@
         public void onTaskDataLoaded(Task task, ThumbnailData thumbnailData);
         /* Notifies when a task has been unbound */
         public void onTaskDataUnloaded();
-        /* Notifies when a task's stack id has changed. */
-        public void onTaskStackIdChanged();
+        /* Notifies when a task's windowing mode has changed. */
+        public void onTaskWindowingModeChanged();
     }
 
     /* The Task Key represents the unique primary key for the task */
@@ -55,7 +57,7 @@
         @ViewDebug.ExportedProperty(category="recents")
         public final int id;
         @ViewDebug.ExportedProperty(category="recents")
-        public int stackId;
+        public int windowingMode;
         @ViewDebug.ExportedProperty(category="recents")
         public final Intent baseIntent;
         @ViewDebug.ExportedProperty(category="recents")
@@ -67,10 +69,10 @@
 
         private int mHashCode;
 
-        public TaskKey(int id, int stackId, Intent intent, int userId, long firstActiveTime,
+        public TaskKey(int id, int windowingMode, Intent intent, int userId, long firstActiveTime,
                 long lastActiveTime) {
             this.id = id;
-            this.stackId = stackId;
+            this.windowingMode = windowingMode;
             this.baseIntent = intent;
             this.userId = userId;
             this.firstActiveTime = firstActiveTime;
@@ -78,8 +80,8 @@
             updateHashCode();
         }
 
-        public void setStackId(int stackId) {
-            this.stackId = stackId;
+        public void setWindowingMode(int windowingMode) {
+            this.windowingMode = windowingMode;
             updateHashCode();
         }
 
@@ -93,7 +95,9 @@
                 return false;
             }
             TaskKey otherKey = (TaskKey) o;
-            return id == otherKey.id && stackId == otherKey.stackId && userId == otherKey.userId;
+            return id == otherKey.id
+                    && windowingMode == otherKey.windowingMode
+                    && userId == otherKey.userId;
         }
 
         @Override
@@ -103,12 +107,12 @@
 
         @Override
         public String toString() {
-            return "id=" + id + " stackId=" + stackId + " user=" + userId + " lastActiveTime=" +
-                    lastActiveTime;
+            return "id=" + id + " windowingMode=" + windowingMode + " user=" + userId
+                    + " lastActiveTime=" + lastActiveTime;
         }
 
         private void updateHashCode() {
-            mHashCode = Objects.hash(id, stackId, userId);
+            mHashCode = Objects.hash(id, windowingMode, userId);
         }
     }
 
@@ -277,14 +281,12 @@
         this.group = group;
     }
 
-    /**
-     * Updates the stack id of this task.
-     */
-    public void setStackId(int stackId) {
-        key.setStackId(stackId);
+    /** Updates the task's windowing mode. */
+    public void setWindowingMode(int windowingMode) {
+        key.setWindowingMode(windowingMode);
         int callbackCount = mCallbacks.size();
         for (int i = 0; i < callbackCount; i++) {
-            mCallbacks.get(i).onTaskStackIdChanged();
+            mCallbacks.get(i).onTaskWindowingModeChanged();
         }
     }
 
@@ -293,7 +295,7 @@
      */
     public boolean isFreeformTask() {
         SystemServicesProxy ssp = Recents.getSystemServices();
-        return ssp.hasFreeformWorkspaceSupport() && ssp.isFreeformStack(key.stackId);
+        return ssp.hasFreeformWorkspaceSupport() && key.windowingMode == WINDOWING_MODE_FREEFORM;
     }
 
     /** Notifies the callback listeners that this task has been loaded */
diff --git a/com/android/systemui/recents/model/TaskKeyCache.java b/com/android/systemui/recents/model/TaskKeyCache.java
index be99f93..247a654 100644
--- a/com/android/systemui/recents/model/TaskKeyCache.java
+++ b/com/android/systemui/recents/model/TaskKeyCache.java
@@ -45,7 +45,7 @@
     final V getAndInvalidateIfModified(Task.TaskKey key) {
         Task.TaskKey lastKey = mKeys.get(key.id);
         if (lastKey != null) {
-            if ((lastKey.stackId != key.stackId) ||
+            if ((lastKey.windowingMode != key.windowingMode) ||
                     (lastKey.lastActiveTime != key.lastActiveTime)) {
                 // The task has updated (been made active since the last time it was put into the
                 // LRU cache) or the stack id for the task has changed, invalidate that cache item
diff --git a/com/android/systemui/recents/model/TaskStack.java b/com/android/systemui/recents/model/TaskStack.java
index 6e3be09..fdae917 100644
--- a/com/android/systemui/recents/model/TaskStack.java
+++ b/com/android/systemui/recents/model/TaskStack.java
@@ -18,8 +18,8 @@
 
 import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_BOTTOM_OR_RIGHT;
 import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT;
-import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.view.WindowManager.DOCKED_BOTTOM;
 import static android.view.WindowManager.DOCKED_INVALID;
 import static android.view.WindowManager.DOCKED_LEFT;
@@ -115,7 +115,7 @@
     /**
      * Moves the given task.
      */
-    public void moveTaskToStack(Task task, int insertIndex, int newStackId) {
+    public void setTaskWindowingMode(Task task, int insertIndex, int windowingMode) {
         int taskIndex = indexOf(task);
         if (taskIndex != insertIndex) {
             mTasks.remove(taskIndex);
@@ -127,7 +127,7 @@
 
         // Update the stack id now, after we've moved the task, and before we update the
         // filtered tasks
-        task.setStackId(newStackId);
+        task.setWindowingMode(windowingMode);
         updateFilteredTasks();
     }
 
@@ -590,17 +590,15 @@
         mCb = cb;
     }
 
-    /**
-     * Moves the given task to either the front of the freeform workspace or the stack.
-     */
-    public void moveTaskToStack(Task task, int newStackId) {
+    /** Sets the windowing mode for a given task. */
+    public void setTaskWindowingMode(Task task, int windowingMode) {
         // Find the index to insert into
         ArrayList<Task> taskList = mStackTaskList.getTasks();
         int taskCount = taskList.size();
-        if (!task.isFreeformTask() && (newStackId == FREEFORM_WORKSPACE_STACK_ID)) {
+        if (!task.isFreeformTask() && (windowingMode == WINDOWING_MODE_FREEFORM)) {
             // Insert freeform tasks at the front
-            mStackTaskList.moveTaskToStack(task, taskCount, newStackId);
-        } else if (task.isFreeformTask() && (newStackId == FULLSCREEN_WORKSPACE_STACK_ID)) {
+            mStackTaskList.setTaskWindowingMode(task, taskCount, windowingMode);
+        } else if (task.isFreeformTask() && (windowingMode == WINDOWING_MODE_FULLSCREEN)) {
             // Insert after the first stacked task
             int insertIndex = 0;
             for (int i = taskCount - 1; i >= 0; i--) {
@@ -609,7 +607,7 @@
                     break;
                 }
             }
-            mStackTaskList.moveTaskToStack(task, insertIndex, newStackId);
+            mStackTaskList.setTaskWindowingMode(task, insertIndex, windowingMode);
         }
     }
 
diff --git a/com/android/systemui/recents/views/RecentsTransitionHelper.java b/com/android/systemui/recents/views/RecentsTransitionHelper.java
index b2675d7..ee05d81 100644
--- a/com/android/systemui/recents/views/RecentsTransitionHelper.java
+++ b/com/android/systemui/recents/views/RecentsTransitionHelper.java
@@ -16,14 +16,17 @@
 
 package com.android.systemui.recents.views;
 
-import static android.app.ActivityManager.StackId.ASSISTANT_STACK_ID;
-import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
-import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 
 import android.annotation.Nullable;
-import android.app.ActivityManager.StackId;
 import android.app.ActivityOptions;
 import android.app.ActivityOptions.OnAnimationStartedListener;
 import android.content.Context;
@@ -107,7 +110,7 @@
      */
     public void launchTaskFromRecents(final TaskStack stack, @Nullable final Task task,
             final TaskStackView stackView, final TaskView taskView,
-            final boolean screenPinningRequested, final int destinationStack) {
+            final boolean screenPinningRequested, final int windowingMode, final int activityType) {
 
         final ActivityOptions.OnAnimationStartedListener animStartedListener;
         final AppTransitionAnimationSpecsFuture transitionFuture;
@@ -116,8 +119,8 @@
             // Fetch window rect here already in order not to be blocked on lock contention in WM
             // when the future calls it.
             final Rect windowRect = Recents.getSystemServices().getWindowRect();
-            transitionFuture = getAppTransitionFuture(
-                    () -> composeAnimationSpecs(task, stackView, destinationStack, windowRect));
+            transitionFuture = getAppTransitionFuture(() -> composeAnimationSpecs(
+                    task, stackView, windowingMode, activityType, windowRect));
             animStartedListener = new OnAnimationStartedListener() {
                 private boolean mHandled;
 
@@ -180,7 +183,8 @@
         if (taskView == null) {
             // If there is no task view, then we do not need to worry about animating out occluding
             // task views, and we can launch immediately
-            startTaskActivity(stack, task, taskView, opts, transitionFuture, destinationStack);
+            startTaskActivity(stack, task, taskView, opts, transitionFuture,
+                    windowingMode, activityType);
         } else {
             LaunchTaskStartedEvent launchStartedEvent = new LaunchTaskStartedEvent(taskView,
                     screenPinningRequested);
@@ -189,13 +193,14 @@
                     @Override
                     public void run() {
                         startTaskActivity(stack, task, taskView, opts, transitionFuture,
-                                destinationStack);
+                                windowingMode, activityType);
                     }
                 });
                 EventBus.getDefault().send(launchStartedEvent);
             } else {
                 EventBus.getDefault().send(launchStartedEvent);
-                startTaskActivity(stack, task, taskView, opts, transitionFuture, destinationStack);
+                startTaskActivity(stack, task, taskView, opts, transitionFuture,
+                        windowingMode, activityType);
             }
         }
         Recents.getSystemServices().sendCloseSystemWindows(
@@ -224,13 +229,13 @@
      *
      * @param taskView this is the {@link TaskView} that we are launching from. This can be null if
      *                 we are toggling recents and the launch-to task is now offscreen.
-     * @param destinationStack id of the stack to put the task into.
      */
     private void startTaskActivity(TaskStack stack, Task task, @Nullable TaskView taskView,
             ActivityOptions opts, AppTransitionAnimationSpecsFuture transitionFuture,
-            int destinationStack) {
+            int windowingMode, int activityType) {
         SystemServicesProxy ssp = Recents.getSystemServices();
-        ssp.startActivityFromRecents(mContext, task.key, task.title, opts, destinationStack,
+        ssp.startActivityFromRecents(mContext, task.key, task.title, opts, windowingMode,
+                activityType,
                 succeeded -> {
             if (succeeded) {
                 // Keep track of the index of the task launch
@@ -310,11 +315,9 @@
      * Composes the animation specs for all the tasks in the target stack.
      */
     private List<AppTransitionAnimationSpec> composeAnimationSpecs(final Task task,
-            final TaskStackView stackView, final int destinationStack, Rect windowRect) {
-        // Ensure we have a valid target stack id
-        final int targetStackId = destinationStack != INVALID_STACK_ID ?
-                destinationStack : task.key.stackId;
-        if (!StackId.useAnimationSpecForAppTransition(targetStackId)) {
+            final TaskStackView stackView, int windowingMode, int activityType, Rect windowRect) {
+        if (activityType == ACTIVITY_TYPE_RECENTS || activityType == ACTIVITY_TYPE_HOME
+                || windowingMode == WINDOWING_MODE_PINNED) {
             return null;
         }
 
@@ -329,9 +332,12 @@
         List<AppTransitionAnimationSpec> specs = new ArrayList<>();
 
         // TODO: Sometimes targetStackId is not initialized after reboot, so we also have to
-        // check for INVALID_STACK_ID
-        if (targetStackId == FULLSCREEN_WORKSPACE_STACK_ID || targetStackId == DOCKED_STACK_ID
-                || targetStackId == ASSISTANT_STACK_ID || targetStackId == INVALID_STACK_ID) {
+        // check for INVALID_STACK_ID (now WINDOWING_MODE_UNDEFINED)
+        if (windowingMode == WINDOWING_MODE_FULLSCREEN
+                || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
+                || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY
+                || activityType == ACTIVITY_TYPE_ASSISTANT
+                || windowingMode == WINDOWING_MODE_UNDEFINED) {
             if (taskView == null) {
                 specs.add(composeOffscreenAnimationSpec(task, offscreenTaskRect));
             } else {
@@ -353,7 +359,7 @@
         int taskCount = tasks.size();
         for (int i = taskCount - 1; i >= 0; i--) {
             Task t = tasks.get(i);
-            if (t.isFreeformTask() || targetStackId == FREEFORM_WORKSPACE_STACK_ID) {
+            if (t.isFreeformTask() || windowingMode == WINDOWING_MODE_FREEFORM) {
                 TaskView tv = stackView.getChildViewForTask(t);
                 if (tv == null) {
                     // TODO: Create a different animation task rect for this case (though it should
diff --git a/com/android/systemui/recents/views/RecentsView.java b/com/android/systemui/recents/views/RecentsView.java
index c44cd72..c7edb9a 100644
--- a/com/android/systemui/recents/views/RecentsView.java
+++ b/com/android/systemui/recents/views/RecentsView.java
@@ -174,8 +174,6 @@
                         ? R.layout.recents_low_ram_stack_action_button
                         : R.layout.recents_stack_action_button,
                     this, false);
-            mStackActionButton.setOnClickListener(
-                    v -> EventBus.getDefault().send(new DismissAllTaskViewsEvent()));
 
             mStackButtonShadowRadius = mStackActionButton.getShadowRadius();
             mStackButtonShadowDistance = new PointF(mStackActionButton.getShadowDx(),
@@ -205,10 +203,6 @@
                         mStackButtonShadowDistance.x, mStackButtonShadowDistance.y,
                         mStackButtonShadowColor);
             }
-            if (Recents.getConfiguration().isLowRamDevice) {
-                int bgColor = Utils.getColorAttr(mContext, R.attr.clearAllBackgroundColor);
-                mStackActionButton.setBackgroundColor(bgColor);
-            }
         }
 
         // Let's also require dark status and nav bars if the text is dark
@@ -338,8 +332,7 @@
             Task task = mTaskStackView.getFocusedTask();
             if (task != null) {
                 TaskView taskView = mTaskStackView.getChildViewForTask(task);
-                EventBus.getDefault().send(new LaunchTaskEvent(taskView, task, null,
-                        INVALID_STACK_ID, false));
+                EventBus.getDefault().send(new LaunchTaskEvent(taskView, task, null, false));
 
                 if (logEvent != 0) {
                     MetricsLogger.action(getContext(), logEvent,
@@ -363,32 +356,13 @@
             Task task = getStack().getLaunchTarget();
             if (task != null) {
                 TaskView taskView = mTaskStackView.getChildViewForTask(task);
-                EventBus.getDefault().send(new LaunchTaskEvent(taskView, task, null,
-                        INVALID_STACK_ID, false));
+                EventBus.getDefault().send(new LaunchTaskEvent(taskView, task, null, false));
                 return true;
             }
         }
         return false;
     }
 
-    /** Launches a given task. */
-    public boolean launchTask(Task task, Rect taskBounds, int destinationStack) {
-        if (mTaskStackView != null) {
-            // Iterate the stack views and try and find the given task.
-            List<TaskView> taskViews = mTaskStackView.getTaskViews();
-            int taskViewCount = taskViews.size();
-            for (int j = 0; j < taskViewCount; j++) {
-                TaskView tv = taskViews.get(j);
-                if (tv.getTask() == task) {
-                    EventBus.getDefault().send(new LaunchTaskEvent(tv, task, taskBounds,
-                            destinationStack, false));
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
     /**
      * Hides the task stack and shows the empty view.
      */
@@ -570,9 +544,10 @@
     public final void onBusEvent(LaunchTaskEvent event) {
         mLastTaskLaunchedWasFreeform = event.task.isFreeformTask();
         mTransitionHelper.launchTaskFromRecents(getStack(), event.task, mTaskStackView,
-                event.taskView, event.screenPinningRequested, event.targetTaskStack);
+                event.taskView, event.screenPinningRequested, event.targetWindowingMode,
+                event.targetActivityType);
         if (Recents.getConfiguration().isLowRamDevice) {
-            hideStackActionButton(HIDE_STACK_ACTION_BUTTON_DURATION, false /* translate */);
+            EventBus.getDefault().send(new HideStackActionButtonEvent(false /* translate */));
         }
     }
 
@@ -580,7 +555,7 @@
         int taskViewExitToHomeDuration = TaskStackAnimationHelper.EXIT_TO_HOME_TRANSLATION_DURATION;
         if (RecentsDebugFlags.Static.EnableStackActionButton) {
             // Hide the stack action button
-            hideStackActionButton(taskViewExitToHomeDuration, false /* translate */);
+            EventBus.getDefault().send(new HideStackActionButtonEvent());
         }
         animateBackgroundScrim(0f, taskViewExitToHomeDuration);
 
@@ -741,13 +716,10 @@
             animateBackgroundScrim(getOpaqueScrimAlpha(),
                     TaskStackAnimationHelper.ENTER_FROM_HOME_TRANSLATION_DURATION);
         }
-        if (Recents.getConfiguration().isLowRamDevice && mEmptyView.getVisibility() != View.VISIBLE) {
-            showStackActionButton(SHOW_STACK_ACTION_BUTTON_DURATION, false /* translate */);
-        }
     }
 
     public final void onBusEvent(AllTaskViewsDismissedEvent event) {
-        hideStackActionButton(HIDE_STACK_ACTION_BUTTON_DURATION, true /* translate */);
+        EventBus.getDefault().send(new HideStackActionButtonEvent());
     }
 
     public final void onBusEvent(DismissAllTaskViewsEvent event) {
@@ -795,7 +767,8 @@
             mStackActionButton.setVisibility(View.VISIBLE);
             mStackActionButton.setAlpha(0f);
             if (translate) {
-                mStackActionButton.setTranslationY(-mStackActionButton.getMeasuredHeight() * 0.25f);
+                mStackActionButton.setTranslationY(mStackActionButton.getMeasuredHeight() *
+                        (Recents.getConfiguration().isLowRamDevice ? 1 : -0.25f));
             } else {
                 mStackActionButton.setTranslationY(0f);
             }
@@ -841,8 +814,8 @@
 
         if (mStackActionButton.getVisibility() == View.VISIBLE) {
             if (translate) {
-                mStackActionButton.animate()
-                    .translationY(-mStackActionButton.getMeasuredHeight() * 0.25f);
+                mStackActionButton.animate().translationY(mStackActionButton.getMeasuredHeight()
+                        * (Recents.getConfiguration().isLowRamDevice ? 1 : -0.25f));
             }
             mStackActionButton.animate()
                     .alpha(0f)
@@ -954,7 +927,7 @@
     /**
      * @return the bounds of the stack action button.
      */
-    private Rect getStackActionButtonBoundsFromStackLayout() {
+    Rect getStackActionButtonBoundsFromStackLayout() {
         Rect actionButtonRect = new Rect(mTaskStackView.mLayoutAlgorithm.getStackActionButtonRect());
         int left, top;
         if (Recents.getConfiguration().isLowRamDevice) {
@@ -976,6 +949,16 @@
         return actionButtonRect;
     }
 
+    View getStackActionButton() {
+        return mStackActionButton;
+    }
+
+    @Override
+    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
+        super.requestDisallowInterceptTouchEvent(disallowIntercept);
+        mTouchHandler.cancelStackActionButtonClick();
+    }
+
     public void dump(String prefix, PrintWriter writer) {
         String innerPrefix = prefix + "  ";
         String id = Integer.toHexString(System.identityHashCode(this));
diff --git a/com/android/systemui/recents/views/RecentsViewTouchHandler.java b/com/android/systemui/recents/views/RecentsViewTouchHandler.java
index 46619c2..b6b24bc 100644
--- a/com/android/systemui/recents/views/RecentsViewTouchHandler.java
+++ b/com/android/systemui/recents/views/RecentsViewTouchHandler.java
@@ -23,6 +23,7 @@
 import android.view.InputDevice;
 import android.view.MotionEvent;
 import android.view.PointerIcon;
+import android.view.View;
 import android.view.ViewConfiguration;
 import android.view.ViewDebug;
 
@@ -31,6 +32,8 @@
 import com.android.systemui.recents.events.EventBus;
 import com.android.systemui.recents.events.activity.ConfigurationChangedEvent;
 import com.android.systemui.recents.events.activity.HideRecentsEvent;
+import com.android.systemui.recents.events.activity.HideStackActionButtonEvent;
+import com.android.systemui.recents.events.ui.DismissAllTaskViewsEvent;
 import com.android.systemui.recents.events.ui.HideIncompatibleAppOverlayEvent;
 import com.android.systemui.recents.events.ui.ShowIncompatibleAppOverlayEvent;
 import com.android.systemui.recents.events.ui.dragndrop.DragDropTargetChangedEvent;
@@ -99,8 +102,7 @@
 
     /** Touch preprocessing for handling below */
     public boolean onInterceptTouchEvent(MotionEvent ev) {
-        handleTouchEvent(ev);
-        return mDragRequested;
+        return handleTouchEvent(ev) || mDragRequested;
     }
 
     /** Handles touch events once we have intercepted them */
@@ -183,22 +185,47 @@
         }
     }
 
+    void cancelStackActionButtonClick() {
+        mRv.getStackActionButton().setPressed(false);
+    }
+
+    private boolean isWithinStackActionButton(float x, float y) {
+        Rect rect = mRv.getStackActionButtonBoundsFromStackLayout();
+        return mRv.getStackActionButton().getVisibility() == View.VISIBLE &&
+                mRv.getStackActionButton().pointInView(x - rect.left, y - rect.top, 0 /* slop */);
+    }
+
+    private void changeStackActionButtonDrawableHotspot(float x, float y) {
+        Rect rect = mRv.getStackActionButtonBoundsFromStackLayout();
+        mRv.getStackActionButton().drawableHotspotChanged(x - rect.left, y - rect.top);
+    }
+
     /**
      * Handles dragging touch events
      */
-    private void handleTouchEvent(MotionEvent ev) {
+    private boolean handleTouchEvent(MotionEvent ev) {
         int action = ev.getActionMasked();
+        boolean consumed = false;
+        float evX = ev.getX();
+        float evY = ev.getY();
         switch (action) {
             case MotionEvent.ACTION_DOWN:
-                mDownPos.set((int) ev.getX(), (int) ev.getY());
+                mDownPos.set((int) evX, (int) evY);
                 mDeviceId = ev.getDeviceId();
+
+                if (isWithinStackActionButton(evX, evY)) {
+                    changeStackActionButtonDrawableHotspot(evX, evY);
+                    mRv.getStackActionButton().setPressed(true);
+                }
                 break;
             case MotionEvent.ACTION_MOVE: {
-                float evX = ev.getX();
-                float evY = ev.getY();
                 float x = evX - mTaskViewOffset.x;
                 float y = evY - mTaskViewOffset.y;
 
+                if (mRv.getStackActionButton().isPressed() && isWithinStackActionButton(evX, evY)) {
+                    changeStackActionButtonDrawableHotspot(evX, evY);
+                }
+
                 if (mDragRequested) {
                     if (!mIsDragging) {
                         mIsDragging = Math.hypot(evX - mDownPos.x, evY - mDownPos.y) > mDragSlop;
@@ -232,9 +259,7 @@
                             EventBus.getDefault().send(new DragDropTargetChangedEvent(mDragTask,
                                     currentDropTarget));
                         }
-
                     }
-
                     mTaskView.setTranslationX(x);
                     mTaskView.setTranslationY(y);
                 }
@@ -242,6 +267,11 @@
             }
             case MotionEvent.ACTION_UP:
             case MotionEvent.ACTION_CANCEL: {
+                if (mRv.getStackActionButton().isPressed() && isWithinStackActionButton(evX, evY)) {
+                    EventBus.getDefault().send(new DismissAllTaskViewsEvent());
+                    consumed = true;
+                }
+                cancelStackActionButtonClick();
                 if (mDragRequested) {
                     boolean cancelled = action == MotionEvent.ACTION_CANCEL;
                     if (cancelled) {
@@ -254,5 +284,6 @@
                 mDeviceId = -1;
             }
         }
+        return consumed;
     }
 }
diff --git a/com/android/systemui/recents/views/TaskStackView.java b/com/android/systemui/recents/views/TaskStackView.java
index 8899e30..3160ee0 100644
--- a/com/android/systemui/recents/views/TaskStackView.java
+++ b/com/android/systemui/recents/views/TaskStackView.java
@@ -16,9 +16,8 @@
 
 package com.android.systemui.recents.views;
 
-import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -1487,7 +1486,7 @@
             Task frontTask = tasks.get(tasks.size() - 1);
             if (frontTask != null && frontTask.isFreeformTask()) {
                 EventBus.getDefault().send(new LaunchTaskEvent(getChildViewForTask(frontTask),
-                        frontTask, null, INVALID_STACK_ID, false));
+                        frontTask, null, false));
                 return true;
             }
         }
@@ -1768,8 +1767,17 @@
         }
 
         // In grid layout, the stack action button always remains visible.
-        if (mEnterAnimationComplete && !useGridLayout() &&
-                !Recents.getConfiguration().isLowRamDevice) {
+        if (mEnterAnimationComplete && !useGridLayout()) {
+            if (Recents.getConfiguration().isLowRamDevice) {
+                // Show stack button when user drags down to show older tasks on low ram devices
+                if (mStack.getTaskCount() > 0 && !mStackActionButtonVisible
+                        && mTouchHandler.mIsScrolling && curScroll - prevScroll < 0) {
+                    // Going up
+                    EventBus.getDefault().send(
+                            new ShowStackActionButtonEvent(true /* translate */));
+                }
+                return;
+            }
             if (prevScroll > SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD &&
                     curScroll <= SHOW_STACK_ACTION_BUTTON_SCROLL_THRESHOLD &&
                     mStack.getTaskCount() > 0) {
@@ -1956,6 +1964,9 @@
         // Remove the task from the stack
         mStack.removeTask(event.task, event.animation, false /* fromDockGesture */);
         EventBus.getDefault().send(new DeleteTaskDataEvent(event.task));
+        if (mStack.getTaskCount() > 0 && Recents.getConfiguration().isLowRamDevice) {
+            EventBus.getDefault().send(new ShowStackActionButtonEvent(false /* translate */));
+        }
 
         MetricsLogger.action(getContext(), MetricsEvent.OVERVIEW_DISMISS,
                 event.task.key.getComponent().toString());
@@ -2082,18 +2093,18 @@
         }
 
         boolean isFreeformTask = event.task.isFreeformTask();
-        boolean hasChangedStacks =
+        boolean hasChangedWindowingMode =
                 (!isFreeformTask && event.dropTarget == mFreeformWorkspaceDropTarget) ||
                         (isFreeformTask && event.dropTarget == mStackDropTarget);
 
-        if (hasChangedStacks) {
+        if (hasChangedWindowingMode) {
             // Move the task to the right position in the stack (ie. the front of the stack if
             // freeform or the front of the stack if fullscreen). Note, we MUST move the tasks
             // before we update their stack ids, otherwise, the keys will have changed.
             if (event.dropTarget == mFreeformWorkspaceDropTarget) {
-                mStack.moveTaskToStack(event.task, FREEFORM_WORKSPACE_STACK_ID);
+                mStack.setTaskWindowingMode(event.task, WINDOWING_MODE_FREEFORM);
             } else if (event.dropTarget == mStackDropTarget) {
-                mStack.moveTaskToStack(event.task, FULLSCREEN_WORKSPACE_STACK_ID);
+                mStack.setTaskWindowingMode(event.task, WINDOWING_MODE_FULLSCREEN);
             }
             updateLayoutAlgorithm(true /* boundScroll */);
 
@@ -2102,7 +2113,7 @@
                 @Override
                 public void run() {
                     SystemServicesProxy ssp = Recents.getSystemServices();
-                    ssp.moveTaskToStack(event.task.key.id, event.task.key.stackId);
+                    ssp.setTaskWindowingMode(event.task.key.id, event.task.key.windowingMode);
                 }
             });
         }
@@ -2369,12 +2380,12 @@
                         public void run() {
                             EventBus.getDefault().send(new LaunchTaskEvent(
                                     getChildViewForTask(task), task, null,
-                                    INVALID_STACK_ID, false /* screenPinningRequested */));
+                                    false /* screenPinningRequested */));
                         }
                     });
         } else {
-            EventBus.getDefault().send(new LaunchTaskEvent(getChildViewForTask(task),
-                    task, null, INVALID_STACK_ID, false /* screenPinningRequested */));
+            EventBus.getDefault().send(new LaunchTaskEvent(getChildViewForTask(task), task, null,
+                    false /* screenPinningRequested */));
         }
     }
 
diff --git a/com/android/systemui/recents/views/TaskView.java b/com/android/systemui/recents/views/TaskView.java
index ceeebd9..9d63964 100644
--- a/com/android/systemui/recents/views/TaskView.java
+++ b/com/android/systemui/recents/views/TaskView.java
@@ -647,7 +647,7 @@
     }
 
     @Override
-    public void onTaskStackIdChanged() {
+    public void onTaskWindowingModeChanged() {
         // Force rebind the header, the thumbnail does not change due to stack changes
         mHeaderView.bindToTask(mTask, mTouchExplorationEnabled, mIsDisabledInSafeMode);
         mHeaderView.onTaskDataLoaded();
@@ -674,8 +674,7 @@
             mActionButtonView.setTranslationZ(0f);
             screenPinningRequested = true;
         }
-        EventBus.getDefault().send(new LaunchTaskEvent(this, mTask, null, INVALID_STACK_ID,
-                screenPinningRequested));
+        EventBus.getDefault().send(new LaunchTaskEvent(this, mTask, null, screenPinningRequested));
 
         MetricsLogger.action(v.getContext(), MetricsEvent.ACTION_OVERVIEW_SELECT,
                 mTask.key.getComponent().toString());
diff --git a/com/android/systemui/recents/views/TaskViewHeader.java b/com/android/systemui/recents/views/TaskViewHeader.java
index ae922fc..198ecae 100644
--- a/com/android/systemui/recents/views/TaskViewHeader.java
+++ b/com/android/systemui/recents/views/TaskViewHeader.java
@@ -16,6 +16,11 @@
 
 package com.android.systemui.recents.views;
 
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.annotation.Nullable;
@@ -57,10 +62,6 @@
 import com.android.systemui.recents.misc.Utilities;
 import com.android.systemui.recents.model.Task;
 
-import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
-
 /* The task bar view */
 public class TaskViewHeader extends FrameLayout
         implements View.OnClickListener, View.OnLongClickListener {
@@ -172,7 +173,7 @@
     int mTaskBarViewLightTextColor;
     int mTaskBarViewDarkTextColor;
     int mDisabledTaskBarBackgroundColor;
-    int mMoveTaskTargetStackId = INVALID_STACK_ID;
+    int mTaskWindowingMode = WINDOWING_MODE_UNDEFINED;
 
     // Header background
     private HighlightColorDrawable mBackground;
@@ -485,12 +486,12 @@
         // current task
         if (mMoveTaskButton != null) {
             if (t.isFreeformTask()) {
-                mMoveTaskTargetStackId = FULLSCREEN_WORKSPACE_STACK_ID;
+                mTaskWindowingMode = WINDOWING_MODE_FULLSCREEN;
                 mMoveTaskButton.setImageDrawable(t.useLightOnPrimaryColor
                         ? mLightFullscreenIcon
                         : mDarkFullscreenIcon);
             } else {
-                mMoveTaskTargetStackId = FREEFORM_WORKSPACE_STACK_ID;
+                mTaskWindowingMode = WINDOWING_MODE_FREEFORM;
                 mMoveTaskButton.setImageDrawable(t.useLightOnPrimaryColor
                         ? mLightFreeformIcon
                         : mDarkFreeformIcon);
@@ -621,8 +622,8 @@
                     Constants.Metrics.DismissSourceHeaderButton);
         } else if (v == mMoveTaskButton) {
             TaskView tv = Utilities.findParent(this, TaskView.class);
-            EventBus.getDefault().send(new LaunchTaskEvent(tv, mTask, null,
-                    mMoveTaskTargetStackId, false));
+            EventBus.getDefault().send(new LaunchTaskEvent(tv, mTask, null, false,
+                    mTaskWindowingMode, ACTIVITY_TYPE_UNDEFINED));
         } else if (v == mAppInfoView) {
             EventBus.getDefault().send(new ShowApplicationInfoEvent(mTask));
         } else if (v == mAppIconView) {
diff --git a/com/android/systemui/recents/views/grid/GridTaskViewThumbnail.java b/com/android/systemui/recents/views/grid/GridTaskViewThumbnail.java
index bcf4f17..2d7cfb1 100644
--- a/com/android/systemui/recents/views/grid/GridTaskViewThumbnail.java
+++ b/com/android/systemui/recents/views/grid/GridTaskViewThumbnail.java
@@ -26,8 +26,8 @@
 
 public class GridTaskViewThumbnail extends TaskViewThumbnail {
 
-    private Path mThumbnailOutline;
-    private Path mRestBackgroundOutline;
+    private final Path mThumbnailOutline = new Path();
+    private final Path mRestBackgroundOutline = new Path();
     // True if either this view's size or thumbnail scale has changed and mThumbnailOutline should
     // be updated.
     private boolean mUpdateThumbnailOutline = true;
@@ -77,47 +77,38 @@
             (int) (mThumbnailRect.width() * mThumbnailScale));
         final int thumbnailHeight = Math.min(viewHeight,
             (int) (mThumbnailRect.height() * mThumbnailScale));
-        // Draw the thumbnail, we only round the bottom corners:
-        //
-        // outerLeft                outerRight
-        //    <----------------------->            mRestBackgroundOutline
-        //    _________________________            (thumbnailWidth < viewWidth)
-        //    |_______________________| outerTop     A ____ B
-        //    |                       |    ↑           |  |
-        //    |                       |    |           |  |
-        //    |                       |    |           |  |
-        //    |                       |    |           |  | C
-        //    \_______________________/    ↓           |__/
-        //  mCornerRadius             outerBottom    E    D
-        //
-        //  mRestBackgroundOutline (thumbnailHeight < viewHeight)
-        //  A _________________________ B
-        //    |                       | C
-        //  F \_______________________/
-        //    E                       D
-        final int outerLeft = 0;
-        final int outerTop = 0;
-        final int outerRight = outerLeft + thumbnailWidth;
-        final int outerBottom = outerTop + thumbnailHeight;
-        mThumbnailOutline = new Path();
-        mThumbnailOutline.moveTo(outerLeft, outerTop);
-        mThumbnailOutline.lineTo(outerRight, outerTop);
-        mThumbnailOutline.lineTo(outerRight, outerBottom - mCornerRadius);
-        mThumbnailOutline.arcTo(outerRight -  2 * mCornerRadius, outerBottom - 2 * mCornerRadius,
-                outerRight, outerBottom, 0, 90, false);
-        mThumbnailOutline.lineTo(outerLeft + mCornerRadius, outerBottom);
-        mThumbnailOutline.arcTo(outerLeft, outerBottom - 2 * mCornerRadius,
-                outerLeft + 2 * mCornerRadius, outerBottom, 90, 90, false);
-        mThumbnailOutline.lineTo(outerLeft, outerTop);
-        mThumbnailOutline.close();
 
         if (mBitmapShader != null && thumbnailWidth > 0 && thumbnailHeight > 0) {
+            // Draw the thumbnail, we only round the bottom corners:
+            //
+            // outerLeft                outerRight
+            //    <----------------------->            mRestBackgroundOutline
+            //    _________________________            (thumbnailWidth < viewWidth)
+            //    |_______________________| outerTop     A ____ B
+            //    |                       |    ↑           |  |
+            //    |                       |    |           |  |
+            //    |                       |    |           |  |
+            //    |                       |    |           |  | C
+            //    \_______________________/    ↓           |__/
+            //  mCornerRadius             outerBottom    E    D
+            //
+            //  mRestBackgroundOutline (thumbnailHeight < viewHeight)
+            //  A _________________________ B
+            //    |                       | C
+            //  F \_______________________/
+            //    E                       D
+            final int outerLeft = 0;
+            final int outerTop = 0;
+            final int outerRight = outerLeft + thumbnailWidth;
+            final int outerBottom = outerTop + thumbnailHeight;
+            createThumbnailPath(outerLeft, outerTop, outerRight, outerBottom, mThumbnailOutline);
+
             if (thumbnailWidth < viewWidth) {
                 final int l = Math.max(0, outerRight - mCornerRadius);
                 final int r = outerRight;
                 final int t = outerTop;
                 final int b = outerBottom;
-                mRestBackgroundOutline = new Path();
+                mRestBackgroundOutline.reset();
                 mRestBackgroundOutline.moveTo(l, t); // A
                 mRestBackgroundOutline.lineTo(r, t); // B
                 mRestBackgroundOutline.lineTo(r, b - mCornerRadius); // C
@@ -133,7 +124,7 @@
                 final int r = outerRight;
                 final int t = Math.max(0, thumbnailHeight - mCornerRadius);
                 final int b = outerBottom;
-                mRestBackgroundOutline = new Path();
+                mRestBackgroundOutline.reset();
                 mRestBackgroundOutline.moveTo(l, t); // A
                 mRestBackgroundOutline.lineTo(r, t); // B
                 mRestBackgroundOutline.lineTo(r, b - mCornerRadius); // C
@@ -145,9 +136,26 @@
                 mRestBackgroundOutline.lineTo(l, t); // A
                 mRestBackgroundOutline.close();
             }
+        } else {
+            createThumbnailPath(0, 0, viewWidth, viewHeight, mThumbnailOutline);
         }
     }
 
+    private void createThumbnailPath(int outerLeft, int outerTop, int outerRight, int outerBottom,
+            Path outPath) {
+        outPath.reset();
+        outPath.moveTo(outerLeft, outerTop);
+        outPath.lineTo(outerRight, outerTop);
+        outPath.lineTo(outerRight, outerBottom - mCornerRadius);
+        outPath.arcTo(outerRight -  2 * mCornerRadius, outerBottom - 2 * mCornerRadius, outerRight,
+                outerBottom, 0, 90, false);
+        outPath.lineTo(outerLeft + mCornerRadius, outerBottom);
+        outPath.arcTo(outerLeft, outerBottom - 2 * mCornerRadius, outerLeft + 2 * mCornerRadius,
+                outerBottom, 90, 90, false);
+        outPath.lineTo(outerLeft, outerTop);
+        outPath.close();
+    }
+
     @Override
     protected void onDraw(Canvas canvas) {
         final int titleHeight = getResources().getDimensionPixelSize(
diff --git a/com/android/systemui/stackdivider/DividerView.java b/com/android/systemui/stackdivider/DividerView.java
index 6bfef20..7bcef57 100644
--- a/com/android/systemui/stackdivider/DividerView.java
+++ b/com/android/systemui/stackdivider/DividerView.java
@@ -16,6 +16,9 @@
 
 package com.android.systemui.stackdivider;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.view.PointerIcon.TYPE_HORIZONTAL_DOUBLE_ARROW;
 import static android.view.PointerIcon.TYPE_VERTICAL_DOUBLE_ARROW;
 
@@ -23,7 +26,6 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
 import android.annotation.Nullable;
-import android.app.ActivityManager.StackId;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.graphics.Rect;
@@ -36,8 +38,6 @@
 import android.view.Choreographer;
 import android.view.Display;
 import android.view.DisplayInfo;
-import android.view.GestureDetector;
-import android.view.GestureDetector.SimpleOnGestureListener;
 import android.view.MotionEvent;
 import android.view.PointerIcon;
 import android.view.VelocityTracker;
@@ -62,8 +62,8 @@
 import com.android.internal.view.SurfaceFlingerVsyncChoreographer;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
-import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.events.activity.DockedFirstAnimationFrameEvent;
 import com.android.systemui.recents.events.activity.DockedTopTaskEvent;
 import com.android.systemui.recents.events.activity.RecentsActivityStartingEvent;
 import com.android.systemui.recents.events.activity.UndockingTaskEvent;
@@ -93,7 +93,6 @@
     private static final int LOG_VALUE_UNDOCK_MAX_OTHER = 1;
 
     private static final int TASK_POSITION_SAME = Integer.MAX_VALUE;
-    private static final boolean SWAPPING_ENABLED = false;
 
     /**
      * How much the background gets scaled when we are in the minimized dock state.
@@ -153,7 +152,6 @@
     private boolean mEntranceAnimationRunning;
     private boolean mExitAnimationRunning;
     private int mExitStartPosition;
-    private GestureDetector mGestureDetector;
     private boolean mDockedStackMinimized;
     private boolean mHomeStackResizable;
     private boolean mAdjustedForIme;
@@ -295,21 +293,6 @@
                 landscape ? TYPE_HORIZONTAL_DOUBLE_ARROW : TYPE_VERTICAL_DOUBLE_ARROW));
         getViewTreeObserver().addOnComputeInternalInsetsListener(this);
         mHandle.setAccessibilityDelegate(mHandleDelegate);
-        mGestureDetector = new GestureDetector(mContext, new SimpleOnGestureListener() {
-            @Override
-            public boolean onSingleTapUp(MotionEvent e) {
-                if (SWAPPING_ENABLED) {
-                    updateDockSide();
-                    SystemServicesProxy ssp = Recents.getSystemServices();
-                    if (mDockSide != WindowManager.DOCKED_INVALID
-                            && !ssp.isRecentsActivityVisible()) {
-                        mWindowManagerProxy.swapTasks();
-                        return true;
-                    }
-                }
-                return false;
-            }
-        });
     }
 
     @Override
@@ -478,7 +461,6 @@
     @Override
     public boolean onTouch(View v, MotionEvent event) {
         convertToScreenCoordinates(event);
-        mGestureDetector.onTouchEvent(event);
         final int action = event.getAction() & MotionEvent.ACTION_MASK;
         switch (action) {
             case MotionEvent.ACTION_DOWN:
@@ -679,7 +661,7 @@
         } else {
             mWindowManagerProxy.maximizeDockedStack();
         }
-        mWindowManagerProxy.setResizeDimLayer(false, -1, 0f);
+        mWindowManagerProxy.setResizeDimLayer(false, WINDOWING_MODE_UNDEFINED, 0f);
     }
 
     private void liftBackground() {
@@ -1015,8 +997,7 @@
         SnapTarget closestDismissTarget = getSnapAlgorithm().getClosestDismissTarget(position);
         float dimFraction = getDimFraction(position, closestDismissTarget);
         mWindowManagerProxy.setResizeDimLayer(dimFraction != 0f,
-                getStackIdForDismissTarget(closestDismissTarget),
-                dimFraction);
+                getWindowingModeForDismissTarget(closestDismissTarget), dimFraction);
     }
 
     private void applyExitAnimationParallax(Rect taskRect, int position) {
@@ -1150,13 +1131,13 @@
         }
     }
 
-    private int getStackIdForDismissTarget(SnapTarget dismissTarget) {
+    private int getWindowingModeForDismissTarget(SnapTarget dismissTarget) {
         if ((dismissTarget.flag == SnapTarget.FLAG_DISMISS_START && dockSideTopLeft(mDockSide))
                 || (dismissTarget.flag == SnapTarget.FLAG_DISMISS_END
                         && dockSideBottomRight(mDockSide))) {
-            return StackId.DOCKED_STACK_ID;
+            return WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
         } else {
-            return StackId.RECENTS_STACK_ID;
+            return WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
         }
     }
 
@@ -1210,6 +1191,10 @@
         }
     }
 
+    public final void onBusEvent(DockedFirstAnimationFrameEvent event) {
+        saveSnapTargetBeforeMinimized(mSnapAlgorithm.getMiddleTarget());
+    }
+
     public final void onBusEvent(DockedTopTaskEvent event) {
         if (event.dragMode == NavigationBarGestureHelper.DRAG_MODE_NONE) {
             mState.growAfterRecentsDrawn = false;
diff --git a/com/android/systemui/stackdivider/WindowManagerProxy.java b/com/android/systemui/stackdivider/WindowManagerProxy.java
index c245126..85a6062 100644
--- a/com/android/systemui/stackdivider/WindowManagerProxy.java
+++ b/com/android/systemui/stackdivider/WindowManagerProxy.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.stackdivider;
 
-import static android.app.ActivityManager.StackId.DOCKED_STACK_ID;
 import static android.view.WindowManager.DOCKED_INVALID;
 
 import android.app.ActivityManager;
@@ -56,7 +55,7 @@
     private final Rect mTouchableRegion = new Rect();
 
     private boolean mDimLayerVisible;
-    private int mDimLayerTargetStack;
+    private int mDimLayerTargetWindowingMode;
     private float mDimLayerAlpha;
 
     private final ExecutorService mExecutor = Executors.newSingleThreadExecutor();
@@ -88,8 +87,7 @@
         @Override
         public void run() {
             try {
-                ActivityManager.getService().moveTasksToFullscreenStack(
-                        DOCKED_STACK_ID, false /* onTop */);
+                ActivityManager.getService().dismissSplitScreenMode(false /* onTop */);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed to remove stack: " + e);
             }
@@ -100,8 +98,7 @@
         @Override
         public void run() {
             try {
-                ActivityManager.getService().resizeStack(
-                        DOCKED_STACK_ID, null, true, true, false, -1);
+                ActivityManager.getService().dismissSplitScreenMode(true /* onTop */);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed to resize stack: " + e);
             }
@@ -113,18 +110,7 @@
         public void run() {
             try {
                 WindowManagerGlobal.getWindowManagerService().setResizeDimLayer(mDimLayerVisible,
-                        mDimLayerTargetStack, mDimLayerAlpha);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Failed to resize stack: " + e);
-            }
-        }
-    };
-
-    private final Runnable mSwapRunnable = new Runnable() {
-        @Override
-        public void run() {
-            try {
-                ActivityManager.getService().swapDockedAndFullscreenStack();
+                        mDimLayerTargetWindowingMode, mDimLayerAlpha);
             } catch (RemoteException e) {
                 Log.w(TAG, "Failed to resize stack: " + e);
             }
@@ -211,17 +197,13 @@
         return DOCKED_INVALID;
     }
 
-    public void setResizeDimLayer(boolean visible, int targetStackId, float alpha) {
+    public void setResizeDimLayer(boolean visible, int targetWindowingMode, float alpha) {
         mDimLayerVisible = visible;
-        mDimLayerTargetStack = targetStackId;
+        mDimLayerTargetWindowingMode = targetWindowingMode;
         mDimLayerAlpha = alpha;
         mExecutor.execute(mDimLayerRunnable);
     }
 
-    public void swapTasks() {
-        mExecutor.execute(mSwapRunnable);
-    }
-
     public void setTouchRegion(Rect region) {
         synchronized (mDockedRect) {
             mTouchableRegion.set(region);
diff --git a/com/android/systemui/statusbar/ExpandableNotificationRow.java b/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 7fe7f39..966e789 100644
--- a/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -64,10 +64,10 @@
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin.MenuItem;
 import com.android.systemui.statusbar.NotificationGuts.GutsContent;
 import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
+import com.android.systemui.statusbar.notification.AboveShelfObserver;
 import com.android.systemui.statusbar.notification.HybridNotificationView;
 import com.android.systemui.statusbar.notification.NotificationInflater;
 import com.android.systemui.statusbar.notification.NotificationUtils;
-import com.android.systemui.statusbar.notification.NotificationViewWrapper;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.StatusBar;
@@ -436,9 +436,6 @@
         } else {
             minHeight = mNotificationMinHeight;
         }
-        NotificationViewWrapper collapsedWrapper = layout.getVisibleWrapper(
-                NotificationContentView.VISIBLE_TYPE_CONTRACTED);
-        minHeight += collapsedWrapper.getMinHeightIncrease(mUseIncreasedCollapsedHeight);
         boolean headsUpCustom = layout.getHeadsUpChild() != null &&
                 layout.getHeadsUpChild().getId()
                         != com.android.internal.R.id.status_bar_latest_event_content;
@@ -450,11 +447,6 @@
         } else {
             headsUpheight = mMaxHeadsUpHeight;
         }
-        NotificationViewWrapper headsUpWrapper = layout.getVisibleWrapper(
-                NotificationContentView.VISIBLE_TYPE_HEADSUP);
-        if (headsUpWrapper != null) {
-            headsUpheight += headsUpWrapper.getMinHeightIncrease(mUseIncreasedCollapsedHeight);
-        }
         layout.setHeights(minHeight, headsUpheight, mNotificationMaxHeight,
                 mNotificationAmbientHeight);
     }
@@ -2024,14 +2016,15 @@
     }
 
     @Override
-    public int getMinHeight() {
-        if (mGuts != null && mGuts.isExposed()) {
+    public int getMinHeight(boolean ignoreTemporaryStates) {
+        if (!ignoreTemporaryStates && mGuts != null && mGuts.isExposed()) {
             return mGuts.getIntrinsicHeight();
-        } else if (isHeadsUpAllowed() && mIsHeadsUp && mHeadsUpManager.isTrackingHeadsUp()) {
+        } else if (!ignoreTemporaryStates && isHeadsUpAllowed() && mIsHeadsUp
+                && mHeadsUpManager.isTrackingHeadsUp()) {
                 return getPinnedHeadsUpHeight(false /* atLeastMinHeight */);
         } else if (mIsSummaryWithChildren && !isGroupExpanded() && !mShowingPublic) {
             return mChildrenContainer.getMinHeight();
-        } else if (isHeadsUpAllowed() && mIsHeadsUp) {
+        } else if (!ignoreTemporaryStates && isHeadsUpAllowed() && mIsHeadsUp) {
             return mHeadsUpHeight;
         }
         NotificationContentView showingLayout = getShowingLayout();
diff --git a/com/android/systemui/statusbar/ExpandableView.java b/com/android/systemui/statusbar/ExpandableView.java
index efe5e0c..aac9af8 100644
--- a/com/android/systemui/statusbar/ExpandableView.java
+++ b/com/android/systemui/statusbar/ExpandableView.java
@@ -151,9 +151,21 @@
     }
 
     /**
-     * @return The minimum content height of this notification.
+     * @return The minimum content height of this notification. This also respects the temporary
+     * states of the view.
      */
     public int getMinHeight() {
+        return getMinHeight(false /* ignoreTemporaryStates */);
+    }
+
+    /**
+     * Get the minimum height of this view.
+     *
+     * @param ignoreTemporaryStates should temporary states be ignored like the guts or heads-up.
+     *
+     * @return The minimum height that this view needs.
+     */
+    public int getMinHeight(boolean ignoreTemporaryStates) {
         return getHeight();
     }
 
diff --git a/com/android/systemui/statusbar/KeyboardShortcuts.java b/com/android/systemui/statusbar/KeyboardShortcuts.java
index d370a63..2d16d22 100644
--- a/com/android/systemui/statusbar/KeyboardShortcuts.java
+++ b/com/android/systemui/statusbar/KeyboardShortcuts.java
@@ -433,24 +433,27 @@
         // Assist.
         final AssistUtils assistUtils = new AssistUtils(mContext);
         final ComponentName assistComponent = assistUtils.getAssistComponentForUser(userId);
-        PackageInfo assistPackageInfo = null;
-        try {
-            assistPackageInfo = mPackageManager.getPackageInfo(
-                    assistComponent.getPackageName(), 0, userId);
-        } catch (RemoteException e) {
-            Log.e(TAG, "PackageManagerService is dead");
-        }
+        // Not all devices have an assist component.
+        if (assistComponent != null) {
+            PackageInfo assistPackageInfo = null;
+            try {
+                assistPackageInfo = mPackageManager.getPackageInfo(
+                        assistComponent.getPackageName(), 0, userId);
+            } catch (RemoteException e) {
+                Log.e(TAG, "PackageManagerService is dead");
+            }
 
-        if (assistPackageInfo != null) {
-            final Icon assistIcon = Icon.createWithResource(
-                    assistPackageInfo.applicationInfo.packageName,
-                    assistPackageInfo.applicationInfo.icon);
+            if (assistPackageInfo != null) {
+                final Icon assistIcon = Icon.createWithResource(
+                        assistPackageInfo.applicationInfo.packageName,
+                        assistPackageInfo.applicationInfo.icon);
 
-            keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
-                    mContext.getString(R.string.keyboard_shortcut_group_applications_assist),
-                    assistIcon,
-                    KeyEvent.KEYCODE_UNKNOWN,
-                    KeyEvent.META_META_ON));
+                keyboardShortcutInfoAppItems.add(new KeyboardShortcutInfo(
+                        mContext.getString(R.string.keyboard_shortcut_group_applications_assist),
+                        assistIcon,
+                        KeyEvent.KEYCODE_UNKNOWN,
+                        KeyEvent.META_META_ON));
+            }
         }
 
         // Browser.
diff --git a/com/android/systemui/statusbar/NotificationData.java b/com/android/systemui/statusbar/NotificationData.java
index ddc7dd0..d0417b5 100644
--- a/com/android/systemui/statusbar/NotificationData.java
+++ b/com/android/systemui/statusbar/NotificationData.java
@@ -292,8 +292,8 @@
 
             if (mRankingMap != null) {
                 // RankingMap as received from NoMan
-                mRankingMap.getRanking(a.key, mRankingA);
-                mRankingMap.getRanking(b.key, mRankingB);
+                getRanking(a.key, mRankingA);
+                getRanking(b.key, mRankingB);
                 aImportance = mRankingA.getImportance();
                 bImportance = mRankingB.getImportance();
                 aRank = mRankingA.getRank();
@@ -381,7 +381,7 @@
 
     public boolean isAmbient(String key) {
         if (mRankingMap != null) {
-            mRankingMap.getRanking(key, mTmpRanking);
+            getRanking(key, mTmpRanking);
             return mTmpRanking.isAmbient();
         }
         return false;
@@ -389,7 +389,7 @@
 
     public int getVisibilityOverride(String key) {
         if (mRankingMap != null) {
-            mRankingMap.getRanking(key, mTmpRanking);
+            getRanking(key, mTmpRanking);
             return mTmpRanking.getVisibilityOverride();
         }
         return Ranking.VISIBILITY_NO_OVERRIDE;
@@ -397,7 +397,7 @@
 
     public boolean shouldSuppressScreenOff(String key) {
         if (mRankingMap != null) {
-            mRankingMap.getRanking(key, mTmpRanking);
+            getRanking(key, mTmpRanking);
             return (mTmpRanking.getSuppressedVisualEffects()
                     & NotificationListenerService.SUPPRESSED_EFFECT_SCREEN_OFF) != 0;
         }
@@ -406,7 +406,7 @@
 
     public boolean shouldSuppressScreenOn(String key) {
         if (mRankingMap != null) {
-            mRankingMap.getRanking(key, mTmpRanking);
+            getRanking(key, mTmpRanking);
             return (mTmpRanking.getSuppressedVisualEffects()
                     & NotificationListenerService.SUPPRESSED_EFFECT_SCREEN_ON) != 0;
         }
@@ -415,7 +415,7 @@
 
     public int getImportance(String key) {
         if (mRankingMap != null) {
-            mRankingMap.getRanking(key, mTmpRanking);
+            getRanking(key, mTmpRanking);
             return mTmpRanking.getImportance();
         }
         return NotificationManager.IMPORTANCE_UNSPECIFIED;
@@ -423,7 +423,7 @@
 
     public String getOverrideGroupKey(String key) {
         if (mRankingMap != null) {
-            mRankingMap.getRanking(key, mTmpRanking);
+            getRanking(key, mTmpRanking);
             return mTmpRanking.getOverrideGroupKey();
         }
          return null;
@@ -431,7 +431,7 @@
 
     public List<SnoozeCriterion> getSnoozeCriteria(String key) {
         if (mRankingMap != null) {
-            mRankingMap.getRanking(key, mTmpRanking);
+            getRanking(key, mTmpRanking);
             return mTmpRanking.getSnoozeCriteria();
         }
         return null;
@@ -439,7 +439,7 @@
 
     public NotificationChannel getChannel(String key) {
         if (mRankingMap != null) {
-            mRankingMap.getRanking(key, mTmpRanking);
+            getRanking(key, mTmpRanking);
             return mTmpRanking.getChannel();
         }
         return null;
@@ -452,6 +452,9 @@
                 final int N = mEntries.size();
                 for (int i = 0; i < N; i++) {
                     Entry entry = mEntries.valueAt(i);
+                    if (!getRanking(entry.key, mTmpRanking)) {
+                        continue;
+                    }
                     final StatusBarNotification oldSbn = entry.notification.cloneLight();
                     final String overrideGroupKey = getOverrideGroupKey(entry.key);
                     if (!Objects.equals(oldSbn.getOverrideGroupKey(), overrideGroupKey)) {
@@ -466,6 +469,19 @@
         filterAndSort();
     }
 
+    /**
+     * Get the ranking from the current ranking map.
+     *
+     * @param key the key to look up
+     * @param outRanking the ranking to populate
+     *
+     * @return {@code true} if the ranking was properly obtained.
+     */
+    @VisibleForTesting
+    protected boolean getRanking(String key, Ranking outRanking) {
+        return mRankingMap.getRanking(key, outRanking);
+    }
+
     // TODO: This should not be public. Instead the Environment should notify this class when
     // anything changed, and this class should call back the UI so it updates itself.
     public void filterAndSort() {
@@ -573,7 +589,7 @@
     }
 
     private void dumpEntry(PrintWriter pw, String indent, int i, Entry e) {
-        mRankingMap.getRanking(e.key, mTmpRanking);
+        getRanking(e.key, mTmpRanking);
         pw.print(indent);
         pw.println("  [" + i + "] key=" + e.key + " icon=" + e.icon);
         StatusBarNotification n = e.notification;
diff --git a/com/android/systemui/statusbar/NotificationSnooze.java b/com/android/systemui/statusbar/NotificationSnooze.java
index c45ca54..492ab44 100644
--- a/com/android/systemui/statusbar/NotificationSnooze.java
+++ b/com/android/systemui/statusbar/NotificationSnooze.java
@@ -16,8 +16,13 @@
  */
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
+import java.util.concurrent.TimeUnit;
 
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.logging.MetricsLogger;
+import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
 
@@ -28,12 +33,15 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Typeface;
+import android.metrics.LogMaker;
 import android.os.Bundle;
+import android.provider.Settings;
 import android.service.notification.SnoozeCriterion;
 import android.service.notification.StatusBarNotification;
 import android.text.SpannableString;
 import android.text.style.StyleSpan;
 import android.util.AttributeSet;
+import android.util.KeyValueListParser;
 import android.util.Log;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -51,11 +59,23 @@
 public class NotificationSnooze extends LinearLayout
         implements NotificationGuts.GutsContent, View.OnClickListener {
 
+    private static final String TAG = "NotificationSnooze";
     /**
      * If this changes more number increases, more assistant action resId's should be defined for
      * accessibility purposes, see {@link #setSnoozeOptions(List)}
      */
     private static final int MAX_ASSISTANT_SUGGESTIONS = 1;
+    private static final String KEY_DEFAULT_SNOOZE = "default";
+    private static final String KEY_OPTIONS = "options_array";
+    private static final LogMaker OPTIONS_OPEN_LOG =
+            new LogMaker(MetricsEvent.NOTIFICATION_SNOOZE_OPTIONS)
+                    .setType(MetricsEvent.TYPE_OPEN);
+    private static final LogMaker OPTIONS_CLOSE_LOG =
+            new LogMaker(MetricsEvent.NOTIFICATION_SNOOZE_OPTIONS)
+                    .setType(MetricsEvent.TYPE_CLOSE);
+    private static final LogMaker UNDO_LOG =
+            new LogMaker(MetricsEvent.NOTIFICATION_UNDO_SNOOZE)
+                    .setType(MetricsEvent.TYPE_ACTION);
     private NotificationGuts mGutsContainer;
     private NotificationSwipeActionHelper mSnoozeListener;
     private StatusBarNotification mSbn;
@@ -72,9 +92,31 @@
     private boolean mSnoozing;
     private boolean mExpanded;
     private AnimatorSet mExpandAnimation;
+    private KeyValueListParser mParser;
+
+    private final static int[] sAccessibilityActions = {
+            R.id.action_snooze_shorter,
+            R.id.action_snooze_short,
+            R.id.action_snooze_long,
+            R.id.action_snooze_longer,
+    };
+
+    private MetricsLogger mMetricsLogger = new MetricsLogger();
 
     public NotificationSnooze(Context context, AttributeSet attrs) {
         super(context, attrs);
+        mParser = new KeyValueListParser(',');
+    }
+
+    @VisibleForTesting
+    SnoozeOption getDefaultOption()
+    {
+        return mDefaultOption;
+    }
+
+    @VisibleForTesting
+    void setKeyValueListParser(KeyValueListParser parser) {
+        mParser = parser;
     }
 
     @Override
@@ -96,7 +138,13 @@
         mSnoozeOptions = getDefaultSnoozeOptions();
         createOptionViews();
 
-        setSelected(mDefaultOption);
+        setSelected(mDefaultOption, false);
+    }
+
+    @Override
+    protected void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        logOptionSelection(MetricsEvent.NOTIFICATION_SNOOZE_CLICKED, mDefaultOption);
     }
 
     @Override
@@ -136,7 +184,7 @@
             SnoozeOption so = mSnoozeOptions.get(i);
             if (so.getAccessibilityAction() != null
                     && so.getAccessibilityAction().getId() == action) {
-                setSelected(so);
+                setSelected(so, true);
                 return true;
             }
         }
@@ -172,17 +220,49 @@
         mSbn = sbn;
     }
 
-    private ArrayList<SnoozeOption> getDefaultSnoozeOptions() {
+    @VisibleForTesting
+    ArrayList<SnoozeOption> getDefaultSnoozeOptions() {
+        final Resources resources = getContext().getResources();
         ArrayList<SnoozeOption> options = new ArrayList<>();
+        try {
+            final String config = Settings.Global.getString(getContext().getContentResolver(),
+                    Settings.Global.NOTIFICATION_SNOOZE_OPTIONS);
+            mParser.setString(config);
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "Bad snooze constants");
+        }
 
-        options.add(createOption(15 /* minutes */, R.id.action_snooze_15_min));
-        options.add(createOption(30 /* minutes */, R.id.action_snooze_30_min));
-        mDefaultOption = createOption(60 /* minutes */, R.id.action_snooze_1_hour);
-        options.add(mDefaultOption);
-        options.add(createOption(60 * 2 /* minutes */, R.id.action_snooze_2_hours));
+        final int defaultSnooze = mParser.getInt(KEY_DEFAULT_SNOOZE,
+                resources.getInteger(R.integer.config_notification_snooze_time_default));
+        final int[] snoozeTimes = parseIntArray(KEY_OPTIONS,
+                resources.getIntArray(R.array.config_notification_snooze_times));
+
+        for (int i = 0; i < snoozeTimes.length && i < sAccessibilityActions.length; i++) {
+            int snoozeTime = snoozeTimes[i];
+            SnoozeOption option = createOption(snoozeTime, sAccessibilityActions[i]);
+            if (i == 0 || snoozeTime == defaultSnooze) {
+                mDefaultOption = option;
+            }
+            options.add(option);
+        }
         return options;
     }
 
+    @VisibleForTesting
+    int[] parseIntArray(final String key, final int[] defaultArray) {
+        final String value = mParser.getString(key, null);
+        if (value != null) {
+            try {
+                return Arrays.stream(value.split(":")).map(String::trim).mapToInt(
+                        Integer::parseInt).toArray();
+            } catch (NumberFormatException e) {
+                return defaultArray;
+            }
+        } else {
+            return defaultArray;
+        }
+    }
+
     private SnoozeOption createOption(int minutes, int accessibilityActionId) {
         Resources res = getResources();
         boolean showInHours = minutes >= 60;
@@ -268,12 +348,24 @@
         mExpandAnimation.start();
     }
 
-    private void setSelected(SnoozeOption option) {
+    private void setSelected(SnoozeOption option, boolean userAction) {
         mSelectedOption = option;
         mSelectedOptionText.setText(option.getConfirmation());
         showSnoozeOptions(false);
         hideSelectedOption();
         sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+        if (userAction) {
+            logOptionSelection(MetricsEvent.NOTIFICATION_SELECT_SNOOZE, option);
+        }
+    }
+
+    private void logOptionSelection(int category, SnoozeOption option) {
+        int index = mSnoozeOptions.indexOf(option);
+        long duration = TimeUnit.MINUTES.toMillis(option.getMinutesToSnoozeFor());
+        mMetricsLogger.write(new LogMaker(category)
+                .setType(MetricsEvent.TYPE_ACTION)
+                .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_SNOOZE_INDEX, index)
+                .addTaggedData(MetricsEvent.FIELD_NOTIFICATION_SNOOZE_DURATION_MS, duration));
     }
 
     @Override
@@ -284,13 +376,15 @@
         final int id = v.getId();
         final SnoozeOption tag = (SnoozeOption) v.getTag();
         if (tag != null) {
-            setSelected(tag);
+            setSelected(tag, true);
         } else if (id == R.id.notification_snooze) {
             // Toggle snooze options
             showSnoozeOptions(!mExpanded);
+            mMetricsLogger.write(!mExpanded ? OPTIONS_OPEN_LOG : OPTIONS_CLOSE_LOG);
         } else {
             // Undo snooze was selected
             undoSnooze(v);
+            mMetricsLogger.write(UNDO_LOG);
         }
     }
 
@@ -321,7 +415,7 @@
     @Override
     public View getContentView() {
         // Reset the view before use
-        setSelected(mDefaultOption);
+        setSelected(mDefaultOption, false);
         return this;
     }
 
@@ -343,7 +437,7 @@
             return true;
         } else {
             // The view should actually be closed
-            setSelected(mSnoozeOptions.get(0));
+            setSelected(mSnoozeOptions.get(0), false);
             return false; // Return false here so that guts handles closing the view
         }
     }
diff --git a/com/android/systemui/statusbar/SignalClusterView.java b/com/android/systemui/statusbar/SignalClusterView.java
index 759d2cf..a7fb61a 100644
--- a/com/android/systemui/statusbar/SignalClusterView.java
+++ b/com/android/systemui/statusbar/SignalClusterView.java
@@ -16,14 +16,15 @@
 
 package com.android.systemui.statusbar;
 
+import static android.app.StatusBarManager.DISABLE2_SYSTEM_ICONS;
+import static android.app.StatusBarManager.DISABLE_NONE;
+
 import android.annotation.DrawableRes;
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
 import android.graphics.Color;
 import android.graphics.Rect;
-import android.graphics.drawable.Animatable;
-import android.graphics.drawable.AnimatedVectorDrawable;
 import android.graphics.drawable.Drawable;
 import android.telephony.SubscriptionInfo;
 import android.util.ArraySet;
@@ -50,14 +51,15 @@
 import com.android.systemui.statusbar.policy.SecurityController;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.tuner.TunerService.Tunable;
+import com.android.systemui.util.Utils.DisableStateTracker;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 
 // Intimately tied to the design of res/layout/signal_cluster_view.xml
 public class SignalClusterView extends LinearLayout implements NetworkControllerImpl.SignalCallback,
-        SecurityController.SecurityControllerCallback, Tunable,
-        DarkReceiver {
+        SecurityController.SecurityControllerCallback, Tunable, DarkReceiver {
 
     static final String TAG = "SignalClusterView";
     static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -73,6 +75,7 @@
 
     private boolean mNoSimsVisible = false;
     private boolean mVpnVisible = false;
+    private boolean mSimDetected;
     private int mVpnIconId = 0;
     private int mLastVpnIconId = -1;
     private boolean mEthernetVisible = false;
@@ -148,6 +151,8 @@
         mIconScaleFactor = typedValue.getFloat();
         mNetworkController = Dependency.get(NetworkController.class);
         mSecurityController = Dependency.get(SecurityController.class);
+        addOnAttachStateChangeListener(
+                new DisableStateTracker(DISABLE_NONE, DISABLE2_SYSTEM_ICONS));
         updateActivityEnabled();
     }
 
@@ -327,8 +332,9 @@
     }
 
     @Override
-    public void setNoSims(boolean show) {
+    public void setNoSims(boolean show, boolean simDetected) {
         mNoSimsVisible = show && !mBlockMobile;
+        mSimDetected = simDetected;
         apply();
     }
 
@@ -548,6 +554,23 @@
         if (mNoSimsVisible) {
             mIconLogger.onIconShown(SLOT_MOBILE);
             mNoSimsCombo.setVisibility(View.VISIBLE);
+            if (!Objects.equals(mSimDetected, mNoSimsCombo.getTag())) {
+                mNoSimsCombo.setTag(mSimDetected);
+                if (mSimDetected) {
+                    SignalDrawable d = new SignalDrawable(mNoSims.getContext());
+                    d.setDarkIntensity(0);
+                    mNoSims.setImageDrawable(d);
+                    mNoSims.setImageLevel(SignalDrawable.getEmptyState(4));
+
+                    SignalDrawable dark = new SignalDrawable(mNoSims.getContext());
+                    dark.setDarkIntensity(1);
+                    mNoSimsDark.setImageDrawable(dark);
+                    mNoSimsDark.setImageLevel(SignalDrawable.getEmptyState(4));
+                } else {
+                    mNoSims.setImageResource(R.drawable.stat_sys_no_sims);
+                    mNoSimsDark.setImageResource(R.drawable.stat_sys_no_sims);
+                }
+            }
         } else {
             mIconLogger.onIconHidden(SLOT_MOBILE);
             mNoSimsCombo.setVisibility(View.GONE);
diff --git a/com/android/systemui/statusbar/StatusBarIconView.java b/com/android/systemui/statusbar/StatusBarIconView.java
index 2cff79d..6cfd42f 100644
--- a/com/android/systemui/statusbar/StatusBarIconView.java
+++ b/com/android/systemui/statusbar/StatusBarIconView.java
@@ -43,6 +43,7 @@
 import android.util.Log;
 import android.util.Property;
 import android.util.TypedValue;
+import android.view.View;
 import android.view.ViewDebug;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.animation.Interpolator;
@@ -142,6 +143,7 @@
     private float[] mMatrix;
     private ColorMatrixColorFilter mMatrixColorFilter;
     private boolean mIsInShelf;
+    private Runnable mLayoutRunnable;
 
     public StatusBarIconView(Context context, String slot, StatusBarNotification sbn) {
         this(context, slot, sbn, false);
@@ -796,6 +798,24 @@
         }
     }
 
+    /**
+     * This method returns the drawing rect for the view which is different from the regular
+     * drawing rect, since we layout all children at position 0 and usually the translation is
+     * neglected. The standard implementation doesn't account for translation.
+     *
+     * @param outRect The (scrolled) drawing bounds of the view.
+     */
+    @Override
+    public void getDrawingRect(Rect outRect) {
+        super.getDrawingRect(outRect);
+        float translationX = getTranslationX();
+        float translationY = getTranslationY();
+        outRect.left += translationX;
+        outRect.right += translationX;
+        outRect.top += translationY;
+        outRect.bottom += translationY;
+    }
+
     public void setIsInShelf(boolean isInShelf) {
         mIsInShelf = isInShelf;
     }
@@ -804,6 +824,19 @@
         return mIsInShelf;
     }
 
+    @Override
+    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+        super.onLayout(changed, left, top, right, bottom);
+        if (mLayoutRunnable != null) {
+            mLayoutRunnable.run();
+            mLayoutRunnable = null;
+        }
+    }
+
+    public void executeOnLayout(Runnable runnable) {
+        mLayoutRunnable = runnable;
+    }
+
     public interface OnVisibilityChangedListener {
         void onVisibilityChanged(int newVisibility);
     }
diff --git a/com/android/systemui/statusbar/car/CarNavigationBarController.java b/com/android/systemui/statusbar/car/CarNavigationBarController.java
index 7e08d56..f5c77f2 100644
--- a/com/android/systemui/statusbar/car/CarNavigationBarController.java
+++ b/com/android/systemui/statusbar/car/CarNavigationBarController.java
@@ -15,7 +15,13 @@
  */
 package com.android.systemui.statusbar.car;
 
-import android.app.ActivityManager.StackId;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+
+import android.app.ActivityManager;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
@@ -101,7 +107,7 @@
         }
     }
 
-    public void taskChanged(String packageName, int stackId) {
+    public void taskChanged(String packageName, ActivityManager.RunningTaskInfo taskInfo) {
         // If the package name belongs to a filter, then highlight appropriate button in
         // the navigation bar.
         if (mFacetPackageMap.containsKey(packageName)) {
@@ -115,9 +121,11 @@
         }
 
         // Set up the persistent docked task if needed.
-        if (mPersistentTaskIntent != null && !mStatusBar.hasDockedTask()
-                && stackId != StackId.HOME_STACK_ID) {
-            mStatusBar.startActivityOnStack(mPersistentTaskIntent, StackId.DOCKED_STACK_ID);
+        boolean isHomeTask =
+                taskInfo.configuration.windowConfiguration.getActivityType() == ACTIVITY_TYPE_HOME;
+        if (mPersistentTaskIntent != null && !mStatusBar.hasDockedTask() && !isHomeTask) {
+            mStatusBar.startActivityOnStack(mPersistentTaskIntent,
+                    WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_UNDEFINED);
         }
     }
 
@@ -375,13 +383,15 @@
         // rather than the "preferred/last run" app.
         intent.putExtra(EXTRA_FACET_LAUNCH_PICKER, index == mCurrentFacetIndex);
 
-        int stackId = StackId.FULLSCREEN_WORKSPACE_STACK_ID;
+        int windowingMode = WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
+        int activityType = ACTIVITY_TYPE_UNDEFINED;
         if (intent.getCategories().contains(Intent.CATEGORY_HOME)) {
-            stackId = StackId.HOME_STACK_ID;
+            windowingMode = WINDOWING_MODE_UNDEFINED;
+            activityType = ACTIVITY_TYPE_HOME;
         }
 
         setCurrentFacet(index);
-        mStatusBar.startActivityOnStack(intent, stackId);
+        mStatusBar.startActivityOnStack(intent, windowingMode, activityType);
     }
 
     /**
@@ -391,6 +401,7 @@
      */
     private void onFacetLongClicked(Intent intent, int index) {
         setCurrentFacet(index);
-        mStatusBar.startActivityOnStack(intent, StackId.FULLSCREEN_WORKSPACE_STACK_ID);
+        mStatusBar.startActivityOnStack(intent,
+                WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_UNDEFINED);
     }
 }
diff --git a/com/android/systemui/statusbar/car/CarStatusBar.java b/com/android/systemui/statusbar/car/CarStatusBar.java
index 680f693..59d3e0a 100644
--- a/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -314,7 +314,7 @@
             ActivityManager.RunningTaskInfo runningTaskInfo = ssp.getRunningTask();
             if (runningTaskInfo != null && runningTaskInfo.baseActivity != null) {
                 mController.taskChanged(runningTaskInfo.baseActivity.getPackageName(),
-                        runningTaskInfo.stackId);
+                        runningTaskInfo);
             }
         }
     }
@@ -378,9 +378,10 @@
         return result;
     }
 
-    public int startActivityOnStack(Intent intent, int stackId) {
-        ActivityOptions options = ActivityOptions.makeBasic();
-        options.setLaunchStackId(stackId);
+    public int startActivityOnStack(Intent intent, int windowingMode, int activityType) {
+        final ActivityOptions options = ActivityOptions.makeBasic();
+        options.setLaunchWindowingMode(windowingMode);
+        options.setLaunchActivityType(activityType);
         return startActivityWithOptions(intent, options.toBundle());
     }
 
diff --git a/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java b/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
index 9bfa7a9..bb979eb 100644
--- a/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
+++ b/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
@@ -25,7 +25,6 @@
 import android.widget.ProgressBar;
 import android.widget.TextView;
 
-import com.android.systemui.R;
 import com.android.systemui.statusbar.CrossFadeHelper;
 import com.android.systemui.statusbar.ExpandableNotificationRow;
 import com.android.systemui.statusbar.TransformableView;
@@ -48,7 +47,6 @@
 
     private int mContentHeight;
     private int mMinHeightHint;
-    private boolean mColorized;
 
     protected NotificationTemplateViewWrapper(Context ctx, View view,
             ExpandableNotificationRow row) {
@@ -164,9 +162,7 @@
     public void onContentUpdated(ExpandableNotificationRow row) {
         // Reinspect the notification. Before the super call, because the super call also updates
         // the transformation types and we need to have our values set by then.
-        StatusBarNotification sbn = row.getStatusBarNotification();
-        resolveTemplateViews(sbn);
-        mColorized = sbn.getNotification().isColorized();
+        resolveTemplateViews(row.getStatusBarNotification());
         super.onContentUpdated(row);
     }
 
@@ -269,17 +265,6 @@
         updateActionOffset();
     }
 
-    @Override
-    public int getMinHeightIncrease(boolean useIncreasedCollapsedHeight) {
-        if (mColorized) {
-            int dimen = useIncreasedCollapsedHeight
-                    ? R.dimen.notification_height_increase_colorized_increased
-                    : R.dimen.notification_height_increase_colorized;
-            return mRow.getResources().getDimensionPixelSize(dimen);
-        }
-        return super.getMinHeightIncrease(useIncreasedCollapsedHeight);
-    }
-
     private void updateActionOffset() {
         if (mActionsContainer != null) {
             // We should never push the actions higher than they are in the headsup view.
diff --git a/com/android/systemui/statusbar/notification/NotificationViewWrapper.java b/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
index 085bce9..5200d69 100644
--- a/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
+++ b/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
@@ -190,14 +190,4 @@
     public boolean disallowSingleClick(float x, float y) {
         return false;
     }
-
-    /**
-     * Get the amount that the minheight is allowed to be increased based on this layout.
-     *
-     * @param increasedHeight is the view allowed to show even bigger, i.e for messaging layouts
-     * @return
-     */
-    public int getMinHeightIncrease(boolean increasedHeight) {
-        return 0;
-    }
 }
diff --git a/com/android/systemui/statusbar/phone/BarTransitions.java b/com/android/systemui/statusbar/phone/BarTransitions.java
index 1f44abe..4bca797 100644
--- a/com/android/systemui/statusbar/phone/BarTransitions.java
+++ b/com/android/systemui/statusbar/phone/BarTransitions.java
@@ -50,7 +50,7 @@
     public static final int MODE_LIGHTS_OUT_TRANSPARENT = 6;
 
     public static final int LIGHTS_IN_DURATION = 250;
-    public static final int LIGHTS_OUT_DURATION = 750;
+    public static final int LIGHTS_OUT_DURATION = 1500;
     public static final int BACKGROUND_DURATION = 200;
 
     private final String mTag;
diff --git a/com/android/systemui/statusbar/phone/DozeScrimController.java b/com/android/systemui/statusbar/phone/DozeScrimController.java
index 021b451..8afb849 100644
--- a/com/android/systemui/statusbar/phone/DozeScrimController.java
+++ b/com/android/systemui/statusbar/phone/DozeScrimController.java
@@ -384,7 +384,7 @@
             if (mDozeParameters.getAlwaysOn()) {
                 // Setting power states can happen after we push out the frame. Make sure we
                 // stay fully opaque until the power state request reaches the lower levels.
-                setDozeInFrontAlphaDelayed(mAodFrontScrimOpacity, 30);
+                setDozeInFrontAlphaDelayed(mAodFrontScrimOpacity, 100);
             }
         }
     };
diff --git a/com/android/systemui/statusbar/phone/LightBarController.java b/com/android/systemui/statusbar/phone/LightBarController.java
index 533771a..d226fed 100644
--- a/com/android/systemui/statusbar/phone/LightBarController.java
+++ b/com/android/systemui/statusbar/phone/LightBarController.java
@@ -264,8 +264,10 @@
         pw.println(" StatusBarTransitionsController:");
         mStatusBarIconController.getTransitionsController().dump(fd, pw, args);
         pw.println();
-        pw.println(" NavigationBarTransitionsController:");
-        mNavigationBarController.dump(fd, pw, args);
-        pw.println();
+        if (mNavigationBarController != null) {
+            pw.println(" NavigationBarTransitionsController:");
+            mNavigationBarController.dump(fd, pw, args);
+            pw.println();
+        }
     }
 }
diff --git a/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index cfe0a4a..6d3bc1d 100644
--- a/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -437,7 +437,7 @@
     }
 
     private boolean onNavigationTouch(View v, MotionEvent event) {
-        mStatusBar.checkUserAutohide(v, event);
+        mStatusBar.checkUserAutohide(event);
         return false;
     }
 
diff --git a/com/android/systemui/statusbar/phone/NotificationIconAreaController.java b/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
index 41a69b4..40fe50f 100644
--- a/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
+++ b/com/android/systemui/statusbar/phone/NotificationIconAreaController.java
@@ -4,10 +4,8 @@
 import android.content.res.Resources;
 import android.graphics.Color;
 import android.graphics.Rect;
-import android.graphics.drawable.Icon;
 import android.support.annotation.NonNull;
 import android.support.v4.util.ArrayMap;
-import android.support.v4.util.ArraySet;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.widget.FrameLayout;
@@ -269,18 +267,26 @@
      */
     private void applyNotificationIconsTint() {
         for (int i = 0; i < mNotificationIcons.getChildCount(); i++) {
-            StatusBarIconView v = (StatusBarIconView) mNotificationIcons.getChildAt(i);
-            boolean isPreL = Boolean.TRUE.equals(v.getTag(R.id.icon_is_pre_L));
-            int color = StatusBarIconView.NO_COLOR;
-            boolean colorize = !isPreL || NotificationUtils.isGrayscale(v, mNotificationColorUtil);
-            if (colorize) {
-                color = DarkIconDispatcher.getTint(mTintArea, v, mIconTint);
+            final StatusBarIconView iv = (StatusBarIconView) mNotificationIcons.getChildAt(i);
+            if (iv.getWidth() != 0) {
+                updateTintForIcon(iv);
+            } else {
+                iv.executeOnLayout(() -> updateTintForIcon(iv));
             }
-            v.setStaticDrawableColor(color);
-            v.setDecorColor(mIconTint);
         }
     }
 
+    private void updateTintForIcon(StatusBarIconView v) {
+        boolean isPreL = Boolean.TRUE.equals(v.getTag(R.id.icon_is_pre_L));
+        int color = StatusBarIconView.NO_COLOR;
+        boolean colorize = !isPreL || NotificationUtils.isGrayscale(v, mNotificationColorUtil);
+        if (colorize) {
+            color = DarkIconDispatcher.getTint(mTintArea, v, mIconTint);
+        }
+        v.setStaticDrawableColor(color);
+        v.setDecorColor(mIconTint);
+    }
+
     public void setDark(boolean dark) {
         mNotificationIcons.setDark(dark, false, 0);
         mShelfIcons.setDark(dark, false, 0);
diff --git a/com/android/systemui/statusbar/phone/NotificationPanelView.java b/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 078e818..7b11ace 100644
--- a/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -509,7 +509,8 @@
             if (row.isRemoved()) {
                 continue;
             }
-            availableSpace -= child.getMinHeight() + notificationPadding;
+            availableSpace -= child.getMinHeight(true /* ignoreTemporaryStates */)
+                    + notificationPadding;
             if (availableSpace >= 0 && count < maximum) {
                 count++;
             } else if (availableSpace > -shelfSize) {
@@ -666,7 +667,7 @@
             return false;
         }
         initDownStates(event);
-        if (mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
+        if (mBar.panelEnabled() && mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
             mIsExpansionFromHeadsUp = true;
             MetricsLogger.count(mContext, COUNTER_PANEL_OPEN, 1);
             MetricsLogger.count(mContext, COUNTER_PANEL_OPEN_PEEK, 1);
diff --git a/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java b/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
index 4ae1393..9c837ed 100644
--- a/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
+++ b/com/android/systemui/statusbar/phone/PhoneStatusBarPolicy.java
@@ -16,8 +16,12 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+
 import android.app.ActivityManager;
-import android.app.ActivityManager.StackId;
 import android.app.ActivityManager.StackInfo;
 import android.app.AlarmManager;
 import android.app.AlarmManager.AlarmClockInfo;
@@ -523,12 +527,18 @@
         mCurrentNotifs.clear();
         mUiOffloadThread.submit(() -> {
             try {
-                int focusedId = ActivityManager.getService().getFocusedStackId();
-                if (focusedId == StackId.FULLSCREEN_WORKSPACE_STACK_ID) {
-                    checkStack(StackId.FULLSCREEN_WORKSPACE_STACK_ID, notifs, noMan, pm);
+                final StackInfo focusedStack = ActivityManager.getService().getFocusedStackInfo();
+                if (focusedStack != null) {
+                    final int windowingMode =
+                            focusedStack.configuration.windowConfiguration.getWindowingMode();
+                    if (windowingMode == WINDOWING_MODE_FULLSCREEN
+                            || windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
+                        checkStack(focusedStack, notifs, noMan, pm);
+                    }
                 }
                 if (mDockedStackExists) {
-                    checkStack(StackId.DOCKED_STACK_ID, notifs, noMan, pm);
+                    checkStack(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_UNDEFINED,
+                            notifs, noMan, pm);
                 }
             } catch (RemoteException e) {
                 e.rethrowFromSystemServer();
@@ -539,10 +549,19 @@
         });
     }
 
-    private void checkStack(int stackId, ArraySet<Pair<String, Integer>> notifs,
+    private void checkStack(int windowingMode, int activityType,
+            ArraySet<Pair<String, Integer>> notifs, NotificationManager noMan, IPackageManager pm) {
+        try {
+            final StackInfo info =
+                    ActivityManager.getService().getStackInfo(windowingMode, activityType);
+            checkStack(info, notifs, noMan, pm);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+    private void checkStack(StackInfo info, ArraySet<Pair<String, Integer>> notifs,
             NotificationManager noMan, IPackageManager pm) {
         try {
-            StackInfo info = ActivityManager.getService().getStackInfo(stackId);
             if (info == null || info.topActivity == null) return;
             String pkg = info.topActivity.getPackageName();
             if (!hasNotif(notifs, pkg, info.userId)) {
diff --git a/com/android/systemui/statusbar/phone/StatusBar.java b/com/android/systemui/statusbar/phone/StatusBar.java
index efc8d8b..54be857 100644
--- a/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/com/android/systemui/statusbar/phone/StatusBar.java
@@ -20,6 +20,7 @@
 import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
 import static android.app.StatusBarManager.windowStateToString;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
 import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP;
 import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE;
 import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_WAKING;
@@ -37,7 +38,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
-import android.app.ActivityManager.StackId;
 import android.app.ActivityOptions;
 import android.app.INotificationManager;
 import android.app.KeyguardManager;
@@ -69,9 +69,6 @@
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.ColorFilter;
-import android.graphics.PixelFormat;
 import android.graphics.Point;
 import android.graphics.PointF;
 import android.graphics.PorterDuff;
@@ -105,10 +102,10 @@
 import android.os.Vibrator;
 import android.provider.Settings;
 import android.service.notification.NotificationListenerService.RankingMap;
+import android.service.notification.NotificationStats;
 import android.service.notification.StatusBarNotification;
 import android.service.vr.IVrManager;
 import android.service.vr.IVrStateCallbacks;
-import android.text.TextUtils;
 import android.util.ArraySet;
 import android.util.DisplayMetrics;
 import android.util.EventLog;
@@ -127,13 +124,11 @@
 import android.view.ViewAnimationUtils;
 import android.view.ViewGroup;
 import android.view.ViewParent;
-import android.view.ViewStub;
 import android.view.ViewTreeObserver;
 import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
 import android.view.accessibility.AccessibilityManager;
 import android.view.animation.AccelerateInterpolator;
-import android.view.animation.Interpolator;
 import android.widget.DateTimeView;
 import android.widget.ImageView;
 import android.widget.RemoteViews;
@@ -259,7 +254,6 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
-import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
 import java.util.Stack;
@@ -279,11 +273,9 @@
             = SystemProperties.getBoolean("debug.child_notifs", true);
     public static final boolean FORCE_REMOTE_INPUT_HISTORY =
             SystemProperties.getBoolean("debug.force_remoteinput_history", false);
-    private static boolean ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT = false;
+    private static final boolean ENABLE_LOCK_SCREEN_ALLOW_REMOTE_INPUT = false;
 
-    protected static final int MSG_SHOW_RECENT_APPS = 1019;
     protected static final int MSG_HIDE_RECENT_APPS = 1020;
-    protected static final int MSG_TOGGLE_RECENTS_APPS = 1021;
     protected static final int MSG_PRELOAD_RECENT_APPS = 1022;
     protected static final int MSG_CANCEL_PRELOAD_RECENT_APPS = 1023;
     protected static final int MSG_TOGGLE_KEYBOARD_SHORTCUTS_MENU = 1026;
@@ -339,7 +331,7 @@
 
     private static final int STATUS_OR_NAV_TRANSIENT =
             View.STATUS_BAR_TRANSIENT | View.NAVIGATION_BAR_TRANSIENT;
-    private static final long AUTOHIDE_TIMEOUT_MS = 3000;
+    private static final long AUTOHIDE_TIMEOUT_MS = 2250;
 
     /** The minimum delay in ms between reports of notification visibility. */
     private static final int VISIBILITY_REPORT_MIN_DELAY_MS = 500;
@@ -365,10 +357,6 @@
     /** If true, the lockscreen will show a distinct wallpaper */
     private static final boolean ENABLE_LOCKSCREEN_WALLPAPER = true;
 
-    /* If true, the device supports freeform window management.
-     * This affects the status bar UI. */
-    private static final boolean FREEFORM_WINDOW_MANAGEMENT;
-
     /**
      * How long to wait before auto-dismissing a notification that was kept for remote input, and
      * has now sent a remote input. We auto-dismiss, because the app may not see a reason to cancel
@@ -387,19 +375,14 @@
 
     static {
         boolean onlyCoreApps;
-        boolean freeformWindowManagement;
         try {
             IPackageManager packageManager =
                     IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
             onlyCoreApps = packageManager.isOnlyCoreApps();
-            freeformWindowManagement = packageManager.hasSystemFeature(
-                    PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT, 0);
         } catch (RemoteException e) {
             onlyCoreApps = false;
-            freeformWindowManagement = false;
         }
         ONLY_CORE_APPS = onlyCoreApps;
-        FREEFORM_WINDOW_MANAGEMENT = freeformWindowManagement;
     }
 
     /**
@@ -410,17 +393,17 @@
     protected boolean mShowLockscreenNotifications;
     protected boolean mAllowLockscreenRemoteInput;
 
-    PhoneStatusBarPolicy mIconPolicy;
+    private PhoneStatusBarPolicy mIconPolicy;
 
-    VolumeComponent mVolumeComponent;
-    BrightnessMirrorController mBrightnessMirrorController;
+    private VolumeComponent mVolumeComponent;
+    private BrightnessMirrorController mBrightnessMirrorController;
     protected FingerprintUnlockController mFingerprintUnlockController;
-    LightBarController mLightBarController;
+    private LightBarController mLightBarController;
     protected LockscreenWallpaper mLockscreenWallpaper;
 
-    int mNaturalBarHeight = -1;
+    private int mNaturalBarHeight = -1;
 
-    Point mCurrentDisplaySize = new Point();
+    private final Point mCurrentDisplaySize = new Point();
 
     protected StatusBarWindowView mStatusBarWindow;
     protected PhoneStatusBarView mStatusBarView;
@@ -431,15 +414,13 @@
     private boolean mWakeUpComingFromTouch;
     private PointF mWakeUpTouchLocation;
 
-    int mPixelFormat;
-    Object mQueueLock = new Object();
+    private final Object mQueueLock = new Object();
 
     protected StatusBarIconController mIconController;
 
     // expanded notifications
     protected NotificationPanelView mNotificationPanel; // the sliding/resizing panel within the notification window
-    View mExpandedContents;
-    TextView mNotificationPanelDebugText;
+    private TextView mNotificationPanelDebugText;
 
     /**
      * {@code true} if notifications not part of a group should by default be rendered in their
@@ -452,12 +433,10 @@
     private QSPanel mQSPanel;
 
     // top bar
-    protected KeyguardStatusBarView mKeyguardStatusBar;
-    boolean mLeaveOpenOnKeyguardHide;
+    private KeyguardStatusBarView mKeyguardStatusBar;
+    private boolean mLeaveOpenOnKeyguardHide;
     KeyguardIndicationController mKeyguardIndicationController;
 
-    // Keyguard is going away soon.
-    private boolean mKeyguardGoingAway;
     // Keyguard is actually fading away now.
     protected boolean mKeyguardFadingAway;
     protected long mKeyguardFadingAwayDelay;
@@ -469,25 +448,19 @@
 
     private View mReportRejectedTouch;
 
-    int mMaxAllowedKeyguardNotifications;
+    private int mMaxAllowedKeyguardNotifications;
 
-    boolean mExpandedVisible;
+    private boolean mExpandedVisible;
 
-    // the tracker view
-    int mTrackingPosition; // the position of the top of the tracking view.
-
-    // Tracking finger for opening/closing.
-    boolean mTracking;
-
-    int[] mAbsPos = new int[2];
-    ArrayList<Runnable> mPostCollapseRunnables = new ArrayList<>();
+    private final int[] mAbsPos = new int[2];
+    private final ArrayList<Runnable> mPostCollapseRunnables = new ArrayList<>();
 
     // for disabling the status bar
-    int mDisabled1 = 0;
-    int mDisabled2 = 0;
+    private int mDisabled1 = 0;
+    private int mDisabled2 = 0;
 
     // tracking calls to View.setSystemUiVisibility()
-    int mSystemUiVisibility = View.SYSTEM_UI_FLAG_VISIBLE;
+    private int mSystemUiVisibility = View.SYSTEM_UI_FLAG_VISIBLE;
     private final Rect mLastFullscreenStackBounds = new Rect();
     private final Rect mLastDockedStackBounds = new Rect();
     private final Rect mTmpRect = new Rect();
@@ -495,7 +468,7 @@
     // last value sent to window manager
     private int mLastDispatchedSystemUiVisibility = ~View.SYSTEM_UI_FLAG_VISIBLE;
 
-    DisplayMetrics mDisplayMetrics = new DisplayMetrics();
+    private final DisplayMetrics mDisplayMetrics = new DisplayMetrics();
 
     // XXX: gesture research
     private final GestureRecorder mGestureRec = DEBUG_GESTURES
@@ -507,14 +480,17 @@
     private final MetricsLogger mMetricsLogger = Dependency.get(MetricsLogger.class);
 
     // ensure quick settings is disabled until the current user makes it through the setup wizard
-    private boolean mUserSetup = false;
-    private DeviceProvisionedListener mUserSetupObserver = new DeviceProvisionedListener() {
+    @VisibleForTesting
+    protected boolean mUserSetup = false;
+    private final DeviceProvisionedListener mUserSetupObserver = new DeviceProvisionedListener() {
         @Override
         public void onUserSetupChanged() {
             final boolean userSetup = mDeviceProvisionedController.isUserSetup(
                     mDeviceProvisionedController.getCurrentUser());
-            if (MULTIUSER_DEBUG) Log.d(TAG, String.format("User setup changed: " +
-                    "userSetup=%s mUserSetup=%s", userSetup, mUserSetup));
+            if (MULTIUSER_DEBUG) {
+                Log.d(TAG, String.format("User setup changed: userSetup=%s mUserSetup=%s",
+                        userSetup, mUserSetup));
+            }
 
             if (userSetup != mUserSetup) {
                 mUserSetup = userSetup;
@@ -528,7 +504,7 @@
         }
     };
 
-    protected H mHandler = createHandler();
+    protected final H mHandler = createHandler();
     final private ContentObserver mHeadsUpObserver = new ContentObserver(mHandler) {
         @Override
         public void onChange(boolean selfChange) {
@@ -537,8 +513,6 @@
                     && Settings.Global.HEADS_UP_OFF != Settings.Global.getInt(
                     mContext.getContentResolver(), Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED,
                     Settings.Global.HEADS_UP_OFF);
-            mHeadsUpTicker = mUseHeadsUp && 0 != Settings.Global.getInt(
-                    mContext.getContentResolver(), SETTING_HEADS_UP_TICKER, 0);
             Log.d(TAG, "heads up is " + (mUseHeadsUp ? "enabled" : "disabled"));
             if (wasUsing != mUseHeadsUp) {
                 if (!mUseHeadsUp) {
@@ -566,26 +540,21 @@
         }
     };
 
-    private boolean mWaitingForKeyguardExit;
     protected boolean mDozing;
     private boolean mDozingRequested;
     protected boolean mScrimSrcModeEnabled;
 
-    public static final Interpolator ALPHA_IN = Interpolators.ALPHA_IN;
-    public static final Interpolator ALPHA_OUT = Interpolators.ALPHA_OUT;
-
     protected BackDropView mBackdrop;
     protected ImageView mBackdropFront, mBackdropBack;
-    protected PorterDuffXfermode mSrcXferMode = new PorterDuffXfermode(PorterDuff.Mode.SRC);
-    protected PorterDuffXfermode mSrcOverXferMode =
+    protected final PorterDuffXfermode mSrcXferMode = new PorterDuffXfermode(PorterDuff.Mode.SRC);
+    protected final PorterDuffXfermode mSrcOverXferMode =
             new PorterDuffXfermode(PorterDuff.Mode.SRC_OVER);
 
     private MediaSessionManager mMediaSessionManager;
     private MediaController mMediaController;
     private String mMediaNotificationKey;
     private MediaMetadata mMediaMetadata;
-    private MediaController.Callback mMediaListener
-            = new MediaController.Callback() {
+    private final MediaController.Callback mMediaListener = new MediaController.Callback() {
         @Override
         public void onPlaybackStateChanged(PlaybackState state) {
             super.onPlaybackStateChanged(state);
@@ -607,17 +576,6 @@
         }
     };
 
-    private final OnChildLocationsChangedListener mOnChildLocationsChangedListener =
-            new OnChildLocationsChangedListener() {
-        @Override
-        public void onChildLocationsChanged(NotificationStackScrollLayout stackScrollLayout) {
-            userActivity();
-        }
-    };
-
-    private int mDisabledUnmodified1;
-    private int mDisabledUnmodified2;
-
     /** Keys of notifications currently visible to the user. */
     private final ArraySet<NotificationVisibility> mCurrentlyVisibleNotifications =
             new ArraySet<>();
@@ -644,15 +602,6 @@
     private boolean mWereIconsJustHidden;
     private boolean mBouncerWasShowingWhenHidden;
 
-    public boolean isStartedGoingToSleep() {
-        return mStartedGoingToSleep;
-    }
-
-    /**
-     * If set, the device has started going to sleep but isn't fully non-interactive yet.
-     */
-    protected boolean mStartedGoingToSleep;
-
     private final OnChildLocationsChangedListener mNotificationLocationsChangedListener =
             new OnChildLocationsChangedListener() {
                 @Override
@@ -686,7 +635,6 @@
         @Override
         public void run() {
             mLastVisibilityReportUptimeMs = SystemClock.uptimeMillis();
-            final String mediaKey = getCurrentMediaNotificationKey();
 
             // 1. Loop over mNotificationData entries:
             //   A. Keep list of visible notifications.
@@ -743,10 +691,10 @@
     private boolean mKeyguardRequested;
     private boolean mIsKeyguard;
     private LogMaker mStatusBarStateLog;
-    private LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger();
+    private final LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger();
     protected NotificationIconAreaController mNotificationIconAreaController;
     private boolean mReinflateNotificationsOnUserSwitched;
-    private HashMap<String, Entry> mPendingNotifications = new HashMap<>();
+    private final HashMap<String, Entry> mPendingNotifications = new HashMap<>();
     private boolean mClearAllEnabled;
     @Nullable private View mAmbientIndicationContainer;
     private String mKeyToRemoveOnGutsClosed;
@@ -769,20 +717,21 @@
             goToLockedShade(null);
         }
     };
-    private HashMap<ExpandableNotificationRow, List<ExpandableNotificationRow>> mTmpChildOrderMap
-            = new HashMap<>();
+    private final HashMap<ExpandableNotificationRow, List<ExpandableNotificationRow>>
+            mTmpChildOrderMap = new HashMap<>();
     private RankingMap mLatestRankingMap;
     private boolean mNoAnimationOnNextBarModeChange;
     private FalsingManager mFalsingManager;
 
-    private KeyguardUpdateMonitorCallback mUpdateCallback = new KeyguardUpdateMonitorCallback() {
-        @Override
-        public void onDreamingStateChanged(boolean dreaming) {
-            if (dreaming) {
-                maybeEscalateHeadsUp();
-            }
-        }
-    };
+    private final KeyguardUpdateMonitorCallback mUpdateCallback =
+            new KeyguardUpdateMonitorCallback() {
+                @Override
+                public void onDreamingStateChanged(boolean dreaming) {
+                    if (dreaming) {
+                        maybeEscalateHeadsUp();
+                    }
+                }
+            };
 
     private NavigationBarFragment mNavigationBar;
     private View mNavigationBarView;
@@ -861,10 +810,6 @@
 
         mRecents = getComponent(Recents.class);
 
-        final Configuration currentConfig = res.getConfiguration();
-        mLocale = currentConfig.locale;
-        mLayoutDirection = TextUtils.getLayoutDirectionFromLocale(mLocale);
-
         mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
         mKeyguardManager = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
         mLockPatternUtils = new LockPatternUtils(mContext);
@@ -994,9 +939,6 @@
         Dependency.get(ConfigurationController.class).addCallback(this);
     }
 
-    protected void createIconController() {
-    }
-
     // ================================================================================
     // Constructing the view
     // ================================================================================
@@ -1012,16 +954,14 @@
 
         // TODO: Deal with the ugliness that comes from having some of the statusbar broken out
         // into fragments, but the rest here, it leaves some awkward lifecycle and whatnot.
-        mNotificationPanel = (NotificationPanelView) mStatusBarWindow.findViewById(
-                R.id.notification_panel);
-        mStackScroller = (NotificationStackScrollLayout) mStatusBarWindow.findViewById(
-                R.id.notification_stack_scroller);
+        mNotificationPanel = mStatusBarWindow.findViewById(R.id.notification_panel);
+        mStackScroller = mStatusBarWindow.findViewById(R.id.notification_stack_scroller);
         mNotificationPanel.setStatusBar(this);
         mNotificationPanel.setGroupManager(mGroupManager);
         mAboveShelfObserver = new AboveShelfObserver(mStackScroller);
         mAboveShelfObserver.setListener(mStatusBarWindow.findViewById(
                 R.id.notification_container_parent));
-        mKeyguardStatusBar = (KeyguardStatusBarView) mStatusBarWindow.findViewById(R.id.keyguard_header);
+        mKeyguardStatusBar = mStatusBarWindow.findViewById(R.id.keyguard_header);
 
         mNotificationIconAreaController = SystemUIFactory.getInstance()
                 .createNotificationIconAreaController(context, this);
@@ -1057,10 +997,10 @@
         mNotificationData.setHeadsUpManager(mHeadsUpManager);
         mGroupManager.setHeadsUpManager(mHeadsUpManager);
         mHeadsUpManager.setVisualStabilityManager(mVisualStabilityManager);
+        putComponent(HeadsUpManager.class, mHeadsUpManager);
 
         if (MULTIUSER_DEBUG) {
-            mNotificationPanelDebugText = (TextView) mNotificationPanel.findViewById(
-                    R.id.header_debug_info);
+            mNotificationPanelDebugText = mNotificationPanel.findViewById(R.id.header_debug_info);
             mNotificationPanelDebugText.setVisibility(View.VISIBLE);
         }
 
@@ -1074,9 +1014,6 @@
             // no window manager? good luck with that
         }
 
-        // figure out which pixel-format to use for the status bar.
-        mPixelFormat = PixelFormat.OPAQUE;
-
         mStackScroller.setLongPressListener(getNotificationLongClicker());
         mStackScroller.setStatusBar(this);
         mStackScroller.setGroupManager(mGroupManager);
@@ -1086,11 +1023,10 @@
 
         inflateEmptyShadeView();
         inflateDismissView();
-        mExpandedContents = mStackScroller;
 
-        mBackdrop = (BackDropView) mStatusBarWindow.findViewById(R.id.backdrop);
-        mBackdropFront = (ImageView) mBackdrop.findViewById(R.id.backdrop_front);
-        mBackdropBack = (ImageView) mBackdrop.findViewById(R.id.backdrop_back);
+        mBackdrop = mStatusBarWindow.findViewById(R.id.backdrop);
+        mBackdropFront = mBackdrop.findViewById(R.id.backdrop_front);
+        mBackdropBack = mBackdrop.findViewById(R.id.backdrop_back);
 
         if (ENABLE_LOCKSCREEN_WALLPAPER) {
             mLockscreenWallpaper = new LockscreenWallpaper(mContext, this, mHandler);
@@ -1098,8 +1034,8 @@
 
         mKeyguardIndicationController =
                 SystemUIFactory.getInstance().createKeyguardIndicationController(mContext,
-                (ViewGroup) mStatusBarWindow.findViewById(R.id.keyguard_indication_area),
-                mNotificationPanel.getLockIcon());
+                        mStatusBarWindow.findViewById(R.id.keyguard_indication_area),
+                        mNotificationPanel.getLockIcon());
         mNotificationPanel.setKeyguardIndicationController(mKeyguardIndicationController);
 
 
@@ -1130,8 +1066,8 @@
             mNavigationBar.setLightBarController(mLightBarController);
         }
 
-        ScrimView scrimBehind = (ScrimView) mStatusBarWindow.findViewById(R.id.scrim_behind);
-        ScrimView scrimInFront = (ScrimView) mStatusBarWindow.findViewById(R.id.scrim_in_front);
+        ScrimView scrimBehind = mStatusBarWindow.findViewById(R.id.scrim_behind);
+        ScrimView scrimInFront = mStatusBarWindow.findViewById(R.id.scrim_in_front);
         View headsUpScrim = mStatusBarWindow.findViewById(R.id.heads_up_scrim);
         mScrimController = SystemUIFactory.getInstance().createScrimController(mLightBarController,
                 scrimBehind, scrimInFront, headsUpScrim, mLockscreenWallpaper,
@@ -1141,13 +1077,10 @@
                     }
                 });
         if (mScrimSrcModeEnabled) {
-            Runnable runnable = new Runnable() {
-                @Override
-                public void run() {
-                    boolean asSrc = mBackdrop.getVisibility() != View.VISIBLE;
-                    mScrimController.setDrawBehindAsSrc(asSrc);
-                    mStackScroller.setDrawBackgroundAsSrc(asSrc);
-                }
+            Runnable runnable = () -> {
+                boolean asSrc = mBackdrop.getVisibility() != View.VISIBLE;
+                mScrimController.setDrawBehindAsSrc(asSrc);
+                mStackScroller.setDrawBackgroundAsSrc(asSrc);
             };
             mBackdrop.setOnVisibilityChangedRunnable(runnable);
             runnable.run();
@@ -1169,11 +1102,11 @@
         if (container != null) {
             FragmentHostManager fragmentHostManager = FragmentHostManager.get(container);
             ExtensionFragmentListener.attachExtensonToFragment(container, QS.TAG, R.id.qs_frame,
-                    Dependency.get(ExtensionController.class).newExtension(QS.class)
+                    Dependency.get(ExtensionController.class)
+                            .newExtension(QS.class)
                             .withPlugin(QS.class)
-                            .withFeature(
-                                    PackageManager.FEATURE_AUTOMOTIVE, () -> new CarQSFragment())
-                            .withDefault(() -> new QSFragment())
+                            .withFeature(PackageManager.FEATURE_AUTOMOTIVE, CarQSFragment::new)
+                            .withDefault(QSFragment::new)
                             .build());
             final QSTileHost qsh = SystemUIFactory.getInstance().createQSTileHost(mContext, this,
                     mIconController);
@@ -1275,7 +1208,7 @@
      */
     protected View.OnTouchListener getStatusBarWindowTouchListener() {
         return (v, event) -> {
-            checkUserAutohide(v, event);
+            checkUserAutohide(event);
             checkRemoteInputOutside(event);
             if (event.getAction() == MotionEvent.ACTION_DOWN) {
                 if (mExpandedVisible) {
@@ -1379,8 +1312,7 @@
 
     public static SignalClusterView reinflateSignalCluster(View view) {
         Context context = view.getContext();
-        SignalClusterView signalCluster =
-                (SignalClusterView) view.findViewById(R.id.signal_cluster);
+        SignalClusterView signalCluster = view.findViewById(R.id.signal_cluster);
         if (signalCluster != null) {
             ViewParent parent = signalCluster.getParent();
             if (parent instanceof ViewGroup) {
@@ -1420,20 +1352,17 @@
 
         mDismissView = (DismissView) LayoutInflater.from(mContext).inflate(
                 R.layout.status_bar_notification_dismiss_all, mStackScroller, false);
-        mDismissView.setOnButtonClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                mMetricsLogger.action(MetricsEvent.ACTION_DISMISS_ALL_NOTES);
-                clearAllNotifications();
-            }
+        mDismissView.setOnButtonClickListener(v -> {
+            mMetricsLogger.action(MetricsEvent.ACTION_DISMISS_ALL_NOTES);
+            clearAllNotifications();
         });
         mStackScroller.setDismissView(mDismissView);
     }
 
     protected void createUserSwitcher() {
         mKeyguardUserSwitcher = new KeyguardUserSwitcher(mContext,
-                (ViewStub) mStatusBarWindow.findViewById(R.id.keyguard_user_switcher),
-                mKeyguardStatusBar, mNotificationPanel);
+                mStatusBarWindow.findViewById(R.id.keyguard_user_switcher), mKeyguardStatusBar,
+                mNotificationPanel);
     }
 
     protected void inflateStatusBarWindow(Context context) {
@@ -1446,7 +1375,7 @@
         // animate-swipe all dismissable notifications, then animate the shade closed
         int numChildren = mStackScroller.getChildCount();
 
-        final ArrayList<View> viewsToHide = new ArrayList<View>(numChildren);
+        final ArrayList<View> viewsToHide = new ArrayList<>(numChildren);
         final ArrayList<ExpandableNotificationRow> viewsToRemove = new ArrayList<>(numChildren);
         for (int i = 0; i < numChildren; i++) {
             final View child = mStackScroller.getChildAt(i);
@@ -1486,20 +1415,18 @@
             return;
         }
 
-        addPostCollapseAction(new Runnable() {
-            @Override
-            public void run() {
-                mStackScroller.setDismissAllInProgress(false);
-                for (ExpandableNotificationRow rowToRemove : viewsToRemove) {
-                    if (mStackScroller.canChildBeDismissed(rowToRemove)) {
-                        removeNotification(rowToRemove.getEntry().key, null);
-                    } else {
-                        rowToRemove.resetTranslation();
-                    }
+        addPostCollapseAction(() -> {
+            mStackScroller.setDismissAllInProgress(false);
+            for (ExpandableNotificationRow rowToRemove : viewsToRemove) {
+                if (mStackScroller.canChildBeDismissed(rowToRemove)) {
+                    removeNotification(rowToRemove.getEntry().key, null);
+                } else {
+                    rowToRemove.resetTranslation();
                 }
-                try {
-                    mBarService.onClearAllNotifications(mCurrentUserId);
-                } catch (Exception ex) { }
+            }
+            try {
+                mBarService.onClearAllNotifications(mCurrentUserId);
+            } catch (Exception ex) {
             }
         });
 
@@ -1508,13 +1435,15 @@
     }
 
     private void performDismissAllAnimations(ArrayList<View> hideAnimatedList) {
-        Runnable animationFinishAction = new Runnable() {
-            @Override
-            public void run() {
-                animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
-            }
+        Runnable animationFinishAction = () -> {
+            animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
         };
 
+        if (hideAnimatedList.isEmpty()) {
+            animationFinishAction.run();
+            return;
+        }
+
         // let's disable our normal animations
         mStackScroller.setDismissAllInProgress(true);
 
@@ -1631,10 +1560,6 @@
         SystemServicesProxy.getInstance(mContext).awakenDreamsAsync();
     }
 
-    public UserHandle getCurrentUserHandle() {
-        return new UserHandle(mCurrentUserId);
-    }
-
     public void addNotification(StatusBarNotification notification, RankingMap ranking)
             throws InflationException {
         String key = notification.getKey();
@@ -1745,7 +1670,7 @@
         boolean deferRemoval = false;
         abortExistingInflation(key);
         if (mHeadsUpManager.isHeadsUp(key)) {
-            // A cancel() in repsonse to a remote input shouldn't be delayed, as it makes the
+            // A cancel() in response to a remote input shouldn't be delayed, as it makes the
             // sending look longer than it takes.
             // Also we should not defer the removal if reordering isn't allowed since otherwise
             // some notifications can't disappear before the panel is closed.
@@ -1771,9 +1696,7 @@
                 newHistory = new CharSequence[1];
             } else {
                 newHistory = new CharSequence[oldHistory.length + 1];
-                for (int i = 0; i < oldHistory.length; i++) {
-                    newHistory[i + 1] = oldHistory[i];
-                }
+                System.arraycopy(oldHistory, 0, newHistory, 1, oldHistory.length);
             }
             newHistory[0] = String.valueOf(entry.remoteInputText);
             b.setRemoteInputHistory(newHistory);
@@ -1832,7 +1755,7 @@
             mStackScroller.cleanUpViewState(entry.row);
         }
         // Let's remove the children if this was a summary
-        handleGroupSummaryRemoved(key, ranking);
+        handleGroupSummaryRemoved(key);
         StatusBarNotification old = removeNotificationViews(key, ranking);
         if (SPEW) Log.d(TAG, "removeNotification key=" + key + " old=" + old);
 
@@ -1856,12 +1779,10 @@
      *
      * This also ensures that the animation looks nice and only consists of a single disappear
      * animation instead of multiple.
+     *  @param key the key of the notification was removed
      *
-     * @param key the key of the notification was removed
-     * @param ranking the current ranking
      */
-    private void handleGroupSummaryRemoved(String key,
-            RankingMap ranking) {
+    private void handleGroupSummaryRemoved(String key) {
         Entry entry = mNotificationData.get(key);
         if (entry != null && entry.row != null
                 && entry.row.isSummaryWithChildren()) {
@@ -1872,15 +1793,13 @@
             }
             List<ExpandableNotificationRow> notificationChildren =
                     entry.row.getNotificationChildren();
-            ArrayList<ExpandableNotificationRow> toRemove = new ArrayList<>();
             for (int i = 0; i < notificationChildren.size(); i++) {
                 ExpandableNotificationRow row = notificationChildren.get(i);
                 if ((row.getStatusBarNotification().getNotification().flags
                         & Notification.FLAG_FOREGROUND_SERVICE) != 0) {
-                    // the child is a forground service notification which we can't remove!
+                    // the child is a foreground service notification which we can't remove!
                     continue;
                 }
-                toRemove.add(row);
                 row.setKeepInParent(true);
                 // we need to set this state earlier as otherwise we might generate some weird
                 // animations
@@ -1900,7 +1819,9 @@
         final int id = n.getId();
         final int userId = n.getUserId();
         try {
-            mBarService.onNotificationClear(pkg, tag, id, userId);
+            // TODO: record actual dismissal surface
+            mBarService.onNotificationClear(pkg, tag, id, userId, n.getKey(),
+                    NotificationStats.DISMISSAL_OTHER);
             if (FORCE_REMOTE_INPUT_HISTORY
                     && mKeysKeptForRemoteInput.contains(n.getKey())) {
                 mKeysKeptForRemoteInput.remove(n.getKey());
@@ -1923,19 +1844,14 @@
 
         // Do not modify the notifications during collapse.
         if (isCollapsing()) {
-            addPostCollapseAction(new Runnable() {
-                @Override
-                public void run() {
-                    updateNotificationShade();
-                }
-            });
+            addPostCollapseAction(this::updateNotificationShade);
             return;
         }
 
         ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications();
         ArrayList<ExpandableNotificationRow> toShow = new ArrayList<>(activeNotifications.size());
         final int N = activeNotifications.size();
-        for (int i=0; i<N; i++) {
+        for (int i = 0; i < N; i++) {
             Entry ent = activeNotifications.get(i);
             if (ent.row.isDismissed() || ent.row.isRemoved()) {
                 // we don't want to update removed notifications because they could
@@ -1979,7 +1895,8 @@
 
         for (ExpandableNotificationRow remove : toRemove) {
             if (mGroupManager.isChildInGroupWithSummary(remove.getStatusBarNotification())) {
-                // we are only transfering this notification to its parent, don't generate an animation
+                // we are only transferring this notification to its parent, don't generate an
+                // animation
                 mStackScroller.setChildTransferInProgress(true);
             }
             if (remove.isSummaryWithChildren()) {
@@ -1991,7 +1908,7 @@
 
         removeNotificationChildren();
 
-        for (int i=0; i<toShow.size(); i++) {
+        for (int i = 0; i < toShow.size(); i++) {
             View v = toShow.get(i);
             if (v.getParent() == null) {
                 mVisualStabilityManager.notifyViewAddition(v);
@@ -2065,6 +1982,7 @@
         mNotificationPanel.setQsExpansionEnabled(isDeviceProvisioned()
                 && (mUserSetup || mUserSwitcherController == null
                         || !mUserSwitcherController.isSimpleUserSwitcher())
+                && ((mDisabled2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) == 0)
                 && ((mDisabled2 & StatusBarManager.DISABLE2_QUICK_SETTINGS) == 0)
                 && !mDozing
                 && !ONLY_CORE_APPS);
@@ -2101,7 +2019,7 @@
                 }
             }
 
-            // Finally after removing and adding has been beformed we can apply the order.
+            // Finally after removing and adding has been performed we can apply the order.
             orderChanged |= parent.applyChildOrder(orderedChildren, mVisualStabilityManager, this);
         }
         if (orderChanged) {
@@ -2274,10 +2192,11 @@
             MediaController controller = null;
             for (int i = 0; i < N; i++) {
                 final Entry entry = activeNotifications.get(i);
+
                 if (isMediaNotification(entry)) {
                     final MediaSession.Token token =
-                            entry.notification.getNotification().extras
-                            .getParcelable(Notification.EXTRA_MEDIA_SESSION);
+                            entry.notification.getNotification().extras.getParcelable(
+                                    Notification.EXTRA_MEDIA_SESSION);
                     if (token != null) {
                         MediaController aController = new MediaController(mContext, token);
                         if (PlaybackState.STATE_PLAYING ==
@@ -2315,7 +2234,7 @@
                                 if (entry.notification.getPackageName().equals(pkg)) {
                                     if (DEBUG_MEDIA) {
                                         Log.v(TAG, "DEBUG_MEDIA: found controller matching "
-                                            + entry.notification.getKey());
+                                                + entry.notification.getKey());
                                     }
                                     controller = aController;
                                     mediaNotification = entry;
@@ -2366,12 +2285,8 @@
     }
 
     private boolean isPlaybackActive(int state) {
-        if (state != PlaybackState.STATE_STOPPED
-                && state != PlaybackState.STATE_ERROR
-                && state != PlaybackState.STATE_NONE) {
-            return true;
-        }
-        return false;
+        return state != PlaybackState.STATE_STOPPED && state != PlaybackState.STATE_ERROR
+                && state != PlaybackState.STATE_NONE;
     }
 
     private void clearCurrentMediaNotification() {
@@ -2396,7 +2311,7 @@
     /**
      * Hide the album artwork that is fading out and release its bitmap.
      */
-    protected Runnable mHideBackdropFront = new Runnable() {
+    protected final Runnable mHideBackdropFront = new Runnable() {
         @Override
         public void run() {
             if (DEBUG_MEDIA) {
@@ -2548,14 +2463,11 @@
                             .setInterpolator(Interpolators.ACCELERATE_DECELERATE)
                             .setDuration(300)
                             .setStartDelay(0)
-                            .withEndAction(new Runnable() {
-                                @Override
-                                public void run() {
-                                    mBackdrop.setVisibility(View.GONE);
-                                    mBackdropFront.animate().cancel();
-                                    mBackdropBack.setImageDrawable(null);
-                                    mHandler.post(mHideBackdropFront);
-                                }
+                            .withEndAction(() -> {
+                                mBackdrop.setVisibility(View.GONE);
+                                mBackdropFront.animate().cancel();
+                                mBackdropBack.setImageDrawable(null);
+                                mHandler.post(mHideBackdropFront);
                             });
                     if (mKeyguardFadingAway) {
                         mBackdrop.animate()
@@ -2586,8 +2498,6 @@
     @Override
     public void disable(int state1, int state2, boolean animate) {
         animate &= mStatusBarWindowState != WINDOW_STATE_HIDDEN;
-        mDisabledUnmodified1 = state1;
-        mDisabledUnmodified2 = state2;
         final int old1 = mDisabled1;
         final int diff1 = state1 ^ old1;
         mDisabled1 = state1;
@@ -2623,8 +2533,13 @@
         flagdbg.append(0 != ((diff1  & StatusBarManager.DISABLE_CLOCK))                 ? '!' : ' ');
         flagdbg.append(0 != ((state1 & StatusBarManager.DISABLE_SEARCH))                ? 'S' : 's');
         flagdbg.append(0 != ((diff1  & StatusBarManager.DISABLE_SEARCH))                ? '!' : ' ');
+        flagdbg.append("> disable2<");
         flagdbg.append(0 != ((state2 & StatusBarManager.DISABLE2_QUICK_SETTINGS))       ? 'Q' : 'q');
         flagdbg.append(0 != ((diff2  & StatusBarManager.DISABLE2_QUICK_SETTINGS))       ? '!' : ' ');
+        flagdbg.append(0 != ((state2 & StatusBarManager.DISABLE2_SYSTEM_ICONS))         ? 'I' : 'i');
+        flagdbg.append(0 != ((diff2  & StatusBarManager.DISABLE2_SYSTEM_ICONS))         ? '!' : ' ');
+        flagdbg.append(0 != ((state2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE))   ? 'N' : 'n');
+        flagdbg.append(0 != ((diff2  & StatusBarManager.DISABLE2_NOTIFICATION_SHADE))   ? '!' : ' ');
         flagdbg.append('>');
         Log.d(TAG, flagdbg.toString());
 
@@ -2651,6 +2566,13 @@
         if ((diff2 & StatusBarManager.DISABLE2_QUICK_SETTINGS) != 0) {
             updateQsExpansionEnabled();
         }
+
+        if ((diff2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0) {
+            updateQsExpansionEnabled();
+            if ((state1 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0) {
+                animateCollapsePanels();
+            }
+        }
     }
 
     /**
@@ -2738,11 +2660,8 @@
                 // make sure that the window stays small for one frame until the touchableRegion is set.
                 mNotificationPanel.requestLayout();
                 mStatusBarWindowManager.setForceWindowCollapsed(true);
-                mNotificationPanel.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        mStatusBarWindowManager.setForceWindowCollapsed(false);
-                    }
+                mNotificationPanel.post(() -> {
+                    mStatusBarWindowManager.setForceWindowCollapsed(false);
                 });
             }
         } else {
@@ -2754,15 +2673,12 @@
                 // we need to keep the panel open artificially, let's wait until the animation
                 // is finished.
                 mHeadsUpManager.setHeadsUpGoingAway(true);
-                mStackScroller.runAfterAnimationFinished(new Runnable() {
-                    @Override
-                    public void run() {
-                        if (!mHeadsUpManager.hasPinnedHeadsUp()) {
-                            mStatusBarWindowManager.setHeadsUpShowing(false);
-                            mHeadsUpManager.setHeadsUpGoingAway(false);
-                        }
-                        removeRemoteInputEntriesKeptUntilCollapsed();
+                mStackScroller.runAfterAnimationFinished(() -> {
+                    if (!mHeadsUpManager.hasPinnedHeadsUp()) {
+                        mStatusBarWindowManager.setHeadsUpShowing(false);
+                        mHeadsUpManager.setHeadsUpGoingAway(false);
                     }
+                    removeRemoteInputEntriesKeptUntilCollapsed();
                 });
             }
         }
@@ -3013,7 +2929,9 @@
     }
 
     boolean panelsEnabled() {
-        return (mDisabled1 & StatusBarManager.DISABLE_EXPAND) == 0 && !ONLY_CORE_APPS;
+        return (mDisabled1 & StatusBarManager.DISABLE_EXPAND) == 0
+                && (mDisabled2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) == 0
+                && !ONLY_CORE_APPS;
     }
 
     void makeExpandedVisible(boolean force) {
@@ -3029,7 +2947,6 @@
         mStatusBarWindowManager.setPanelVisible(true);
 
         visibilityChanged(true);
-        mWaitingForKeyguardExit = false;
         recomputeDisableFlags(!force /* animate */);
         setInteracting(StatusBarManager.WINDOW_STATUS_BAR, true);
     }
@@ -3038,23 +2955,15 @@
         animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE);
     }
 
-    private final Runnable mAnimateCollapsePanels = new Runnable() {
-        @Override
-        public void run() {
-            animateCollapsePanels();
-        }
-    };
+    private final Runnable mAnimateCollapsePanels = this::animateCollapsePanels;
 
     public void postAnimateCollapsePanels() {
         mHandler.post(mAnimateCollapsePanels);
     }
 
     public void postAnimateForceCollapsePanels() {
-        mHandler.post(new Runnable() {
-            @Override
-            public void run() {
-                animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */);
-            }
+        mHandler.post(() -> {
+            animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */);
         });
     }
 
@@ -3104,6 +3013,9 @@
             }
         }
 
+        // TODO(b/62444020): remove when this bug is fixed
+        Log.v(TAG, "mStatusBarWindow: " + mStatusBarWindow + " canPanelBeCollapsed(): "
+                + mNotificationPanel.canPanelBeCollapsed());
         if (mStatusBarWindow != null && mNotificationPanel.canPanelBeCollapsed()) {
             // release focus immediately to kick off focus change transition
             mStatusBarWindowManager.setStatusBarFocusable(false);
@@ -3209,7 +3121,7 @@
 
         if (SPEW) {
             Log.d(TAG, "Touch: rawY=" + event.getRawY() + " event=" + event + " mDisabled1="
-                + mDisabled1 + " mDisabled2=" + mDisabled2 + " mTracking=" + mTracking);
+                    + mDisabled1 + " mDisabled2=" + mDisabled2);
         } else if (CHATTY) {
             if (event.getAction() != MotionEvent.ACTION_MOVE) {
                 Log.d(TAG, String.format(
@@ -3294,10 +3206,8 @@
 
             sbModeChanged = sbMode != -1;
             if (sbModeChanged && sbMode != mStatusBarMode) {
-                if (sbMode != mStatusBarMode) {
-                    mStatusBarMode = sbMode;
-                    checkBarModes();
-                }
+                mStatusBarMode = sbMode;
+                checkBarModes();
                 touchAutoHide();
             }
 
@@ -3321,7 +3231,6 @@
         } else {
             cancelAutohide();
         }
-        touchAutoDim();
     }
 
     protected int computeStatusBarMode(int oldVal, int newVal) {
@@ -3385,12 +3294,7 @@
         }
     }
 
-    private final Runnable mCheckBarModes = new Runnable() {
-        @Override
-        public void run() {
-            checkBarModes();
-        }
-    };
+    private final Runnable mCheckBarModes = this::checkBarModes;
 
     public void setInteracting(int barWindow, boolean interacting) {
         final boolean changing = ((mInteractingWindows & barWindow) != 0) != interacting;
@@ -3404,10 +3308,10 @@
         }
         // manually dismiss the volume panel when interacting with the nav bar
         if (changing && interacting && barWindow == StatusBarManager.WINDOW_NAVIGATION_BAR) {
+            touchAutoDim();
             dismissVolumeDialog();
         }
         checkBarModes();
-        touchAutoDim();
     }
 
     private void dismissVolumeDialog() {
@@ -3449,7 +3353,7 @@
         }
     }
 
-    void checkUserAutohide(View v, MotionEvent event) {
+    void checkUserAutohide(MotionEvent event) {
         if ((mSystemUiVisibility & STATUS_OR_NAV_TRANSIENT) != 0  // a transient bar is revealed
                 && event.getAction() == MotionEvent.ACTION_OUTSIDE // touch outside the source bar
                 && event.getX() == 0 && event.getY() == 0  // a touch outside both bars
@@ -3516,9 +3420,7 @@
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         synchronized (mQueueLock) {
             pw.println("Current Status Bar state:");
-            pw.println("  mExpandedVisible=" + mExpandedVisible
-                    + ", mTrackingPosition=" + mTrackingPosition);
-            pw.println("  mTracking=" + mTracking);
+            pw.println("  mExpandedVisible=" + mExpandedVisible);
             pw.println("  mDisplayMetrics=" + mDisplayMetrics);
             pw.println("  mStackScroller: " + viewInfo(mStackScroller));
             pw.println("  mStackScroller: " + viewInfo(mStackScroller)
@@ -3606,16 +3508,12 @@
             if (false) {
                 pw.println("see the logcat for a dump of the views we have created.");
                 // must happen on ui thread
-                mHandler.post(new Runnable() {
-                        @Override
-                        public void run() {
-                            mStatusBarView.getLocationOnScreen(mAbsPos);
-                            Log.d(TAG, "mStatusBarView: ----- (" + mAbsPos[0] + "," + mAbsPos[1]
-                                    + ") " + mStatusBarView.getWidth() + "x"
-                                    + getStatusBarHeight());
-                            mStatusBarView.debug();
-                        }
-                    });
+                mHandler.post(() -> {
+                    mStatusBarView.getLocationOnScreen(mAbsPos);
+                    Log.d(TAG, "mStatusBarView: ----- (" + mAbsPos[0] + "," + mAbsPos[1] +
+                            ") " + mStatusBarView.getWidth() + "x" + getStatusBarHeight());
+                    mStatusBarView.debug();
+                });
             }
         }
 
@@ -3695,49 +3593,43 @@
 
         final boolean afterKeyguardGone = PreviewInflater.wouldLaunchResolverActivity(
                 mContext, intent, mCurrentUserId);
-        Runnable runnable = new Runnable() {
-            @Override
-            public void run() {
-                mAssistManager.hideAssist();
-                intent.setFlags(
-                        Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
-                int result = ActivityManager.START_CANCELED;
-                ActivityOptions options = new ActivityOptions(getActivityOptions());
-                options.setDisallowEnterPictureInPictureWhileLaunching(
-                        disallowEnterPictureInPictureWhileLaunching);
-                if (intent == KeyguardBottomAreaView.INSECURE_CAMERA_INTENT) {
-                    // Normally an activity will set it's requested rotation
-                    // animation on its window. However when launching an activity
-                    // causes the orientation to change this is too late. In these cases
-                    // the default animation is used. This doesn't look good for
-                    // the camera (as it rotates the camera contents out of sync
-                    // with physical reality). So, we ask the WindowManager to
-                    // force the crossfade animation if an orientation change
-                    // happens to occur during the launch.
-                    options.setRotationAnimationHint(
-                            WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS);
-                }
-                try {
-                    result = ActivityManager.getService().startActivityAsUser(
-                            null, mContext.getBasePackageName(),
-                            intent,
-                            intent.resolveTypeIfNeeded(mContext.getContentResolver()),
-                            null, null, 0, Intent.FLAG_ACTIVITY_NEW_TASK, null,
-                            options.toBundle(), UserHandle.CURRENT.getIdentifier());
-                } catch (RemoteException e) {
-                    Log.w(TAG, "Unable to start activity", e);
-                }
-                if (callback != null) {
-                    callback.onActivityStarted(result);
-                }
+        Runnable runnable = () -> {
+            mAssistManager.hideAssist();
+            intent.setFlags(
+                    Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+            int result = ActivityManager.START_CANCELED;
+            ActivityOptions options = new ActivityOptions(getActivityOptions());
+            options.setDisallowEnterPictureInPictureWhileLaunching(
+                    disallowEnterPictureInPictureWhileLaunching);
+            if (intent == KeyguardBottomAreaView.INSECURE_CAMERA_INTENT) {
+                // Normally an activity will set it's requested rotation
+                // animation on its window. However when launching an activity
+                // causes the orientation to change this is too late. In these cases
+                // the default animation is used. This doesn't look good for
+                // the camera (as it rotates the camera contents out of sync
+                // with physical reality). So, we ask the WindowManager to
+                // force the crossfade animation if an orientation change
+                // happens to occur during the launch.
+                options.setRotationAnimationHint(
+                        WindowManager.LayoutParams.ROTATION_ANIMATION_SEAMLESS);
+            }
+            try {
+                result = ActivityManager.getService().startActivityAsUser(
+                        null, mContext.getBasePackageName(),
+                        intent,
+                        intent.resolveTypeIfNeeded(mContext.getContentResolver()),
+                        null, null, 0, Intent.FLAG_ACTIVITY_NEW_TASK, null,
+                        options.toBundle(), UserHandle.CURRENT.getIdentifier());
+            } catch (RemoteException e) {
+                Log.w(TAG, "Unable to start activity", e);
+            }
+            if (callback != null) {
+                callback.onActivityStarted(result);
             }
         };
-        Runnable cancelRunnable = new Runnable() {
-            @Override
-            public void run() {
-                if (callback != null) {
-                    callback.onActivityStarted(ActivityManager.START_CANCELED);
-                }
+        Runnable cancelRunnable = () -> {
+            if (callback != null) {
+                callback.onActivityStarted(ActivityManager.START_CANCELED);
             }
         };
         executeRunnableDismissingKeyguard(runnable, cancelRunnable, dismissShade,
@@ -3782,7 +3674,7 @@
         }, cancelAction, afterKeyguardGone);
     }
 
-    private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
             if (DEBUG) Log.v(TAG, "onReceive: " + intent);
@@ -3811,7 +3703,7 @@
         }
     };
 
-    private BroadcastReceiver mDemoReceiver = new BroadcastReceiver() {
+    private final BroadcastReceiver mDemoReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
             if (DEBUG) Log.v(TAG, "onReceive: " + intent);
@@ -3972,7 +3864,6 @@
      * The LEDs are turned off when the notification panel is shown, even just a little bit.
      * See also StatusBar.setPanelExpanded for another place where we attempt to do this.
      */
-    // Old BaseStatusBar.handleVisibileToUserChanged
     private void handleVisibleToUserChangedImpl(boolean visibleToUser) {
         try {
             if (visibleToUser) {
@@ -3997,8 +3888,8 @@
         // Report all notifications as invisible and turn down the
         // reporter.
         if (!mCurrentlyVisibleNotifications.isEmpty()) {
-            logNotificationVisibilityChanges(Collections.<NotificationVisibility>emptyList(),
-                    mCurrentlyVisibleNotifications);
+            logNotificationVisibilityChanges(
+                    Collections.emptyList(), mCurrentlyVisibleNotifications);
             recycleAllVisibilityObjects(mCurrentlyVisibleNotifications);
         }
         mHandler.removeCallbacks(mVisibilityReporter);
@@ -4105,7 +3996,7 @@
         vib.vibrate(250, VIBRATION_ATTRIBUTES);
     }
 
-    Runnable mStartTracing = new Runnable() {
+    final Runnable mStartTracing = new Runnable() {
         @Override
         public void run() {
             vibrate();
@@ -4116,13 +4007,10 @@
         }
     };
 
-    Runnable mStopTracing = new Runnable() {
-        @Override
-        public void run() {
-            android.os.Debug.stopMethodTracing();
-            Log.d(TAG, "stopTracing");
-            vibrate();
-        }
+    final Runnable mStopTracing = () -> {
+        android.os.Debug.stopMethodTracing();
+        Log.d(TAG, "stopTracing");
+        vibrate();
     };
 
     @Override
@@ -4149,40 +4037,6 @@
         startActivityDismissingKeyguard(intent, onlyProvisioned, true /* dismissShade */);
     }
 
-    private static class FastColorDrawable extends Drawable {
-        private final int mColor;
-
-        public FastColorDrawable(int color) {
-            mColor = 0xff000000 | color;
-        }
-
-        @Override
-        public void draw(Canvas canvas) {
-            canvas.drawColor(mColor, PorterDuff.Mode.SRC);
-        }
-
-        @Override
-        public void setAlpha(int alpha) {
-        }
-
-        @Override
-        public void setColorFilter(ColorFilter colorFilter) {
-        }
-
-        @Override
-        public int getOpacity() {
-            return PixelFormat.OPAQUE;
-        }
-
-        @Override
-        public void setBounds(int left, int top, int right, int bottom) {
-        }
-
-        @Override
-        public void setBounds(Rect bounds) {
-        }
-    }
-
     public void destroy() {
         // Begin old BaseStatusBar.destroy().
         mContext.unregisterReceiver(mBaseBroadcastReceiver);
@@ -4400,31 +4254,23 @@
             Runnable endRunnable) {
         mHandler.removeMessages(MSG_LAUNCH_TRANSITION_TIMEOUT);
         mLaunchTransitionEndRunnable = endRunnable;
-        Runnable hideRunnable = new Runnable() {
-            @Override
-            public void run() {
-                mLaunchTransitionFadingAway = true;
-                if (beforeFading != null) {
-                    beforeFading.run();
-                }
-                mScrimController.forceHideScrims(true /* hide */, false /* animated */);
-                updateMediaMetaData(false, true);
-                mNotificationPanel.setAlpha(1);
-                mStackScroller.setParentNotFullyVisible(true);
-                mNotificationPanel.animate()
-                        .alpha(0)
-                        .setStartDelay(FADE_KEYGUARD_START_DELAY)
-                        .setDuration(FADE_KEYGUARD_DURATION)
-                        .withLayer()
-                        .withEndAction(new Runnable() {
-                            @Override
-                            public void run() {
-                                onLaunchTransitionFadingEnded();
-                            }
-                        });
-                mCommandQueue.appTransitionStarting(SystemClock.uptimeMillis(),
-                        LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION, true);
+        Runnable hideRunnable = () -> {
+            mLaunchTransitionFadingAway = true;
+            if (beforeFading != null) {
+                beforeFading.run();
             }
+            mScrimController.forceHideScrims(true /* hide */, false /* animated */);
+            updateMediaMetaData(false, true);
+            mNotificationPanel.setAlpha(1);
+            mStackScroller.setParentNotFullyVisible(true);
+            mNotificationPanel.animate()
+                    .alpha(0)
+                    .setStartDelay(FADE_KEYGUARD_START_DELAY)
+                    .setDuration(FADE_KEYGUARD_DURATION)
+                    .withLayer()
+                    .withEndAction(this::onLaunchTransitionFadingEnded);
+            mCommandQueue.appTransitionStarting(SystemClock.uptimeMillis(),
+                    LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION, true);
         };
         if (mNotificationPanel.isLaunchTransitionRunning()) {
             mNotificationPanel.setLaunchTransitionEndRunnable(hideRunnable);
@@ -4553,7 +4399,6 @@
 
         // Treat Keyguard exit animation as an app transition to achieve nice transition for status
         // bar.
-        mKeyguardGoingAway = true;
         mKeyguardMonitor.notifyKeyguardGoingAway(true);
         mCommandQueue.appTransitionPending(true);
     }
@@ -4562,14 +4407,13 @@
      * Notifies the status bar the Keyguard is fading away with the specified timings.
      *
      * @param startTime the start time of the animations in uptime millis
-     * @param delay the precalculated animation delay in miliseconds
+     * @param delay the precalculated animation delay in milliseconds
      * @param fadeoutDuration the duration of the exit animation, in milliseconds
      */
     public void setKeyguardFadingAway(long startTime, long delay, long fadeoutDuration) {
         mKeyguardFadingAway = true;
         mKeyguardFadingAwayDelay = delay;
         mKeyguardFadingAwayDuration = fadeoutDuration;
-        mWaitingForKeyguardExit = false;
         mCommandQueue.appTransitionStarting(startTime + fadeoutDuration
                         - LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION,
                 LightBarTransitionsController.DEFAULT_TINT_ANIMATION_DURATION, true);
@@ -4589,14 +4433,9 @@
      */
     public void finishKeyguardFadingAway() {
         mKeyguardFadingAway = false;
-        mKeyguardGoingAway = false;
         mKeyguardMonitor.notifyKeyguardDoneFading();
     }
 
-    public void stopWaitingForKeyguardExit() {
-        mWaitingForKeyguardExit = false;
-    }
-
     private void updatePublicMode() {
         final boolean showingKeyguard = mStatusBarKeyguardViewManager.isShowing();
         final boolean devicePublic = showingKeyguard
@@ -4810,7 +4649,6 @@
     }
 
     protected void showBouncer() {
-        mWaitingForKeyguardExit = mStatusBarKeyguardViewManager.isShowing();
         mStatusBarKeyguardViewManager.dismiss();
     }
 
@@ -4827,7 +4665,7 @@
 
     @Override
     public void onActivated(ActivatableNotificationView view) {
-        onActivated((View)view);
+        onActivated((View) view);
         mStackScroller.setActivatedChild(view);
     }
 
@@ -5022,6 +4860,10 @@
      * @param expandView The view to expand after going to the shade.
      */
     public void goToLockedShade(View expandView) {
+        if ((mDisabled2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0) {
+            return;
+        }
+
         int userId = mCurrentUserId;
         ExpandableNotificationRow row = null;
         if (expandView instanceof ExpandableNotificationRow) {
@@ -5129,51 +4971,41 @@
         updateNotifications();
         if (mPendingWorkRemoteInputView != null && !isAnyProfilePublicMode()) {
             // Expand notification panel and the notification row, then click on remote input view
-            final Runnable clickPendingViewRunnable = new Runnable() {
-                @Override
-                public void run() {
-                    final View pendingWorkRemoteInputView = mPendingWorkRemoteInputView;
-                    if (pendingWorkRemoteInputView == null) {
+            final Runnable clickPendingViewRunnable = () -> {
+                final View pendingWorkRemoteInputView = mPendingWorkRemoteInputView;
+                if (pendingWorkRemoteInputView == null) {
+                    return;
+                }
+
+                // Climb up the hierarchy until we get to the container for this row.
+                ViewParent p = pendingWorkRemoteInputView.getParent();
+                while (!(p instanceof ExpandableNotificationRow)) {
+                    if (p == null) {
                         return;
                     }
+                    p = p.getParent();
+                }
 
-                    // Climb up the hierarchy until we get to the container for this row.
-                    ViewParent p = pendingWorkRemoteInputView.getParent();
-                    while (!(p instanceof ExpandableNotificationRow)) {
-                        if (p == null) {
-                            return;
+                final ExpandableNotificationRow row = (ExpandableNotificationRow) p;
+                ViewParent viewParent = row.getParent();
+                if (viewParent instanceof NotificationStackScrollLayout) {
+                    final NotificationStackScrollLayout scrollLayout =
+                            (NotificationStackScrollLayout) viewParent;
+                    row.makeActionsVisibile();
+                    row.post(() -> {
+                        final Runnable finishScrollingCallback = () -> {
+                            mPendingWorkRemoteInputView.callOnClick();
+                            mPendingWorkRemoteInputView = null;
+                            scrollLayout.setFinishScrollingCallback(null);
+                        };
+                        if (scrollLayout.scrollTo(row)) {
+                            // It scrolls! So call it when it's finished.
+                            scrollLayout.setFinishScrollingCallback(finishScrollingCallback);
+                        } else {
+                            // It does not scroll, so call it now!
+                            finishScrollingCallback.run();
                         }
-                        p = p.getParent();
-                    }
-
-                    final ExpandableNotificationRow row = (ExpandableNotificationRow) p;
-                    ViewParent viewParent = row.getParent();
-                    if (viewParent instanceof NotificationStackScrollLayout) {
-                        final NotificationStackScrollLayout scrollLayout =
-                                (NotificationStackScrollLayout) viewParent;
-                        row.makeActionsVisibile();
-                        row.post(new Runnable() {
-                            @Override
-                            public void run() {
-                                final Runnable finishScrollingCallback = new Runnable() {
-                                    @Override
-                                    public void run() {
-                                        mPendingWorkRemoteInputView.callOnClick();
-                                        mPendingWorkRemoteInputView = null;
-                                        scrollLayout.setFinishScrollingCallback(null);
-                                    }
-                                };
-                                if (scrollLayout.scrollTo(row)) {
-                                    // It scrolls! So call it when it's finished.
-                                    scrollLayout.setFinishScrollingCallback(
-                                            finishScrollingCallback);
-                                } else {
-                                    // It does not scroll, so call it now!
-                                    finishScrollingCallback.run();
-                                }
-                            }
-                        });
-                    }
+                    });
                 }
             };
             mNotificationPanel.getViewTreeObserver().addOnGlobalLayoutListener(
@@ -5236,7 +5068,7 @@
         }
     }
 
-    WakefulnessLifecycle.Observer mWakefulnessObserver = new WakefulnessLifecycle.Observer() {
+    final WakefulnessLifecycle.Observer mWakefulnessObserver = new WakefulnessLifecycle.Observer() {
         @Override
         public void onFinishedGoingToSleep() {
             mNotificationPanel.onAffordanceLaunchEnded();
@@ -5259,12 +5091,7 @@
 
                 // This gets executed before we will show Keyguard, so post it in order that the state
                 // is correct.
-                mHandler.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        onCameraLaunchGestureDetected(mLastCameraLaunchSource);
-                    }
-                });
+                mHandler.post(() -> onCameraLaunchGestureDetected(mLastCameraLaunchSource));
             }
             updateIsKeyguard();
         }
@@ -5290,7 +5117,7 @@
         }
     };
 
-    ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() {
+    final ScreenLifecycle.Observer mScreenObserver = new ScreenLifecycle.Observer() {
         @Override
         public void onScreenTurningOn() {
             mFalsingManager.onScreenTurningOn();
@@ -5487,7 +5314,7 @@
     }
 
     private final class DozeServiceHost implements DozeHost {
-        private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>();
+        private final ArrayList<Callback> mCallbacks = new ArrayList<>();
         private boolean mAnimateWakeup;
         private boolean mIgnoreTouchWhilePulsing;
 
@@ -5700,7 +5527,7 @@
     protected NotificationData mNotificationData;
     protected NotificationStackScrollLayout mStackScroller;
 
-    protected NotificationGroupManager mGroupManager = new NotificationGroupManager();
+    protected final NotificationGroupManager mGroupManager = new NotificationGroupManager();
 
     protected RemoteInputController mRemoteInputController;
 
@@ -5710,34 +5537,30 @@
     private AboveShelfObserver mAboveShelfObserver;
 
     // handling reordering
-    protected VisualStabilityManager mVisualStabilityManager = new VisualStabilityManager();
+    protected final VisualStabilityManager mVisualStabilityManager = new VisualStabilityManager();
 
     protected int mCurrentUserId = 0;
-    final protected SparseArray<UserInfo> mCurrentProfiles = new SparseArray<UserInfo>();
+    final protected SparseArray<UserInfo> mCurrentProfiles = new SparseArray<>();
 
-    protected int mLayoutDirection = -1; // invalid
     protected AccessibilityManager mAccessibilityManager;
 
     protected boolean mDeviceInteractive;
 
     protected boolean mVisible;
-    protected ArraySet<Entry> mHeadsUpEntriesToRemoveOnSwitch = new ArraySet<>();
-    protected ArraySet<Entry> mRemoteInputEntriesToRemoveOnCollapse = new ArraySet<>();
+    protected final ArraySet<Entry> mHeadsUpEntriesToRemoveOnSwitch = new ArraySet<>();
+    protected final ArraySet<Entry> mRemoteInputEntriesToRemoveOnCollapse = new ArraySet<>();
 
     /**
      * Notifications with keys in this set are not actually around anymore. We kept them around
      * when they were canceled in response to a remote input interaction. This allows us to show
      * what you replied and allows you to continue typing into it.
      */
-    protected ArraySet<String> mKeysKeptForRemoteInput = new ArraySet<>();
+    protected final ArraySet<String> mKeysKeptForRemoteInput = new ArraySet<>();
 
     // mScreenOnFromKeyguard && mVisible.
     private boolean mVisibleToUser;
 
-    private Locale mLocale;
-
     protected boolean mUseHeadsUp = false;
-    protected boolean mHeadsUpTicker = false;
     protected boolean mDisableNotificationAlerts = false;
 
     protected DevicePolicyManager mDevicePolicyManager;
@@ -5772,13 +5595,11 @@
     private NotificationGuts mNotificationGutsExposed;
     private MenuItem mGutsMenuItem;
 
-    private KeyboardShortcuts mKeyboardShortcuts;
-
     protected NotificationShelf mNotificationShelf;
     protected DismissView mDismissView;
     protected EmptyShadeView mEmptyShadeView;
 
-    private NotificationClicker mNotificationClicker = new NotificationClicker();
+    private final NotificationClicker mNotificationClicker = new NotificationClicker();
 
     protected AssistManager mAssistManager;
 
@@ -5838,15 +5659,14 @@
         }
     };
 
-    private RemoteViews.OnClickHandler mOnClickHandler = new RemoteViews.OnClickHandler() {
+    private final RemoteViews.OnClickHandler mOnClickHandler = new RemoteViews.OnClickHandler() {
 
         @Override
         public boolean onClickHandler(
                 final View view, final PendingIntent pendingIntent, final Intent fillInIntent) {
             wakeUpIfDozing(SystemClock.uptimeMillis(), view);
 
-
-            if (handleRemoteInput(view, pendingIntent, fillInIntent)) {
+            if (handleRemoteInput(view, pendingIntent)) {
                 return true;
             }
 
@@ -5864,33 +5684,29 @@
             }
             final boolean isActivity = pendingIntent.isActivity();
             if (isActivity) {
-                final boolean keyguardShowing = mStatusBarKeyguardViewManager.isShowing();
                 final boolean afterKeyguardGone = PreviewInflater.wouldLaunchResolverActivity(
                         mContext, pendingIntent.getIntent(), mCurrentUserId);
-                dismissKeyguardThenExecute(new OnDismissAction() {
-                    @Override
-                    public boolean onDismiss() {
-                        try {
-                            ActivityManager.getService().resumeAppSwitches();
-                        } catch (RemoteException e) {
-                        }
-
-                        boolean handled = superOnClickHandler(view, pendingIntent, fillInIntent);
-
-                        // close the shade if it was open
-                        if (handled && !mNotificationPanel.isFullyCollapsed()) {
-                            animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
-                                    true /* force */);
-                            visibilityChanged(false);
-                            mAssistManager.hideAssist();
-
-                            // Wait for activity start.
-                            return true;
-                        } else {
-                            return false;
-                        }
-
+                dismissKeyguardThenExecute(() -> {
+                    try {
+                        ActivityManager.getService().resumeAppSwitches();
+                    } catch (RemoteException e) {
                     }
+
+                    boolean handled = superOnClickHandler(view, pendingIntent, fillInIntent);
+
+                    // close the shade if it was open
+                    if (handled && !mNotificationPanel.isFullyCollapsed()) {
+                        animateCollapsePanels(
+                                CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */);
+                        visibilityChanged(false);
+                        mAssistManager.hideAssist();
+
+                        // Wait for activity start.
+                        return true;
+                    } else {
+                        return false;
+                    }
+
                 }, afterKeyguardGone);
                 return true;
             } else {
@@ -5932,10 +5748,15 @@
         private boolean superOnClickHandler(View view, PendingIntent pendingIntent,
                 Intent fillInIntent) {
             return super.onClickHandler(view, pendingIntent, fillInIntent,
-                    StackId.FULLSCREEN_WORKSPACE_STACK_ID);
+                    WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
         }
 
-        private boolean handleRemoteInput(View view, PendingIntent pendingIntent, Intent fillInIntent) {
+        private boolean handleRemoteInput(View view, PendingIntent pendingIntent) {
+            if ((mDisabled2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0) {
+                // Skip remote input as doing so will expand the notification shade.
+                return true;
+            }
+
             Object tag = view.getTag(com.android.internal.R.id.remote_input_tag);
             RemoteInput[] inputs = null;
             if (tag instanceof RemoteInput[]) {
@@ -6050,7 +5871,7 @@
             if (Intent.ACTION_USER_SWITCHED.equals(action)) {
                 mCurrentUserId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
                 updateCurrentProfilesCache();
-                if (true) Log.v(TAG, "userId " + mCurrentUserId + " is in the house");
+                Log.v(TAG, "userId " + mCurrentUserId + " is in the house");
 
                 updateLockscreenNotificationSetting();
 
@@ -6073,8 +5894,7 @@
                         Toast toast = Toast.makeText(mContext,
                                 R.string.managed_profile_foreground_toast,
                                 Toast.LENGTH_SHORT);
-                        TextView text = (TextView) toast.getView().findViewById(
-                                android.R.id.message);
+                        TextView text = toast.getView().findViewById(android.R.id.message);
                         text.setCompoundDrawablesRelativeWithIntrinsicBounds(
                                 R.drawable.stat_sys_managed_profile_status, 0, 0, 0);
                         int paddingPx = mContext.getResources().getDimensionPixelSize(
@@ -6150,15 +5970,12 @@
                 return;
             }
             final RankingMap currentRanking = getCurrentRanking();
-            mHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    for (StatusBarNotification sbn : notifications) {
-                        try {
-                            addNotification(sbn, currentRanking);
-                        } catch (InflationException e) {
-                            handleInflationException(sbn, e);
-                        }
+            mHandler.post(() -> {
+                for (StatusBarNotification sbn : notifications) {
+                    try {
+                        addNotification(sbn, currentRanking);
+                    } catch (InflationException e) {
+                        handleInflationException(sbn, e);
                     }
                 }
             });
@@ -6169,40 +5986,37 @@
                 final RankingMap rankingMap) {
             if (DEBUG) Log.d(TAG, "onNotificationPosted: " + sbn);
             if (sbn != null && !onPluginNotificationPosted(sbn, rankingMap)) {
-                mHandler.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        processForRemoteInput(sbn.getNotification());
-                        String key = sbn.getKey();
-                        mKeysKeptForRemoteInput.remove(key);
-                        boolean isUpdate = mNotificationData.get(key) != null;
-                        // In case we don't allow child notifications, we ignore children of
-                        // notifications that have a summary, since we're not going to show them
-                        // anyway. This is true also when the summary is canceled,
-                        // because children are automatically canceled by NoMan in that case.
-                        if (!ENABLE_CHILD_NOTIFICATIONS
+                mHandler.post(() -> {
+                    processForRemoteInput(sbn.getNotification());
+                    String key = sbn.getKey();
+                    mKeysKeptForRemoteInput.remove(key);
+                    boolean isUpdate = mNotificationData.get(key) != null;
+                    // In case we don't allow child notifications, we ignore children of
+                    // notifications that have a summary, since we're not going to show them
+                    // anyway. This is true also when the summary is canceled,
+                    // because children are automatically canceled by NoMan in that case.
+                    if (!ENABLE_CHILD_NOTIFICATIONS
                             && mGroupManager.isChildInGroupWithSummary(sbn)) {
-                            if (DEBUG) {
-                                Log.d(TAG, "Ignoring group child due to existing summary: " + sbn);
-                            }
+                        if (DEBUG) {
+                            Log.d(TAG, "Ignoring group child due to existing summary: " + sbn);
+                        }
 
-                            // Remove existing notification to avoid stale data.
-                            if (isUpdate) {
-                                removeNotification(key, rankingMap);
-                            } else {
-                                mNotificationData.updateRanking(rankingMap);
-                            }
-                            return;
+                        // Remove existing notification to avoid stale data.
+                        if (isUpdate) {
+                            removeNotification(key, rankingMap);
+                        } else {
+                            mNotificationData.updateRanking(rankingMap);
                         }
-                        try {
-                            if (isUpdate) {
-                                updateNotification(sbn, rankingMap);
-                            } else {
-                                addNotification(sbn, rankingMap);
-                            }
-                        } catch (InflationException e) {
-                            handleInflationException(sbn, e);
+                        return;
+                    }
+                    try {
+                        if (isUpdate) {
+                            updateNotification(sbn, rankingMap);
+                        } else {
+                            addNotification(sbn, rankingMap);
                         }
+                    } catch (InflationException e) {
+                        handleInflationException(sbn, e);
                     }
                 });
             }
@@ -6250,7 +6064,7 @@
                         Settings.Secure.SHOW_NOTE_ABOUT_NOTIFICATION_HIDING, 0);
                 return;
             }
-            Log.d(TAG, "disabling lockecreen notifications and alerting the user");
+            Log.d(TAG, "disabling lockscreen notifications and alerting the user");
             // disable lockscreen notifications until user acts on the banner.
             Settings.Secure.putInt(mContext.getContentResolver(),
                     Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 0);
@@ -6291,11 +6105,10 @@
 
     @Override  // NotificationData.Environment
     public boolean isNotificationForCurrentProfiles(StatusBarNotification n) {
-        final int thisUserId = mCurrentUserId;
         final int notificationUserId = n.getUserId();
         if (DEBUG && MULTIUSER_DEBUG) {
-            Log.v(TAG, String.format("%s: current userid: %d, notification userid: %d",
-                    n, thisUserId, notificationUserId));
+            Log.v(TAG, String.format("%s: current userid: %d, notification userid: %d", n,
+                    mCurrentUserId, notificationUserId));
         }
         return isCurrentProfile(notificationUserId);
     }
@@ -6343,21 +6156,15 @@
     }
 
     private void startNotificationGutsIntent(final Intent intent, final int appUid) {
-        dismissKeyguardThenExecute(new OnDismissAction() {
-            @Override
-            public boolean onDismiss() {
-                AsyncTask.execute(new Runnable() {
-                    @Override
-                    public void run() {
-                        TaskStackBuilder.create(mContext)
-                                .addNextIntentWithParentStack(intent)
-                                .startActivities(getActivityOptions(),
-                                        new UserHandle(UserHandle.getUserId(appUid)));
-                    }
-                });
-                animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */);
-                return true;
-            }
+        dismissKeyguardThenExecute(() -> {
+            AsyncTask.execute(() -> {
+                TaskStackBuilder.create(mContext)
+                        .addNextIntentWithParentStack(intent)
+                        .startActivities(getActivityOptions(),
+                                new UserHandle(UserHandle.getUserId(appUid)));
+            });
+            animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */);
+            return true;
         }, false /* afterKeyguardGone */);
     }
 
@@ -6428,7 +6235,7 @@
                 startNotificationGutsIntent(intent, sbn.getUid());
             };
             final View.OnClickListener onDoneClick = (View v) -> {
-                saveAndCloseNotificationMenu(info, row, guts, v);
+                saveAndCloseNotificationMenu(row, guts, v);
             };
             final NotificationInfo.CheckSaveListener checkSaveListener =
                     (Runnable saveImportance) -> {
@@ -6445,7 +6252,7 @@
                 }
             };
 
-            ArraySet<NotificationChannel> channels = new ArraySet<NotificationChannel>();
+            ArraySet<NotificationChannel> channels = new ArraySet<>();
             channels.add(row.getEntry().channel);
             if (row.isSummaryWithChildren()) {
                 // If this is a summary, then add in the children notification channels for the
@@ -6473,7 +6280,7 @@
         }
     }
 
-    private void saveAndCloseNotificationMenu(NotificationInfo info,
+    private void saveAndCloseNotificationMenu(
             ExpandableNotificationRow row, NotificationGuts guts, View done) {
         guts.resetFalsingCheck();
         int[] rowLocation = new int[2];
@@ -6642,13 +6449,6 @@
         updateHideIconsForBouncer(true /* animate */);
     }
 
-    protected void sendCloseSystemWindows(String reason) {
-        try {
-            ActivityManager.getService().closeSystemDialogs(reason);
-        } catch (RemoteException e) {
-        }
-    }
-
     protected void toggleKeyboardShortcuts(int deviceId) {
         KeyboardShortcuts.toggle(mContext, deviceId);
     }
@@ -6750,18 +6550,6 @@
         return isLockscreenPublicMode(userId);
     }
 
-    public void onNotificationClear(StatusBarNotification notification) {
-        try {
-            mBarService.onNotificationClear(
-                    notification.getPackageName(),
-                    notification.getTag(),
-                    notification.getId(),
-                    notification.getUserId());
-        } catch (android.os.RemoteException ex) {
-            // oh well
-        }
-    }
-
     /**
      * Called when the notification panel layouts
      */
@@ -6919,49 +6707,42 @@
     public void startPendingIntentDismissingKeyguard(final PendingIntent intent) {
         if (!isDeviceProvisioned()) return;
 
-        final boolean keyguardShowing = mStatusBarKeyguardViewManager.isShowing();
         final boolean afterKeyguardGone = intent.isActivity()
                 && PreviewInflater.wouldLaunchResolverActivity(mContext, intent.getIntent(),
                 mCurrentUserId);
-        dismissKeyguardThenExecute(new OnDismissAction() {
-            @Override
-            public boolean onDismiss() {
-                new Thread() {
-                    @Override
-                    public void run() {
-                        try {
-                            // The intent we are sending is for the application, which
-                            // won't have permission to immediately start an activity after
-                            // the user switches to home.  We know it is safe to do at this
-                            // point, so make sure new activity switches are now allowed.
-                            ActivityManager.getService().resumeAppSwitches();
-                        } catch (RemoteException e) {
-                        }
-                        try {
-                            intent.send(null, 0, null, null, null, null, getActivityOptions());
-                        } catch (PendingIntent.CanceledException e) {
-                            // the stack trace isn't very helpful here.
-                            // Just log the exception message.
-                            Log.w(TAG, "Sending intent failed: " + e);
-
-                            // TODO: Dismiss Keyguard.
-                        }
-                        if (intent.isActivity()) {
-                            mAssistManager.hideAssist();
-                        }
-                    }
-                }.start();
-
-                if (!mNotificationPanel.isFullyCollapsed()) {
-                    // close the shade if it was open
-                    animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
-                            true /* force */, true /* delayed */);
-                    visibilityChanged(false);
-
-                    return true;
-                } else {
-                    return false;
+        dismissKeyguardThenExecute(() -> {
+            new Thread(() -> {
+                try {
+                    // The intent we are sending is for the application, which
+                    // won't have permission to immediately start an activity after
+                    // the user switches to home.  We know it is safe to do at this
+                    // point, so make sure new activity switches are now allowed.
+                    ActivityManager.getService().resumeAppSwitches();
+                } catch (RemoteException e) {
                 }
+                try {
+                    intent.send(null, 0, null, null, null, null, getActivityOptions());
+                } catch (PendingIntent.CanceledException e) {
+                    // the stack trace isn't very helpful here.
+                    // Just log the exception message.
+                    Log.w(TAG, "Sending intent failed: " + e);
+
+                    // TODO: Dismiss Keyguard.
+                }
+                if (intent.isActivity()) {
+                    mAssistManager.hideAssist();
+                }
+            }).start();
+
+            if (!mNotificationPanel.isFullyCollapsed()) {
+                // close the shade if it was open
+                animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */,
+                        true /* delayed */);
+                visibilityChanged(false);
+
+                return true;
+            } else {
+                return false;
             }
         }, afterKeyguardGone);
     }
@@ -6999,130 +6780,110 @@
 
             // Mark notification for one frame.
             row.setJustClicked(true);
-            DejankUtils.postAfterTraversal(new Runnable() {
-                @Override
-                public void run() {
-                    row.setJustClicked(false);
-                }
-            });
+            DejankUtils.postAfterTraversal(() -> row.setJustClicked(false));
 
             final boolean afterKeyguardGone = intent.isActivity()
                     && PreviewInflater.wouldLaunchResolverActivity(mContext, intent.getIntent(),
                             mCurrentUserId);
-            dismissKeyguardThenExecute(new OnDismissAction() {
-                @Override
-                public boolean onDismiss() {
-                    if (mHeadsUpManager != null && mHeadsUpManager.isHeadsUp(notificationKey)) {
-                        // Release the HUN notification to the shade.
+            dismissKeyguardThenExecute(() -> {
+                if (mHeadsUpManager != null && mHeadsUpManager.isHeadsUp(notificationKey)) {
+                    // Release the HUN notification to the shade.
 
-                        if (isPanelFullyCollapsed()) {
-                            HeadsUpManager.setIsClickedNotification(row, true);
-                        }
-                        //
-                        // In most cases, when FLAG_AUTO_CANCEL is set, the notification will
-                        // become canceled shortly by NoMan, but we can't assume that.
-                        mHeadsUpManager.releaseImmediately(notificationKey);
+                    if (isPanelFullyCollapsed()) {
+                        HeadsUpManager.setIsClickedNotification(row, true);
                     }
-                    StatusBarNotification parentToCancel = null;
-                    if (shouldAutoCancel(sbn) && mGroupManager.isOnlyChildInGroup(sbn)) {
-                        StatusBarNotification summarySbn = mGroupManager.getLogicalGroupSummary(sbn)
-                                        .getStatusBarNotification();
-                        if (shouldAutoCancel(summarySbn)) {
-                            parentToCancel = summarySbn;
-                        }
+                    //
+                    // In most cases, when FLAG_AUTO_CANCEL is set, the notification will
+                    // become canceled shortly by NoMan, but we can't assume that.
+                    mHeadsUpManager.releaseImmediately(notificationKey);
+                }
+                StatusBarNotification parentToCancel = null;
+                if (shouldAutoCancel(sbn) && mGroupManager.isOnlyChildInGroup(sbn)) {
+                    StatusBarNotification summarySbn =
+                            mGroupManager.getLogicalGroupSummary(sbn).getStatusBarNotification();
+                    if (shouldAutoCancel(summarySbn)) {
+                        parentToCancel = summarySbn;
                     }
-                    final StatusBarNotification parentToCancelFinal = parentToCancel;
-                    final Runnable runnable = new Runnable() {
-                        @Override
-                        public void run() {
-                            try {
-                                // The intent we are sending is for the application, which
-                                // won't have permission to immediately start an activity after
-                                // the user switches to home.  We know it is safe to do at this
-                                // point, so make sure new activity switches are now allowed.
-                                ActivityManager.getService().resumeAppSwitches();
-                            } catch (RemoteException e) {
-                            }
-                            if (intent != null) {
-                                // If we are launching a work activity and require to launch
-                                // separate work challenge, we defer the activity action and cancel
-                                // notification until work challenge is unlocked.
-                                if (intent.isActivity()) {
-                                    final int userId = intent.getCreatorUserHandle()
-                                            .getIdentifier();
-                                    if (mLockPatternUtils.isSeparateProfileChallengeEnabled(userId)
-                                            && mKeyguardManager.isDeviceLocked(userId)) {
-                                        // TODO(b/28935539): should allow certain activities to
-                                        // bypass work challenge
-                                        if (startWorkChallengeIfNecessary(userId,
-                                                intent.getIntentSender(), notificationKey)) {
-                                            // Show work challenge, do not run PendingIntent and
-                                            // remove notification
-                                            return;
-                                        }
-                                    }
-                                }
-                                try {
-                                    intent.send(null, 0, null, null, null, null,
-                                            getActivityOptions());
-                                } catch (PendingIntent.CanceledException e) {
-                                    // the stack trace isn't very helpful here.
-                                    // Just log the exception message.
-                                    Log.w(TAG, "Sending contentIntent failed: " + e);
-
-                                    // TODO: Dismiss Keyguard.
-                                }
-                                if (intent.isActivity()) {
-                                    mAssistManager.hideAssist();
+                }
+                final StatusBarNotification parentToCancelFinal = parentToCancel;
+                final Runnable runnable = () -> {
+                    try {
+                        // The intent we are sending is for the application, which
+                        // won't have permission to immediately start an activity after
+                        // the user switches to home.  We know it is safe to do at this
+                        // point, so make sure new activity switches are now allowed.
+                        ActivityManager.getService().resumeAppSwitches();
+                    } catch (RemoteException e) {
+                    }
+                    if (intent != null) {
+                        // If we are launching a work activity and require to launch
+                        // separate work challenge, we defer the activity action and cancel
+                        // notification until work challenge is unlocked.
+                        if (intent.isActivity()) {
+                            final int userId = intent.getCreatorUserHandle().getIdentifier();
+                            if (mLockPatternUtils.isSeparateProfileChallengeEnabled(userId)
+                                    && mKeyguardManager.isDeviceLocked(userId)) {
+                                // TODO(b/28935539): should allow certain activities to
+                                // bypass work challenge
+                                if (startWorkChallengeIfNecessary(userId, intent.getIntentSender(),
+                                        notificationKey)) {
+                                    // Show work challenge, do not run PendingIntent and
+                                    // remove notification
+                                    return;
                                 }
                             }
-
-                            try {
-                                mBarService.onNotificationClick(notificationKey);
-                            } catch (RemoteException ex) {
-                                // system process is dead if we're here.
-                            }
-                            if (parentToCancelFinal != null) {
-                                // We have to post it to the UI thread for synchronization
-                                mHandler.post(new Runnable() {
-                                    @Override
-                                    public void run() {
-                                        Runnable removeRunnable = new Runnable() {
-                                            @Override
-                                            public void run() {
-                                                performRemoveNotification(parentToCancelFinal);
-                                            }
-                                        };
-                                        if (isCollapsing()) {
-                                            // To avoid lags we're only performing the remove
-                                            // after the shade was collapsed
-                                            addPostCollapseAction(removeRunnable);
-                                        } else {
-                                            removeRunnable.run();
-                                        }
-                                    }
-                                });
-                            }
                         }
-                    };
+                        try {
+                            intent.send(null, 0, null, null, null, null, getActivityOptions());
+                        } catch (PendingIntent.CanceledException e) {
+                            // the stack trace isn't very helpful here.
+                            // Just log the exception message.
+                            Log.w(TAG, "Sending contentIntent failed: " + e);
 
-                    if (mStatusBarKeyguardViewManager.isShowing()
-                            && mStatusBarKeyguardViewManager.isOccluded()) {
-                        mStatusBarKeyguardViewManager.addAfterKeyguardGoneRunnable(runnable);
-                    } else {
-                        new Thread(runnable).start();
+                            // TODO: Dismiss Keyguard.
+                        }
+                        if (intent.isActivity()) {
+                            mAssistManager.hideAssist();
+                        }
                     }
 
-                    if (!mNotificationPanel.isFullyCollapsed()) {
-                        // close the shade if it was open
-                        animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
-                                true /* force */, true /* delayed */);
-                        visibilityChanged(false);
-
-                        return true;
-                    } else {
-                        return false;
+                    try {
+                        mBarService.onNotificationClick(notificationKey);
+                    } catch (RemoteException ex) {
+                        // system process is dead if we're here.
                     }
+                    if (parentToCancelFinal != null) {
+                        // We have to post it to the UI thread for synchronization
+                        mHandler.post(() -> {
+                            Runnable removeRunnable =
+                                    () -> performRemoveNotification(parentToCancelFinal);
+                            if (isCollapsing()) {
+                                // To avoid lags we're only performing the remove
+                                // after the shade was collapsed
+                                addPostCollapseAction(removeRunnable);
+                            } else {
+                                removeRunnable.run();
+                            }
+                        });
+                    }
+                };
+
+                if (mStatusBarKeyguardViewManager.isShowing()
+                        && mStatusBarKeyguardViewManager.isOccluded()) {
+                    mStatusBarKeyguardViewManager.addAfterKeyguardGoneRunnable(runnable);
+                } else {
+                    new Thread(runnable).start();
+                }
+
+                if (!mNotificationPanel.isFullyCollapsed()) {
+                    // close the shade if it was open
+                    animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL, true /* force */,
+                            true /* delayed */);
+                    visibilityChanged(false);
+
+                    return true;
+                } else {
+                    return false;
                 }
             }, afterKeyguardGone);
         }
@@ -7149,10 +6910,10 @@
     }
 
     protected Bundle getActivityOptions() {
-        // Anything launched from the notification shade should always go into the
-        // fullscreen stack.
-        ActivityOptions options = ActivityOptions.makeBasic();
-        options.setLaunchStackId(StackId.FULLSCREEN_WORKSPACE_STACK_ID);
+        // Anything launched from the notification shade should always go into the secondary
+        // split-screen windowing mode.
+        final ActivityOptions options = ActivityOptions.makeBasic();
+        options.setLaunchWindowingMode(WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
         return options.toBundle();
     }
 
diff --git a/com/android/systemui/statusbar/phone/StatusBarIconController.java b/com/android/systemui/statusbar/phone/StatusBarIconController.java
index c240765..bcda60e 100644
--- a/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -14,10 +14,10 @@
 
 package com.android.systemui.statusbar.phone;
 
-import android.annotation.ColorInt;
+import static android.app.StatusBarManager.DISABLE2_SYSTEM_ICONS;
+import static android.app.StatusBarManager.DISABLE_NONE;
+
 import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Color;
 import android.support.annotation.VisibleForTesting;
 import android.text.TextUtils;
 import android.util.ArraySet;
@@ -29,11 +29,11 @@
 import android.widget.LinearLayout.LayoutParams;
 
 import com.android.internal.statusbar.StatusBarIcon;
-import com.android.settingslib.Utils;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.statusbar.StatusBarIconView;
 import com.android.systemui.statusbar.policy.DarkIconDispatcher;
+import com.android.systemui.util.Utils.DisableStateTracker;
 
 public interface StatusBarIconController {
 
@@ -149,6 +149,14 @@
             mContext = group.getContext();
             mIconSize = mContext.getResources().getDimensionPixelSize(
                     com.android.internal.R.dimen.status_bar_icon_size);
+
+            DisableStateTracker tracker =
+                    new DisableStateTracker(DISABLE_NONE, DISABLE2_SYSTEM_ICONS);
+            mGroup.addOnAttachStateChangeListener(tracker);
+            if (mGroup.isAttachedToWindow()) {
+                // In case we miss the first onAttachedToWindow event
+                tracker.onViewAttachedToWindow(mGroup);
+            }
         }
 
         protected void onIconAdded(int index, String slot, boolean blocked,
diff --git a/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
index 68f8e06..1c3ee75 100644
--- a/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
+++ b/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
@@ -33,7 +33,6 @@
 import com.android.systemui.statusbar.StatusBarIconView;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
-import com.android.systemui.statusbar.policy.DarkIconDispatcher;
 import com.android.systemui.statusbar.policy.IconLogger;
 import com.android.systemui.tuner.TunerService;
 import com.android.systemui.tuner.TunerService.Tunable;
@@ -50,21 +49,17 @@
 public class StatusBarIconControllerImpl extends StatusBarIconList implements Tunable,
         ConfigurationListener, Dumpable, CommandQueue.Callbacks, StatusBarIconController {
 
-    private final DarkIconDispatcher mDarkIconDispatcher;
+    private final ArrayList<IconManager> mIconGroups = new ArrayList<>();
+    private final ArraySet<String> mIconBlacklist = new ArraySet<>();
+    private final IconLogger mIconLogger = Dependency.get(IconLogger.class);
 
     private Context mContext;
     private DemoStatusIcons mDemoStatusIcons;
 
-    private final ArrayList<IconManager> mIconGroups = new ArrayList<>();
-
-    private final ArraySet<String> mIconBlacklist = new ArraySet<>();
-    private final IconLogger mIconLogger = Dependency.get(IconLogger.class);
-
     public StatusBarIconControllerImpl(Context context) {
         super(context.getResources().getStringArray(
                 com.android.internal.R.array.config_statusBarIcons));
         Dependency.get(ConfigurationController.class).addCallback(this);
-        mDarkIconDispatcher = Dependency.get(DarkIconDispatcher.class);
         mContext = context;
 
         loadDimens();
diff --git a/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index bbce751..09828dc 100644
--- a/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -225,7 +225,6 @@
         if (mShowing) {
             if (mOccluded && !mDozing) {
                 mStatusBar.hideKeyguard();
-                mStatusBar.stopWaitingForKeyguardExit();
                 if (hideBouncerWhenShowing || mBouncer.needsFullscreenBouncer()) {
                     hideBouncer(false /* destroyView */);
                 }
diff --git a/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java b/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java
index c0a6837..0d21c4e 100644
--- a/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java
+++ b/com/android/systemui/statusbar/policy/AccessPointControllerImpl.java
@@ -20,7 +20,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.net.wifi.WifiManager.ActionListener;
-import android.os.Looper;
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.provider.Settings;
@@ -59,13 +58,19 @@
 
     private int mCurrentUser;
 
-    public AccessPointControllerImpl(Context context, Looper bgLooper) {
+    public AccessPointControllerImpl(Context context) {
         mContext = context;
         mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
-        mWifiTracker = new WifiTracker(context, this, bgLooper, false, true);
+        mWifiTracker = new WifiTracker(context, this, false, true);
         mCurrentUser = ActivityManager.getCurrentUser();
     }
 
+    @Override
+    protected void finalize() throws Throwable {
+        super.finalize();
+        mWifiTracker.onDestroy();
+    }
+
     public boolean canConfigWifi() {
         return !mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_WIFI,
                 new UserHandle(mCurrentUser));
@@ -81,7 +86,7 @@
         if (DEBUG) Log.d(TAG, "addCallback " + callback);
         mCallbacks.add(callback);
         if (mCallbacks.size() == 1) {
-            mWifiTracker.startTracking();
+            mWifiTracker.onStart();
         }
     }
 
@@ -91,7 +96,7 @@
         if (DEBUG) Log.d(TAG, "removeCallback " + callback);
         mCallbacks.remove(callback);
         if (mCallbacks.isEmpty()) {
-            mWifiTracker.stopTracking();
+            mWifiTracker.onStop();
         }
     }
 
diff --git a/com/android/systemui/statusbar/policy/CallbackHandler.java b/com/android/systemui/statusbar/policy/CallbackHandler.java
index a456786..5159e8d 100644
--- a/com/android/systemui/statusbar/policy/CallbackHandler.java
+++ b/com/android/systemui/statusbar/policy/CallbackHandler.java
@@ -71,7 +71,7 @@
                 break;
             case MSG_NO_SIM_VISIBLE_CHANGED:
                 for (SignalCallback signalCluster : mSignalCallbacks) {
-                    signalCluster.setNoSims(msg.arg1 != 0);
+                    signalCluster.setNoSims(msg.arg1 != 0, msg.arg2 != 0);
                 }
                 break;
             case MSG_ETHERNET_CHANGED:
@@ -144,8 +144,8 @@
     }
 
     @Override
-    public void setNoSims(boolean show) {
-        obtainMessage(MSG_NO_SIM_VISIBLE_CHANGED, show ? 1 : 0, 0).sendToTarget();
+    public void setNoSims(boolean show, boolean simDetected) {
+        obtainMessage(MSG_NO_SIM_VISIBLE_CHANGED, show ? 1 : 0, simDetected ? 1 : 0).sendToTarget();
     }
 
     @Override
diff --git a/com/android/systemui/statusbar/policy/NetworkController.java b/com/android/systemui/statusbar/policy/NetworkController.java
index 2771011..9eee906 100644
--- a/com/android/systemui/statusbar/policy/NetworkController.java
+++ b/com/android/systemui/statusbar/policy/NetworkController.java
@@ -52,7 +52,7 @@
                 int qsType, boolean activityIn, boolean activityOut, String typeContentDescription,
                 String description, boolean isWide, int subId, boolean roaming) {}
         default void setSubs(List<SubscriptionInfo> subs) {}
-        default void setNoSims(boolean show) {}
+        default void setNoSims(boolean show, boolean simDetected) {}
 
         default void setEthernetIndicators(IconState icon) {}
 
diff --git a/com/android/systemui/statusbar/policy/NetworkControllerImpl.java b/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
index c217bda..d24e51c 100644
--- a/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
+++ b/com/android/systemui/statusbar/policy/NetworkControllerImpl.java
@@ -58,10 +58,8 @@
 import java.util.BitSet;
 import java.util.Collections;
 import java.util.Comparator;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
-import java.util.Map;
 
 import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED;
 
@@ -116,7 +114,7 @@
 
     // States that don't belong to a subcontroller.
     private boolean mAirplaneMode = false;
-    private boolean mHasNoSims;
+    private boolean mHasNoSubs;
     private Locale mLocale = null;
     // This list holds our ordering.
     private List<SubscriptionInfo> mCurrentSubscriptions = new ArrayList<>();
@@ -140,6 +138,7 @@
     @VisibleForTesting
     ServiceState mLastServiceState;
     private boolean mUserSetup;
+    private boolean mSimDetected;
 
     /**
      * Construct this controller object and register for updates.
@@ -151,7 +150,7 @@
                 (WifiManager) context.getSystemService(Context.WIFI_SERVICE),
                 SubscriptionManager.from(context), Config.readConfig(context), bgLooper,
                 new CallbackHandler(),
-                new AccessPointControllerImpl(context, bgLooper),
+                new AccessPointControllerImpl(context),
                 new DataUsageController(context),
                 new SubscriptionDefaults(),
                 deviceProvisionedController);
@@ -363,7 +362,7 @@
         cb.setSubs(mCurrentSubscriptions);
         cb.setIsAirplaneMode(new IconState(mAirplaneMode,
                 TelephonyIcons.FLIGHT_MODE_ICON, R.string.accessibility_airplane_mode, mContext));
-        cb.setNoSims(mHasNoSims);
+        cb.setNoSims(mHasNoSubs, mSimDetected);
         mWifiSignalController.notifyListeners(cb);
         mEthernetSignalController.notifyListeners(cb);
         for (int i = 0; i < mMobileSignalControllers.size(); i++) {
@@ -498,13 +497,27 @@
 
     @VisibleForTesting
     protected void updateNoSims() {
-        boolean hasNoSims = mHasMobileDataFeature && mMobileSignalControllers.size() == 0;
-        if (hasNoSims != mHasNoSims) {
-            mHasNoSims = hasNoSims;
-            mCallbackHandler.setNoSims(mHasNoSims);
+        boolean hasNoSubs = mHasMobileDataFeature && mMobileSignalControllers.size() == 0;
+        boolean simDetected = hasAnySim();
+        if (hasNoSubs != mHasNoSubs || simDetected != mSimDetected) {
+            mHasNoSubs = hasNoSubs;
+            mSimDetected = simDetected;
+            mCallbackHandler.setNoSims(mHasNoSubs, mSimDetected);
         }
     }
 
+    private boolean hasAnySim() {
+        int simCount = mPhone.getSimCount();
+        for (int i = 0; i < simCount; i++) {
+            int state = mPhone.getSimState(i);
+            if (state != TelephonyManager.SIM_STATE_ABSENT
+                    && state != TelephonyManager.SIM_STATE_UNKNOWN) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     @VisibleForTesting
     void setCurrentSubscriptions(List<SubscriptionInfo> subscriptions) {
         Collections.sort(subscriptions, new Comparator<SubscriptionInfo>() {
@@ -631,7 +644,7 @@
     private void notifyListeners() {
         mCallbackHandler.setIsAirplaneMode(new IconState(mAirplaneMode,
                 TelephonyIcons.FLIGHT_MODE_ICON, R.string.accessibility_airplane_mode, mContext));
-        mCallbackHandler.setNoSims(mHasNoSims);
+        mCallbackHandler.setNoSims(mHasNoSubs, mSimDetected);
     }
 
     /**
@@ -804,6 +817,10 @@
                 } else {
                     mWifiSignalController.setActivity(WifiManager.DATA_ACTIVITY_NONE);
                 }
+                String ssid = args.getString("ssid");
+                if (ssid != null) {
+                    mDemoWifiState.ssid = ssid;
+                }
                 mDemoWifiState.enabled = show;
                 mWifiSignalController.notifyListeners();
             }
@@ -822,8 +839,8 @@
             }
             String nosim = args.getString("nosim");
             if (nosim != null) {
-                mHasNoSims = nosim.equals("show");
-                mCallbackHandler.setNoSims(mHasNoSims);
+                mHasNoSubs = nosim.equals("show");
+                mCallbackHandler.setNoSims(mHasNoSubs, mSimDetected);
             }
             String mobile = args.getString("mobile");
             if (mobile != null) {
diff --git a/com/android/systemui/util/Utils.java b/com/android/systemui/util/Utils.java
index f4aebae..eca6127 100644
--- a/com/android/systemui/util/Utils.java
+++ b/com/android/systemui/util/Utils.java
@@ -14,6 +14,11 @@
 
 package com.android.systemui.util;
 
+import android.view.View;
+
+import com.android.systemui.SysUiServiceProvider;
+import com.android.systemui.statusbar.CommandQueue;
+
 import java.util.List;
 import java.util.function.Consumer;
 
@@ -28,4 +33,52 @@
             c.accept(list.get(i));
         }
     }
+
+    /**
+     * Sets the visibility of an UI element according to the DISABLE_* flags in
+     * {@link android.app.StatusBarManager}.
+     */
+    public static class DisableStateTracker implements CommandQueue.Callbacks,
+            View.OnAttachStateChangeListener {
+        private final int mMask1;
+        private final int mMask2;
+        private View mView;
+        private boolean mDisabled;
+
+        public DisableStateTracker(int disableMask, int disable2Mask) {
+            mMask1 = disableMask;
+            mMask2 = disable2Mask;
+        }
+
+        @Override
+        public void onViewAttachedToWindow(View v) {
+            mView = v;
+            SysUiServiceProvider.getComponent(v.getContext(), CommandQueue.class)
+                    .addCallbacks(this);
+        }
+
+        @Override
+        public void onViewDetachedFromWindow(View v) {
+            SysUiServiceProvider.getComponent(mView.getContext(), CommandQueue.class)
+                    .removeCallbacks(this);
+            mView = null;
+        }
+
+        /**
+         * Sets visibility of this {@link View} given the states passed from
+         * {@link com.android.systemui.statusbar.CommandQueue.Callbacks#disable(int, int)}.
+         */
+        @Override
+        public void disable(int state1, int state2, boolean animate) {
+            final boolean disabled = ((state1 & mMask1) != 0) || ((state2 & mMask2) != 0);
+            if (disabled == mDisabled) return;
+            mDisabled = disabled;
+            mView.setVisibility(disabled ? View.GONE : View.VISIBLE);
+        }
+
+        /** @return {@code true} if and only if this {@link View} is currently disabled */
+        public boolean isDisabled() {
+            return mDisabled;
+        }
+    }
 }
diff --git a/com/android/systemui/volume/ZenModePanel.java b/com/android/systemui/volume/ZenModePanel.java
index a3aca6e..7bb987c 100644
--- a/com/android/systemui/volume/ZenModePanel.java
+++ b/com/android/systemui/volume/ZenModePanel.java
@@ -524,18 +524,17 @@
             bindGenericCountdown();
             bindNextAlarm(getTimeUntilNextAlarmCondition());
         } else if (isForever(c)) {
+
             getConditionTagAt(FOREVER_CONDITION_INDEX).rb.setChecked(true);
             bindGenericCountdown();
             bindNextAlarm(getTimeUntilNextAlarmCondition());
         } else {
             if (isAlarm(c)) {
                 bindGenericCountdown();
-
                 bindNextAlarm(c);
                 getConditionTagAt(COUNTDOWN_ALARM_CONDITION_INDEX).rb.setChecked(true);
             } else if (isCountdown(c)) {
                 bindNextAlarm(getTimeUntilNextAlarmCondition());
-
                 bind(c, mZenRadioGroupContent.getChildAt(COUNTDOWN_CONDITION_INDEX),
                         COUNTDOWN_CONDITION_INDEX);
                 getConditionTagAt(COUNTDOWN_CONDITION_INDEX).rb.setChecked(true);
@@ -568,8 +567,8 @@
         tag = (ConditionTag) alarmContent.getTag();
         boolean showAlarm = tag != null && tag.condition != null;
         mZenRadioGroup.getChildAt(COUNTDOWN_ALARM_CONDITION_INDEX).setVisibility(
-                showAlarm ? View.VISIBLE : View.GONE);
-        alarmContent.setVisibility(showAlarm ? View.VISIBLE : View.GONE);
+                showAlarm ? View.VISIBLE : View.INVISIBLE);
+        alarmContent.setVisibility(showAlarm ? View.VISIBLE : View.INVISIBLE);
     }
 
     private Condition forever() {
diff --git a/com/android/uiautomator/testrunner/UiAutomatorTestCase.java b/com/android/uiautomator/testrunner/UiAutomatorTestCase.java
index 7c9aede..3d5476d 100644
--- a/com/android/uiautomator/testrunner/UiAutomatorTestCase.java
+++ b/com/android/uiautomator/testrunner/UiAutomatorTestCase.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 The Android Open Source Project
+ * Copyright (C) 2012 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,24 +16,55 @@
 
 package com.android.uiautomator.testrunner;
 
-import android.app.Instrumentation;
+import android.content.Context;
 import android.os.Bundle;
+import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.SystemClock;
-import android.test.InstrumentationTestCase;
+import android.view.inputmethod.InputMethodInfo;
 
-import com.android.uiautomator.core.InstrumentationUiAutomatorBridge;
+import com.android.internal.view.IInputMethodManager;
 import com.android.uiautomator.core.UiDevice;
 
+import junit.framework.TestCase;
+
+import java.util.List;
+
 /**
- * UI Automator test case that is executed on the device.
+ * UI automation test should extend this class. This class provides access
+ * to the following:
+ * {@link UiDevice} instance
+ * {@link Bundle} for command line parameters.
+ * @since API Level 16
  * @deprecated New tests should be written using UI Automator 2.0 which is available as part of the
  * Android Testing Support Library.
  */
 @Deprecated
-public class UiAutomatorTestCase extends InstrumentationTestCase {
+public class UiAutomatorTestCase extends TestCase {
 
+    private static final String DISABLE_IME = "disable_ime";
+    private static final String DUMMY_IME_PACKAGE = "com.android.testing.dummyime";
+    private UiDevice mUiDevice;
     private Bundle mParams;
     private IAutomationSupport mAutomationSupport;
+    private boolean mShouldDisableIme = false;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mShouldDisableIme = "true".equals(mParams.getString(DISABLE_IME));
+        if (mShouldDisableIme) {
+            setDummyIme();
+        }
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        if (mShouldDisableIme) {
+            restoreActiveIme();
+        }
+        super.tearDown();
+    }
 
     /**
      * Get current instance of {@link UiDevice}. Works similar to calling the static
@@ -41,7 +72,7 @@
      * @since API Level 16
      */
     public UiDevice getUiDevice() {
-        return UiDevice.getInstance();
+        return mUiDevice;
     }
 
     /**
@@ -54,43 +85,34 @@
         return mParams;
     }
 
-    void setAutomationSupport(IAutomationSupport automationSupport) {
-        mAutomationSupport = automationSupport;
-    }
-
     /**
      * Provides support for running tests to report interim status
      *
      * @return IAutomationSupport
      * @since API Level 16
-     * @deprecated Use {@link Instrumentation#sendStatus(int, Bundle)} instead
      */
     public IAutomationSupport getAutomationSupport() {
-        if (mAutomationSupport == null) {
-            mAutomationSupport = new InstrumentationAutomationSupport(getInstrumentation());
-        }
         return mAutomationSupport;
     }
 
     /**
-     * Initializes this test case.
-     *
-     * @param params Instrumentation arguments.
+     * package private
+     * @param uiDevice
      */
-    void initialize(Bundle params) {
+    void setUiDevice(UiDevice uiDevice) {
+        mUiDevice = uiDevice;
+    }
+
+    /**
+     * package private
+     * @param params
+     */
+    void setParams(Bundle params) {
         mParams = params;
+    }
 
-        // check if this is a monkey test mode
-        String monkeyVal = mParams.getString("monkey");
-        if (monkeyVal != null) {
-            // only if the monkey key is specified, we alter the state of monkey
-            // else we should leave things as they are.
-            getInstrumentation().getUiAutomation().setRunAsMonkey(Boolean.valueOf(monkeyVal));
-        }
-
-        UiDevice.getInstance().initialize(new InstrumentationUiAutomatorBridge(
-                getInstrumentation().getContext(),
-                getInstrumentation().getUiAutomation()));
+    void setAutomationSupport(IAutomationSupport automationSupport) {
+        mAutomationSupport = automationSupport;
     }
 
     /**
@@ -101,4 +123,28 @@
     public void sleep(long ms) {
         SystemClock.sleep(ms);
     }
+
+    private void setDummyIme() throws RemoteException {
+        IInputMethodManager im = IInputMethodManager.Stub.asInterface(ServiceManager
+                .getService(Context.INPUT_METHOD_SERVICE));
+        List<InputMethodInfo> infos = im.getInputMethodList();
+        String id = null;
+        for (InputMethodInfo info : infos) {
+            if (DUMMY_IME_PACKAGE.equals(info.getComponent().getPackageName())) {
+                id = info.getId();
+            }
+        }
+        if (id == null) {
+            throw new RuntimeException(String.format(
+                    "Required testing fixture missing: IME package (%s)", DUMMY_IME_PACKAGE));
+        }
+        im.setInputMethod(null, id);
+    }
+
+    private void restoreActiveIme() throws RemoteException {
+        // TODO: figure out a way to restore active IME
+        // Currently retrieving active IME requires querying secure settings provider, which is hard
+        // to do without a Context; so the caveat here is that to make the post test device usable,
+        // the active IME needs to be manually switched.
+    }
 }
diff --git a/com/android/webview/nullwebview/NullWebViewFactoryProvider.java b/com/android/webview/nullwebview/NullWebViewFactoryProvider.java
deleted file mode 100644
index ed12446..0000000
--- a/com/android/webview/nullwebview/NullWebViewFactoryProvider.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.webview.nullwebview;
-
-import android.content.Context;
-import android.webkit.CookieManager;
-import android.webkit.GeolocationPermissions;
-import android.webkit.ServiceWorkerController;
-import android.webkit.TokenBindingService;
-import android.webkit.WebIconDatabase;
-import android.webkit.WebStorage;
-import android.webkit.WebView;
-import android.webkit.WebViewDatabase;
-import android.webkit.WebViewDelegate;
-import android.webkit.WebViewFactoryProvider;
-import android.webkit.WebViewProvider;
-
-public class NullWebViewFactoryProvider implements WebViewFactoryProvider {
-
-    public static WebViewFactoryProvider create(WebViewDelegate delegate) {
-        return new NullWebViewFactoryProvider(delegate);
-    }
-
-    public NullWebViewFactoryProvider(WebViewDelegate delegate) {
-    }
-
-    @Override
-    public WebViewFactoryProvider.Statics getStatics() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public WebViewProvider createWebView(WebView webView, WebView.PrivateAccess privateAccess) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public GeolocationPermissions getGeolocationPermissions() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public CookieManager getCookieManager() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public TokenBindingService getTokenBindingService() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public ServiceWorkerController getServiceWorkerController() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public WebIconDatabase getWebIconDatabase() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public WebStorage getWebStorage() {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
-    public WebViewDatabase getWebViewDatabase(Context context) {
-        throw new UnsupportedOperationException();
-    }
-}