Import Android SDK Platform P [4477446]

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

AndroidVersion.ApiLevel has been modified to appear as 28

Change-Id: If0559643d7c328e36aafca98f0c114641d33642c
diff --git a/com/android/car/setupwizardlib/CarSetupWizardLayout.java b/com/android/car/setupwizardlib/CarSetupWizardLayout.java
index d183897..3586310 100644
--- a/com/android/car/setupwizardlib/CarSetupWizardLayout.java
+++ b/com/android/car/setupwizardlib/CarSetupWizardLayout.java
@@ -23,6 +23,7 @@
 import android.view.View;
 import android.widget.Button;
 import android.widget.LinearLayout;
+import android.widget.ProgressBar;
 
 
 /**
@@ -41,6 +42,8 @@
     private Button mPrimaryContinueButton;
     private Button mSecondaryContinueButton;
 
+    private ProgressBar mProgressBar;
+
     public CarSetupWizardLayout(Context context) {
         this(context, null);
     }
@@ -84,6 +87,8 @@
         String secondaryContinueButtonText;
         boolean secondaryContinueButtonEnabled;
 
+        boolean showProgressBar;
+
         try {
             showBackButton = attrArray.getBoolean(
                     R.styleable.CarSetupWizardLayout_showBackButton, true);
@@ -99,6 +104,8 @@
                     R.styleable.CarSetupWizardLayout_secondaryContinueButtonText);
             secondaryContinueButtonEnabled = attrArray.getBoolean(
                     R.styleable.CarSetupWizardLayout_secondaryContinueButtonEnabled, true);
+            showProgressBar = attrArray.getBoolean(
+                    R.styleable.CarSetupWizardLayout_showProgressBar, false);
         } finally {
             attrArray.recycle();
         }
@@ -130,14 +137,16 @@
             setSecondaryContinueButtonVisible(false);
         }
 
-        // TODO: Handle loading bar logic
+        mProgressBar = findViewById(R.id.progress_bar);
+        setProgressBarVisible(showProgressBar);
+
     }
 
     /**
      * Set a given button's visibility.
      */
-    private void setViewVisible(View button, boolean visible) {
-        button.setVisibility(visible ? View.VISIBLE : View.GONE);
+    private void setViewVisible(View view, boolean visible) {
+        view.setVisibility(visible ? View.VISIBLE : View.GONE);
     }
 
     /**
@@ -212,4 +221,11 @@
     public void setSecondaryContinueButtonVisible(boolean visible) {
         setViewVisible(mSecondaryContinueButton, visible);
     }
+
+    /**
+     * Set the progress bar visibility to the given visibility.
+     */
+    public void setProgressBarVisible(boolean visible) {
+        setViewVisible(mProgressBar, visible);
+    }
 }
diff --git a/com/android/commands/pm/Pm.java b/com/android/commands/pm/Pm.java
deleted file mode 100644
index 9490880..0000000
--- a/com/android/commands/pm/Pm.java
+++ /dev/null
@@ -1,822 +0,0 @@
-/*
- * Copyright (C) 2007 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.commands.pm;
-
-import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS;
-import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK;
-import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK;
-import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER;
-import static android.content.pm.PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED;
-
-import android.accounts.IAccountManager;
-import android.app.ActivityManager;
-import android.app.PackageInstallObserver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.IIntentReceiver;
-import android.content.IIntentSender;
-import android.content.Intent;
-import android.content.IntentSender;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.IPackageDataObserver;
-import android.content.pm.IPackageInstaller;
-import android.content.pm.IPackageManager;
-import android.content.pm.PackageInfo;
-import android.content.pm.PackageInstaller;
-import android.content.pm.PackageInstaller.SessionInfo;
-import android.content.pm.PackageInstaller.SessionParams;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageParser;
-import android.content.pm.PackageParser.ApkLite;
-import android.content.pm.PackageParser.PackageLite;
-import android.content.pm.PackageParser.PackageParserException;
-import android.content.pm.UserInfo;
-import android.net.Uri;
-import android.os.Binder;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.IBinder;
-import android.os.IUserManager;
-import android.os.ParcelFileDescriptor;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.ResultReceiver;
-import android.os.SELinux;
-import android.os.ServiceManager;
-import android.os.ShellCallback;
-import android.os.SystemClock;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.os.storage.StorageManager;
-import android.text.TextUtils;
-import android.text.format.DateUtils;
-import android.util.Log;
-import android.util.Pair;
-
-import com.android.internal.content.PackageHelper;
-import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.SizedInputStream;
-
-import libcore.io.IoUtils;
-
-import java.io.File;
-import java.io.FileDescriptor;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.concurrent.SynchronousQueue;
-import java.util.concurrent.TimeUnit;
-
-public final class Pm {
-    private static final String TAG = "Pm";
-    private static final String STDIN_PATH = "-";
-
-    IPackageManager mPm;
-    IPackageInstaller mInstaller;
-    IUserManager mUm;
-    IAccountManager mAm;
-
-    private String[] mArgs;
-    private int mNextArg;
-    private String mCurArgData;
-
-    private static final String PM_NOT_RUNNING_ERR =
-        "Error: Could not access the Package Manager.  Is the system running?";
-
-    public static void main(String[] args) {
-        int exitCode = 1;
-        try {
-            exitCode = new Pm().run(args);
-        } catch (Exception e) {
-            Log.e(TAG, "Error", e);
-            System.err.println("Error: " + e);
-            if (e instanceof RemoteException) {
-                System.err.println(PM_NOT_RUNNING_ERR);
-            }
-        }
-        System.exit(exitCode);
-    }
-
-    public int run(String[] args) throws RemoteException {
-        if (args.length < 1) {
-            return runShellCommand("package", mArgs);
-        }
-        mAm = IAccountManager.Stub.asInterface(ServiceManager.getService(Context.ACCOUNT_SERVICE));
-        mUm = IUserManager.Stub.asInterface(ServiceManager.getService(Context.USER_SERVICE));
-        mPm = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
-
-        if (mPm == null) {
-            System.err.println(PM_NOT_RUNNING_ERR);
-            return 1;
-        }
-        mInstaller = mPm.getPackageInstaller();
-
-        mArgs = args;
-        String op = args[0];
-        mNextArg = 1;
-
-        if ("install".equals(op)) {
-            return runInstall();
-        }
-
-        if ("install-create".equals(op)) {
-            return runInstallCreate();
-        }
-
-        if ("install-write".equals(op)) {
-            return runInstallWrite();
-        }
-
-        if ("install-commit".equals(op)) {
-            return runInstallCommit();
-        }
-
-        if ("install-abandon".equals(op) || "install-destroy".equals(op)) {
-            return runInstallAbandon();
-        }
-
-        return runShellCommand("package", mArgs);
-    }
-
-    static final class MyShellCallback extends ShellCallback {
-        @Override public ParcelFileDescriptor onOpenFile(String path, String seLinuxContext,
-                String mode) {
-            File file = new File(path);
-            final ParcelFileDescriptor fd;
-            try {
-                fd = ParcelFileDescriptor.open(file,
-                            ParcelFileDescriptor.MODE_CREATE |
-                            ParcelFileDescriptor.MODE_TRUNCATE |
-                            ParcelFileDescriptor.MODE_WRITE_ONLY);
-            } catch (FileNotFoundException e) {
-                String msg = "Unable to open file " + path + ": " + e;
-                System.err.println(msg);
-                throw new IllegalArgumentException(msg);
-            }
-            if (seLinuxContext != null) {
-                final String tcon = SELinux.getFileContext(file.getAbsolutePath());
-                if (!SELinux.checkSELinuxAccess(seLinuxContext, tcon, "file", "write")) {
-                    try {
-                        fd.close();
-                    } catch (IOException e) {
-                    }
-                    String msg = "System server has no access to file context " + tcon;
-                    System.err.println(msg + " (from path " + file.getAbsolutePath()
-                            + ", context " + seLinuxContext + ")");
-                    throw new IllegalArgumentException(msg);
-                }
-            }
-            return fd;
-        }
-    }
-
-    private int runShellCommand(String serviceName, String[] args) {
-        final HandlerThread handlerThread = new HandlerThread("results");
-        handlerThread.start();
-        try {
-            ServiceManager.getService(serviceName).shellCommand(
-                    FileDescriptor.in, FileDescriptor.out, FileDescriptor.err,
-                    args, new MyShellCallback(),
-                    new ResultReceiver(new Handler(handlerThread.getLooper())));
-            return 0;
-        } catch (RemoteException e) {
-            e.printStackTrace();
-        } finally {
-            handlerThread.quitSafely();
-        }
-        return -1;
-    }
-
-    private static class LocalIntentReceiver {
-        private final SynchronousQueue<Intent> mResult = new SynchronousQueue<>();
-
-        private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
-            @Override
-            public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken,
-                    IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) {
-                try {
-                    mResult.offer(intent, 5, TimeUnit.SECONDS);
-                } catch (InterruptedException e) {
-                    throw new RuntimeException(e);
-                }
-            }
-        };
-
-        public IntentSender getIntentSender() {
-            return new IntentSender((IIntentSender) mLocalSender);
-        }
-
-        public Intent getResult() {
-            try {
-                return mResult.take();
-            } catch (InterruptedException e) {
-                throw new RuntimeException(e);
-            }
-        }
-    }
-
-    private int translateUserId(int userId, String logContext) {
-        return ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(),
-                userId, true, true, logContext, "pm command");
-    }
-
-    private static String checkAbiArgument(String abi) {
-        if (TextUtils.isEmpty(abi)) {
-            throw new IllegalArgumentException("Missing ABI argument");
-        }
-        if ("-".equals(abi)) {
-            return abi;
-        }
-        final String[] supportedAbis = Build.SUPPORTED_ABIS;
-        for (String supportedAbi : supportedAbis) {
-            if (supportedAbi.equals(abi)) {
-                return abi;
-            }
-        }
-        throw new IllegalArgumentException("ABI " + abi + " not supported on this device");
-    }
-
-    /*
-     * Keep this around to support existing users of the "pm install" command that may not be
-     * able to be updated [or, at least informed the API has changed] such as ddmlib.
-     *
-     * Moving the implementation of "pm install" to "cmd package install" changes the executing
-     * context. Instead of being a stand alone process, "cmd package install" runs in the
-     * system_server process. Due to SELinux rules, system_server cannot access many directories;
-     * one of which being the package install staging directory [/data/local/tmp].
-     *
-     * The use of "adb install" or "cmd package install" over "pm install" is highly encouraged.
-     */
-    private int runInstall() throws RemoteException {
-        long startedTime = SystemClock.elapsedRealtime();
-        final InstallParams params = makeInstallParams();
-        final String inPath = nextArg();
-        if (params.sessionParams.sizeBytes == -1 && !STDIN_PATH.equals(inPath)) {
-            File file = new File(inPath);
-            if (file.isFile()) {
-                try {
-                    ApkLite baseApk = PackageParser.parseApkLite(file, 0);
-                    PackageLite pkgLite = new PackageLite(null, baseApk, null, null, null, null,
-                            null, null);
-                    params.sessionParams.setSize(
-                            PackageHelper.calculateInstalledSize(pkgLite,
-                            params.sessionParams.abiOverride));
-                } catch (PackageParserException | IOException e) {
-                    System.err.println("Error: Failed to parse APK file: " + e);
-                    return 1;
-                }
-            } else {
-                System.err.println("Error: Can't open non-file: " + inPath);
-                return 1;
-            }
-        }
-
-        final int sessionId = doCreateSession(params.sessionParams,
-                params.installerPackageName, params.userId);
-
-        try {
-            if (inPath == null && params.sessionParams.sizeBytes == -1) {
-                System.err.println("Error: must either specify a package size or an APK file");
-                return 1;
-            }
-            if (doWriteSession(sessionId, inPath, params.sessionParams.sizeBytes, "base.apk",
-                    false /*logSuccess*/) != PackageInstaller.STATUS_SUCCESS) {
-                return 1;
-            }
-            Pair<String, Integer> status = doCommitSession(sessionId, false /*logSuccess*/);
-            if (status.second != PackageInstaller.STATUS_SUCCESS) {
-                return 1;
-            }
-            Log.i(TAG, "Package " + status.first + " installed in " + (SystemClock.elapsedRealtime()
-                    - startedTime) + " ms");
-            System.out.println("Success");
-            return 0;
-        } finally {
-            try {
-                mInstaller.abandonSession(sessionId);
-            } catch (Exception ignore) {
-            }
-        }
-    }
-
-    private int runInstallAbandon() throws RemoteException {
-        final int sessionId = Integer.parseInt(nextArg());
-        return doAbandonSession(sessionId, true /*logSuccess*/);
-    }
-
-    private int runInstallCommit() throws RemoteException {
-        final int sessionId = Integer.parseInt(nextArg());
-        return doCommitSession(sessionId, true /*logSuccess*/).second;
-    }
-
-    private int runInstallCreate() throws RemoteException {
-        final InstallParams installParams = makeInstallParams();
-        final int sessionId = doCreateSession(installParams.sessionParams,
-                installParams.installerPackageName, installParams.userId);
-
-        // NOTE: adb depends on parsing this string
-        System.out.println("Success: created install session [" + sessionId + "]");
-        return PackageInstaller.STATUS_SUCCESS;
-    }
-
-    private int runInstallWrite() throws RemoteException {
-        long sizeBytes = -1;
-
-        String opt;
-        while ((opt = nextOption()) != null) {
-            if (opt.equals("-S")) {
-                sizeBytes = Long.parseLong(nextArg());
-            } else {
-                throw new IllegalArgumentException("Unknown option: " + opt);
-            }
-        }
-
-        final int sessionId = Integer.parseInt(nextArg());
-        final String splitName = nextArg();
-        final String path = nextArg();
-        return doWriteSession(sessionId, path, sizeBytes, splitName, true /*logSuccess*/);
-    }
-
-    private static class InstallParams {
-        SessionParams sessionParams;
-        String installerPackageName;
-        int userId = UserHandle.USER_ALL;
-    }
-
-    private InstallParams makeInstallParams() {
-        final SessionParams sessionParams = new SessionParams(SessionParams.MODE_FULL_INSTALL);
-        final InstallParams params = new InstallParams();
-        params.sessionParams = sessionParams;
-        String opt;
-        while ((opt = nextOption()) != null) {
-            switch (opt) {
-                case "-l":
-                    sessionParams.installFlags |= PackageManager.INSTALL_FORWARD_LOCK;
-                    break;
-                case "-r":
-                    sessionParams.installFlags |= PackageManager.INSTALL_REPLACE_EXISTING;
-                    break;
-                case "-i":
-                    params.installerPackageName = nextArg();
-                    if (params.installerPackageName == null) {
-                        throw new IllegalArgumentException("Missing installer package");
-                    }
-                    break;
-                case "-t":
-                    sessionParams.installFlags |= PackageManager.INSTALL_ALLOW_TEST;
-                    break;
-                case "-s":
-                    sessionParams.installFlags |= PackageManager.INSTALL_EXTERNAL;
-                    break;
-                case "-f":
-                    sessionParams.installFlags |= PackageManager.INSTALL_INTERNAL;
-                    break;
-                case "-d":
-                    sessionParams.installFlags |= PackageManager.INSTALL_ALLOW_DOWNGRADE;
-                    break;
-                case "-g":
-                    sessionParams.installFlags |= PackageManager.INSTALL_GRANT_RUNTIME_PERMISSIONS;
-                    break;
-                case "--dont-kill":
-                    sessionParams.installFlags |= PackageManager.INSTALL_DONT_KILL_APP;
-                    break;
-                case "--originating-uri":
-                    sessionParams.originatingUri = Uri.parse(nextOptionData());
-                    break;
-                case "--referrer":
-                    sessionParams.referrerUri = Uri.parse(nextOptionData());
-                    break;
-                case "-p":
-                    sessionParams.mode = SessionParams.MODE_INHERIT_EXISTING;
-                    sessionParams.appPackageName = nextOptionData();
-                    if (sessionParams.appPackageName == null) {
-                        throw new IllegalArgumentException("Missing inherit package name");
-                    }
-                    break;
-                case "--pkg":
-                    sessionParams.appPackageName = nextOptionData();
-                    if (sessionParams.appPackageName == null) {
-                        throw new IllegalArgumentException("Missing package name");
-                    }
-                    break;
-                case "-S":
-                    final long sizeBytes = Long.parseLong(nextOptionData());
-                    if (sizeBytes <= 0) {
-                        throw new IllegalArgumentException("Size must be positive");
-                    }
-                    sessionParams.setSize(sizeBytes);
-                    break;
-                case "--abi":
-                    sessionParams.abiOverride = checkAbiArgument(nextOptionData());
-                    break;
-                case "--ephemeral":
-                case "--instant":
-                    sessionParams.setInstallAsInstantApp(true /*isInstantApp*/);
-                    break;
-                case "--full":
-                    sessionParams.setInstallAsInstantApp(false /*isInstantApp*/);
-                    break;
-                case "--user":
-                    params.userId = UserHandle.parseUserArg(nextOptionData());
-                    break;
-                case "--install-location":
-                    sessionParams.installLocation = Integer.parseInt(nextOptionData());
-                    break;
-                case "--force-uuid":
-                    sessionParams.installFlags |= PackageManager.INSTALL_FORCE_VOLUME_UUID;
-                    sessionParams.volumeUuid = nextOptionData();
-                    if ("internal".equals(sessionParams.volumeUuid)) {
-                        sessionParams.volumeUuid = null;
-                    }
-                    break;
-                case "--force-sdk":
-                    sessionParams.installFlags |= PackageManager.INSTALL_FORCE_SDK;
-                    break;
-                default:
-                    throw new IllegalArgumentException("Unknown option " + opt);
-            }
-        }
-        return params;
-    }
-
-    private int doCreateSession(SessionParams params, String installerPackageName, int userId)
-            throws RemoteException {
-        userId = translateUserId(userId, "runInstallCreate");
-        if (userId == UserHandle.USER_ALL) {
-            userId = UserHandle.USER_SYSTEM;
-            params.installFlags |= PackageManager.INSTALL_ALL_USERS;
-        }
-
-        final int sessionId = mInstaller.createSession(params, installerPackageName, userId);
-        return sessionId;
-    }
-
-    private int doWriteSession(int sessionId, String inPath, long sizeBytes, String splitName,
-            boolean logSuccess) throws RemoteException {
-        if (STDIN_PATH.equals(inPath)) {
-            inPath = null;
-        } else if (inPath != null) {
-            final File file = new File(inPath);
-            if (file.isFile()) {
-                sizeBytes = file.length();
-            }
-        }
-
-        final SessionInfo info = mInstaller.getSessionInfo(sessionId);
-
-        PackageInstaller.Session session = null;
-        InputStream in = null;
-        OutputStream out = null;
-        try {
-            session = new PackageInstaller.Session(
-                    mInstaller.openSession(sessionId));
-
-            if (inPath != null) {
-                in = new FileInputStream(inPath);
-            } else {
-                in = new SizedInputStream(System.in, sizeBytes);
-            }
-            out = session.openWrite(splitName, 0, sizeBytes);
-
-            int total = 0;
-            byte[] buffer = new byte[1024 * 1024];
-            int c;
-            while ((c = in.read(buffer)) != -1) {
-                total += c;
-                out.write(buffer, 0, c);
-
-                if (info.sizeBytes > 0) {
-                    final float fraction = ((float) c / (float) info.sizeBytes);
-                    session.addProgress(fraction);
-                }
-            }
-            session.fsync(out);
-
-            if (logSuccess) {
-                System.out.println("Success: streamed " + total + " bytes");
-            }
-            return PackageInstaller.STATUS_SUCCESS;
-        } catch (IOException e) {
-            System.err.println("Error: failed to write; " + e.getMessage());
-            return PackageInstaller.STATUS_FAILURE;
-        } finally {
-            IoUtils.closeQuietly(out);
-            IoUtils.closeQuietly(in);
-            IoUtils.closeQuietly(session);
-        }
-    }
-
-    private Pair<String, Integer> doCommitSession(int sessionId, boolean logSuccess)
-            throws RemoteException {
-        PackageInstaller.Session session = null;
-        try {
-            session = new PackageInstaller.Session(
-                    mInstaller.openSession(sessionId));
-
-            final LocalIntentReceiver receiver = new LocalIntentReceiver();
-            session.commit(receiver.getIntentSender());
-
-            final Intent result = receiver.getResult();
-            final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
-                    PackageInstaller.STATUS_FAILURE);
-            if (status == PackageInstaller.STATUS_SUCCESS) {
-                if (logSuccess) {
-                    System.out.println("Success");
-                }
-            } else {
-                System.err.println("Failure ["
-                        + result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) + "]");
-            }
-            return new Pair<>(result.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME), status);
-        } finally {
-            IoUtils.closeQuietly(session);
-        }
-    }
-
-    private int doAbandonSession(int sessionId, boolean logSuccess) throws RemoteException {
-        PackageInstaller.Session session = null;
-        try {
-            session = new PackageInstaller.Session(mInstaller.openSession(sessionId));
-            session.abandon();
-            if (logSuccess) {
-                System.out.println("Success");
-            }
-            return PackageInstaller.STATUS_SUCCESS;
-        } finally {
-            IoUtils.closeQuietly(session);
-        }
-    }
-
-    class LocalPackageInstallObserver extends PackageInstallObserver {
-        boolean finished;
-        int result;
-        String extraPermission;
-        String extraPackage;
-
-        @Override
-        public void onPackageInstalled(String name, int status, String msg, Bundle extras) {
-            synchronized (this) {
-                finished = true;
-                result = status;
-                if (status == PackageManager.INSTALL_FAILED_DUPLICATE_PERMISSION) {
-                    extraPermission = extras.getString(
-                            PackageManager.EXTRA_FAILURE_EXISTING_PERMISSION);
-                    extraPackage = extras.getString(
-                            PackageManager.EXTRA_FAILURE_EXISTING_PACKAGE);
-                }
-                notifyAll();
-            }
-        }
-    }
-
-    private static boolean isNumber(String s) {
-        try {
-            Integer.parseInt(s);
-        } catch (NumberFormatException nfe) {
-            return false;
-        }
-        return true;
-    }
-
-    static class ClearCacheObserver extends IPackageDataObserver.Stub {
-        boolean finished;
-        boolean result;
-
-        @Override
-        public void onRemoveCompleted(String packageName, boolean succeeded) throws RemoteException {
-            synchronized (this) {
-                finished = true;
-                result = succeeded;
-                notifyAll();
-            }
-        }
-
-    }
-
-    static class ClearDataObserver extends IPackageDataObserver.Stub {
-        boolean finished;
-        boolean result;
-
-        @Override
-        public void onRemoveCompleted(String packageName, boolean succeeded) throws RemoteException {
-            synchronized (this) {
-                finished = true;
-                result = succeeded;
-                notifyAll();
-            }
-        }
-    }
-
-    /**
-     * Displays the package file for a package.
-     * @param pckg
-     */
-    private int displayPackageFilePath(String pckg, int userId) {
-        try {
-            PackageInfo info = mPm.getPackageInfo(pckg, 0, userId);
-            if (info != null && info.applicationInfo != null) {
-                System.out.print("package:");
-                System.out.println(info.applicationInfo.sourceDir);
-                if (!ArrayUtils.isEmpty(info.applicationInfo.splitSourceDirs)) {
-                    for (String splitSourceDir : info.applicationInfo.splitSourceDirs) {
-                        System.out.print("package:");
-                        System.out.println(splitSourceDir);
-                    }
-                }
-                return 0;
-            }
-        } catch (RemoteException e) {
-            System.err.println(e.toString());
-            System.err.println(PM_NOT_RUNNING_ERR);
-        }
-        return 1;
-    }
-
-    private String nextOption() {
-        if (mNextArg >= mArgs.length) {
-            return null;
-        }
-        String arg = mArgs[mNextArg];
-        if (!arg.startsWith("-")) {
-            return null;
-        }
-        mNextArg++;
-        if (arg.equals("--")) {
-            return null;
-        }
-        if (arg.length() > 1 && arg.charAt(1) != '-') {
-            if (arg.length() > 2) {
-                mCurArgData = arg.substring(2);
-                return arg.substring(0, 2);
-            } else {
-                mCurArgData = null;
-                return arg;
-            }
-        }
-        mCurArgData = null;
-        return arg;
-    }
-
-    private String nextOptionData() {
-        if (mCurArgData != null) {
-            return mCurArgData;
-        }
-        if (mNextArg >= mArgs.length) {
-            return null;
-        }
-        String data = mArgs[mNextArg];
-        mNextArg++;
-        return data;
-    }
-
-    private String nextArg() {
-        if (mNextArg >= mArgs.length) {
-            return null;
-        }
-        String arg = mArgs[mNextArg];
-        mNextArg++;
-        return arg;
-    }
-
-    private static int showUsage() {
-        System.err.println("usage: pm path [--user USER_ID] PACKAGE");
-        System.err.println("       pm dump PACKAGE");
-        System.err.println("       pm install [-lrtsfdg] [-i PACKAGE] [--user USER_ID]");
-        System.err.println("               [-p INHERIT_PACKAGE] [--install-location 0/1/2]");
-        System.err.println("               [--originating-uri URI] [---referrer URI]");
-        System.err.println("               [--abi ABI_NAME] [--force-sdk]");
-        System.err.println("               [--preload] [--instantapp] [--full] [--dont-kill]");
-        System.err.println("               [--force-uuid internal|UUID] [--pkg PACKAGE] [-S BYTES] [PATH|-]");
-        System.err.println("       pm install-create [-lrtsfdg] [-i PACKAGE] [--user USER_ID]");
-        System.err.println("               [-p INHERIT_PACKAGE] [--install-location 0/1/2]");
-        System.err.println("               [--originating-uri URI] [---referrer URI]");
-        System.err.println("               [--abi ABI_NAME] [--force-sdk]");
-        System.err.println("               [--preload] [--instantapp] [--full] [--dont-kill]");
-        System.err.println("               [--force-uuid internal|UUID] [--pkg PACKAGE] [-S BYTES]");
-        System.err.println("       pm install-write [-S BYTES] SESSION_ID SPLIT_NAME [PATH|-]");
-        System.err.println("       pm install-commit SESSION_ID");
-        System.err.println("       pm install-abandon SESSION_ID");
-        System.err.println("       pm uninstall [-k] [--user USER_ID] [--versionCode VERSION_CODE] PACKAGE");
-        System.err.println("       pm set-installer PACKAGE INSTALLER");
-        System.err.println("       pm move-package PACKAGE [internal|UUID]");
-        System.err.println("       pm move-primary-storage [internal|UUID]");
-        System.err.println("       pm clear [--user USER_ID] PACKAGE");
-        System.err.println("       pm enable [--user USER_ID] PACKAGE_OR_COMPONENT");
-        System.err.println("       pm disable [--user USER_ID] PACKAGE_OR_COMPONENT");
-        System.err.println("       pm disable-user [--user USER_ID] PACKAGE_OR_COMPONENT");
-        System.err.println("       pm disable-until-used [--user USER_ID] PACKAGE_OR_COMPONENT");
-        System.err.println("       pm default-state [--user USER_ID] PACKAGE_OR_COMPONENT");
-        System.err.println("       pm set-user-restriction [--user USER_ID] RESTRICTION VALUE");
-        System.err.println("       pm hide [--user USER_ID] PACKAGE_OR_COMPONENT");
-        System.err.println("       pm unhide [--user USER_ID] PACKAGE_OR_COMPONENT");
-        System.err.println("       pm grant [--user USER_ID] PACKAGE PERMISSION");
-        System.err.println("       pm revoke [--user USER_ID] PACKAGE PERMISSION");
-        System.err.println("       pm reset-permissions");
-        System.err.println("       pm set-app-link [--user USER_ID] PACKAGE {always|ask|never|undefined}");
-        System.err.println("       pm get-app-link [--user USER_ID] PACKAGE");
-        System.err.println("       pm set-install-location [0/auto] [1/internal] [2/external]");
-        System.err.println("       pm get-install-location");
-        System.err.println("       pm set-permission-enforced PERMISSION [true|false]");
-        System.err.println("       pm trim-caches DESIRED_FREE_SPACE [internal|UUID]");
-        System.err.println("       pm create-user [--profileOf USER_ID] [--managed] [--restricted] [--ephemeral] [--guest] USER_NAME");
-        System.err.println("       pm remove-user USER_ID");
-        System.err.println("       pm get-max-users");
-        System.err.println("");
-        System.err.println("NOTE: 'pm list' commands have moved! Run 'adb shell cmd package'");
-        System.err.println("  to display the new commands.");
-        System.err.println("");
-        System.err.println("pm path: print the path to the .apk of the given PACKAGE.");
-        System.err.println("");
-        System.err.println("pm dump: print system state associated with the given PACKAGE.");
-        System.err.println("");
-        System.err.println("pm install: install a single legacy package");
-        System.err.println("pm install-create: create an install session");
-        System.err.println("    -l: forward lock application");
-        System.err.println("    -r: allow replacement of existing application");
-        System.err.println("    -t: allow test packages");
-        System.err.println("    -i: specify package name of installer owning the app");
-        System.err.println("    -s: install application on sdcard");
-        System.err.println("    -f: install application on internal flash");
-        System.err.println("    -d: allow version code downgrade (debuggable packages only)");
-        System.err.println("    -p: partial application install (new split on top of existing pkg)");
-        System.err.println("    -g: grant all runtime permissions");
-        System.err.println("    -S: size in bytes of entire session");
-        System.err.println("    --dont-kill: installing a new feature split, don't kill running app");
-        System.err.println("    --originating-uri: set URI where app was downloaded from");
-        System.err.println("    --referrer: set URI that instigated the install of the app");
-        System.err.println("    --pkg: specify expected package name of app being installed");
-        System.err.println("    --abi: override the default ABI of the platform");
-        System.err.println("    --instantapp: cause the app to be installed as an ephemeral install app");
-        System.err.println("    --full: cause the app to be installed as a non-ephemeral full app");
-        System.err.println("    --install-location: force the install location:");
-        System.err.println("        0=auto, 1=internal only, 2=prefer external");
-        System.err.println("    --force-uuid: force install on to disk volume with given UUID");
-        System.err.println("    --force-sdk: allow install even when existing app targets platform");
-        System.err.println("        codename but new one targets a final API level");
-        System.err.println("");
-        System.err.println("pm install-write: write a package into existing session; path may");
-        System.err.println("  be '-' to read from stdin");
-        System.err.println("    -S: size in bytes of package, required for stdin");
-        System.err.println("");
-        System.err.println("pm install-commit: perform install of fully staged session");
-        System.err.println("pm install-abandon: abandon session");
-        System.err.println("");
-        System.err.println("pm set-installer: set installer package name");
-        System.err.println("");
-        System.err.println("pm uninstall: removes a package from the system. Options:");
-        System.err.println("    -k: keep the data and cache directories around after package removal.");
-        System.err.println("");
-        System.err.println("pm clear: deletes all data associated with a package.");
-        System.err.println("");
-        System.err.println("pm enable, disable, disable-user, disable-until-used, default-state:");
-        System.err.println("  these commands change the enabled state of a given package or");
-        System.err.println("  component (written as \"package/class\").");
-        System.err.println("");
-        System.err.println("pm grant, revoke: these commands either grant or revoke permissions");
-        System.err.println("    to apps. The permissions must be declared as used in the app's");
-        System.err.println("    manifest, be runtime permissions (protection level dangerous),");
-        System.err.println("    and the app targeting SDK greater than Lollipop MR1.");
-        System.err.println("");
-        System.err.println("pm reset-permissions: revert all runtime permissions to their default state.");
-        System.err.println("");
-        System.err.println("pm get-install-location: returns the current install location.");
-        System.err.println("    0 [auto]: Let system decide the best location");
-        System.err.println("    1 [internal]: Install on internal device storage");
-        System.err.println("    2 [external]: Install on external media");
-        System.err.println("");
-        System.err.println("pm set-install-location: changes the default install location.");
-        System.err.println("  NOTE: this is only intended for debugging; using this can cause");
-        System.err.println("  applications to break and other undersireable behavior.");
-        System.err.println("    0 [auto]: Let system decide the best location");
-        System.err.println("    1 [internal]: Install on internal device storage");
-        System.err.println("    2 [external]: Install on external media");
-        System.err.println("");
-        System.err.println("pm trim-caches: trim cache files to reach the given free space.");
-        System.err.println("");
-        System.err.println("pm create-user: create a new user with the given USER_NAME,");
-        System.err.println("  printing the new user identifier of the user.");
-        System.err.println("");
-        System.err.println("pm remove-user: remove the user with the given USER_IDENTIFIER,");
-        System.err.println("  deleting all data associated with that user");
-        System.err.println("");
-        return 1;
-    }
-}
diff --git a/com/android/datetimepicker/AccessibleLinearLayout.java b/com/android/datetimepicker/AccessibleLinearLayout.java
index 629f856..a67d809 100644
--- a/com/android/datetimepicker/AccessibleLinearLayout.java
+++ b/com/android/datetimepicker/AccessibleLinearLayout.java
@@ -25,7 +25,10 @@
 
 /**
  * Fake Button class, used so TextViews can announce themselves as Buttons, for accessibility.
+ *
+ * @deprecated This module is deprecated. Do not use this class.
  */
+@Deprecated
 public class AccessibleLinearLayout extends LinearLayout {
 
     public AccessibleLinearLayout(Context context, AttributeSet attrs) {
diff --git a/com/android/datetimepicker/AccessibleTextView.java b/com/android/datetimepicker/AccessibleTextView.java
index 98fa744..9a2ecbb 100644
--- a/com/android/datetimepicker/AccessibleTextView.java
+++ b/com/android/datetimepicker/AccessibleTextView.java
@@ -25,7 +25,10 @@
 
 /**
  * Fake Button class, used so TextViews can announce themselves as Buttons, for accessibility.
+ *
+ * @deprecated This module is deprecated. Do not use this class.
  */
+@Deprecated
 public class AccessibleTextView extends TextView {
 
     public AccessibleTextView(Context context, AttributeSet attrs) {
diff --git a/com/android/datetimepicker/HapticFeedbackController.java b/com/android/datetimepicker/HapticFeedbackController.java
index b9be63f..96d3a24 100644
--- a/com/android/datetimepicker/HapticFeedbackController.java
+++ b/com/android/datetimepicker/HapticFeedbackController.java
@@ -10,7 +10,10 @@
 
 /**
  * A simple utility class to handle haptic feedback.
+ *
+ * @deprecated This module is deprecated. Do not use this class.
  */
+@Deprecated
 public class HapticFeedbackController {
     private static final int VIBRATE_DELAY_MS = 125;
     private static final int VIBRATE_LENGTH_MS = 5;
diff --git a/com/android/datetimepicker/Utils.java b/com/android/datetimepicker/Utils.java
index 4a3110c..6c0adbe 100644
--- a/com/android/datetimepicker/Utils.java
+++ b/com/android/datetimepicker/Utils.java
@@ -28,7 +28,10 @@
 
 /**
  * Utility helper functions for time and date pickers.
+ *
+ * @deprecated This module is deprecated. Do not use this class.
  */
+@Deprecated
 public class Utils {
 
     public static final int MONDAY_BEFORE_JULIAN_EPOCH = Time.EPOCH_JULIAN_DAY - 3;
diff --git a/com/android/datetimepicker/date/AccessibleDateAnimator.java b/com/android/datetimepicker/date/AccessibleDateAnimator.java
index fc022cd..4e65aea 100644
--- a/com/android/datetimepicker/date/AccessibleDateAnimator.java
+++ b/com/android/datetimepicker/date/AccessibleDateAnimator.java
@@ -22,7 +22,7 @@
 import android.view.accessibility.AccessibilityEvent;
 import android.widget.ViewAnimator;
 
-public class AccessibleDateAnimator extends ViewAnimator {
+class AccessibleDateAnimator extends ViewAnimator {
     private long mDateMillis;
 
     public AccessibleDateAnimator(Context context, AttributeSet attrs) {
diff --git a/com/android/datetimepicker/date/DatePickerController.java b/com/android/datetimepicker/date/DatePickerController.java
index 42989dd..b027d33 100644
--- a/com/android/datetimepicker/date/DatePickerController.java
+++ b/com/android/datetimepicker/date/DatePickerController.java
@@ -24,7 +24,7 @@
 /**
  * Controller class to communicate among the various components of the date picker dialog.
  */
-public interface DatePickerController {
+interface DatePickerController {
 
     void onYearSelected(int year);
 
diff --git a/com/android/datetimepicker/date/DatePickerDialog.java b/com/android/datetimepicker/date/DatePickerDialog.java
index 994fdf2..9b26b48 100644
--- a/com/android/datetimepicker/date/DatePickerDialog.java
+++ b/com/android/datetimepicker/date/DatePickerDialog.java
@@ -48,7 +48,10 @@
 
 /**
  * Dialog allowing users to select a date.
+ *
+ * @deprecated Use {@link android.app.DatePickerDialog}.
  */
+@Deprecated
 public class DatePickerDialog extends DialogFragment implements
         OnClickListener, DatePickerController {
 
diff --git a/com/android/datetimepicker/date/DayPickerView.java b/com/android/datetimepicker/date/DayPickerView.java
index 47a2aa7..9f81537 100644
--- a/com/android/datetimepicker/date/DayPickerView.java
+++ b/com/android/datetimepicker/date/DayPickerView.java
@@ -42,7 +42,7 @@
 /**
  * This displays a list of months in a calendar format with selectable days.
  */
-public abstract class DayPickerView extends ListView implements OnScrollListener,
+abstract class DayPickerView extends ListView implements OnScrollListener,
     OnDateChangedListener {
 
     private static final String TAG = "MonthFragment";
diff --git a/com/android/datetimepicker/date/MonthAdapter.java b/com/android/datetimepicker/date/MonthAdapter.java
index 3ed88b0..0f95e55 100644
--- a/com/android/datetimepicker/date/MonthAdapter.java
+++ b/com/android/datetimepicker/date/MonthAdapter.java
@@ -32,7 +32,7 @@
 /**
  * An adapter for a list of {@link MonthView} items.
  */
-public abstract class MonthAdapter extends BaseAdapter implements OnDayClickListener {
+abstract class MonthAdapter extends BaseAdapter implements OnDayClickListener {
 
     private static final String TAG = "SimpleMonthAdapter";
 
diff --git a/com/android/datetimepicker/date/MonthView.java b/com/android/datetimepicker/date/MonthView.java
index 00711f3..63d073c 100644
--- a/com/android/datetimepicker/date/MonthView.java
+++ b/com/android/datetimepicker/date/MonthView.java
@@ -52,7 +52,7 @@
  * A calendar-like view displaying a specified month and the appropriate selectable day numbers
  * within the specified month.
  */
-public abstract class MonthView extends View {
+abstract class MonthView extends View {
     private static final String TAG = "MonthView";
 
     /**
diff --git a/com/android/datetimepicker/date/SimpleDayPickerView.java b/com/android/datetimepicker/date/SimpleDayPickerView.java
index 658c8a2..c20c754 100644
--- a/com/android/datetimepicker/date/SimpleDayPickerView.java
+++ b/com/android/datetimepicker/date/SimpleDayPickerView.java
@@ -22,7 +22,7 @@
 /**
  * A DayPickerView customized for {@link SimpleMonthAdapter}
  */
-public class SimpleDayPickerView extends DayPickerView {
+class SimpleDayPickerView extends DayPickerView {
 
     public SimpleDayPickerView(Context context, AttributeSet attrs) {
         super(context, attrs);
diff --git a/com/android/datetimepicker/date/SimpleMonthAdapter.java b/com/android/datetimepicker/date/SimpleMonthAdapter.java
index 0c939fe..394abc2 100644
--- a/com/android/datetimepicker/date/SimpleMonthAdapter.java
+++ b/com/android/datetimepicker/date/SimpleMonthAdapter.java
@@ -21,7 +21,7 @@
 /**
  * An adapter for a list of {@link SimpleMonthView} items.
  */
-public class SimpleMonthAdapter extends MonthAdapter {
+class SimpleMonthAdapter extends MonthAdapter {
 
     public SimpleMonthAdapter(Context context, DatePickerController controller) {
         super(context, controller);
diff --git a/com/android/datetimepicker/date/SimpleMonthView.java b/com/android/datetimepicker/date/SimpleMonthView.java
index b416a45..bb392eb 100644
--- a/com/android/datetimepicker/date/SimpleMonthView.java
+++ b/com/android/datetimepicker/date/SimpleMonthView.java
@@ -21,7 +21,7 @@
 
 import java.util.Calendar;
 
-public class SimpleMonthView extends MonthView {
+class SimpleMonthView extends MonthView {
 
     public SimpleMonthView(Context context) {
         super(context);
diff --git a/com/android/datetimepicker/date/TextViewWithCircularIndicator.java b/com/android/datetimepicker/date/TextViewWithCircularIndicator.java
index ad78746..e4d69e1 100644
--- a/com/android/datetimepicker/date/TextViewWithCircularIndicator.java
+++ b/com/android/datetimepicker/date/TextViewWithCircularIndicator.java
@@ -29,7 +29,10 @@
 
 /**
  * A text view which, when pressed or activated, displays a blue circle around the text.
+ *
+ * @deprecated This module is deprecated. Do not use this class.
  */
+@Deprecated
 public class TextViewWithCircularIndicator extends TextView {
 
     private static final int SELECTED_CIRCLE_ALPHA = 60;
diff --git a/com/android/datetimepicker/date/YearPickerView.java b/com/android/datetimepicker/date/YearPickerView.java
index ae14eb5..d058b36 100644
--- a/com/android/datetimepicker/date/YearPickerView.java
+++ b/com/android/datetimepicker/date/YearPickerView.java
@@ -37,7 +37,7 @@
 /**
  * Displays a selectable list of years.
  */
-public class YearPickerView extends ListView implements OnItemClickListener, OnDateChangedListener {
+class YearPickerView extends ListView implements OnItemClickListener, OnDateChangedListener {
     private static final String TAG = "YearPickerView";
 
     private final DatePickerController mController;
diff --git a/com/android/datetimepicker/time/AmPmCirclesView.java b/com/android/datetimepicker/time/AmPmCirclesView.java
index 902abd9..bafa51c 100644
--- a/com/android/datetimepicker/time/AmPmCirclesView.java
+++ b/com/android/datetimepicker/time/AmPmCirclesView.java
@@ -33,7 +33,7 @@
 /**
  * Draw the two smaller AM and PM circles next to where the larger circle will be.
  */
-public class AmPmCirclesView extends View {
+class AmPmCirclesView extends View {
     private static final String TAG = "AmPmCirclesView";
 
     // Alpha level for selected circle.
diff --git a/com/android/datetimepicker/time/CircleView.java b/com/android/datetimepicker/time/CircleView.java
index 1dd4eea..0d6766f 100644
--- a/com/android/datetimepicker/time/CircleView.java
+++ b/com/android/datetimepicker/time/CircleView.java
@@ -28,7 +28,7 @@
 /**
  * Draws a simple white circle on which the numbers will be drawn.
  */
-public class CircleView extends View {
+class CircleView extends View {
     private static final String TAG = "CircleView";
 
     private final Paint mPaint = new Paint();
diff --git a/com/android/datetimepicker/time/RadialPickerLayout.java b/com/android/datetimepicker/time/RadialPickerLayout.java
index 1d44907..6a4adea 100644
--- a/com/android/datetimepicker/time/RadialPickerLayout.java
+++ b/com/android/datetimepicker/time/RadialPickerLayout.java
@@ -44,6 +44,8 @@
  * The primary layout to hold the circular picker, and the am/pm buttons. This view well measure
  * itself to end up as a square. It also handles touches to be passed in to views that need to know
  * when they'd been touched.
+ *
+ * @deprecated This module is deprecated. Do not use this class.
  */
 public class RadialPickerLayout extends FrameLayout implements OnTouchListener {
     private static final String TAG = "RadialPickerLayout";
diff --git a/com/android/datetimepicker/time/RadialSelectorView.java b/com/android/datetimepicker/time/RadialSelectorView.java
index 0339dcd..4906991 100644
--- a/com/android/datetimepicker/time/RadialSelectorView.java
+++ b/com/android/datetimepicker/time/RadialSelectorView.java
@@ -35,7 +35,7 @@
  * View to show what number is selected. This will draw a blue circle over the number, with a blue
  * line coming from the center of the main circle to the edge of the blue selection.
  */
-public class RadialSelectorView extends View {
+class RadialSelectorView extends View {
     private static final String TAG = "RadialSelectorView";
 
     // Alpha level for selected circle.
diff --git a/com/android/datetimepicker/time/RadialTextsView.java b/com/android/datetimepicker/time/RadialTextsView.java
index 684e8f5..0b69d06 100644
--- a/com/android/datetimepicker/time/RadialTextsView.java
+++ b/com/android/datetimepicker/time/RadialTextsView.java
@@ -35,7 +35,7 @@
 /**
  * A view to show a series of numbers in a circular pattern.
  */
-public class RadialTextsView extends View {
+class RadialTextsView extends View {
     private final static String TAG = "RadialTextsView";
 
     private final Paint mPaint = new Paint();
diff --git a/com/android/datetimepicker/time/TimePickerDialog.java b/com/android/datetimepicker/time/TimePickerDialog.java
index c7661ad..0dd13c9 100644
--- a/com/android/datetimepicker/time/TimePickerDialog.java
+++ b/com/android/datetimepicker/time/TimePickerDialog.java
@@ -46,7 +46,10 @@
 
 /**
  * Dialog to set a time.
+ *
+ * @deprecated Use {@link android.app.TimePickerDialog}.
  */
+@Deprecated
 public class TimePickerDialog extends DialogFragment implements OnValueSelectedListener{
     private static final String TAG = "TimePickerDialog";
 
diff --git a/com/android/ex/photo/ActionBarWrapper.java b/com/android/ex/photo/ActionBarWrapper.java
index ae62197..6d4d4d2 100644
--- a/com/android/ex/photo/ActionBarWrapper.java
+++ b/com/android/ex/photo/ActionBarWrapper.java
@@ -1,8 +1,7 @@
 package com.android.ex.photo;
 
-
+import android.app.ActionBar;
 import android.graphics.drawable.Drawable;
-import android.support.v7.app.ActionBar;
 
 /**
  * Wrapper around {@link ActionBar}.
diff --git a/com/android/ex/photo/PhotoViewActivity.java b/com/android/ex/photo/PhotoViewActivity.java
index a5c4a43..7b53918 100644
--- a/com/android/ex/photo/PhotoViewActivity.java
+++ b/com/android/ex/photo/PhotoViewActivity.java
@@ -21,14 +21,14 @@
 import android.content.Intent;
 import android.os.Bundle;
 import android.support.annotation.Nullable;
-import android.support.v7.app.AppCompatActivity;
+import android.support.v4.app.FragmentActivity;
 import android.view.Menu;
 import android.view.MenuItem;
 
 /**
  * Activity to view the contents of an album.
  */
-public class PhotoViewActivity extends AppCompatActivity
+public class PhotoViewActivity extends FragmentActivity
         implements PhotoViewController.ActivityInterface {
 
     private PhotoViewController mController;
@@ -41,7 +41,7 @@
         mController.onCreate(savedInstanceState);
     }
 
-    protected PhotoViewController createController() {
+    public PhotoViewController createController() {
         return new PhotoViewController(this);
     }
 
@@ -122,7 +122,7 @@
     @Override
     public ActionBarInterface getActionBarInterface() {
         if (mActionBar == null) {
-            mActionBar = new ActionBarWrapper(getSupportActionBar());
+            mActionBar = new ActionBarWrapper(getActionBar());
         }
         return mActionBar;
     }
diff --git a/com/android/ims/ImsManager.java b/com/android/ims/ImsManager.java
index 813118b..e0a966a 100644
--- a/com/android/ims/ImsManager.java
+++ b/com/android/ims/ImsManager.java
@@ -43,6 +43,7 @@
 import com.android.ims.internal.IImsCallSession;
 import com.android.ims.internal.IImsConfig;
 import com.android.ims.internal.IImsEcbm;
+import com.android.ims.internal.IImsMMTelFeature;
 import com.android.ims.internal.IImsMultiEndpoint;
 import com.android.ims.internal.IImsRegistrationListener;
 import com.android.ims.internal.IImsServiceController;
@@ -281,22 +282,28 @@
     }
 
     /**
-     * Returns the user configuration of Enhanced 4G LTE Mode setting for slot. If not set, it
-     * returns true as default value.
+     * Returns the user configuration of Enhanced 4G LTE Mode setting for slot. If the option is
+     * not editable ({@link CarrierConfigManager#KEY_EDITABLE_ENHANCED_4G_LTE_BOOL} is false), or
+     * the setting is not initialized, this method will return default value specified by
+     * {@link CarrierConfigManager#KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL}.
+     *
+     * Note that even if the setting was set, it may no longer be editable. If this is the case we
+     * return the default value.
      */
     public boolean isEnhanced4gLteModeSettingEnabledByUser() {
-        // If user can't edit Enhanced 4G LTE Mode, it assumes Enhanced 4G LTE Mode is always true.
-        // If user changes SIM from editable mode to uneditable mode, need to return true.
-        if (!getBooleanCarrierConfig(CarrierConfigManager.KEY_EDITABLE_ENHANCED_4G_LTE_BOOL)) {
-            return true;
-        }
-
         int setting = SubscriptionManager.getIntegerSubscriptionProperty(
                 getSubId(), SubscriptionManager.ENHANCED_4G_MODE_ENABLED,
                 SUB_PROPERTY_NOT_INITIALIZED, mContext);
+        boolean onByDefault = getBooleanCarrierConfig(
+                CarrierConfigManager.KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL);
 
-        // If it's never set, by default we return true.
-        return (setting == SUB_PROPERTY_NOT_INITIALIZED || setting == 1);
+        // If Enhanced 4G LTE Mode is uneditable or not initialized, we use the default value
+        if (!getBooleanCarrierConfig(CarrierConfigManager.KEY_EDITABLE_ENHANCED_4G_LTE_BOOL)
+                || setting == SUB_PROPERTY_NOT_INITIALIZED) {
+            return onByDefault;
+        } else {
+            return (setting == ImsConfig.FeatureValueConstants.ON);
+        }
     }
 
     /**
@@ -315,21 +322,26 @@
     }
 
     /**
-     * Change persistent Enhanced 4G LTE Mode setting. If the the option is not editable
+     * Change persistent Enhanced 4G LTE Mode setting. If the option is not editable
      * ({@link CarrierConfigManager#KEY_EDITABLE_ENHANCED_4G_LTE_BOOL} is false), this method will
-     * always set the setting to true.
+     * set the setting to the default value specified by
+     * {@link CarrierConfigManager#KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL}.
      *
      */
     public void setEnhanced4gLteModeSetting(boolean enabled) {
-        // If false, we must always keep advanced 4G mode set to true.
-        enabled = getBooleanCarrierConfig(CarrierConfigManager.KEY_EDITABLE_ENHANCED_4G_LTE_BOOL)
-                ? enabled : true;
+        // If editable=false, we must keep default advanced 4G mode.
+        if (!getBooleanCarrierConfig(CarrierConfigManager.KEY_EDITABLE_ENHANCED_4G_LTE_BOOL)) {
+            enabled = getBooleanCarrierConfig(
+                    CarrierConfigManager.KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL);
+        }
 
         int prevSetting = SubscriptionManager.getIntegerSubscriptionProperty(
                 getSubId(), SubscriptionManager.ENHANCED_4G_MODE_ENABLED,
                 SUB_PROPERTY_NOT_INITIALIZED, mContext);
 
-        if (prevSetting != (enabled ? 1 : 0)) {
+        if (prevSetting != (enabled ?
+                   ImsConfig.FeatureValueConstants.ON :
+                   ImsConfig.FeatureValueConstants.OFF)) {
             SubscriptionManager.setSubscriptionProperty(getSubId(),
                     SubscriptionManager.ENHANCED_4G_MODE_ENABLED, booleanToPropertyString(enabled));
             if (isNonTtyOrTtyOnVolteEnabled()) {
@@ -566,7 +578,8 @@
                 SUB_PROPERTY_NOT_INITIALIZED, mContext);
 
         // If it's never set, by default we return true.
-        return (setting == SUB_PROPERTY_NOT_INITIALIZED || setting == 1);
+        return (setting == SUB_PROPERTY_NOT_INITIALIZED
+                || setting == ImsConfig.FeatureValueConstants.ON);
     }
 
     /**
@@ -672,7 +685,7 @@
             return getBooleanCarrierConfig(
                     CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_ENABLED_BOOL);
         } else {
-            return setting == 1;
+            return setting == ImsConfig.FeatureValueConstants.ON;
         }
     }
 
@@ -957,7 +970,7 @@
             return getBooleanCarrierConfig(
                             CarrierConfigManager.KEY_CARRIER_DEFAULT_WFC_IMS_ROAMING_ENABLED_BOOL);
         } else {
-            return (setting == 1);
+            return setting == ImsConfig.FeatureValueConstants.ON;
         }
     }
 
@@ -1982,8 +1995,8 @@
         serviceProxy.setStatusCallback(() ->  mStatusCallbacks.forEach(
                 ImsServiceProxy.INotifyStatusChanged::notifyStatusChanged));
         // Returns null if the service is not available.
-        IImsServiceController b = tm.getImsServiceControllerAndListen(mPhoneId,
-                ImsFeature.MMTEL, serviceProxy.getListener());
+        IImsMMTelFeature b = tm.getImsMMTelFeatureAndListen(mPhoneId,
+                serviceProxy.getListener());
         if (b != null) {
             serviceProxy.setBinder(b.asBinder());
             // Trigger the cache to be updated for feature status.
@@ -2405,7 +2418,9 @@
     public void factoryReset() {
         // Set VoLTE to default
         SubscriptionManager.setSubscriptionProperty(getSubId(),
-                SubscriptionManager.ENHANCED_4G_MODE_ENABLED, booleanToPropertyString(true));
+                SubscriptionManager.ENHANCED_4G_MODE_ENABLED,
+                booleanToPropertyString(getBooleanCarrierConfig(
+                        CarrierConfigManager.KEY_ENHANCED_4G_LTE_ON_BY_DEFAULT_BOOL)));
 
         // Set VoWiFi to default
         SubscriptionManager.setSubscriptionProperty(getSubId(),
diff --git a/com/android/ims/ImsServiceProxy.java b/com/android/ims/ImsServiceProxy.java
index 8c51202..f348919 100644
--- a/com/android/ims/ImsServiceProxy.java
+++ b/com/android/ims/ImsServiceProxy.java
@@ -27,10 +27,10 @@
 import com.android.ims.internal.IImsCallSessionListener;
 import com.android.ims.internal.IImsConfig;
 import com.android.ims.internal.IImsEcbm;
+import com.android.ims.internal.IImsMMTelFeature;
 import com.android.ims.internal.IImsMultiEndpoint;
 import com.android.ims.internal.IImsRegistrationListener;
-import com.android.ims.internal.IImsServiceController;
-import com.android.ims.internal.IImsServiceFeatureListener;
+import com.android.ims.internal.IImsServiceFeatureCallback;
 import com.android.ims.internal.IImsUt;
 
 /**
@@ -57,8 +57,8 @@
         void notifyStatusChanged();
     }
 
-    private final IImsServiceFeatureListener mListenerBinder =
-            new IImsServiceFeatureListener.Stub() {
+    private final IImsServiceFeatureCallback mListenerBinder =
+            new IImsServiceFeatureCallback.Stub() {
 
         @Override
         public void imsFeatureCreated(int slotId, int feature) throws RemoteException {
@@ -108,7 +108,7 @@
         this(slotId, null, featureType);
     }
 
-    public IImsServiceFeatureListener getListener() {
+    public IImsServiceFeatureCallback getListener() {
         return mListenerBinder;
     }
 
@@ -120,8 +120,7 @@
             throws RemoteException {
         synchronized (mLock) {
             checkServiceIsReady();
-            return getServiceInterface(mBinder).startSession(mSlotId, mSupportedFeature,
-                    incomingCallIntent, listener);
+            return getServiceInterface(mBinder).startSession(incomingCallIntent, listener);
         }
     }
 
@@ -130,7 +129,7 @@
             // Only check to make sure the binder connection still exists. This method should
             // still be able to be called when the state is STATE_NOT_AVAILABLE.
             checkBinderConnection();
-            getServiceInterface(mBinder).endSession(mSlotId, mSupportedFeature, sessionId);
+            getServiceInterface(mBinder).endSession(sessionId);
         }
     }
 
@@ -138,15 +137,14 @@
             throws RemoteException {
         synchronized (mLock) {
             checkServiceIsReady();
-            return getServiceInterface(mBinder).isConnected(mSlotId, mSupportedFeature,
-                    callServiceType, callType);
+            return getServiceInterface(mBinder).isConnected(callServiceType, callType);
         }
     }
 
     public boolean isOpened() throws RemoteException {
         synchronized (mLock) {
             checkServiceIsReady();
-            return getServiceInterface(mBinder).isOpened(mSlotId, mSupportedFeature);
+            return getServiceInterface(mBinder).isOpened();
         }
     }
 
@@ -154,8 +152,7 @@
     throws RemoteException {
         synchronized (mLock) {
             checkServiceIsReady();
-            getServiceInterface(mBinder).addRegistrationListener(mSlotId, mSupportedFeature,
-                    listener);
+            getServiceInterface(mBinder).addRegistrationListener(listener);
         }
     }
 
@@ -163,8 +160,7 @@
             throws RemoteException {
         synchronized (mLock) {
             checkServiceIsReady();
-            getServiceInterface(mBinder).removeRegistrationListener(mSlotId, mSupportedFeature,
-                    listener);
+            getServiceInterface(mBinder).removeRegistrationListener(listener);
         }
     }
 
@@ -172,8 +168,8 @@
             throws RemoteException {
         synchronized (mLock) {
             checkServiceIsReady();
-            return getServiceInterface(mBinder).createCallProfile(mSlotId, mSupportedFeature,
-                    sessionId, callServiceType, callType);
+            return getServiceInterface(mBinder).createCallProfile(sessionId, callServiceType,
+                    callType);
         }
     }
 
@@ -181,8 +177,7 @@
             IImsCallSessionListener listener) throws RemoteException {
         synchronized (mLock) {
             checkServiceIsReady();
-            return getServiceInterface(mBinder).createCallSession(mSlotId, mSupportedFeature,
-                    sessionId, profile, listener);
+            return getServiceInterface(mBinder).createCallSession(sessionId, profile, listener);
         }
     }
 
@@ -190,43 +185,42 @@
             throws RemoteException {
         synchronized (mLock) {
             checkServiceIsReady();
-            return getServiceInterface(mBinder).getPendingCallSession(mSlotId, mSupportedFeature,
-                    sessionId, callId);
+            return getServiceInterface(mBinder).getPendingCallSession(sessionId, callId);
         }
     }
 
     public IImsUt getUtInterface() throws RemoteException {
         synchronized (mLock) {
             checkServiceIsReady();
-            return getServiceInterface(mBinder).getUtInterface(mSlotId, mSupportedFeature);
+            return getServiceInterface(mBinder).getUtInterface();
         }
     }
 
     public IImsConfig getConfigInterface() throws RemoteException {
         synchronized (mLock) {
             checkServiceIsReady();
-            return getServiceInterface(mBinder).getConfigInterface(mSlotId, mSupportedFeature);
+            return getServiceInterface(mBinder).getConfigInterface();
         }
     }
 
     public void turnOnIms() throws RemoteException {
         synchronized (mLock) {
             checkServiceIsReady();
-            getServiceInterface(mBinder).turnOnIms(mSlotId, mSupportedFeature);
+            getServiceInterface(mBinder).turnOnIms();
         }
     }
 
     public void turnOffIms() throws RemoteException {
         synchronized (mLock) {
             checkServiceIsReady();
-            getServiceInterface(mBinder).turnOffIms(mSlotId, mSupportedFeature);
+            getServiceInterface(mBinder).turnOffIms();
         }
     }
 
     public IImsEcbm getEcbmInterface() throws RemoteException {
         synchronized (mLock) {
             checkServiceIsReady();
-            return getServiceInterface(mBinder).getEcbmInterface(mSlotId, mSupportedFeature);
+            return getServiceInterface(mBinder).getEcbmInterface();
         }
     }
 
@@ -234,16 +228,14 @@
             throws RemoteException {
         synchronized (mLock) {
             checkServiceIsReady();
-            getServiceInterface(mBinder).setUiTTYMode(mSlotId, mSupportedFeature, uiTtyMode,
-                    onComplete);
+            getServiceInterface(mBinder).setUiTTYMode(uiTtyMode, onComplete);
         }
     }
 
     public IImsMultiEndpoint getMultiEndpointInterface() throws RemoteException {
         synchronized (mLock) {
             checkServiceIsReady();
-            return getServiceInterface(mBinder).getMultiEndpointInterface(mSlotId,
-                    mSupportedFeature);
+            return getServiceInterface(mBinder).getMultiEndpointInterface();
         }
     }
 
@@ -277,7 +269,7 @@
     private Integer retrieveFeatureStatus() {
         if (mBinder != null) {
             try {
-                return getServiceInterface(mBinder).getFeatureStatus(mSlotId, mSupportedFeature);
+                return getServiceInterface(mBinder).getFeatureStatus();
             } catch (RemoteException e) {
                 // Status check failed, don't update cache
             }
@@ -318,8 +310,8 @@
         }
     }
 
-    private IImsServiceController getServiceInterface(IBinder b) {
-        return IImsServiceController.Stub.asInterface(b);
+    private IImsMMTelFeature getServiceInterface(IBinder b) {
+        return IImsMMTelFeature.Stub.asInterface(b);
     }
 
     protected void checkBinderConnection() throws RemoteException {
diff --git a/com/android/internal/app/procstats/ProcessState.java b/com/android/internal/app/procstats/ProcessState.java
index 7519fce..fbdf17d 100644
--- a/com/android/internal/app/procstats/ProcessState.java
+++ b/com/android/internal/app/procstats/ProcessState.java
@@ -102,6 +102,7 @@
         STATE_LAST_ACTIVITY,            // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
         STATE_CACHED_ACTIVITY,          // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
         STATE_CACHED_ACTIVITY_CLIENT,   // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT
+        STATE_CACHED_ACTIVITY,          // ActivityManager.PROCESS_STATE_CACHED_RECENT
         STATE_CACHED_EMPTY,             // ActivityManager.PROCESS_STATE_CACHED_EMPTY
     };
 
diff --git a/com/android/internal/content/NativeLibraryHelper.java b/com/android/internal/content/NativeLibraryHelper.java
index 83b7d2f..a1e6fd8 100644
--- a/com/android/internal/content/NativeLibraryHelper.java
+++ b/com/android/internal/content/NativeLibraryHelper.java
@@ -43,6 +43,7 @@
 
 import java.io.Closeable;
 import java.io.File;
+import java.io.FileDescriptor;
 import java.io.IOException;
 import java.util.List;
 
@@ -118,6 +119,17 @@
             return new Handle(apkHandles, multiArch, extractNativeLibs, debuggable);
         }
 
+        public static Handle createFd(PackageLite lite, FileDescriptor fd) throws IOException {
+            final long[] apkHandles = new long[1];
+            final String path = lite.baseCodePath;
+            apkHandles[0] = nativeOpenApkFd(fd, path);
+            if (apkHandles[0] == 0) {
+                throw new IOException("Unable to open APK " + path + " from fd " + fd);
+            }
+
+            return new Handle(apkHandles, lite.multiArch, lite.extractNativeLibs, lite.debuggable);
+        }
+
         Handle(long[] apkHandles, boolean multiArch, boolean extractNativeLibs,
                 boolean debuggable) {
             this.apkHandles = apkHandles;
@@ -152,6 +164,7 @@
     }
 
     private static native long nativeOpenApk(String path);
+    private static native long nativeOpenApkFd(FileDescriptor fd, String debugPath);
     private static native void nativeClose(long handle);
 
     private static native long nativeSumNativeBinaries(long handle, String cpuAbi,
diff --git a/com/android/internal/content/PackageHelper.java b/com/android/internal/content/PackageHelper.java
index 59a7995..e765ab1 100644
--- a/com/android/internal/content/PackageHelper.java
+++ b/com/android/internal/content/PackageHelper.java
@@ -42,6 +42,7 @@
 import libcore.io.IoUtils;
 
 import java.io.File;
+import java.io.FileDescriptor;
 import java.io.IOException;
 import java.util.Objects;
 import java.util.UUID;
@@ -383,9 +384,15 @@
 
     public static long calculateInstalledSize(PackageLite pkg, String abiOverride)
             throws IOException {
+        return calculateInstalledSize(pkg, abiOverride, null);
+    }
+
+    public static long calculateInstalledSize(PackageLite pkg, String abiOverride,
+            FileDescriptor fd) throws IOException {
         NativeLibraryHelper.Handle handle = null;
         try {
-            handle = NativeLibraryHelper.Handle.create(pkg);
+            handle = fd != null ? NativeLibraryHelper.Handle.createFd(pkg, fd)
+                    : NativeLibraryHelper.Handle.create(pkg);
             return calculateInstalledSize(pkg, handle, abiOverride);
         } finally {
             IoUtils.closeQuietly(handle);
diff --git a/com/android/internal/content/ReferrerIntent.java b/com/android/internal/content/ReferrerIntent.java
index 8d9a1cf..76dcc9b 100644
--- a/com/android/internal/content/ReferrerIntent.java
+++ b/com/android/internal/content/ReferrerIntent.java
@@ -19,6 +19,8 @@
 import android.content.Intent;
 import android.os.Parcel;
 
+import java.util.Objects;
+
 /**
  * Subclass of Intent that also contains referrer (as a package name) information.
  */
@@ -48,4 +50,21 @@
             return new ReferrerIntent[size];
         }
     };
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == null || !(obj instanceof ReferrerIntent)) {
+            return false;
+        }
+        final ReferrerIntent other = (ReferrerIntent) obj;
+        return filterEquals(other) && Objects.equals(mReferrer, other.mReferrer);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = 17;
+        result = 31 * result + filterHashCode();
+        result = 31 * result + Objects.hashCode(mReferrer);
+        return result;
+    }
 }
diff --git a/com/android/internal/os/BatteryStatsImpl.java b/com/android/internal/os/BatteryStatsImpl.java
index f2483c0..56d0bb2 100644
--- a/com/android/internal/os/BatteryStatsImpl.java
+++ b/com/android/internal/os/BatteryStatsImpl.java
@@ -120,7 +120,7 @@
     private static final int MAGIC = 0xBA757475; // 'BATSTATS'
 
     // Current on-disk Parcel version
-    private static final int VERSION = 168 + (USE_OLD_HISTORY ? 1000 : 0);
+    private static final int VERSION = 169 + (USE_OLD_HISTORY ? 1000 : 0);
 
     // Maximum number of items we will record in the history.
     private static final int MAX_HISTORY_ITEMS;
@@ -599,6 +599,8 @@
     private LongSamplingCounter mDischargeScreenOffCounter;
     private LongSamplingCounter mDischargeScreenDozeCounter;
     private LongSamplingCounter mDischargeCounter;
+    private LongSamplingCounter mDischargeLightDozeCounter;
+    private LongSamplingCounter mDischargeDeepDozeCounter;
 
     static final int MAX_LEVEL_STEPS = 200;
 
@@ -697,6 +699,16 @@
     }
 
     @Override
+    public long getUahDischargeLightDoze(int which) {
+        return mDischargeLightDozeCounter.getCountLocked(which);
+    }
+
+    @Override
+    public long getUahDischargeDeepDoze(int which) {
+        return mDischargeDeepDozeCounter.getCountLocked(which);
+    }
+
+    @Override
     public int getEstimatedBatteryCapacity() {
         return mEstimatedBatteryCapacity;
     }
@@ -6008,6 +6020,11 @@
         }
 
         @Override
+        public Timer getMulticastWakelockStats() {
+            return mWifiMulticastTimer;
+        }
+
+        @Override
         public ArrayMap<String, ? extends BatteryStats.Timer> getSyncStats() {
             return mSyncStats.getMap();
         }
@@ -9085,6 +9102,8 @@
         mBluetoothScanTimer = new StopwatchTimer(mClocks, null, -14, null, mOnBatteryTimeBase);
         mDischargeScreenOffCounter = new LongSamplingCounter(mOnBatteryScreenOffTimeBase);
         mDischargeScreenDozeCounter = new LongSamplingCounter(mOnBatteryTimeBase);
+        mDischargeLightDozeCounter = new LongSamplingCounter(mOnBatteryTimeBase);
+        mDischargeDeepDozeCounter = new LongSamplingCounter(mOnBatteryTimeBase);
         mDischargeCounter = new LongSamplingCounter(mOnBatteryTimeBase);
         mOnBattery = mOnBatteryInternal = false;
         long uptime = mClocks.uptimeMillis() * 1000;
@@ -9664,6 +9683,8 @@
         mChargeStepTracker.init();
         mDischargeScreenOffCounter.reset(false);
         mDischargeScreenDozeCounter.reset(false);
+        mDischargeLightDozeCounter.reset(false);
+        mDischargeDeepDozeCounter.reset(false);
         mDischargeCounter.reset(false);
     }
 
@@ -11263,6 +11284,11 @@
                 if (isScreenDoze(mScreenState)) {
                     mDischargeScreenDozeCounter.addCountLocked(chargeDiff);
                 }
+                if (mDeviceIdleMode == DEVICE_IDLE_MODE_LIGHT) {
+                    mDischargeLightDozeCounter.addCountLocked(chargeDiff);
+                } else if (mDeviceIdleMode == DEVICE_IDLE_MODE_DEEP) {
+                    mDischargeDeepDozeCounter.addCountLocked(chargeDiff);
+                }
             }
             mHistoryCur.batteryChargeUAh = chargeUAh;
             setOnBatteryLocked(elapsedRealtime, uptime, onBattery, oldStatus, level, chargeUAh);
@@ -11308,6 +11334,11 @@
                     if (isScreenDoze(mScreenState)) {
                         mDischargeScreenDozeCounter.addCountLocked(chargeDiff);
                     }
+                    if (mDeviceIdleMode == DEVICE_IDLE_MODE_LIGHT) {
+                        mDischargeLightDozeCounter.addCountLocked(chargeDiff);
+                    } else if (mDeviceIdleMode == DEVICE_IDLE_MODE_DEEP) {
+                        mDischargeDeepDozeCounter.addCountLocked(chargeDiff);
+                    }
                 }
                 mHistoryCur.batteryChargeUAh = chargeUAh;
                 changed = true;
@@ -12069,6 +12100,8 @@
         mDischargeCounter.readSummaryFromParcelLocked(in);
         mDischargeScreenOffCounter.readSummaryFromParcelLocked(in);
         mDischargeScreenDozeCounter.readSummaryFromParcelLocked(in);
+        mDischargeLightDozeCounter.readSummaryFromParcelLocked(in);
+        mDischargeDeepDozeCounter.readSummaryFromParcelLocked(in);
         int NPKG = in.readInt();
         if (NPKG > 0) {
             mDailyPackageChanges = new ArrayList<>(NPKG);
@@ -12493,6 +12526,8 @@
         mDischargeCounter.writeSummaryFromParcelLocked(out);
         mDischargeScreenOffCounter.writeSummaryFromParcelLocked(out);
         mDischargeScreenDozeCounter.writeSummaryFromParcelLocked(out);
+        mDischargeLightDozeCounter.writeSummaryFromParcelLocked(out);
+        mDischargeDeepDozeCounter.writeSummaryFromParcelLocked(out);
         if (mDailyPackageChanges != null) {
             final int NPKG = mDailyPackageChanges.size();
             out.writeInt(NPKG);
@@ -13044,6 +13079,8 @@
         mDischargeCounter = new LongSamplingCounter(mOnBatteryTimeBase, in);
         mDischargeScreenOffCounter = new LongSamplingCounter(mOnBatteryScreenOffTimeBase, in);
         mDischargeScreenDozeCounter = new LongSamplingCounter(mOnBatteryTimeBase, in);
+        mDischargeLightDozeCounter = new LongSamplingCounter(mOnBatteryTimeBase, in);
+        mDischargeDeepDozeCounter = new LongSamplingCounter(mOnBatteryTimeBase, in);
         mLastWriteTime = in.readLong();
 
         mRpmStats.clear();
@@ -13230,6 +13267,8 @@
         mDischargeCounter.writeToParcel(out);
         mDischargeScreenOffCounter.writeToParcel(out);
         mDischargeScreenDozeCounter.writeToParcel(out);
+        mDischargeLightDozeCounter.writeToParcel(out);
+        mDischargeDeepDozeCounter.writeToParcel(out);
         out.writeLong(mLastWriteTime);
 
         out.writeInt(mRpmStats.size());
diff --git a/com/android/internal/policy/DecorView.java b/com/android/internal/policy/DecorView.java
index 85251d4..5fddfba 100644
--- a/com/android/internal/policy/DecorView.java
+++ b/com/android/internal/policy/DecorView.java
@@ -431,7 +431,11 @@
             }
         }
 
-        return super.dispatchKeyEvent(event);
+        if (super.dispatchKeyEvent(event)) {
+            return true;
+        }
+
+        return (getViewRootImpl() != null) && getViewRootImpl().dispatchKeyFallbackEvent(event);
     }
 
     public boolean superDispatchKeyShortcutEvent(KeyEvent event) {
diff --git a/com/android/internal/telephony/CarrierIdentifier.java b/com/android/internal/telephony/CarrierIdentifier.java
new file mode 100644
index 0000000..e8be159
--- /dev/null
+++ b/com/android/internal/telephony/CarrierIdentifier.java
@@ -0,0 +1,569 @@
+/*
+ * Copyright 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.telephony;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Message;
+import android.preference.PreferenceManager;
+import android.provider.Telephony;
+import android.telephony.Rlog;
+import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.LocalLog;
+import android.util.Log;
+
+import com.android.internal.telephony.uicc.IccRecords;
+import com.android.internal.telephony.uicc.UiccController;
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static android.provider.Telephony.CarrierIdentification;
+
+/**
+ * CarrierIdentifier identifies the subscription carrier and returns a canonical carrier Id
+ * and a user friendly carrier name. CarrierIdentifier reads subscription info and check against
+ * all carrier matching rules stored in CarrierIdProvider. It is msim aware, each phone has a
+ * dedicated CarrierIdentifier.
+ */
+public class CarrierIdentifier extends Handler {
+    private static final String LOG_TAG = CarrierIdentifier.class.getSimpleName();
+    private static final boolean DBG = true;
+    private static final boolean VDBG = Rlog.isLoggable(LOG_TAG, Log.VERBOSE);
+
+    // events to trigger carrier identification
+    private static final int SIM_LOAD_EVENT             = 1;
+    private static final int SIM_ABSENT_EVENT           = 2;
+    private static final int SPN_OVERRIDE_EVENT         = 3;
+    private static final int ICC_CHANGED_EVENT          = 4;
+    private static final int PREFER_APN_UPDATE_EVENT    = 5;
+    private static final int CARRIER_ID_DB_UPDATE_EVENT = 6;
+
+    private static final Uri CONTENT_URL_PREFER_APN = Uri.withAppendedPath(
+            Telephony.Carriers.CONTENT_URI, "preferapn");
+    private static final String OPERATOR_BRAND_OVERRIDE_PREFIX = "operator_branding_";
+    private static final int INVALID_CARRIER_ID         = -1;
+
+    // cached matching rules based mccmnc to speed up resolution
+    private List<CarrierMatchingRule> mCarrierMatchingRulesOnMccMnc = new ArrayList<>();
+    // cached carrier Id
+    private int mCarrierId = INVALID_CARRIER_ID;
+    // cached carrier name
+    private String mCarrierName;
+    // cached preferapn name
+    private String mPreferApn;
+    // cached service provider name. telephonyManager API returns empty string as default value.
+    // some carriers need to target devices with Empty SPN. In that case, carrier matching rule
+    // should specify "" spn explicitly.
+    private String mSpn = "";
+
+    private Context mContext;
+    private Phone mPhone;
+    private IccRecords mIccRecords;
+    private final LocalLog mCarrierIdLocalLog = new LocalLog(20);
+    private final TelephonyManager mTelephonyMgr;
+    private final SubscriptionsChangedListener mOnSubscriptionsChangedListener =
+            new SubscriptionsChangedListener();
+    private final SharedPreferenceChangedListener mSharedPrefListener =
+            new SharedPreferenceChangedListener();
+
+    private final ContentObserver mContentObserver = new ContentObserver(this) {
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            logd("onChange URI: " + uri);
+            if (CONTENT_URL_PREFER_APN.equals(uri.getLastPathSegment())) {
+                sendEmptyMessage(PREFER_APN_UPDATE_EVENT);
+            } else {
+                sendEmptyMessage(CARRIER_ID_DB_UPDATE_EVENT);
+            }
+        }
+    };
+
+    private class SubscriptionsChangedListener
+            extends SubscriptionManager.OnSubscriptionsChangedListener {
+        final AtomicInteger mPreviousSubId =
+                new AtomicInteger(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+        /**
+         * Callback invoked when there is any change to any SubscriptionInfo. Typically
+         * this method would invoke {@link SubscriptionManager#getActiveSubscriptionInfoList}
+         */
+        @Override
+        public void onSubscriptionsChanged() {
+            int subId = mPhone.getSubId();
+            if (mPreviousSubId.getAndSet(subId) != subId) {
+                if (DBG) {
+                    logd("SubscriptionListener.onSubscriptionInfoChanged subId: "
+                            + mPreviousSubId);
+                }
+                if (SubscriptionManager.isValidSubscriptionId(subId)) {
+                    sendEmptyMessage(SIM_LOAD_EVENT);
+                } else {
+                    sendEmptyMessage(SIM_ABSENT_EVENT);
+                }
+            }
+        }
+    }
+
+    private class SharedPreferenceChangedListener implements
+            SharedPreferences.OnSharedPreferenceChangeListener {
+        @Override
+        public void onSharedPreferenceChanged(
+                SharedPreferences sharedPreferences, String key) {
+            if (TextUtils.equals(key, OPERATOR_BRAND_OVERRIDE_PREFIX
+                    + mPhone.getIccSerialNumber())) {
+                // SPN override from carrier privileged apps
+                logd("[onSharedPreferenceChanged]: " + key);
+                sendEmptyMessage(SPN_OVERRIDE_EVENT);
+            }
+        }
+    }
+
+    public CarrierIdentifier(Phone phone) {
+        logd("Creating CarrierIdentifier[" + phone.getPhoneId() + "]");
+        mContext = phone.getContext();
+        mPhone = phone;
+        mTelephonyMgr = TelephonyManager.from(mContext);
+
+        // register events
+        mContext.getContentResolver().registerContentObserver(CONTENT_URL_PREFER_APN, false,
+                mContentObserver);
+        mContext.getContentResolver().registerContentObserver(
+                Telephony.CarrierIdentification.CONTENT_URI, false, mContentObserver);
+        SubscriptionManager.from(mContext).addOnSubscriptionsChangedListener(
+                mOnSubscriptionsChangedListener);
+        PreferenceManager.getDefaultSharedPreferences(mContext)
+                .registerOnSharedPreferenceChangeListener(mSharedPrefListener);
+        UiccController.getInstance().registerForIccChanged(this, ICC_CHANGED_EVENT, null);
+    }
+
+    /**
+     * Entry point for the carrier identification.
+     *
+     *    1. SIM_LOAD_EVENT
+     *        This indicates that all SIM records has been loaded and its first entry point for the
+     *        carrier identification. Note, there are other attributes could be changed on the fly
+     *        like APN and SPN. We cached all carrier matching rules based on MCCMNC to speed
+     *        up carrier resolution on following trigger events.
+     *
+     *    2. PREFER_APN_UPDATE_EVENT
+     *        This indicates prefer apn has been changed. It could be triggered when user modified
+     *        APN settings or when default data connection first establishes on the current carrier.
+     *        We follow up on this by querying prefer apn sqlite and re-issue carrier identification
+     *        with the updated prefer apn name.
+     *
+     *    3. SPN_OVERRIDE_EVENT
+     *        This indicates that SPN value as been changed. It could be triggered from EF_SPN
+     *        record loading, carrier config override
+     *        {@link android.telephony.CarrierConfigManager#KEY_CARRIER_NAME_STRING}
+     *        or carrier app override {@link TelephonyManager#setOperatorBrandOverride(String)}.
+     *        we follow up this by checking the cached mSPN against the latest value and issue
+     *        carrier identification only if spn changes.
+     *
+     *    4. CARRIER_ID_DB_UPDATE_EVENT
+     *        This indicates that carrierIdentification database which stores all matching rules
+     *        has been updated. It could be triggered from OTA or assets update.
+     */
+    @Override
+    public void handleMessage(Message msg) {
+        if (VDBG) logd("handleMessage: " + msg.what);
+        switch (msg.what) {
+            case SIM_LOAD_EVENT:
+            case CARRIER_ID_DB_UPDATE_EVENT:
+                mSpn = mTelephonyMgr.getSimOperatorNameForPhone(mPhone.getPhoneId());
+                mPreferApn = getPreferApn();
+                loadCarrierMatchingRulesOnMccMnc();
+                break;
+            case SIM_ABSENT_EVENT:
+                mCarrierMatchingRulesOnMccMnc.clear();
+                mSpn = null;
+                mPreferApn = null;
+                updateCarrierIdAndName(INVALID_CARRIER_ID, null);
+                break;
+            case PREFER_APN_UPDATE_EVENT:
+                String preferApn = getPreferApn();
+                if (!equals(mPreferApn, preferApn, true)) {
+                    logd("[updatePreferApn] from:" + mPreferApn + " to:" + preferApn);
+                    mPreferApn = preferApn;
+                    matchCarrier();
+                }
+                break;
+            case SPN_OVERRIDE_EVENT:
+                String spn = mTelephonyMgr.getSimOperatorNameForPhone(mPhone.getPhoneId());
+                if (!equals(mSpn, spn, true)) {
+                    logd("[updateSpn] from:" + mSpn + " to:" + spn);
+                    mSpn = spn;
+                    matchCarrier();
+                }
+                break;
+            case ICC_CHANGED_EVENT:
+                IccRecords newIccRecords = UiccController.getInstance().getIccRecords(
+                        mPhone.getPhoneId(), UiccController.APP_FAM_3GPP);
+                if (mIccRecords != newIccRecords) {
+                    if (mIccRecords != null) {
+                        logd("Removing stale icc objects.");
+                        mIccRecords.unregisterForSpnUpdate(this);
+                        mIccRecords = null;
+                    }
+                    if (newIccRecords != null) {
+                        logd("new Icc object");
+                        newIccRecords.registerForSpnUpdate(this, SPN_OVERRIDE_EVENT, null);
+                        mIccRecords = newIccRecords;
+                    }
+                }
+                break;
+            default:
+                loge("invalid msg: " + msg.what);
+                break;
+        }
+    }
+
+    private void loadCarrierMatchingRulesOnMccMnc() {
+        try {
+            String mccmnc = mTelephonyMgr.getSimOperatorNumericForPhone(mPhone.getPhoneId());
+            Cursor cursor = mContext.getContentResolver().query(CarrierIdentification.CONTENT_URI,
+                    /* projection */ null,
+                    /* selection */ CarrierIdentification.MCCMNC + "=?",
+                    /* selectionArgs */ new String[]{mccmnc}, null);
+            try {
+                if (cursor != null) {
+                    if (VDBG) {
+                        logd("[loadCarrierMatchingRules]- " + cursor.getCount()
+                                + " Records(s) in DB" + " mccmnc: " + mccmnc);
+                    }
+                    mCarrierMatchingRulesOnMccMnc.clear();
+                    while (cursor.moveToNext()) {
+                        mCarrierMatchingRulesOnMccMnc.add(makeCarrierMatchingRule(cursor));
+                    }
+                    matchCarrier();
+                }
+            } finally {
+                if (cursor != null) {
+                    cursor.close();
+                }
+            }
+        } catch (Exception ex) {
+            loge("[loadCarrierMatchingRules]- ex: " + ex);
+        }
+    }
+
+    private String getPreferApn() {
+        Cursor cursor = mContext.getContentResolver().query(
+                Uri.withAppendedPath(Telephony.Carriers.CONTENT_URI, "preferapn/subId/"
+                + mPhone.getSubId()), /* projection */ new String[]{Telephony.Carriers.APN},
+                /* selection */ null, /* selectionArgs */ null, /* sortOrder */ null);
+        try {
+            if (cursor != null) {
+                if (VDBG) {
+                    logd("[getPreferApn]- " + cursor.getCount() + " Records(s) in DB");
+                }
+                while (cursor.moveToNext()) {
+                    String apn = cursor.getString(cursor.getColumnIndexOrThrow(
+                            Telephony.Carriers.APN));
+                    logd("[getPreferApn]- " + apn);
+                    return apn;
+                }
+            }
+        } catch (Exception ex) {
+            loge("[getPreferApn]- exception: " + ex);
+        } finally {
+            if (cursor != null) {
+                cursor.close();
+            }
+        }
+        return null;
+    }
+
+    private void updateCarrierIdAndName(int cid, String name) {
+        boolean update = false;
+        if (!equals(name, mCarrierName, true)) {
+            logd("[updateCarrierName] from:" + mCarrierName + " to:" + name);
+            mCarrierName = name;
+            update = true;
+        }
+        if (cid != mCarrierId) {
+            logd("[updateCarrierId] from:" + mCarrierId + " to:" + cid);
+            mCarrierId = cid;
+            update = true;
+        }
+        if (update) {
+            // TODO new public intent CARRIER_ID_CHANGED
+            mCarrierIdLocalLog.log("[updateCarrierIdAndName] cid:" + mCarrierId + " name:"
+                    + mCarrierName);
+        }
+    }
+
+    private CarrierMatchingRule makeCarrierMatchingRule(Cursor cursor) {
+        return new CarrierMatchingRule(
+                cursor.getString(cursor.getColumnIndexOrThrow(CarrierIdentification.MCCMNC)),
+                cursor.getString(cursor.getColumnIndexOrThrow(
+                        CarrierIdentification.IMSI_PREFIX_XPATTERN)),
+                cursor.getString(cursor.getColumnIndexOrThrow(CarrierIdentification.GID1)),
+                cursor.getString(cursor.getColumnIndexOrThrow(CarrierIdentification.GID2)),
+                cursor.getString(cursor.getColumnIndexOrThrow(CarrierIdentification.PLMN)),
+                cursor.getString(cursor.getColumnIndexOrThrow(CarrierIdentification.SPN)),
+                cursor.getString(cursor.getColumnIndexOrThrow(CarrierIdentification.APN)),
+                cursor.getInt(cursor.getColumnIndexOrThrow(CarrierIdentification.CID)),
+                cursor.getString(cursor.getColumnIndexOrThrow(CarrierIdentification.NAME)));
+    }
+
+    /**
+     * carrier matching attributes with corresponding cid
+     */
+    private static class CarrierMatchingRule {
+        /**
+         * These scores provide the hierarchical relationship between the attributes, intended to
+         * resolve conflicts in a deterministic way. The scores are constructed such that a match
+         * from a higher tier will beat any subsequent match which does not match at that tier,
+         * so MCCMNC beats everything else. This avoids problems when two (or more) carriers rule
+         * matches as the score helps to find the best match uniquely. e.g.,
+         * rule 1 {mccmnc, imsi} rule 2 {mccmnc, imsi, gid1} and rule 3 {mccmnc, imsi, gid2} all
+         * matches with subscription data. rule 2 wins with the highest matching score.
+         */
+        private static final int SCORE_MCCMNC          = 1 << 6;
+        private static final int SCORE_IMSI_PREFIX     = 1 << 5;
+        private static final int SCORE_GID1            = 1 << 4;
+        private static final int SCORE_GID2            = 1 << 3;
+        private static final int SCORE_PLMN            = 1 << 2;
+        private static final int SCORE_SPN             = 1 << 1;
+        private static final int SCORE_APN             = 1 << 0;
+
+        private static final int SCORE_INVALID         = -1;
+
+        // carrier matching attributes
+        private String mMccMnc;
+        private String mImsiPrefixPattern;
+        private String mGid1;
+        private String mGid2;
+        private String mPlmn;
+        private String mSpn;
+        private String mApn;
+
+        // user-facing carrier name
+        private String mName;
+        // unique carrier id
+        private int mCid;
+
+        private int mScore = 0;
+
+        CarrierMatchingRule(String mccmnc, String imsiPrefixPattern, String gid1, String gid2,
+                String plmn, String spn, String apn, int cid, String name) {
+            mMccMnc = mccmnc;
+            mImsiPrefixPattern = imsiPrefixPattern;
+            mGid1 = gid1;
+            mGid2 = gid2;
+            mPlmn = plmn;
+            mSpn = spn;
+            mApn = apn;
+            mCid = cid;
+            mName = name;
+        }
+
+        // Calculate matching score. Values which aren't set in the rule are considered "wild".
+        // All values in the rule must match in order for the subscription to be considered part of
+        // the carrier. otherwise, a invalid score -1 will be assigned. A match from a higher tier
+        // will beat any subsequent match which does not match at that tier. When there are multiple
+        // matches at the same tier, the longest, best match will be used.
+        public void match(CarrierMatchingRule subscriptionRule) {
+            mScore = 0;
+            if (mMccMnc != null) {
+                if (!CarrierIdentifier.equals(subscriptionRule.mMccMnc, mMccMnc, false)) {
+                    mScore = SCORE_INVALID;
+                    return;
+                }
+                mScore += SCORE_MCCMNC;
+            }
+            if (mImsiPrefixPattern != null) {
+                if (!imsiPrefixMatch(subscriptionRule.mImsiPrefixPattern, mImsiPrefixPattern)) {
+                    mScore = SCORE_INVALID;
+                    return;
+                }
+                mScore += SCORE_IMSI_PREFIX;
+            }
+            if (mGid1 != null) {
+                // full string match. carrier matching should cover the corner case that gid1
+                // with garbage tail due to SIM manufacture issues.
+                if (!CarrierIdentifier.equals(subscriptionRule.mGid1, mGid1, true)) {
+                    mScore = SCORE_INVALID;
+                    return;
+                }
+                mScore += SCORE_GID1;
+            }
+            if (mGid2 != null) {
+                // full string match. carrier matching should cover the corner case that gid2
+                // with garbage tail due to SIM manufacture issues.
+                if (!CarrierIdentifier.equals(subscriptionRule.mGid2, mGid2, true)) {
+                    mScore = SCORE_INVALID;
+                    return;
+                }
+                mScore += SCORE_GID2;
+            }
+            if (mPlmn != null) {
+                if (!CarrierIdentifier.equals(subscriptionRule.mPlmn, mPlmn, true)) {
+                    mScore = SCORE_INVALID;
+                    return;
+                }
+                mScore += SCORE_PLMN;
+            }
+            if (mSpn != null) {
+                if (!CarrierIdentifier.equals(subscriptionRule.mSpn, mSpn, true)) {
+                    mScore = SCORE_INVALID;
+                    return;
+                }
+                mScore += SCORE_SPN;
+            }
+            if (mApn != null) {
+                if (!CarrierIdentifier.equals(subscriptionRule.mApn, mApn, true)) {
+                    mScore = SCORE_INVALID;
+                    return;
+                }
+                mScore += SCORE_APN;
+            }
+        }
+
+        private boolean imsiPrefixMatch(String imsi, String prefixXPattern) {
+            if (TextUtils.isEmpty(prefixXPattern)) return true;
+            if (TextUtils.isEmpty(imsi)) return false;
+            if (imsi.length() < prefixXPattern.length()) {
+                return false;
+            }
+            for (int i = 0; i < prefixXPattern.length(); i++) {
+                if ((prefixXPattern.charAt(i) != 'x') && (prefixXPattern.charAt(i) != 'X')
+                        && (prefixXPattern.charAt(i) != imsi.charAt(i))) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        public String toString() {
+            return "[CarrierMatchingRule] -"
+                    + " mccmnc: " + mMccMnc
+                    + " gid1: " + mGid1
+                    + " gid2: " + mGid2
+                    + " plmn: " + mPlmn
+                    + " imsi_prefix: " + mImsiPrefixPattern
+                    + " spn: " + mSpn
+                    + " apn: " + mApn
+                    + " name: " + mName
+                    + " cid: " + mCid
+                    + " score: " + mScore;
+        }
+    }
+
+    /**
+     * find the best matching carrier from candidates with matched MCCMNC and notify
+     * all interested parties on carrier id change.
+     */
+    private void matchCarrier() {
+        if (!SubscriptionManager.isValidSubscriptionId(mPhone.getSubId())) {
+            logd("[matchCarrier]" + "skip before sim records loaded");
+            return;
+        }
+        final String mccmnc = mTelephonyMgr.getSimOperatorNumericForPhone(mPhone.getPhoneId());
+        final String gid1 = mPhone.getGroupIdLevel1();
+        final String gid2 = mPhone.getGroupIdLevel2();
+        final String imsi = mPhone.getSubscriberId();
+        final String plmn = mPhone.getPlmn();
+        final String spn = mSpn;
+        final String apn = mPreferApn;
+
+        if (VDBG) {
+            logd("[matchCarrier]"
+                    + " gid1: " + gid1
+                    + " gid2: " + gid2
+                    + " imsi: " + Rlog.pii(LOG_TAG, imsi)
+                    + " plmn: " + plmn
+                    + " spn: " + spn
+                    + " apn: " + apn);
+        }
+
+        CarrierMatchingRule subscriptionRule = new CarrierMatchingRule(
+                mccmnc, imsi, gid1, gid2, plmn,  spn, apn, INVALID_CARRIER_ID, null);
+
+        int maxScore = CarrierMatchingRule.SCORE_INVALID;
+        CarrierMatchingRule maxRule = null;
+
+        for (CarrierMatchingRule rule : mCarrierMatchingRulesOnMccMnc) {
+            rule.match(subscriptionRule);
+            if (rule.mScore > maxScore) {
+                maxScore = rule.mScore;
+                maxRule = rule;
+            }
+        }
+        if (maxScore == CarrierMatchingRule.SCORE_INVALID) {
+            logd("[matchCarrier - no match] cid: " + INVALID_CARRIER_ID + " name: " + null);
+            updateCarrierIdAndName(INVALID_CARRIER_ID, null);
+        } else {
+            logd("[matchCarrier] cid: " + maxRule.mCid + " name: " + maxRule.mName);
+            updateCarrierIdAndName(maxRule.mCid, maxRule.mName);
+        }
+    }
+
+    public int getCarrierId() {
+        return mCarrierId;
+    }
+
+    public String getCarrierName() {
+        return mCarrierName;
+    }
+
+    private static boolean equals(String a, String b, boolean ignoreCase) {
+        if (a == null && b == null) return true;
+        if (a != null && b != null) {
+            return (ignoreCase) ? a.equalsIgnoreCase(b) : a.equals(b);
+        }
+        return false;
+    }
+
+    private static void logd(String str) {
+        Rlog.d(LOG_TAG, str);
+    }
+    private static void loge(String str) {
+        Rlog.e(LOG_TAG, str);
+    }
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
+        ipw.println("mCarrierIdLocalLogs:");
+        ipw.increaseIndent();
+        mCarrierIdLocalLog.dump(fd, pw, args);
+        ipw.decreaseIndent();
+
+        ipw.println("mCarrierId: " + mCarrierId);
+        ipw.println("mCarrierName: " + mCarrierName);
+
+        ipw.println("mCarrierMatchingRules on mccmnc: "
+                + mTelephonyMgr.getSimOperatorNumericForPhone(mPhone.getPhoneId()));
+        ipw.increaseIndent();
+        for (CarrierMatchingRule rule : mCarrierMatchingRulesOnMccMnc) {
+            ipw.println(rule.toString());
+        }
+        ipw.decreaseIndent();
+
+        ipw.println("mSpn: " + mSpn);
+        ipw.println("mPreferApn: " + mPreferApn);
+        ipw.flush();
+    }
+}
diff --git a/com/android/internal/telephony/CarrierInfoManager.java b/com/android/internal/telephony/CarrierInfoManager.java
index ebf04e8..d224c7d 100644
--- a/com/android/internal/telephony/CarrierInfoManager.java
+++ b/com/android/internal/telephony/CarrierInfoManager.java
@@ -38,30 +38,30 @@
     /**
      * Returns Carrier specific information that will be used to encrypt the IMSI and IMPI.
      * @param keyType whether the key is being used for WLAN or ePDG.
-     * @param mContext
+     * @param context
      * @return ImsiEncryptionInfo which contains the information, including the public key, to be
      *         used for encryption.
      */
     public static ImsiEncryptionInfo getCarrierInfoForImsiEncryption(int keyType,
-                                                                     Context mContext) {
+                                                                     Context context) {
         String mcc = "";
         String mnc = "";
         final TelephonyManager telephonyManager =
-                (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
-        String networkOperator = telephonyManager.getNetworkOperator();
-        if (!TextUtils.isEmpty(networkOperator)) {
-            mcc = networkOperator.substring(0, 3);
-            mnc = networkOperator.substring(3);
+                (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+        String simOperator = telephonyManager.getSimOperator();
+        if (!TextUtils.isEmpty(simOperator)) {
+            mcc = simOperator.substring(0, 3);
+            mnc = simOperator.substring(3);
             Log.i(LOG_TAG, "using values for mnc, mcc: " + mnc + "," + mcc);
         } else {
-            Log.e(LOG_TAG, "Invalid networkOperator: " + networkOperator);
+            Log.e(LOG_TAG, "Invalid networkOperator: " + simOperator);
             return null;
         }
         Cursor findCursor = null;
         try {
             // In the current design, MVNOs are not supported. If we decide to support them,
             // we'll need to add to this CL.
-            ContentResolver mContentResolver = mContext.getContentResolver();
+            ContentResolver mContentResolver = context.getContentResolver();
             String[] columns = {Telephony.CarrierColumns.PUBLIC_KEY,
                     Telephony.CarrierColumns.EXPIRATION_TIME,
                     Telephony.CarrierColumns.KEY_IDENTIFIER};
@@ -95,12 +95,12 @@
     /**
      * Inserts or update the Carrier Key in the database
      * @param imsiEncryptionInfo ImsiEncryptionInfo object.
-     * @param mContext Context.
+     * @param context Context.
      */
     public static void updateOrInsertCarrierKey(ImsiEncryptionInfo imsiEncryptionInfo,
-                                                Context mContext) {
+                                                Context context) {
         byte[] keyBytes = imsiEncryptionInfo.getPublicKey().getEncoded();
-        ContentResolver mContentResolver = mContext.getContentResolver();
+        ContentResolver mContentResolver = context.getContentResolver();
         // In the current design, MVNOs are not supported. If we decide to support them,
         // we'll need to add to this CL.
         ContentValues contentValues = new ContentValues();
@@ -150,12 +150,54 @@
      *        {@link java.security.PublicKey} and the Key Identifier.
      *        The keyIdentifier Attribute value pair that helps a server locate
      *        the private key to decrypt the permanent identity.
-     * @param mContext Context.
+     * @param context Context.
      */
     public static void setCarrierInfoForImsiEncryption(ImsiEncryptionInfo imsiEncryptionInfo,
-                                                       Context mContext) {
+                                                       Context context) {
         Log.i(LOG_TAG, "inserting carrier key: " + imsiEncryptionInfo);
-        updateOrInsertCarrierKey(imsiEncryptionInfo, mContext);
+        updateOrInsertCarrierKey(imsiEncryptionInfo, context);
         //todo send key to modem. Will be done in a subsequent CL.
     }
+
+    /**
+     * Deletes all the keys for a given Carrier from the device keystore.
+     * @param context Context
+     */
+    public static void deleteCarrierInfoForImsiEncryption(Context context) {
+        Log.i(LOG_TAG, "deleting carrier key from db");
+        String mcc = "";
+        String mnc = "";
+        final TelephonyManager telephonyManager =
+                (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
+        String simOperator = telephonyManager.getSimOperator();
+        if (!TextUtils.isEmpty(simOperator)) {
+            mcc = simOperator.substring(0, 3);
+            mnc = simOperator.substring(3);
+        } else {
+            Log.e(LOG_TAG, "Invalid networkOperator: " + simOperator);
+            return;
+        }
+        ContentResolver mContentResolver = context.getContentResolver();
+        try {
+            String whereClause = "mcc=? and mnc=?";
+            String[] whereArgs = new String[] { mcc, mnc };
+            mContentResolver.delete(Telephony.CarrierColumns.CONTENT_URI, whereClause, whereArgs);
+        } catch (Exception e) {
+            Log.e(LOG_TAG, "Delete failed" + e);
+        }
+    }
+
+    /**
+     * Deletes all the keys from the device keystore.
+     * @param context Context
+     */
+    public static void deleteAllCarrierKeysForImsiEncryption(Context context) {
+        Log.i(LOG_TAG, "deleting ALL carrier keys from db");
+        ContentResolver mContentResolver = context.getContentResolver();
+        try {
+            mContentResolver.delete(Telephony.CarrierColumns.CONTENT_URI, null, null);
+        } catch (Exception e) {
+            Log.e(LOG_TAG, "Delete failed" + e);
+        }
+    }
 }
\ No newline at end of file
diff --git a/com/android/internal/telephony/CommandsInterface.java b/com/android/internal/telephony/CommandsInterface.java
index 7026ff2..d5eb546 100644
--- a/com/android/internal/telephony/CommandsInterface.java
+++ b/com/android/internal/telephony/CommandsInterface.java
@@ -23,9 +23,9 @@
 import android.telephony.ClientRequestStats;
 import android.telephony.ImsiEncryptionInfo;
 import android.telephony.NetworkScanRequest;
+import android.telephony.data.DataProfile;
 
 import com.android.internal.telephony.cdma.CdmaSmsBroadcastConfigInfo;
-import com.android.internal.telephony.dataconnection.DataProfile;
 import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
 import com.android.internal.telephony.uicc.IccCardStatus;
 
diff --git a/com/android/internal/telephony/GsmCdmaPhone.java b/com/android/internal/telephony/GsmCdmaPhone.java
index 47289e5..6480417 100644
--- a/com/android/internal/telephony/GsmCdmaPhone.java
+++ b/com/android/internal/telephony/GsmCdmaPhone.java
@@ -157,6 +157,8 @@
     private ArrayList <MmiCode> mPendingMMIs = new ArrayList<MmiCode>();
     private IccPhoneBookInterfaceManager mIccPhoneBookIntManager;
     private DeviceStateMonitor mDeviceStateMonitor;
+    // Used for identify the carrier of current subscription
+    private CarrierIdentifier mCarrerIdentifier;
 
     private int mPrecisePhoneType;
 
@@ -212,6 +214,8 @@
         mSST = mTelephonyComponentFactory.makeServiceStateTracker(this, this.mCi);
         // DcTracker uses SST so needs to be created after it is instantiated
         mDcTracker = mTelephonyComponentFactory.makeDcTracker(this);
+        mCarrerIdentifier = mTelephonyComponentFactory.makeCarrierIdentifier(this);
+
         mSST.registerForNetworkAttached(this, EVENT_REGISTERED_TO_NETWORK, null);
         mDeviceStateMonitor = mTelephonyComponentFactory.makeDeviceStateMonitor(this);
         logd("GsmCdmaPhone: constructor: sub = " + mPhoneId);
@@ -1537,6 +1541,16 @@
     }
 
     @Override
+    public int getCarrierId() {
+        return mCarrerIdentifier.getCarrierId();
+    }
+
+    @Override
+    public String getCarrierName() {
+        return mCarrerIdentifier.getCarrierName();
+    }
+
+    @Override
     public String getGroupIdLevel1() {
         if (isPhoneTypeGsm()) {
             IccRecords r = mIccRecords.get();
@@ -1573,6 +1587,19 @@
     }
 
     @Override
+    public String getPlmn() {
+        if (isPhoneTypeGsm()) {
+            IccRecords r = mIccRecords.get();
+            return (r != null) ? r.getPnnHomeName() : null;
+        } else if (isPhoneTypeCdma()) {
+            loge("Plmn is not available in CDMA");
+            return null;
+        } else { //isPhoneTypeCdmaLte()
+            return (mSimRecords != null) ? mSimRecords.getPnnHomeName() : null;
+        }
+    }
+
+    @Override
     public String getCdmaPrlVersion() {
         return mSST.getPrlVersion();
     }
diff --git a/com/android/internal/telephony/Phone.java b/com/android/internal/telephony/Phone.java
index c51a5fe..d702c09 100644
--- a/com/android/internal/telephony/Phone.java
+++ b/com/android/internal/telephony/Phone.java
@@ -2881,6 +2881,13 @@
     }
 
     /**
+     * Retrieves the EF_PNN from the UICC For GSM/UMTS phones.
+     */
+    public String getPlmn() {
+        return null;
+    }
+
+    /**
      * Get the current for the default apn DataState. No change notification
      * exists at this interface -- use
      * {@link android.telephony.PhoneStateListener} instead.
@@ -2985,6 +2992,15 @@
         return;
     }
 
+    public int getCarrierId() {
+        // TODO remove hardcoding and expose a public API for INVALID CARRIER ID
+        return -1;
+    }
+
+    public String getCarrierName() {
+        return null;
+    }
+
     /**
      * Return if UT capability of ImsPhone is enabled or not
      */
diff --git a/com/android/internal/telephony/RIL.java b/com/android/internal/telephony/RIL.java
index 3ddbf12..2d33498 100644
--- a/com/android/internal/telephony/RIL.java
+++ b/com/android/internal/telephony/RIL.java
@@ -54,6 +54,7 @@
 import android.hardware.radio.V1_0.UusInfo;
 import android.net.ConnectivityManager;
 import android.os.AsyncResult;
+import android.os.Build;
 import android.os.Handler;
 import android.os.HwBinder;
 import android.os.Message;
@@ -80,15 +81,17 @@
 import android.telephony.SmsManager;
 import android.telephony.TelephonyHistogram;
 import android.telephony.TelephonyManager;
+import android.telephony.data.DataProfile;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.SparseArray;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.cat.ComprehensionTlv;
+import com.android.internal.telephony.cat.ComprehensionTlvTag;
 import com.android.internal.telephony.cdma.CdmaInformationRecords;
 import com.android.internal.telephony.cdma.CdmaSmsBroadcastConfigInfo;
 import com.android.internal.telephony.dataconnection.DataCallResponse;
-import com.android.internal.telephony.dataconnection.DataProfile;
 import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
 import com.android.internal.telephony.metrics.TelephonyMetrics;
 import com.android.internal.telephony.nano.TelephonyProto.SmsSession;
@@ -189,6 +192,9 @@
 
     static final int IRADIO_GET_SERVICE_DELAY_MILLIS = 4 * 1000;
 
+    static final String EMPTY_ALPHA_LONG = "";
+    static final String EMPTY_ALPHA_SHORT = "";
+
     public static List<TelephonyHistogram> getTelephonyRILTimingHistograms() {
         List<TelephonyHistogram> list;
         synchronized (mRilTimeHistograms) {
@@ -1061,23 +1067,23 @@
     private static DataProfileInfo convertToHalDataProfile(DataProfile dp) {
         DataProfileInfo dpi = new DataProfileInfo();
 
-        dpi.profileId = dp.profileId;
-        dpi.apn = dp.apn;
-        dpi.protocol = dp.protocol;
-        dpi.roamingProtocol = dp.roamingProtocol;
-        dpi.authType = dp.authType;
-        dpi.user = dp.user;
-        dpi.password = dp.password;
-        dpi.type = dp.type;
-        dpi.maxConnsTime = dp.maxConnsTime;
-        dpi.maxConns = dp.maxConns;
-        dpi.waitTime = dp.waitTime;
-        dpi.enabled = dp.enabled;
-        dpi.supportedApnTypesBitmap = dp.supportedApnTypesBitmap;
-        dpi.bearerBitmap = dp.bearerBitmap;
-        dpi.mtu = dp.mtu;
-        dpi.mvnoType = convertToHalMvnoType(dp.mvnoType);
-        dpi.mvnoMatchData = dp.mvnoMatchData;
+        dpi.profileId = dp.getProfileId();
+        dpi.apn = dp.getApn();
+        dpi.protocol = dp.getProtocol();
+        dpi.roamingProtocol = dp.getRoamingProtocol();
+        dpi.authType = dp.getAuthType();
+        dpi.user = dp.getUserName();
+        dpi.password = dp.getPassword();
+        dpi.type = dp.getType();
+        dpi.maxConnsTime = dp.getMaxConnsTime();
+        dpi.maxConns = dp.getMaxConns();
+        dpi.waitTime = dp.getWaitTime();
+        dpi.enabled = dp.isEnabled();
+        dpi.supportedApnTypesBitmap = dp.getSupportedApnTypesBitmap();
+        dpi.bearerBitmap = dp.getBearerBitmap();
+        dpi.mtu = dp.getMtu();
+        dpi.mvnoType = convertToHalMvnoType(dp.getMvnoType());
+        dpi.mvnoMatchData = dp.getMvnoMatchData();
 
         return dpi;
     }
@@ -1143,7 +1149,7 @@
 
             try {
                 radioProxy.setupDataCall(rr.mSerial, radioTechnology, dpi,
-                        dataProfile.modemCognitive, allowRoaming, isRoaming);
+                        dataProfile.isModemCognitive(), allowRoaming, isRoaming);
                 mMetrics.writeRilSetupDataCall(mPhoneId, rr.mSerial, radioTechnology, dpi.profileId,
                         dpi.apn, dpi.authType, dpi.protocol);
             } catch (RemoteException | RuntimeException e) {
@@ -1167,12 +1173,16 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> iccIO: "
-                        + requestToString(rr.mRequest) + " command = 0x"
-                        + Integer.toHexString(command) + " fileId = 0x"
-                        + Integer.toHexString(fileId) + " path = " + path + " p1 = "
-                        + p1 + " p2 = " + p2 + " p3 = " + " data = " + data
-                        + " aid = " + aid);
+                if (Build.IS_DEBUGGABLE) {
+                    riljLog(rr.serialString() + "> iccIO: "
+                            + requestToString(rr.mRequest) + " command = 0x"
+                            + Integer.toHexString(command) + " fileId = 0x"
+                            + Integer.toHexString(fileId) + " path = " + path + " p1 = "
+                            + p1 + " p2 = " + p2 + " p3 = " + " data = " + data
+                            + " aid = " + aid);
+                } else {
+                    riljLog(rr.serialString() + "> iccIO: " + requestToString(rr.mRequest));
+                }
             }
 
             IccIo iccIo = new IccIo();
@@ -2027,7 +2037,7 @@
 
             if (RILJ_LOGD) {
                 riljLog(rr.serialString() + "> " + requestToString(rr.mRequest) + " contents = "
-                        + contents);
+                        + (Build.IS_DEBUGGABLE ? contents : censoredTerminalResponse(contents)));
             }
 
             try {
@@ -2039,6 +2049,33 @@
         }
     }
 
+    private String censoredTerminalResponse(String terminalResponse) {
+        try {
+            byte[] bytes = IccUtils.hexStringToBytes(terminalResponse);
+            if (bytes != null) {
+                List<ComprehensionTlv> ctlvs = ComprehensionTlv.decodeMany(bytes, 0);
+                int from = 0;
+                for (ComprehensionTlv ctlv : ctlvs) {
+                    // Find text strings which might be personal information input by user,
+                    // then replace it with "********".
+                    if (ComprehensionTlvTag.TEXT_STRING.value() == ctlv.getTag()) {
+                        byte[] target = Arrays.copyOfRange(ctlv.getRawValue(), from,
+                                ctlv.getValueIndex() + ctlv.getLength());
+                        terminalResponse = terminalResponse.toLowerCase().replace(
+                                IccUtils.bytesToHexString(target), "********");
+                    }
+                    // The text string tag and the length field should also be hidden.
+                    from = ctlv.getValueIndex() + ctlv.getLength();
+                }
+            }
+        } catch (Exception e) {
+            Rlog.e(RILJ_LOG_TAG, "Could not censor the terminal response: " + e);
+            terminalResponse = null;
+        }
+
+        return terminalResponse;
+    }
+
     @Override
     public void sendEnvelopeWithStatus(String contents, Message result) {
         IRadio radioProxy = getRadioProxy(result);
@@ -2864,7 +2901,7 @@
 
             try {
                 radioProxy.setInitialAttachApn(rr.mSerial, convertToHalDataProfile(dataProfile),
-                        dataProfile.modemCognitive, isRoaming);
+                        dataProfile.isModemCognitive(), isRoaming);
             } catch (RemoteException | RuntimeException e) {
                 handleRadioProxyExceptionForRR(rr, "setInitialAttachApn", e);
             }
@@ -2969,9 +3006,13 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
-                        + " cla = " + cla + " instruction = " + instruction
-                        + " p1 = " + p1 + " p2 = " + " p3 = " + p3 + " data = " + data);
+                if (Build.IS_DEBUGGABLE) {
+                    riljLog(rr.serialString() + "> " + requestToString(rr.mRequest)
+                            + " cla = " + cla + " instruction = " + instruction
+                            + " p1 = " + p1 + " p2 = " + " p3 = " + p3 + " data = " + data);
+                } else {
+                    riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+                }
             }
 
             SimApdu msg = createSimApdu(0, cla, instruction, p1, p2, p3, data);
@@ -2991,8 +3032,12 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest) + " aid = " + aid
-                        + " p2 = " + p2);
+                if (Build.IS_DEBUGGABLE) {
+                    riljLog(rr.serialString() + "> " + requestToString(rr.mRequest) + " aid = " + aid
+                            + " p2 = " + p2);
+                } else {
+                    riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+                }
             }
 
             try {
@@ -3038,9 +3083,13 @@
                     mRILDefaultWorkSource);
 
             if (RILJ_LOGD) {
-                riljLog(rr.serialString() + "> " + requestToString(rr.mRequest) + " channel = "
-                        + channel + " cla = " + cla + " instruction = " + instruction
-                        + " p1 = " + p1 + " p2 = " + " p3 = " + p3 + " data = " + data);
+                if (Build.IS_DEBUGGABLE) {
+                    riljLog(rr.serialString() + "> " + requestToString(rr.mRequest) + " channel = "
+                            + channel + " cla = " + cla + " instruction = " + instruction
+                            + " p1 = " + p1 + " p2 = " + " p3 = " + p3 + " data = " + data);
+                } else {
+                    riljLog(rr.serialString() + "> " + requestToString(rr.mRequest));
+                }
             }
 
             SimApdu msg = createSimApdu(channel, cla, instruction, p1, p2, p3, data);
@@ -4813,7 +4862,80 @@
         return capacityResponse;
     }
 
-    static ArrayList<CellInfo> convertHalCellInfoList(
+    private static void writeToParcelForGsm(
+            Parcel p, int lac, int cid, int arfcn, int bsic, String mcc, String mnc,
+            String al, String as, int ss, int ber, int ta) {
+        p.writeInt(lac);
+        p.writeInt(cid);
+        p.writeInt(arfcn);
+        p.writeInt(bsic);
+        p.writeString(mcc);
+        p.writeString(mnc);
+        p.writeString(al);
+        p.writeString(as);
+        p.writeInt(ss);
+        p.writeInt(ber);
+        p.writeInt(ta);
+    }
+
+    private static void writeToParcelForCdma(
+            Parcel p, int ni, int si, int bsi, int lon, int lat, String al, String as,
+            int dbm, int ecio, int eDbm, int eEcio, int eSnr) {
+        p.writeInt(ni);
+        p.writeInt(si);
+        p.writeInt(bsi);
+        p.writeInt(lon);
+        p.writeInt(lat);
+        p.writeString(al);
+        p.writeString(as);
+        p.writeInt(dbm);
+        p.writeInt(ecio);
+        p.writeInt(eDbm);
+        p.writeInt(eEcio);
+        p.writeInt(eSnr);
+    }
+
+    private static void writeToParcelForLte(
+            Parcel p, int ci, int pci, int tac, int earfcn, String mcc, String mnc, String al,
+            String as, int ss, int rsrp, int rsrq, int rssnr, int cqi, int ta) {
+        p.writeInt(ci);
+        p.writeInt(pci);
+        p.writeInt(tac);
+        p.writeInt(earfcn);
+        p.writeString(mcc);
+        p.writeString(mnc);
+        p.writeString(al);
+        p.writeString(as);
+        p.writeInt(ss);
+        p.writeInt(rsrp);
+        p.writeInt(rsrq);
+        p.writeInt(rssnr);
+        p.writeInt(cqi);
+        p.writeInt(ta);
+    }
+
+    private static void writeToParcelForWcdma(
+            Parcel p, int lac, int cid, int psc, int uarfcn, String mcc, String mnc,
+            String al, String as, int ss, int ber) {
+        p.writeInt(lac);
+        p.writeInt(cid);
+        p.writeInt(psc);
+        p.writeInt(uarfcn);
+        p.writeString(mcc);
+        p.writeString(mnc);
+        p.writeString(al);
+        p.writeString(as);
+        p.writeInt(ss);
+        p.writeInt(ber);
+    }
+
+    /**
+     * Convert CellInfo defined in 1.0/types.hal to CellInfo type.
+     * @param records List of CellInfo defined in 1.0/types.hal
+     * @return List of converted CellInfo object
+     */
+    @VisibleForTesting
+    public static ArrayList<CellInfo> convertHalCellInfoList(
             ArrayList<android.hardware.radio.V1_0.CellInfo> records) {
         ArrayList<CellInfo> response = new ArrayList<CellInfo>(records.size());
 
@@ -4827,60 +4949,182 @@
             switch (record.cellInfoType) {
                 case CellInfoType.GSM: {
                     CellInfoGsm cellInfoGsm = record.gsm.get(0);
-                    p.writeInt(Integer.parseInt(cellInfoGsm.cellIdentityGsm.mcc));
-                    p.writeInt(Integer.parseInt(cellInfoGsm.cellIdentityGsm.mnc));
-                    p.writeInt(cellInfoGsm.cellIdentityGsm.lac);
-                    p.writeInt(cellInfoGsm.cellIdentityGsm.cid);
-                    p.writeInt(cellInfoGsm.cellIdentityGsm.arfcn);
-                    p.writeInt(Byte.toUnsignedInt(cellInfoGsm.cellIdentityGsm.bsic));
-                    p.writeInt(cellInfoGsm.signalStrengthGsm.signalStrength);
-                    p.writeInt(cellInfoGsm.signalStrengthGsm.bitErrorRate);
-                    p.writeInt(cellInfoGsm.signalStrengthGsm.timingAdvance);
+                    writeToParcelForGsm(
+                            p,
+                            cellInfoGsm.cellIdentityGsm.lac,
+                            cellInfoGsm.cellIdentityGsm.cid,
+                            cellInfoGsm.cellIdentityGsm.arfcn,
+                            Byte.toUnsignedInt(cellInfoGsm.cellIdentityGsm.bsic),
+                            cellInfoGsm.cellIdentityGsm.mcc,
+                            cellInfoGsm.cellIdentityGsm.mnc,
+                            EMPTY_ALPHA_LONG,
+                            EMPTY_ALPHA_SHORT,
+                            cellInfoGsm.signalStrengthGsm.signalStrength,
+                            cellInfoGsm.signalStrengthGsm.bitErrorRate,
+                            cellInfoGsm.signalStrengthGsm.timingAdvance);
                     break;
                 }
 
                 case CellInfoType.CDMA: {
                     CellInfoCdma cellInfoCdma = record.cdma.get(0);
-                    p.writeInt(cellInfoCdma.cellIdentityCdma.networkId);
-                    p.writeInt(cellInfoCdma.cellIdentityCdma.systemId);
-                    p.writeInt(cellInfoCdma.cellIdentityCdma.baseStationId);
-                    p.writeInt(cellInfoCdma.cellIdentityCdma.longitude);
-                    p.writeInt(cellInfoCdma.cellIdentityCdma.latitude);
-                    p.writeInt(cellInfoCdma.signalStrengthCdma.dbm);
-                    p.writeInt(cellInfoCdma.signalStrengthCdma.ecio);
-                    p.writeInt(cellInfoCdma.signalStrengthEvdo.dbm);
-                    p.writeInt(cellInfoCdma.signalStrengthEvdo.ecio);
-                    p.writeInt(cellInfoCdma.signalStrengthEvdo.signalNoiseRatio);
+                    writeToParcelForCdma(
+                            p,
+                            cellInfoCdma.cellIdentityCdma.networkId,
+                            cellInfoCdma.cellIdentityCdma.systemId,
+                            cellInfoCdma.cellIdentityCdma.baseStationId,
+                            cellInfoCdma.cellIdentityCdma.longitude,
+                            cellInfoCdma.cellIdentityCdma.latitude,
+                            EMPTY_ALPHA_LONG,
+                            EMPTY_ALPHA_SHORT,
+                            cellInfoCdma.signalStrengthCdma.dbm,
+                            cellInfoCdma.signalStrengthCdma.ecio,
+                            cellInfoCdma.signalStrengthEvdo.dbm,
+                            cellInfoCdma.signalStrengthEvdo.ecio,
+                            cellInfoCdma.signalStrengthEvdo.signalNoiseRatio);
                     break;
                 }
 
                 case CellInfoType.LTE: {
                     CellInfoLte cellInfoLte = record.lte.get(0);
-                    p.writeInt(Integer.parseInt(cellInfoLte.cellIdentityLte.mcc));
-                    p.writeInt(Integer.parseInt(cellInfoLte.cellIdentityLte.mnc));
-                    p.writeInt(cellInfoLte.cellIdentityLte.ci);
-                    p.writeInt(cellInfoLte.cellIdentityLte.pci);
-                    p.writeInt(cellInfoLte.cellIdentityLte.tac);
-                    p.writeInt(cellInfoLte.cellIdentityLte.earfcn);
-                    p.writeInt(cellInfoLte.signalStrengthLte.signalStrength);
-                    p.writeInt(cellInfoLte.signalStrengthLte.rsrp);
-                    p.writeInt(cellInfoLte.signalStrengthLte.rsrq);
-                    p.writeInt(cellInfoLte.signalStrengthLte.rssnr);
-                    p.writeInt(cellInfoLte.signalStrengthLte.cqi);
-                    p.writeInt(cellInfoLte.signalStrengthLte.timingAdvance);
+                    writeToParcelForLte(
+                            p,
+                            cellInfoLte.cellIdentityLte.ci,
+                            cellInfoLte.cellIdentityLte.pci,
+                            cellInfoLte.cellIdentityLte.tac,
+                            cellInfoLte.cellIdentityLte.earfcn,
+                            cellInfoLte.cellIdentityLte.mcc,
+                            cellInfoLte.cellIdentityLte.mnc,
+                            EMPTY_ALPHA_LONG,
+                            EMPTY_ALPHA_SHORT,
+                            cellInfoLte.signalStrengthLte.signalStrength,
+                            cellInfoLte.signalStrengthLte.rsrp,
+                            cellInfoLte.signalStrengthLte.rsrq,
+                            cellInfoLte.signalStrengthLte.rssnr,
+                            cellInfoLte.signalStrengthLte.cqi,
+                            cellInfoLte.signalStrengthLte.timingAdvance);
                     break;
                 }
 
                 case CellInfoType.WCDMA: {
                     CellInfoWcdma cellInfoWcdma = record.wcdma.get(0);
-                    p.writeInt(Integer.parseInt(cellInfoWcdma.cellIdentityWcdma.mcc));
-                    p.writeInt(Integer.parseInt(cellInfoWcdma.cellIdentityWcdma.mnc));
-                    p.writeInt(cellInfoWcdma.cellIdentityWcdma.lac);
-                    p.writeInt(cellInfoWcdma.cellIdentityWcdma.cid);
-                    p.writeInt(cellInfoWcdma.cellIdentityWcdma.psc);
-                    p.writeInt(cellInfoWcdma.cellIdentityWcdma.uarfcn);
-                    p.writeInt(cellInfoWcdma.signalStrengthWcdma.signalStrength);
-                    p.writeInt(cellInfoWcdma.signalStrengthWcdma.bitErrorRate);
+                    writeToParcelForWcdma(
+                            p,
+                            cellInfoWcdma.cellIdentityWcdma.lac,
+                            cellInfoWcdma.cellIdentityWcdma.cid,
+                            cellInfoWcdma.cellIdentityWcdma.psc,
+                            cellInfoWcdma.cellIdentityWcdma.uarfcn,
+                            cellInfoWcdma.cellIdentityWcdma.mcc,
+                            cellInfoWcdma.cellIdentityWcdma.mnc,
+                            EMPTY_ALPHA_LONG,
+                            EMPTY_ALPHA_SHORT,
+                            cellInfoWcdma.signalStrengthWcdma.signalStrength,
+                            cellInfoWcdma.signalStrengthWcdma.bitErrorRate);
+                    break;
+                }
+
+                default:
+                    throw new RuntimeException("unexpected cellinfotype: " + record.cellInfoType);
+            }
+
+            p.setDataPosition(0);
+            CellInfo InfoRec = CellInfo.CREATOR.createFromParcel(p);
+            p.recycle();
+            response.add(InfoRec);
+        }
+
+        return response;
+    }
+
+    /**
+     * Convert CellInfo defined in 1.2/types.hal to CellInfo type.
+     * @param records List of CellInfo defined in 1.2/types.hal
+     * @return List of converted CellInfo object
+     */
+    @VisibleForTesting
+    public static ArrayList<CellInfo> convertHalCellInfoList_1_2(
+            ArrayList<android.hardware.radio.V1_2.CellInfo> records) {
+        ArrayList<CellInfo> response = new ArrayList<CellInfo>(records.size());
+
+        for (android.hardware.radio.V1_2.CellInfo record : records) {
+            // first convert RIL CellInfo to Parcel
+            Parcel p = Parcel.obtain();
+            p.writeInt(record.cellInfoType);
+            p.writeInt(record.registered ? 1 : 0);
+            p.writeInt(record.timeStampType);
+            p.writeLong(record.timeStamp);
+            switch (record.cellInfoType) {
+                case CellInfoType.GSM: {
+                    android.hardware.radio.V1_2.CellInfoGsm cellInfoGsm = record.gsm.get(0);
+                    writeToParcelForGsm(
+                            p,
+                            cellInfoGsm.cellIdentityGsm.base.lac,
+                            cellInfoGsm.cellIdentityGsm.base.cid,
+                            cellInfoGsm.cellIdentityGsm.base.arfcn,
+                            Byte.toUnsignedInt(cellInfoGsm.cellIdentityGsm.base.bsic),
+                            cellInfoGsm.cellIdentityGsm.base.mcc,
+                            cellInfoGsm.cellIdentityGsm.base.mnc,
+                            cellInfoGsm.cellIdentityGsm.operatorNames.alphaLong,
+                            cellInfoGsm.cellIdentityGsm.operatorNames.alphaShort,
+                            cellInfoGsm.signalStrengthGsm.signalStrength,
+                            cellInfoGsm.signalStrengthGsm.bitErrorRate,
+                            cellInfoGsm.signalStrengthGsm.timingAdvance);
+                    break;
+                }
+
+                case CellInfoType.CDMA: {
+                    android.hardware.radio.V1_2.CellInfoCdma cellInfoCdma = record.cdma.get(0);
+                    writeToParcelForCdma(
+                            p,
+                            cellInfoCdma.cellIdentityCdma.base.networkId,
+                            cellInfoCdma.cellIdentityCdma.base.systemId,
+                            cellInfoCdma.cellIdentityCdma.base.baseStationId,
+                            cellInfoCdma.cellIdentityCdma.base.longitude,
+                            cellInfoCdma.cellIdentityCdma.base.latitude,
+                            cellInfoCdma.cellIdentityCdma.operatorNames.alphaLong,
+                            cellInfoCdma.cellIdentityCdma.operatorNames.alphaShort,
+                            cellInfoCdma.signalStrengthCdma.dbm,
+                            cellInfoCdma.signalStrengthCdma.ecio,
+                            cellInfoCdma.signalStrengthEvdo.dbm,
+                            cellInfoCdma.signalStrengthEvdo.ecio,
+                            cellInfoCdma.signalStrengthEvdo.signalNoiseRatio);
+                    break;
+                }
+
+                case CellInfoType.LTE: {
+                    android.hardware.radio.V1_2.CellInfoLte cellInfoLte = record.lte.get(0);
+                    writeToParcelForLte(
+                            p,
+                            cellInfoLte.cellIdentityLte.base.ci,
+                            cellInfoLte.cellIdentityLte.base.pci,
+                            cellInfoLte.cellIdentityLte.base.tac,
+                            cellInfoLte.cellIdentityLte.base.earfcn,
+                            cellInfoLte.cellIdentityLte.base.mcc,
+                            cellInfoLte.cellIdentityLte.base.mnc,
+                            cellInfoLte.cellIdentityLte.operatorNames.alphaLong,
+                            cellInfoLte.cellIdentityLte.operatorNames.alphaShort,
+                            cellInfoLte.signalStrengthLte.signalStrength,
+                            cellInfoLte.signalStrengthLte.rsrp,
+                            cellInfoLte.signalStrengthLte.rsrq,
+                            cellInfoLte.signalStrengthLte.rssnr,
+                            cellInfoLte.signalStrengthLte.cqi,
+                            cellInfoLte.signalStrengthLte.timingAdvance);
+                    break;
+                }
+
+                case CellInfoType.WCDMA: {
+                    android.hardware.radio.V1_2.CellInfoWcdma cellInfoWcdma = record.wcdma.get(0);
+                    writeToParcelForWcdma(
+                            p,
+                            cellInfoWcdma.cellIdentityWcdma.base.lac,
+                            cellInfoWcdma.cellIdentityWcdma.base.cid,
+                            cellInfoWcdma.cellIdentityWcdma.base.psc,
+                            cellInfoWcdma.cellIdentityWcdma.base.uarfcn,
+                            cellInfoWcdma.cellIdentityWcdma.base.mcc,
+                            cellInfoWcdma.cellIdentityWcdma.base.mnc,
+                            cellInfoWcdma.cellIdentityWcdma.operatorNames.alphaLong,
+                            cellInfoWcdma.cellIdentityWcdma.operatorNames.alphaShort,
+                            cellInfoWcdma.signalStrengthWcdma.signalStrength,
+                            cellInfoWcdma.signalStrengthWcdma.bitErrorRate);
                     break;
                 }
 
diff --git a/com/android/internal/telephony/RadioIndication.java b/com/android/internal/telephony/RadioIndication.java
index a7d2418..bcae450 100644
--- a/com/android/internal/telephony/RadioIndication.java
+++ b/com/android/internal/telephony/RadioIndication.java
@@ -640,6 +640,12 @@
         responseCellInfos(indicationType, result);
     }
 
+    /** Incremental network scan results with HAL V1_2 */
+    public void networkScanResult_1_2(int indicationType,
+                                      android.hardware.radio.V1_2.NetworkScanResult result) {
+        responseCellInfos_1_2(indicationType, result);
+    }
+
     public void imsNetworkStateChanged(int indicationType) {
         mRil.processIndication(indicationType);
 
@@ -842,4 +848,15 @@
         if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_NETWORK_SCAN_RESULT, nsr);
         mRil.mRilNetworkScanResultRegistrants.notifyRegistrants(new AsyncResult(null, nsr, null));
     }
+
+    private void responseCellInfos_1_2(int indicationType,
+                                       android.hardware.radio.V1_2.NetworkScanResult result) {
+        mRil.processIndication(indicationType);
+
+        NetworkScanResult nsr = null;
+        ArrayList<CellInfo> infos = RIL.convertHalCellInfoList_1_2(result.networkInfos);
+        nsr = new NetworkScanResult(result.status, result.error, infos);
+        if (RIL.RILJ_LOGD) mRil.unsljLogRet(RIL_UNSOL_NETWORK_SCAN_RESULT, nsr);
+        mRil.mRilNetworkScanResultRegistrants.notifyRegistrants(new AsyncResult(null, nsr, null));
+    }
 }
diff --git a/com/android/internal/telephony/RadioResponse.java b/com/android/internal/telephony/RadioResponse.java
index 3990d24..84c1d57 100644
--- a/com/android/internal/telephony/RadioResponse.java
+++ b/com/android/internal/telephony/RadioResponse.java
@@ -946,6 +946,16 @@
 
     /**
      * @param responseInfo Response info struct containing response type, serial no. and error
+     * @param cellInfo List of current cell information known to radio
+     */
+    public void getCellInfoListResponse_1_2(
+            RadioResponseInfo responseInfo,
+            ArrayList<android.hardware.radio.V1_2.CellInfo> cellInfo) {
+        responseCellInfoList_1_2(responseInfo, cellInfo);
+    }
+
+    /**
+     * @param responseInfo Response info struct containing response type, serial no. and error
      */
     public void setCellInfoListRateResponse(RadioResponseInfo responseInfo) {
         responseVoid(responseInfo);
@@ -1681,6 +1691,20 @@
         }
     }
 
+    private void responseCellInfoList_1_2(
+            RadioResponseInfo responseInfo,
+            ArrayList<android.hardware.radio.V1_2.CellInfo> cellInfo) {
+        RILRequest rr = mRil.processResponse(responseInfo);
+
+        if (rr != null) {
+            ArrayList<CellInfo> ret = RIL.convertHalCellInfoList_1_2(cellInfo);
+            if (responseInfo.error == RadioError.NONE) {
+                sendMessageResponse(rr.mResult, ret);
+            }
+            mRil.processResponseDone(rr, responseInfo, ret);
+        }
+    }
+
     private void responseActivityData(RadioResponseInfo responseInfo,
                                       ActivityStatsInfo activityInfo) {
         RILRequest rr = mRil.processResponse(responseInfo);
diff --git a/com/android/internal/telephony/ServiceStateTracker.java b/com/android/internal/telephony/ServiceStateTracker.java
index 0f4c9e4..a9523bb 100644
--- a/com/android/internal/telephony/ServiceStateTracker.java
+++ b/com/android/internal/telephony/ServiceStateTracker.java
@@ -210,6 +210,7 @@
     protected static final int EVENT_RADIO_POWER_FROM_CARRIER          = 51;
     protected static final int EVENT_SIM_NOT_INSERTED                  = 52;
     protected static final int EVENT_IMS_SERVICE_STATE_CHANGED         = 53;
+    protected static final int EVENT_RADIO_POWER_OFF_DONE              = 54;
 
     protected static final String TIMEZONE_PROPERTY = "persist.sys.timezone";
 
@@ -1130,6 +1131,16 @@
                 }
                 break;
 
+            case EVENT_RADIO_POWER_OFF_DONE:
+                if (DBG) log("EVENT_RADIO_POWER_OFF_DONE");
+                if (mDeviceShuttingDown && mCi.getRadioState().isAvailable()) {
+                    // during shutdown the modem may not send radio state changed event
+                    // as a result of radio power request
+                    // Hence, issuing shut down regardless of radio power response
+                    mCi.requestShutdown(null);
+                }
+                break;
+
             // GSM
             case EVENT_SIM_READY:
                 // Reset the mPreviousSubId so we treat a SIM power bounce
@@ -4403,7 +4414,7 @@
             mPhone.mCT.mForegroundCall.hangupIfAlive();
         }
 
-        mCi.setRadioPower(false, null);
+        mCi.setRadioPower(false, obtainMessage(EVENT_RADIO_POWER_OFF_DONE));
 
     }
 
diff --git a/com/android/internal/telephony/SubscriptionInfoUpdater.java b/com/android/internal/telephony/SubscriptionInfoUpdater.java
index 1a2e09c..983c436 100644
--- a/com/android/internal/telephony/SubscriptionInfoUpdater.java
+++ b/com/android/internal/telephony/SubscriptionInfoUpdater.java
@@ -273,7 +273,8 @@
                 if (ar.exception == null) {
                     if (ar.result != null) {
                         byte[] data = (byte[])ar.result;
-                        mIccId[slotId] = IccUtils.bchToString(data, 0, data.length);
+                        mIccId[slotId] = stripIccIdSuffix(
+                                IccUtils.bchToString(data, 0, data.length));
                     } else {
                         logd("Null ar");
                         mIccId[slotId] = ICCID_STRING_FOR_NO_SIM;
@@ -399,11 +400,11 @@
             logd("handleSimLoaded: IccRecords null");
             return;
         }
-        if (records.getFullIccId() == null) {
+        if (stripIccIdSuffix(records.getFullIccId()) == null) {
             logd("onRecieve: IccID null");
             return;
         }
-        mIccId[slotId] = records.getFullIccId();
+        mIccId[slotId] = stripIccIdSuffix(records.getFullIccId());
 
         if (isAllIccIdQueryDone()) {
             updateSubscriptionInfoByIccId();
@@ -820,6 +821,15 @@
         IntentBroadcaster.getInstance().broadcastStickyIntent(i, slotId);
     }
 
+    // Remove trailing F's from full hexadecimal IccId, as they should be considered padding
+    private String stripIccIdSuffix(String hexIccId) {
+        if (hexIccId == null) {
+            return null;
+        } else {
+            return hexIccId.replaceAll("(?i)f*$", "");
+        }
+    }
+
     public void dispose() {
         logd("[dispose]");
         mContext.unregisterReceiver(sReceiver);
diff --git a/com/android/internal/telephony/TelephonyComponentFactory.java b/com/android/internal/telephony/TelephonyComponentFactory.java
index 193d29e..82a0823 100644
--- a/com/android/internal/telephony/TelephonyComponentFactory.java
+++ b/com/android/internal/telephony/TelephonyComponentFactory.java
@@ -77,6 +77,10 @@
         return new CarrierActionAgent(phone);
     }
 
+    public CarrierIdentifier makeCarrierIdentifier(Phone phone) {
+        return new CarrierIdentifier(phone);
+    }
+
     public IccPhoneBookInterfaceManager makeIccPhoneBookInterfaceManager(Phone phone) {
         return new IccPhoneBookInterfaceManager(phone);
     }
diff --git a/com/android/internal/telephony/cat/ComprehensionTlv.java b/com/android/internal/telephony/cat/ComprehensionTlv.java
index e2522a4..d4ad532 100644
--- a/com/android/internal/telephony/cat/ComprehensionTlv.java
+++ b/com/android/internal/telephony/cat/ComprehensionTlv.java
@@ -29,7 +29,7 @@
  *
  * {@hide}
  */
-class ComprehensionTlv {
+public class ComprehensionTlv {
     private static final String LOG_TAG = "ComprehensionTlv";
     private int mTag;
     private boolean mCr;
diff --git a/com/android/internal/telephony/cdma/sms/CdmaSmsAddress.java b/com/android/internal/telephony/cdma/sms/CdmaSmsAddress.java
index 5f2e561..d27a758 100644
--- a/com/android/internal/telephony/cdma/sms/CdmaSmsAddress.java
+++ b/com/android/internal/telephony/cdma/sms/CdmaSmsAddress.java
@@ -18,6 +18,7 @@
 
 import android.util.SparseBooleanArray;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.SmsAddress;
 import com.android.internal.telephony.cdma.sms.UserData;
 import com.android.internal.util.HexDump;
@@ -113,8 +114,8 @@
      * share code and logic with GSM.  Also, gather all DTMF/BCD
      * processing code in one place.
      */
-
-    private static byte[] parseToDtmf(String address) {
+    @VisibleForTesting
+    public static byte[] parseToDtmf(String address) {
         int digits = address.length();
         byte[] result = new byte[digits];
         for (int i = 0; i < digits; i++) {
@@ -196,33 +197,46 @@
     public static CdmaSmsAddress parse(String address) {
         CdmaSmsAddress addr = new CdmaSmsAddress();
         addr.address = address;
-        addr.ton = CdmaSmsAddress.TON_UNKNOWN;
-        byte[] origBytes = null;
+        addr.ton = TON_UNKNOWN;
+        addr.digitMode = DIGIT_MODE_4BIT_DTMF;
+        addr.numberPlan = NUMBERING_PLAN_UNKNOWN;
+        addr.numberMode = NUMBER_MODE_NOT_DATA_NETWORK;
+
+        byte[] origBytes;
         String filteredAddr = filterNumericSugar(address);
-        if (filteredAddr != null) {
-            origBytes = parseToDtmf(filteredAddr);
-        }
-        if (origBytes != null) {
-            addr.digitMode = DIGIT_MODE_4BIT_DTMF;
-            addr.numberMode = NUMBER_MODE_NOT_DATA_NETWORK;
-            if (address.indexOf('+') != -1) {
-                addr.ton = TON_INTERNATIONAL_OR_IP;
-            }
-        } else {
-            filteredAddr = filterWhitespace(address);
-            origBytes = UserData.stringToAscii(filteredAddr);
-            if (origBytes == null) {
-                return null;
-            }
+        if (address.contains("+") || filteredAddr == null) {
+            // 3GPP2 C.S0015-B section 3.4.3.3 Address Parameters
+            // NUMBER_MODE should set to 1 for network address and email address.
             addr.digitMode = DIGIT_MODE_8BIT_CHAR;
             addr.numberMode = NUMBER_MODE_DATA_NETWORK;
-            if (address.indexOf('@') != -1) {
+            filteredAddr = filterWhitespace(address);
+
+            if (address.contains("@")) {
+                // This is an email address
                 addr.ton = TON_NATIONAL_OR_EMAIL;
+            } else if (address.contains("+") && filterNumericSugar(address) != null) {
+                // This is an international number
+                // 3GPP2 C.S0015-B section 3.4.3.3 Address Parameters
+                // digit mode is set to 1 and number mode is set to 0, type of number should set
+                // to the value correspond to the value in 3GPP2 C.S005-D, table2.7.1.3.2.4-2
+                addr.ton = TON_INTERNATIONAL_OR_IP;
+                addr.numberPlan = NUMBERING_PLAN_ISDN_TELEPHONY;
+                addr.numberMode = NUMBER_MODE_NOT_DATA_NETWORK;
+                filteredAddr = filterNumericSugar(address);
             }
+
+            origBytes = UserData.stringToAscii(filteredAddr);
+        } else {
+            // The address is not an international number and it only contains digit and *#
+            origBytes = parseToDtmf(filteredAddr);
         }
+
+        if (origBytes == null) {
+            return null;
+        }
+
         addr.origBytes = origBytes;
         addr.numberOfDigits = origBytes.length;
         return addr;
     }
-
 }
diff --git a/com/android/internal/telephony/dataconnection/ApnSetting.java b/com/android/internal/telephony/dataconnection/ApnSetting.java
index ce8318d..0eeed6a 100644
--- a/com/android/internal/telephony/dataconnection/ApnSetting.java
+++ b/com/android/internal/telephony/dataconnection/ApnSetting.java
@@ -23,6 +23,7 @@
 import android.telephony.Rlog;
 import android.telephony.ServiceState;
 import android.text.TextUtils;
+import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.Phone;
@@ -48,6 +49,7 @@
 
     static final String V2_FORMAT_REGEX = "^\\[ApnSettingV2\\]\\s*";
     static final String V3_FORMAT_REGEX = "^\\[ApnSettingV3\\]\\s*";
+    static final String TAG = "ApnSetting";
 
     public final String carrier;
     public final String apn;
@@ -100,6 +102,7 @@
       *   "spn": Service provider name.
       *   "imsi": IMSI.
       *   "gid": Group identifier level 1.
+      *   "iccid": ICCID
       */
     public final String mvnoType;
     /**
@@ -107,6 +110,7 @@
       *   "spn": A MOBILE, BEN NL
       *   "imsi": 302720x94, 2060188
       *   "gid": 4E, 33
+      *   "iccid": 898603 etc.
       */
     public final String mvnoMatchData;
 
@@ -362,6 +366,17 @@
         return false;
     }
 
+    private static boolean iccidMatches(String mvnoData, String iccId) {
+        String[] mvnoIccidList = mvnoData.split(",");
+        for (String mvnoIccid : mvnoIccidList) {
+            if (iccId.startsWith(mvnoIccid)) {
+                Log.d(TAG, "mvno icc id match found");
+                return true;
+            }
+        }
+        return false;
+    }
+
     private static boolean imsiMatches(String imsiDB, String imsiSIM) {
         // Note: imsiDB value has digit number or 'x' character for seperating USIM information
         // for MVNO operator. And then digit number is matched at same order and 'x' character
@@ -404,7 +419,13 @@
                     gid1.substring(0, mvno_match_data_length).equalsIgnoreCase(mvnoMatchData)) {
                 return true;
             }
+        } else if (mvnoType.equalsIgnoreCase("iccid")) {
+            String iccId = r.getIccId();
+            if ((iccId != null) && iccidMatches(mvnoMatchData, iccId)) {
+                return true;
+            }
         }
+
         return false;
     }
 
diff --git a/com/android/internal/telephony/dataconnection/DataCallResponse.java b/com/android/internal/telephony/dataconnection/DataCallResponse.java
index bc02b97..5196bce 100644
--- a/com/android/internal/telephony/dataconnection/DataCallResponse.java
+++ b/com/android/internal/telephony/dataconnection/DataCallResponse.java
@@ -17,18 +17,9 @@
 
 package com.android.internal.telephony.dataconnection;
 
-import android.net.LinkAddress;
-import android.net.LinkProperties;
-import android.net.NetworkUtils;
-import android.net.RouteInfo;
 import android.os.SystemProperties;
-import android.telephony.Rlog;
 import android.text.TextUtils;
 
-import java.net.Inet4Address;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-
 /**
  * This is RIL_Data_Call_Response_v5 from ril.h
  */
@@ -44,34 +35,10 @@
     public final String ifname;
     public final String [] addresses;
     public final String [] dnses;
-    // TODO: Change this to final if possible.
-    public String[] gateways;
+    public final String[] gateways;
     public final String [] pcscf;
     public final int mtu;
 
-    /**
-     * Class returned by onSetupConnectionCompleted.
-     */
-    public enum SetupResult {
-        SUCCESS,
-        ERR_BadCommand,
-        ERR_UnacceptableParameter,
-        ERR_GetLastErrorFromRil,
-        ERR_Stale,
-        ERR_RilError;
-
-        public DcFailCause mFailCause;
-
-        SetupResult() {
-            mFailCause = DcFailCause.fromInt(0);
-        }
-
-        @Override
-        public String toString() {
-            return name() + "  SetupResult.mFailCause=" + mFailCause;
-        }
-    }
-
     public DataCallResponse(int status, int suggestedRetryTime, int cid, int active, String type,
                             String ifname, String addresses, String dnses, String gateways,
                             String pcscf, int mtu) {
@@ -86,7 +53,22 @@
         }
         this.addresses = TextUtils.isEmpty(addresses) ? new String[0] : addresses.split(" ");
         this.dnses = TextUtils.isEmpty(dnses) ? new String[0] : dnses.split(" ");
-        this.gateways = TextUtils.isEmpty(gateways) ? new String[0] : gateways.split(" ");
+
+        String[] myGateways = TextUtils.isEmpty(gateways)
+                ? new String[0] : gateways.split(" ");
+
+        // set gateways
+        if (myGateways.length == 0) {
+            String propertyPrefix = "net." + this.ifname + ".";
+            String sysGateways = SystemProperties.get(propertyPrefix + "gw");
+            if (sysGateways != null) {
+                myGateways = sysGateways.split(" ");
+            } else {
+                myGateways = new String[0];
+            }
+        }
+        this.gateways = myGateways;
+
         this.pcscf = TextUtils.isEmpty(pcscf) ? new String[0] : pcscf.split(" ");
         this.mtu = mtu;
     }
@@ -129,147 +111,4 @@
         sb.append("]}");
         return sb.toString();
     }
-
-    public SetupResult setLinkProperties(LinkProperties linkProperties,
-            boolean okToUseSystemPropertyDns) {
-        SetupResult result;
-
-        // Start with clean network properties and if we have
-        // a failure we'll clear again at the bottom of this code.
-        if (linkProperties == null)
-            linkProperties = new LinkProperties();
-        else
-            linkProperties.clear();
-
-        if (status == DcFailCause.NONE.getErrorCode()) {
-            String propertyPrefix = "net." + ifname + ".";
-
-            try {
-                // set interface name
-                linkProperties.setInterfaceName(ifname);
-
-                // set link addresses
-                if (addresses != null && addresses.length > 0) {
-                    for (String addr : addresses) {
-                        addr = addr.trim();
-                        if (addr.isEmpty()) continue;
-                        LinkAddress la;
-                        int addrPrefixLen;
-
-                        String [] ap = addr.split("/");
-                        if (ap.length == 2) {
-                            addr = ap[0];
-                            addrPrefixLen = Integer.parseInt(ap[1]);
-                        } else {
-                            addrPrefixLen = 0;
-                        }
-                        InetAddress ia;
-                        try {
-                            ia = NetworkUtils.numericToInetAddress(addr);
-                        } catch (IllegalArgumentException e) {
-                            throw new UnknownHostException("Non-numeric ip addr=" + addr);
-                        }
-                        if (! ia.isAnyLocalAddress()) {
-                            if (addrPrefixLen == 0) {
-                                // Assume point to point
-                                addrPrefixLen = (ia instanceof Inet4Address) ? 32 : 128;
-                            }
-                            if (DBG) Rlog.d(LOG_TAG, "addr/pl=" + addr + "/" + addrPrefixLen);
-                            try {
-                                la = new LinkAddress(ia, addrPrefixLen);
-                            } catch (IllegalArgumentException e) {
-                                throw new UnknownHostException("Bad parameter for LinkAddress, ia="
-                                        + ia.getHostAddress() + "/" + addrPrefixLen);
-                            }
-
-                            linkProperties.addLinkAddress(la);
-                        }
-                    }
-                } else {
-                    throw new UnknownHostException("no address for ifname=" + ifname);
-                }
-
-                // set dns servers
-                if (dnses != null && dnses.length > 0) {
-                    for (String addr : dnses) {
-                        addr = addr.trim();
-                        if (addr.isEmpty()) continue;
-                        InetAddress ia;
-                        try {
-                            ia = NetworkUtils.numericToInetAddress(addr);
-                        } catch (IllegalArgumentException e) {
-                            throw new UnknownHostException("Non-numeric dns addr=" + addr);
-                        }
-                        if (! ia.isAnyLocalAddress()) {
-                            linkProperties.addDnsServer(ia);
-                        }
-                    }
-                } else if (okToUseSystemPropertyDns){
-                    String dnsServers[] = new String[2];
-                    dnsServers[0] = SystemProperties.get(propertyPrefix + "dns1");
-                    dnsServers[1] = SystemProperties.get(propertyPrefix + "dns2");
-                    for (String dnsAddr : dnsServers) {
-                        dnsAddr = dnsAddr.trim();
-                        if (dnsAddr.isEmpty()) continue;
-                        InetAddress ia;
-                        try {
-                            ia = NetworkUtils.numericToInetAddress(dnsAddr);
-                        } catch (IllegalArgumentException e) {
-                            throw new UnknownHostException("Non-numeric dns addr=" + dnsAddr);
-                        }
-                        if (! ia.isAnyLocalAddress()) {
-                            linkProperties.addDnsServer(ia);
-                        }
-                    }
-                } else {
-                    throw new UnknownHostException("Empty dns response and no system default dns");
-                }
-
-                // set gateways
-                if ((gateways == null) || (gateways.length == 0)) {
-                    String sysGateways = SystemProperties.get(propertyPrefix + "gw");
-                    if (sysGateways != null) {
-                        gateways = sysGateways.split(" ");
-                    } else {
-                        gateways = new String[0];
-                    }
-                }
-                for (String addr : gateways) {
-                    addr = addr.trim();
-                    if (addr.isEmpty()) continue;
-                    InetAddress ia;
-                    try {
-                        ia = NetworkUtils.numericToInetAddress(addr);
-                    } catch (IllegalArgumentException e) {
-                        throw new UnknownHostException("Non-numeric gateway addr=" + addr);
-                    }
-                    // Allow 0.0.0.0 or :: as a gateway; this indicates a point-to-point interface.
-                    linkProperties.addRoute(new RouteInfo(ia));
-                }
-
-                // set interface MTU
-                // this may clobber the setting read from the APN db, but that's ok
-                linkProperties.setMtu(mtu);
-
-                result = SetupResult.SUCCESS;
-            } catch (UnknownHostException e) {
-                Rlog.d(LOG_TAG, "setLinkProperties: UnknownHostException " + e);
-                e.printStackTrace();
-                result = SetupResult.ERR_UnacceptableParameter;
-            }
-        } else {
-            result = SetupResult.ERR_RilError;
-        }
-
-        // An error occurred so clear properties
-        if (result != SetupResult.SUCCESS) {
-            if(DBG) {
-                Rlog.d(LOG_TAG, "setLinkProperties: error clearing LinkProperties " +
-                        "status=" + status + " result=" + result);
-            }
-            linkProperties.clear();
-        }
-
-        return result;
-    }
 }
diff --git a/com/android/internal/telephony/dataconnection/DataConnection.java b/com/android/internal/telephony/dataconnection/DataConnection.java
index 9e4b29c..3dbcc3d 100644
--- a/com/android/internal/telephony/dataconnection/DataConnection.java
+++ b/com/android/internal/telephony/dataconnection/DataConnection.java
@@ -19,12 +19,15 @@
 import android.app.PendingIntent;
 import android.content.Context;
 import android.net.ConnectivityManager;
+import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.NetworkAgent;
 import android.net.NetworkCapabilities;
 import android.net.NetworkInfo;
 import android.net.NetworkMisc;
+import android.net.NetworkUtils;
 import android.net.ProxyInfo;
+import android.net.RouteInfo;
 import android.net.StringNetworkSpecifier;
 import android.os.AsyncResult;
 import android.os.Looper;
@@ -34,6 +37,7 @@
 import android.telephony.Rlog;
 import android.telephony.ServiceState;
 import android.telephony.TelephonyManager;
+import android.telephony.data.DataProfile;
 import android.text.TextUtils;
 import android.util.LocalLog;
 import android.util.Pair;
@@ -59,7 +63,9 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.io.StringWriter;
+import java.net.Inet4Address;
 import java.net.InetAddress;
+import java.net.UnknownHostException;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
@@ -283,7 +289,7 @@
     }
 
     public static class UpdateLinkPropertyResult {
-        public DataCallResponse.SetupResult setupResult = DataCallResponse.SetupResult.SUCCESS;
+        public SetupResult setupResult = SetupResult.SUCCESS;
         public LinkProperties oldLp;
         public LinkProperties newLp;
         public UpdateLinkPropertyResult(LinkProperties curLp) {
@@ -292,6 +298,29 @@
         }
     }
 
+    /**
+     * Class returned by onSetupConnectionCompleted.
+     */
+    public enum SetupResult {
+        SUCCESS,
+        ERR_BadCommand,
+        ERR_UnacceptableParameter,
+        ERR_GetLastErrorFromRil,
+        ERR_Stale,
+        ERR_RilError;
+
+        public DcFailCause mFailCause;
+
+        SetupResult() {
+            mFailCause = DcFailCause.fromInt(0);
+        }
+
+        @Override
+        public String toString() {
+            return name() + "  SetupResult.mFailCause=" + mFailCause;
+        }
+    }
+
     public boolean isIpv4Connected() {
         boolean ret = false;
         Collection <InetAddress> addresses = mLinkProperties.getAddresses();
@@ -331,12 +360,12 @@
 
         if (newState == null) return result;
 
-        DataCallResponse.SetupResult setupResult;
+        SetupResult setupResult;
         result.newLp = new LinkProperties();
 
         // set link properties based on data call response
         result.setupResult = setLinkProperties(newState, result.newLp);
-        if (result.setupResult != DataCallResponse.SetupResult.SUCCESS) {
+        if (result.setupResult != SetupResult.SUCCESS) {
             if (DBG) log("updateLinkProperty failed : " + result.setupResult);
             return result;
         }
@@ -465,7 +494,7 @@
         Message msg = obtainMessage(EVENT_SETUP_DATA_CONNECTION_DONE, cp);
         msg.obj = cp;
 
-        DataProfile dp = new DataProfile(mApnSetting, cp.mProfileId);
+        DataProfile dp = DcTracker.createDataProfile(mApnSetting, cp.mProfileId);
 
         // We need to use the actual modem roaming state instead of the framework roaming state
         // here. This flag is only passed down to ril_service for picking the correct protocol (for
@@ -665,16 +694,16 @@
      * @param ar is the result
      * @return SetupResult.
      */
-    private DataCallResponse.SetupResult onSetupConnectionCompleted(AsyncResult ar) {
+    private SetupResult onSetupConnectionCompleted(AsyncResult ar) {
         DataCallResponse response = (DataCallResponse) ar.result;
         ConnectionParams cp = (ConnectionParams) ar.userObj;
-        DataCallResponse.SetupResult result;
+        SetupResult result;
 
         if (cp.mTag != mTag) {
             if (DBG) {
                 log("onSetupConnectionCompleted stale cp.tag=" + cp.mTag + ", mtag=" + mTag);
             }
-            result = DataCallResponse.SetupResult.ERR_Stale;
+            result = SetupResult.ERR_Stale;
         } else if (ar.exception != null) {
             if (DBG) {
                 log("onSetupConnectionCompleted failed, ar.exception=" + ar.exception +
@@ -684,14 +713,14 @@
             if (ar.exception instanceof CommandException
                     && ((CommandException) (ar.exception)).getCommandError()
                     == CommandException.Error.RADIO_NOT_AVAILABLE) {
-                result = DataCallResponse.SetupResult.ERR_BadCommand;
+                result = SetupResult.ERR_BadCommand;
                 result.mFailCause = DcFailCause.RADIO_NOT_AVAILABLE;
             } else {
-                result = DataCallResponse.SetupResult.ERR_RilError;
+                result = SetupResult.ERR_RilError;
                 result.mFailCause = DcFailCause.fromInt(response.status);
             }
         } else if (response.status != 0) {
-            result = DataCallResponse.SetupResult.ERR_RilError;
+            result = SetupResult.ERR_RilError;
             result.mFailCause = DcFailCause.fromInt(response.status);
         } else {
             if (DBG) log("onSetupConnectionCompleted received successful DataCallResponse");
@@ -995,18 +1024,139 @@
         return InetAddress.isNumeric(address);
     }
 
-    private DataCallResponse.SetupResult setLinkProperties(DataCallResponse response,
-            LinkProperties lp) {
+    private SetupResult setLinkProperties(DataCallResponse response,
+            LinkProperties linkProperties) {
         // Check if system property dns usable
-        boolean okToUseSystemPropertyDns = false;
         String propertyPrefix = "net." + response.ifname + ".";
         String dnsServers[] = new String[2];
         dnsServers[0] = SystemProperties.get(propertyPrefix + "dns1");
         dnsServers[1] = SystemProperties.get(propertyPrefix + "dns2");
-        okToUseSystemPropertyDns = isDnsOk(dnsServers);
+        boolean okToUseSystemPropertyDns = isDnsOk(dnsServers);
 
-        // set link properties based on data call response
-        return response.setLinkProperties(lp, okToUseSystemPropertyDns);
+        SetupResult result;
+
+        // Start with clean network properties and if we have
+        // a failure we'll clear again at the bottom of this code.
+        linkProperties.clear();
+
+        if (response.status == DcFailCause.NONE.getErrorCode()) {
+            try {
+                // set interface name
+                linkProperties.setInterfaceName(response.ifname);
+
+                // set link addresses
+                if (response.addresses != null && response.addresses.length > 0) {
+                    for (String addr : response.addresses) {
+                        addr = addr.trim();
+                        if (addr.isEmpty()) continue;
+                        LinkAddress la;
+                        int addrPrefixLen;
+
+                        String [] ap = addr.split("/");
+                        if (ap.length == 2) {
+                            addr = ap[0];
+                            addrPrefixLen = Integer.parseInt(ap[1]);
+                        } else {
+                            addrPrefixLen = 0;
+                        }
+                        InetAddress ia;
+                        try {
+                            ia = NetworkUtils.numericToInetAddress(addr);
+                        } catch (IllegalArgumentException e) {
+                            throw new UnknownHostException("Non-numeric ip addr=" + addr);
+                        }
+                        if (!ia.isAnyLocalAddress()) {
+                            if (addrPrefixLen == 0) {
+                                // Assume point to point
+                                addrPrefixLen = (ia instanceof Inet4Address) ? 32 : 128;
+                            }
+                            if (DBG) log("addr/pl=" + addr + "/" + addrPrefixLen);
+                            try {
+                                la = new LinkAddress(ia, addrPrefixLen);
+                            } catch (IllegalArgumentException e) {
+                                throw new UnknownHostException("Bad parameter for LinkAddress, ia="
+                                        + ia.getHostAddress() + "/" + addrPrefixLen);
+                            }
+
+                            linkProperties.addLinkAddress(la);
+                        }
+                    }
+                } else {
+                    throw new UnknownHostException("no address for ifname=" + response.ifname);
+                }
+
+                // set dns servers
+                if (response.dnses != null && response.dnses.length > 0) {
+                    for (String addr : response.dnses) {
+                        addr = addr.trim();
+                        if (addr.isEmpty()) continue;
+                        InetAddress ia;
+                        try {
+                            ia = NetworkUtils.numericToInetAddress(addr);
+                        } catch (IllegalArgumentException e) {
+                            throw new UnknownHostException("Non-numeric dns addr=" + addr);
+                        }
+                        if (!ia.isAnyLocalAddress()) {
+                            linkProperties.addDnsServer(ia);
+                        }
+                    }
+                } else if (okToUseSystemPropertyDns) {
+                    dnsServers[0] = SystemProperties.get(propertyPrefix + "dns1");
+                    dnsServers[1] = SystemProperties.get(propertyPrefix + "dns2");
+                    for (String dnsAddr : dnsServers) {
+                        dnsAddr = dnsAddr.trim();
+                        if (dnsAddr.isEmpty()) continue;
+                        InetAddress ia;
+                        try {
+                            ia = NetworkUtils.numericToInetAddress(dnsAddr);
+                        } catch (IllegalArgumentException e) {
+                            throw new UnknownHostException("Non-numeric dns addr=" + dnsAddr);
+                        }
+                        if (!ia.isAnyLocalAddress()) {
+                            linkProperties.addDnsServer(ia);
+                        }
+                    }
+                } else {
+                    throw new UnknownHostException("Empty dns response and no system default dns");
+                }
+
+                for (String addr : response.gateways) {
+                    addr = addr.trim();
+                    if (addr.isEmpty()) continue;
+                    InetAddress ia;
+                    try {
+                        ia = NetworkUtils.numericToInetAddress(addr);
+                    } catch (IllegalArgumentException e) {
+                        throw new UnknownHostException("Non-numeric gateway addr=" + addr);
+                    }
+                    // Allow 0.0.0.0 or :: as a gateway; this indicates a point-to-point interface.
+                    linkProperties.addRoute(new RouteInfo(ia));
+                }
+
+                // set interface MTU
+                // this may clobber the setting read from the APN db, but that's ok
+                linkProperties.setMtu(response.mtu);
+
+                result = SetupResult.SUCCESS;
+            } catch (UnknownHostException e) {
+                log("setLinkProperties: UnknownHostException " + e);
+                e.printStackTrace();
+                result = SetupResult.ERR_UnacceptableParameter;
+            }
+        } else {
+            result = SetupResult.ERR_RilError;
+        }
+
+        // An error occurred so clear properties
+        if (result != SetupResult.SUCCESS) {
+            if (DBG) {
+                log("setLinkProperties: error clearing LinkProperties status=" + response.status
+                        + " result=" + result);
+            }
+            linkProperties.clear();
+        }
+
+        return result;
     }
 
     /**
@@ -1421,8 +1571,8 @@
                     ar = (AsyncResult) msg.obj;
                     cp = (ConnectionParams) ar.userObj;
 
-                    DataCallResponse.SetupResult result = onSetupConnectionCompleted(ar);
-                    if (result != DataCallResponse.SetupResult.ERR_Stale) {
+                    SetupResult result = onSetupConnectionCompleted(ar);
+                    if (result != SetupResult.ERR_Stale) {
                         if (mConnectionParams != cp) {
                             loge("DcActivatingState: WEIRD mConnectionsParams:"+ mConnectionParams
                                     + " != cp:" + cp);
diff --git a/com/android/internal/telephony/dataconnection/DataProfile.java b/com/android/internal/telephony/dataconnection/DataProfile.java
deleted file mode 100644
index 48a8107..0000000
--- a/com/android/internal/telephony/dataconnection/DataProfile.java
+++ /dev/null
@@ -1,133 +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.internal.telephony.dataconnection;
-
-import android.telephony.ServiceState;
-import android.text.TextUtils;
-
-import com.android.internal.telephony.RILConstants;
-
-public class DataProfile {
-
-    static final int TYPE_COMMON = 0;
-    static final int TYPE_3GPP = 1;
-    static final int TYPE_3GPP2 = 2;
-
-    //id of the data profile
-    public final int profileId;
-    //the APN to connect to
-    public final String apn;
-    //one of the PDP_type values in TS 27.007 section 10.1.1.
-    //For example, "IP", "IPV6", "IPV4V6", or "PPP".
-    public final String protocol;
-    //authentication protocol used for this PDP context
-    //(None: 0, PAP: 1, CHAP: 2, PAP&CHAP: 3)
-    public final int authType;
-    //the username for APN, or NULL
-    public final String user;
-    //the password for APN, or NULL
-    public final String password;
-    //the profile type, TYPE_COMMON, TYPE_3GPP, TYPE_3GPP2
-    public final int type;
-    //the period in seconds to limit the maximum connections
-    public final int maxConnsTime;
-    //the maximum connections during maxConnsTime
-    public final int maxConns;
-    //the required wait time in seconds after a successful UE initiated
-    //disconnect of a given PDN connection before the device can send
-    //a new PDN connection request for that given PDN
-    public final int waitTime;
-    //true to enable the profile, false to disable
-    public final boolean enabled;
-    //supported APN types bitmap. See RIL_ApnTypes for the value of each bit.
-    public final int supportedApnTypesBitmap;
-    //one of the PDP_type values in TS 27.007 section 10.1.1 used on roaming network.
-    //For example, "IP", "IPV6", "IPV4V6", or "PPP".
-    public final String roamingProtocol;
-    //The bearer bitmap. See RIL_RadioAccessFamily for the value of each bit.
-    public final int bearerBitmap;
-    //maximum transmission unit (MTU) size in bytes
-    public final int mtu;
-    //the MVNO type: possible values are "imsi", "gid", "spn"
-    public final String mvnoType;
-    //MVNO match data. For example, SPN: A MOBILE, BEN NL, ...
-    //IMSI: 302720x94, 2060188, ...
-    //GID: 4E, 33, ...
-    public final String mvnoMatchData;
-    //indicating the data profile was sent to the modem through setDataProfile earlier.
-    public final boolean modemCognitive;
-
-    DataProfile(int profileId, String apn, String protocol, int authType,
-                String user, String password, int type, int maxConnsTime, int maxConns,
-                int waitTime, boolean enabled, int supportedApnTypesBitmap, String roamingProtocol,
-                int bearerBitmap, int mtu, String mvnoType, String mvnoMatchData,
-                boolean modemCognitive) {
-
-        this.profileId = profileId;
-        this.apn = apn;
-        this.protocol = protocol;
-        if (authType == -1) {
-            authType = TextUtils.isEmpty(user) ? RILConstants.SETUP_DATA_AUTH_NONE
-                    : RILConstants.SETUP_DATA_AUTH_PAP_CHAP;
-        }
-        this.authType = authType;
-        this.user = user;
-        this.password = password;
-        this.type = type;
-        this.maxConnsTime = maxConnsTime;
-        this.maxConns = maxConns;
-        this.waitTime = waitTime;
-        this.enabled = enabled;
-
-        this.supportedApnTypesBitmap = supportedApnTypesBitmap;
-        this.roamingProtocol = roamingProtocol;
-        this.bearerBitmap = bearerBitmap;
-        this.mtu = mtu;
-        this.mvnoType = mvnoType;
-        this.mvnoMatchData = mvnoMatchData;
-        this.modemCognitive = modemCognitive;
-    }
-
-    public DataProfile(ApnSetting apn) {
-        this(apn, apn.profileId);
-    }
-
-    public DataProfile(ApnSetting apn, int profileId) {
-        this(profileId, apn.apn, apn.protocol,
-                apn.authType, apn.user, apn.password, apn.bearerBitmask == 0
-                        ? TYPE_COMMON : (ServiceState.bearerBitmapHasCdma(apn.bearerBitmask)
-                        ? TYPE_3GPP2 : TYPE_3GPP),
-                apn.maxConnsTime, apn.maxConns, apn.waitTime, apn.carrierEnabled, apn.typesBitmap,
-                apn.roamingProtocol, apn.bearerBitmask, apn.mtu, apn.mvnoType, apn.mvnoMatchData,
-                apn.modemCognitive);
-    }
-
-    @Override
-    public String toString() {
-        return "DataProfile=" + profileId + "/" + apn + "/" + protocol + "/" + authType
-                + "/" + user + "/" + password + "/" + type + "/" + maxConnsTime
-                + "/" + maxConns + "/" + waitTime + "/" + enabled + "/" + supportedApnTypesBitmap
-                + "/" + roamingProtocol + "/" + bearerBitmap + "/" + mtu + "/" + mvnoType + "/"
-                + mvnoMatchData + "/" + modemCognitive;
-    }
-
-    @Override
-    public boolean equals(Object o) {
-        if (o instanceof DataProfile == false) return false;
-        return (o == this || toString().equals(o.toString()));
-    }
-}
diff --git a/com/android/internal/telephony/dataconnection/DcTracker.java b/com/android/internal/telephony/dataconnection/DcTracker.java
index fb756cd..540ec54 100644
--- a/com/android/internal/telephony/dataconnection/DcTracker.java
+++ b/com/android/internal/telephony/dataconnection/DcTracker.java
@@ -65,6 +65,7 @@
 import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
 import android.telephony.TelephonyManager;
 import android.telephony.cdma.CdmaCellLocation;
+import android.telephony.data.DataProfile;
 import android.telephony.gsm.GsmCellLocation;
 import android.text.TextUtils;
 import android.util.EventLog;
@@ -2093,7 +2094,7 @@
         } else {
             if (DBG) log("setInitialAttachApn: X selected Apn=" + initialAttachApnSetting);
 
-            mPhone.mCi.setInitialAttachApn(new DataProfile(initialAttachApnSetting),
+            mPhone.mCi.setInitialAttachApn(createDataProfile(initialAttachApnSetting),
                     mPhone.getServiceState().getDataRoamingFromRegistration(), null);
         }
     }
@@ -3281,7 +3282,7 @@
             ArrayList<DataProfile> dps = new ArrayList<DataProfile>();
             for (ApnSetting apn : mAllApnSettings) {
                 if (apn.modemCognitive) {
-                    DataProfile dp = new DataProfile(apn);
+                    DataProfile dp = createDataProfile(apn);
                     if (!dps.contains(dp)) {
                         dps.add(dp);
                     }
@@ -4800,4 +4801,25 @@
         }
     }
 
+    private static DataProfile createDataProfile(ApnSetting apn) {
+        return createDataProfile(apn, apn.profileId);
+    }
+
+    @VisibleForTesting
+    public static DataProfile createDataProfile(ApnSetting apn, int profileId) {
+        int profileType;
+        if (apn.bearerBitmask == 0) {
+            profileType = DataProfile.TYPE_COMMON;
+        } else if (ServiceState.bearerBitmapHasCdma(apn.bearerBitmask)) {
+            profileType = DataProfile.TYPE_3GPP2;
+        } else {
+            profileType = DataProfile.TYPE_3GPP;
+        }
+
+        return new DataProfile(profileId, apn.apn, apn.protocol,
+                apn.authType, apn.user, apn.password, profileType,
+                apn.maxConnsTime, apn.maxConns, apn.waitTime, apn.carrierEnabled, apn.typesBitmap,
+                apn.roamingProtocol, apn.bearerBitmask, apn.mtu, apn.mvnoType, apn.mvnoMatchData,
+                apn.modemCognitive);
+    }
 }
diff --git a/com/android/internal/telephony/euicc/EuiccController.java b/com/android/internal/telephony/euicc/EuiccController.java
index ac2a039..dc84718 100644
--- a/com/android/internal/telephony/euicc/EuiccController.java
+++ b/com/android/internal/telephony/euicc/EuiccController.java
@@ -38,6 +38,7 @@
 import android.telephony.euicc.DownloadableSubscription;
 import android.telephony.euicc.EuiccInfo;
 import android.telephony.euicc.EuiccManager;
+import android.text.TextUtils;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -226,6 +227,7 @@
                     addResolutionIntent(extrasIntent,
                             EuiccService.ACTION_RESOLVE_DEACTIVATE_SIM,
                             mCallingPackage,
+                            false /* confirmationCodeRetried */,
                             getOperationForDeactivateSim());
                     break;
                 default:
@@ -306,6 +308,7 @@
                 Intent extrasIntent = new Intent();
                 addResolutionIntent(extrasIntent, EuiccService.ACTION_RESOLVE_NO_PRIVILEGES,
                         mCallingPackage,
+                        false /* confirmationCodeRetried */,
                         EuiccOperation.forDownloadNoPrivileges(
                                 mCallingToken, mSubscription, mSwitchAfterDownload,
                                 mCallingPackage));
@@ -354,6 +357,7 @@
                     Intent extrasIntent = new Intent();
                     addResolutionIntent(extrasIntent, EuiccService.ACTION_RESOLVE_NO_PRIVILEGES,
                             mCallingPackage,
+                            false /* confirmationCodeRetried */,
                             EuiccOperation.forDownloadNoPrivileges(
                                     mCallingToken, subscription, mSwitchAfterDownload,
                                     mCallingPackage));
@@ -407,15 +411,21 @@
                                 addResolutionIntent(extrasIntent,
                                         EuiccService.ACTION_RESOLVE_DEACTIVATE_SIM,
                                         callingPackage,
+                                        false /* confirmationCodeRetried */,
                                         EuiccOperation.forDownloadDeactivateSim(
                                                 callingToken, subscription, switchAfterDownload,
                                                 callingPackage));
                                 break;
                             case EuiccService.RESULT_NEED_CONFIRMATION_CODE:
                                 resultCode = RESOLVABLE_ERROR;
+                                boolean retried = false;
+                                if (!TextUtils.isEmpty(subscription.getConfirmationCode())) {
+                                    retried = true;
+                                }
                                 addResolutionIntent(extrasIntent,
                                         EuiccService.ACTION_RESOLVE_CONFIRMATION_CODE,
                                         callingPackage,
+                                        retried /* confirmationCodeRetried */,
                                         EuiccOperation.forDownloadConfirmationCode(
                                                 callingToken, subscription, switchAfterDownload,
                                                 callingPackage));
@@ -520,6 +530,7 @@
                     addResolutionIntent(extrasIntent,
                             EuiccService.ACTION_RESOLVE_DEACTIVATE_SIM,
                             mCallingPackage,
+                            false /* confirmationCodeRetried */,
                             EuiccOperation.forGetDefaultListDeactivateSim(
                                     mCallingToken, mCallingPackage));
                     break;
@@ -671,6 +682,7 @@
                 addResolutionIntent(extrasIntent,
                         EuiccService.ACTION_RESOLVE_NO_PRIVILEGES,
                         callingPackage,
+                        false /* confirmationCodeRetried */,
                         EuiccOperation.forSwitchNoPrivileges(
                                 token, subscriptionId, callingPackage));
                 sendResult(callbackIntent, RESOLVABLE_ERROR, extrasIntent);
@@ -716,6 +728,7 @@
                                 addResolutionIntent(extrasIntent,
                                         EuiccService.ACTION_RESOLVE_DEACTIVATE_SIM,
                                         callingPackage,
+                                        false /* confirmationCodeRetried */,
                                         EuiccOperation.forSwitchDeactivateSim(
                                                 callingToken, subscriptionId, callingPackage));
                                 break;
@@ -883,11 +896,13 @@
     /** Add a resolution intent to the given extras intent. */
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     public void addResolutionIntent(Intent extrasIntent, String resolutionAction,
-            String callingPackage, EuiccOperation op) {
+            String callingPackage, boolean confirmationCodeRetried, EuiccOperation op) {
         Intent intent = new Intent(EuiccManager.ACTION_RESOLVE_ERROR);
         intent.putExtra(EuiccManager.EXTRA_EMBEDDED_SUBSCRIPTION_RESOLUTION_ACTION,
                 resolutionAction);
         intent.putExtra(EuiccService.EXTRA_RESOLUTION_CALLING_PACKAGE, callingPackage);
+        intent.putExtra(EuiccService.EXTRA_RESOLUTION_CONFIRMATION_CODE_RETRIED,
+                confirmationCodeRetried);
         intent.putExtra(EXTRA_OPERATION, op);
         PendingIntent resolutionIntent = PendingIntent.getActivity(
                 mContext, 0 /* requestCode */, intent, PendingIntent.FLAG_ONE_SHOT);
diff --git a/com/android/internal/telephony/euicc/EuiccOperation.java b/com/android/internal/telephony/euicc/EuiccOperation.java
index 84df52e..148d9dc 100644
--- a/com/android/internal/telephony/euicc/EuiccOperation.java
+++ b/com/android/internal/telephony/euicc/EuiccOperation.java
@@ -309,9 +309,7 @@
         if (TextUtils.isEmpty(confirmationCode)) {
             fail(callbackIntent);
         } else {
-            if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {
-                mDownloadableSubscription.setConfirmationCode(confirmationCode);
-            }
+            mDownloadableSubscription.setConfirmationCode(confirmationCode);
             EuiccController.get()
                     .downloadSubscription(
                             mDownloadableSubscription,
diff --git a/com/android/internal/telephony/gsm/GsmMmiCode.java b/com/android/internal/telephony/gsm/GsmMmiCode.java
index b9a07f9..3376e2b 100644
--- a/com/android/internal/telephony/gsm/GsmMmiCode.java
+++ b/com/android/internal/telephony/gsm/GsmMmiCode.java
@@ -863,6 +863,13 @@
         return mIsSsInfo;
     }
 
+    public static boolean isVoiceUnconditionalForwarding(int reason, int serviceClass) {
+        return (((reason == CommandsInterface.CF_REASON_UNCONDITIONAL)
+                || (reason == CommandsInterface.CF_REASON_ALL))
+                && (((serviceClass & CommandsInterface.SERVICE_CLASS_VOICE) != 0)
+                || (serviceClass == CommandsInterface.SERVICE_CLASS_NONE)));
+    }
+
     /** Process a MMI code or short code...anything that isn't a dialing number */
     public void
     processCode() throws CallStateException {
@@ -933,12 +940,6 @@
                         throw new RuntimeException ("invalid action");
                     }
 
-                    int isSettingUnconditionalVoice =
-                        (((reason == CommandsInterface.CF_REASON_UNCONDITIONAL) ||
-                                (reason == CommandsInterface.CF_REASON_ALL)) &&
-                                (((serviceClass & CommandsInterface.SERVICE_CLASS_VOICE) != 0) ||
-                                 (serviceClass == CommandsInterface.SERVICE_CLASS_NONE))) ? 1 : 0;
-
                     int isEnableDesired =
                         ((cfAction == CommandsInterface.CF_ACTION_ENABLE) ||
                                 (cfAction == CommandsInterface.CF_ACTION_REGISTRATION)) ? 1 : 0;
@@ -947,7 +948,7 @@
                     mPhone.mCi.setCallForward(cfAction, reason, serviceClass,
                             dialingNumber, time, obtainMessage(
                                     EVENT_SET_CFF_COMPLETE,
-                                    isSettingUnconditionalVoice,
+                                    isVoiceUnconditionalForwarding(reason, serviceClass) ? 1 : 0,
                                     isEnableDesired, this));
                 }
             } else if (isServiceCodeCallBarring(mSc)) {
diff --git a/com/android/internal/telephony/ims/ImsResolver.java b/com/android/internal/telephony/ims/ImsResolver.java
index 2f790b7..4a64984 100644
--- a/com/android/internal/telephony/ims/ImsResolver.java
+++ b/com/android/internal/telephony/ims/ImsResolver.java
@@ -38,8 +38,9 @@
 import android.util.Pair;
 import android.util.SparseArray;
 
-import com.android.ims.internal.IImsServiceController;
-import com.android.ims.internal.IImsServiceFeatureListener;
+import com.android.ims.internal.IImsMMTelFeature;
+import com.android.ims.internal.IImsRcsFeature;
+import com.android.ims.internal.IImsServiceFeatureCallback;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.PhoneConstants;
 
@@ -295,21 +296,58 @@
     }
 
     /**
-     * Returns the {@link IImsServiceController} that corresponds to the given slot Id and IMS
-     * feature or {@link null} if the service is not available. If an ImsServiceController is
-     * available, the {@link IImsServiceFeatureListener} callback is registered as a listener for
-     * feature updates.
-     * @param slotId The SIM slot that we are requesting the {@link IImsServiceController} for.
-     * @param feature The IMS Feature we are requesting.
+     * Returns the {@link IImsMMTelFeature} that corresponds to the given slot Id or {@link null} if
+     * the service is not available. If an IImsMMTelFeature is available, the
+     * {@link IImsServiceFeatureCallback} callback is registered as a listener for feature updates.
+     * @param slotId The SIM slot that we are requesting the {@link IImsMMTelFeature} for.
      * @param callback Listener that will send updates to ImsManager when there are updates to
-     * ImsServiceController.
-     * @return {@link IImsServiceController} interface for the feature specified or {@link null} if
-     * it is unavailable.
+     * the feature.
+     * @return {@link IImsMMTelFeature} interface or {@link null} if it is unavailable.
      */
-    public IImsServiceController getImsServiceControllerAndListen(int slotId, int feature,
-            IImsServiceFeatureListener callback) {
-        if (slotId < 0 || slotId >= mNumSlots || feature <= ImsFeature.INVALID
-                || feature >= ImsFeature.MAX) {
+    public IImsMMTelFeature getMMTelFeatureAndListen(int slotId,
+            IImsServiceFeatureCallback callback) {
+        ImsServiceController controller = getImsServiceControllerAndListen(slotId, ImsFeature.MMTEL,
+                callback);
+        return (controller != null) ? controller.getMMTelFeature(slotId) : null;
+    }
+
+    /**
+     * Returns the {@link IImsMMTelFeature} that corresponds to the given slot Id for emergency
+     * calling or {@link null} if the service is not available. If an IImsMMTelFeature is
+     * available, the {@link IImsServiceFeatureCallback} callback is registered as a listener for
+     * feature updates.
+     * @param slotId The SIM slot that we are requesting the {@link IImsMMTelFeature} for.
+     * @param callback listener that will send updates to ImsManager when there are updates to
+     * the feature.
+     * @return {@link IImsMMTelFeature} interface or {@link null} if it is unavailable.
+     */
+    public IImsMMTelFeature getEmergencyMMTelFeatureAndListen(int slotId,
+            IImsServiceFeatureCallback callback) {
+        ImsServiceController controller = getImsServiceControllerAndListen(slotId,
+                ImsFeature.EMERGENCY_MMTEL, callback);
+        return (controller != null) ? controller.getEmergencyMMTelFeature(slotId) : null;
+    }
+
+    /**
+     * Returns the {@link IImsMMTelFeature} that corresponds to the given slot Id for emergency
+     * calling or {@link null} if the service is not available. If an IImsMMTelFeature is
+     * available, the {@link IImsServiceFeatureCallback} callback is registered as a listener for
+     * feature updates.
+     * @param slotId The SIM slot that we are requesting the {@link IImsMMTelFeature} for.
+     * @param callback listener that will send updates to ImsManager when there are updates to
+     * the feature.
+     * @return {@link IImsMMTelFeature} interface or {@link null} if it is unavailable.
+     */
+    public IImsRcsFeature getRcsFeatureAndListen(int slotId, IImsServiceFeatureCallback callback) {
+        ImsServiceController controller = getImsServiceControllerAndListen(slotId, ImsFeature.RCS,
+                callback);
+        return (controller != null) ? controller.getRcsFeature(slotId) : null;
+    }
+
+    @VisibleForTesting
+    public ImsServiceController getImsServiceControllerAndListen(int slotId, int feature,
+            IImsServiceFeatureCallback callback) {
+        if (slotId < 0 || slotId >= mNumSlots) {
             return null;
         }
         ImsServiceController controller;
@@ -322,7 +360,7 @@
         }
         if (controller != null) {
             controller.addImsServiceFeatureListener(callback);
-            return controller.getImsServiceController();
+            return controller;
         }
         return null;
     }
diff --git a/com/android/internal/telephony/ims/ImsServiceController.java b/com/android/internal/telephony/ims/ImsServiceController.java
index 6fcefbd..6f31b50 100644
--- a/com/android/internal/telephony/ims/ImsServiceController.java
+++ b/com/android/internal/telephony/ims/ImsServiceController.java
@@ -24,14 +24,18 @@
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
+import android.os.IInterface;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.telephony.ims.feature.ImsFeature;
 import android.util.Log;
 import android.util.Pair;
 
 import com.android.ims.internal.IImsFeatureStatusCallback;
+import com.android.ims.internal.IImsMMTelFeature;
+import com.android.ims.internal.IImsRcsFeature;
 import com.android.ims.internal.IImsServiceController;
-import com.android.ims.internal.IImsServiceFeatureListener;
+import com.android.ims.internal.IImsServiceFeatureCallback;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.ExponentialBackoff;
 
@@ -171,15 +175,54 @@
     private boolean mIsBinding = false;
     // Set of a pair of slotId->feature
     private HashSet<Pair<Integer, Integer>> mImsFeatures;
+    // Binder interfaces to the features set in mImsFeatures;
+    private HashSet<ImsFeatureContainer> mImsFeatureBinders = new HashSet<>();
     private IImsServiceController mIImsServiceController;
     // Easier for testing.
     private IBinder mImsServiceControllerBinder;
     private ImsServiceConnection mImsServiceConnection;
     private ImsDeathRecipient mImsDeathRecipient;
-    private Set<IImsServiceFeatureListener> mImsStatusCallbacks = new HashSet<>();
+    private Set<IImsServiceFeatureCallback> mImsStatusCallbacks = new HashSet<>();
     // Only added or removed, never accessed on purpose.
     private Set<ImsFeatureStatusCallback> mFeatureStatusCallbacks = new HashSet<>();
 
+    private class ImsFeatureContainer {
+        public int slotId;
+        public int featureType;
+        private IInterface mBinder;
+
+        ImsFeatureContainer(int slotId, int featureType, IInterface binder) {
+            this.slotId = slotId;
+            this.featureType = featureType;
+            this.mBinder = binder;
+        }
+
+        // Casts the IInterface into the binder class we are looking for.
+        public <T extends IInterface> T resolve(Class<T> className) {
+            return className.cast(mBinder);
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+
+            ImsFeatureContainer that = (ImsFeatureContainer) o;
+
+            if (slotId != that.slotId) return false;
+            if (featureType != that.featureType) return false;
+            return mBinder != null ? mBinder.equals(that.mBinder) : that.mBinder == null;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = slotId;
+            result = 31 * result + featureType;
+            result = 31 * result + (mBinder != null ? mBinder.hashCode() : 0);
+            return result;
+        }
+    }
+
     /**
      * Container class for the IImsFeatureStatusCallback callback implementation. This class is
      * never used directly, but we need to keep track of the IImsFeatureStatusCallback
@@ -373,12 +416,56 @@
     /**
      * Add a callback to ImsManager that signals a new feature that the ImsServiceProxy can handle.
      */
-    public void addImsServiceFeatureListener(IImsServiceFeatureListener callback) {
+    public void addImsServiceFeatureListener(IImsServiceFeatureCallback callback) {
         synchronized (mLock) {
             mImsStatusCallbacks.add(callback);
         }
     }
 
+    /**
+     * Return the {@Link MMTelFeature} binder on the slot associated with the slotId.
+     * Used for normal calling.
+     */
+    public IImsMMTelFeature getMMTelFeature(int slotId) {
+        synchronized (mLock) {
+            ImsFeatureContainer f = getImsFeatureContainer(slotId, ImsFeature.MMTEL);
+            if (f == null) {
+                Log.w(LOG_TAG, "Requested null MMTelFeature on slot " + slotId);
+                return null;
+            }
+            return f.resolve(IImsMMTelFeature.class);
+        }
+    }
+
+    /**
+     * Return the {@Link MMTelFeature} binder on the slot associated with the slotId.
+     * Used for emergency calling only.
+     */
+    public IImsMMTelFeature getEmergencyMMTelFeature(int slotId) {
+        synchronized (mLock) {
+            ImsFeatureContainer f = getImsFeatureContainer(slotId, ImsFeature.EMERGENCY_MMTEL);
+            if (f == null) {
+                Log.w(LOG_TAG, "Requested null Emergency MMTelFeature on slot " + slotId);
+                return null;
+            }
+            return f.resolve(IImsMMTelFeature.class);
+        }
+    }
+
+    /**
+     * Return the {@Link RcsFeature} binder on the slot associated with the slotId.
+     */
+    public IImsRcsFeature getRcsFeature(int slotId) {
+        synchronized (mLock) {
+            ImsFeatureContainer f = getImsFeatureContainer(slotId, ImsFeature.RCS);
+            if (f == null) {
+                Log.w(LOG_TAG, "Requested null RcsFeature on slot " + slotId);
+                return null;
+            }
+            return f.resolve(IImsRcsFeature.class);
+        }
+    }
+
     private void removeImsServiceFeatureListener() {
         synchronized (mLock) {
             mImsStatusCallbacks.clear();
@@ -407,9 +494,9 @@
 
     private void sendImsFeatureCreatedCallback(int slot, int feature) {
         synchronized (mLock) {
-            for (Iterator<IImsServiceFeatureListener> i = mImsStatusCallbacks.iterator();
+            for (Iterator<IImsServiceFeatureCallback> i = mImsStatusCallbacks.iterator();
                     i.hasNext(); ) {
-                IImsServiceFeatureListener callbacks = i.next();
+                IImsServiceFeatureCallback callbacks = i.next();
                 try {
                     callbacks.imsFeatureCreated(slot, feature);
                 } catch (RemoteException e) {
@@ -424,9 +511,9 @@
 
     private void sendImsFeatureRemovedCallback(int slot, int feature) {
         synchronized (mLock) {
-            for (Iterator<IImsServiceFeatureListener> i = mImsStatusCallbacks.iterator();
+            for (Iterator<IImsServiceFeatureCallback> i = mImsStatusCallbacks.iterator();
                     i.hasNext(); ) {
-                IImsServiceFeatureListener callbacks = i.next();
+                IImsServiceFeatureCallback callbacks = i.next();
                 try {
                     callbacks.imsFeatureRemoved(slot, feature);
                 } catch (RemoteException e) {
@@ -441,9 +528,9 @@
 
     private void sendImsFeatureStatusChanged(int slot, int feature, int status) {
         synchronized (mLock) {
-            for (Iterator<IImsServiceFeatureListener> i = mImsStatusCallbacks.iterator();
+            for (Iterator<IImsServiceFeatureCallback> i = mImsStatusCallbacks.iterator();
                     i.hasNext(); ) {
-                IImsServiceFeatureListener callbacks = i.next();
+                IImsServiceFeatureCallback callbacks = i.next();
                 try {
                     callbacks.imsStatusChanged(slot, feature, status);
                 } catch (RemoteException e) {
@@ -465,8 +552,8 @@
         ImsFeatureStatusCallback c = new ImsFeatureStatusCallback(featurePair.first,
                 featurePair.second);
         mFeatureStatusCallbacks.add(c);
-        mIImsServiceController.createImsFeature(featurePair.first, featurePair.second,
-                c.getCallback());
+        IInterface f = createImsFeature(featurePair.first, featurePair.second, c.getCallback());
+        addImsFeatureBinder(featurePair.first, featurePair.second, f);
         // Signal ImsResolver to change supported ImsFeatures for this ImsServiceController
         mCallbacks.imsServiceFeatureCreated(featurePair.first, featurePair.second, this);
         // Send callback to ImsServiceProxy to change supported ImsFeatures
@@ -489,6 +576,7 @@
         }
         mIImsServiceController.removeImsFeature(featurePair.first, featurePair.second,
                 (callbackToRemove != null ? callbackToRemove.getCallback() : null));
+        removeImsFeatureBinder(featurePair.first, featurePair.second);
         // Signal ImsResolver to change supported ImsFeatures for this ImsServiceController
         mCallbacks.imsServiceFeatureRemoved(featurePair.first, featurePair.second, this);
         // Send callback to ImsServiceProxy to change supported ImsFeatures
@@ -498,6 +586,45 @@
         sendImsFeatureRemovedCallback(featurePair.first, featurePair.second);
     }
 
+    // This method should only be called when already synchronized on mLock;
+    private IInterface createImsFeature(int slotId, int featureType, IImsFeatureStatusCallback c)
+            throws RemoteException {
+        switch (featureType) {
+            case ImsFeature.EMERGENCY_MMTEL: {
+                return mIImsServiceController.createEmergencyMMTelFeature(slotId, c);
+            }
+            case ImsFeature.MMTEL: {
+                return mIImsServiceController.createMMTelFeature(slotId, c);
+            }
+            case ImsFeature.RCS: {
+                return mIImsServiceController.createRcsFeature(slotId, c);
+            }
+            default:
+                return null;
+        }
+    }
+
+    // This method should only be called when synchronized on mLock
+    private void addImsFeatureBinder(int slotId, int featureType, IInterface b) {
+        mImsFeatureBinders.add(new ImsFeatureContainer(slotId, featureType, b));
+    }
+
+    // This method should only be called when synchronized on mLock
+    private void removeImsFeatureBinder(int slotId, int featureType) {
+        ImsFeatureContainer container = mImsFeatureBinders.stream()
+                .filter(f-> (f.slotId == slotId && f.featureType == featureType))
+                .findFirst().orElse(null);
+        if (container != null) {
+            mImsFeatureBinders.remove(container);
+        }
+    }
+
+    private ImsFeatureContainer getImsFeatureContainer(int slotId, int featureType) {
+        return mImsFeatureBinders.stream()
+                .filter(f-> (f.slotId == slotId && f.featureType == featureType))
+                .findFirst().orElse(null);
+    }
+
     private void notifyAllFeaturesRemoved() {
         if (mCallbacks == null) {
             Log.w(LOG_TAG, "notifyAllFeaturesRemoved called with invalid callbacks.");
diff --git a/com/android/internal/telephony/imsphone/ImsPhone.java b/com/android/internal/telephony/imsphone/ImsPhone.java
index bb2c34f..e54d7bc 100644
--- a/com/android/internal/telephony/imsphone/ImsPhone.java
+++ b/com/android/internal/telephony/imsphone/ImsPhone.java
@@ -95,6 +95,7 @@
 import com.android.internal.telephony.TelephonyIntents;
 import com.android.internal.telephony.TelephonyProperties;
 import com.android.internal.telephony.UUSInfo;
+import com.android.internal.telephony.gsm.GsmMmiCode;
 import com.android.internal.telephony.gsm.SuppServiceNotification;
 import com.android.internal.telephony.uicc.IccRecords;
 import com.android.internal.telephony.util.NotificationChannelController;
@@ -876,9 +877,8 @@
         if ((isValidCommandInterfaceCFAction(commandInterfaceCFAction)) &&
                 (isValidCommandInterfaceCFReason(commandInterfaceCFReason))) {
             Message resp;
-            Cf cf = new Cf(dialingNumber,
-                    (commandInterfaceCFReason == CF_REASON_UNCONDITIONAL ? true : false),
-                    onComplete);
+            Cf cf = new Cf(dialingNumber, GsmMmiCode.isVoiceUnconditionalForwarding(
+                    commandInterfaceCFReason, serviceClass), onComplete);
             resp = obtainMessage(EVENT_SET_CALL_FORWARD_DONE,
                     isCfEnable(commandInterfaceCFAction) ? 1 : 0, 0, cf);
 
diff --git a/com/android/internal/telephony/imsphone/ImsPhoneCommandInterface.java b/com/android/internal/telephony/imsphone/ImsPhoneCommandInterface.java
index cea8b21..c2711e8 100644
--- a/com/android/internal/telephony/imsphone/ImsPhoneCommandInterface.java
+++ b/com/android/internal/telephony/imsphone/ImsPhoneCommandInterface.java
@@ -22,13 +22,13 @@
 import android.service.carrier.CarrierIdentifier;
 import android.telephony.ImsiEncryptionInfo;
 import android.telephony.NetworkScanRequest;
+import android.telephony.data.DataProfile;
 
 import com.android.internal.telephony.BaseCommands;
 import com.android.internal.telephony.CommandsInterface;
 import com.android.internal.telephony.RadioCapability;
 import com.android.internal.telephony.UUSInfo;
 import com.android.internal.telephony.cdma.CdmaSmsBroadcastConfigInfo;
-import com.android.internal.telephony.dataconnection.DataProfile;
 import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
 
 import java.util.List;
diff --git a/com/android/internal/telephony/sip/SipCommandInterface.java b/com/android/internal/telephony/sip/SipCommandInterface.java
index 1264053..59af1bb 100644
--- a/com/android/internal/telephony/sip/SipCommandInterface.java
+++ b/com/android/internal/telephony/sip/SipCommandInterface.java
@@ -22,12 +22,12 @@
 import android.service.carrier.CarrierIdentifier;
 import android.telephony.ImsiEncryptionInfo;
 import android.telephony.NetworkScanRequest;
+import android.telephony.data.DataProfile;
 
 import com.android.internal.telephony.BaseCommands;
 import com.android.internal.telephony.CommandsInterface;
 import com.android.internal.telephony.UUSInfo;
 import com.android.internal.telephony.cdma.CdmaSmsBroadcastConfigInfo;
-import com.android.internal.telephony.dataconnection.DataProfile;
 import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
 
 import java.util.List;
diff --git a/com/android/internal/telephony/test/SimulatedCommands.java b/com/android/internal/telephony/test/SimulatedCommands.java
index 7553b61..b90a292 100644
--- a/com/android/internal/telephony/test/SimulatedCommands.java
+++ b/com/android/internal/telephony/test/SimulatedCommands.java
@@ -35,6 +35,7 @@
 import android.telephony.Rlog;
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
+import android.telephony.data.DataProfile;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.telephony.BaseCommands;
@@ -49,7 +50,6 @@
 import com.android.internal.telephony.UUSInfo;
 import com.android.internal.telephony.cdma.CdmaSmsBroadcastConfigInfo;
 import com.android.internal.telephony.dataconnection.DataCallResponse;
-import com.android.internal.telephony.dataconnection.DataProfile;
 import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
 import com.android.internal.telephony.gsm.SuppServiceNotification;
 import com.android.internal.telephony.uicc.IccCardStatus;
@@ -132,6 +132,7 @@
 
     private boolean mDcSuccess = true;
     private DataCallResponse mDcResponse;
+    private boolean mIsRadioPowerFailResponse = false;
 
     //***** Constructor
     public
@@ -1188,11 +1189,17 @@
 
     @Override
     public void setRadioPower(boolean on, Message result) {
+        if (mIsRadioPowerFailResponse) {
+            resultFail(result, null, new RuntimeException("setRadioPower failed!"));
+            return;
+        }
+
         if(on) {
             setRadioState(RadioState.RADIO_ON);
         } else {
             setRadioState(RadioState.RADIO_OFF);
         }
+        resultSuccess(result, null);
     }
 
 
@@ -2151,4 +2158,8 @@
         super.setOnRestrictedStateChanged(h, what, obj);
         SimulatedCommandsVerifier.getInstance().setOnRestrictedStateChanged(h, what, obj);
     }
+
+    public void setRadioPowerFailResponse(boolean fail) {
+        mIsRadioPowerFailResponse = fail;
+    }
 }
diff --git a/com/android/internal/telephony/test/SimulatedCommandsVerifier.java b/com/android/internal/telephony/test/SimulatedCommandsVerifier.java
index b03e70b..c51b6ad 100644
--- a/com/android/internal/telephony/test/SimulatedCommandsVerifier.java
+++ b/com/android/internal/telephony/test/SimulatedCommandsVerifier.java
@@ -21,12 +21,12 @@
 import android.service.carrier.CarrierIdentifier;
 import android.telephony.ImsiEncryptionInfo;
 import android.telephony.NetworkScanRequest;
+import android.telephony.data.DataProfile;
 
 import com.android.internal.telephony.CommandsInterface;
 import com.android.internal.telephony.RadioCapability;
 import com.android.internal.telephony.UUSInfo;
 import com.android.internal.telephony.cdma.CdmaSmsBroadcastConfigInfo;
-import com.android.internal.telephony.dataconnection.DataProfile;
 import com.android.internal.telephony.gsm.SmsBroadcastConfigInfo;
 
 import java.util.List;
diff --git a/com/android/internal/telephony/uicc/SIMRecords.java b/com/android/internal/telephony/uicc/SIMRecords.java
index 8594f80..3af04a9 100644
--- a/com/android/internal/telephony/uicc/SIMRecords.java
+++ b/com/android/internal/telephony/uicc/SIMRecords.java
@@ -60,9 +60,6 @@
 
     VoiceMailConstants mVmConfig;
 
-
-    SpnOverride mSpnOverride;
-
     // ***** Cached SIM State; cleared on channel close
 
     private int mCallForwardingStatus;
@@ -99,7 +96,6 @@
     public String toString() {
         return "SimRecords: " + super.toString()
                 + " mVmConfig" + mVmConfig
-                + " mSpnOverride=" + mSpnOverride
                 + " callForwardingEnabled=" + mCallForwardingStatus
                 + " spnState=" + mSpnState
                 + " mCphsInfo=" + mCphsInfo
@@ -214,7 +210,6 @@
         mAdnCache = new AdnRecordCache(mFh);
 
         mVmConfig = new VoiceMailConstants();
-        mSpnOverride = new SpnOverride();
 
         mRecordsRequested = false;  // No load request is made till SIM ready
 
@@ -1640,57 +1635,63 @@
 
     //***** Private methods
 
+    /**
+     * Override the carrier name with either carrier config or SPN
+     * if an override is provided.
+     */
     private void handleCarrierNameOverride() {
-        CarrierConfigManager configLoader = (CarrierConfigManager)
-                mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
-        if (configLoader != null && configLoader.getConfig().getBoolean(
-                CarrierConfigManager.KEY_CARRIER_NAME_OVERRIDE_BOOL)) {
-            String carrierName = configLoader.getConfig().getString(
-                    CarrierConfigManager.KEY_CARRIER_NAME_STRING);
-            setServiceProviderName(carrierName);
-            mTelephonyManager.setSimOperatorNameForPhone(mParentApp.getPhoneId(),
-                    carrierName);
-        } else {
-            setSpnFromConfig(getOperatorNumeric());
-        }
-
-        /* update display name with carrier override */
-        setDisplayName();
-    }
-
-    private void setDisplayName() {
-        SubscriptionManager subManager = SubscriptionManager.from(mContext);
-        int[] subId = subManager.getSubId(mParentApp.getPhoneId());
-
-        if ((subId == null) || subId.length <= 0) {
-            log("subId not valid for Phone " + mParentApp.getPhoneId());
+        final int phoneId = mParentApp.getPhoneId();
+        SubscriptionController subCon = SubscriptionController.getInstance();
+        final int subId = subCon.getSubIdUsingPhoneId(phoneId);
+        if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+            loge("subId not valid for Phone " + phoneId);
             return;
         }
 
-        SubscriptionInfo subInfo = subManager.getActiveSubscriptionInfo(subId[0]);
-        if (subInfo != null && subInfo.getNameSource()
-                    != SubscriptionManager.NAME_SOURCE_USER_INPUT) {
-            CharSequence oldSubName = subInfo.getDisplayName();
-            String newCarrierName = mTelephonyManager.getSimOperatorName(subId[0]);
-
-            if (!TextUtils.isEmpty(newCarrierName) && !newCarrierName.equals(oldSubName)) {
-                log("sim name[" + mParentApp.getPhoneId() + "] = " + newCarrierName);
-                SubscriptionController.getInstance().setDisplayName(newCarrierName, subId[0]);
-            }
-        } else {
-            log("SUB[" + mParentApp.getPhoneId() + "] " + subId[0] + " SubInfo not created yet");
+        CarrierConfigManager configLoader = (CarrierConfigManager)
+                mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
+        if (configLoader == null) {
+            loge("Failed to load a Carrier Config");
+            return;
         }
+
+        PersistableBundle config = configLoader.getConfigForSubId(subId);
+        boolean preferCcName = config.getBoolean(
+                CarrierConfigManager.KEY_CARRIER_NAME_OVERRIDE_BOOL, false);
+        String ccName = config.getString(CarrierConfigManager.KEY_CARRIER_NAME_STRING);
+        // If carrier config is priority, use it regardless - the preference
+        // and the name were both set by the carrier, so this is safe;
+        // otherwise, if the SPN is priority but we don't have one *and* we have
+        // a name in carrier config, use the carrier config name as a backup.
+        if (preferCcName || (TextUtils.isEmpty(getServiceProviderName())
+                             && !TextUtils.isEmpty(ccName))) {
+            setServiceProviderName(ccName);
+            mTelephonyManager.setSimOperatorNameForPhone(phoneId, ccName);
+        }
+
+        updateCarrierNameForSubscription(subCon, subId);
     }
 
-    private void setSpnFromConfig(String carrier) {
-        if (mSpnOverride.containsCarrier(carrier)) {
-            setServiceProviderName(mSpnOverride.getSpn(carrier));
-            mTelephonyManager.setSimOperatorNameForPhone(
-                    mParentApp.getPhoneId(), getServiceProviderName());
+    private void updateCarrierNameForSubscription(SubscriptionController subCon, int subId) {
+        /* update display name with carrier override */
+        SubscriptionInfo subInfo = subCon.getActiveSubscriptionInfo(
+                subId, mContext.getOpPackageName());
+
+        if (subInfo == null || subInfo.getNameSource()
+                == SubscriptionManager.NAME_SOURCE_USER_INPUT) {
+            // either way, there is no subinfo to update
+            return;
+        }
+
+        CharSequence oldSubName = subInfo.getDisplayName();
+        String newCarrierName = mTelephonyManager.getSimOperatorName(subId);
+
+        if (!TextUtils.isEmpty(newCarrierName) && !newCarrierName.equals(oldSubName)) {
+            log("sim name[" + mParentApp.getPhoneId() + "] = " + newCarrierName);
+            subCon.setDisplayName(newCarrierName, subId);
         }
     }
 
-
     private void setVoiceMailByCountry (String spn) {
         if (mVmConfig.containsCarrier(spn)) {
             mIsVoiceMailFixed = true;
@@ -2223,7 +2224,6 @@
         pw.println(" extends:");
         super.dump(fd, pw, args);
         pw.println(" mVmConfig=" + mVmConfig);
-        pw.println(" mSpnOverride=" + mSpnOverride);
         pw.println(" mCallForwardingStatus=" + mCallForwardingStatus);
         pw.println(" mSpnState=" + mSpnState);
         pw.println(" mCphsInfo=" + mCphsInfo);
diff --git a/com/android/internal/telephony/uicc/SpnOverride.java b/com/android/internal/telephony/uicc/SpnOverride.java
deleted file mode 100644
index 3a01af6..0000000
--- a/com/android/internal/telephony/uicc/SpnOverride.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright (C) 2009 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.telephony.uicc;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileReader;
-import java.io.IOException;
-import java.util.HashMap;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import android.os.Environment;
-import android.telephony.Rlog;
-import android.util.Xml;
-
-import com.android.internal.util.XmlUtils;
-
-public class SpnOverride {
-    private HashMap<String, String> mCarrierSpnMap;
-
-
-    static final String LOG_TAG = "SpnOverride";
-    static final String PARTNER_SPN_OVERRIDE_PATH ="etc/spn-conf.xml";
-    static final String OEM_SPN_OVERRIDE_PATH = "telephony/spn-conf.xml";
-
-    SpnOverride () {
-        mCarrierSpnMap = new HashMap<String, String>();
-        loadSpnOverrides();
-    }
-
-    boolean containsCarrier(String carrier) {
-        return mCarrierSpnMap.containsKey(carrier);
-    }
-
-    String getSpn(String carrier) {
-        return mCarrierSpnMap.get(carrier);
-    }
-
-    private void loadSpnOverrides() {
-        FileReader spnReader;
-
-        File spnFile = new File(Environment.getRootDirectory(),
-                PARTNER_SPN_OVERRIDE_PATH);
-        File oemSpnFile = new File(Environment.getOemDirectory(),
-                OEM_SPN_OVERRIDE_PATH);
-
-        if (oemSpnFile.exists()) {
-            // OEM image exist SPN xml, get the timestamp from OEM & System image for comparison.
-            long oemSpnTime = oemSpnFile.lastModified();
-            long sysSpnTime = spnFile.lastModified();
-            Rlog.d(LOG_TAG, "SPN Timestamp: oemTime = " + oemSpnTime + " sysTime = " + sysSpnTime);
-
-            // To get the newer version of SPN from OEM image
-            if (oemSpnTime > sysSpnTime) {
-                Rlog.d(LOG_TAG, "SPN in OEM image is newer than System image");
-                spnFile = oemSpnFile;
-            }
-        } else {
-            // No SPN in OEM image, so load it from system image.
-            Rlog.d(LOG_TAG, "No SPN in OEM image = " + oemSpnFile.getPath() +
-                " Load SPN from system image");
-        }
-
-        try {
-            spnReader = new FileReader(spnFile);
-        } catch (FileNotFoundException e) {
-            Rlog.w(LOG_TAG, "Can not open " + spnFile.getAbsolutePath());
-            return;
-        }
-
-        try {
-            XmlPullParser parser = Xml.newPullParser();
-            parser.setInput(spnReader);
-
-            XmlUtils.beginDocument(parser, "spnOverrides");
-
-            while (true) {
-                XmlUtils.nextElement(parser);
-
-                String name = parser.getName();
-                if (!"spnOverride".equals(name)) {
-                    break;
-                }
-
-                String numeric = parser.getAttributeValue(null, "numeric");
-                String data    = parser.getAttributeValue(null, "spn");
-
-                mCarrierSpnMap.put(numeric, data);
-            }
-            spnReader.close();
-        } catch (XmlPullParserException e) {
-            Rlog.w(LOG_TAG, "Exception in spn-conf parser " + e);
-        } catch (IOException e) {
-            Rlog.w(LOG_TAG, "Exception in spn-conf parser " + e);
-        }
-    }
-
-}
diff --git a/com/android/internal/util/ArrayUtils.java b/com/android/internal/util/ArrayUtils.java
index 91bc681..22bfcc3 100644
--- a/com/android/internal/util/ArrayUtils.java
+++ b/com/android/internal/util/ArrayUtils.java
@@ -30,6 +30,7 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 
 /**
@@ -134,6 +135,13 @@
     }
 
     /**
+     * Checks if given map is null or has zero elements.
+     */
+    public static boolean isEmpty(@Nullable Map<?, ?> map) {
+        return map == null || map.isEmpty();
+    }
+
+    /**
      * Checks if given array is null or has zero elements.
      */
     public static <T> boolean isEmpty(@Nullable T[] array) {
diff --git a/com/android/internal/util/RingBuffer.java b/com/android/internal/util/RingBuffer.java
index c22be2c..9a6e542 100644
--- a/com/android/internal/util/RingBuffer.java
+++ b/com/android/internal/util/RingBuffer.java
@@ -60,6 +60,25 @@
         mBuffer[indexOf(mCursor++)] = t;
     }
 
+    /**
+     * Returns object of type <T> at the next writable slot, creating one if it is not already
+     * available. In case of any errors while creating the object, <code>null</code> will
+     * be returned.
+     */
+    public T getNextSlot() {
+        final int nextSlotIdx = indexOf(mCursor++);
+        T item = mBuffer[nextSlotIdx];
+        if (item == null) {
+            try {
+                item = (T) mBuffer.getClass().getComponentType().newInstance();
+            } catch (IllegalAccessException | InstantiationException e) {
+                return null;
+            }
+            mBuffer[nextSlotIdx] = item;
+        }
+        return item;
+    }
+
     public T[] toArray() {
         // Only generic way to create a T[] from another T[]
         T[] out = Arrays.copyOf(mBuffer, size(), (Class<T[]>) mBuffer.getClass());
diff --git a/com/android/internal/util/UserIcons.java b/com/android/internal/util/UserIcons.java
index daf745f..bfe4323 100644
--- a/com/android/internal/util/UserIcons.java
+++ b/com/android/internal/util/UserIcons.java
@@ -61,17 +61,19 @@
      * Returns a default user icon for the given user.
      *
      * Note that for guest users, you should pass in {@code UserHandle.USER_NULL}.
+     *
+     * @param resources resources object to fetch user icon / color.
      * @param userId the user id or {@code UserHandle.USER_NULL} for a non-user specific icon
      * @param light whether we want a light icon (suitable for a dark background)
      */
-    public static Drawable getDefaultUserIcon(int userId, boolean light) {
+    public static Drawable getDefaultUserIcon(Resources resources, int userId, boolean light) {
         int colorResId = light ? R.color.user_icon_default_white : R.color.user_icon_default_gray;
         if (userId != UserHandle.USER_NULL) {
             // Return colored icon instead
             colorResId = USER_ICON_COLORS[userId % USER_ICON_COLORS.length];
         }
-        Drawable icon = Resources.getSystem().getDrawable(R.drawable.ic_account_circle, null).mutate();
-        icon.setColorFilter(Resources.getSystem().getColor(colorResId, null), Mode.SRC_IN);
+        Drawable icon = resources.getDrawable(R.drawable.ic_account_circle, null).mutate();
+        icon.setColorFilter(resources.getColor(colorResId, null), Mode.SRC_IN);
         icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
         return icon;
     }
diff --git a/com/android/internal/view/TooltipPopup.java b/com/android/internal/view/TooltipPopup.java
index d38ea2c..24f0b0c 100644
--- a/com/android/internal/view/TooltipPopup.java
+++ b/com/android/internal/view/TooltipPopup.java
@@ -142,7 +142,7 @@
         mTmpAnchorPos[1] -= mTmpAppPos[1];
         // mTmpAnchorPos is now relative to the main app window.
 
-        outParams.x = mTmpAnchorPos[0] + offsetX - mTmpDisplayFrame.width() / 2;
+        outParams.x = mTmpAnchorPos[0] + offsetX - appView.getWidth() / 2;
 
         final int spec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
         mContentView.measure(spec, spec);
@@ -157,6 +157,9 @@
                 outParams.y = yBelow;
             }
         } else {
+            // Use mTmpDisplayFrame.height() as the lower boundary instead of appView.getHeight(),
+            // as the latter includes the navigation bar, and tooltips do not look good over
+            // the navigation bar.
             if (yBelow + tooltipHeight <= mTmpDisplayFrame.height()) {
                 outParams.y = yBelow;
             } else {
diff --git a/com/android/internal/widget/ExploreByTouchHelper.java b/com/android/internal/widget/ExploreByTouchHelper.java
index 50ad547..759a41a 100644
--- a/com/android/internal/widget/ExploreByTouchHelper.java
+++ b/com/android/internal/widget/ExploreByTouchHelper.java
@@ -186,6 +186,9 @@
         }
 
         final AccessibilityEvent event = createEvent(virtualViewId, eventType);
+        if (event == null) {
+            return false;
+        }
         return parent.requestSendAccessibilityEvent(mView, event);
     }
 
@@ -240,6 +243,9 @@
             if (parent != null) {
                 final AccessibilityEvent event = createEvent(virtualViewId,
                         AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
+                if (event == null) {
+                    return;
+                }
                 event.setContentChangeTypes(changeTypes);
                 parent.requestSendAccessibilityEvent(mView, event);
             }
@@ -305,6 +311,9 @@
      *         the specified item.
      */
     private AccessibilityEvent createEventForHost(int eventType) {
+        if (!AccessibilityManager.getInstance(mContext).isObservedEventType(eventType)) {
+            return null;
+        }
         final AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
         mView.onInitializeAccessibilityEvent(event);
 
@@ -325,6 +334,9 @@
      *         the specified item.
      */
     private AccessibilityEvent createEventForChild(int virtualViewId, int eventType) {
+        if (!AccessibilityManager.getInstance(mContext).isObservedEventType(eventType)) {
+            return null;
+        }
         final AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
         event.setEnabled(true);
         event.setClassName(DEFAULT_CLASS_NAME);
diff --git a/com/android/internal/widget/Magnifier.java b/com/android/internal/widget/Magnifier.java
deleted file mode 100644
index f6741c3..0000000
--- a/com/android/internal/widget/Magnifier.java
+++ /dev/null
@@ -1,218 +0,0 @@
-/*
- * 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;
-
-import java.util.Timer;
-import java.util.TimerTask;
-
-/**
- * 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";
-    private static final int MAGNIFIER_REFRESH_RATE_MS = 33; // ~30fps
-    // 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();
-    // Current magnification scale.
-    private final float mZoomScale;
-    // Timer used to schedule the copy task.
-    private Timer mTimer;
-
-    /**
-     * 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 float elevation = context.getResources().getDimension(R.dimen.magnifier_elevation);
-        final View content = LayoutInflater.from(context).inflate(R.layout.magnifier, null);
-        content.findViewById(R.id.magnifier_inner).setClipToOutline(true);
-        mWindowWidth = context.getResources().getDimensionPixelSize(R.dimen.magnifier_width);
-        mWindowHeight = context.getResources().getDimensionPixelSize(R.dimen.magnifier_height);
-        mZoomScale = context.getResources().getFloat(R.dimen.magnifier_zoom_scale);
-
-        mWindow = new PopupWindow(context);
-        mWindow.setContentView(content);
-        mWindow.setWidth(mWindowWidth);
-        mWindow.setHeight(mWindowHeight);
-        mWindow.setElevation(elevation);
-        mWindow.setTouchable(false);
-        mWindow.setBackgroundDrawable(null);
-
-        final int bitmapWidth = (int) (mWindowWidth / mZoomScale);
-        final int bitmapHeight = (int) (mWindowHeight / mZoomScale);
-        mBitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888);
-        getImageView().setImageBitmap(mBitmap);
-    }
-
-    /**
-     * Shows the magnifier on the screen.
-     *
-     * @param xPosInView horizontal coordinate of the center point of the magnifier source relative
-     *        to the view. The lower end is clamped to 0
-     * @param yPosInView vertical coordinate of the center point of the magnifier source
-     *        relative to the view. The lower end is clamped to 0
-     */
-    public void show(@FloatRange(from=0) float xPosInView, @FloatRange(from=0) float yPosInView) {
-        if (xPosInView < 0) {
-            xPosInView = 0;
-        }
-
-        if (yPosInView < 0) {
-            yPosInView = 0;
-        }
-
-        configureCoordinates(xPosInView, yPosInView);
-
-        if (mTimer == null) {
-            mTimer = new Timer();
-            mTimer.schedule(new TimerTask() {
-                @Override
-                public void run() {
-                    performPixelCopy();
-                }
-            }, 0 /* delay */, MAGNIFIER_REFRESH_RATE_MS);
-        }
-
-        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();
-
-        if (mTimer != null) {
-            mTimer.cancel();
-            mTimer.purge();
-            mTimer = null;
-        }
-    }
-
-    /**
-     * @return the height of the magnifier window.
-     */
-    public int getHeight() {
-        return mWindowHeight;
-    }
-
-    /**
-     * @return the width of the magnifier window.
-     */
-    public int getWidth() {
-        return mWindowWidth;
-    }
-
-    /**
-     * @return the zoom scale of the magnifier.
-     */
-    public float getZoomScale() {
-        return mZoomScale;
-    }
-
-    private void configureCoordinates(float xPosInView, float yPosInView) {
-        final int[] coordinatesOnScreen = new int[2];
-        mView.getLocationOnScreen(coordinatesOnScreen);
-        final float posXOnScreen = xPosInView + coordinatesOnScreen[0];
-        final float posYOnScreen = yPosInView + coordinatesOnScreen[1];
-
-        mCenterZoomCoords.x = (int) posXOnScreen;
-        mCenterZoomCoords.y = (int) posYOnScreen;
-
-        final int verticalMagnifierOffset = mView.getContext().getResources().getDimensionPixelSize(
-                R.dimen.magnifier_offset);
-        mWindowCoords.x = mCenterZoomCoords.x - mWindowWidth / 2;
-        mWindowCoords.y = mCenterZoomCoords.y - mWindowHeight / 2 - verticalMagnifierOffset;
-    }
-
-    private void performPixelCopy() {
-        final int startY = mCenterZoomCoords.y - mBitmap.getHeight() / 2;
-        int rawStartX = mCenterZoomCoords.x - mBitmap.getWidth() / 2;
-
-        // Clamp startX value to avoid distorting the rendering of the magnifier content.
-        if (rawStartX < 0) {
-            rawStartX = 0;
-        } else if (rawStartX + mBitmap.getWidth() > mView.getWidth()) {
-            rawStartX = mView.getWidth() - mBitmap.getWidth();
-        }
-
-        final int startX = rawStartX;
-        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/internal/widget/PointerLocationView.java b/com/android/internal/widget/PointerLocationView.java
index e53162c..5847033 100644
--- a/com/android/internal/widget/PointerLocationView.java
+++ b/com/android/internal/widget/PointerLocationView.java
@@ -32,7 +32,7 @@
 import android.view.VelocityTracker;
 import android.view.View;
 import android.view.ViewConfiguration;
-import android.view.WindowManagerPolicy.PointerEventListener;
+import android.view.WindowManagerPolicyConstants.PointerEventListener;
 import android.view.MotionEvent.PointerCoords;
 
 import java.util.ArrayList;
diff --git a/com/android/internal/widget/RecyclerView.java b/com/android/internal/widget/RecyclerView.java
index 7abc76a..408a4e9 100644
--- a/com/android/internal/widget/RecyclerView.java
+++ b/com/android/internal/widget/RecyclerView.java
@@ -9556,7 +9556,7 @@
             if (vScroll == 0 && hScroll == 0) {
                 return false;
             }
-            mRecyclerView.scrollBy(hScroll, vScroll);
+            mRecyclerView.smoothScrollBy(hScroll, vScroll);
             return true;
         }
 
diff --git a/com/android/keyguard/KeyguardSliceView.java b/com/android/keyguard/KeyguardSliceView.java
index c18f9b6..cb3d59c 100644
--- a/com/android/keyguard/KeyguardSliceView.java
+++ b/com/android/keyguard/KeyguardSliceView.java
@@ -33,6 +33,8 @@
 import com.android.systemui.R;
 import com.android.systemui.keyguard.KeyguardSliceProvider;
 
+import java.util.Collections;
+
 /**
  * View visible under the clock on the lock screen and AoD.
  */
@@ -75,7 +77,8 @@
         super.onAttachedToWindow();
 
         // Set initial content
-        showSlice(Slice.bindSlice(getContext().getContentResolver(), mKeyguardSliceUri));
+        showSlice(Slice.bindSlice(getContext().getContentResolver(), mKeyguardSliceUri,
+                Collections.emptyList()));
 
         // Make sure we always have the most current slice
         getContext().getContentResolver().registerContentObserver(mKeyguardSliceUri,
@@ -91,7 +94,7 @@
 
     private void showSlice(Slice slice) {
         // Items will be wrapped into an action when they have tap targets.
-        SliceItem actionSlice = SliceQuery.find(slice, SliceItem.TYPE_ACTION);
+        SliceItem actionSlice = SliceQuery.find(slice, SliceItem.FORMAT_ACTION);
         if (actionSlice != null) {
             mSlice = actionSlice.getSlice();
             mSliceAction = actionSlice.getAction();
@@ -105,7 +108,7 @@
             return;
         }
 
-        SliceItem title = SliceQuery.find(mSlice, SliceItem.TYPE_TEXT, Slice.HINT_TITLE, null);
+        SliceItem title = SliceQuery.find(mSlice, SliceItem.FORMAT_TEXT, Slice.HINT_TITLE, null);
         if (title == null) {
             mTitle.setVisibility(GONE);
         } else {
@@ -113,7 +116,7 @@
             mTitle.setText(title.getText());
         }
 
-        SliceItem text = SliceQuery.find(mSlice, SliceItem.TYPE_TEXT, null, Slice.HINT_TITLE);
+        SliceItem text = SliceQuery.find(mSlice, SliceItem.FORMAT_TEXT, null, Slice.HINT_TITLE);
         if (text == null) {
             mText.setVisibility(GONE);
         } else {
@@ -154,7 +157,8 @@
 
         @Override
         public void onChange(boolean selfChange, Uri uri) {
-            showSlice(Slice.bindSlice(getContext().getContentResolver(), mKeyguardSliceUri));
+            showSlice(Slice.bindSlice(getContext().getContentResolver(), mKeyguardSliceUri,
+                    Collections.emptyList()));
         }
     }
 }
diff --git a/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/com/android/keyguard/KeyguardUpdateMonitorCallback.java
index 5a02178..8d55eea 100644
--- a/com/android/keyguard/KeyguardUpdateMonitorCallback.java
+++ b/com/android/keyguard/KeyguardUpdateMonitorCallback.java
@@ -21,7 +21,7 @@
 import android.os.SystemClock;
 import android.hardware.fingerprint.FingerprintManager;
 import android.telephony.TelephonyManager;
-import android.view.WindowManagerPolicy;
+import android.view.WindowManagerPolicyConstants;
 
 import com.android.internal.telephony.IccCardConstants;
 
@@ -171,9 +171,9 @@
 
     /**
      * Called when the device has finished going to sleep.
-     * @param why either {@link WindowManagerPolicy#OFF_BECAUSE_OF_ADMIN},
-     * {@link WindowManagerPolicy#OFF_BECAUSE_OF_USER}, or
-     * {@link WindowManagerPolicy#OFF_BECAUSE_OF_TIMEOUT}.
+     * @param why either {@link WindowManagerPolicyConstants#OFF_BECAUSE_OF_ADMIN},
+     * {@link WindowManagerPolicyConstants#OFF_BECAUSE_OF_USER}, or
+     * {@link WindowManagerPolicyConstants#OFF_BECAUSE_OF_TIMEOUT}.
      *
      * @deprecated use {@link com.android.systemui.keyguard.WakefulnessLifecycle}.
      */
diff --git a/com/android/keyguard/PasswordTextView.java b/com/android/keyguard/PasswordTextView.java
index 12f75bb..0219db3 100644
--- a/com/android/keyguard/PasswordTextView.java
+++ b/com/android/keyguard/PasswordTextView.java
@@ -307,8 +307,9 @@
 
     void sendAccessibilityEventTypeViewTextChanged(String beforeText, int fromIndex,
                                                    int removedCount, int addedCount) {
-        if (AccessibilityManager.getInstance(mContext).isEnabled() &&
-                (isFocused() || isSelected() && isShown())) {
+        if (AccessibilityManager.getInstance(mContext).isObservedEventType(
+                    AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED)
+                && (isFocused() || isSelected() && isShown())) {
             AccessibilityEvent event =
                     AccessibilityEvent.obtain(AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED);
             event.setFromIndex(fromIndex);
diff --git a/com/android/layoutlib/bridge/Bridge.java b/com/android/layoutlib/bridge/Bridge.java
index 0cfc181..5dca8e7 100644
--- a/com/android/layoutlib/bridge/Bridge.java
+++ b/com/android/layoutlib/bridge/Bridge.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2016 The Android Open Source Project
+ * Copyright (C) 2008 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,62 +14,659 @@
  * limitations under the License.
  */
 
-package com.android.layoutlib.bridge;import com.android.ide.common.rendering.api.RenderSession;
+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;
 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 java.awt.Graphics2D;
-import java.awt.image.BufferedImage;
+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;
 
 /**
- * Legacy Bridge used in the SDK version of layoutlib
+ * 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)}
  */
 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 class BridgeRenderSession extends RenderSession {
+    private static final String ICU_LOCALE_DIRECTION_RTL = "right-to-left";
 
-        @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 static class StaticMethodNotImplementedException extends RuntimeException {
+        private static final long serialVersionUID = 1L;
 
-            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;
+        public StaticMethodNotImplementedException(String msg) {
+            super(msg);
         }
     }
 
+    /**
+     * 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();
 
-    @Override
-    public RenderSession createSession(SessionParams params) {
-        return new BridgeRenderSession();
-    }
+    /**
+     * 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 int getApiLevel() {
-        return 0;
+        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));
+        }
     }
 }
diff --git a/com/android/layoutlib/bridge/android/BridgePackageManager.java b/com/android/layoutlib/bridge/android/BridgePackageManager.java
index 98937ef..37dc166 100644
--- a/com/android/layoutlib/bridge/android/BridgePackageManager.java
+++ b/com/android/layoutlib/bridge/android/BridgePackageManager.java
@@ -26,12 +26,11 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.ChangedPackages;
-import android.content.pm.InstantAppInfo;
 import android.content.pm.FeatureInfo;
 import android.content.pm.IPackageDataObserver;
 import android.content.pm.IPackageDeleteObserver;
-import android.content.pm.IPackageInstallObserver;
 import android.content.pm.IPackageStatsObserver;
+import android.content.pm.InstantAppInfo;
 import android.content.pm.InstrumentationInfo;
 import android.content.pm.IntentFilterVerificationInfo;
 import android.content.pm.KeySet;
@@ -56,6 +55,7 @@
 import android.os.Handler;
 import android.os.UserHandle;
 import android.os.storage.VolumeInfo;
+
 import java.util.List;
 
 /**
@@ -611,11 +611,6 @@
     }
 
     @Override
-    public void installPackage(Uri packageURI, IPackageInstallObserver observer, int flags,
-            String installerPackageName) {
-    }
-
-    @Override
     public void installPackage(Uri packageURI, PackageInstallObserver observer, int flags,
             String installerPackageName) {
     }
diff --git a/com/android/providers/settings/SettingsProtoDumpUtil.java b/com/android/providers/settings/SettingsProtoDumpUtil.java
index 41b205b..52b4f4d 100644
--- a/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -801,6 +801,9 @@
                 Settings.Global.BLUETOOTH_PAN_PRIORITY_PREFIX,
                 GlobalSettingsProto.BLUETOOTH_PAN_PRIORITY_PREFIX);
         dumpSetting(s, p,
+                Settings.Global.BLUETOOTH_HEARING_AID_PRIORITY_PREFIX,
+                GlobalSettingsProto.BLUETOOTH_HEARING_AID_PRIORITY_PREFIX);
+        dumpSetting(s, p,
                 Settings.Global.ACTIVITY_MANAGER_CONSTANTS,
                 GlobalSettingsProto.ACTIVITY_MANAGER_CONSTANTS);
         dumpSetting(s, p,
@@ -869,6 +872,15 @@
         dumpSetting(s, p,
                 Settings.Global.WAIT_FOR_DEBUGGER,
                 GlobalSettingsProto.WAIT_FOR_DEBUGGER);
+        dumpSetting(s, p,
+                Settings.Global.ENABLE_GPU_DEBUG_LAYERS,
+                GlobalSettingsProto.ENABLE_GPU_DEBUG_LAYERS);
+        dumpSetting(s, p,
+                Settings.Global.GPU_DEBUG_APP,
+                GlobalSettingsProto.GPU_DEBUG_APP);
+        dumpSetting(s, p,
+                Settings.Global.GPU_DEBUG_LAYERS,
+                GlobalSettingsProto.GPU_DEBUG_LAYERS);
         // Settings.Global.SHOW_PROCESSES intentionally excluded since it's deprecated.
         dumpSetting(s, p,
                 Settings.Global.LOW_POWER_MODE,
diff --git a/com/android/providers/settings/SettingsProvider.java b/com/android/providers/settings/SettingsProvider.java
index 258c96c..7fb6ede 100644
--- a/com/android/providers/settings/SettingsProvider.java
+++ b/com/android/providers/settings/SettingsProvider.java
@@ -61,6 +61,7 @@
 import android.os.UserManagerInternal;
 import android.provider.Settings;
 import android.provider.Settings.Global;
+import android.provider.Settings.Secure;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -343,7 +344,8 @@
             }
 
             case Settings.CALL_METHOD_GET_SECURE: {
-                Setting setting = getSecureSetting(name, requestingUserId);
+                Setting setting = getSecureSetting(name, requestingUserId,
+                        /*enableOverride=*/ true);
                 return packageValueForCallResult(setting, isTrackingGeneration(args));
             }
 
@@ -1073,6 +1075,10 @@
     }
 
     private Setting getSecureSetting(String name, int requestingUserId) {
+        return getSecureSetting(name, requestingUserId, /*enableOverride=*/ false);
+    }
+
+    private Setting getSecureSetting(String name, int requestingUserId, boolean enableOverride) {
         if (DEBUG) {
             Slog.v(LOG_TAG, "getSecureSetting(" + name + ", " + requestingUserId + ")");
         }
@@ -1102,6 +1108,14 @@
                 return getSsaidSettingLocked(callingPkg, owningUserId);
             }
         }
+        if (enableOverride) {
+            if (Secure.LOCATION_PROVIDERS_ALLOWED.equals(name)) {
+                final Setting overridden = getLocationProvidersAllowedSetting(owningUserId);
+                if (overridden != null) {
+                    return overridden;
+                }
+            }
+        }
 
         // Not the SSAID; do a straight lookup
         synchronized (mLock) {
@@ -1190,6 +1204,35 @@
         return null;
     }
 
+    private Setting getLocationProvidersAllowedSetting(int owningUserId) {
+        synchronized (mLock) {
+            final Setting setting = getGlobalSetting(
+                    Global.LOCATION_GLOBAL_KILL_SWITCH);
+            if (!"1".equals(setting.getValue())) {
+                return null;
+            }
+            // Global kill-switch is enabled. Return an empty value.
+            final SettingsState settingsState = mSettingsRegistry.getSettingsLocked(
+                    SETTINGS_TYPE_SECURE, owningUserId);
+            return settingsState.new Setting(
+                    Secure.LOCATION_PROVIDERS_ALLOWED,
+                    "", // value
+                    "", // tag
+                    "", // default value
+                    "", // package name
+                    false, // from system
+                    "0" // id
+            ) {
+                @Override
+                public boolean update(String value, boolean setDefault, String packageName,
+                        String tag, boolean forceNonSystemPackage) {
+                    Slog.wtf(LOG_TAG, "update shoudln't be called on this instance.");
+                    return false;
+                }
+            };
+        }
+    }
+
     private boolean insertSecureSetting(String name, String value, String tag,
             boolean makeDefault, int requestingUserId, boolean forceNotify) {
         if (DEBUG) {
@@ -2780,6 +2823,12 @@
             }
 
             mHandler.obtainMessage(MyHandler.MSG_NOTIFY_DATA_CHANGED).sendToTarget();
+
+            // When the global kill switch is updated, send the change notification for
+            // the location setting.
+            if (isGlobalSettingsKey(key) && Global.LOCATION_GLOBAL_KILL_SWITCH.equals(name)) {
+                notifyLocationChangeForRunningUsers();
+            }
         }
 
         private void maybeNotifyProfiles(int type, int userId, Uri uri, String name,
@@ -2799,6 +2848,24 @@
             }
         }
 
+        private void notifyLocationChangeForRunningUsers() {
+            final List<UserInfo> users = mUserManager.getUsers(/*excludeDying=*/ true);
+
+            for (int i = 0; i < users.size(); i++) {
+                final int userId = users.get(i).id;
+
+                if (!mUserManager.isUserRunning(UserHandle.of(userId))) {
+                    continue;
+                }
+
+                final int key = makeKey(SETTINGS_TYPE_GLOBAL, userId);
+                final Uri uri = getNotificationUriFor(key, Secure.LOCATION_PROVIDERS_ALLOWED);
+
+                mHandler.obtainMessage(MyHandler.MSG_NOTIFY_URI_CHANGED,
+                        userId, 0, uri).sendToTarget();
+            }
+        }
+
         private boolean isGlobalSettingsKey(int key) {
             return getTypeFromKey(key) == SETTINGS_TYPE_GLOBAL;
         }
@@ -2885,7 +2952,7 @@
                         } catch (SecurityException e) {
                             Slog.w(LOG_TAG, "Failed to notify for " + userId + ": " + uri, e);
                         }
-                        if (DEBUG) {
+                        if (DEBUG || true) {
                             Slog.v(LOG_TAG, "Notifying for " + userId + ": " + uri);
                         }
                     } break;
diff --git a/com/android/server/AlarmManagerService.java b/com/android/server/AlarmManagerService.java
index 3904fc9..ca15249 100644
--- a/com/android/server/AlarmManagerService.java
+++ b/com/android/server/AlarmManagerService.java
@@ -46,16 +46,15 @@
 import android.os.PowerManager;
 import android.os.Process;
 import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.os.SystemProperties;
+import android.os.Trace;
 import android.os.UserHandle;
 import android.os.WorkSource;
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.text.format.DateFormat;
 import android.util.ArrayMap;
-import android.util.ArraySet;
 import android.util.KeyValueListParser;
 import android.util.Log;
 import android.util.Slog;
@@ -81,6 +80,7 @@
 import java.util.Random;
 import java.util.TimeZone;
 import java.util.TreeSet;
+import java.util.function.Predicate;
 
 import static android.app.AlarmManager.RTC_WAKEUP;
 import static android.app.AlarmManager.RTC;
@@ -88,11 +88,17 @@
 import static android.app.AlarmManager.ELAPSED_REALTIME;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.app.IAppOpsCallback;
-import com.android.internal.app.IAppOpsService;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.LocalLog;
+import com.android.server.ForceAppStandbyTracker.Listener;
 
+/**
+ * Alarm manager implementaion.
+ *
+ * Unit test:
+ atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/AlarmManagerServiceTest.java
+ */
 class AlarmManagerService extends SystemService {
     private static final int RTC_WAKEUP_MASK = 1 << RTC_WAKEUP;
     private static final int RTC_MASK = 1 << RTC;
@@ -131,13 +137,10 @@
     final LocalLog mLog = new LocalLog(TAG);
 
     AppOpsManager mAppOps;
-    IAppOpsService mAppOpsService;
     DeviceIdleController.LocalService mLocalDeviceIdleController;
 
     final Object mLock = new Object();
 
-    ArraySet<String> mForcedAppStandbyPackages = new ArraySet<>();
-    SparseBooleanArray mForegroundUids = new SparseBooleanArray();
     // List of alarms per uid deferred due to user applied background restrictions on the source app
     SparseArray<ArrayList<Alarm>> mPendingBackgroundAlarms = new SparseArray<>();
     long mNativeData;
@@ -184,12 +187,6 @@
     int mSystemUiUid;
 
     /**
-     * The current set of user whitelisted apps for device idle mode, meaning these are allowed
-     * to freely schedule alarms.
-     */
-    int[] mDeviceIdleUserWhitelist = new int[0];
-
-    /**
      * For each uid, this is the last time we dispatched an "allow while idle" alarm,
      * used to determine the earliest we can dispatch the next such alarm. Times are in the
      * 'elapsed' timebase.
@@ -223,6 +220,8 @@
     private final SparseArray<AlarmManager.AlarmClockInfo> mHandlerSparseAlarmClockArray =
             new SparseArray<>();
 
+    private final ForceAppStandbyTracker mForceAppStandbyTracker;
+
     /**
      * All times are in milliseconds. These constants are kept synchronized with the system
      * global Settings. Any access to this class or its fields should be done while
@@ -757,6 +756,9 @@
     public AlarmManagerService(Context context) {
         super(context);
         mConstants = new Constants(mHandler);
+
+        mForceAppStandbyTracker = ForceAppStandbyTracker.getInstance(context);
+        mForceAppStandbyTracker.addListener(mForceAppStandbyListener);
     }
 
     static long convertToElapsed(long when, int type) {
@@ -894,17 +896,48 @@
         deliverPendingBackgroundAlarmsLocked(alarmsToDeliver, SystemClock.elapsedRealtime());
     }
 
-    void sendPendingBackgroundAlarmsForAppIdLocked(int appId) {
+    /**
+     * Check all alarms in {@link #mPendingBackgroundAlarms} and send the ones that are not
+     * restricted.
+     *
+     * This is only called when the global "force all apps-standby" flag changes or when the
+     * power save whitelist changes, so it's okay to be slow.
+     */
+    void sendAllUnrestrictedPendingBackgroundAlarmsLocked() {
         final ArrayList<Alarm> alarmsToDeliver = new ArrayList<>();
-        for (int i = mPendingBackgroundAlarms.size() - 1; i >= 0; i--) {
-            final int uid = mPendingBackgroundAlarms.keyAt(i);
-            final ArrayList<Alarm> alarmsForUid = mPendingBackgroundAlarms.valueAt(i);
-            if (UserHandle.getAppId(uid) == appId) {
-                alarmsToDeliver.addAll(alarmsForUid);
-                mPendingBackgroundAlarms.removeAt(i);
+
+        findAllUnrestrictedPendingBackgroundAlarmsLockedInner(
+                mPendingBackgroundAlarms, alarmsToDeliver, this::isBackgroundRestricted);
+
+        if (alarmsToDeliver.size() > 0) {
+            deliverPendingBackgroundAlarmsLocked(alarmsToDeliver, SystemClock.elapsedRealtime());
+        }
+    }
+
+    @VisibleForTesting
+    static void findAllUnrestrictedPendingBackgroundAlarmsLockedInner(
+            SparseArray<ArrayList<Alarm>> pendingAlarms, ArrayList<Alarm> unrestrictedAlarms,
+            Predicate<Alarm> isBackgroundRestricted) {
+
+        for (int uidIndex = pendingAlarms.size() - 1; uidIndex >= 0; uidIndex--) {
+            final int uid = pendingAlarms.keyAt(uidIndex);
+            final ArrayList<Alarm> alarmsForUid = pendingAlarms.valueAt(uidIndex);
+
+            for (int alarmIndex = alarmsForUid.size() - 1; alarmIndex >= 0; alarmIndex--) {
+                final Alarm alarm = alarmsForUid.get(alarmIndex);
+
+                if (isBackgroundRestricted.test(alarm)) {
+                    continue;
+                }
+
+                unrestrictedAlarms.add(alarm);
+                alarmsForUid.remove(alarmIndex);
+            }
+
+            if (alarmsForUid.size() == 0) {
+                pendingAlarms.removeAt(uidIndex);
             }
         }
-        deliverPendingBackgroundAlarmsLocked(alarmsToDeliver, SystemClock.elapsedRealtime());
     }
 
     private void deliverPendingBackgroundAlarmsLocked(ArrayList<Alarm> alarms, long nowELAPSED) {
@@ -1234,10 +1267,8 @@
         } catch (RemoteException e) {
             // ignored; both services live in system_server
         }
-        mAppOpsService = IAppOpsService.Stub.asInterface(
-                ServiceManager.getService(Context.APP_OPS_SERVICE));
         publishBinderService(Context.ALARM_SERVICE, mService);
-        publishLocalService(LocalService.class, new LocalService());
+        mForceAppStandbyTracker.start();
     }
 
     @Override
@@ -1247,13 +1278,6 @@
             mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE);
             mLocalDeviceIdleController
                     = LocalServices.getService(DeviceIdleController.LocalService.class);
-            try {
-                mAppOpsService.startWatchingMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, null,
-                        new AppOpsWatcher());
-            } catch (RemoteException rexc) {
-                // Shouldn't happen as they are in the same process.
-                Slog.e(TAG, "AppOps service not reachable", rexc);
-            }
         }
     }
 
@@ -1582,8 +1606,7 @@
             // timing restrictions.
             } else if (workSource == null && (callingUid < Process.FIRST_APPLICATION_UID
                     || callingUid == mSystemUiUid
-                    || Arrays.binarySearch(mDeviceIdleUserWhitelist,
-                            UserHandle.getAppId(callingUid)) >= 0)) {
+                    || mForceAppStandbyTracker.isUidPowerSaveWhitelisted(callingUid))) {
                 flags |= AlarmManager.FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED;
                 flags &= ~AlarmManager.FLAG_ALLOW_WHILE_IDLE;
             }
@@ -1660,24 +1683,14 @@
         }
     };
 
-    public final class LocalService {
-        public void setDeviceIdleUserWhitelist(int[] appids) {
-            setDeviceIdleUserWhitelistImpl(appids);
-        }
-    }
-
     void dumpImpl(PrintWriter pw) {
         synchronized (mLock) {
             pw.println("Current Alarm Manager state:");
             mConstants.dump(pw);
             pw.println();
 
-            pw.print("  Foreground uids: [");
-            for (int i = 0; i < mForegroundUids.size(); i++) {
-                if (mForegroundUids.valueAt(i)) pw.print(mForegroundUids.keyAt(i) + " ");
-            }
-            pw.println("]");
-            pw.println("  Forced app standby packages: " + mForcedAppStandbyPackages);
+            mForceAppStandbyTracker.dump(pw, "  ");
+
             final long nowRTC = System.currentTimeMillis();
             final long nowELAPSED = SystemClock.elapsedRealtime();
             SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
@@ -1717,7 +1730,6 @@
             pw.print(" set at "); TimeUtils.formatDuration(mLastWakeupSet, nowELAPSED, pw);
             pw.println();
             pw.print("  Num time change events: "); pw.println(mNumTimeChanged);
-            pw.println("  mDeviceIdleUserWhitelist=" + Arrays.toString(mDeviceIdleUserWhitelist));
 
             pw.println();
             pw.println("  Next alarm clock information: ");
@@ -1990,15 +2002,8 @@
 
             mConstants.dumpProto(proto, AlarmManagerServiceProto.SETTINGS);
 
-            final int foregroundUidsSize = mForegroundUids.size();
-            for (int i = 0; i < foregroundUidsSize; i++) {
-                if (mForegroundUids.valueAt(i)) {
-                    proto.write(AlarmManagerServiceProto.FOREGROUND_UIDS, mForegroundUids.keyAt(i));
-                }
-            }
-            for (String pkg : mForcedAppStandbyPackages) {
-                proto.write(AlarmManagerServiceProto.FORCED_APP_STANDBY_PACKAGES, pkg);
-            }
+            mForceAppStandbyTracker.dumpProto(proto,
+                    AlarmManagerServiceProto.FORCE_APP_STANDBY_TRACKER);
 
             proto.write(AlarmManagerServiceProto.IS_INTERACTIVE, mInteractive);
             if (!mInteractive) {
@@ -2022,9 +2027,6 @@
             proto.write(AlarmManagerServiceProto.TIME_SINCE_LAST_WAKEUP_SET_MS,
                     nowElapsed - mLastWakeupSet);
             proto.write(AlarmManagerServiceProto.TIME_CHANGE_EVENT_COUNT, mNumTimeChanged);
-            for (int i : mDeviceIdleUserWhitelist) {
-                proto.write(AlarmManagerServiceProto.DEVICE_IDLE_USER_WHITELIST_APP_IDS, i);
-            }
 
             final TreeSet<Integer> users = new TreeSet<>();
             final int nextAlarmClockForUserSize = mNextAlarmClockForUser.size();
@@ -2266,28 +2268,6 @@
         }
     }
 
-    void setDeviceIdleUserWhitelistImpl(int[] appids) {
-        synchronized (mLock) {
-            // appids are sorted, just send pending alarms for any new appids added to the whitelist
-            int i = 0, j = 0;
-            while (i < appids.length) {
-                while (j < mDeviceIdleUserWhitelist.length
-                        && mDeviceIdleUserWhitelist[j] < appids[i]) {
-                    j++;
-                }
-                if (j < mDeviceIdleUserWhitelist.length
-                        && appids[i] != mDeviceIdleUserWhitelist[j]) {
-                    if (DEBUG_BG_LIMIT) {
-                        Slog.d(TAG, "Sending blocked alarms for whitelisted appid " + appids[j]);
-                    }
-                    sendPendingBackgroundAlarmsForAppIdLocked(appids[j]);
-                }
-                i++;
-            }
-            mDeviceIdleUserWhitelist = appids;
-        }
-    }
-
     AlarmManager.AlarmClockInfo getNextAlarmClockImpl(int userId) {
         synchronized (mLock) {
             return mNextAlarmClockForUser.get(userId);
@@ -2710,9 +2690,7 @@
         final String sourcePackage =
                 (alarm.operation != null) ? alarm.operation.getCreatorPackage() : alarm.packageName;
         final int sourceUid = alarm.creatorUid;
-        return mForcedAppStandbyPackages.contains(sourcePackage) && !mForegroundUids.get(sourceUid)
-                && Arrays.binarySearch(mDeviceIdleUserWhitelist, UserHandle.getAppId(sourceUid))
-                < 0;
+        return mForceAppStandbyTracker.areAlarmsRestricted(sourceUid, sourcePackage);
     }
 
     private native long init();
@@ -2859,7 +2837,8 @@
         }
     }
 
-    private static class Alarm {
+    @VisibleForTesting
+    static class Alarm {
         public final int type;
         public final long origWhen;
         public final boolean wakeup;
@@ -3073,6 +3052,11 @@
         for (int i=0; i<triggerList.size(); i++) {
             Alarm alarm = triggerList.get(i);
             final boolean allowWhileIdle = (alarm.flags&AlarmManager.FLAG_ALLOW_WHILE_IDLE) != 0;
+            if (alarm.wakeup) {
+              Trace.traceBegin(Trace.TRACE_TAG_POWER, "Dispatch wakeup alarm to " + alarm.packageName);
+            } else {
+              Trace.traceBegin(Trace.TRACE_TAG_POWER, "Dispatch non-wakeup alarm to " + alarm.packageName);
+            }
             try {
                 if (localLOGV) {
                     Slog.v(TAG, "sending alarm " + alarm);
@@ -3092,6 +3076,7 @@
             } catch (RuntimeException e) {
                 Slog.w(TAG, "Failure sending alarm.", e);
             }
+            Trace.traceEnd(Trace.TRACE_TAG_POWER);
         }
     }
 
@@ -3476,17 +3461,10 @@
                 if (disabled) {
                     removeForStoppedLocked(uid);
                 }
-                mForegroundUids.delete(uid);
             }
         }
 
         @Override public void onUidActive(int uid) {
-            synchronized (mLock) {
-                if (!mForegroundUids.get(uid)) {
-                    mForegroundUids.put(uid, true);
-                    sendPendingBackgroundAlarmsLocked(uid, null);
-                }
-            }
         }
 
         @Override public void onUidIdle(int uid, boolean disabled) {
@@ -3494,7 +3472,6 @@
                 if (disabled) {
                     removeForStoppedLocked(uid);
                 }
-                mForegroundUids.delete(uid);
             }
         }
 
@@ -3502,27 +3479,29 @@
         }
     };
 
-    private final class AppOpsWatcher extends IAppOpsCallback.Stub {
+
+    private final Listener mForceAppStandbyListener = new Listener() {
         @Override
-        public void opChanged(int op, int uid, String packageName) throws RemoteException {
+        public void unblockAllUnrestrictedAlarms() {
             synchronized (mLock) {
-                final int mode = mAppOpsService.checkOperation(op, uid, packageName);
-                if (DEBUG_BG_LIMIT) {
-                    Slog.d(TAG,
-                            "Appop changed for " + uid + ", " + packageName + " to " + mode);
-                }
-                final boolean changed;
-                if (mode != AppOpsManager.MODE_ALLOWED) {
-                    changed = mForcedAppStandbyPackages.add(packageName);
-                } else {
-                    changed = mForcedAppStandbyPackages.remove(packageName);
-                }
-                if (changed && mode == AppOpsManager.MODE_ALLOWED) {
-                    sendPendingBackgroundAlarmsLocked(uid, packageName);
-                }
+                sendAllUnrestrictedPendingBackgroundAlarmsLocked();
             }
         }
-    }
+
+        @Override
+        public void unblockAlarmsForUid(int uid) {
+            synchronized (mLock) {
+                sendPendingBackgroundAlarmsLocked(uid, null);
+            }
+        }
+
+        @Override
+        public void unblockAlarmsForUidPackage(int uid, String packageName) {
+            synchronized (mLock) {
+                sendPendingBackgroundAlarmsLocked(uid, packageName);
+            }
+        }
+    };
 
     private final BroadcastStats getStatsLocked(PendingIntent pi) {
         String pkg = pi.getCreatorPackage();
diff --git a/com/android/server/BatteryService.java b/com/android/server/BatteryService.java
index ea0ed27..924e736 100644
--- a/com/android/server/BatteryService.java
+++ b/com/android/server/BatteryService.java
@@ -619,6 +619,7 @@
         intent.putExtra(BatteryManager.EXTRA_HEALTH, mHealthInfo.batteryHealth);
         intent.putExtra(BatteryManager.EXTRA_PRESENT, mHealthInfo.batteryPresent);
         intent.putExtra(BatteryManager.EXTRA_LEVEL, mHealthInfo.batteryLevel);
+        intent.putExtra(BatteryManager.EXTRA_BATTERY_LOW, mSentLowBatteryBroadcast);
         intent.putExtra(BatteryManager.EXTRA_SCALE, BATTERY_SCALE);
         intent.putExtra(BatteryManager.EXTRA_ICON_SMALL, icon);
         intent.putExtra(BatteryManager.EXTRA_PLUGGED, mPlugType);
diff --git a/com/android/server/BluetoothManagerService.java b/com/android/server/BluetoothManagerService.java
index c34c30c..04279a3 100644
--- a/com/android/server/BluetoothManagerService.java
+++ b/com/android/server/BluetoothManagerService.java
@@ -2150,31 +2150,26 @@
                                           (int)((onDuration / (1000 * 60)) % 60),
                                           (int)((onDuration / 1000) % 60),
                                           (int)(onDuration % 1000));
-                writer.println("  time since enabled: " + onDurationString + "\n");
+                writer.println("  time since enabled: " + onDurationString);
             }
 
             if (mActiveLogs.size() == 0) {
-                writer.println("Bluetooth never enabled!");
+                writer.println("\nBluetooth never enabled!");
             } else {
-                writer.println("Enable log:");
+                writer.println("\nEnable log:");
                 for (ActiveLog log : mActiveLogs) {
                     writer.println("  " + log);
                 }
             }
 
-            writer.println("Bluetooth crashed " + mCrashes + " time" + (mCrashes == 1 ? "" : "s"));
+            writer.println("\nBluetooth crashed " + mCrashes + " time" + (mCrashes == 1 ? "" : "s"));
             if (mCrashes == CRASH_LOG_MAX_SIZE) writer.println("(last " + CRASH_LOG_MAX_SIZE + ")");
             for (Long time : mCrashTimestamps) {
               writer.println("  " + timeToLog(time.longValue()));
             }
 
-            String bleAppString = "No BLE Apps registered.";
-            if (mBleApps.size() == 1) {
-                bleAppString = "1 BLE App registered:";
-            } else if (mBleApps.size() > 1) {
-                bleAppString = mBleApps.size() + " BLE Apps registered:";
-            }
-            writer.println("\n" + bleAppString);
+            writer.println("\n" + mBleApps.size() + " BLE app" +
+                            (mBleApps.size() == 1 ? "" : "s") + "registered");
             for (ClientDeathRecipient app : mBleApps.values()) {
                 writer.println("  " + app.getPackageName());
             }
diff --git a/com/android/server/DeviceIdleController.java b/com/android/server/DeviceIdleController.java
index 0921a00..d7aeb8c 100644
--- a/com/android/server/DeviceIdleController.java
+++ b/com/android/server/DeviceIdleController.java
@@ -119,7 +119,6 @@
     private PowerManagerInternal mLocalPowerManager;
     private PowerManager mPowerManager;
     private ConnectivityService mConnectivityService;
-    private AlarmManagerService.LocalService mLocalAlarmManager;
     private INetworkPolicyManager mNetworkPolicyManager;
     private SensorManager mSensorManager;
     private Sensor mMotionSensor;
@@ -1435,7 +1434,6 @@
                 mGoingIdleWakeLock.setReferenceCounted(true);
                 mConnectivityService = (ConnectivityService)ServiceManager.getService(
                         Context.CONNECTIVITY_SERVICE);
-                mLocalAlarmManager = getLocalService(AlarmManagerService.LocalService.class);
                 mNetworkPolicyManager = INetworkPolicyManager.Stub.asInterface(
                         ServiceManager.getService(Context.NETWORK_POLICY_SERVICE));
                 mNetworkPolicyManagerInternal = getLocalService(NetworkPolicyManagerInternal.class);
@@ -1500,8 +1498,8 @@
 
                 mLocalActivityManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray);
                 mLocalPowerManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray);
-                mLocalAlarmManager.setDeviceIdleUserWhitelist(mPowerSaveWhitelistUserAppIdArray);
 
+                passWhiteListToForceAppStandbyTrackerLocked();
                 updateInteractivityLocked();
             }
             updateConnectivityState(null);
@@ -2477,13 +2475,7 @@
             }
             mLocalPowerManager.setDeviceIdleWhitelist(mPowerSaveWhitelistAllAppIdArray);
         }
-        if (mLocalAlarmManager != null) {
-            if (DEBUG) {
-                Slog.d(TAG, "Setting alarm whitelist to "
-                        + Arrays.toString(mPowerSaveWhitelistUserAppIdArray));
-            }
-            mLocalAlarmManager.setDeviceIdleUserWhitelist(mPowerSaveWhitelistUserAppIdArray);
-        }
+        passWhiteListToForceAppStandbyTrackerLocked();
     }
 
     private void updateTempWhitelistAppIdsLocked(int appId, boolean adding) {
@@ -2509,6 +2501,7 @@
             }
             mLocalPowerManager.setDeviceIdleTempWhitelist(mTempWhitelistAppIdArray);
         }
+        passWhiteListToForceAppStandbyTrackerLocked();
     }
 
     private void reportPowerSaveWhitelistChangedLocked() {
@@ -2523,6 +2516,12 @@
         getContext().sendBroadcastAsUser(intent, UserHandle.SYSTEM);
     }
 
+    private void passWhiteListToForceAppStandbyTrackerLocked() {
+        ForceAppStandbyTracker.getInstance(getContext()).setPowerSaveWhitelistAppIds(
+                mPowerSaveWhitelistAllAppIdArray,
+                mTempWhitelistAppIdArray);
+    }
+
     void readConfigFileLocked() {
         if (DEBUG) Slog.d(TAG, "Reading config from " + mConfigFile.getBaseFile());
         mPowerSaveWhitelistUserApps.clear();
diff --git a/com/android/server/ForceAppStandbyTracker.java b/com/android/server/ForceAppStandbyTracker.java
new file mode 100644
index 0000000..61d3833
--- /dev/null
+++ b/com/android/server/ForceAppStandbyTracker.java
@@ -0,0 +1,838 @@
+/*
+ * 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;
+
+import android.annotation.NonNull;
+import android.app.ActivityManager;
+import android.app.AppOpsManager;
+import android.app.AppOpsManager.PackageOps;
+import android.app.IActivityManager;
+import android.app.IUidObserver;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PowerManager.ServiceType;
+import android.os.PowerManagerInternal;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.util.ArraySet;
+import android.util.Pair;
+import android.util.SparseBooleanArray;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.IAppOpsCallback;
+import com.android.internal.app.IAppOpsService;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.Preconditions;
+import com.android.server.ForceAppStandbyTrackerProto.RunAnyInBackgroundRestrictedPackages;
+
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Class to keep track of the information related to "force app standby", which includes:
+ * - OP_RUN_ANY_IN_BACKGROUND for each package
+ * - UID foreground state
+ * - User+system power save whitelist
+ * - Temporary power save whitelist
+ * - Global "force all apps standby" mode enforced by battery saver.
+ *
+ * TODO: In general, we can reduce the number of callbacks by checking all signals before sending
+ *    each callback. For example, even when an UID comes into the foreground, if it wasn't
+ *    originally restricted, then there's no need to send an event.
+ *    Doing this would be error-prone, so we punt it for now, but we should revisit it later.
+ *
+ * Test:
+   atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/ForceAppStandbyTrackerTest.java
+ */
+public class ForceAppStandbyTracker {
+    private static final String TAG = "ForceAppStandbyTracker";
+
+    @GuardedBy("ForceAppStandbyTracker.class")
+    private static ForceAppStandbyTracker sInstance;
+
+    private final Object mLock = new Object();
+    private final Context mContext;
+
+    @VisibleForTesting
+    static final int TARGET_OP = AppOpsManager.OP_RUN_ANY_IN_BACKGROUND;
+
+    IActivityManager mIActivityManager;
+    AppOpsManager mAppOpsManager;
+    IAppOpsService mAppOpsService;
+    PowerManagerInternal mPowerManagerInternal;
+
+    private final MyHandler mHandler;
+
+    /**
+     * Pair of (uid (not user-id), packageName) with OP_RUN_ANY_IN_BACKGROUND *not* allowed.
+     */
+    @GuardedBy("mLock")
+    final ArraySet<Pair<Integer, String>> mRunAnyRestrictedPackages = new ArraySet<>();
+
+    @GuardedBy("mLock")
+    final SparseBooleanArray mForegroundUids = new SparseBooleanArray();
+
+    @GuardedBy("mLock")
+    private int[] mPowerWhitelistedAllAppIds = new int[0];
+
+    @GuardedBy("mLock")
+    private int[] mTempWhitelistedAppIds = mPowerWhitelistedAllAppIds;
+
+    @GuardedBy("mLock")
+    final ArraySet<Listener> mListeners = new ArraySet<>();
+
+    @GuardedBy("mLock")
+    boolean mStarted;
+
+    @GuardedBy("mLock")
+    boolean mForceAllAppsStandby;
+
+    public static abstract class Listener {
+        /**
+         * This is called when the OP_RUN_ANY_IN_BACKGROUND appops changed for a package.
+         */
+        private void onRunAnyAppOpsChanged(ForceAppStandbyTracker sender,
+                int uid, @NonNull String packageName) {
+            updateJobsForUidPackage(uid, packageName);
+
+            if (!sender.areAlarmsRestricted(uid, packageName)) {
+                unblockAlarmsForUidPackage(uid, packageName);
+            }
+        }
+
+        /**
+         * This is called when the foreground state changed for a UID.
+         */
+        private void onUidForegroundStateChanged(ForceAppStandbyTracker sender, int uid) {
+            updateJobsForUid(uid);
+
+            if (sender.isInForeground(uid)) {
+                unblockAlarmsForUid(uid);
+            }
+        }
+
+        /**
+         * This is called when an app-id(s) is removed from the power save whitelist.
+         */
+        private void onPowerSaveUnwhitelisted(ForceAppStandbyTracker sender) {
+            updateAllJobs();
+            unblockAllUnrestrictedAlarms();
+        }
+
+        /**
+         * This is called when the power save whitelist changes, excluding the
+         * {@link #onPowerSaveUnwhitelisted} case.
+         */
+        private void onPowerSaveWhitelistedChanged(ForceAppStandbyTracker sender) {
+            updateAllJobs();
+        }
+
+        /**
+         * This is called when the temp whitelist changes.
+         */
+        private void onTempPowerSaveWhitelistChanged(ForceAppStandbyTracker sender) {
+
+            // TODO This case happens rather frequently; consider optimizing and update jobs
+            // only for affected app-ids.
+
+            updateAllJobs();
+        }
+
+        /**
+         * This is called when the global "force all apps standby" flag changes.
+         */
+        private void onForceAllAppsStandbyChanged(ForceAppStandbyTracker sender) {
+            updateAllJobs();
+
+            if (!sender.isForceAllAppsStandbyEnabled()) {
+                unblockAllUnrestrictedAlarms();
+            }
+        }
+
+        /**
+         * Called when the job restrictions for multiple UIDs might have changed, so the job
+         * scheduler should re-evaluate all restrictions for all jobs.
+         */
+        public void updateAllJobs() {
+        }
+
+        /**
+         * Called when the job restrictions for a UID might have changed, so the job
+         * scheduler should re-evaluate all restrictions for all jobs.
+         */
+        public void updateJobsForUid(int uid) {
+        }
+
+        /**
+         * Called when the job restrictions for a UID - package might have changed, so the job
+         * scheduler should re-evaluate all restrictions for all jobs.
+         */
+        public void updateJobsForUidPackage(int uid, String packageName) {
+        }
+
+        /**
+         * Called when the job restrictions for multiple UIDs might have changed, so the alarm
+         * manager should re-evaluate all restrictions for all blocked jobs.
+         */
+        public void unblockAllUnrestrictedAlarms() {
+        }
+
+        /**
+         * Called when all jobs for a specific UID are unblocked.
+         */
+        public void unblockAlarmsForUid(int uid) {
+        }
+
+        /**
+         * Called when all alarms for a specific UID - package are unblocked.
+         */
+        public void unblockAlarmsForUidPackage(int uid, String packageName) {
+        }
+    }
+
+    @VisibleForTesting
+    ForceAppStandbyTracker(Context context, Looper looper) {
+        mContext = context;
+        mHandler = new MyHandler(looper);
+    }
+
+    private ForceAppStandbyTracker(Context context) {
+        this(context, FgThread.get().getLooper());
+    }
+
+    /**
+     * Get the singleton instance.
+     */
+    public static synchronized ForceAppStandbyTracker getInstance(Context context) {
+        if (sInstance == null) {
+            sInstance = new ForceAppStandbyTracker(context);
+        }
+        return sInstance;
+    }
+
+    /**
+     * Call it when the system is ready.
+     */
+    public void start() {
+        synchronized (mLock) {
+            if (mStarted) {
+                return;
+            }
+            mStarted = true;
+
+            mIActivityManager = Preconditions.checkNotNull(injectIActivityManager());
+            mAppOpsManager = Preconditions.checkNotNull(injectAppOpsManager());
+            mAppOpsService = Preconditions.checkNotNull(injectIAppOpsService());
+            mPowerManagerInternal = Preconditions.checkNotNull(injectPowerManagerInternal());
+
+            try {
+                mIActivityManager.registerUidObserver(new UidObserver(),
+                        ActivityManager.UID_OBSERVER_GONE | ActivityManager.UID_OBSERVER_IDLE
+                                | ActivityManager.UID_OBSERVER_ACTIVE,
+                        ActivityManager.PROCESS_STATE_UNKNOWN, null);
+                mAppOpsService.startWatchingMode(TARGET_OP, null,
+                        new AppOpsWatcher());
+            } catch (RemoteException e) {
+                // shouldn't happen.
+            }
+
+            IntentFilter filter = new IntentFilter();
+            filter.addAction(Intent.ACTION_USER_REMOVED);
+            mContext.registerReceiver(new MyReceiver(), filter);
+
+            refreshForcedAppStandbyUidPackagesLocked();
+
+            mPowerManagerInternal.registerLowPowerModeObserver(
+                    ServiceType.FORCE_ALL_APPS_STANDBY,
+                    (state) -> updateForceAllAppsStandby(state.batterySaverEnabled));
+
+            updateForceAllAppsStandby(mPowerManagerInternal.getLowPowerState(
+                    ServiceType.FORCE_ALL_APPS_STANDBY).batterySaverEnabled);
+        }
+    }
+
+    @VisibleForTesting
+    AppOpsManager injectAppOpsManager() {
+        return mContext.getSystemService(AppOpsManager.class);
+    }
+
+    @VisibleForTesting
+    IAppOpsService injectIAppOpsService() {
+        return IAppOpsService.Stub.asInterface(
+                ServiceManager.getService(Context.APP_OPS_SERVICE));
+    }
+
+    @VisibleForTesting
+    IActivityManager injectIActivityManager() {
+        return ActivityManager.getService();
+    }
+
+    @VisibleForTesting
+    PowerManagerInternal injectPowerManagerInternal() {
+        return LocalServices.getService(PowerManagerInternal.class);
+    }
+
+    /**
+     * Update {@link #mRunAnyRestrictedPackages} with the current app ops state.
+     */
+    private void refreshForcedAppStandbyUidPackagesLocked() {
+        mRunAnyRestrictedPackages.clear();
+        final List<PackageOps> ops = mAppOpsManager.getPackagesForOps(
+                new int[] {TARGET_OP});
+
+        if (ops == null) {
+            return;
+        }
+        final int size = ops.size();
+        for (int i = 0; i < size; i++) {
+            final AppOpsManager.PackageOps pkg = ops.get(i);
+            final List<AppOpsManager.OpEntry> entries = ops.get(i).getOps();
+
+            for (int j = 0; j < entries.size(); j++) {
+                AppOpsManager.OpEntry ent = entries.get(j);
+                if (ent.getOp() != TARGET_OP) {
+                    continue;
+                }
+                if (ent.getMode() != AppOpsManager.MODE_ALLOWED) {
+                    mRunAnyRestrictedPackages.add(Pair.create(
+                            pkg.getUid(), pkg.getPackageName()));
+                }
+            }
+        }
+    }
+
+    /**
+     * Update {@link #mForceAllAppsStandby} and notifies the listeners.
+     */
+    void updateForceAllAppsStandby(boolean enable) {
+        synchronized (mLock) {
+            if (enable == mForceAllAppsStandby) {
+                return;
+            }
+            mForceAllAppsStandby = enable;
+
+            mHandler.notifyForceAllAppsStandbyChanged();
+        }
+    }
+
+    private int findForcedAppStandbyUidPackageIndexLocked(int uid, @NonNull String packageName) {
+        final int size = mRunAnyRestrictedPackages.size();
+        if (size > 8) {
+            return mRunAnyRestrictedPackages.indexOf(Pair.create(uid, packageName));
+        }
+        for (int i = 0; i < size; i++) {
+            final Pair<Integer, String> pair = mRunAnyRestrictedPackages.valueAt(i);
+
+            if ((pair.first == uid) && packageName.equals(pair.second)) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * @return whether a uid package-name pair is in mRunAnyRestrictedPackages.
+     */
+    boolean isRunAnyRestrictedLocked(int uid, @NonNull String packageName) {
+        return findForcedAppStandbyUidPackageIndexLocked(uid, packageName) >= 0;
+    }
+
+    /**
+     * Add to / remove from {@link #mRunAnyRestrictedPackages}.
+     */
+    boolean updateForcedAppStandbyUidPackageLocked(int uid, @NonNull String packageName,
+            boolean restricted) {
+        final int index =  findForcedAppStandbyUidPackageIndexLocked(uid, packageName);
+        final boolean wasRestricted = index >= 0;
+        if (wasRestricted == restricted) {
+            return false;
+        }
+        if (restricted) {
+            mRunAnyRestrictedPackages.add(Pair.create(uid, packageName));
+        } else {
+            mRunAnyRestrictedPackages.removeAt(index);
+        }
+        return true;
+    }
+
+    /**
+     * Puts a UID to {@link #mForegroundUids}.
+     */
+    void uidToForeground(int uid) {
+        synchronized (mLock) {
+            if (!UserHandle.isApp(uid)) {
+                return;
+            }
+            // TODO This can be optimized by calling indexOfKey and sharing the index for get and
+            // put.
+            if (mForegroundUids.get(uid)) {
+                return;
+            }
+            mForegroundUids.put(uid, true);
+            mHandler.notifyUidForegroundStateChanged(uid);
+        }
+    }
+
+    /**
+     * Sets false for a UID {@link #mForegroundUids}, or remove it when {@code remove} is true.
+     */
+    void uidToBackground(int uid, boolean remove) {
+        synchronized (mLock) {
+            if (!UserHandle.isApp(uid)) {
+                return;
+            }
+            // TODO This can be optimized by calling indexOfKey and sharing the index for get and
+            // put.
+            if (!mForegroundUids.get(uid)) {
+                return;
+            }
+            if (remove) {
+                mForegroundUids.delete(uid);
+            } else {
+                mForegroundUids.put(uid, false);
+            }
+            mHandler.notifyUidForegroundStateChanged(uid);
+        }
+    }
+
+    private final class UidObserver extends IUidObserver.Stub {
+        @Override public void onUidStateChanged(int uid, int procState, long procStateSeq) {
+        }
+
+        @Override public void onUidGone(int uid, boolean disabled) {
+            uidToBackground(uid, /*remove=*/ true);
+        }
+
+        @Override public void onUidActive(int uid) {
+            uidToForeground(uid);
+        }
+
+        @Override public void onUidIdle(int uid, boolean disabled) {
+            // Just to avoid excessive memcpy, don't remove from the array in this case.
+            uidToBackground(uid, /*remove=*/ false);
+        }
+
+        @Override public void onUidCachedChanged(int uid, boolean cached) {
+        }
+    };
+
+    private final class AppOpsWatcher extends IAppOpsCallback.Stub {
+        @Override
+        public void opChanged(int op, int uid, String packageName) throws RemoteException {
+            boolean restricted = false;
+            try {
+                restricted = mAppOpsService.checkOperation(TARGET_OP,
+                        uid, packageName) != AppOpsManager.MODE_ALLOWED;
+            } catch (RemoteException e) {
+                // Shouldn't happen
+            }
+            synchronized (mLock) {
+                if (updateForcedAppStandbyUidPackageLocked(uid, packageName, restricted)) {
+                    mHandler.notifyRunAnyAppOpsChanged(uid, packageName);
+                }
+            }
+        }
+    }
+
+    private final class MyReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (Intent.ACTION_USER_REMOVED.equals(intent.getAction())) {
+                final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+                if (userId > 0) {
+                    mHandler.doUserRemoved(userId);
+                }
+            }
+        }
+    }
+
+    private Listener[] cloneListeners() {
+        synchronized (mLock) {
+            return mListeners.toArray(new Listener[mListeners.size()]);
+        }
+    }
+
+    private class MyHandler extends Handler {
+        private static final int MSG_UID_STATE_CHANGED = 1;
+        private static final int MSG_RUN_ANY_CHANGED = 2;
+        private static final int MSG_ALL_UNWHITELISTED = 3;
+        private static final int MSG_ALL_WHITELIST_CHANGED = 4;
+        private static final int MSG_TEMP_WHITELIST_CHANGED = 5;
+        private static final int MSG_FORCE_ALL_CHANGED = 6;
+
+        private static final int MSG_USER_REMOVED = 7;
+
+        public MyHandler(Looper looper) {
+            super(looper);
+        }
+
+        public void notifyUidForegroundStateChanged(int uid) {
+            obtainMessage(MSG_UID_STATE_CHANGED, uid, 0).sendToTarget();
+        }
+        public void notifyRunAnyAppOpsChanged(int uid, @NonNull String packageName) {
+            obtainMessage(MSG_RUN_ANY_CHANGED, uid, 0, packageName).sendToTarget();
+        }
+
+        public void notifyAllUnwhitelisted() {
+            obtainMessage(MSG_ALL_UNWHITELISTED).sendToTarget();
+        }
+
+        public void notifyAllWhitelistChanged() {
+            obtainMessage(MSG_ALL_WHITELIST_CHANGED).sendToTarget();
+        }
+
+        public void notifyTempWhitelistChanged() {
+            obtainMessage(MSG_TEMP_WHITELIST_CHANGED).sendToTarget();
+        }
+
+        public void notifyForceAllAppsStandbyChanged() {
+            obtainMessage(MSG_FORCE_ALL_CHANGED).sendToTarget();
+        }
+
+        public void doUserRemoved(int userId) {
+            obtainMessage(MSG_USER_REMOVED, userId, 0).sendToTarget();
+        }
+
+        @Override
+        public void dispatchMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_USER_REMOVED:
+                    handleUserRemoved(msg.arg1);
+                    return;
+            }
+
+            // Only notify the listeners when started.
+            synchronized (mLock) {
+                if (!mStarted) {
+                    return;
+                }
+            }
+            final ForceAppStandbyTracker sender = ForceAppStandbyTracker.this;
+
+            switch (msg.what) {
+                case MSG_UID_STATE_CHANGED:
+                    for (Listener l : cloneListeners()) {
+                        l.onUidForegroundStateChanged(sender, msg.arg1);
+                    }
+                    return;
+                case MSG_RUN_ANY_CHANGED:
+                    for (Listener l : cloneListeners()) {
+                        l.onRunAnyAppOpsChanged(sender, msg.arg1, (String) msg.obj);
+                    }
+                    return;
+                case MSG_ALL_UNWHITELISTED:
+                    for (Listener l : cloneListeners()) {
+                        l.onPowerSaveUnwhitelisted(sender);
+                    }
+                    return;
+                case MSG_ALL_WHITELIST_CHANGED:
+                    for (Listener l : cloneListeners()) {
+                        l.onPowerSaveWhitelistedChanged(sender);
+                    }
+                    return;
+                case MSG_TEMP_WHITELIST_CHANGED:
+                    for (Listener l : cloneListeners()) {
+                        l.onTempPowerSaveWhitelistChanged(sender);
+                    }
+                    return;
+                case MSG_FORCE_ALL_CHANGED:
+                    for (Listener l : cloneListeners()) {
+                        l.onForceAllAppsStandbyChanged(sender);
+                    }
+                    return;
+                case MSG_USER_REMOVED:
+                    handleUserRemoved(msg.arg1);
+                    return;
+            }
+        }
+    }
+
+    void handleUserRemoved(int removedUserId) {
+        synchronized (mLock) {
+            for (int i = mRunAnyRestrictedPackages.size() - 1; i >= 0; i--) {
+                final Pair<Integer, String> pair = mRunAnyRestrictedPackages.valueAt(i);
+                final int uid = pair.first;
+                final int userId = UserHandle.getUserId(uid);
+
+                if (userId == removedUserId) {
+                    mRunAnyRestrictedPackages.removeAt(i);
+                }
+            }
+            for (int i = mForegroundUids.size() - 1; i >= 0; i--) {
+                final int uid = mForegroundUids.keyAt(i);
+                final int userId = UserHandle.getUserId(uid);
+
+                if (userId == removedUserId) {
+                    mForegroundUids.removeAt(i);
+                }
+            }
+        }
+    }
+
+    /**
+     * Called by device idle controller to update the power save whitelists.
+     */
+    public void setPowerSaveWhitelistAppIds(
+            int[] powerSaveWhitelistAllAppIdArray, int[] tempWhitelistAppIdArray) {
+        synchronized (mLock) {
+            final int[] previousWhitelist = mPowerWhitelistedAllAppIds;
+            final int[] previousTempWhitelist = mTempWhitelistedAppIds;
+
+            mPowerWhitelistedAllAppIds = powerSaveWhitelistAllAppIdArray;
+            mTempWhitelistedAppIds = tempWhitelistAppIdArray;
+
+            if (isAnyAppIdUnwhitelisted(previousWhitelist, mPowerWhitelistedAllAppIds)) {
+                mHandler.notifyAllUnwhitelisted();
+            } else if (!Arrays.equals(previousWhitelist, mPowerWhitelistedAllAppIds)) {
+                mHandler.notifyAllWhitelistChanged();
+            }
+
+            if (!Arrays.equals(previousTempWhitelist, mTempWhitelistedAppIds)) {
+                mHandler.notifyTempWhitelistChanged();
+            }
+
+        }
+    }
+
+    /**
+     * @retunr true if a sorted app-id array {@code prevArray} has at least one element
+     * that's not in a sorted app-id array {@code newArray}.
+     */
+    @VisibleForTesting
+    static boolean isAnyAppIdUnwhitelisted(int[] prevArray, int[] newArray) {
+        int i1 = 0;
+        int i2 = 0;
+        boolean prevFinished;
+        boolean newFinished;
+
+        for (;;) {
+            prevFinished = i1 >= prevArray.length;
+            newFinished = i2 >= newArray.length;
+            if (prevFinished || newFinished) {
+                break;
+            }
+            int a1 = prevArray[i1];
+            int a2 = newArray[i2];
+
+            if (a1 == a2) {
+                i1++;
+                i2++;
+                continue;
+            }
+            if (a1 < a2) {
+                // prevArray has an element that's not in a2.
+                return true;
+            }
+            i2++;
+        }
+        if (prevFinished) {
+            return false;
+        }
+        return newFinished;
+    }
+
+    // Public interface.
+
+    /**
+     * Register a new listener.
+     */
+    public void addListener(@NonNull Listener listener) {
+        synchronized (mLock) {
+            mListeners.add(listener);
+        }
+    }
+
+    /**
+     * @return whether alarms should be restricted for a UID package-name.
+     */
+    public boolean areAlarmsRestricted(int uid, @NonNull String packageName) {
+        return isRestricted(uid, packageName, /*useTempWhitelistToo=*/ false);
+    }
+
+    /**
+     * @return whether jobs should be restricted for a UID package-name.
+     */
+    public boolean areJobsRestricted(int uid, @NonNull String packageName) {
+        return isRestricted(uid, packageName, /*useTempWhitelistToo=*/ true);
+    }
+
+    /**
+     * @return whether force-app-standby is effective for a UID package-name.
+     */
+    private boolean isRestricted(int uid, @NonNull String packageName,
+            boolean useTempWhitelistToo) {
+        if (isInForeground(uid)) {
+            return false;
+        }
+        synchronized (mLock) {
+            // Whitelisted?
+            final int appId = UserHandle.getAppId(uid);
+            if (ArrayUtils.contains(mPowerWhitelistedAllAppIds, appId)) {
+                return false;
+            }
+            if (useTempWhitelistToo &&
+                    ArrayUtils.contains(mTempWhitelistedAppIds, appId)) {
+                return false;
+            }
+
+            if (mForceAllAppsStandby) {
+                return true;
+            }
+
+            return isRunAnyRestrictedLocked(uid, packageName);
+        }
+    }
+
+    /**
+     * @return whether a UID is in the foreground or not.
+     *
+     * Note clients normally shouldn't need to access it. It's only for dumpsys.
+     */
+    public boolean isInForeground(int uid) {
+        if (!UserHandle.isApp(uid)) {
+            return true;
+        }
+        synchronized (mLock) {
+            return mForegroundUids.get(uid);
+        }
+    }
+
+    /**
+     * @return whether force all apps standby is enabled or not.
+     *
+     * Note clients normally shouldn't need to access it.
+     */
+    boolean isForceAllAppsStandbyEnabled() {
+        synchronized (mLock) {
+            return mForceAllAppsStandby;
+        }
+    }
+
+    /**
+     * @return whether a UID/package has {@code OP_RUN_ANY_IN_BACKGROUND} allowed or not.
+     *
+     * Note clients normally shouldn't need to access it. It's only for dumpsys.
+     */
+    public boolean isRunAnyInBackgroundAppOpsAllowed(int uid, @NonNull String packageName) {
+        synchronized (mLock) {
+            return !isRunAnyRestrictedLocked(uid, packageName);
+        }
+    }
+
+    /**
+     * @return whether a UID is in the user / system defined power-save whitelist or not.
+     *
+     * Note clients normally shouldn't need to access it. It's only for dumpsys.
+     */
+    public boolean isUidPowerSaveWhitelisted(int uid) {
+        synchronized (mLock) {
+            return ArrayUtils.contains(mPowerWhitelistedAllAppIds, UserHandle.getAppId(uid));
+        }
+    }
+
+    /**
+     * @return whether a UID is in the temp power-save whitelist or not.
+     *
+     * Note clients normally shouldn't need to access it. It's only for dumpsys.
+     */
+    public boolean isUidTempPowerSaveWhitelisted(int uid) {
+        synchronized (mLock) {
+            return ArrayUtils.contains(mTempWhitelistedAppIds, UserHandle.getAppId(uid));
+        }
+    }
+
+    public void dump(PrintWriter pw, String indent) {
+        synchronized (mLock) {
+            pw.print(indent);
+            pw.print("Force all apps standby: ");
+            pw.println(isForceAllAppsStandbyEnabled());
+
+            pw.print(indent);
+            pw.print("Foreground uids: [");
+
+            String sep = "";
+            for (int i = 0; i < mForegroundUids.size(); i++) {
+                if (mForegroundUids.valueAt(i)) {
+                    pw.print(sep);
+                    pw.print(UserHandle.formatUid(mForegroundUids.keyAt(i)));
+                    sep = " ";
+                }
+            }
+            pw.println("]");
+
+            pw.print(indent);
+            pw.print("Whitelist appids: ");
+            pw.println(Arrays.toString(mPowerWhitelistedAllAppIds));
+
+            pw.print(indent);
+            pw.print("Temp whitelist appids: ");
+            pw.println(Arrays.toString(mTempWhitelistedAppIds));
+
+            pw.print(indent);
+            pw.println("Restricted packages:");
+            for (Pair<Integer, String> uidAndPackage : mRunAnyRestrictedPackages) {
+                pw.print(indent);
+                pw.print("  ");
+                pw.print(UserHandle.formatUid(uidAndPackage.first));
+                pw.print(" ");
+                pw.print(uidAndPackage.second);
+                pw.println();
+            }
+        }
+    }
+
+    public void dumpProto(ProtoOutputStream proto, long fieldId) {
+        synchronized (mLock) {
+            final long token = proto.start(fieldId);
+
+            proto.write(ForceAppStandbyTrackerProto.FORCE_ALL_APPS_STANDBY, mForceAllAppsStandby);
+
+            for (int i = 0; i < mForegroundUids.size(); i++) {
+                if (mForegroundUids.valueAt(i)) {
+                    proto.write(ForceAppStandbyTrackerProto.FOREGROUND_UIDS,
+                            mForegroundUids.keyAt(i));
+                }
+            }
+
+            for (int appId : mPowerWhitelistedAllAppIds) {
+                proto.write(ForceAppStandbyTrackerProto.POWER_SAVE_WHITELIST_APP_IDS, appId);
+            }
+
+            for (int appId : mTempWhitelistedAppIds) {
+                proto.write(ForceAppStandbyTrackerProto.TEMP_POWER_SAVE_WHITELIST_APP_IDS, appId);
+            }
+
+            for (Pair<Integer, String> uidAndPackage : mRunAnyRestrictedPackages) {
+                final long token2 = proto.start(
+                        ForceAppStandbyTrackerProto.RUN_ANY_IN_BACKGROUND_RESTRICTED_PACKAGES);
+                proto.write(RunAnyInBackgroundRestrictedPackages.UID, uidAndPackage.first);
+                proto.write(RunAnyInBackgroundRestrictedPackages.PACKAGE_NAME,
+                        uidAndPackage.second);
+                proto.end(token2);
+            }
+            proto.end(token);
+        }
+    }
+}
diff --git a/com/android/server/GestureLauncherService.java b/com/android/server/GestureLauncherService.java
index a903f3d..b7b5bd9 100644
--- a/com/android/server/GestureLauncherService.java
+++ b/com/android/server/GestureLauncherService.java
@@ -40,13 +40,13 @@
 import android.util.MutableBoolean;
 import android.util.Slog;
 import android.view.KeyEvent;
-import android.view.WindowManagerInternal;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.server.LocalServices;
 import com.android.server.statusbar.StatusBarManagerInternal;
+import com.android.server.wm.WindowManagerInternal;
 
 /**
  * The service that listens for gestures detected in sensor firmware and starts the intent
diff --git a/com/android/server/InputMethodManagerService.java b/com/android/server/InputMethodManagerService.java
index f007bcc..fc57a0d 100644
--- a/com/android/server/InputMethodManagerService.java
+++ b/com/android/server/InputMethodManagerService.java
@@ -128,7 +128,6 @@
 import android.view.ViewGroup;
 import android.view.Window;
 import android.view.WindowManager;
-import android.view.WindowManagerInternal;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputBinding;
 import android.view.inputmethod.InputConnection;
@@ -147,6 +146,8 @@
 import android.widget.TextView;
 import android.widget.Toast;
 
+import com.android.server.wm.WindowManagerInternal;
+
 import java.io.File;
 import java.io.FileDescriptor;
 import java.io.FileInputStream;
diff --git a/com/android/server/LocationManagerService.java b/com/android/server/LocationManagerService.java
index 0fd59ea..bdfccd6 100644
--- a/com/android/server/LocationManagerService.java
+++ b/com/android/server/LocationManagerService.java
@@ -118,7 +118,7 @@
     private static final String TAG = "LocationManagerService";
     public static final boolean D = Log.isLoggable(TAG, Log.DEBUG);
 
-    private static final String WAKELOCK_KEY = TAG;
+    private static final String WAKELOCK_KEY = "*location*";
 
     // Location resolution level: no location data whatsoever
     private static final int RESOLUTION_LEVEL_NONE = 0;
diff --git a/com/android/server/NetworkTimeUpdateService.java b/com/android/server/NetworkTimeUpdateService.java
index 1ad1404..2c24798 100644
--- a/com/android/server/NetworkTimeUpdateService.java
+++ b/com/android/server/NetworkTimeUpdateService.java
@@ -69,13 +69,10 @@
     private static final String ACTION_POLL =
             "com.android.server.NetworkTimeUpdateService.action.POLL";
 
-    private static final int NETWORK_CHANGE_EVENT_DELAY_MS = 1000;
-    private static int POLL_REQUEST = 0;
+    private static final int POLL_REQUEST = 0;
 
     private static final long NOT_SET = -1;
     private long mNitzTimeSetTime = NOT_SET;
-    // TODO: Have a way to look up the timezone we are in
-    private long mNitzZoneSetTime = NOT_SET;
     private Network mDefaultNetwork = null;
 
     private Context mContext;
@@ -144,7 +141,6 @@
     private void registerForTelephonyIntents() {
         IntentFilter intentFilter = new IntentFilter();
         intentFilter.addAction(TelephonyIntents.ACTION_NETWORK_SET_TIME);
-        intentFilter.addAction(TelephonyIntents.ACTION_NETWORK_SET_TIMEZONE);
         mContext.registerReceiver(mNitzReceiver, intentFilter);
     }
 
@@ -257,8 +253,6 @@
             if (DBG) Log.d(TAG, "Received " + action);
             if (TelephonyIntents.ACTION_NETWORK_SET_TIME.equals(action)) {
                 mNitzTimeSetTime = SystemClock.elapsedRealtime();
-            } else if (TelephonyIntents.ACTION_NETWORK_SET_TIMEZONE.equals(action)) {
-                mNitzZoneSetTime = SystemClock.elapsedRealtime();
             }
         }
     };
diff --git a/com/android/server/StorageManagerService.java b/com/android/server/StorageManagerService.java
index 3c955eb..66b3adb 100644
--- a/com/android/server/StorageManagerService.java
+++ b/com/android/server/StorageManagerService.java
@@ -2192,6 +2192,11 @@
         mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
             "no permission to access the crypt keeper");
 
+        if (StorageManager.isFileEncryptedNativeOnly()) {
+            // Not supported on FBE devices
+            return -1;
+        }
+
         if (type == StorageManager.CRYPT_TYPE_DEFAULT) {
             password = "";
         } else if (TextUtils.isEmpty(password)) {
@@ -2268,6 +2273,11 @@
         mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
             "no permission to access the crypt keeper");
 
+        if (StorageManager.isFileEncryptedNativeOnly()) {
+            // Not supported on FBE devices
+            return;
+        }
+
         try {
             mVold.fdeSetField(field, contents);
             return;
@@ -2287,6 +2297,11 @@
         mContext.enforceCallingOrSelfPermission(Manifest.permission.CRYPT_KEEPER,
             "no permission to access the crypt keeper");
 
+        if (StorageManager.isFileEncryptedNativeOnly()) {
+            // Not supported on FBE devices
+            return null;
+        }
+
         try {
             return mVold.fdeGetField(field);
         } catch (Exception e) {
@@ -2568,7 +2583,7 @@
     }
 
     @Override
-    public int mkdirs(String callingPkg, String appPath) {
+    public void mkdirs(String callingPkg, String appPath) {
         final int userId = UserHandle.getUserId(Binder.getCallingUid());
         final UserEnvironment userEnv = new UserEnvironment(userId);
 
@@ -2581,8 +2596,7 @@
         try {
             appFile = new File(appPath).getCanonicalFile();
         } catch (IOException e) {
-            Slog.e(TAG, "Failed to resolve " + appPath + ": " + e);
-            return -1;
+            throw new IllegalStateException("Failed to resolve " + appPath + ": " + e);
         }
 
         // Try translating the app path into a vold path, but require that it
@@ -2597,9 +2611,8 @@
 
             try {
                 mVold.mkdirs(appPath);
-                return 0;
             } catch (Exception e) {
-                Slog.wtf(TAG, e);
+                throw new IllegalStateException("Failed to prepare " + appPath + ": " + e);
             }
         }
 
diff --git a/com/android/server/SystemServer.java b/com/android/server/SystemServer.java
index 74a7bd4..33f4e34 100644
--- a/com/android/server/SystemServer.java
+++ b/com/android/server/SystemServer.java
@@ -35,6 +35,7 @@
 import android.os.IIncidentManager;
 import android.os.Looper;
 import android.os.Message;
+import android.os.Parcel;
 import android.os.PowerManager;
 import android.os.Process;
 import android.os.ServiceManager;
@@ -360,6 +361,9 @@
             // to avoid throwing BadParcelableException.
             BaseBundle.setShouldDefuse(true);
 
+            // Within the system server, when parceling exceptions, include the stack trace
+            Parcel.setStackTraceParceling(true);
+
             // Ensure binder calls into the system always run at foreground priority.
             BinderInternal.disableBackgroundScheduling(true);
 
diff --git a/com/android/server/accessibility/AccessibilityClientConnection.java b/com/android/server/accessibility/AccessibilityClientConnection.java
index df4c8ed..22d922b 100644
--- a/com/android/server/accessibility/AccessibilityClientConnection.java
+++ b/com/android/server/accessibility/AccessibilityClientConnection.java
@@ -20,9 +20,7 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
 
-import android.accessibilityservice.AccessibilityService;
 import android.accessibilityservice.AccessibilityServiceInfo;
-import android.accessibilityservice.GestureDescription;
 import android.accessibilityservice.IAccessibilityServiceClient;
 import android.accessibilityservice.IAccessibilityServiceConnection;
 import android.annotation.NonNull;
@@ -47,10 +45,8 @@
 import android.view.KeyEvent;
 import android.view.MagnificationSpec;
 import android.view.View;
-import android.view.WindowManagerInternal;
 import android.view.accessibility.AccessibilityCache;
 import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityInteractionClient;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.accessibility.AccessibilityWindowInfo;
 import android.view.accessibility.IAccessibilityInteractionConnection;
@@ -60,11 +56,13 @@
 import com.android.internal.os.SomeArgs;
 import com.android.internal.util.DumpUtils;
 import com.android.server.accessibility.AccessibilityManagerService.SecurityPolicy;
+import com.android.server.wm.WindowManagerInternal;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -106,7 +104,7 @@
 
     int mFeedbackType;
 
-    Set<String> mPackageNames = new HashSet<>();
+    final Set<String> mPackageNames = new HashSet<>();
 
     boolean mIsDefault;
 
@@ -284,40 +282,98 @@
         return true;
     }
 
-    public void setDynamicallyConfigurableProperties(AccessibilityServiceInfo info) {
-        mEventTypes = info.eventTypes;
-        mFeedbackType = info.feedbackType;
-        String[] packageNames = info.packageNames;
-        if (packageNames != null) {
-            mPackageNames.addAll(Arrays.asList(packageNames));
+    boolean setDynamicallyConfigurableProperties(AccessibilityServiceInfo info) {
+        boolean somethingChanged = false;
+
+        if (mEventTypes != info.eventTypes) {
+            mEventTypes = info.eventTypes;
+            somethingChanged = true;
         }
-        mNotificationTimeout = info.notificationTimeout;
-        mIsDefault = (info.flags & DEFAULT) != 0;
+
+        if (mFeedbackType != info.feedbackType) {
+            mFeedbackType = info.feedbackType;
+            somethingChanged = true;
+        }
+
+        final String[] oldPackageNames = mPackageNames.toArray(new String[mPackageNames.size()]);
+        if (!Arrays.equals(oldPackageNames, info.packageNames)) {
+            mPackageNames.clear();
+            if (info.packageNames != null) {
+                Collections.addAll(mPackageNames, info.packageNames);
+            }
+            somethingChanged = true;
+        }
+
+        if (mNotificationTimeout != info.notificationTimeout) {
+            mNotificationTimeout = info.notificationTimeout;
+            somethingChanged = true;
+        }
+
+        final boolean newIsDefault = (info.flags & DEFAULT) != 0;
+        if (mIsDefault != newIsDefault) {
+            mIsDefault = newIsDefault;
+            somethingChanged = true;
+        }
 
         if (supportsFlagForNotImportantViews(info)) {
-            if ((info.flags & AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) != 0) {
-                mFetchFlags |= AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
-            } else {
-                mFetchFlags &= ~AccessibilityNodeInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS;
+            somethingChanged |= updateFetchFlag(info.flags,
+                    AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS);
+        }
+
+        somethingChanged |= updateFetchFlag(info.flags,
+                AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS);
+
+        final boolean newRequestTouchExplorationMode = (info.flags
+                & AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE) != 0;
+        if (mRequestTouchExplorationMode != newRequestTouchExplorationMode) {
+            mRequestTouchExplorationMode = newRequestTouchExplorationMode;
+            somethingChanged = true;
+        }
+
+        final boolean newRequestFilterKeyEvents = (info.flags
+                & AccessibilityServiceInfo.FLAG_REQUEST_FILTER_KEY_EVENTS) != 0;
+        if (mRequestFilterKeyEvents != newRequestFilterKeyEvents) {
+            mRequestFilterKeyEvents = newRequestFilterKeyEvents;
+            somethingChanged = true;
+        }
+
+        final boolean newRetrieveInteractiveWindows = (info.flags
+                & AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS) != 0;
+        if (mRetrieveInteractiveWindows != newRetrieveInteractiveWindows) {
+            mRetrieveInteractiveWindows = newRetrieveInteractiveWindows;
+            somethingChanged = true;
+        }
+
+        final boolean newCaptureFingerprintGestures = (info.flags
+                & AccessibilityServiceInfo.FLAG_REQUEST_FINGERPRINT_GESTURES) != 0;
+        if (mCaptureFingerprintGestures != newCaptureFingerprintGestures) {
+            mCaptureFingerprintGestures = newCaptureFingerprintGestures;
+            somethingChanged = true;
+        }
+
+        final boolean newRequestAccessibilityButton = (info.flags
+                & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0;
+        if (mRequestAccessibilityButton != newRequestAccessibilityButton) {
+            mRequestAccessibilityButton = newRequestAccessibilityButton;
+            somethingChanged = true;
+        }
+
+        return somethingChanged;
+    }
+
+    private boolean updateFetchFlag(int allFlags, int flagToUpdate) {
+        if ((allFlags & flagToUpdate) != 0) {
+            if ((mFetchFlags & flagToUpdate) == 0) {
+                mFetchFlags |= flagToUpdate;
+                return true;
+            }
+        } else {
+            if ((mFetchFlags & flagToUpdate) != 0) {
+                mFetchFlags &= ~flagToUpdate;
+                return true;
             }
         }
-
-        if ((info.flags & AccessibilityServiceInfo.FLAG_REPORT_VIEW_IDS) != 0) {
-            mFetchFlags |= AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS;
-        } else {
-            mFetchFlags &= ~AccessibilityNodeInfo.FLAG_REPORT_VIEW_IDS;
-        }
-
-        mRequestTouchExplorationMode = (info.flags
-                & AccessibilityServiceInfo.FLAG_REQUEST_TOUCH_EXPLORATION_MODE) != 0;
-        mRequestFilterKeyEvents = (info.flags
-                & AccessibilityServiceInfo.FLAG_REQUEST_FILTER_KEY_EVENTS) != 0;
-        mRetrieveInteractiveWindows = (info.flags
-                & AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS) != 0;
-        mCaptureFingerprintGestures = (info.flags
-                & AccessibilityServiceInfo.FLAG_REQUEST_FINGERPRINT_GESTURES) != 0;
-        mRequestAccessibilityButton = (info.flags
-                & AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON) != 0;
+        return false;
     }
 
     protected boolean supportsFlagForNotImportantViews(AccessibilityServiceInfo info) {
@@ -349,14 +405,15 @@
                 // If the XML manifest had data to configure the service its info
                 // should be already set. In such a case update only the dynamically
                 // configurable properties.
+                final boolean serviceInfoChanged;
                 AccessibilityServiceInfo oldInfo = mAccessibilityServiceInfo;
                 if (oldInfo != null) {
                     oldInfo.updateDynamicallyConfigurableProperties(info);
-                    setDynamicallyConfigurableProperties(oldInfo);
+                    serviceInfoChanged = setDynamicallyConfigurableProperties(oldInfo);
                 } else {
-                    setDynamicallyConfigurableProperties(info);
+                    serviceInfoChanged = setDynamicallyConfigurableProperties(info);
                 }
-                mSystemSupport.onClientChange(true);
+                mSystemSupport.onClientChange(serviceInfoChanged);
             }
         } finally {
             Binder.restoreCallingIdentity(identity);
diff --git a/com/android/server/accessibility/AccessibilityInputFilter.java b/com/android/server/accessibility/AccessibilityInputFilter.java
index f6fcaae..11b2343 100644
--- a/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -31,11 +31,11 @@
 import android.view.InputFilter;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
-import android.view.WindowManagerPolicy;
 import android.view.accessibility.AccessibilityEvent;
 
 import com.android.internal.util.BitUtils;
 import com.android.server.LocalServices;
+import com.android.server.policy.WindowManagerPolicy;
 
 /**
  * This class is an input filter for implementing accessibility features such
diff --git a/com/android/server/accessibility/AccessibilityManagerService.java b/com/android/server/accessibility/AccessibilityManagerService.java
index d661754..8b5c85a 100644
--- a/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/com/android/server/accessibility/AccessibilityManagerService.java
@@ -82,7 +82,6 @@
 import android.view.View;
 import android.view.WindowInfo;
 import android.view.WindowManager;
-import android.view.WindowManagerInternal;
 import android.view.accessibility.AccessibilityCache;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityInteractionClient;
@@ -102,6 +101,7 @@
 import com.android.internal.util.IntPair;
 import com.android.server.LocalServices;
 import com.android.server.policy.AccessibilityShortcutController;
+import com.android.server.wm.WindowManagerInternal;
 
 import org.xmlpull.v1.XmlPullParserException;
 
@@ -2400,7 +2400,8 @@
         private void announceNewUserIfNeeded() {
             synchronized (mLock) {
                 UserState userState = getCurrentUserStateLocked();
-                if (userState.isHandlingAccessibilityEvents()) {
+                if (userState.isHandlingAccessibilityEvents()
+                        && userState.isObservedEventType(AccessibilityEvent.TYPE_ANNOUNCEMENT)) {
                     UserManager userManager = (UserManager) mContext.getSystemService(
                             Context.USER_SERVICE);
                     String message = mContext.getString(R.string.user_switched,
@@ -3157,13 +3158,21 @@
             if (mWindowsForAccessibilityCallback == null) {
                 return;
             }
+            final int userId;
+            synchronized (mLock) {
+                userId = mCurrentUserId;
+                final UserState userState = getUserStateLocked(userId);
+                if (!userState.isObservedEventType(AccessibilityEvent.TYPE_WINDOWS_CHANGED)) {
+                    return;
+                }
+            }
             final long identity = Binder.clearCallingIdentity();
             try {
                 // Let the client know the windows changed.
                 AccessibilityEvent event = AccessibilityEvent.obtain(
                         AccessibilityEvent.TYPE_WINDOWS_CHANGED);
                 event.setEventTime(SystemClock.uptimeMillis());
-                sendAccessibilityEvent(event, mCurrentUserId);
+                sendAccessibilityEvent(event, userId);
             } finally {
                 Binder.restoreCallingIdentity(identity);
             }
@@ -3368,6 +3377,10 @@
             mUserId = userId;
         }
 
+        public boolean isObservedEventType(@AccessibilityEvent.EventType int type) {
+            return (mLastSentRelevantEventTypes & type) != 0;
+        }
+
         public int getClientState() {
             int clientState = 0;
             final boolean a11yEnabled = (mUiAutomationManager.isUiAutomationRunningLocked()
diff --git a/com/android/server/accessibility/AccessibilityServiceConnection.java b/com/android/server/accessibility/AccessibilityServiceConnection.java
index eb26752..9cafa1e 100644
--- a/com/android/server/accessibility/AccessibilityServiceConnection.java
+++ b/com/android/server/accessibility/AccessibilityServiceConnection.java
@@ -33,10 +33,10 @@
 import android.os.UserHandle;
 import android.provider.Settings;
 import android.util.Slog;
-import android.view.WindowManagerInternal;
 
 import com.android.server.accessibility.AccessibilityManagerService.SecurityPolicy;
 import com.android.server.accessibility.AccessibilityManagerService.UserState;
+import com.android.server.wm.WindowManagerInternal;
 
 import java.lang.ref.WeakReference;
 import java.util.List;
diff --git a/com/android/server/accessibility/GlobalActionPerformer.java b/com/android/server/accessibility/GlobalActionPerformer.java
index 5db6f7d..3b8d4bc 100644
--- a/com/android/server/accessibility/GlobalActionPerformer.java
+++ b/com/android/server/accessibility/GlobalActionPerformer.java
@@ -21,14 +21,18 @@
 import android.content.Context;
 import android.hardware.input.InputManager;
 import android.os.Binder;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.SystemClock;
+import android.view.IWindowManager;
 import android.view.InputDevice;
 import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
-import android.view.WindowManagerInternal;
 
 import com.android.server.LocalServices;
 import com.android.server.statusbar.StatusBarManagerInternal;
+import com.android.server.wm.WindowManagerInternal;
 
 /**
  * Handle the back-end of AccessibilityService#performGlobalAction
@@ -72,6 +76,9 @@
                 case AccessibilityService.GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN: {
                     return toggleSplitScreen();
                 }
+                case AccessibilityService.GLOBAL_ACTION_LOCK_SCREEN: {
+                    return lockScreen();
+                }
             }
             return false;
         } finally {
@@ -153,4 +160,11 @@
         }
         return true;
     }
+
+    private boolean lockScreen() {
+        mContext.getSystemService(PowerManager.class).goToSleep(SystemClock.uptimeMillis(),
+                PowerManager.GO_TO_SLEEP_REASON_ACCESSIBILITY, 0);
+        mWindowManagerService.lockNow();
+        return true;
+    }
 }
diff --git a/com/android/server/accessibility/KeyEventDispatcher.java b/com/android/server/accessibility/KeyEventDispatcher.java
index 3358432..b144e1c 100644
--- a/com/android/server/accessibility/KeyEventDispatcher.java
+++ b/com/android/server/accessibility/KeyEventDispatcher.java
@@ -26,7 +26,8 @@
 import android.util.Slog;
 import android.view.InputEventConsistencyVerifier;
 import android.view.KeyEvent;
-import android.view.WindowManagerPolicy;
+
+import com.android.server.policy.WindowManagerPolicy;
 
 import java.util.ArrayList;
 import java.util.List;
diff --git a/com/android/server/accessibility/KeyboardInterceptor.java b/com/android/server/accessibility/KeyboardInterceptor.java
index 7724945..bc379c2 100644
--- a/com/android/server/accessibility/KeyboardInterceptor.java
+++ b/com/android/server/accessibility/KeyboardInterceptor.java
@@ -22,7 +22,8 @@
 import android.util.Pools;
 import android.util.Slog;
 import android.view.KeyEvent;
-import android.view.WindowManagerPolicy;
+
+import com.android.server.policy.WindowManagerPolicy;
 
 /**
  * Intercepts key events and forwards them to accessibility manager service.
diff --git a/com/android/server/accessibility/MagnificationController.java b/com/android/server/accessibility/MagnificationController.java
index a10b7a2..a70b88e 100644
--- a/com/android/server/accessibility/MagnificationController.java
+++ b/com/android/server/accessibility/MagnificationController.java
@@ -34,7 +34,6 @@
 import android.util.Slog;
 import android.view.MagnificationSpec;
 import android.view.View;
-import android.view.WindowManagerInternal;
 import android.view.animation.DecelerateInterpolator;
 
 import com.android.internal.R;
@@ -42,6 +41,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.SomeArgs;
 import com.android.server.LocalServices;
+import com.android.server.wm.WindowManagerInternal;
 
 import java.util.Locale;
 
diff --git a/com/android/server/accessibility/MotionEventInjector.java b/com/android/server/accessibility/MotionEventInjector.java
index b6b7812..46e3226 100644
--- a/com/android/server/accessibility/MotionEventInjector.java
+++ b/com/android/server/accessibility/MotionEventInjector.java
@@ -31,9 +31,9 @@
 import android.util.SparseIntArray;
 import android.view.InputDevice;
 import android.view.MotionEvent;
-import android.view.WindowManagerPolicy;
 
 import com.android.internal.os.SomeArgs;
+import com.android.server.policy.WindowManagerPolicy;
 
 import java.util.ArrayList;
 import java.util.Arrays;
diff --git a/com/android/server/accessibility/TouchExplorer.java b/com/android/server/accessibility/TouchExplorer.java
index a32686d..62017e8 100644
--- a/com/android/server/accessibility/TouchExplorer.java
+++ b/com/android/server/accessibility/TouchExplorer.java
@@ -26,11 +26,12 @@
 import android.view.MotionEvent.PointerCoords;
 import android.view.MotionEvent.PointerProperties;
 import android.view.ViewConfiguration;
-import android.view.WindowManagerPolicy;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
 import android.view.accessibility.AccessibilityNodeInfo;
 
+import com.android.server.policy.WindowManagerPolicy;
+
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
@@ -790,7 +791,7 @@
      */
     private void sendAccessibilityEvent(int type) {
         AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(mContext);
-        if (accessibilityManager.isEnabled()) {
+        if (accessibilityManager.isObservedEventType(type)) {
             AccessibilityEvent event = AccessibilityEvent.obtain(type);
             event.setWindowId(mAms.getActiveWindowId());
             accessibilityManager.sendAccessibilityEvent(event);
diff --git a/com/android/server/accessibility/UiAutomationManager.java b/com/android/server/accessibility/UiAutomationManager.java
index a6b81ff..f057112 100644
--- a/com/android/server/accessibility/UiAutomationManager.java
+++ b/com/android/server/accessibility/UiAutomationManager.java
@@ -26,9 +26,10 @@
 import android.os.IBinder.DeathRecipient;
 import android.os.RemoteException;
 import android.util.Slog;
-import android.view.WindowManagerInternal;
 import android.view.accessibility.AccessibilityEvent;
 
+import com.android.server.wm.WindowManagerInternal;
+
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 
diff --git a/com/android/server/am/ActiveServices.java b/com/android/server/am/ActiveServices.java
index 2131731..3cd2f6a 100644
--- a/com/android/server/am/ActiveServices.java
+++ b/com/android/server/am/ActiveServices.java
@@ -58,6 +58,7 @@
 import com.android.internal.util.FastPrintWriter;
 import com.android.server.am.ActivityManagerService.ItemMatcher;
 import com.android.server.am.ActivityManagerService.NeededUriGrants;
+import com.android.server.am.proto.ActiveServicesProto;
 
 import android.app.ActivityManager;
 import android.app.AppGlobals;
@@ -85,6 +86,7 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
 import android.webkit.WebViewZygote;
 
 public final class ActiveServices {
@@ -633,7 +635,7 @@
                         sb.append("Stopping service due to app idle: ");
                         UserHandle.formatUid(sb, service.appInfo.uid);
                         sb.append(" ");
-                        TimeUtils.formatDuration(service.createTime
+                        TimeUtils.formatDuration(service.createRealTime
                                 - SystemClock.elapsedRealtime(), sb);
                         sb.append(" ");
                         sb.append(compName);
@@ -1043,8 +1045,8 @@
                         try {
                             if (AppGlobals.getPackageManager().checkPermission(
                                     android.Manifest.permission.INSTANT_APP_FOREGROUND_SERVICE,
-                                    r.appInfo.packageName,
-                                    r.appInfo.uid) != PackageManager.PERMISSION_GRANTED) {
+                                    r.appInfo.packageName, UserHandle.getUserId(r.appInfo.uid))
+                                            != PackageManager.PERMISSION_GRANTED) {
                                 throw new SecurityException("Instant app " + r.appInfo.packageName
                                         + " does not have permission to create foreground"
                                         + "services");
@@ -3220,7 +3222,7 @@
         info.uid = r.appInfo.uid;
         info.process = r.processName;
         info.foreground = r.isForeground;
-        info.activeSince = r.createTime;
+        info.activeSince = r.createRealTime;
         info.started = r.startRequested;
         info.clientCount = r.connections.size();
         info.crashCount = r.crashCount;
@@ -3574,7 +3576,7 @@
                 pw.print("    app=");
                 pw.println(r.app);
                 pw.print("    created=");
-                TimeUtils.formatDuration(r.createTime, nowReal, pw);
+                TimeUtils.formatDuration(r.createRealTime, nowReal, pw);
                 pw.print(" started=");
                 pw.print(r.startRequested);
                 pw.print(" connections=");
@@ -3840,6 +3842,26 @@
         return new ServiceDumper(fd, pw, args, opti, dumpAll, dumpPackage);
     }
 
+    protected void writeToProto(ProtoOutputStream proto) {
+        synchronized (mAm) {
+            int[] users = mAm.mUserController.getUsers();
+            for (int user : users) {
+                ServiceMap smap = mServiceMap.get(user);
+                if (smap == null) {
+                    continue;
+                }
+                long token = proto.start(ActiveServicesProto.SERVICES_BY_USERS);
+                proto.write(ActiveServicesProto.ServicesByUser.USER_ID, user);
+                ArrayMap<ComponentName, ServiceRecord> alls = smap.mServicesByName;
+                for (int i=0; i<alls.size(); i++) {
+                    alls.valueAt(i).writeToProto(proto,
+                            ActiveServicesProto.ServicesByUser.SERVICE_RECORDS);
+                }
+                proto.end(token);
+            }
+        }
+    }
+
     /**
      * There are three ways to call this:
      *  - no service specified: dump all the services
diff --git a/com/android/server/am/ActivityDisplay.java b/com/android/server/am/ActivityDisplay.java
index 2289f85..b11b16e 100644
--- a/com/android/server/am/ActivityDisplay.java
+++ b/com/android/server/am/ActivityDisplay.java
@@ -42,6 +42,8 @@
 import android.app.ActivityManagerInternal;
 import android.app.ActivityOptions;
 import android.app.WindowConfiguration;
+import android.graphics.Point;
+import android.graphics.Rect;
 import android.util.IntArray;
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
@@ -89,6 +91,9 @@
     private ActivityStack mPinnedStack = null;
     private ActivityStack mSplitScreenPrimaryStack = null;
 
+    // Used in updating the display size
+    private Point mTmpDisplaySize = new Point();
+
     ActivityDisplay(ActivityStackSupervisor supervisor, int displayId) {
         mSupervisor = supervisor;
         mDisplayId = displayId;
@@ -97,6 +102,13 @@
             throw new IllegalStateException("Display does not exist displayId=" + displayId);
         }
         mDisplay = display;
+
+        updateBounds();
+    }
+
+    void updateBounds() {
+        mDisplay.getSize(mTmpDisplaySize);
+        setBounds(0, 0, mTmpDisplaySize.x, mTmpDisplaySize.y);
     }
 
     void addChild(ActivityStack stack, int position) {
@@ -387,6 +399,16 @@
                 otherStack.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
             }
         } finally {
+            if (mHomeStack != null && !isTopStack(mHomeStack)) {
+                // Whenever split-screen is dismissed we want the home stack directly behind the
+                // currently top stack so it shows up when the top stack is finished.
+                final ActivityStack topStack = getTopStack();
+                // TODO: Would be better to use ActivityDisplay.positionChildAt() for this, however
+                // ActivityDisplay doesn't have a direct controller to WM side yet. We can switch
+                // once we have that.
+                mHomeStack.moveToFront("onSplitScreenModeDismissed");
+                topStack.moveToFront("onSplitScreenModeDismissed");
+            }
             mSupervisor.mWindowManager.continueSurfaceLayout();
         }
     }
diff --git a/com/android/server/am/ActivityManagerService.java b/com/android/server/am/ActivityManagerService.java
index 4361856..fe992da 100644
--- a/com/android/server/am/ActivityManagerService.java
+++ b/com/android/server/am/ActivityManagerService.java
@@ -246,6 +246,7 @@
 import android.app.assist.AssistContent;
 import android.app.assist.AssistStructure;
 import android.app.backup.IBackupManager;
+import android.app.servertransaction.ConfigurationChangeItem;
 import android.app.usage.UsageEvents;
 import android.app.usage.UsageStatsManagerInternal;
 import android.appwidget.AppWidgetManager;
@@ -317,6 +318,7 @@
 import android.os.ParcelFileDescriptor;
 import android.os.PersistableBundle;
 import android.os.PowerManager;
+import android.os.PowerManager.ServiceType;
 import android.os.PowerManagerInternal;
 import android.os.Process;
 import android.os.RemoteCallbackList;
@@ -351,7 +353,6 @@
 import android.util.StatsLog;
 import android.util.TimingsTraceLog;
 import android.util.DebugUtils;
-import android.util.DisplayMetrics;
 import android.util.EventLog;
 import android.util.Log;
 import android.util.Pair;
@@ -415,6 +416,8 @@
 import com.android.server.am.ActivityStack.ActivityState;
 import com.android.server.am.proto.ActivityManagerServiceProto;
 import com.android.server.am.proto.BroadcastProto;
+import com.android.server.am.proto.GrantUriProto;
+import com.android.server.am.proto.NeededUriGrantsProto;
 import com.android.server.am.proto.StickyBroadcastProto;
 import com.android.server.firewall.IntentFirewall;
 import com.android.server.job.JobSchedulerInternal;
@@ -630,6 +633,8 @@
 
     final ActivityStarter mActivityStarter;
 
+    final ClientLifecycleManager mLifecycleManager;
+
     final TaskChangeNotificationController mTaskChangeNotificationController;
 
     final InstrumentationReporter mInstrumentationReporter = new InstrumentationReporter();
@@ -712,8 +717,16 @@
 
     final UserController mUserController;
 
+    /**
+     * Packages that are being allowed to perform unrestricted app switches.  Mapping is
+     * User -> Type -> uid.
+     */
+    final SparseArray<ArrayMap<String, Integer>> mAllowAppSwitchUids = new SparseArray<>();
+
     final AppErrors mAppErrors;
 
+    final AppWarnings mAppWarnings;
+
     /**
      * Dump of the activity state at the time of the last ANR. Cleared after
      * {@link WindowManagerService#LAST_ANR_LIFETIME_DURATION_MSECS}
@@ -1191,6 +1204,13 @@
             return result;
         }
 
+        public void writeToProto(ProtoOutputStream proto, long fieldId) {
+            long token = proto.start(fieldId);
+            proto.write(GrantUriProto.URI, uri.toString());
+            proto.write(GrantUriProto.SOURCE_USER_ID, sourceUserId);
+            proto.end(token);
+        }
+
         public static GrantUri resolve(int defaultSourceUserHandle, Uri uri) {
             return new GrantUri(ContentProvider.getUserIdFromUri(uri, defaultSourceUserHandle),
                     ContentProvider.getUriWithoutUserId(uri), false);
@@ -1717,7 +1737,6 @@
     static final int IDLE_UIDS_MSG = 58;
     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 NOTIFY_VR_SLEEPING_MSG = 65;
     static final int SERVICE_FOREGROUND_TIMEOUT_MSG = 66;
@@ -1736,7 +1755,6 @@
     static KillHandler sKillHandler = null;
 
     CompatModeDialog mCompatModeDialog;
-    UnsupportedDisplaySizeDialog mUnsupportedDisplaySizeDialog;
     long mLastMemUsageReportTime = 0;
 
     /**
@@ -1764,6 +1782,11 @@
 
     final boolean mPermissionReviewRequired;
 
+    /**
+     * Whether to force background check on all apps (for battery saver) or not.
+     */
+    boolean mForceBackgroundCheck;
+
     private static String sTheRealBuildSerial = Build.UNKNOWN;
 
     /**
@@ -1918,23 +1941,6 @@
                 }
                 break;
             }
-            case SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG_MSG: {
-                synchronized (ActivityManagerService.this) {
-                    final ActivityRecord ar = (ActivityRecord) msg.obj;
-                    if (mUnsupportedDisplaySizeDialog != null) {
-                        mUnsupportedDisplaySizeDialog.dismiss();
-                        mUnsupportedDisplaySizeDialog = null;
-                    }
-                    if (ar != null && mCompatModePackages.getPackageNotifyUnsupportedZoomLocked(
-                            ar.packageName)) {
-                        // TODO(multi-display): Show dialog on appropriate display.
-                        mUnsupportedDisplaySizeDialog = new UnsupportedDisplaySizeDialog(
-                                ActivityManagerService.this, mUiContext, ar.info.applicationInfo);
-                        mUnsupportedDisplaySizeDialog.show();
-                    }
-                }
-                break;
-            }
             case DISMISS_DIALOG_UI_MSG: {
                 final Dialog d = (Dialog) msg.obj;
                 d.dismiss();
@@ -2503,7 +2509,7 @@
                     DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_NORMAL | DUMP_FLAG_PROTO);
             ServiceManager.addService(ProcessStats.SERVICE_NAME, mProcessStats);
             ServiceManager.addService("meminfo", new MemBinder(this), /* allowIsolated= */ false,
-                    DUMP_FLAG_PRIORITY_HIGH | DUMP_FLAG_PRIORITY_NORMAL);
+                    DUMP_FLAG_PRIORITY_HIGH);
             ServiceManager.addService("gfxinfo", new GraphicsBinder(this));
             ServiceManager.addService("dbinfo", new DbBinder(this));
             if (MONITOR_CPU_USAGE) {
@@ -2561,9 +2567,15 @@
         private final PriorityDump.PriorityDumper mPriorityDumper =
                 new PriorityDump.PriorityDumper() {
             @Override
-            public void dumpNormal(FileDescriptor fd, PrintWriter pw, String[] args,
+            public void dumpHigh(FileDescriptor fd, PrintWriter pw, String[] args,
                     boolean asProto) {
                 if (asProto) return;
+                mActivityManagerService.dumpApplicationMemoryUsage(fd,
+                        pw, "  ", new String[] {"-a"}, false, null);
+            }
+            @Override
+            public void dump(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) {
+                if (asProto) return;
                 mActivityManagerService.dumpApplicationMemoryUsage(fd, pw, "  ", args, false, null);
             }
         };
@@ -2667,6 +2679,7 @@
         GL_ES_VERSION = 0;
         mActivityStarter = null;
         mAppErrors = null;
+        mAppWarnings = null;
         mAppOpsService = mInjector.getAppOpsService(null, null);
         mBatteryStatsService = null;
         mCompatModePackages = null;
@@ -2689,6 +2702,7 @@
         mUserController = null;
         mVrController = null;
         mLockTaskController = null;
+        mLifecycleManager = null;
     }
 
     // Note: This method is invoked on the main thread but may need to attach various
@@ -2734,10 +2748,13 @@
         mProviderMap = new ProviderMap(this);
         mAppErrors = new AppErrors(mUiContext, this);
 
-        // TODO: Move creation of battery stats service outside of activity manager service.
         File dataDir = Environment.getDataDirectory();
         File systemDir = new File(dataDir, "system");
         systemDir.mkdirs();
+
+        mAppWarnings = new AppWarnings(this, mUiContext, mHandler, mUiHandler, systemDir);
+
+        // TODO: Move creation of battery stats service outside of activity manager service.
         mBatteryStatsService = new BatteryStatsService(systemContext, systemDir, mHandler);
         mBatteryStatsService.getActiveStatistics().readLocked();
         mBatteryStatsService.scheduleWriteToDisk();
@@ -2784,10 +2801,11 @@
         mIntentFirewall = new IntentFirewall(new IntentFirewallInterface(), mHandler);
         mTaskChangeNotificationController =
                 new TaskChangeNotificationController(this, mStackSupervisor, mHandler);
-        mActivityStarter = new ActivityStarter(this, AppGlobals.getPackageManager());
+        mActivityStarter = new ActivityStarter(this);
         mRecentTasks = createRecentTasks();
         mStackSupervisor.setRecentTasks(mRecentTasks);
         mLockTaskController = new LockTaskController(mContext, mStackSupervisor, mHandler);
+        mLifecycleManager = new ClientLifecycleManager();
 
         mProcessCpuThread = new Thread("CpuTracker") {
             @Override
@@ -2871,6 +2889,7 @@
 
     void onUserStoppedLocked(int userId) {
         mRecentTasks.unloadUserDataFromMemoryLocked(userId);
+        mAllowAppSwitchUids.remove(userId);
     }
 
     public void initPowerManagement() {
@@ -3301,22 +3320,25 @@
         mUiHandler.sendMessage(msg);
     }
 
-    final void showUnsupportedZoomDialogIfNeededLocked(ActivityRecord r) {
-        final Configuration globalConfig = getGlobalConfiguration();
-        if (globalConfig.densityDpi != DisplayMetrics.DENSITY_DEVICE_STABLE
-                && r.appInfo.requiresSmallestWidthDp > globalConfig.smallestScreenWidthDp) {
-            final Message msg = Message.obtain();
-            msg.what = SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG_MSG;
-            msg.obj = r;
-            mUiHandler.sendMessage(msg);
-        }
+    final AppWarnings getAppWarningsLocked() {
+        return mAppWarnings;
+    }
+
+    /**
+     * Shows app warning dialogs, if necessary.
+     *
+     * @param r activity record for which the warnings may be displayed
+     */
+    final void showAppWarningsIfNeededLocked(ActivityRecord r) {
+        mAppWarnings.showUnsupportedCompileSdkDialogIfNeeded(r);
+        mAppWarnings.showUnsupportedDisplaySizeDialogIfNeeded(r);
     }
 
     private int updateLruProcessInternalLocked(ProcessRecord app, long now, int index,
             String what, Object obj, ProcessRecord srcApp) {
         app.lastActivityTime = now;
 
-        if (app.activities.size() > 0) {
+        if (app.activities.size() > 0 || app.recentTasks.size() > 0) {
             // Don't want to touch dependent processes that are hosting activities.
             return index;
         }
@@ -3380,7 +3402,7 @@
     final void updateLruProcessLocked(ProcessRecord app, boolean activityChange,
             ProcessRecord client) {
         final boolean hasActivity = app.activities.size() > 0 || app.hasClientActivities
-                || app.treatLikeActivity;
+                || app.treatLikeActivity || app.recentTasks.size() > 0;
         final boolean hasService = false; // not impl yet. app.services.size() > 0;
         if (!activityChange && hasActivity) {
             // The process has activities, so we are only allowing activity-based adjustments
@@ -3484,7 +3506,8 @@
         int nextIndex;
         if (hasActivity) {
             final int N = mLruProcesses.size();
-            if (app.activities.size() == 0 && mLruProcessActivityStart < (N - 1)) {
+            if ((app.activities.size() == 0 || app.recentTasks.size() > 0)
+                    && mLruProcessActivityStart < (N - 1)) {
                 // Process doesn't have activities, but has clients with
                 // activities...  move it up, but one below the top (the top
                 // should always have a real activity).
@@ -5116,7 +5139,8 @@
                     // because we don't support returning them across task boundaries. Also, to
                     // keep backwards compatibility we remove the task from recents when finishing
                     // task with root activity.
-                    res = mStackSupervisor.removeTaskByIdLocked(tr.taskId, false, finishWithRootActivity);
+                    res = mStackSupervisor.removeTaskByIdLocked(tr.taskId, false,
+                            finishWithRootActivity, "finish-activity");
                     if (!res) {
                         Slog.i(TAG, "Removing task failed to finish activity");
                     }
@@ -5321,6 +5345,8 @@
         // Remove this application's activities from active lists.
         boolean hasVisibleActivities = mStackSupervisor.handleAppDiedLocked(app);
 
+        app.clearRecentTasks();
+
         app.activities.clear();
 
         if (app.instr != null) {
@@ -8091,7 +8117,7 @@
                     return false;
                 }
                 // An activity is consider to be in multi-window mode if its task isn't fullscreen.
-                return !r.getTask().mFullscreen;
+                return r.inMultiWindowMode();
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
@@ -8600,6 +8626,16 @@
         }
         switch (appop) {
             case AppOpsManager.MODE_ALLOWED:
+                // If force-background-check is enabled, restrict all apps that aren't whitelisted.
+                if (mForceBackgroundCheck &&
+                        UserHandle.isApp(uid) &&
+                        !isOnDeviceIdleWhitelistLocked(uid)) {
+                    if (DEBUG_BACKGROUND_CHECK) {
+                        Slog.i(TAG, "Force background check: " +
+                                uid + "/" + packageName + " restricted");
+                    }
+                    return ActivityManager.APP_START_MODE_DELAYED;
+                }
                 return ActivityManager.APP_START_MODE_NORMAL;
             case AppOpsManager.MODE_IGNORED:
                 return ActivityManager.APP_START_MODE_DELAYED;
@@ -8699,6 +8735,9 @@
         return ActivityManager.APP_START_MODE_NORMAL;
     }
 
+    /**
+     * @return whether a UID is in the system, user or temp doze whitelist.
+     */
     boolean isOnDeviceIdleWhitelistLocked(int uid) {
         final int appId = UserHandle.getAppId(uid);
         return Arrays.binarySearch(mDeviceIdleWhitelist, appId) >= 0
@@ -9044,6 +9083,19 @@
             this.targetUid = targetUid;
             this.flags = flags;
         }
+
+        void writeToProto(ProtoOutputStream proto, long fieldId) {
+            long token = proto.start(fieldId);
+            proto.write(NeededUriGrantsProto.TARGET_PACKAGE, targetPkg);
+            proto.write(NeededUriGrantsProto.TARGET_UID, targetUid);
+            proto.write(NeededUriGrantsProto.FLAGS, flags);
+
+            final int N = this.size();
+            for (int i=0; i<N; i++) {
+                this.get(i).writeToProto(proto, NeededUriGrantsProto.GRANTS);
+            }
+            proto.end(token);
+        }
     }
 
     /**
@@ -10097,8 +10149,8 @@
                 } else {
                     // Task isn't in window manager yet since it isn't associated with a stack.
                     // Return the persist value from activity manager
-                    if (task.mBounds != null) {
-                        rect.set(task.mBounds);
+                    if (!task.matchParentBounds()) {
+                        rect.set(task.getBounds());
                     } else if (task.mLastNonFullscreenBounds != null) {
                         rect.set(task.mLastNonFullscreenBounds);
                     }
@@ -10279,7 +10331,8 @@
         synchronized (this) {
             final long ident = Binder.clearCallingIdentity();
             try {
-                return mStackSupervisor.removeTaskByIdLocked(taskId, true, REMOVE_FROM_RECENTS);
+                return mStackSupervisor.removeTaskByIdLocked(taskId, true, REMOVE_FROM_RECENTS,
+                        "remove-task");
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -10469,12 +10522,12 @@
                             + " non-standard task " + taskId + " to windowing mode="
                             + windowingMode);
                 }
-                final ActivityDisplay display = task.getStack().getDisplay();
-                final ActivityStack stack = display.getOrCreateStack(windowingMode,
-                        task.getStack().getActivityType(), toTop);
-                // TODO: Use ActivityStack.setWindowingMode instead of re-parenting.
-                task.reparent(stack, toTop, REPARENT_KEEP_STACK_AT_FRONT, ANIMATE, !DEFER_RESUME,
-                        "moveTaskToStack");
+
+                final ActivityStack stack = task.getStack();
+                if (toTop) {
+                    stack.moveToFront("setTaskWindowingMode", task);
+                }
+                stack.setWindowingMode(windowingMode);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -11693,6 +11746,15 @@
         return true;
     }
 
+    /**
+     * Returns the PackageManager. Used by classes hosted by {@link ActivityManagerService}. The
+     * PackageManager could be unavailable at construction time and therefore needs to be accessed
+     * on demand.
+     */
+    IPackageManager getPackageManager() {
+        return AppGlobals.getPackageManager();
+    }
+
     PackageManagerInternal getPackageManagerInternalLocked() {
         if (mPackageManagerInt == null) {
             mPackageManagerInt = LocalServices.getService(PackageManagerInternal.class);
@@ -12582,6 +12644,18 @@
         }
     }
 
+    boolean checkAllowAppSwitchUid(int uid) {
+        ArrayMap<String, Integer> types = mAllowAppSwitchUids.get(UserHandle.getUserId(uid));
+        if (types != null) {
+            for (int i = types.size() - 1; i >= 0; i--) {
+                if (types.valueAt(i).intValue() == uid) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
     boolean checkAppSwitchAllowedLocked(int sourcePid, int sourceUid,
             int callingPid, int callingUid, String name) {
         if (mAppSwitchesAllowedTime < SystemClock.uptimeMillis()) {
@@ -12594,6 +12668,9 @@
         if (perm == PackageManager.PERMISSION_GRANTED) {
             return true;
         }
+        if (checkAllowAppSwitchUid(sourceUid)) {
+            return true;
+        }
 
         // If the actual IPC caller is different from the logical source, then
         // also see if they are allowed to control app switches.
@@ -12604,6 +12681,9 @@
             if (perm == PackageManager.PERMISSION_GRANTED) {
                 return true;
             }
+            if (checkAllowAppSwitchUid(callingUid)) {
+                return true;
+            }
         }
 
         Slog.w(TAG, name + " request from " + sourceUid + " stopped");
@@ -14115,6 +14195,16 @@
             readGrantedUriPermissionsLocked();
         }
 
+        final PowerManagerInternal pmi = LocalServices.getService(PowerManagerInternal.class);
+        if (pmi != null) {
+            pmi.registerLowPowerModeObserver(ServiceType.FORCE_BACKGROUND_CHECK,
+                    state -> updateForceBackgroundCheck(state.batterySaverEnabled));
+            updateForceBackgroundCheck(
+                    pmi.getLowPowerState(ServiceType.FORCE_BACKGROUND_CHECK).batterySaverEnabled);
+        } else {
+            Slog.wtf(TAG, "PowerManagerInternal not found.");
+        }
+
         if (goingCallback != null) goingCallback.run();
         traceLog.traceBegin("ActivityManagerStartApps");
         mBatteryStatsService.noteEvent(BatteryStats.HistoryItem.EVENT_USER_RUNNING_START,
@@ -14213,6 +14303,23 @@
         }
     }
 
+    private void updateForceBackgroundCheck(boolean enabled) {
+        synchronized (this) {
+            if (mForceBackgroundCheck != enabled) {
+                mForceBackgroundCheck = enabled;
+
+                if (DEBUG_BACKGROUND_CHECK) {
+                    Slog.i(TAG, "Force background check " + (enabled ? "enabled" : "disabled"));
+                }
+
+                if (mForceBackgroundCheck) {
+                    // Stop background services for idle UIDs.
+                    doStopUidForIdleUidsLocked();
+                }
+            }
+        }
+    }
+
     void killAppAtUsersRequest(ProcessRecord app, Dialog fromDialog) {
         synchronized (this) {
             mAppErrors.killAppAtUserRequestLocked(app, fromDialog);
@@ -14544,6 +14651,18 @@
         final String dropboxTag = processClass(process) + "_" + eventType;
         if (dbox == null || !dbox.isTagEnabled(dropboxTag)) return;
 
+        // Log to StatsLog before the rate-limiting.
+        // The logging below is adapated from appendDropboxProcessHeaders.
+        StatsLog.write(StatsLog.DROPBOX_ERROR_CHANGED,
+                process != null ? process.uid : -1,
+                dropboxTag,
+                processName,
+                process != null ? process.pid : -1,
+                (process != null && process.info != null) ?
+                        (process.info.isInstantApp() ? 1 : 0) : -1,
+                activity != null ? activity.shortComponentName : null,
+                process != null ? (process.isInterestingToUserLocked() ? 1 : 0) : -1);
+
         // Rate-limit how often we're willing to do the heavy lifting below to
         // collect and record logs; currently 5 logs per 10 second period.
         final long now = SystemClock.elapsedRealtime();
@@ -14868,6 +14987,7 @@
         boolean dumpVisibleStacksOnly = false;
         boolean dumpFocusedStackOnly = false;
         String dumpPackage = null;
+        int dumpAppId = -1;
 
         int opti = 0;
         while (opti < args.length) {
@@ -14922,6 +15042,25 @@
                 synchronized (this) {
                     writeBroadcastsToProtoLocked(proto);
                 }
+            } else if ("provider".equals(cmd)) {
+                String[] newArgs;
+                String name;
+                if (opti >= args.length) {
+                    name = null;
+                    newArgs = EMPTY_STRING_ARRAY;
+                } else {
+                    name = args[opti];
+                    opti++;
+                    newArgs = new String[args.length - opti];
+                    if (args.length > 2) System.arraycopy(args, opti, newArgs, 0,
+                            args.length - opti);
+                }
+                if (!dumpProviderProto(fd, pw, name, newArgs)) {
+                    pw.println("No providers match: " + name);
+                    pw.println("Use -h for help.");
+                }
+            } else if ("service".equals(cmd)) {
+                mServices.writeToProto(proto);
             } else {
                 // default option, dump everything, output is ActivityManagerServiceProto
                 synchronized (this) {
@@ -14932,6 +15071,10 @@
                     long broadcastToken = proto.start(ActivityManagerServiceProto.BROADCASTS);
                     writeBroadcastsToProtoLocked(proto);
                     proto.end(broadcastToken);
+
+                    long serviceToken = proto.start(ActivityManagerServiceProto.SERVICES);
+                    mServices.writeToProto(proto);
+                    proto.end(serviceToken);
                 }
             }
             proto.flush();
@@ -14939,6 +15082,16 @@
             return;
         }
 
+        if (dumpPackage != null) {
+            try {
+                ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(
+                        dumpPackage, 0);
+                dumpAppId = UserHandle.getAppId(info.uid);
+            } catch (NameNotFoundException e) {
+                e.printStackTrace();
+            }
+        }
+
         boolean more = false;
         // Is the caller requesting to dump a particular piece of data?
         if (opti < args.length) {
@@ -15046,7 +15199,7 @@
                             args.length - opti);
                 }
                 synchronized (this) {
-                    dumpProcessesLocked(fd, pw, args, opti, true, dumpPackage);
+                    dumpProcessesLocked(fd, pw, args, opti, true, dumpPackage, dumpAppId);
                 }
             } else if ("oom".equals(cmd) || "o".equals(cmd)) {
                 synchronized (this) {
@@ -15232,7 +15385,7 @@
                 if (dumpAll) {
                     pw.println("-------------------------------------------------------------------------------");
                 }
-                dumpProcessesLocked(fd, pw, args, opti, dumpAll, dumpPackage);
+                dumpProcessesLocked(fd, pw, args, opti, dumpAll, dumpPackage, dumpAppId);
             }
 
         } else {
@@ -15309,7 +15462,7 @@
                 if (dumpAll) {
                     pw.println("-------------------------------------------------------------------------------");
                 }
-                dumpProcessesLocked(fd, pw, args, opti, dumpAll, dumpPackage);
+                dumpProcessesLocked(fd, pw, args, opti, dumpAll, dumpPackage, dumpAppId);
             }
         }
         Binder.restoreCallingIdentity(origId);
@@ -15464,22 +15617,12 @@
         }
     }
 
-    boolean dumpUids(PrintWriter pw, String dumpPackage, SparseArray<UidRecord> uids,
+    boolean dumpUids(PrintWriter pw, String dumpPackage, int dumpAppId, SparseArray<UidRecord> uids,
             String header, boolean needSep) {
         boolean printed = false;
-        int whichAppId = -1;
-        if (dumpPackage != null) {
-            try {
-                ApplicationInfo info = mContext.getPackageManager().getApplicationInfo(
-                        dumpPackage, 0);
-                whichAppId = UserHandle.getAppId(info.uid);
-            } catch (NameNotFoundException e) {
-                e.printStackTrace();
-            }
-        }
         for (int i=0; i<uids.size(); i++) {
             UidRecord uidRec = uids.valueAt(i);
-            if (dumpPackage != null && UserHandle.getAppId(uidRec.uid) != whichAppId) {
+            if (dumpPackage != null && UserHandle.getAppId(uidRec.uid) != dumpAppId) {
                 continue;
             }
             if (!printed) {
@@ -15526,9 +15669,8 @@
     }
 
     void dumpProcessesLocked(FileDescriptor fd, PrintWriter pw, String[] args,
-            int opti, boolean dumpAll, String dumpPackage) {
+            int opti, boolean dumpAll, String dumpPackage, int dumpAppId) {
         boolean needSep = false;
-        boolean printedAnything = false;
         int numPers = 0;
 
         pw.println("ACTIVITY MANAGER RUNNING PROCESSES (dumpsys activity processes)");
@@ -15546,7 +15688,6 @@
                     if (!needSep) {
                         pw.println("  All known processes:");
                         needSep = true;
-                        printedAnything = true;
                     }
                     pw.print(r.persistent ? "  *PERS*" : "  *APP*");
                         pw.print(" UID "); pw.print(procs.keyAt(ia));
@@ -15571,7 +15712,6 @@
                         pw.println();
                     }
                     pw.println("  Isolated process list (sorted by uid):");
-                    printedAnything = true;
                     printed = true;
                     needSep = true;
                 }
@@ -15593,7 +15733,6 @@
                         pw.println();
                     }
                     pw.println("  Active instrumentation:");
-                    printedAnything = true;
                     printed = true;
                     needSep = true;
                 }
@@ -15604,14 +15743,15 @@
         }
 
         if (mActiveUids.size() > 0) {
-            if (dumpUids(pw, dumpPackage, mActiveUids, "UID states:", needSep)) {
-                printedAnything = needSep = true;
+            if (dumpUids(pw, dumpPackage, dumpAppId, mActiveUids, "UID states:", needSep)) {
+                needSep = true;
             }
         }
         if (dumpAll) {
             if (mValidateUids.size() > 0) {
-                if (dumpUids(pw, dumpPackage, mValidateUids, "UID validation:", needSep)) {
-                    printedAnything = needSep = true;
+                if (dumpUids(pw, dumpPackage, dumpAppId, mValidateUids, "UID validation:",
+                        needSep)) {
+                    needSep = true;
                 }
             }
         }
@@ -15628,7 +15768,6 @@
                     pw.println("):");
             dumpProcessOomList(pw, this, mLruProcesses, "    ", "Proc", "PERS", false, dumpPackage);
             needSep = true;
-            printedAnything = true;
         }
 
         if (dumpAll || dumpPackage != null) {
@@ -15644,7 +15783,6 @@
                         needSep = true;
                         pw.println("  PID mappings:");
                         printed = true;
-                        printedAnything = true;
                     }
                     pw.print("    PID #"); pw.print(mPidsSelfLocked.keyAt(i));
                         pw.print(": "); pw.println(mPidsSelfLocked.valueAt(i));
@@ -15667,7 +15805,6 @@
                         needSep = true;
                         pw.println("  Foreground Processes:");
                         printed = true;
-                        printedAnything = true;
                     }
                     pw.print("    PID #"); pw.print(mImportantProcesses.keyAt(i));
                             pw.print(": "); pw.println(mImportantProcesses.valueAt(i));
@@ -15678,7 +15815,6 @@
         if (mPersistentStartingProcesses.size() > 0) {
             if (needSep) pw.println();
             needSep = true;
-            printedAnything = true;
             pw.println("  Persisent processes that are starting:");
             dumpProcessList(pw, this, mPersistentStartingProcesses, "    ",
                     "Starting Norm", "Restarting PERS", dumpPackage);
@@ -15687,7 +15823,6 @@
         if (mRemovedProcesses.size() > 0) {
             if (needSep) pw.println();
             needSep = true;
-            printedAnything = true;
             pw.println("  Processes that are being removed:");
             dumpProcessList(pw, this, mRemovedProcesses, "    ",
                     "Removed Norm", "Removed PERS", dumpPackage);
@@ -15696,7 +15831,6 @@
         if (mProcessesOnHold.size() > 0) {
             if (needSep) pw.println();
             needSep = true;
-            printedAnything = true;
             pw.println("  Processes that are on old until the system is ready:");
             dumpProcessList(pw, this, mProcessesOnHold, "    ",
                     "OnHold Norm", "OnHold PERS", dumpPackage);
@@ -15705,9 +15839,6 @@
         needSep = dumpProcessesToGc(fd, pw, args, opti, needSep, dumpAll, dumpPackage);
 
         needSep = mAppErrors.dumpLocked(fd, pw, needSep, dumpPackage);
-        if (needSep) {
-            printedAnything = true;
-        }
 
         if (dumpPackage == null) {
             pw.println();
@@ -15914,6 +16045,32 @@
                 pw.println("  mNativeDebuggingApp=" + mNativeDebuggingApp);
             }
         }
+        if (mAllowAppSwitchUids.size() > 0) {
+            boolean printed = false;
+            for (int i = 0; i < mAllowAppSwitchUids.size(); i++) {
+                ArrayMap<String, Integer> types = mAllowAppSwitchUids.valueAt(i);
+                for (int j = 0; j < types.size(); j++) {
+                    if (dumpPackage == null ||
+                            UserHandle.getAppId(types.valueAt(j).intValue()) == dumpAppId) {
+                        if (needSep) {
+                            pw.println();
+                            needSep = false;
+                        }
+                        if (!printed) {
+                            pw.println("  mAllowAppSwitchUids:");
+                            printed = true;
+                        }
+                        pw.print("    User ");
+                        pw.print(mAllowAppSwitchUids.keyAt(i));
+                        pw.print(": Type ");
+                        pw.print(types.keyAt(j));
+                        pw.print(" = ");
+                        UserHandle.formatUid(pw, types.valueAt(j).intValue());
+                        pw.println();
+                    }
+                }
+            }
+        }
         if (dumpPackage == null) {
             if (mAlwaysFinishActivities) {
                 pw.println("  mAlwaysFinishActivities=" + mAlwaysFinishActivities);
@@ -15953,10 +16110,7 @@
                         pw.println();
             }
         }
-
-        if (!printedAnything) {
-            pw.println("  (nothing)");
-        }
+        pw.println("  mForceBackgroundCheck=" + mForceBackgroundCheck);
     }
 
     boolean dumpProcessesToGc(FileDescriptor fd, PrintWriter pw, String[] args,
@@ -16063,6 +16217,15 @@
         return mProviderMap.dumpProvider(fd, pw, name, args, opti, dumpAll);
     }
 
+    /**
+     * Similar to the dumpProvider, but only dumps the first matching provider.
+     * The provider is responsible for dumping as proto.
+     */
+    protected boolean dumpProviderProto(FileDescriptor fd, PrintWriter pw, String name,
+            String[] args) {
+        return mProviderMap.dumpProviderProto(fd, pw, name, args);
+    }
+
     static class ItemMatcher {
         ArrayList<ComponentName> components;
         ArrayList<String> strings;
@@ -17883,7 +18046,7 @@
             PrintWriter catPw = new FastPrintWriter(catSw, false, 256);
             String[] emptyArgs = new String[] { };
             catPw.println();
-            dumpProcessesLocked(null, catPw, emptyArgs, 0, false, null);
+            dumpProcessesLocked(null, catPw, emptyArgs, 0, false, null, -1);
             catPw.println();
             mServices.newServiceDumperLocked(null, catPw, emptyArgs, 0,
                     false, null).dumpLocked();
@@ -19296,13 +19459,7 @@
                                         mRecentTasks.removeTasksByPackageName(ssp, userId);
 
                                         mServices.forceStopPackageLocked(ssp, userId);
-
-                                        // Hide the "unsupported display" dialog if necessary.
-                                        if (mUnsupportedDisplaySizeDialog != null && ssp.equals(
-                                                mUnsupportedDisplaySizeDialog.getPackageName())) {
-                                            mUnsupportedDisplaySizeDialog.dismiss();
-                                            mUnsupportedDisplaySizeDialog = null;
-                                        }
+                                        mAppWarnings.onPackageUninstalled(ssp);
                                         mCompatModePackages.handlePackageUninstalledLocked(ssp);
                                         mBatteryStatsService.notePackageUninstalled(ssp);
                                     }
@@ -19381,13 +19538,8 @@
                     Uri data = intent.getData();
                     String ssp;
                     if (data != null && (ssp = data.getSchemeSpecificPart()) != null) {
-                        // Hide the "unsupported display" dialog if necessary.
-                        if (mUnsupportedDisplaySizeDialog != null && ssp.equals(
-                                mUnsupportedDisplaySizeDialog.getPackageName())) {
-                            mUnsupportedDisplaySizeDialog.dismiss();
-                            mUnsupportedDisplaySizeDialog = null;
-                        }
                         mCompatModePackages.handlePackageDataClearedLocked(ssp);
+                        mAppWarnings.onPackageDataCleared(ssp);
                     }
                     break;
                 }
@@ -20447,9 +20599,11 @@
                 if (app.thread != null) {
                     if (DEBUG_CONFIGURATION) Slog.v(TAG_CONFIGURATION, "Sending to proc "
                             + app.processName + " new config " + configCopy);
-                    app.thread.scheduleConfigurationChanged(configCopy);
+                    mLifecycleManager.scheduleTransaction(app.thread,
+                            new ConfigurationChangeItem(configCopy));
                 }
             } catch (Exception e) {
+                Slog.e(TAG_CONFIGURATION, "Failed to schedule configuration change", e);
             }
         }
 
@@ -20577,8 +20731,7 @@
 
             final boolean isDensityChange = (changes & ActivityInfo.CONFIG_DENSITY) != 0;
             if (isDensityChange && displayId == DEFAULT_DISPLAY) {
-                // Reset the unsupported display size dialog.
-                mUiHandler.sendEmptyMessage(SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG_MSG);
+                mAppWarnings.onDensityChanged();
 
                 killAllBackgroundProcessesExcept(N,
                         ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
@@ -20959,7 +21112,7 @@
                             + " instead of expected " + app);
                     if (r.app == null || (r.app.uid == app.uid)) {
                         // Only fix things up when they look sane
-                        r.app = app;
+                        r.setProcess(app);
                     } else {
                         continue;
                     }
@@ -21038,6 +21191,11 @@
                 adj += minLayer;
             }
         }
+        if (procState > ActivityManager.PROCESS_STATE_CACHED_RECENT && app.recentTasks.size() > 0) {
+            procState = ActivityManager.PROCESS_STATE_CACHED_RECENT;
+            app.adjType = "cch-rec";
+            if (DEBUG_OOM_ADJ_REASON) Slog.d(TAG, "Raise to cached recent: " + app);
+        }
 
         if (adj > ProcessList.PERCEPTIBLE_APP_ADJ
                 || procState > ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
@@ -22598,6 +22756,7 @@
                     switch (app.curProcState) {
                         case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY:
                         case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT:
+                        case ActivityManager.PROCESS_STATE_CACHED_RECENT:
                             // This process is a cached process holding activities...
                             // assign it the next cached value for that type, and then
                             // step that cached level.
@@ -23212,6 +23371,24 @@
         }
     }
 
+    /**
+     * Call {@link #doStopUidLocked} (which will also stop background services) for all idle UIDs.
+     */
+    void doStopUidForIdleUidsLocked() {
+        final int size = mActiveUids.size();
+        for (int i = 0; i < size; i++) {
+            final int uid = mActiveUids.keyAt(i);
+            if (!UserHandle.isApp(uid)) {
+                continue;
+            }
+            final UidRecord uidRec = mActiveUids.valueAt(i);
+            if (!uidRec.idle) {
+                continue;
+            }
+            doStopUidLocked(uidRec.uid, uidRec);
+        }
+    }
+
     final void doStopUidLocked(int uid, final UidRecord uidRec) {
         mServices.stopInBackgroundLocked(uid);
         enqueueUidChangeLocked(uidRec, uid, UidRecord.CHANGE_IDLE);
@@ -23322,7 +23499,7 @@
             // has been removed.
             for (i=mRemovedProcesses.size()-1; i>=0; i--) {
                 final ProcessRecord app = mRemovedProcesses.get(i);
-                if (app.activities.size() == 0
+                if (app.activities.size() == 0 && app.recentTasks.size() == 0
                         && app.curReceivers.isEmpty() && app.services.size() == 0) {
                     Slog.i(
                         TAG, "Exiting empty application process "
@@ -24189,6 +24366,32 @@
                 }
             }
         }
+
+        @Override
+        public void setAllowAppSwitches(@NonNull String type, int uid, int userId) {
+            synchronized (ActivityManagerService.this) {
+                if (mUserController.isUserRunning(userId, ActivityManager.FLAG_OR_STOPPED)) {
+                    ArrayMap<String, Integer> types = mAllowAppSwitchUids.get(userId);
+                    if (types == null) {
+                        if (uid < 0) {
+                            return;
+                        }
+                        types = new ArrayMap<>();
+                        mAllowAppSwitchUids.put(userId, types);
+                    }
+                    if (uid < 0) {
+                        types.remove(type);
+                    } else {
+                        types.put(type, uid);
+                    }
+                }
+            }
+        }
+
+        @Override
+        public boolean isRuntimeRestarted() {
+            return mSystemServiceManager.isRuntimeRestarted();
+        }
     }
 
     /**
diff --git a/com/android/server/am/ActivityRecord.java b/com/android/server/am/ActivityRecord.java
index 9a16745..a089e6c 100644
--- a/com/android/server/am/ActivityRecord.java
+++ b/com/android/server/am/ActivityRecord.java
@@ -79,7 +79,6 @@
 import static android.os.Build.VERSION_CODES.O;
 import static android.os.Process.SYSTEM_UID;
 import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
-import static android.view.WindowManagerPolicy.NAV_BAR_LEFT;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_CONFIGURATION;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_SAVED_STATE;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_STATES;
@@ -117,6 +116,7 @@
 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.policy.WindowManagerPolicy.NAV_BAR_LEFT;
 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;
@@ -131,6 +131,12 @@
 import android.app.PendingIntent;
 import android.app.PictureInPictureParams;
 import android.app.ResultInfo;
+import android.app.servertransaction.MoveToDisplayItem;
+import android.app.servertransaction.MultiWindowModeChangeItem;
+import android.app.servertransaction.NewIntentItem;
+import android.app.servertransaction.PipModeChangeItem;
+import android.app.servertransaction.WindowVisibilityItem;
+import android.app.servertransaction.ActivityConfigurationChangeItem;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
@@ -165,7 +171,6 @@
 import android.view.WindowManager.LayoutParams;
 
 import com.android.internal.R;
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.ResolverActivity;
 import com.android.internal.content.ReferrerIntent;
 import com.android.internal.util.XmlUtils;
@@ -343,12 +348,6 @@
     // on the window.
     int mRotationAnimationHint = -1;
 
-    // The bounds of this activity. Mainly used for aspect-ratio compatibility.
-    // TODO(b/36505427): Every level on ConfigurationContainer now has bounds information, which
-    // directly affects the configuration. We should probably move this into that class and have it
-    // handle calculating override configuration from the bounds.
-    private final Rect mBounds = new Rect();
-
     private boolean mShowWhenLocked;
     private boolean mTurnScreenOn;
 
@@ -414,8 +413,8 @@
         if (!getOverrideConfiguration().equals(EMPTY)) {
             pw.println(prefix + "OverrideConfiguration=" + getOverrideConfiguration());
         }
-        if (!mBounds.isEmpty()) {
-            pw.println(prefix + "mBounds=" + mBounds);
+        if (!matchParentBounds()) {
+            pw.println(prefix + "bounds=" + getBounds());
         }
         if (resultTo != null || resultWho != null) {
             pw.print(prefix); pw.print("resultTo="); pw.print(resultTo);
@@ -618,8 +617,8 @@
                     "Reporting activity moved to display" + ", activityRecord=" + this
                             + ", displayId=" + displayId + ", config=" + config);
 
-            app.thread.scheduleActivityMovedToDisplay(appToken, displayId,
-                    new Configuration(config));
+            service.mLifecycleManager.scheduleTransaction(app.thread, appToken,
+                    new MoveToDisplayItem(displayId, config));
         } catch (RemoteException e) {
             // If process died, whatever.
         }
@@ -636,7 +635,8 @@
             if (DEBUG_CONFIGURATION) Slog.v(TAG, "Sending new config to " + this + ", config: "
                     + config);
 
-            app.thread.scheduleActivityConfigurationChanged(appToken, new Configuration(config));
+            service.mLifecycleManager.scheduleTransaction(app.thread, appToken,
+                    new ActivityConfigurationChangeItem(config));
         } catch (RemoteException e) {
             // If process died, whatever.
         }
@@ -647,8 +647,13 @@
             return;
         }
 
+        if (task.getStack().deferScheduleMultiWindowModeChanged()) {
+            // Don't do anything if we are currently deferring multi-window mode change.
+            return;
+        }
+
         // An activity is considered to be in multi-window mode if its task isn't fullscreen.
-        final boolean inMultiWindowMode = !task.mFullscreen;
+        final boolean inMultiWindowMode = inMultiWindowMode();
         if (inMultiWindowMode != mLastReportedMultiWindowMode) {
             mLastReportedMultiWindowMode = inMultiWindowMode;
             scheduleMultiWindowModeChanged(getConfiguration());
@@ -657,8 +662,9 @@
 
     private void scheduleMultiWindowModeChanged(Configuration overrideConfig) {
         try {
-            app.thread.scheduleMultiWindowModeChanged(appToken, mLastReportedMultiWindowMode,
-                    overrideConfig);
+            service.mLifecycleManager.scheduleTransaction(app.thread, appToken,
+                    new MultiWindowModeChangeItem(mLastReportedMultiWindowMode,
+                            overrideConfig));
         } catch (Exception e) {
             // If process died, I don't care.
         }
@@ -674,7 +680,7 @@
             // Picture-in-picture mode changes also trigger a multi-window mode change as well, so
             // update that here in order
             mLastReportedPictureInPictureMode = inPictureInPictureMode;
-            mLastReportedMultiWindowMode = inPictureInPictureMode;
+            mLastReportedMultiWindowMode = inMultiWindowMode();
             final Configuration newConfig = task.computeNewOverrideConfigurationForBounds(
                     targetStackBounds, null);
             schedulePictureInPictureModeChanged(newConfig);
@@ -684,8 +690,9 @@
 
     private void schedulePictureInPictureModeChanged(Configuration overrideConfig) {
         try {
-            app.thread.schedulePictureInPictureModeChanged(appToken,
-                    mLastReportedPictureInPictureMode, overrideConfig);
+            service.mLifecycleManager.scheduleTransaction(app.thread, appToken,
+                    new PipModeChangeItem(mLastReportedPictureInPictureMode,
+                            overrideConfig));
         } catch (Exception e) {
             // If process died, no one cares.
         }
@@ -934,6 +941,14 @@
         }
     }
 
+    void setProcess(ProcessRecord proc) {
+        app = proc;
+        final ActivityRecord root = task != null ? task.getRootActivity() : null;
+        if (root == this) {
+            task.setRootProcess(proc);
+        }
+    }
+
     AppWindowContainerController getWindowContainerController() {
         return mWindowContainerController;
     }
@@ -958,14 +973,14 @@
                 (info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0, info.configChanges,
                 task.voiceSession != null, mLaunchTaskBehind, isAlwaysFocusable(),
                 appInfo.targetSdkVersion, mRotationAnimationHint,
-                ActivityManagerService.getInputDispatchingTimeoutLocked(this) * 1000000L, mBounds);
+                ActivityManagerService.getInputDispatchingTimeoutLocked(this) * 1000000L);
 
         task.addActivityToTop(this);
 
         // When an activity is started directly into a split-screen fullscreen stack, we need to
         // update the initial multi-window modes so that the callbacks are scheduled correctly when
         // the user leaves that mode.
-        mLastReportedMultiWindowMode = !task.mFullscreen;
+        mLastReportedMultiWindowMode = inMultiWindowMode();
         mLastReportedPictureInPictureMode = inPinnedWindowingMode();
     }
 
@@ -1364,8 +1379,8 @@
             try {
                 ArrayList<ReferrerIntent> ar = new ArrayList<>(1);
                 ar.add(rintent);
-                app.thread.scheduleNewIntent(
-                        ar, appToken, state == PAUSED /* andPause */);
+                service.mLifecycleManager.scheduleTransaction(app.thread, appToken,
+                        new NewIntentItem(ar, state == PAUSED));
                 unsent = false;
             } catch (RemoteException e) {
                 Slog.w(TAG, "Exception thrown sending new intent to " + this, e);
@@ -1587,7 +1602,8 @@
             setVisible(true);
             sleeping = false;
             app.pendingUiClean = true;
-            app.thread.scheduleWindowVisibility(appToken, true /* showWindow */);
+            service.mLifecycleManager.scheduleTransaction(app.thread, appToken,
+                    new WindowVisibilityItem(true /* showWindow */));
             // The activity may be waiting for stop, but that is no longer appropriate for it.
             mStackSupervisor.mStoppingActivities.remove(this);
             mStackSupervisor.mGoingToSleepActivities.remove(this);
@@ -2164,33 +2180,25 @@
         mLastReportedConfiguration.setConfiguration(global, override);
     }
 
-    @Override
-    public void onOverrideConfigurationChanged(Configuration newConfig) {
-        final Configuration currentConfig = getOverrideConfiguration();
-        if (currentConfig.equals(newConfig)) {
-            return;
-        }
-        super.onOverrideConfigurationChanged(newConfig);
-        if (mWindowContainerController == null) {
-            return;
-        }
-        mWindowContainerController.onOverrideConfigurationChanged(newConfig, mBounds);
-    }
-
     // TODO(b/36505427): Consider moving this method and similar ones to ConfigurationContainer.
     private void updateOverrideConfiguration() {
         mTmpConfig.unset();
         computeBounds(mTmpBounds);
-        if (mTmpBounds.equals(mBounds)) {
+
+        if (mTmpBounds.equals(getOverrideBounds())) {
             return;
         }
 
-        mBounds.set(mTmpBounds);
+        setBounds(mTmpBounds);
+
+        final Rect updatedBounds = getOverrideBounds();
+
         // Bounds changed...update configuration to match.
-        if (!mBounds.isEmpty()) {
-            task.computeOverrideConfiguration(mTmpConfig, mBounds, null /* insetBounds */,
+        if (!matchParentBounds()) {
+            task.computeOverrideConfiguration(mTmpConfig, updatedBounds, null /* insetBounds */,
                     false /* overrideWidth */, false /* overrideHeight */);
         }
+
         onOverrideConfigurationChanged(mTmpConfig);
     }
 
@@ -2217,7 +2225,7 @@
         outBounds.setEmpty();
         final float maxAspectRatio = info.maxAspectRatio;
         final ActivityStack stack = getStack();
-        if (task == null || stack == null || !task.mFullscreen || maxAspectRatio == 0
+        if (task == null || stack == null || task.inMultiWindowMode() || maxAspectRatio == 0
                 || isInVrUiMode(getConfiguration())) {
             // We don't set override configuration if that activity task isn't fullscreen. I.e. the
             // activity is in multi-window mode. Or, there isn't a max aspect ratio specified for
@@ -2248,11 +2256,11 @@
         if (containingAppWidth <= maxActivityWidth && containingAppHeight <= maxActivityHeight) {
             // The display matches or is less than the activity aspect ratio, so nothing else to do.
             // Return the existing bounds. If this method is running for the first time,
-            // {@link mBounds} will be empty (representing no override). If the method has run
-            // before, then effect of {@link mBounds} will already have been applied to the
+            // {@link #getOverrideBounds()} will be empty (representing no override). If the method has run
+            // before, then effect of {@link #getOverrideBounds()} will already have been applied to the
             // value returned from {@link getConfiguration}. Refer to
             // {@link TaskRecord#computeOverrideConfiguration}.
-            outBounds.set(mBounds);
+            outBounds.set(getOverrideBounds());
             return;
         }
 
@@ -2264,12 +2272,6 @@
         outBounds.offsetTo(left, 0 /* top */);
     }
 
-    /** Get bounds of the activity. */
-    @VisibleForTesting
-    Rect getBounds() {
-        return new Rect(mBounds);
-    }
-
     /**
      * Make sure the given activity matches the current configuration. Returns false if the activity
      * had to be destroyed.  Returns true if the configuration is the same, or the activity will
@@ -2549,7 +2551,7 @@
             }
             results = null;
             newIntents = null;
-            service.showUnsupportedZoomDialogIfNeededLocked(this);
+            service.getAppWarningsLocked().onResumeActivity(this);
             service.showAskCompatModeDialogLocked(this);
         } else {
             service.mHandler.removeMessages(PAUSE_TIMEOUT_MSG, this);
diff --git a/com/android/server/am/ActivityStack.java b/com/android/server/am/ActivityStack.java
index c086c52..bdfd82f 100644
--- a/com/android/server/am/ActivityStack.java
+++ b/com/android/server/am/ActivityStack.java
@@ -104,6 +104,13 @@
 import android.app.ResultInfo;
 import android.app.WindowConfiguration.ActivityType;
 import android.app.WindowConfiguration.WindowingMode;
+import android.app.servertransaction.ActivityResultItem;
+import android.app.servertransaction.NewIntentItem;
+import android.app.servertransaction.WindowVisibilityItem;
+import android.app.servertransaction.DestroyActivityItem;
+import android.app.servertransaction.PauseActivityItem;
+import android.app.servertransaction.ResumeActivityItem;
+import android.app.servertransaction.StopActivityItem;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
@@ -156,7 +163,6 @@
  */
 class ActivityStack<T extends StackWindowController> extends ConfigurationContainer
         implements StackWindowListener {
-
     private static final String TAG = TAG_WITH_CLASS_NAME ? "ActivityStack" : TAG_AM;
     private static final String TAG_ADD_REMOVE = TAG + POSTFIX_ADD_REMOVE;
     private static final String TAG_APP = TAG + POSTFIX_APP;
@@ -322,11 +328,6 @@
      */
     boolean mForceHidden = false;
 
-    // Whether or not this stack covers the entire screen; by default stacks are fullscreen
-    boolean mFullscreen = true;
-    // Current bounds of the stack or null if fullscreen.
-    Rect mBounds = null;
-
     private boolean mUpdateBoundsDeferred;
     private boolean mUpdateBoundsDeferredCalled;
     private final Rect mDeferredBounds = new Rect();
@@ -342,8 +343,6 @@
     /** The attached Display's unique identifier, or -1 if detached */
     int mDisplayId;
 
-    /** Temp variables used during override configuration update. */
-    private final SparseArray<Configuration> mTmpConfigs = new SparseArray<>();
     private final SparseArray<Rect> mTmpBounds = new SparseArray<>();
     private final SparseArray<Rect> mTmpInsetBounds = new SparseArray<>();
     private final Rect mTmpRect2 = new Rect();
@@ -495,16 +494,18 @@
 
         // Need to make sure windowing mode is supported.
         int windowingMode = display.resolveWindowingMode(
-                null /* ActivityRecord */, mTmpOptions, topTask, getActivityType());;
+                null /* ActivityRecord */, mTmpOptions, topTask, getActivityType());
         if (splitScreenStack == this && windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
             // Resolution to split-screen secondary for the primary split-screen stack means we want
             // to go fullscreen.
             windowingMode = WINDOWING_MODE_FULLSCREEN;
         }
 
+        final boolean alreadyInSplitScreenMode = display.hasSplitScreenPrimaryStack();
+
         // Take any required action due to us not supporting the preferred windowing mode.
         if (windowingMode != preferredWindowingMode && isActivityTypeStandardOrUndefined()) {
-            if (display.hasSplitScreenPrimaryStack()
+            if (alreadyInSplitScreenMode
                     && (preferredWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY
                     || preferredWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY)) {
                 // Looks like we can't launch in split screen mode, go ahead an dismiss split-screen
@@ -574,11 +575,11 @@
                 }
             }
 
-            if (!Objects.equals(mBounds, mTmpRect2)) {
+            if (!Objects.equals(getOverrideBounds(), mTmpRect2)) {
                 resize(mTmpRect2, null /* tempTaskBounds */, null /* tempTaskInsetBounds */);
             }
         } finally {
-            if (mDisplayId == DEFAULT_DISPLAY
+            if (!alreadyInSplitScreenMode && mDisplayId == DEFAULT_DISPLAY
                     && windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
                 // Make sure recents stack exist when creating a dock stack as it normally needs to
                 // be on the other side of the docked stack and we make visibility decisions based
@@ -641,9 +642,7 @@
      */
     private void postAddToDisplay(ActivityDisplay activityDisplay, Rect bounds, boolean onTop) {
         mDisplayId = activityDisplay.mDisplayId;
-        mBounds = bounds != null ? new Rect(bounds) : null;
-        mFullscreen = mBounds == null;
-
+        setBounds(bounds);
         onParentChanged();
 
         activityDisplay.addChild(this, onTop ? POSITION_TOP : POSITION_BOTTOM);
@@ -651,7 +650,7 @@
             // If we created a docked stack we want to resize it so it resizes all other stacks
             // in the system.
             mStackSupervisor.resizeDockedStackLocked(
-                    mBounds, null, null, null, null, PRESERVE_WINDOWS);
+                    getOverrideBounds(), null, null, null, null, PRESERVE_WINDOWS);
         }
     }
 
@@ -766,8 +765,9 @@
         return false;
     }
 
-    void setBounds(Rect bounds) {
-        mBounds = mFullscreen ? null : new Rect(bounds);
+    @Override
+    public int setBounds(Rect bounds) {
+        return super.setBounds(!inMultiWindowMode() ? null : bounds);
     }
 
     ActivityRecord topRunningActivityLocked() {
@@ -1428,8 +1428,10 @@
                         prev.userId, System.identityHashCode(prev),
                         prev.shortComponentName);
                 mService.updateUsageStats(prev, false);
-                prev.app.thread.schedulePauseActivity(prev.appToken, prev.finishing,
-                        userLeaving, prev.configChangeFlags, pauseImmediately);
+
+                mService.mLifecycleManager.scheduleTransaction(prev.app.thread, prev.appToken,
+                        new PauseActivityItem(prev.finishing, userLeaving,
+                                prev.configChangeFlags, pauseImmediately));
             } catch (Exception e) {
                 // Ignore exception, if process died other code will cleanup.
                 Slog.w(TAG, "Exception thrown during pause", e);
@@ -1678,12 +1680,6 @@
         return true;
     }
 
-    /** Returns true if the stack is currently considered visible. */
-    boolean isVisible() {
-        return mWindowContainerController != null && mWindowContainerController.isVisible()
-                && !mForceHidden;
-    }
-
     boolean isTopStackOnDisplay() {
         return getDisplay().isTopStack(this);
     }
@@ -2064,7 +2060,8 @@
                     if (r.app != null && r.app.thread != null) {
                         if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY,
                                 "Scheduling invisibility: " + r);
-                        r.app.thread.scheduleWindowVisibility(r.appToken, false);
+                        mService.mLifecycleManager.scheduleTransaction(r.app.thread, r.appToken,
+                                new WindowVisibilityItem(false /* showWindow */));
                     }
 
                     // Reset the flag indicating that an app can enter picture-in-picture once the
@@ -2495,7 +2492,7 @@
             // apps, maybeUpdateTransitToWallpaper() will fail to identify this as a
             // TRANSIT_WALLPAPER_OPEN animation, and run some funny animation.
             final boolean lastActivityTranslucent = lastStack != null
-                    && (!lastStack.mFullscreen
+                    && (lastStack.inMultiWindowMode()
                     || (lastStack.mLastPausedActivity != null
                     && !lastStack.mLastPausedActivity.fullscreen));
 
@@ -2584,13 +2581,15 @@
                         if (!next.finishing && N > 0) {
                             if (DEBUG_RESULTS) Slog.v(TAG_RESULTS,
                                     "Delivering results to " + next + ": " + a);
-                            next.app.thread.scheduleSendResult(next.appToken, a);
+                            mService.mLifecycleManager.scheduleTransaction(next.app.thread,
+                                    next.appToken, new ActivityResultItem(a));
                         }
                     }
 
                     if (next.newIntents != null) {
-                        next.app.thread.scheduleNewIntent(
-                                next.newIntents, next.appToken, false /* andPause */);
+                        mService.mLifecycleManager.scheduleTransaction(next.app.thread,
+                                next.appToken, new NewIntentItem(next.newIntents,
+                                        false /* andPause */));
                     }
 
                     // Well the app will no longer be stopped.
@@ -2602,13 +2601,14 @@
                             next.shortComponentName);
 
                     next.sleeping = false;
-                    mService.showUnsupportedZoomDialogIfNeededLocked(next);
+                    mService.getAppWarningsLocked().onResumeActivity(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);
+                    mService.mLifecycleManager.scheduleTransaction(next.app.thread, next.appToken,
+                            new ResumeActivityItem(next.app.repProcState,
+                                    mService.isNextTransitionForward()));
 
                     if (DEBUG_STATES) Slog.d(TAG_STATES, "resumeTopActivityLocked: Resumed "
                             + next);
@@ -2739,8 +2739,7 @@
         position = getAdjustedPositionForTask(task, position, null /* starting */);
         mTaskHistory.remove(task);
         mTaskHistory.add(position, task);
-        mWindowContainerController.positionChildAt(task.getWindowContainerController(), position,
-                task.mBounds, task.getOverrideConfiguration());
+        mWindowContainerController.positionChildAt(task.getWindowContainerController(), position);
         updateTaskMovement(task, true);
     }
 
@@ -3259,7 +3258,8 @@
                 ArrayList<ResultInfo> list = new ArrayList<ResultInfo>();
                 list.add(new ResultInfo(resultWho, requestCode,
                         resultCode, data));
-                r.app.thread.scheduleSendResult(r.appToken, list);
+                mService.mLifecycleManager.scheduleTransaction(r.app.thread, r.appToken,
+                        new ActivityResultItem(list));
                 return;
             } catch (Exception e) {
                 Slog.w(TAG, "Exception thrown sending result to " + r, e);
@@ -3387,7 +3387,8 @@
                 }
                 EventLogTags.writeAmStopActivity(
                         r.userId, System.identityHashCode(r), r.shortComponentName);
-                r.app.thread.scheduleStopActivity(r.appToken, r.visible, r.configChangeFlags);
+                mService.mLifecycleManager.scheduleTransaction(r.app.thread, r.appToken,
+                        new StopActivityItem(r.visible, r.configChangeFlags));
                 if (shouldSleepOrShutDownActivities()) {
                     r.setSleeping(true);
                 }
@@ -4019,7 +4020,7 @@
                 // TODO: If the callers to removeTask() changes such that we have multiple places
                 //       where we are destroying the task, move this back into removeTask()
                 mStackSupervisor.removeTaskByIdLocked(task.taskId, false /* killProcess */,
-                        !REMOVE_FROM_RECENTS, PAUSE_IMMEDIATELY);
+                        !REMOVE_FROM_RECENTS, PAUSE_IMMEDIATELY, reason);
             }
 
             // We must keep the task around until all activities are destroyed. The following
@@ -4184,8 +4185,8 @@
 
             try {
                 if (DEBUG_SWITCH) Slog.i(TAG_SWITCH, "Destroying: " + r);
-                r.app.thread.scheduleDestroyActivity(r.appToken, r.finishing,
-                        r.configChangeFlags);
+                mService.mLifecycleManager.scheduleTransaction(r.app.thread, r.appToken,
+                        new DestroyActivityItem(r.finishing, r.configChangeFlags));
             } catch (Exception e) {
                 // We can just ignore exceptions here...  if the process
                 // has crashed, our death notification will clean things
@@ -4602,8 +4603,6 @@
     // TODO: Can only be called from special methods in ActivityStackSupervisor.
     // Need to consolidate those calls points into this resize method so anyone can call directly.
     void resize(Rect bounds, Rect tempTaskBounds, Rect tempTaskInsetBounds) {
-        bounds = TaskRecord.validateBounds(bounds);
-
         if (!updateBoundsAllowed(bounds, tempTaskBounds, tempTaskInsetBounds)) {
             return;
         }
@@ -4613,7 +4612,6 @@
         final Rect insetBounds = tempTaskInsetBounds != null ? tempTaskInsetBounds : taskBounds;
 
         mTmpBounds.clear();
-        mTmpConfigs.clear();
         mTmpInsetBounds.clear();
 
         for (int i = mTaskHistory.size() - 1; i >= 0; i--) {
@@ -4624,7 +4622,7 @@
                     // For freeform stack we don't adjust the size of the tasks to match that
                     // of the stack, but we do try to make sure the tasks are still contained
                     // with the bounds of the stack.
-                    mTmpRect2.set(task.mBounds);
+                    mTmpRect2.set(task.getOverrideBounds());
                     fitWithinBounds(mTmpRect2, bounds);
                     task.updateOverrideConfiguration(mTmpRect2);
                 } else {
@@ -4632,15 +4630,13 @@
                 }
             }
 
-            mTmpConfigs.put(task.taskId, task.getOverrideConfiguration());
-            mTmpBounds.put(task.taskId, task.mBounds);
+            mTmpBounds.put(task.taskId, task.getOverrideBounds());
             if (tempTaskInsetBounds != null) {
                 mTmpInsetBounds.put(task.taskId, tempTaskInsetBounds);
             }
         }
 
-        mFullscreen = mWindowContainerController.resize(bounds, mTmpConfigs, mTmpBounds,
-                mTmpInsetBounds);
+        mWindowContainerController.resize(bounds, mTmpBounds, mTmpInsetBounds);
         setBounds(bounds);
     }
 
@@ -4655,7 +4651,7 @@
      * @param stackBounds Bounds within which the other bounds should remain.
      */
     private static void fitWithinBounds(Rect bounds, Rect stackBounds) {
-        if (stackBounds == null || stackBounds.contains(bounds)) {
+        if (stackBounds == null || stackBounds.isEmpty() || stackBounds.contains(bounds)) {
             return;
         }
 
@@ -4873,8 +4869,7 @@
                 pw.println("");
             }
             pw.println(prefix + "Task id #" + task.taskId);
-            pw.println(prefix + "mFullscreen=" + task.mFullscreen);
-            pw.println(prefix + "mBounds=" + task.mBounds);
+            pw.println(prefix + "mBounds=" + task.getOverrideBounds());
             pw.println(prefix + "mMinWidth=" + task.mMinWidth);
             pw.println(prefix + "mMinHeight=" + task.mMinHeight);
             pw.println(prefix + "mLastNonFullscreenBounds=" + task.mLastNonFullscreenBounds);
@@ -4981,7 +4976,7 @@
             if (isOnHomeDisplay() && mode != REMOVE_TASK_MODE_MOVING_TO_TOP
                     && mStackSupervisor.isFocusedStack(this)) {
                 String myReason = reason + " leftTaskHistoryEmpty";
-                if (mFullscreen || !adjustFocusToNextFocusableStack(myReason)) {
+                if (!inMultiWindowMode() || !adjustFocusToNextFocusableStack(myReason)) {
                     mStackSupervisor.moveHomeStackToFront(myReason);
                 }
             }
@@ -5004,15 +4999,24 @@
     TaskRecord createTaskRecord(int taskId, ActivityInfo info, Intent intent,
             IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
             boolean toTop) {
+        return createTaskRecord(taskId, info, intent, voiceSession, voiceInteractor, toTop,
+                null /*activity*/, null /*source*/, null /*options*/);
+    }
+
+    TaskRecord createTaskRecord(int taskId, ActivityInfo info, Intent intent,
+            IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor,
+            boolean toTop, ActivityRecord activity, ActivityRecord source,
+            ActivityOptions options) {
         final TaskRecord task = new TaskRecord(mService, taskId, info, intent, voiceSession,
                 voiceInteractor);
         // add the task to stack first, mTaskPositioner might need the stack association
         addTask(task, toTop, "createTaskRecord");
         final boolean isLockscreenShown = mService.mStackSupervisor.getKeyguardController()
                 .isKeyguardShowing(mDisplayId != INVALID_DISPLAY ? mDisplayId : DEFAULT_DISPLAY);
-        if (!mStackSupervisor.getLaunchingBoundsController().layoutTask(task, info.windowLayout)
-                && mBounds != null && task.isResizeable() && !isLockscreenShown) {
-            task.updateOverrideConfiguration(mBounds);
+        if (!mStackSupervisor.getLaunchingBoundsController()
+                .layoutTask(task, info.windowLayout, activity, source, options)
+                && !matchParentBounds() && task.isResizeable() && !isLockscreenShown) {
+            task.updateOverrideConfiguration(getOverrideBounds());
         }
         task.createWindowContainer(toTop, (info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0);
         return task;
@@ -5174,10 +5178,13 @@
             mResumedActivity.writeIdentifierToProto(proto, RESUMED_ACTIVITY);
         }
         proto.write(DISPLAY_ID, mDisplayId);
-        if (mBounds != null) {
-            mBounds.writeToProto(proto, BOUNDS);
+        if (!matchParentBounds()) {
+            final Rect bounds = getOverrideBounds();
+            bounds.writeToProto(proto, BOUNDS);
         }
-        proto.write(FULLSCREEN, mFullscreen);
+
+        // TODO: Remove, no longer needed with windowingMode.
+        proto.write(FULLSCREEN, matchParentBounds());
         proto.end(token);
     }
 }
diff --git a/com/android/server/am/ActivityStackSupervisor.java b/com/android/server/am/ActivityStackSupervisor.java
index 745e9fb..445bf67 100644
--- a/com/android/server/am/ActivityStackSupervisor.java
+++ b/com/android/server/am/ActivityStackSupervisor.java
@@ -114,6 +114,7 @@
 import android.app.WaitResult;
 import android.app.WindowConfiguration.ActivityType;
 import android.app.WindowConfiguration.WindowingMode;
+import android.app.servertransaction.LaunchActivityItem;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -1270,7 +1271,7 @@
             // schedule launch ticks to collect information about slow apps.
             r.startLaunchTickingLocked();
 
-            r.app = app;
+            r.setProcess(app);
 
             if (mKeyguardController.isKeyguardLocked()) {
                 r.notifyUnknownVisibilityLaunched();
@@ -1358,7 +1359,7 @@
                         PackageManager.NOTIFY_PACKAGE_USE_ACTIVITY);
                 r.sleeping = false;
                 r.forceNewConfig = false;
-                mService.showUnsupportedZoomDialogIfNeededLocked(r);
+                mService.getAppWarningsLocked().onStartActivity(r);
                 mService.showAskCompatModeDialogLocked(r);
                 r.compat = mService.compatibilityInfoForPackageLocked(r.info.applicationInfo);
                 ProfilerInfo profilerInfo = null;
@@ -1392,7 +1393,8 @@
                 r.setLastReportedConfiguration(mergedConfiguration);
 
                 logIfTransactionTooLarge(r.intent, r.icicle);
-                app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken,
+                mService.mLifecycleManager.scheduleTransaction(app.thread, r.appToken,
+                        new LaunchActivityItem(new Intent(r.intent),
                         System.identityHashCode(r), r.info,
                         // TODO: Have this take the merged configuration instead of separate global
                         // and override configs.
@@ -1400,7 +1402,7 @@
                         mergedConfiguration.getOverrideConfiguration(), r.compat,
                         r.launchedFromPackage, task.voiceInteractor, app.repProcState, r.icicle,
                         r.persistentState, results, newIntents, !andResume,
-                        mService.isNextTransitionForward(), profilerInfo);
+                        mService.isNextTransitionForward(), profilerInfo));
 
                 if ((app.info.privateFlags & ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE) != 0) {
                     // This may be a heavy-weight process!  Note that the package
@@ -2118,7 +2120,7 @@
         }
 
         if (task.isResizeable() && canUseActivityOptionsLaunchBounds(options)) {
-            final Rect bounds = TaskRecord.validateBounds(options.getLaunchBounds());
+            final Rect bounds = options.getLaunchBounds();
             task.updateOverrideConfiguration(bounds);
 
             ActivityStack stack = getLaunchStack(null, options, task, ON_TOP);
@@ -2626,7 +2628,8 @@
 
             // TODO: Checking for isAttached might not be needed as if the user passes in null
             // dockedBounds then they want the docked stack to be dismissed.
-            if (stack.mFullscreen || (dockedBounds == null && !stack.isAttached())) {
+            if (stack.getWindowingMode() == WINDOWING_MODE_FULLSCREEN
+                    || (dockedBounds == null && !stack.isAttached())) {
                 // 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.
@@ -2736,7 +2739,7 @@
         } else {
             for (int i = tasks.size() - 1; i >= 0; i--) {
                 removeTaskByIdLocked(tasks.get(i).taskId, true /* killProcess */,
-                        REMOVE_FROM_RECENTS);
+                        REMOVE_FROM_RECENTS, "remove-stack");
             }
         }
     }
@@ -2769,8 +2772,10 @@
     /**
      * See {@link #removeTaskByIdLocked(int, boolean, boolean, boolean)}
      */
-    boolean removeTaskByIdLocked(int taskId, boolean killProcess, boolean removeFromRecents) {
-        return removeTaskByIdLocked(taskId, killProcess, removeFromRecents, !PAUSE_IMMEDIATELY);
+    boolean removeTaskByIdLocked(int taskId, boolean killProcess, boolean removeFromRecents,
+            String reason) {
+        return removeTaskByIdLocked(taskId, killProcess, removeFromRecents, !PAUSE_IMMEDIATELY,
+                reason);
     }
 
     /**
@@ -2784,10 +2789,10 @@
      * @return Returns true if the given task was found and removed.
      */
     boolean removeTaskByIdLocked(int taskId, boolean killProcess, boolean removeFromRecents,
-            boolean pauseImmediately) {
+            boolean pauseImmediately, String reason) {
         final TaskRecord tr = anyTaskForIdLocked(taskId, MATCH_TASK_IN_STACKS_OR_RECENT_TASKS);
         if (tr != null) {
-            tr.removeTaskActivitiesLocked(pauseImmediately);
+            tr.removeTaskActivitiesLocked(pauseImmediately, reason);
             cleanUpRemovedTaskLocked(tr, killProcess, removeFromRecents);
             mService.mLockTaskController.clearLockedTask(tr);
             if (tr.isPersistable) {
@@ -2921,7 +2926,12 @@
 
     @Override
     public void onRecentTaskRemoved(TaskRecord task, boolean wasTrimmed) {
-        // TODO: Trim active task once b/68045330 is fixed
+        if (wasTrimmed) {
+            // Task was trimmed from the recent tasks list -- remove the active task record as well
+            // since the user won't really be able to go back to it
+            removeTaskByIdLocked(task.taskId, false /* killProcess */,
+                    false /* removeFromRecents */, !PAUSE_IMMEDIATELY, "recent-task-trimmed");
+        }
         task.removedFromRecents();
     }
 
@@ -3058,7 +3068,7 @@
             // Resize the pinned stack to match the current size of the task the activity we are
             // going to be moving is currently contained in. We do this to have the right starting
             // animation bounds for the pinned stack to the desired bounds the caller wants.
-            resizeStackLocked(stack, task.mBounds, null /* tempTaskBounds */,
+            resizeStackLocked(stack, task.getOverrideBounds(), null /* tempTaskBounds */,
                     null /* tempTaskInsetBounds */, !PRESERVE_WINDOWS,
                     true /* allowResizeInDockedMode */, !DEFER_RESUME);
 
@@ -3782,9 +3792,8 @@
                 pw.println("  Stack #" + stack.mStackId
                         + ": type=" + activityTypeToString(stack.getActivityType())
                         + " mode=" + windowingModeToString(stack.getWindowingMode()));
-                pw.println("  mFullscreen=" + stack.mFullscreen);
                 pw.println("  isSleeping=" + stack.shouldSleepActivities());
-                pw.println("  mBounds=" + stack.mBounds);
+                pw.println("  mBounds=" + stack.getOverrideBounds());
 
                 printed |= stack.dumpActivitiesLocked(fd, pw, dumpAll, dumpClient, dumpPackage,
                         needSep);
@@ -4054,6 +4063,7 @@
     private void handleDisplayChanged(int displayId) {
         synchronized (mService) {
             ActivityDisplay activityDisplay = mActivityDisplays.get(displayId);
+            // TODO: The following code block should be moved into {@link ActivityDisplay}.
             if (activityDisplay != null) {
                 // The window policy is responsible for stopping activities on the default display
                 if (displayId != Display.DEFAULT_DISPLAY) {
@@ -4067,7 +4077,8 @@
                         activityDisplay.mOffToken = null;
                     }
                 }
-                // TODO: Update the bounds.
+
+                activityDisplay.updateBounds();
             }
             mWindowManager.onDisplayChanged(displayId);
         }
@@ -4289,7 +4300,7 @@
             return;
         }
 
-        scheduleUpdatePictureInPictureModeIfNeeded(task, stack.mBounds);
+        scheduleUpdatePictureInPictureModeIfNeeded(task, stack.getOverrideBounds());
     }
 
     void scheduleUpdatePictureInPictureModeIfNeeded(TaskRecord task, Rect targetStackBounds) {
@@ -4297,6 +4308,10 @@
             final ActivityRecord r = task.mActivities.get(i);
             if (r.app != null && r.app.thread != null) {
                 mPipModeChangedActivities.add(r);
+                // If we are scheduling pip change, then remove this activity from multi-window
+                // change list as the processing of pip change will make sure multi-window changed
+                // message is processed in the right order relative to pip changed.
+                mMultiWindowModeChangedActivities.remove(r);
             }
         }
         mPipModeChangedTargetStackBounds = targetStackBounds;
diff --git a/com/android/server/am/ActivityStarter.java b/com/android/server/am/ActivityStarter.java
index 9b8cbc1..2fc5dda 100644
--- a/com/android/server/am/ActivityStarter.java
+++ b/com/android/server/am/ActivityStarter.java
@@ -134,7 +134,6 @@
     private static final int INVALID_LAUNCH_MODE = -1;
 
     private final ActivityManagerService mService;
-    private final IPackageManager mPackageManager;
     private final ActivityStackSupervisor mSupervisor;
     private final ActivityStartInterceptor mInterceptor;
 
@@ -234,9 +233,8 @@
         mIntentDelivered = false;
     }
 
-    ActivityStarter(ActivityManagerService service, IPackageManager packageManager) {
+    ActivityStarter(ActivityManagerService service) {
         mService = service;
-        mPackageManager = packageManager;
         mSupervisor = mService.mStackSupervisor;
         mInterceptor = new ActivityStartInterceptor(mService, mSupervisor);
     }
@@ -379,7 +377,7 @@
                     && sourceRecord.info.applicationInfo.uid != aInfo.applicationInfo.uid) {
                 try {
                     intent.addCategory(Intent.CATEGORY_VOICE);
-                    if (!mPackageManager.activitySupportsIntent(
+                    if (!mService.getPackageManager().activitySupportsIntent(
                             intent.getComponent(), intent, resolvedType)) {
                         Slog.w(TAG,
                                 "Activity being started in current voice task does not support voice: "
@@ -397,7 +395,7 @@
             // If the caller is starting a new voice session, just make sure the target
             // is actually allowing it to run this way.
             try {
-                if (!mPackageManager.activitySupportsIntent(intent.getComponent(),
+                if (!mService.getPackageManager().activitySupportsIntent(intent.getComponent(),
                         intent, resolvedType)) {
                     Slog.w(TAG,
                             "Activity being started in new voice task does not support: "
@@ -608,21 +606,9 @@
             return;
         }
 
-        if (startedActivityStack.inSplitScreenPrimaryWindowingMode()) {
-            final ActivityStack homeStack = mSupervisor.mHomeStack;
-            final boolean homeStackVisible = homeStack != null && homeStack.isVisible();
-            if (homeStackVisible) {
-                // We launch an activity while being in home stack, which means either launcher or
-                // recents into docked stack. We don't want the launched activity to be alone in a
-                // docked stack, so we want to immediately launch recents too.
-                if (DEBUG_RECENTS) Slog.d(TAG, "Scheduling recents launch.");
-                mService.mWindowManager.showRecentApps(true /* fromHome */);
-            }
-            return;
-        }
-
-        boolean clearedTask = (mLaunchFlags & (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK))
-                == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK) && (mReuseTask != null);
+        final int clearTaskFlags = FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK;
+        boolean clearedTask = (mLaunchFlags & clearTaskFlags) == clearTaskFlags
+                && mReuseTask != null;
         if (startedActivityStack.inPinnedWindowingMode()
                 && (result == START_TASK_TO_FRONT || result == START_DELIVERED_TO_TOP
                 || clearedTask)) {
@@ -1758,7 +1744,8 @@
                     mSupervisor.getNextTaskIdForUserLocked(mStartActivity.userId),
                     mNewTaskInfo != null ? mNewTaskInfo : mStartActivity.info,
                     mNewTaskIntent != null ? mNewTaskIntent : mIntent, mVoiceSession,
-                    mVoiceInteractor, !mLaunchTaskBehind /* toTop */);
+                    mVoiceInteractor, !mLaunchTaskBehind /* toTop */, mStartActivity, mSourceRecord,
+                    mOptions);
             addOrReparentStartingActivity(task, "setTaskFromReuseOrCreateNewTask - mReuseTask");
             updateBounds(mStartActivity.getTask(), mLaunchBounds);
 
@@ -1967,7 +1954,7 @@
         final ActivityRecord prev = mTargetStack.getTopActivity();
         final TaskRecord task = (prev != null) ? prev.getTask() : mTargetStack.createTaskRecord(
                 mSupervisor.getNextTaskIdForUserLocked(mStartActivity.userId), mStartActivity.info,
-                mIntent, null, null, true);
+                mIntent, null, null, true, mStartActivity, mSourceRecord, mOptions);
         addOrReparentStartingActivity(task, "setTaskToCurrentTopOrCreateNewTask");
         mTargetStack.positionChildWindowContainerAtTop(task);
         if (DEBUG_TASKS) Slog.v(TAG_TASKS, "Starting new activity " + mStartActivity
@@ -2124,18 +2111,13 @@
             return mReuseTask.getStack();
         }
 
-        final int vrDisplayId = mPreferredDisplayId == mService.mVr2dDisplayId
-                ? mPreferredDisplayId : INVALID_DISPLAY;
-        final ActivityStack launchStack = mSupervisor.getLaunchStack(r, aOptions, task, ON_TOP,
-                vrDisplayId);
-
-        if (launchStack != null) {
-            return launchStack;
-        }
-
         if (((launchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) == 0)
                  || mPreferredDisplayId != DEFAULT_DISPLAY) {
-            return null;
+            // We don't pass in the default display id into the get launch stack call so it can do a
+            // full resolution.
+            final int candidateDisplay =
+                    mPreferredDisplayId != DEFAULT_DISPLAY ? mPreferredDisplayId : INVALID_DISPLAY;
+            return mSupervisor.getLaunchStack(r, aOptions, task, ON_TOP, candidateDisplay);
         }
         // Otherwise handle adjacent launch.
 
@@ -2167,7 +2149,7 @@
                         mSupervisor.getDefaultDisplay().getSplitScreenPrimaryStack();
                 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;
+                    return mSupervisor.getLaunchStack(r, aOptions, task, ON_TOP);
                 } else {
                     return dockedStack;
                 }
diff --git a/com/android/server/am/AppBindRecord.java b/com/android/server/am/AppBindRecord.java
index df833ad..7b38597 100644
--- a/com/android/server/am/AppBindRecord.java
+++ b/com/android/server/am/AppBindRecord.java
@@ -17,6 +17,9 @@
 package com.android.server.am;
 
 import android.util.ArraySet;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.server.am.proto.AppBindRecordProto;
 
 import java.io.PrintWriter;
 
@@ -60,4 +63,18 @@
             + Integer.toHexString(System.identityHashCode(this))
             + " " + service.shortName + ":" + client.processName + "}";
     }
+
+    void writeToProto(ProtoOutputStream proto, long fieldId) {
+        long token = proto.start(fieldId);
+        proto.write(AppBindRecordProto.HEX_HASH,
+                Integer.toHexString(System.identityHashCode(this)));
+        if (client != null) {
+            client.writeToProto(proto, AppBindRecordProto.CLIENT);
+        }
+        final int N = connections.size();
+        for (int i=0; i<N; i++) {
+            connections.valueAt(i).writeToProto(proto, AppBindRecordProto.CONNECTIONS);
+        }
+        proto.end(token);
+    }
 }
diff --git a/com/android/server/am/AppTaskImpl.java b/com/android/server/am/AppTaskImpl.java
index 17626ea..ab86dbd 100644
--- a/com/android/server/am/AppTaskImpl.java
+++ b/com/android/server/am/AppTaskImpl.java
@@ -61,7 +61,7 @@
             try {
                 // We remove the task from recents to preserve backwards
                 if (!mService.mStackSupervisor.removeTaskByIdLocked(mTaskId, false,
-                        REMOVE_FROM_RECENTS)) {
+                        REMOVE_FROM_RECENTS, "finish-and-remove-task")) {
                     throw new IllegalArgumentException("Unable to find task ID " + mTaskId);
                 }
             } finally {
diff --git a/com/android/server/am/AppWarnings.java b/com/android/server/am/AppWarnings.java
new file mode 100644
index 0000000..a3c0345
--- /dev/null
+++ b/com/android/server/am/AppWarnings.java
@@ -0,0 +1,498 @@
+/*
+ * Copyright (C) 2018 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 android.annotation.UiThread;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.AtomicFile;
+import android.util.DisplayMetrics;
+import android.util.Slog;
+import android.util.Xml;
+
+import com.android.internal.util.FastXmlSerializer;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Manages warning dialogs shown during application lifecycle.
+ */
+class AppWarnings {
+    private static final String TAG = "AppWarnings";
+    private static final String CONFIG_FILE_NAME = "packages-warnings.xml";
+
+    public static final int FLAG_HIDE_DISPLAY_SIZE = 0x01;
+    public static final int FLAG_HIDE_COMPILE_SDK = 0x02;
+
+    private final HashMap<String, Integer> mPackageFlags = new HashMap<>();
+
+    private final ActivityManagerService mAms;
+    private final Context mUiContext;
+    private final ConfigHandler mAmsHandler;
+    private final UiHandler mUiHandler;
+    private final AtomicFile mConfigFile;
+
+    private UnsupportedDisplaySizeDialog mUnsupportedDisplaySizeDialog;
+    private UnsupportedCompileSdkDialog mUnsupportedCompileSdkDialog;
+
+    /**
+     * Creates a new warning dialog manager.
+     * <p>
+     * <strong>Note:</strong> Must be called from the ActivityManagerService thread.
+     *
+     * @param ams
+     * @param uiContext
+     * @param amsHandler
+     * @param uiHandler
+     * @param systemDir
+     */
+    public AppWarnings(ActivityManagerService ams, Context uiContext, Handler amsHandler,
+            Handler uiHandler, File systemDir) {
+        mAms = ams;
+        mUiContext = uiContext;
+        mAmsHandler = new ConfigHandler(amsHandler.getLooper());
+        mUiHandler = new UiHandler(uiHandler.getLooper());
+        mConfigFile = new AtomicFile(new File(systemDir, CONFIG_FILE_NAME));
+
+        readConfigFromFileAmsThread();
+    }
+
+    /**
+     * Shows the "unsupported display size" warning, if necessary.
+     *
+     * @param r activity record for which the warning may be displayed
+     */
+    public void showUnsupportedDisplaySizeDialogIfNeeded(ActivityRecord r) {
+        final Configuration globalConfig = mAms.getGlobalConfiguration();
+        if (globalConfig.densityDpi != DisplayMetrics.DENSITY_DEVICE_STABLE
+                && r.appInfo.requiresSmallestWidthDp > globalConfig.smallestScreenWidthDp) {
+            mUiHandler.showUnsupportedDisplaySizeDialog(r);
+        }
+    }
+
+    /**
+     * Shows the "unsupported compile SDK" warning, if necessary.
+     *
+     * @param r activity record for which the warning may be displayed
+     */
+    public void showUnsupportedCompileSdkDialogIfNeeded(ActivityRecord r) {
+        if (r.appInfo.compileSdkVersion == 0 || r.appInfo.compileSdkVersionCodename == null) {
+            // We don't know enough about this package. Abort!
+            return;
+        }
+
+        // If the application was built against an pre-release SDK that's older than the current
+        // platform OR if the current platform is pre-release and older than the SDK against which
+        // the application was built OR both are pre-release with the same SDK_INT but different
+        // codenames (e.g. simultaneous pre-release development), then we're likely to run into
+        // compatibility issues. Warn the user and offer to check for an update.
+        final int compileSdk = r.appInfo.compileSdkVersion;
+        final int platformSdk = Build.VERSION.SDK_INT;
+        final boolean isCompileSdkPreview = !"REL".equals(r.appInfo.compileSdkVersionCodename);
+        final boolean isPlatformSdkPreview = !"REL".equals(Build.VERSION.CODENAME);
+        if ((isCompileSdkPreview && compileSdk < platformSdk)
+                || (isPlatformSdkPreview && platformSdk < compileSdk)
+                || (isCompileSdkPreview && isPlatformSdkPreview && platformSdk == compileSdk
+                    && !Build.VERSION.CODENAME.equals(r.appInfo.compileSdkVersionCodename))) {
+            mUiHandler.showUnsupportedCompileSdkDialog(r);
+        }
+    }
+
+    /**
+     * Called when an activity is being started.
+     *
+     * @param r record for the activity being started
+     */
+    public void onStartActivity(ActivityRecord r) {
+        showUnsupportedCompileSdkDialogIfNeeded(r);
+        showUnsupportedDisplaySizeDialogIfNeeded(r);
+    }
+
+    /**
+     * Called when an activity was previously started and is being resumed.
+     *
+     * @param r record for the activity being resumed
+     */
+    public void onResumeActivity(ActivityRecord r) {
+        showUnsupportedDisplaySizeDialogIfNeeded(r);
+    }
+
+    /**
+     * Called by ActivityManagerService when package data has been cleared.
+     *
+     * @param name the package whose data has been cleared
+     */
+    public void onPackageDataCleared(String name) {
+        removePackageAndHideDialogs(name);
+    }
+
+    /**
+     * Called by ActivityManagerService when a package has been uninstalled.
+     *
+     * @param name the package that has been uninstalled
+     */
+    public void onPackageUninstalled(String name) {
+        removePackageAndHideDialogs(name);
+    }
+
+    /**
+     * Called by ActivityManagerService when the default display density has changed.
+     */
+    public void onDensityChanged() {
+        mUiHandler.hideUnsupportedDisplaySizeDialog();
+    }
+
+    /**
+     * Does what it says on the tin.
+     */
+    private void removePackageAndHideDialogs(String name) {
+        mUiHandler.hideDialogsForPackage(name);
+
+        synchronized (mPackageFlags) {
+            mPackageFlags.remove(name);
+            mAmsHandler.scheduleWrite();
+        }
+    }
+
+    /**
+     * Hides the "unsupported display size" warning.
+     * <p>
+     * <strong>Note:</strong> Must be called on the UI thread.
+     */
+    @UiThread
+    private void hideUnsupportedDisplaySizeDialogUiThread() {
+        if (mUnsupportedDisplaySizeDialog != null) {
+            mUnsupportedDisplaySizeDialog.dismiss();
+            mUnsupportedDisplaySizeDialog = null;
+        }
+    }
+
+    /**
+     * Shows the "unsupported display size" warning for the given application.
+     * <p>
+     * <strong>Note:</strong> Must be called on the UI thread.
+     *
+     * @param ar record for the activity that triggered the warning
+     */
+    @UiThread
+    private void showUnsupportedDisplaySizeDialogUiThread(ActivityRecord ar) {
+        if (mUnsupportedDisplaySizeDialog != null) {
+            mUnsupportedDisplaySizeDialog.dismiss();
+            mUnsupportedDisplaySizeDialog = null;
+        }
+        if (ar != null && !hasPackageFlag(
+                ar.packageName, FLAG_HIDE_DISPLAY_SIZE)) {
+            mUnsupportedDisplaySizeDialog = new UnsupportedDisplaySizeDialog(
+                    AppWarnings.this, mUiContext, ar.info.applicationInfo);
+            mUnsupportedDisplaySizeDialog.show();
+        }
+    }
+
+    /**
+     * Shows the "unsupported compile SDK" warning for the given application.
+     * <p>
+     * <strong>Note:</strong> Must be called on the UI thread.
+     *
+     * @param ar record for the activity that triggered the warning
+     */
+    @UiThread
+    private void showUnsupportedCompileSdkDialogUiThread(ActivityRecord ar) {
+        if (mUnsupportedCompileSdkDialog != null) {
+            mUnsupportedCompileSdkDialog.dismiss();
+            mUnsupportedCompileSdkDialog = null;
+        }
+        if (ar != null && !hasPackageFlag(
+                ar.packageName, FLAG_HIDE_COMPILE_SDK)) {
+            mUnsupportedCompileSdkDialog = new UnsupportedCompileSdkDialog(
+                    AppWarnings.this, mUiContext, ar.info.applicationInfo);
+            mUnsupportedCompileSdkDialog.show();
+        }
+    }
+
+    /**
+     * Dismisses all warnings for the given package.
+     * <p>
+     * <strong>Note:</strong> Must be called on the UI thread.
+     *
+     * @param name the package for which warnings should be dismissed, or {@code null} to dismiss
+     *             all warnings
+     */
+    @UiThread
+    private void hideDialogsForPackageUiThread(String name) {
+        // Hides the "unsupported display" dialog if necessary.
+        if (mUnsupportedDisplaySizeDialog != null && (name == null || name.equals(
+                mUnsupportedDisplaySizeDialog.getPackageName()))) {
+            mUnsupportedDisplaySizeDialog.dismiss();
+            mUnsupportedDisplaySizeDialog = null;
+        }
+
+        // Hides the "unsupported compile SDK" dialog if necessary.
+        if (mUnsupportedCompileSdkDialog != null && (name == null || name.equals(
+                mUnsupportedCompileSdkDialog.getPackageName()))) {
+            mUnsupportedCompileSdkDialog.dismiss();
+            mUnsupportedCompileSdkDialog = null;
+        }
+    }
+
+    /**
+     * Returns the value of the flag for the given package.
+     *
+     * @param name the package from which to retrieve the flag
+     * @param flag the bitmask for the flag to retrieve
+     * @return {@code true} if the flag is enabled, {@code false} otherwise
+     */
+    boolean hasPackageFlag(String name, int flag) {
+        return (getPackageFlags(name) & flag) == flag;
+    }
+
+    /**
+     * Sets the flag for the given package to the specified value.
+     *
+     * @param name the package on which to set the flag
+     * @param flag the bitmask for flag to set
+     * @param enabled the value to set for the flag
+     */
+    void setPackageFlag(String name, int flag, boolean enabled) {
+        synchronized (mPackageFlags) {
+            final int curFlags = getPackageFlags(name);
+            final int newFlags = enabled ? (curFlags & ~flag) : (curFlags | flag);
+            if (curFlags != newFlags) {
+                if (newFlags != 0) {
+                    mPackageFlags.put(name, newFlags);
+                } else {
+                    mPackageFlags.remove(name);
+                }
+                mAmsHandler.scheduleWrite();
+            }
+        }
+    }
+
+    /**
+     * Returns the bitmask of flags set for the specified package.
+     */
+    private int getPackageFlags(String name) {
+        synchronized (mPackageFlags) {
+            return mPackageFlags.getOrDefault(name, 0);
+        }
+    }
+
+    /**
+     * Handles messages on the system process UI thread.
+     */
+    private final class UiHandler extends Handler {
+        private static final int MSG_SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG = 1;
+        private static final int MSG_HIDE_UNSUPPORTED_DISPLAY_SIZE_DIALOG = 2;
+        private static final int MSG_SHOW_UNSUPPORTED_COMPILE_SDK_DIALOG = 3;
+        private static final int MSG_HIDE_DIALOGS_FOR_PACKAGE = 4;
+
+        public UiHandler(Looper looper) {
+            super(looper, null, true);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG: {
+                    final ActivityRecord ar = (ActivityRecord) msg.obj;
+                    showUnsupportedDisplaySizeDialogUiThread(ar);
+                } break;
+                case MSG_HIDE_UNSUPPORTED_DISPLAY_SIZE_DIALOG: {
+                    hideUnsupportedDisplaySizeDialogUiThread();
+                } break;
+                case MSG_SHOW_UNSUPPORTED_COMPILE_SDK_DIALOG: {
+                    final ActivityRecord ar = (ActivityRecord) msg.obj;
+                    showUnsupportedCompileSdkDialogUiThread(ar);
+                } break;
+                case MSG_HIDE_DIALOGS_FOR_PACKAGE: {
+                    final String name = (String) msg.obj;
+                    hideDialogsForPackageUiThread(name);
+                } break;
+            }
+        }
+
+        public void showUnsupportedDisplaySizeDialog(ActivityRecord r) {
+            removeMessages(MSG_SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG);
+            obtainMessage(MSG_SHOW_UNSUPPORTED_DISPLAY_SIZE_DIALOG, r).sendToTarget();
+        }
+
+        public void hideUnsupportedDisplaySizeDialog() {
+            removeMessages(MSG_HIDE_UNSUPPORTED_DISPLAY_SIZE_DIALOG);
+            sendEmptyMessage(MSG_HIDE_UNSUPPORTED_DISPLAY_SIZE_DIALOG);
+        }
+
+        public void showUnsupportedCompileSdkDialog(ActivityRecord r) {
+            removeMessages(MSG_SHOW_UNSUPPORTED_COMPILE_SDK_DIALOG);
+            obtainMessage(MSG_SHOW_UNSUPPORTED_COMPILE_SDK_DIALOG, r).sendToTarget();
+        }
+
+        public void hideDialogsForPackage(String name) {
+            obtainMessage(MSG_HIDE_DIALOGS_FOR_PACKAGE, name).sendToTarget();
+        }
+    }
+
+    /**
+     * Handles messages on the ActivityManagerService thread.
+     */
+    private final class ConfigHandler extends Handler {
+        private static final int MSG_WRITE = ActivityManagerService.FIRST_COMPAT_MODE_MSG;
+
+        private static final int DELAY_MSG_WRITE = 10000;
+
+        public ConfigHandler(Looper looper) {
+            super(looper, null, true);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_WRITE:
+                    writeConfigToFileAmsThread();
+                    break;
+            }
+        }
+
+        public void scheduleWrite() {
+            removeMessages(MSG_WRITE);
+            sendEmptyMessageDelayed(MSG_WRITE, DELAY_MSG_WRITE);
+        }
+    }
+
+    /**
+     * Writes the configuration file.
+     * <p>
+     * <strong>Note:</strong> Should be called from the ActivityManagerService thread unless you
+     * don't care where you're doing I/O operations. But you <i>do</i> care, don't you?
+     */
+    private void writeConfigToFileAmsThread() {
+        // Create a shallow copy so that we don't have to synchronize on config.
+        final HashMap<String, Integer> packageFlags;
+        synchronized (mPackageFlags) {
+            packageFlags = new HashMap<>(mPackageFlags);
+        }
+
+        FileOutputStream fos = null;
+        try {
+            fos = mConfigFile.startWrite();
+
+            final XmlSerializer out = new FastXmlSerializer();
+            out.setOutput(fos, StandardCharsets.UTF_8.name());
+            out.startDocument(null, true);
+            out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+            out.startTag(null, "packages");
+
+            for (Map.Entry<String, Integer> entry : packageFlags.entrySet()) {
+                String pkg = entry.getKey();
+                int mode = entry.getValue();
+                if (mode == 0) {
+                    continue;
+                }
+                out.startTag(null, "package");
+                out.attribute(null, "name", pkg);
+                out.attribute(null, "flags", Integer.toString(mode));
+                out.endTag(null, "package");
+            }
+
+            out.endTag(null, "packages");
+            out.endDocument();
+
+            mConfigFile.finishWrite(fos);
+        } catch (java.io.IOException e1) {
+            Slog.w(TAG, "Error writing package metadata", e1);
+            if (fos != null) {
+                mConfigFile.failWrite(fos);
+            }
+        }
+    }
+
+    /**
+     * Reads the configuration file and populates the package flags.
+     * <p>
+     * <strong>Note:</strong> Must be called from the constructor (and thus on the
+     * ActivityManagerService thread) since we don't synchronize on config.
+     */
+    private void readConfigFromFileAmsThread() {
+        FileInputStream fis = null;
+
+        try {
+            fis = mConfigFile.openRead();
+
+            final XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(fis, StandardCharsets.UTF_8.name());
+
+            int eventType = parser.getEventType();
+            while (eventType != XmlPullParser.START_TAG &&
+                    eventType != XmlPullParser.END_DOCUMENT) {
+                eventType = parser.next();
+            }
+            if (eventType == XmlPullParser.END_DOCUMENT) {
+                return;
+            }
+
+            String tagName = parser.getName();
+            if ("packages".equals(tagName)) {
+                eventType = parser.next();
+                do {
+                    if (eventType == XmlPullParser.START_TAG) {
+                        tagName = parser.getName();
+                        if (parser.getDepth() == 2) {
+                            if ("package".equals(tagName)) {
+                                final String name = parser.getAttributeValue(null, "name");
+                                if (name != null) {
+                                    final String flags = parser.getAttributeValue(
+                                            null, "flags");
+                                    int flagsInt = 0;
+                                    if (flags != null) {
+                                        try {
+                                            flagsInt = Integer.parseInt(flags);
+                                        } catch (NumberFormatException e) {
+                                        }
+                                    }
+                                    mPackageFlags.put(name, flagsInt);
+                                }
+                            }
+                        }
+                    }
+                    eventType = parser.next();
+                } while (eventType != XmlPullParser.END_DOCUMENT);
+            }
+        } catch (XmlPullParserException e) {
+            Slog.w(TAG, "Error reading package metadata", e);
+        } catch (java.io.IOException e) {
+            if (fis != null) Slog.w(TAG, "Error reading package metadata", e);
+        } finally {
+            if (fis != null) {
+                try {
+                    fis.close();
+                } catch (java.io.IOException e1) {
+                }
+            }
+        }
+    }
+}
diff --git a/com/android/server/am/ClientLifecycleManager.java b/com/android/server/am/ClientLifecycleManager.java
new file mode 100644
index 0000000..c04d103
--- /dev/null
+++ b/com/android/server/am/ClientLifecycleManager.java
@@ -0,0 +1,120 @@
+/*
+ * 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 android.annotation.NonNull;
+import android.app.IApplicationThread;
+import android.app.servertransaction.ClientTransaction;
+import android.app.servertransaction.ClientTransactionItem;
+import android.app.servertransaction.ActivityLifecycleItem;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+/**
+ * Class that is able to combine multiple client lifecycle transition requests and/or callbacks,
+ * and execute them as a single transaction.
+ *
+ * @see ClientTransaction
+ */
+class ClientLifecycleManager {
+    // TODO(lifecycler): Implement building transactions or global transaction.
+    // TODO(lifecycler): Use object pools for transactions and transaction items.
+
+    /**
+     * Schedule a transaction, which may consist of multiple callbacks and a lifecycle request.
+     * @param transaction A sequence of client transaction items.
+     * @throws RemoteException
+     *
+     * @see ClientTransaction
+     */
+    void scheduleTransaction(ClientTransaction transaction) throws RemoteException {
+        transaction.schedule();
+    }
+
+    /**
+     * Schedule a single lifecycle request or callback to client activity.
+     * @param client Target client.
+     * @param activityToken Target activity token.
+     * @param stateRequest A request to move target activity to a desired lifecycle state.
+     * @throws RemoteException
+     *
+     * @see ClientTransactionItem
+     */
+    void scheduleTransaction(@NonNull IApplicationThread client, @NonNull IBinder activityToken,
+            @NonNull ActivityLifecycleItem stateRequest) throws RemoteException {
+        final ClientTransaction clientTransaction = transactionWithState(client, activityToken,
+                stateRequest);
+        scheduleTransaction(clientTransaction);
+    }
+
+    /**
+     * Schedule a single callback delivery to client activity.
+     * @param client Target client.
+     * @param activityToken Target activity token.
+     * @param callback A request to deliver a callback.
+     * @throws RemoteException
+     *
+     * @see ClientTransactionItem
+     */
+    void scheduleTransaction(@NonNull IApplicationThread client, @NonNull IBinder activityToken,
+            @NonNull ClientTransactionItem callback) throws RemoteException {
+        final ClientTransaction clientTransaction = transactionWithCallback(client, activityToken,
+                callback);
+        scheduleTransaction(clientTransaction);
+    }
+
+    /**
+     * Schedule a single callback delivery to client application.
+     * @param client Target client.
+     * @param callback A request to deliver a callback.
+     * @throws RemoteException
+     *
+     * @see ClientTransactionItem
+     */
+    void scheduleTransaction(@NonNull IApplicationThread client,
+            @NonNull ClientTransactionItem callback) throws RemoteException {
+        final ClientTransaction clientTransaction = transactionWithCallback(client,
+                null /* activityToken */, callback);
+        scheduleTransaction(clientTransaction);
+    }
+
+    /**
+     * @return A new instance of {@link ClientTransaction} with a single lifecycle state request.
+     *
+     * @see ClientTransaction
+     * @see ClientTransactionItem
+     */
+    private static ClientTransaction transactionWithState(@NonNull IApplicationThread client,
+            @NonNull IBinder activityToken, @NonNull ActivityLifecycleItem stateRequest) {
+        final ClientTransaction clientTransaction = new ClientTransaction(client, activityToken);
+        clientTransaction.setLifecycleStateRequest(stateRequest);
+        return clientTransaction;
+    }
+
+    /**
+     * @return A new instance of {@link ClientTransaction} with a single callback invocation.
+     *
+     * @see ClientTransaction
+     * @see ClientTransactionItem
+     */
+    private static ClientTransaction transactionWithCallback(@NonNull IApplicationThread client,
+            IBinder activityToken, @NonNull ClientTransactionItem callback) {
+        final ClientTransaction clientTransaction = new ClientTransaction(client, activityToken);
+        clientTransaction.addCallback(callback);
+        return clientTransaction;
+    }
+}
diff --git a/com/android/server/am/CompatModePackages.java b/com/android/server/am/CompatModePackages.java
index bfc0456..65c4a42 100644
--- a/com/android/server/am/CompatModePackages.java
+++ b/com/android/server/am/CompatModePackages.java
@@ -58,8 +58,6 @@
     public static final int COMPAT_FLAG_DONT_ASK = 1<<0;
     // Compatibility state: compatibility mode is enabled.
     public static final int COMPAT_FLAG_ENABLED = 1<<1;
-    // Unsupported zoom state: don't warn the user about unsupported zoom mode.
-    public static final int UNSUPPORTED_ZOOM_FLAG_DONT_NOTIFY = 1<<2;
 
     private final HashMap<String, Integer> mPackages = new HashMap<String, Integer>();
 
@@ -233,10 +231,6 @@
         return (getPackageFlags(packageName)&COMPAT_FLAG_DONT_ASK) == 0;
     }
 
-    public boolean getPackageNotifyUnsupportedZoomLocked(String packageName) {
-        return (getPackageFlags(packageName)&UNSUPPORTED_ZOOM_FLAG_DONT_NOTIFY) == 0;
-    }
-
     public void setFrontActivityAskCompatModeLocked(boolean ask) {
         ActivityRecord r = mService.getFocusedStack().topRunningActivityLocked();
         if (r != null) {
@@ -245,22 +239,12 @@
     }
 
     public void setPackageAskCompatModeLocked(String packageName, boolean ask) {
-        int curFlags = getPackageFlags(packageName);
-        int newFlags = ask ? (curFlags&~COMPAT_FLAG_DONT_ASK) : (curFlags|COMPAT_FLAG_DONT_ASK);
-        if (curFlags != newFlags) {
-            if (newFlags != 0) {
-                mPackages.put(packageName, newFlags);
-            } else {
-                mPackages.remove(packageName);
-            }
-            scheduleWrite();
-        }
+        setPackageFlagLocked(packageName, COMPAT_FLAG_DONT_ASK, ask);
     }
 
-    public void setPackageNotifyUnsupportedZoomLocked(String packageName, boolean notify) {
+    private void setPackageFlagLocked(String packageName, int flag, boolean set) {
         final int curFlags = getPackageFlags(packageName);
-        final int newFlags = notify ? (curFlags&~UNSUPPORTED_ZOOM_FLAG_DONT_NOTIFY) :
-                (curFlags|UNSUPPORTED_ZOOM_FLAG_DONT_NOTIFY);
+        final int newFlags = set ? (curFlags & ~flag) : (curFlags | flag);
         if (curFlags != newFlags) {
             if (newFlags != 0) {
                 mPackages.put(packageName, newFlags);
diff --git a/com/android/server/am/ConnectionRecord.java b/com/android/server/am/ConnectionRecord.java
index 9b7a0c4..6df283c 100644
--- a/com/android/server/am/ConnectionRecord.java
+++ b/com/android/server/am/ConnectionRecord.java
@@ -19,6 +19,9 @@
 import android.app.IServiceConnection;
 import android.app.PendingIntent;
 import android.content.Context;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.server.am.proto.ConnectionRecordProto;
 
 import java.io.PrintWriter;
 
@@ -119,4 +122,70 @@
         sb.append('}');
         return stringName = sb.toString();
     }
+
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+        if (binding == null) return; // if binding is null, don't write data, something is wrong.
+        long token = proto.start(fieldId);
+        proto.write(ConnectionRecordProto.HEX_HASH,
+                Integer.toHexString(System.identityHashCode(this)));
+        if (binding.client != null) {
+            proto.write(ConnectionRecordProto.USER_ID, binding.client.userId);
+        }
+        if ((flags&Context.BIND_AUTO_CREATE) != 0) {
+            proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.AUTO_CREATE);
+        }
+        if ((flags&Context.BIND_DEBUG_UNBIND) != 0) {
+            proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.DEBUG_UNBIND);
+        }
+        if ((flags&Context.BIND_NOT_FOREGROUND) != 0) {
+            proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.NOT_FG);
+        }
+        if ((flags&Context.BIND_IMPORTANT_BACKGROUND) != 0) {
+            proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.IMPORTANT_BG);
+        }
+        if ((flags&Context.BIND_ABOVE_CLIENT) != 0) {
+            proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.ABOVE_CLIENT);
+        }
+        if ((flags&Context.BIND_ALLOW_OOM_MANAGEMENT) != 0) {
+            proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.ALLOW_OOM_MANAGEMENT);
+        }
+        if ((flags&Context.BIND_WAIVE_PRIORITY) != 0) {
+            proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.WAIVE_PRIORITY);
+        }
+        if ((flags&Context.BIND_IMPORTANT) != 0) {
+            proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.IMPORTANT);
+        }
+        if ((flags&Context.BIND_ADJUST_WITH_ACTIVITY) != 0) {
+            proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.ADJUST_WITH_ACTIVITY);
+        }
+        if ((flags&Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE) != 0) {
+            proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.FG_SERVICE_WHILE_WAKE);
+        }
+        if ((flags&Context.BIND_FOREGROUND_SERVICE) != 0) {
+            proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.FG_SERVICE);
+        }
+        if ((flags&Context.BIND_TREAT_LIKE_ACTIVITY) != 0) {
+            proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.TREAT_LIKE_ACTIVITY);
+        }
+        if ((flags&Context.BIND_VISIBLE) != 0) {
+            proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.VISIBLE);
+        }
+        if ((flags&Context.BIND_SHOWING_UI) != 0) {
+            proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.SHOWING_UI);
+        }
+        if ((flags&Context.BIND_NOT_VISIBLE) != 0) {
+            proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.NOT_VISIBLE);
+        }
+        if (serviceDead) {
+            proto.write(ConnectionRecordProto.FLAGS, ConnectionRecordProto.DEAD);
+        }
+        if (binding.service != null) {
+            proto.write(ConnectionRecordProto.SERVICE_NAME, binding.service.shortName);
+        }
+        if (conn != null) {
+            proto.write(ConnectionRecordProto.CONN_HEX_HASH,
+                    Integer.toHexString(System.identityHashCode(conn.asBinder())));
+        }
+        proto.end(token);
+    }
 }
diff --git a/com/android/server/am/IntentBindRecord.java b/com/android/server/am/IntentBindRecord.java
index be290e9..01ce64c 100644
--- a/com/android/server/am/IntentBindRecord.java
+++ b/com/android/server/am/IntentBindRecord.java
@@ -21,6 +21,10 @@
 import android.os.IBinder;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.server.am.proto.AppBindRecordProto;
+import com.android.server.am.proto.IntentBindRecordProto;
 
 import java.io.PrintWriter;
 
@@ -106,4 +110,32 @@
         sb.append('}');
         return stringName = sb.toString();
     }
+
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+        long token = proto.start(fieldId);
+        proto.write(IntentBindRecordProto.HEX_HASH,
+                Integer.toHexString(System.identityHashCode(this)));
+        proto.write(IntentBindRecordProto.IS_CREATE,
+                (collectFlags()&Context.BIND_AUTO_CREATE) != 0);
+        if (intent != null) {
+            intent.getIntent().writeToProto(proto,
+                    IntentBindRecordProto.INTENT, false, true, false, false);
+        }
+        if (binder != null) {
+            proto.write(IntentBindRecordProto.BINDER, binder.toString());
+        }
+        proto.write(IntentBindRecordProto.REQUESTED, requested);
+        proto.write(IntentBindRecordProto.RECEIVED, received);
+        proto.write(IntentBindRecordProto.HAS_BOUND, hasBound);
+        proto.write(IntentBindRecordProto.DO_REBIND, doRebind);
+
+        final int N = apps.size();
+        for (int i=0; i<N; i++) {
+            AppBindRecord a = apps.valueAt(i);
+            if (a != null) {
+                a.writeToProto(proto, IntentBindRecordProto.APPS);
+            }
+        }
+        proto.end(token);
+    }
 }
diff --git a/com/android/server/am/KeyguardController.java b/com/android/server/am/KeyguardController.java
index 76b4679..35f4f25 100644
--- a/com/android/server/am/KeyguardController.java
+++ b/com/android/server/am/KeyguardController.java
@@ -19,9 +19,9 @@
 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 android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS;
+import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_SHADE;
+import static android.view.WindowManagerPolicyConstants.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;
@@ -41,8 +41,11 @@
 import android.os.Trace;
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
+
 import com.android.internal.policy.IKeyguardDismissCallback;
+import com.android.server.policy.WindowManagerPolicy;
 import com.android.server.wm.WindowManagerService;
+
 import java.io.PrintWriter;
 
 /**
@@ -118,7 +121,7 @@
     /**
      * Called when Keyguard is going away.
      *
-     * @param flags See {@link android.view.WindowManagerPolicy#KEYGUARD_GOING_AWAY_FLAG_TO_SHADE}
+     * @param flags See {@link WindowManagerPolicy#KEYGUARD_GOING_AWAY_FLAG_TO_SHADE}
      *              etc.
      */
     void keyguardGoingAway(int flags) {
diff --git a/com/android/server/am/LaunchingActivityPositioner.java b/com/android/server/am/LaunchingActivityPositioner.java
index d5f9cf3..793884d 100644
--- a/com/android/server/am/LaunchingActivityPositioner.java
+++ b/com/android/server/am/LaunchingActivityPositioner.java
@@ -47,10 +47,10 @@
             return RESULT_SKIP;
         }
 
-        final Rect bounds = TaskRecord.validateBounds(options.getLaunchBounds());
+        final Rect bounds = options.getLaunchBounds();
 
         // Bounds weren't valid.
-        if (bounds == null) {
+        if (bounds == null || bounds.isEmpty()) {
             return RESULT_SKIP;
         }
 
diff --git a/com/android/server/am/LaunchingBoundsController.java b/com/android/server/am/LaunchingBoundsController.java
index c762f7f..5aa7f58 100644
--- a/com/android/server/am/LaunchingBoundsController.java
+++ b/com/android/server/am/LaunchingBoundsController.java
@@ -101,8 +101,12 @@
      * @return {@code true} if bounds were set on the task. {@code false} otherwise.
      */
     boolean layoutTask(TaskRecord task, WindowLayout layout) {
-        calculateBounds(task, layout, null /*activity*/, null /*source*/, null /*options*/,
-                mTmpRect);
+        return layoutTask(task, layout, null /*activity*/, null /*source*/, null /*options*/);
+    }
+
+    boolean layoutTask(TaskRecord task, WindowLayout layout, ActivityRecord activity,
+            ActivityRecord source, ActivityOptions options) {
+        calculateBounds(task, layout, activity, source, options, mTmpRect);
 
         if (mTmpRect.isEmpty()) {
             return false;
diff --git a/com/android/server/am/LaunchingTaskPositioner.java b/com/android/server/am/LaunchingTaskPositioner.java
index c958fca..d89568e 100644
--- a/com/android/server/am/LaunchingTaskPositioner.java
+++ b/com/android/server/am/LaunchingTaskPositioner.java
@@ -88,7 +88,7 @@
 
         final ArrayList<TaskRecord> tasks = task.getStack().getAllTasks();
 
-        updateAvailableRect(task, mAvailableRect);
+        mAvailableRect.set(task.getParent().getBounds());
 
         if (layout == null) {
             positionCenter(tasks, mAvailableRect, getFreeformWidth(mAvailableRect),
@@ -123,17 +123,6 @@
         return RESULT_CONTINUE;
     }
 
-    private void updateAvailableRect(TaskRecord task, Rect availableRect) {
-        final Rect stackBounds = task.getStack().mBounds;
-
-        if (stackBounds != null) {
-            availableRect.set(stackBounds);
-        } else {
-            task.getStack().getDisplay().mDisplay.getSize(mDisplaySize);
-            availableRect.set(0, 0, mDisplaySize.x, mDisplaySize.y);
-        }
-    }
-
     @VisibleForTesting
     static int getFreeformStartLeft(Rect bounds) {
         return bounds.left + bounds.width() / MARGIN_SIZE_DENOMINATOR;
@@ -294,9 +283,9 @@
 
     private static boolean boundsConflict(Rect proposal, ArrayList<TaskRecord> tasks) {
         for (int i = tasks.size() - 1; i >= 0; i--) {
-            TaskRecord task = tasks.get(i);
-            if (!task.mActivities.isEmpty() && task.mBounds != null) {
-                Rect bounds = task.mBounds;
+            final TaskRecord task = tasks.get(i);
+            if (!task.mActivities.isEmpty() && !task.matchParentBounds()) {
+                final Rect bounds = task.getOverrideBounds();
                 if (closeLeftTopCorner(proposal, bounds) || closeRightTopCorner(proposal, bounds)
                         || closeLeftBottomCorner(proposal, bounds)
                         || closeRightBottomCorner(proposal, bounds)) {
diff --git a/com/android/server/am/LockTaskController.java b/com/android/server/am/LockTaskController.java
index e87b4e6..d77e1a2 100644
--- a/com/android/server/am/LockTaskController.java
+++ b/com/android/server/am/LockTaskController.java
@@ -250,7 +250,24 @@
     }
 
     /**
-     * @return whether the requested task is allowed to be launched.
+     * @return whether the requested task is allowed to be locked (either whitelisted, or declares
+     * lockTaskMode="always" in the manifest).
+     */
+    boolean isTaskWhitelisted(TaskRecord task) {
+        switch(task.mLockTaskAuth) {
+            case LOCK_TASK_AUTH_WHITELISTED:
+            case LOCK_TASK_AUTH_LAUNCHABLE:
+            case LOCK_TASK_AUTH_LAUNCHABLE_PRIV:
+                return true;
+            case LOCK_TASK_AUTH_PINNABLE:
+            case LOCK_TASK_AUTH_DONT_LOCK:
+            default:
+                return false;
+        }
+    }
+
+    /**
+     * @return whether the requested task is disallowed to be launched.
      */
     boolean isLockTaskModeViolation(TaskRecord task) {
         return isLockTaskModeViolation(task, false);
@@ -258,7 +275,7 @@
 
     /**
      * @param isNewClearTask whether the task would be cleared as part of the operation.
-     * @return whether the requested task is allowed to be launched.
+     * @return whether the requested task is disallowed to be launched.
      */
     boolean isLockTaskModeViolation(TaskRecord task, boolean isNewClearTask) {
         if (isLockTaskModeViolationInternal(task, isNewClearTask)) {
@@ -275,21 +292,18 @@
             // If the task is already at the top and won't be cleared, then allow the operation
             return false;
         }
-        final int lockTaskAuth = task.mLockTaskAuth;
-        switch (lockTaskAuth) {
-            case LOCK_TASK_AUTH_DONT_LOCK:
-                return !mLockTaskModeTasks.isEmpty();
-            case LOCK_TASK_AUTH_LAUNCHABLE_PRIV:
-            case LOCK_TASK_AUTH_LAUNCHABLE:
-            case LOCK_TASK_AUTH_WHITELISTED:
-                return false;
-            case LOCK_TASK_AUTH_PINNABLE:
-                // Pinnable tasks can't be launched on top of locktask tasks.
-                return !mLockTaskModeTasks.isEmpty();
-            default:
-                Slog.w(TAG, "isLockTaskModeViolation: invalid lockTaskAuth value=" + lockTaskAuth);
-                return true;
+
+        // Allow recents activity if enabled by policy
+        if (task.isActivityTypeRecents() && isRecentsAllowed(task.userId)) {
+            return false;
         }
+
+        return !(isTaskWhitelisted(task) || mLockTaskModeTasks.isEmpty());
+    }
+
+    private boolean isRecentsAllowed(int userId) {
+        return (getLockTaskFeaturesForUser(userId)
+                & DevicePolicyManager.LOCK_TASK_FEATURE_RECENTS) != 0;
     }
 
     /**
@@ -491,6 +505,7 @@
         }
 
         if (mLockTaskModeTasks.isEmpty()) {
+            mSupervisor.mRecentTasks.onLockTaskModeStateChanged(lockTaskModeState, task.userId);
             // Start lock task on the handler thread
             mHandler.post(() -> performStartLockTask(
                     task.intent.getComponent().getPackageName(),
diff --git a/com/android/server/am/ProcessList.java b/com/android/server/am/ProcessList.java
index 7810c5e..6fb3dbb 100644
--- a/com/android/server/am/ProcessList.java
+++ b/com/android/server/am/ProcessList.java
@@ -40,7 +40,7 @@
 /**
  * Activity manager code dealing with processes.
  */
-final class ProcessList {
+public final class ProcessList {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "ProcessList" : TAG_AM;
 
     // The minimum time we allow between crashes, for us to consider this
@@ -400,6 +400,9 @@
             case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT:
                 procState = "CACC";
                 break;
+            case ActivityManager.PROCESS_STATE_CACHED_RECENT:
+                procState = "CRE ";
+                break;
             case ActivityManager.PROCESS_STATE_CACHED_EMPTY:
                 procState = "CEM ";
                 break;
@@ -494,6 +497,7 @@
         PROC_MEM_CACHED,                // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
         PROC_MEM_CACHED,                // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
         PROC_MEM_CACHED,                // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT
+        PROC_MEM_CACHED,                // ActivityManager.PROCESS_STATE_CACHED_RECENT
         PROC_MEM_CACHED,                // ActivityManager.PROCESS_STATE_CACHED_EMPTY
     };
 
@@ -515,6 +519,7 @@
         PSS_FIRST_CACHED_INTERVAL,      // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
         PSS_FIRST_CACHED_INTERVAL,      // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
         PSS_FIRST_CACHED_INTERVAL,      // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT
+        PSS_FIRST_CACHED_INTERVAL,      // ActivityManager.PROCESS_STATE_CACHED_RECENT
         PSS_FIRST_CACHED_INTERVAL,      // ActivityManager.PROCESS_STATE_CACHED_EMPTY
     };
 
@@ -536,6 +541,7 @@
         PSS_SAME_CACHED_INTERVAL,       // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
         PSS_SAME_CACHED_INTERVAL,       // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
         PSS_SAME_CACHED_INTERVAL,       // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT
+        PSS_SAME_CACHED_INTERVAL,       // ActivityManager.PROCESS_STATE_CACHED_RECENT
         PSS_SAME_CACHED_INTERVAL,       // ActivityManager.PROCESS_STATE_CACHED_EMPTY
     };
 
@@ -557,6 +563,7 @@
         PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
         PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
         PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT
+        PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_RECENT
         PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_EMPTY
     };
 
@@ -578,6 +585,7 @@
         PSS_TEST_SAME_BACKGROUND_INTERVAL,  // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
         PSS_TEST_SAME_BACKGROUND_INTERVAL,  // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
         PSS_TEST_SAME_BACKGROUND_INTERVAL,  // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT
+        PSS_TEST_SAME_BACKGROUND_INTERVAL,  // ActivityManager.PROCESS_STATE_CACHED_RECENT
         PSS_TEST_SAME_BACKGROUND_INTERVAL,  // ActivityManager.PROCESS_STATE_CACHED_EMPTY
     };
 
diff --git a/com/android/server/am/ProcessRecord.java b/com/android/server/am/ProcessRecord.java
index e847723..9d3c2ae 100644
--- a/com/android/server/am/ProcessRecord.java
+++ b/com/android/server/am/ProcessRecord.java
@@ -164,6 +164,8 @@
 
     // all activities running in the process
     final ArrayList<ActivityRecord> activities = new ArrayList<>();
+    // any tasks this process had run root activities in
+    final ArrayList<TaskRecord> recentTasks = new ArrayList<>();
     // all ServiceRecord running in this process
     final ArraySet<ServiceRecord> services = new ArraySet<>();
     // services that are currently executing code (need to remain foreground).
@@ -396,6 +398,12 @@
                 pw.print(prefix); pw.print("  - "); pw.println(activities.get(i));
             }
         }
+        if (recentTasks.size() > 0) {
+            pw.print(prefix); pw.println("Recent Tasks:");
+            for (int i=0; i<recentTasks.size(); i++) {
+                pw.print(prefix); pw.print("  - "); pw.println(recentTasks.get(i));
+            }
+        }
         if (services.size() > 0) {
             pw.print(prefix); pw.println("Services:");
             for (int i=0; i<services.size(); i++) {
@@ -512,6 +520,13 @@
         }
     }
 
+    public void clearRecentTasks() {
+        for (int i = recentTasks.size() - 1; i >= 0; i--) {
+            recentTasks.get(i).clearRootProcess();
+        }
+        recentTasks.clear();
+    }
+
     /**
      * This method returns true if any of the activities within the process record are interesting
      * to the user. See HistoryRecord.isInterestingToUserLocked()
diff --git a/com/android/server/am/ProviderMap.java b/com/android/server/am/ProviderMap.java
index 32d03da..8a905f8 100644
--- a/com/android/server/am/ProviderMap.java
+++ b/com/android/server/am/ProviderMap.java
@@ -28,6 +28,7 @@
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map;
@@ -322,8 +323,7 @@
         return needSep;
     }
 
-    protected boolean dumpProvider(FileDescriptor fd, PrintWriter pw, String name, String[] args,
-            int opti, boolean dumpAll) {
+    private ArrayList<ContentProviderRecord> getProvidersForName(String name) {
         ArrayList<ContentProviderRecord> allProviders = new ArrayList<ContentProviderRecord>();
         ArrayList<ContentProviderRecord> providers = new ArrayList<ContentProviderRecord>();
 
@@ -365,6 +365,12 @@
                 }
             }
         }
+        return providers;
+    }
+
+    protected boolean dumpProvider(FileDescriptor fd, PrintWriter pw, String name, String[] args,
+            int opti, boolean dumpAll) {
+        ArrayList<ContentProviderRecord> providers = getProvidersForName(name);
 
         if (providers.size() <= 0) {
             return false;
@@ -417,6 +423,33 @@
     }
 
     /**
+     * Similar to the dumpProvider, but only dumps the first matching provider.
+     * The provider is responsible for dumping as proto.
+     */
+    protected boolean dumpProviderProto(FileDescriptor fd, PrintWriter pw, String name,
+            String[] args) {
+        //add back the --proto arg, which was stripped out by PriorityDump
+        String[] newArgs = Arrays.copyOf(args, args.length + 1);
+        newArgs[args.length] = "--proto";
+
+        ArrayList<ContentProviderRecord> providers = getProvidersForName(name);
+
+        if (providers.size() <= 0) {
+            return false;
+        }
+
+        // Only dump the first provider, since we are dumping in proto format
+        for (int i = 0; i < providers.size(); i++) {
+            final ContentProviderRecord r = providers.get(i);
+            if (r.proc != null && r.proc.thread != null) {
+                dumpToTransferPipe(null, fd, pw, r, newArgs);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
      * Invokes IApplicationThread.dumpProvider() on the thread of the specified provider without
      * any meta string (e.g., provider info, indentation) written to the file descriptor.
      */
diff --git a/com/android/server/am/RecentTasks.java b/com/android/server/am/RecentTasks.java
index d35c37b..abb296e 100644
--- a/com/android/server/am/RecentTasks.java
+++ b/com/android/server/am/RecentTasks.java
@@ -80,6 +80,20 @@
 /**
  * Class for managing the recent tasks list. The list is ordered by most recent (index 0) to the
  * least recent.
+ *
+ * The trimming logic can be boiled down to the following.  For recent task list with a number of
+ * tasks, the visible tasks are an interleaving subset of tasks that would normally be presented to
+ * the user. Non-visible tasks are not considered for trimming. Of the visible tasks, only a
+ * sub-range are presented to the user, based on the device type, last task active time, or other
+ * task state. Tasks that are not in the visible range and are not returnable from the SystemUI
+ * (considering the back stack) are considered trimmable. If the device does not support recent
+ * tasks, then trimming is completely disabled.
+ *
+ * eg.
+ * L = [TTTTTTTTTTTTTTTTTTTTTTTTTT] // list of tasks
+ *     [VVV  VV   VVVV  V V V     ] // Visible tasks
+ *     [RRR  RR   XXXX  X X X     ] // Visible range tasks, eg. if the device only shows 5 tasks,
+ *                                  // 'X' tasks are trimmed.
  */
 class RecentTasks {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "RecentTasks" : TAG_AM;
@@ -488,6 +502,18 @@
         }
     }
 
+    void onLockTaskModeStateChanged(int lockTaskModeState, int userId) {
+        if (lockTaskModeState != ActivityManager.LOCK_TASK_MODE_LOCKED) {
+            return;
+        }
+        for (int i = mTasks.size() - 1; i >= 0; --i) {
+            final TaskRecord tr = mTasks.get(i);
+            if (tr.userId == userId && !mService.mLockTaskController.isTaskWhitelisted(tr)) {
+                remove(tr);
+            }
+        }
+    }
+
     void removeTasksByPackageName(String packageName, int userId) {
         for (int i = mTasks.size() - 1; i >= 0; --i) {
             final TaskRecord tr = mTasks.get(i);
@@ -496,7 +522,8 @@
             if (tr.userId != userId) return;
             if (!taskPackageName.equals(packageName)) return;
 
-            mService.mStackSupervisor.removeTaskByIdLocked(tr.taskId, true, REMOVE_FROM_RECENTS);
+            mService.mStackSupervisor.removeTaskByIdLocked(tr.taskId, true, REMOVE_FROM_RECENTS,
+                    "remove-package-task");
         }
     }
 
@@ -513,7 +540,7 @@
                     && (filterByClasses == null || filterByClasses.contains(cn.getClassName()));
             if (sameComponent) {
                 mService.mStackSupervisor.removeTaskByIdLocked(tr.taskId, false,
-                        REMOVE_FROM_RECENTS);
+                        REMOVE_FROM_RECENTS, "disabled-package");
             }
         }
     }
@@ -1001,12 +1028,13 @@
                     continue;
                 } else {
                     numVisibleTasks++;
-                    if (isInVisibleRange(task, numVisibleTasks)) {
+                    if (isInVisibleRange(task, numVisibleTasks) || !isTrimmable(task)) {
                         // Keep visible tasks in range
                         i++;
                         continue;
                     } else {
-                        // Fall through to trim visible tasks that are no longer in range
+                        // Fall through to trim visible tasks that are no longer in range and
+                        // trimmable
                         if (DEBUG_RECENTS_TRIM_TASKS) Slog.d(TAG,
                                 "Trimming out-of-range visible task=" + task);
                     }
@@ -1122,6 +1150,28 @@
     }
 
     /**
+     * @return whether the given task can be trimmed even if it is outside the visible range.
+     */
+    protected boolean isTrimmable(TaskRecord task) {
+        final ActivityStack stack = task.getStack();
+        final ActivityStack homeStack = mService.mStackSupervisor.mHomeStack;
+
+        // No stack for task, just trim it
+        if (stack == null) {
+            return true;
+        }
+
+        // Ignore tasks from different displays
+        if (stack.getDisplay() != homeStack.getDisplay()) {
+            return false;
+        }
+
+        // Trim tasks that are in stacks that are behind the home stack
+        final ActivityDisplay display = stack.getDisplay();
+        return display.getIndexOf(stack) < display.getIndexOf(homeStack);
+    }
+
+    /**
      * If needed, remove oldest existing entries in recents that are for the same kind
      * of task as the given one.
      */
@@ -1426,9 +1476,6 @@
      * Creates a new RecentTaskInfo from a TaskRecord.
      */
     ActivityManager.RecentTaskInfo createRecentTaskInfo(TaskRecord tr) {
-        // Update the task description to reflect any changes in the task stack
-        tr.updateTaskDescription();
-
         // Compose the recent task info
         ActivityManager.RecentTaskInfo rti = new ActivityManager.RecentTaskInfo();
         rti.id = tr.getTopActivity() == null ? INVALID_TASK_ID : tr.taskId;
@@ -1444,8 +1491,8 @@
         rti.affiliatedTaskId = tr.mAffiliatedTaskId;
         rti.affiliatedTaskColor = tr.mAffiliatedTaskColor;
         rti.numActivities = 0;
-        if (tr.mBounds != null) {
-            rti.bounds = new Rect(tr.mBounds);
+        if (!tr.matchParentBounds()) {
+            rti.bounds = new Rect(tr.getOverrideBounds());
         }
         rti.supportsSplitScreenMultiWindow = tr.supportsSplitScreenWindowingMode();
         rti.resizeMode = tr.mResizeMode;
diff --git a/com/android/server/am/ServiceRecord.java b/com/android/server/am/ServiceRecord.java
index 16995e5..b6eff00 100644
--- a/com/android/server/am/ServiceRecord.java
+++ b/com/android/server/am/ServiceRecord.java
@@ -19,6 +19,7 @@
 import com.android.internal.app.procstats.ServiceState;
 import com.android.internal.os.BatteryStatsImpl;
 import com.android.server.LocalServices;
+import com.android.server.am.proto.ServiceRecordProto;
 import com.android.server.notification.NotificationManagerInternal;
 
 import android.app.INotificationManager;
@@ -42,6 +43,8 @@
 import android.util.ArrayMap;
 import android.util.Slog;
 import android.util.TimeUtils;
+import android.util.proto.ProtoOutputStream;
+import android.util.proto.ProtoUtils;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -79,7 +82,7 @@
     final String permission;// permission needed to access service
     final boolean exported; // from ServiceInfo.exported
     final Runnable restarter; // used to schedule retries of starting the service
-    final long createTime;  // when this service was created
+    final long createRealTime;  // when this service was created
     final ArrayMap<Intent.FilterComparison, IntentBindRecord> bindings
             = new ArrayMap<Intent.FilterComparison, IntentBindRecord>();
                             // All active bindings to the service.
@@ -103,7 +106,7 @@
     boolean startRequested; // someone explicitly called start?
     boolean delayedStop;    // service has been stopped but is in a delayed start?
     boolean stopIfKilled;   // last onStart() said to stop if service killed?
-    boolean callStart;      // last onStart() has asked to alway be called on restart.
+    boolean callStart;      // last onStart() has asked to always be called on restart.
     int executeNesting;     // number of outstanding operations keeping foreground.
     boolean executeFg;      // should we be executing in the foreground?
     long executingStart;    // start time of last execute request.
@@ -159,6 +162,27 @@
             }
         }
 
+        public void writeToProto(ProtoOutputStream proto, long fieldId, long now) {
+            long token = proto.start(fieldId);
+            proto.write(ServiceRecordProto.StartItemProto.ID, id);
+            ProtoUtils.toDuration(proto,
+                    ServiceRecordProto.StartItemProto.DURATION, deliveredTime, now);
+            proto.write(ServiceRecordProto.StartItemProto.DELIVERY_COUNT, deliveryCount);
+            proto.write(ServiceRecordProto.StartItemProto.DONE_EXECUTING_COUNT, doneExecutingCount);
+            if (intent != null) {
+                intent.writeToProto(proto, ServiceRecordProto.StartItemProto.INTENT, true, true,
+                        true, false);
+            }
+            if (neededGrants != null) {
+                neededGrants.writeToProto(proto, ServiceRecordProto.StartItemProto.NEEDED_GRANTS);
+            }
+            if (uriPermissions != null) {
+                uriPermissions.writeToProto(proto,
+                        ServiceRecordProto.StartItemProto.URI_PERMISSIONS);
+            }
+            proto.end(token);
+        }
+
         public String toString() {
             if (stringName != null) {
                 return stringName;
@@ -209,6 +233,117 @@
         }
     }
 
+    void writeToProto(ProtoOutputStream proto, long fieldId) {
+        long token = proto.start(fieldId);
+        proto.write(ServiceRecordProto.SHORT_NAME, this.shortName);
+        proto.write(ServiceRecordProto.HEX_HASH,
+                Integer.toHexString(System.identityHashCode(this)));
+        proto.write(ServiceRecordProto.IS_RUNNING, app != null);
+        if (app != null) {
+            proto.write(ServiceRecordProto.PID, app.pid);
+        }
+        if (intent != null) {
+            intent.getIntent().writeToProto(proto, ServiceRecordProto.INTENT, false, true, false,
+                    true);
+        }
+        proto.write(ServiceRecordProto.PACKAGE_NAME, packageName);
+        proto.write(ServiceRecordProto.PROCESS_NAME, processName);
+        proto.write(ServiceRecordProto.PERMISSION, permission);
+
+        long now = SystemClock.uptimeMillis();
+        long nowReal = SystemClock.elapsedRealtime();
+        if (appInfo != null) {
+            long appInfoToken = proto.start(ServiceRecordProto.APPINFO);
+            proto.write(ServiceRecordProto.AppInfo.BASE_DIR, appInfo.sourceDir);
+            if (!Objects.equals(appInfo.sourceDir, appInfo.publicSourceDir)) {
+                proto.write(ServiceRecordProto.AppInfo.RES_DIR, appInfo.publicSourceDir);
+            }
+            proto.write(ServiceRecordProto.AppInfo.DATA_DIR, appInfo.dataDir);
+            proto.end(appInfoToken);
+        }
+        if (app != null) {
+            app.writeToProto(proto, ServiceRecordProto.APP);
+        }
+        if (isolatedProc != null) {
+            isolatedProc.writeToProto(proto, ServiceRecordProto.ISOLATED_PROC);
+        }
+        proto.write(ServiceRecordProto.WHITELIST_MANAGER, whitelistManager);
+        proto.write(ServiceRecordProto.DELAYED, delayed);
+        if (isForeground || foregroundId != 0) {
+            long fgToken = proto.start(ServiceRecordProto.FOREGROUND);
+            proto.write(ServiceRecordProto.Foreground.ID, foregroundId);
+            foregroundNoti.writeToProto(proto, ServiceRecordProto.Foreground.NOTIFICATION);
+            proto.end(fgToken);
+        }
+        ProtoUtils.toDuration(proto, ServiceRecordProto.CREATE_REAL_TIME, createRealTime, nowReal);
+        ProtoUtils.toDuration(proto,
+                ServiceRecordProto.STARTING_BG_TIMEOUT, startingBgTimeout, now);
+        ProtoUtils.toDuration(proto, ServiceRecordProto.LAST_ACTIVITY_TIME, lastActivity, now);
+        ProtoUtils.toDuration(proto, ServiceRecordProto.RESTART_TIME, restartTime, now);
+        proto.write(ServiceRecordProto.CREATED_FROM_FG, createdFromFg);
+
+        if (startRequested || delayedStop || lastStartId != 0) {
+            long startToken = proto.start(ServiceRecordProto.START);
+            proto.write(ServiceRecordProto.Start.START_REQUESTED, startRequested);
+            proto.write(ServiceRecordProto.Start.DELAYED_STOP, delayedStop);
+            proto.write(ServiceRecordProto.Start.STOP_IF_KILLED, stopIfKilled);
+            proto.write(ServiceRecordProto.Start.LAST_START_ID, lastStartId);
+            proto.end(startToken);
+        }
+
+        if (executeNesting != 0) {
+            long executNestingToken = proto.start(ServiceRecordProto.EXECUTE);
+            proto.write(ServiceRecordProto.ExecuteNesting.EXECUTE_NESTING, executeNesting);
+            proto.write(ServiceRecordProto.ExecuteNesting.EXECUTE_FG, executeFg);
+            ProtoUtils.toDuration(proto,
+                    ServiceRecordProto.ExecuteNesting.EXECUTING_START, executingStart, now);
+            proto.end(executNestingToken);
+        }
+        if (destroying || destroyTime != 0) {
+            ProtoUtils.toDuration(proto, ServiceRecordProto.DESTORY_TIME, destroyTime, now);
+        }
+        if (crashCount != 0 || restartCount != 0 || restartDelay != 0 || nextRestartTime != 0) {
+            long crashToken = proto.start(ServiceRecordProto.CRASH);
+            proto.write(ServiceRecordProto.Crash.RESTART_COUNT, restartCount);
+            ProtoUtils.toDuration(proto, ServiceRecordProto.Crash.RESTART_DELAY, restartDelay, now);
+            ProtoUtils.toDuration(proto,
+                    ServiceRecordProto.Crash.NEXT_RESTART_TIME, nextRestartTime, now);
+            proto.write(ServiceRecordProto.Crash.CRASH_COUNT, crashCount);
+            proto.end(crashToken);
+        }
+
+        if (deliveredStarts.size() > 0) {
+            final int N = deliveredStarts.size();
+            for (int i = 0; i < N; i++) {
+                deliveredStarts.get(i).writeToProto(proto,
+                        ServiceRecordProto.DELIVERED_STARTS, now);
+            }
+        }
+        if (pendingStarts.size() > 0) {
+            final int N = pendingStarts.size();
+            for (int i = 0; i < N; i++) {
+                pendingStarts.get(i).writeToProto(proto, ServiceRecordProto.PENDING_STARTS, now);
+            }
+        }
+        if (bindings.size() > 0) {
+            final int N = bindings.size();
+            for (int i=0; i<N; i++) {
+                IntentBindRecord b = bindings.valueAt(i);
+                b.writeToProto(proto, ServiceRecordProto.BINDINGS);
+            }
+        }
+        if (connections.size() > 0) {
+            final int N = connections.size();
+            for (int conni=0; conni<N; conni++) {
+                ArrayList<ConnectionRecord> c = connections.valueAt(conni);
+                for (int i=0; i<c.size(); i++) {
+                    c.get(i).writeToProto(proto, ServiceRecordProto.CONNECTIONS);
+                }
+            }
+        }
+        proto.end(token);
+    }
+
     void dump(PrintWriter pw, String prefix) {
         pw.print(prefix); pw.print("intent={");
                 pw.print(intent.getIntent().toShortString(false, true, false, true));
@@ -243,7 +378,7 @@
                     pw.print(" foregroundNoti="); pw.println(foregroundNoti);
         }
         pw.print(prefix); pw.print("createTime=");
-                TimeUtils.formatDuration(createTime, nowReal, pw);
+                TimeUtils.formatDuration(createRealTime, nowReal, pw);
                 pw.print(" startingBgTimeout=");
                 TimeUtils.formatDuration(startingBgTimeout, now, pw);
                 pw.println();
@@ -329,7 +464,7 @@
         permission = sInfo.permission;
         exported = sInfo.exported;
         this.restarter = restarter;
-        createTime = SystemClock.elapsedRealtime();
+        createRealTime = SystemClock.elapsedRealtime();
         lastActivity = SystemClock.uptimeMillis();
         userId = UserHandle.getUserId(appInfo.uid);
         createdFromFg = callerIsFg;
diff --git a/com/android/server/am/TaskRecord.java b/com/android/server/am/TaskRecord.java
index 949f51f..83965ee 100644
--- a/com/android/server/am/TaskRecord.java
+++ b/com/android/server/am/TaskRecord.java
@@ -19,9 +19,6 @@
 import static android.app.ActivityManager.RESIZE_MODE_FORCED;
 import static android.app.ActivityManager.RESIZE_MODE_SYSTEM;
 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.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
@@ -257,6 +254,11 @@
     /** Current stack. Setter must always be used to update the value. */
     private ActivityStack mStack;
 
+    /** The process that had previously hosted the root activity of this task.
+     * Used to know that we should try harder to keep this process around, in case the
+     * user wants to return to it. */
+    private ProcessRecord mRootProcess;
+
     /** Takes on same value as first root activity */
     boolean isPersistable = false;
     int maxRecents;
@@ -288,11 +290,6 @@
 
     final ActivityManagerService mService;
 
-    // Whether or not this task covers the entire screen; by default tasks are fullscreen.
-    boolean mFullscreen = true;
-
-    // Bounds of the Task. null for fullscreen tasks.
-    Rect mBounds = null;
     private final Rect mTmpStableBounds = new Rect();
     private final Rect mTmpNonDecorBounds = new Rect();
     private final Rect mTmpRect = new Rect();
@@ -475,68 +472,76 @@
     }
 
     boolean resize(Rect bounds, int resizeMode, boolean preserveWindow, boolean deferResume) {
-        if (!isResizeable()) {
-            Slog.w(TAG, "resizeTask: task " + this + " not resizeable.");
-            return true;
-        }
+        mService.mWindowManager.deferSurfaceLayout();
 
-        // If this is a forced resize, let it go through even if the bounds is not changing,
-        // as we might need a relayout due to surface size change (to/from fullscreen).
-        final boolean forced = (resizeMode & RESIZE_MODE_FORCED) != 0;
-        if (Objects.equals(mBounds, bounds) && !forced) {
-            // Nothing to do here...
-            return true;
-        }
-        bounds = validateBounds(bounds);
-
-        if (mWindowContainerController == null) {
-            // Task doesn't exist in window manager yet (e.g. was restored from recents).
-            // All we can do for now is update the bounds so it can be used when the task is
-            // added to window manager.
-            updateOverrideConfiguration(bounds);
-            if (!inFreeformWindowingMode()) {
-                // re-restore the task so it can have the proper stack association.
-                mService.mStackSupervisor.restoreRecentTaskLocked(this, null, !ON_TOP);
+        try {
+            if (!isResizeable()) {
+                Slog.w(TAG, "resizeTask: task " + this + " not resizeable.");
+                return true;
             }
-            return true;
-        }
 
-        if (!canResizeToBounds(bounds)) {
-            throw new IllegalArgumentException("resizeTask: Can not resize task=" + this
-                    + " to bounds=" + bounds + " resizeMode=" + mResizeMode);
-        }
+            // If this is a forced resize, let it go through even if the bounds is not changing,
+            // as we might need a relayout due to surface size change (to/from fullscreen).
+            final boolean forced = (resizeMode & RESIZE_MODE_FORCED) != 0;
+            if (equivalentOverrideBounds(bounds) && !forced) {
+                // Nothing to do here...
+                return true;
+            }
 
-        // Do not move the task to another stack here.
-        // This method assumes that the task is already placed in the right stack.
-        // we do not mess with that decision and we only do the resize!
+            if (mWindowContainerController == null) {
+                // Task doesn't exist in window manager yet (e.g. was restored from recents).
+                // All we can do for now is update the bounds so it can be used when the task is
+                // added to window manager.
+                updateOverrideConfiguration(bounds);
+                if (!inFreeformWindowingMode()) {
+                    // re-restore the task so it can have the proper stack association.
+                    mService.mStackSupervisor.restoreRecentTaskLocked(this, null, !ON_TOP);
+                }
+                return true;
+            }
 
-        Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "am.resizeTask_" + taskId);
+            if (!canResizeToBounds(bounds)) {
+                throw new IllegalArgumentException("resizeTask: Can not resize task=" + this
+                        + " to bounds=" + bounds + " resizeMode=" + mResizeMode);
+            }
 
-        final boolean updatedConfig = updateOverrideConfiguration(bounds);
-        // This variable holds information whether the configuration didn't change in a significant
-        // way and the activity was kept the way it was. If it's false, it means the activity had
-        // to be relaunched due to configuration change.
-        boolean kept = true;
-        if (updatedConfig) {
-            final ActivityRecord r = topRunningActivityLocked();
-            if (r != null && !deferResume) {
-                kept = r.ensureActivityConfigurationLocked(0 /* globalChanges */, preserveWindow);
-                mService.mStackSupervisor.ensureActivitiesVisibleLocked(r, 0, !PRESERVE_WINDOWS);
-                if (!kept) {
-                    mService.mStackSupervisor.resumeFocusedStackTopActivityLocked();
+            // Do not move the task to another stack here.
+            // This method assumes that the task is already placed in the right stack.
+            // we do not mess with that decision and we only do the resize!
+
+            Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "am.resizeTask_" + taskId);
+
+            final boolean updatedConfig = updateOverrideConfiguration(bounds);
+            // This variable holds information whether the configuration didn't change in a significant
+
+            // way and the activity was kept the way it was. If it's false, it means the activity
+            // had
+            // to be relaunched due to configuration change.
+            boolean kept = true;
+            if (updatedConfig) {
+                final ActivityRecord r = topRunningActivityLocked();
+                if (r != null && !deferResume) {
+                    kept = r.ensureActivityConfigurationLocked(0 /* globalChanges */,
+                            preserveWindow);
+                    mService.mStackSupervisor.ensureActivitiesVisibleLocked(r, 0,
+                            !PRESERVE_WINDOWS);
+                    if (!kept) {
+                        mService.mStackSupervisor.resumeFocusedStackTopActivityLocked();
+                    }
                 }
             }
-        }
-        mWindowContainerController.resize(mBounds, getOverrideConfiguration(), kept, forced);
+            mWindowContainerController.resize(kept, forced);
 
-        Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
-        return kept;
+            Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
+            return kept;
+        } finally {
+            mService.mWindowManager.continueSurfaceLayout();
+        }
     }
 
     // TODO: Investigate combining with the resize() method above.
     void resizeWindowContainer() {
-        mWindowContainerController.resize(mBounds, getOverrideConfiguration(), false /* relayout */,
-                false /* forced */);
+        mWindowContainerController.resize(false /* relayout */, false /* forced */);
     }
 
     void getWindowContainerBounds(Rect bounds) {
@@ -681,16 +686,17 @@
             // Make sure the task has the appropriate bounds/size for the stack it is in.
             final boolean toStackSplitScreenPrimary =
                     toStackWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+            final Rect configBounds = getOverrideBounds();
             if ((toStackWindowingMode == WINDOWING_MODE_FULLSCREEN
                     || toStackWindowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY)
-                    && !Objects.equals(mBounds, toStack.mBounds)) {
-                kept = resize(toStack.mBounds, RESIZE_MODE_SYSTEM, !mightReplaceWindow,
+                    && !Objects.equals(configBounds, toStack.getOverrideBounds())) {
+                kept = resize(toStack.getOverrideBounds(), RESIZE_MODE_SYSTEM, !mightReplaceWindow,
                         deferResume);
             } else if (toStackWindowingMode == WINDOWING_MODE_FREEFORM) {
                 Rect bounds = getLaunchBounds();
                 if (bounds == null) {
                     mService.mStackSupervisor.getLaunchingBoundsController().layoutTask(this, null);
-                    bounds = mBounds;
+                    bounds = configBounds;
                 }
                 kept = resize(bounds, RESIZE_MODE_FORCED, !mightReplaceWindow, deferResume);
             } else if (toStackSplitScreenPrimary || toStackWindowingMode == WINDOWING_MODE_PINNED) {
@@ -699,7 +705,7 @@
                     // mode
                     mService.mStackSupervisor.moveRecentsStackToFront(reason);
                 }
-                kept = resize(toStack.mBounds, RESIZE_MODE_SYSTEM, !mightReplaceWindow,
+                kept = resize(toStack.getOverrideBounds(), RESIZE_MODE_SYSTEM, !mightReplaceWindow,
                         deferResume);
             }
         } finally {
@@ -962,6 +968,8 @@
             mService.notifyTaskPersisterLocked(this, false);
         }
 
+        clearRootProcess();
+
         // TODO: Use window container controller once tasks are better synced between AM and WM
         mService.mWindowManager.notifyTaskRemovedFromRecents(taskId, userId);
     }
@@ -1303,7 +1311,8 @@
      * Completely remove all activities associated with an existing
      * task starting at a specified index.
      */
-    final void performClearTaskAtIndexLocked(int activityNdx, boolean pauseImmediately) {
+    final void performClearTaskAtIndexLocked(int activityNdx, boolean pauseImmediately,
+            String reason) {
         int numActivities = mActivities.size();
         for ( ; activityNdx < numActivities; ++activityNdx) {
             final ActivityRecord r = mActivities.get(activityNdx);
@@ -1317,7 +1326,7 @@
                 --activityNdx;
                 --numActivities;
             } else if (mStack.finishActivityLocked(r, Activity.RESULT_CANCELED, null,
-                    "clear-task-index", false, pauseImmediately)) {
+                    reason, false, pauseImmediately)) {
                 --activityNdx;
                 --numActivities;
             }
@@ -1329,7 +1338,7 @@
      */
     void performClearTaskLocked() {
         mReuseTask = true;
-        performClearTaskAtIndexLocked(0, !PAUSE_IMMEDIATELY);
+        performClearTaskAtIndexLocked(0, !PAUSE_IMMEDIATELY, "clear-task-all");
         mReuseTask = false;
     }
 
@@ -1400,9 +1409,9 @@
         return null;
     }
 
-    void removeTaskActivitiesLocked(boolean pauseImmediately) {
+    void removeTaskActivitiesLocked(boolean pauseImmediately, String reason) {
         // Just remove the entire task.
-        performClearTaskAtIndexLocked(0, pauseImmediately);
+        performClearTaskAtIndexLocked(0, pauseImmediately, reason);
     }
 
     String lockTaskAuthToString() {
@@ -1494,8 +1503,10 @@
             return true;
         }
         final boolean landscape = bounds.width() > bounds.height();
+        final Rect configBounds = getOverrideBounds();
         if (mResizeMode == RESIZE_MODE_FORCE_RESIZABLE_PRESERVE_ORIENTATION) {
-            return mBounds == null || landscape == (mBounds.width() > mBounds.height());
+            return configBounds.isEmpty()
+                    || landscape == (configBounds.width() > configBounds.height());
         }
         return (mResizeMode != RESIZE_MODE_FORCE_RESIZABLE_PORTRAIT_ONLY || !landscape)
                 && (mResizeMode != RESIZE_MODE_FORCE_RESIZABLE_LANDSCAPE_ONLY || landscape);
@@ -1616,6 +1627,9 @@
         final int effectiveRootIndex = findEffectiveRootIndex();
         final ActivityRecord r = mActivities.get(effectiveRootIndex);
         setIntent(r);
+
+        // Update the task description when the activities change
+        updateTaskDescription();
     }
 
     void saveToXml(XmlSerializer out) throws IOException, XmlPullParserException {
@@ -1913,8 +1927,9 @@
             return;
         }
 
+        final Rect configBounds = getOverrideBounds();
         if (adjustWidth) {
-            if (mBounds != null && bounds.right == mBounds.right) {
+            if (!configBounds.isEmpty() && bounds.right == configBounds.right) {
                 bounds.left = bounds.right - minWidth;
             } else {
                 // Either left bounds match, or neither match, or the previous bounds were
@@ -1923,7 +1938,7 @@
             }
         }
         if (adjustHeight) {
-            if (mBounds != null && bounds.bottom == mBounds.bottom) {
+            if (!configBounds.isEmpty() && bounds.bottom == configBounds.bottom) {
                 bounds.top = bounds.bottom - minHeight;
             } else {
                 // Either top bounds match, or neither match, or the previous bounds were
@@ -1970,42 +1985,45 @@
      * @return True if the override configuration was updated.
      */
     boolean updateOverrideConfiguration(Rect bounds, @Nullable Rect insetBounds) {
-        if (Objects.equals(mBounds, bounds)) {
+        if (equivalentOverrideBounds(bounds)) {
             return false;
         }
+        final Rect currentBounds = getOverrideBounds();
+
         mTmpConfig.setTo(getOverrideConfiguration());
-        final boolean oldFullscreen = mFullscreen;
         final Configuration newConfig = getOverrideConfiguration();
 
-        mFullscreen = bounds == null;
+        final boolean matchParentBounds = bounds == null || bounds.isEmpty();
         final boolean persistBounds = getWindowConfiguration().persistTaskBounds();
-        if (mFullscreen) {
-            if (mBounds != null && persistBounds) {
-                mLastNonFullscreenBounds = mBounds;
+        if (matchParentBounds) {
+            if (!currentBounds.isEmpty() && persistBounds) {
+                mLastNonFullscreenBounds = currentBounds;
             }
-            mBounds = null;
+            setBounds(null);
             newConfig.unset();
         } else {
             mTmpRect.set(bounds);
             adjustForMinimalTaskDimensions(mTmpRect);
-            if (mBounds == null) {
-                mBounds = new Rect(mTmpRect);
-            } else {
-                mBounds.set(mTmpRect);
-            }
+            setBounds(mTmpRect);
+
             if (mStack == null || persistBounds) {
-                mLastNonFullscreenBounds = mBounds;
+                mLastNonFullscreenBounds = getOverrideBounds();
             }
             computeOverrideConfiguration(newConfig, mTmpRect, insetBounds,
                     mTmpRect.right != bounds.right, mTmpRect.bottom != bounds.bottom);
         }
         onOverrideConfigurationChanged(newConfig);
+        return !mTmpConfig.equals(newConfig);
+    }
 
-        if (mFullscreen != oldFullscreen) {
+    @Override
+    public void onConfigurationChanged(Configuration newParentConfig) {
+        final boolean wasInMultiWindowMode = inMultiWindowMode();
+        super.onConfigurationChanged(newParentConfig);
+        if (wasInMultiWindowMode != inMultiWindowMode()) {
             mService.mStackSupervisor.scheduleUpdateMultiWindowMode(this);
         }
-
-        return !mTmpConfig.equals(newConfig);
+        // TODO: Should also take care of Pip mode changes here.
     }
 
     /** Clears passed config and fills it with new override values. */
@@ -2046,23 +2064,19 @@
         final int longSize = Math.max(compatScreenHeightDp, compatScreenWidthDp);
         final int shortSize = Math.min(compatScreenHeightDp, compatScreenWidthDp);
         config.screenLayout = Configuration.reduceScreenLayout(sl, longSize, shortSize);
-
     }
 
     Rect updateOverrideConfigurationFromLaunchBounds() {
-        final Rect bounds = validateBounds(getLaunchBounds());
+        final Rect bounds = getLaunchBounds();
         updateOverrideConfiguration(bounds);
-        if (bounds != null) {
-            bounds.set(mBounds);
+        if (bounds != null && !bounds.isEmpty()) {
+            // TODO: Review if we actually want to do this - we are setting the launch bounds
+            // directly here.
+            bounds.set(getOverrideBounds());
         }
         return bounds;
     }
 
-    static Rect validateBounds(Rect bounds) {
-        // TODO: Not needed once we have bounds in WindowConfiguration.
-        return (bounds != null && bounds.isEmpty()) ? null : bounds;
-    }
-
     /** Updates the task's bounds and override configuration to match what is expected for the
      * input stack. */
     void updateOverrideConfigurationForStack(ActivityStack inStack) {
@@ -2075,7 +2089,7 @@
                 throw new IllegalArgumentException("Can not position non-resizeable task="
                         + this + " in stack=" + inStack);
             }
-            if (mBounds != null) {
+            if (!matchParentBounds()) {
                 return;
             }
             if (mLastNonFullscreenBounds != null) {
@@ -2084,7 +2098,7 @@
                 mService.mStackSupervisor.getLaunchingBoundsController().layoutTask(this, null);
             }
         } else {
-            updateOverrideConfiguration(inStack.mBounds);
+            updateOverrideConfiguration(inStack.getOverrideBounds());
         }
     }
 
@@ -2098,9 +2112,9 @@
         if (!isActivityTypeStandardOrUndefined()
                 || windowingMode == WINDOWING_MODE_FULLSCREEN
                 || (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY && !isResizeable())) {
-            return isResizeable() ? mStack.mBounds : null;
+            return isResizeable() ? mStack.getOverrideBounds() : null;
         } else if (!getWindowConfiguration().persistTaskBounds()) {
-            return mStack.mBounds;
+            return mStack.getOverrideBounds();
         }
         return mLastNonFullscreenBounds;
     }
@@ -2114,6 +2128,22 @@
         }
     }
 
+    void setRootProcess(ProcessRecord proc) {
+        clearRootProcess();
+        if (intent != null &&
+                (intent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) == 0) {
+            mRootProcess = proc;
+            proc.recentTasks.add(this);
+        }
+    }
+
+    void clearRootProcess() {
+        if (mRootProcess != null) {
+            mRootProcess.recentTasks.remove(this);
+            mRootProcess = null;
+        }
+    }
+
     void dump(PrintWriter pw, String prefix) {
         pw.print(prefix); pw.print("userId="); pw.print(userId);
                 pw.print(" effectiveUid="); UserHandle.formatUid(pw, effectiveUid);
@@ -2198,6 +2228,9 @@
         if (lastDescription != null) {
             pw.print(prefix); pw.print("lastDescription="); pw.println(lastDescription);
         }
+        if (mRootProcess != null) {
+            pw.print(prefix); pw.print("mRootProcess="); pw.println(mRootProcess);
+        }
         pw.print(prefix); pw.print("stackId="); pw.println(getStackId());
         pw.print(prefix + "hasBeenVisible=" + hasBeenVisible);
                 pw.print(" mResizeMode=" + ActivityInfo.resizeModeToString(mResizeMode));
@@ -2261,9 +2294,12 @@
         }
         proto.write(ACTIVITY_TYPE, getActivityType());
         proto.write(RESIZE_MODE, mResizeMode);
-        proto.write(FULLSCREEN, mFullscreen);
-        if (mBounds != null) {
-            mBounds.writeToProto(proto, BOUNDS);
+        // TODO: Remove, no longer needed with windowingMode.
+        proto.write(FULLSCREEN, matchParentBounds());
+
+        if (!matchParentBounds()) {
+            final Rect bounds = getOverrideBounds();
+            bounds.writeToProto(proto, BOUNDS);
         }
         proto.write(MIN_WIDTH, mMinWidth);
         proto.write(MIN_HEIGHT, mMinHeight);
diff --git a/com/android/server/am/UnsupportedCompileSdkDialog.java b/com/android/server/am/UnsupportedCompileSdkDialog.java
new file mode 100644
index 0000000..b6f6ae6
--- /dev/null
+++ b/com/android/server/am/UnsupportedCompileSdkDialog.java
@@ -0,0 +1,83 @@
+/*
+ * 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 android.app.AlertDialog;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.CheckBox;
+
+import com.android.internal.R;
+import com.android.server.utils.AppInstallerUtil;
+
+public class UnsupportedCompileSdkDialog {
+    private final AlertDialog mDialog;
+    private final String mPackageName;
+
+    public UnsupportedCompileSdkDialog(final AppWarnings manager, Context context,
+            ApplicationInfo appInfo) {
+        mPackageName = appInfo.packageName;
+
+        final PackageManager pm = context.getPackageManager();
+        final CharSequence label = appInfo.loadSafeLabel(pm);
+        final CharSequence message = context.getString(R.string.unsupported_compile_sdk_message,
+                label);
+
+        final AlertDialog.Builder builder = new AlertDialog.Builder(context)
+                .setPositiveButton(R.string.ok, null)
+                .setMessage(message)
+                .setView(R.layout.unsupported_compile_sdk_dialog_content);
+
+        // If we might be able to update the app, show a button.
+        final Intent installerIntent = AppInstallerUtil.createIntent(context, appInfo.packageName);
+        if (installerIntent != null) {
+                builder.setNeutralButton(R.string.unsupported_compile_sdk_check_update,
+                        (dialog, which) -> context.startActivity(installerIntent));
+        }
+
+        // Ensure the content view is prepared.
+        mDialog = builder.create();
+        mDialog.create();
+
+        final Window window = mDialog.getWindow();
+        window.setType(WindowManager.LayoutParams.TYPE_PHONE);
+
+        // DO NOT MODIFY. Used by CTS to verify the dialog is displayed.
+        window.getAttributes().setTitle("UnsupportedCompileSdkDialog");
+
+        final CheckBox alwaysShow = mDialog.findViewById(R.id.ask_checkbox);
+        alwaysShow.setChecked(true);
+        alwaysShow.setOnCheckedChangeListener((buttonView, isChecked) -> manager.setPackageFlag(
+                mPackageName, AppWarnings.FLAG_HIDE_COMPILE_SDK, !isChecked));
+    }
+
+    public String getPackageName() {
+        return mPackageName;
+    }
+
+    public void show() {
+        mDialog.show();
+    }
+
+    public void dismiss() {
+        mDialog.dismiss();
+    }
+}
diff --git a/com/android/server/am/UnsupportedDisplaySizeDialog.java b/com/android/server/am/UnsupportedDisplaySizeDialog.java
index 501cd6b..8850663 100644
--- a/com/android/server/am/UnsupportedDisplaySizeDialog.java
+++ b/com/android/server/am/UnsupportedDisplaySizeDialog.java
@@ -30,7 +30,7 @@
     private final AlertDialog mDialog;
     private final String mPackageName;
 
-    public UnsupportedDisplaySizeDialog(final ActivityManagerService service, Context context,
+    public UnsupportedDisplaySizeDialog(final AppWarnings manager, Context context,
             ApplicationInfo appInfo) {
         mPackageName = appInfo.packageName;
 
@@ -54,14 +54,10 @@
         // DO NOT MODIFY. Used by CTS to verify the dialog is displayed.
         window.getAttributes().setTitle("UnsupportedDisplaySizeDialog");
 
-        final CheckBox alwaysShow = (CheckBox) mDialog.findViewById(R.id.ask_checkbox);
+        final CheckBox alwaysShow = mDialog.findViewById(R.id.ask_checkbox);
         alwaysShow.setChecked(true);
-        alwaysShow.setOnCheckedChangeListener((buttonView, isChecked) -> {
-            synchronized (service) {
-                service.mCompatModePackages.setPackageNotifyUnsupportedZoomLocked(
-                        mPackageName, isChecked);
-            }
-        });
+        alwaysShow.setOnCheckedChangeListener((buttonView, isChecked) -> manager.setPackageFlag(
+                mPackageName, AppWarnings.FLAG_HIDE_DISPLAY_SIZE, !isChecked));
     }
 
     public String getPackageName() {
diff --git a/com/android/server/am/UriPermissionOwner.java b/com/android/server/am/UriPermissionOwner.java
index 28344df..fc07c1a 100644
--- a/com/android/server/am/UriPermissionOwner.java
+++ b/com/android/server/am/UriPermissionOwner.java
@@ -20,6 +20,9 @@
 import android.os.Binder;
 import android.os.IBinder;
 import android.util.ArraySet;
+import android.util.proto.ProtoOutputStream;
+
+import com.android.server.am.proto.UriPermissionOwnerProto;
 
 import com.google.android.collect.Sets;
 
@@ -139,6 +142,26 @@
         }
     }
 
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+        long token = proto.start(fieldId);
+        proto.write(UriPermissionOwnerProto.OWNER, owner.toString());
+        if (mReadPerms != null) {
+            synchronized (mReadPerms) {
+                for (UriPermission p : mReadPerms) {
+                    p.uri.writeToProto(proto, UriPermissionOwnerProto.READ_PERMS);
+                }
+            }
+        }
+        if (mWritePerms != null) {
+            synchronized (mWritePerms) {
+                for (UriPermission p : mWritePerms) {
+                    p.uri.writeToProto(proto, UriPermissionOwnerProto.WRITE_PERMS);
+                }
+            }
+        }
+        proto.end(token);
+    }
+
     @Override
     public String toString() {
         return owner.toString();
diff --git a/com/android/server/am/UserController.java b/com/android/server/am/UserController.java
index 2df5dc9..4e3d8d2 100644
--- a/com/android/server/am/UserController.java
+++ b/com/android/server/am/UserController.java
@@ -89,6 +89,7 @@
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.Preconditions;
 import com.android.internal.widget.LockPatternUtils;
+import com.android.server.FgThread;
 import com.android.server.LocalServices;
 import com.android.server.SystemServiceManager;
 import com.android.server.pm.UserManagerService;
@@ -355,27 +356,35 @@
         // 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;
-
-            // Do not proceed if unexpected state
-            if (!uss.setState(STATE_RUNNING_LOCKED, STATE_RUNNING_UNLOCKING)) {
+            // Do not proceed if unexpected state or a stale user
+            if (mStartedUsers.get(userId) != uss || uss.state != STATE_RUNNING_LOCKED) {
                 return;
             }
         }
-        mInjector.getUserManagerInternal().setUserState(userId, uss.state);
         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);
 
-        // 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();
+        // Call onBeforeUnlockUser on a worker thread that allows disk I/O
+        FgThread.getHandler().post(() -> {
+            mInjector.getUserManager().onBeforeUnlockUser(userId);
+            synchronized (mLock) {
+                // Do not proceed if unexpected state
+                if (!uss.setState(STATE_RUNNING_LOCKED, STATE_RUNNING_UNLOCKING)) {
+                    return;
+                }
+            }
+            mInjector.getUserManagerInternal().setUserState(userId, uss.state);
+
+            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();
+        });
     }
 
     /**
@@ -1819,7 +1828,10 @@
             case SYSTEM_USER_UNLOCK_MSG:
                 final int userId = msg.arg1;
                 mInjector.getSystemServiceManager().unlockUser(userId);
-                mInjector.loadUserRecents(userId);
+                // Loads recents on a worker thread that allows disk I/O
+                FgThread.getHandler().post(() -> {
+                    mInjector.loadUserRecents(userId);
+                });
                 if (userId == UserHandle.USER_SYSTEM) {
                     mInjector.startPersistentApps(PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
                 }
diff --git a/com/android/server/autofill/RemoteFillService.java b/com/android/server/autofill/RemoteFillService.java
index af55807..831c488 100644
--- a/com/android/server/autofill/RemoteFillService.java
+++ b/com/android/server/autofill/RemoteFillService.java
@@ -26,6 +26,7 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentSender;
 import android.content.ServiceConnection;
 import android.os.Handler;
 import android.os.IBinder;
@@ -100,7 +101,8 @@
                 @NonNull String servicePackageName);
         void onFillRequestFailure(@Nullable CharSequence message,
                 @NonNull String servicePackageName);
-        void onSaveRequestSuccess(@NonNull String servicePackageName);
+        void onSaveRequestSuccess(@NonNull String servicePackageName,
+                @Nullable IntentSender intentSender);
         void onSaveRequestFailure(@Nullable CharSequence message,
                 @NonNull String servicePackageName);
         void onServiceDied(RemoteFillService service);
@@ -308,10 +310,11 @@
         });
     }
 
-    private void dispatchOnSaveRequestSuccess(PendingRequest pendingRequest) {
+    private void dispatchOnSaveRequestSuccess(PendingRequest pendingRequest,
+            IntentSender intentSender) {
         mHandler.getHandler().post(() -> {
             if (handleResponseCallbackCommon(pendingRequest)) {
-                mCallbacks.onSaveRequestSuccess(mComponentName.getPackageName());
+                mCallbacks.onSaveRequestSuccess(mComponentName.getPackageName(), intentSender);
             }
         });
     }
@@ -624,12 +627,13 @@
 
             mCallback = new ISaveCallback.Stub() {
                 @Override
-                public void onSuccess() {
+                public void onSuccess(IntentSender intentSender) {
                     if (!finish()) return;
 
                     final RemoteFillService remoteService = getService();
                     if (remoteService != null) {
-                        remoteService.dispatchOnSaveRequestSuccess(PendingSaveRequest.this);
+                        remoteService.dispatchOnSaveRequestSuccess(PendingSaveRequest.this,
+                                intentSender);
                     }
                 }
 
diff --git a/com/android/server/autofill/Session.java b/com/android/server/autofill/Session.java
index af4668a..99b92b9 100644
--- a/com/android/server/autofill/Session.java
+++ b/com/android/server/autofill/Session.java
@@ -561,7 +561,8 @@
 
     // FillServiceCallbacks
     @Override
-    public void onSaveRequestSuccess(@NonNull String servicePackageName) {
+    public void onSaveRequestSuccess(@NonNull String servicePackageName,
+            @Nullable IntentSender intentSender) {
         synchronized (mLock) {
             mIsSaving = false;
 
@@ -572,8 +573,12 @@
             }
         }
         LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_DATA_SAVE_REQUEST, servicePackageName)
-                .setType(MetricsEvent.TYPE_SUCCESS);
+                .setType(intentSender == null ? MetricsEvent.TYPE_SUCCESS : MetricsEvent.TYPE_OPEN);
         mMetricsLogger.write(log);
+        if (intentSender != null) {
+            if (sDebug) Slog.d(TAG, "Starting intent sender on save()");
+            startIntentSender(intentSender);
+        }
 
         // Nothing left to do...
         removeSelf();
@@ -1167,7 +1172,16 @@
                         break;
                     }
                 }
+
                 value = getSanitizedValue(sanitizers, id, value);
+                if (value == null) {
+                    if (sDebug) {
+                        Slog.d(TAG, "value of required field " + id + " failed sanitization");
+                    }
+                    allRequiredAreNotEmpty = false;
+                    break;
+                }
+                viewState.setSanitizedValue(value);
                 currentValues.put(id, value);
                 final AutofillValue filledValue = viewState.getAutofilledValue();
 
@@ -1337,7 +1351,7 @@
         return sanitizers;
     }
 
-    @NonNull
+    @Nullable
     private AutofillValue getSanitizedValue(
             @Nullable ArrayMap<AutofillId, InternalSanitizer> sanitizers,
             @NonNull AutofillId id,
@@ -1431,10 +1445,10 @@
             if (sVerbose) Slog.v(TAG, "callSaveLocked(): updating " + context);
 
             for (int viewStateNum = 0; viewStateNum < mViewStates.size(); viewStateNum++) {
-                final ViewState state = mViewStates.valueAt(viewStateNum);
+                final ViewState viewState = mViewStates.valueAt(viewStateNum);
 
-                final AutofillId id = state.id;
-                final AutofillValue value = state.getCurrentValue();
+                final AutofillId id = viewState.id;
+                final AutofillValue value = viewState.getCurrentValue();
                 if (value == null) {
                     if (sVerbose) Slog.v(TAG, "callSaveLocked(): skipping " + id);
                     continue;
@@ -1446,9 +1460,17 @@
                 }
                 if (sVerbose) Slog.v(TAG, "callSaveLocked(): updating " + id + " to " + value);
 
-                final AutofillValue sanitizedValue = getSanitizedValue(sanitizers, id, value);
+                AutofillValue sanitizedValue = viewState.getSanitizedValue();
 
-                node.updateAutofillValue(sanitizedValue);
+                if (sanitizedValue == null) {
+                    // Field is optional and haven't been sanitized yet.
+                    sanitizedValue = getSanitizedValue(sanitizers, id, value);
+                }
+                if (sanitizedValue != null) {
+                    node.updateAutofillValue(sanitizedValue);
+                } else if (sDebug) {
+                    Slog.d(TAG, "Not updating field " + id + " because it failed sanitization");
+                }
             }
 
             // Sanitize structure before it's sent to service.
diff --git a/com/android/server/autofill/ViewState.java b/com/android/server/autofill/ViewState.java
index 832a66b..0dbdc13 100644
--- a/com/android/server/autofill/ViewState.java
+++ b/com/android/server/autofill/ViewState.java
@@ -76,6 +76,7 @@
     private FillResponse mResponse;
     private AutofillValue mCurrentValue;
     private AutofillValue mAutofilledValue;
+    private AutofillValue mSanitizedValue;
     private Rect mVirtualBounds;
     private int mState;
     private String mDatasetId;
@@ -117,6 +118,15 @@
     }
 
     @Nullable
+    AutofillValue getSanitizedValue() {
+        return mSanitizedValue;
+    }
+
+    void setSanitizedValue(@Nullable AutofillValue value) {
+        mSanitizedValue = value;
+    }
+
+    @Nullable
     FillResponse getResponse() {
         return mResponse;
     }
@@ -218,6 +228,7 @@
         }
         pw.print(prefix); pw.print("currentValue:" ); pw.println(mCurrentValue);
         pw.print(prefix); pw.print("autofilledValue:" ); pw.println(mAutofilledValue);
+        pw.print(prefix); pw.print("sanitizedValue:" ); pw.println(mSanitizedValue);
         pw.print(prefix); pw.print("virtualBounds:" ); pw.println(mVirtualBounds);
     }
 }
\ No newline at end of file
diff --git a/com/android/server/backup/RefactoredBackupManagerService.java b/com/android/server/backup/RefactoredBackupManagerService.java
index a45a4f0..2788218 100644
--- a/com/android/server/backup/RefactoredBackupManagerService.java
+++ b/com/android/server/backup/RefactoredBackupManagerService.java
@@ -48,7 +48,6 @@
 import android.app.backup.IFullBackupRestoreObserver;
 import android.app.backup.IRestoreSession;
 import android.app.backup.ISelectBackupTransportCallback;
-import android.app.backup.SelectBackupTransportCallback;
 import android.content.ActivityNotFoundException;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -103,6 +102,7 @@
 import com.android.server.backup.internal.BackupHandler;
 import com.android.server.backup.internal.BackupRequest;
 import com.android.server.backup.internal.ClearDataObserver;
+import com.android.server.backup.internal.OnTaskFinishedListener;
 import com.android.server.backup.internal.Operation;
 import com.android.server.backup.internal.PerformInitializeTask;
 import com.android.server.backup.internal.ProvisionedObserver;
@@ -117,6 +117,7 @@
 import com.android.server.backup.params.RestoreParams;
 import com.android.server.backup.restore.ActiveRestoreSession;
 import com.android.server.backup.restore.PerformUnifiedRestoreTask;
+import com.android.server.backup.transport.TransportClient;
 import com.android.server.backup.utils.AppBackupUtils;
 import com.android.server.backup.utils.BackupManagerMonitorUtils;
 import com.android.server.backup.utils.BackupObserverUtils;
@@ -1585,8 +1586,27 @@
             return BackupManager.ERROR_BACKUP_NOT_ALLOWED;
         }
 
+        // We're using pieces of the new binding on-demand infra-structure and the old always-bound
+        // infra-structure below this comment. The TransportManager.getCurrentTransportClient() line
+        // is using the new one and TransportManager.getCurrentTransportBinder() is using the old.
+        // This is weird but there is a reason.
+        // This is the natural place to put TransportManager.getCurrentTransportClient() because of
+        // the null handling below that should be the same for TransportClient.
+        // TransportClient.connect() would return a IBackupTransport for us (instead of using the
+        // old infra), but it may block and we don't want this in this thread.
+        // The only usage of transport in this method is for transport.transportDirName(). When the
+        // push-from-transport part of binding on-demand is in place we will replace the calls for
+        // IBackupTransport.transportDirName() with calls for
+        // TransportManager.transportDirName(transportName) or similar. So we'll leave the old piece
+        // here until we implement that.
+        // TODO(brufino): Remove always-bound code mTransportManager.getCurrentTransportBinder()
+        TransportClient transportClient =
+                mTransportManager.getCurrentTransportClient("BMS.requestBackup()");
         IBackupTransport transport = mTransportManager.getCurrentTransportBinder();
-        if (transport == null) {
+        if (transportClient == null || transport == null) {
+            if (transportClient != null) {
+                mTransportManager.disposeOfTransportClient(transportClient, "BMS.requestBackup()");
+            }
             BackupObserverUtils.sendBackupFinished(observer, BackupManager.ERROR_TRANSPORT_ABORTED);
             monitor = BackupManagerMonitorUtils.monitorEvent(monitor,
                     BackupManagerMonitor.LOG_EVENT_ID_TRANSPORT_IS_NULL,
@@ -1594,6 +1614,9 @@
             return BackupManager.ERROR_TRANSPORT_ABORTED;
         }
 
+        OnTaskFinishedListener listener =
+                caller -> mTransportManager.disposeOfTransportClient(transportClient, caller);
+
         ArrayList<String> fullBackupList = new ArrayList<>();
         ArrayList<String> kvBackupList = new ArrayList<>();
         for (String packageName : packages) {
@@ -1640,8 +1663,8 @@
         boolean nonIncrementalBackup = (flags & BackupManager.FLAG_NON_INCREMENTAL_BACKUP) != 0;
 
         Message msg = mBackupHandler.obtainMessage(MSG_REQUEST_BACKUP);
-        msg.obj = new BackupParams(transport, dirName, kvBackupList, fullBackupList, observer,
-                monitor, true, nonIncrementalBackup);
+        msg.obj = new BackupParams(transportClient, dirName, kvBackupList, fullBackupList, observer,
+                monitor, listener, true, nonIncrementalBackup);
         mBackupHandler.sendMessage(msg);
         return BackupManager.SUCCESS;
     }
@@ -2135,8 +2158,17 @@
             mFullBackupQueue.remove(0);
             CountDownLatch latch = new CountDownLatch(1);
             String[] pkg = new String[]{entry.packageName};
-            mRunningFullBackupTask = new PerformFullTransportBackupTask(this, null, pkg, true,
-                    scheduledJob, latch, null, null, false /* userInitiated */);
+            mRunningFullBackupTask = PerformFullTransportBackupTask.newWithCurrentTransport(
+                    this,
+                    /* observer */ null,
+                    pkg,
+                    /* updateSchedule */ true,
+                    scheduledJob,
+                    latch,
+                    /* backupObserver */ null,
+                    /* monitor */ null,
+                    /* userInitiated */ false,
+                    "BMS.beginFullBackup()");
             // Acquiring wakelock for PerformFullTransportBackupTask before its start.
             mWakelock.acquire();
             (new Thread(mRunningFullBackupTask)).start();
@@ -2490,8 +2522,17 @@
             final long oldId = Binder.clearCallingIdentity();
             try {
                 CountDownLatch latch = new CountDownLatch(1);
-                PerformFullTransportBackupTask task = new PerformFullTransportBackupTask(this, null,
-                        pkgNames, false, null, latch, null, null, false /* userInitiated */);
+                Runnable task = PerformFullTransportBackupTask.newWithCurrentTransport(
+                        this,
+                        /* observer */ null,
+                        pkgNames,
+                        /* updateSchedule */ false,
+                        /* runningJob */ null,
+                        latch,
+                        /* backupObserver */ null,
+                        /* monitor */ null,
+                        /* userInitiated */ false,
+                        "BMS.fullTransportBackup()");
                 // Acquiring wakelock for PerformFullTransportBackupTask before its start.
                 mWakelock.acquire();
                 (new Thread(task, "full-transport-master")).start();
diff --git a/com/android/server/backup/TransportManager.java b/com/android/server/backup/TransportManager.java
index 7a0173f..a2b5cb8 100644
--- a/com/android/server/backup/TransportManager.java
+++ b/com/android/server/backup/TransportManager.java
@@ -16,8 +16,9 @@
 
 package com.android.server.backup;
 
+import android.annotation.Nullable;
 import android.app.backup.BackupManager;
-import android.app.backup.SelectBackupTransportCallback;
+import android.app.backup.BackupTransport;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -44,9 +45,11 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.backup.IBackupTransport;
 import com.android.server.EventLogTags;
+import com.android.server.backup.transport.TransportClient;
+import com.android.server.backup.transport.TransportClientManager;
+import com.android.server.backup.transport.TransportConnectionListener;
 
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -60,8 +63,7 @@
     private static final String TAG = "BackupTransportManager";
 
     @VisibleForTesting
-    /* package */ static final String SERVICE_ACTION_TRANSPORT_HOST =
-            "android.backup.TRANSPORT_HOST";
+    public static final String SERVICE_ACTION_TRANSPORT_HOST = "android.backup.TRANSPORT_HOST";
 
     private static final long REBINDING_TIMEOUT_UNPROVISIONED_MS = 30 * 1000; // 30 sec
     private static final long REBINDING_TIMEOUT_PROVISIONED_MS = 5 * 60 * 1000; // 5 mins
@@ -72,6 +74,7 @@
     private final PackageManager mPackageManager;
     private final Set<ComponentName> mTransportWhitelist;
     private final Handler mHandler;
+    private final TransportClientManager mTransportClientManager;
 
     /**
      * This listener is called after we bind to any transport. If it returns true, this is a valid
@@ -95,6 +98,10 @@
     @GuardedBy("mTransportLock")
     private final Map<String, ComponentName> mBoundTransports = new ArrayMap<>();
 
+    /** Names of transports we've bound to at least once */
+    @GuardedBy("mTransportLock")
+    private final Map<String, ComponentName> mTransportsByName = new ArrayMap<>();
+
     /**
      * Callback interface for {@link #ensureTransportReady(ComponentName, TransportReadyCallback)}.
      */
@@ -123,6 +130,7 @@
         mCurrentTransportName = defaultTransport;
         mTransportBoundListener = listener;
         mHandler = new RebindOnTimeoutHandler(looper);
+        mTransportClientManager = new TransportClientManager(context);
     }
 
     void onPackageAdded(String packageName) {
@@ -204,6 +212,67 @@
         return null;
     }
 
+    /**
+     * Returns the transport name associated with {@param transportClient} or {@code null} if not
+     * found.
+     */
+    @Nullable
+    public String getTransportName(TransportClient transportClient) {
+        ComponentName transportComponent = transportClient.getTransportComponent();
+        synchronized (mTransportLock) {
+            for (Map.Entry<String, ComponentName> transportEntry : mTransportsByName.entrySet()) {
+                if (transportEntry.getValue().equals(transportComponent)) {
+                    return transportEntry.getKey();
+                }
+            }
+            return null;
+        }
+    }
+
+    /**
+     * Returns a {@link TransportClient} for {@param transportName} or {@code null} if not found.
+     *
+     * @param transportName The name of the transport as returned by {@link BackupTransport#name()}.
+     * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
+     *     {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more
+     *     details.
+     * @return A {@link TransportClient} or null if not found.
+     */
+    @Nullable
+    public TransportClient getTransportClient(String transportName, String caller) {
+        ComponentName transportComponent = mTransportsByName.get(transportName);
+        if (transportComponent == null) {
+            Slog.w(TAG, "Transport " + transportName + " not registered");
+            return null;
+        }
+        return mTransportClientManager.getTransportClient(transportComponent, caller);
+    }
+
+    /**
+     * Returns a {@link TransportClient} for the current transport or null if not found.
+     *
+     * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
+     *     {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more
+     *     details.
+     * @return A {@link TransportClient} or null if not found.
+     */
+    @Nullable
+    public TransportClient getCurrentTransportClient(String caller) {
+        return getTransportClient(mCurrentTransportName, caller);
+    }
+
+    /**
+     * Disposes of the {@link TransportClient}.
+     *
+     * @param transportClient The {@link TransportClient} to be disposed of.
+     * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
+     *     {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more
+     *     details.
+     */
+    public void disposeOfTransportClient(TransportClient transportClient, String caller) {
+        mTransportClientManager.disposeOfTransportClient(transportClient, caller);
+    }
+
     String[] getBoundTransportNames() {
         synchronized (mTransportLock) {
             return mBoundTransports.keySet().toArray(new String[mBoundTransports.size()]);
@@ -374,6 +443,7 @@
                     String componentShortString = component.flattenToShortString().intern();
                     if (success) {
                         Slog.d(TAG, "Bound to transport: " + componentShortString);
+                        mTransportsByName.put(mTransportName, component);
                         mBoundTransports.put(mTransportName, component);
                         for (TransportReadyCallback listener : mListeners) {
                             listener.onSuccess(mTransportName);
@@ -528,7 +598,7 @@
     // These only exists to make it testable with Robolectric, which is not updated to API level 24
     // yet.
     // TODO: Get rid of this once Robolectric is updated.
-    private static UserHandle createSystemUserHandle() {
+    public static UserHandle createSystemUserHandle() {
         return new UserHandle(UserHandle.USER_SYSTEM);
     }
 }
diff --git a/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java b/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
index 90134e1..d5b3d98 100644
--- a/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
+++ b/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
@@ -24,6 +24,7 @@
 import static com.android.server.backup.RefactoredBackupManagerService.OP_TYPE_BACKUP_WAIT;
 import static com.android.server.backup.RefactoredBackupManagerService.TIMEOUT_FULL_BACKUP_INTERVAL;
 
+import android.annotation.Nullable;
 import android.app.IBackupAgent;
 import android.app.backup.BackupManager;
 import android.app.backup.BackupManagerMonitor;
@@ -46,7 +47,11 @@
 import com.android.server.backup.BackupRestoreTask;
 import com.android.server.backup.FullBackupJob;
 import com.android.server.backup.RefactoredBackupManagerService;
+import com.android.server.backup.TransportManager;
+import com.android.server.backup.internal.OnTaskFinishedListener;
 import com.android.server.backup.internal.Operation;
+import com.android.server.backup.transport.TransportClient;
+import com.android.server.backup.transport.TransportNotAvailableException;
 import com.android.server.backup.utils.AppBackupUtils;
 import com.android.server.backup.utils.BackupManagerMonitorUtils;
 import com.android.server.backup.utils.BackupObserverUtils;
@@ -89,6 +94,36 @@
  *       mBackupRunner.getBackupResultBlocking().
  */
 public class PerformFullTransportBackupTask extends FullBackupTask implements BackupRestoreTask {
+    public static PerformFullTransportBackupTask newWithCurrentTransport(
+            RefactoredBackupManagerService backupManagerService,
+            IFullBackupRestoreObserver observer,
+            String[] whichPackages,
+            boolean updateSchedule,
+            FullBackupJob runningJob,
+            CountDownLatch latch,
+            IBackupObserver backupObserver,
+            IBackupManagerMonitor monitor,
+            boolean userInitiated,
+            String caller) {
+        TransportManager transportManager = backupManagerService.getTransportManager();
+        TransportClient transportClient = transportManager.getCurrentTransportClient(caller);
+        OnTaskFinishedListener listener =
+                listenerCaller ->
+                        transportManager.disposeOfTransportClient(transportClient, listenerCaller);
+        return new PerformFullTransportBackupTask(
+                backupManagerService,
+                transportClient,
+                observer,
+                whichPackages,
+                updateSchedule,
+                runningJob,
+                latch,
+                backupObserver,
+                monitor,
+                listener,
+                userInitiated);
+    }
+
     private static final String TAG = "PFTBT";
 
     private RefactoredBackupManagerService backupManagerService;
@@ -102,9 +137,10 @@
     IBackupObserver mBackupObserver;
     IBackupManagerMonitor mMonitor;
     boolean mUserInitiated;
-    private volatile IBackupTransport mTransport;
     SinglePackageBackupRunner mBackupRunner;
     private final int mBackupRunnerOpToken;
+    private final OnTaskFinishedListener mListener;
+    private final TransportClient mTransportClient;
 
     // This is true when a backup operation for some package is in progress.
     private volatile boolean mIsDoingBackup;
@@ -112,18 +148,22 @@
     private final int mCurrentOpToken;
 
     public PerformFullTransportBackupTask(RefactoredBackupManagerService backupManagerService,
+            TransportClient transportClient,
             IFullBackupRestoreObserver observer,
             String[] whichPackages, boolean updateSchedule,
             FullBackupJob runningJob, CountDownLatch latch, IBackupObserver backupObserver,
-            IBackupManagerMonitor monitor, boolean userInitiated) {
+            IBackupManagerMonitor monitor, @Nullable OnTaskFinishedListener listener,
+            boolean userInitiated) {
         super(observer);
         this.backupManagerService = backupManagerService;
+        mTransportClient = transportClient;
         mUpdateSchedule = updateSchedule;
         mLatch = latch;
         mJob = runningJob;
         mPackages = new ArrayList<>(whichPackages.length);
         mBackupObserver = backupObserver;
         mMonitor = monitor;
+        mListener = (listener != null) ? listener : OnTaskFinishedListener.NOP;
         mUserInitiated = userInitiated;
         mCurrentOpToken = backupManagerService.generateRandomIntegerToken();
         mBackupRunnerOpToken = backupManagerService.generateRandomIntegerToken();
@@ -241,8 +281,11 @@
             if (mIsDoingBackup) {
                 backupManagerService.handleCancel(mBackupRunnerOpToken, cancelAll);
                 try {
-                    mTransport.cancelFullBackup();
-                } catch (RemoteException e) {
+                    // If we're running a backup we should be connected to a transport
+                    IBackupTransport transport =
+                            mTransportClient.getConnectedTransport("PFTBT.handleCancel()");
+                    transport.cancelFullBackup();
+                } catch (RemoteException | TransportNotAvailableException e) {
                     Slog.w(TAG, "Error calling cancelFullBackup() on transport: " + e);
                     // Can't do much.
                 }
@@ -291,8 +334,8 @@
                 return;
             }
 
-            mTransport = backupManagerService.getTransportManager().getCurrentTransportBinder();
-            if (mTransport == null) {
+            IBackupTransport transport = mTransportClient.connect("PFTBT.run()");
+            if (transport == null) {
                 Slog.w(TAG, "Transport not present; full data backup not performed");
                 backupRunStatus = BackupManager.ERROR_TRANSPORT_ABORTED;
                 mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
@@ -325,17 +368,17 @@
                     if (mCancelAll) {
                         break;
                     }
-                    backupPackageStatus = mTransport.performFullBackup(currentPackage,
+                    backupPackageStatus = transport.performFullBackup(currentPackage,
                             transportPipes[0], flags);
 
                     if (backupPackageStatus == BackupTransport.TRANSPORT_OK) {
-                        quota = mTransport.getBackupQuota(currentPackage.packageName,
+                        quota = transport.getBackupQuota(currentPackage.packageName,
                                 true /* isFullBackup */);
                         // Now set up the backup engine / data source end of things
                         enginePipes = ParcelFileDescriptor.createPipe();
                         mBackupRunner =
                                 new SinglePackageBackupRunner(enginePipes[1], currentPackage,
-                                        mTransport, quota, mBackupRunnerOpToken);
+                                        mTransportClient, quota, mBackupRunnerOpToken);
                         // The runner dup'd the pipe half, so we close it here
                         enginePipes[1].close();
                         enginePipes[1] = null;
@@ -389,7 +432,7 @@
                                 out.write(buffer, 0, nRead);
                                 synchronized (mCancelLock) {
                                     if (!mCancelAll) {
-                                        backupPackageStatus = mTransport.sendBackupData(nRead);
+                                        backupPackageStatus = transport.sendBackupData(nRead);
                                     }
                                 }
                                 totalRead += nRead;
@@ -425,12 +468,12 @@
                                 // result based on what finishBackup() returns.  If we're in a
                                 // failure case already, preserve that result and ignore whatever
                                 // finishBackup() reports.
-                                final int finishResult = mTransport.finishBackup();
+                                final int finishResult = transport.finishBackup();
                                 if (backupPackageStatus == BackupTransport.TRANSPORT_OK) {
                                     backupPackageStatus = finishResult;
                                 }
                             } else {
-                                mTransport.cancelFullBackup();
+                                transport.cancelFullBackup();
                             }
                         }
                     }
@@ -469,7 +512,7 @@
 
                     // Also ask the transport how long it wants us to wait before
                     // moving on to the next package, if any.
-                    backoff = mTransport.requestFullBackupTime();
+                    backoff = transport.requestFullBackupTime();
                     if (DEBUG_SCHEDULING) {
                         Slog.i(TAG, "Transport suggested backoff=" + backoff);
                     }
@@ -591,6 +634,8 @@
                 backupManagerService.setRunningFullBackupTask(null);
             }
 
+            mListener.onFinished("PFTBT.run()");
+
             mLatch.countDown();
 
             // Now that we're actually done with schedule-driven work, reschedule
@@ -633,12 +678,13 @@
     class SinglePackageBackupPreflight implements BackupRestoreTask, FullBackupPreflight {
         final AtomicLong mResult = new AtomicLong(BackupTransport.AGENT_ERROR);
         final CountDownLatch mLatch = new CountDownLatch(1);
-        final IBackupTransport mTransport;
+        final TransportClient mTransportClient;
         final long mQuota;
         private final int mCurrentOpToken;
 
-        SinglePackageBackupPreflight(IBackupTransport transport, long quota, int currentOpToken) {
-            mTransport = transport;
+        SinglePackageBackupPreflight(
+                TransportClient transportClient, long quota, int currentOpToken) {
+            mTransportClient = transportClient;
             mQuota = quota;
             mCurrentOpToken = currentOpToken;
         }
@@ -672,7 +718,9 @@
                     Slog.v(TAG, "Got preflight response; size=" + totalSize);
                 }
 
-                result = mTransport.checkFullBackupSize(totalSize);
+                IBackupTransport transport =
+                        mTransportClient.connectOrThrow("PFTBT$SPBP.preflightFullBackup()");
+                result = transport.checkFullBackupSize(totalSize);
                 if (result == BackupTransport.TRANSPORT_QUOTA_EXCEEDED) {
                     if (MORE_DEBUG) {
                         Slog.d(TAG, "Package hit quota limit on preflight " +
@@ -739,12 +787,13 @@
         private volatile boolean mIsCancelled;
 
         SinglePackageBackupRunner(ParcelFileDescriptor output, PackageInfo target,
-                IBackupTransport transport, long quota, int currentOpToken) throws IOException {
+                TransportClient transportClient, long quota, int currentOpToken)
+                throws IOException {
             mOutput = ParcelFileDescriptor.dup(output.getFileDescriptor());
             mTarget = target;
             mCurrentOpToken = currentOpToken;
             mEphemeralToken = backupManagerService.generateRandomIntegerToken();
-            mPreflight = new SinglePackageBackupPreflight(transport, quota, mEphemeralToken);
+            mPreflight = new SinglePackageBackupPreflight(transportClient, quota, mEphemeralToken);
             mPreflightLatch = new CountDownLatch(1);
             mBackupLatch = new CountDownLatch(1);
             mPreflightResult = BackupTransport.AGENT_ERROR;
diff --git a/com/android/server/backup/internal/BackupHandler.java b/com/android/server/backup/internal/BackupHandler.java
index 8f82300..9011b95 100644
--- a/com/android/server/backup/internal/BackupHandler.java
+++ b/com/android/server/backup/internal/BackupHandler.java
@@ -38,6 +38,8 @@
 import com.android.server.backup.BackupRestoreTask;
 import com.android.server.backup.DataChangedJournal;
 import com.android.server.backup.RefactoredBackupManagerService;
+import com.android.server.backup.transport.TransportClient;
+import com.android.server.backup.TransportManager;
 import com.android.server.backup.fullbackup.PerformAdbBackupTask;
 import com.android.server.backup.fullbackup.PerformFullTransportBackupTask;
 import com.android.server.backup.params.AdbBackupParams;
@@ -51,10 +53,8 @@
 import com.android.server.backup.restore.PerformAdbRestoreTask;
 import com.android.server.backup.restore.PerformUnifiedRestoreTask;
 
-import java.io.File;
 import java.util.ArrayList;
 import java.util.Collections;
-import java.util.HashSet;
 
 /**
  * Asynchronous backup/restore handler thread.
@@ -81,7 +81,7 @@
     public static final int MSG_BACKUP_RESTORE_STEP = 20;
     public static final int MSG_OP_COMPLETE = 21;
 
-    private RefactoredBackupManagerService backupManagerService;
+    private final RefactoredBackupManagerService backupManagerService;
 
     public BackupHandler(
             RefactoredBackupManagerService backupManagerService, Looper looper) {
@@ -91,13 +91,23 @@
 
     public void handleMessage(Message msg) {
 
+        TransportManager transportManager = backupManagerService.getTransportManager();
         switch (msg.what) {
             case MSG_RUN_BACKUP: {
                 backupManagerService.setLastBackupPass(System.currentTimeMillis());
 
+                String callerLogString = "BH/MSG_RUN_BACKUP";
+                TransportClient transportClient =
+                        transportManager.getCurrentTransportClient(callerLogString);
                 IBackupTransport transport =
-                        backupManagerService.getTransportManager().getCurrentTransportBinder();
+                        transportClient != null
+                                ? transportClient.connect(callerLogString)
+                                : null;
                 if (transport == null) {
+                    if (transportClient != null) {
+                        transportManager
+                                .disposeOfTransportClient(transportClient, callerLogString);
+                    }
                     Slog.v(TAG, "Backup requested but no transport available");
                     synchronized (backupManagerService.getQueueLock()) {
                         backupManagerService.setBackupRunning(false);
@@ -138,9 +148,13 @@
                     // Spin up a backup state sequence and set it running
                     try {
                         String dirName = transport.transportDirName();
+                        OnTaskFinishedListener listener =
+                                caller ->
+                                        transportManager
+                                                .disposeOfTransportClient(transportClient, caller);
                         PerformBackupTask pbt = new PerformBackupTask(
-                                backupManagerService, transport, dirName, queue,
-                                oldJournal, null, null, Collections.<String>emptyList(), false,
+                                backupManagerService, transportClient, dirName, queue,
+                                oldJournal, null, null, listener, Collections.emptyList(), false,
                                 false /* nonIncremental */);
                         Message pbtMessage = obtainMessage(MSG_BACKUP_RESTORE_STEP, pbt);
                         sendMessage(pbtMessage);
@@ -157,6 +171,7 @@
                 }
 
                 if (!staged) {
+                    transportManager.disposeOfTransportClient(transportClient, callerLogString);
                     // if we didn't actually hand off the wakelock, rewind until next time
                     synchronized (backupManagerService.getQueueLock()) {
                         backupManagerService.setBackupRunning(false);
@@ -382,9 +397,9 @@
 
                 PerformBackupTask pbt = new PerformBackupTask(
                         backupManagerService,
-                        params.transport, params.dirName,
-                        kvQueue, null, params.observer, params.monitor, params.fullPackages, true,
-                        params.nonIncrementalBackup);
+                        params.transportClient, params.dirName,
+                        kvQueue, null, params.observer, params.monitor, params.listener,
+                        params.fullPackages, true, params.nonIncrementalBackup);
                 Message pbtMessage = obtainMessage(MSG_BACKUP_RESTORE_STEP, pbt);
                 sendMessage(pbtMessage);
                 break;
diff --git a/com/android/server/backup/internal/OnTaskFinishedListener.java b/com/android/server/backup/internal/OnTaskFinishedListener.java
new file mode 100644
index 0000000..e417f06
--- /dev/null
+++ b/com/android/server/backup/internal/OnTaskFinishedListener.java
@@ -0,0 +1,34 @@
+/*
+ * 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.backup.internal;
+
+import com.android.server.backup.transport.TransportClient;
+import com.android.server.backup.transport.TransportConnectionListener;
+
+/** Listener to be called when a task finishes, successfully or not. */
+public interface OnTaskFinishedListener {
+    OnTaskFinishedListener NOP = caller -> {};
+
+    /**
+     * Called when a task finishes, successfully or not.
+     *
+     * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
+     *     {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more
+     *     details.
+     */
+    void onFinished(String caller);
+}
diff --git a/com/android/server/backup/internal/PerformBackupTask.java b/com/android/server/backup/internal/PerformBackupTask.java
index c0caa55..5be1b39 100644
--- a/com/android/server/backup/internal/PerformBackupTask.java
+++ b/com/android/server/backup/internal/PerformBackupTask.java
@@ -63,6 +63,8 @@
 import com.android.server.backup.PackageManagerBackupAgent;
 import com.android.server.backup.RefactoredBackupManagerService;
 import com.android.server.backup.fullbackup.PerformFullTransportBackupTask;
+import com.android.server.backup.transport.TransportClient;
+import com.android.server.backup.transport.TransportUtils;
 import com.android.server.backup.utils.AppBackupUtils;
 import com.android.server.backup.utils.BackupManagerMonitorUtils;
 import com.android.server.backup.utils.BackupObserverUtils;
@@ -112,7 +114,6 @@
     private RefactoredBackupManagerService backupManagerService;
     private final Object mCancelLock = new Object();
 
-    IBackupTransport mTransport;
     ArrayList<BackupRequest> mQueue;
     ArrayList<BackupRequest> mOriginalQueue;
     File mStateDir;
@@ -122,6 +123,8 @@
     IBackupObserver mObserver;
     IBackupManagerMonitor mMonitor;
 
+    private final TransportClient mTransportClient;
+    private final OnTaskFinishedListener mListener;
     private final PerformFullTransportBackupTask mFullBackupTask;
     private final int mCurrentOpToken;
     private volatile int mEphemeralOpToken;
@@ -143,17 +146,19 @@
     private volatile boolean mCancelAll;
 
     public PerformBackupTask(RefactoredBackupManagerService backupManagerService,
-            IBackupTransport transport, String dirName,
+            TransportClient transportClient, String dirName,
             ArrayList<BackupRequest> queue, @Nullable DataChangedJournal journal,
             IBackupObserver observer, IBackupManagerMonitor monitor,
-            List<String> pendingFullBackups, boolean userInitiated, boolean nonIncremental) {
+            @Nullable OnTaskFinishedListener listener, List<String> pendingFullBackups,
+            boolean userInitiated, boolean nonIncremental) {
         this.backupManagerService = backupManagerService;
-        mTransport = transport;
+        mTransportClient = transportClient;
         mOriginalQueue = queue;
         mQueue = new ArrayList<>();
         mJournal = journal;
         mObserver = observer;
         mMonitor = monitor;
+        mListener = (listener != null) ? listener : OnTaskFinishedListener.NOP;
         mPendingFullBackups = pendingFullBackups;
         mUserInitiated = userInitiated;
         mNonIncremental = nonIncremental;
@@ -179,11 +184,11 @@
                         mPendingFullBackups.toArray(new String[mPendingFullBackups.size()]);
                 mFullBackupTask =
                         new PerformFullTransportBackupTask(backupManagerService,
-                                /*fullBackupRestoreObserver*/
-                                null,
+                                transportClient,
+                                /*fullBackupRestoreObserver*/ null,
                                 fullBackups, /*updateSchedule*/ false, /*runningJob*/ null,
                                 latch,
-                                mObserver, mMonitor, mUserInitiated);
+                                mObserver, mMonitor, mListener, mUserInitiated);
 
                 registerTask();
                 backupManagerService.addBackupTrace("STATE => INITIAL");
@@ -289,10 +294,10 @@
         if (DEBUG) {
             Slog.v(TAG, "Beginning backup of " + mQueue.size() + " targets");
         }
-
         File pmState = new File(mStateDir, PACKAGE_MANAGER_SENTINEL);
         try {
-            final String transportName = mTransport.transportDirName();
+            IBackupTransport transport = mTransportClient.connectOrThrow("PBT.beginBackup()");
+            final String transportName = transport.transportDirName();
             EventLog.writeEvent(EventLogTags.BACKUP_START, transportName);
 
             // If we haven't stored package manager metadata yet, we must init the transport.
@@ -300,7 +305,7 @@
                 Slog.i(TAG, "Initializing (wiping) backup state and transport storage");
                 backupManagerService.addBackupTrace("initializing transport " + transportName);
                 backupManagerService.resetBackupState(mStateDir);  // Just to make sure.
-                mStatus = mTransport.initializeDevice();
+                mStatus = transport.initializeDevice();
 
                 backupManagerService.addBackupTrace("transport.initializeDevice() == " + mStatus);
                 if (mStatus == BackupTransport.TRANSPORT_OK) {
@@ -324,7 +329,7 @@
                     PackageManagerBackupAgent pmAgent = backupManagerService.makeMetadataAgent();
                     mStatus = invokeAgentForBackup(
                             PACKAGE_MANAGER_SENTINEL,
-                            IBackupAgent.Stub.asInterface(pmAgent.onBind()), mTransport);
+                            IBackupAgent.Stub.asInterface(pmAgent.onBind()));
                     backupManagerService.addBackupTrace("PMBA invoke: " + mStatus);
 
                     // Because the PMBA is a local instance, it has already executed its
@@ -445,7 +450,7 @@
                 backupManagerService.addBackupTrace("agent bound; a? = " + (agent != null));
                 if (agent != null) {
                     mAgentBinder = agent;
-                    mStatus = invokeAgentForBackup(request.packageName, agent, mTransport);
+                    mStatus = invokeAgentForBackup(request.packageName, agent);
                     // at this point we'll either get a completion callback from the
                     // agent, or a timeout message on the main handler.  either way, we're
                     // done here as long as we're successful so far.
@@ -526,11 +531,14 @@
         // If everything actually went through and this is the first time we've
         // done a backup, we can now record what the current backup dataset token
         // is.
+        String callerLogString = "PBT.finalizeBackup()";
         if ((backupManagerService.getCurrentToken() == 0) && (mStatus
                 == BackupTransport.TRANSPORT_OK)) {
             backupManagerService.addBackupTrace("success; recording token");
             try {
-                backupManagerService.setCurrentToken(mTransport.getCurrentRestoreSet());
+                IBackupTransport transport =
+                        mTransportClient.connectOrThrow(callerLogString);
+                backupManagerService.setCurrentToken(transport.getCurrentRestoreSet());
                 backupManagerService.writeRestoreTokens();
             } catch (Exception e) {
                 // nothing for it at this point, unfortunately, but this will be
@@ -553,13 +561,13 @@
                 backupManagerService.addBackupTrace("init required; rerunning");
                 try {
                     final String name = backupManagerService.getTransportManager().getTransportName(
-                            mTransport);
+                            mTransportClient);
                     if (name != null) {
                         backupManagerService.getPendingInits().add(name);
                     } else {
                         if (DEBUG) {
-                            Slog.w(TAG, "Couldn't find name of transport " + mTransport
-                                    + " for init");
+                            Slog.w(TAG, "Couldn't find name of transport "
+                                    + mTransportClient.getTransportComponent() + " for init");
                         }
                     }
                 } catch (Exception e) {
@@ -580,14 +588,18 @@
             Slog.d(TAG, "Starting full backups for: " + mPendingFullBackups);
             // Acquiring wakelock for PerformFullTransportBackupTask before its start.
             backupManagerService.getWakelock().acquire();
+            // The full-backup task is now responsible for calling onFinish() on mListener, which
+            // was the listener we passed it.
             (new Thread(mFullBackupTask, "full-transport-requested")).start();
         } else if (mCancelAll) {
+            mListener.onFinished(callerLogString);
             if (mFullBackupTask != null) {
                 mFullBackupTask.unregisterTask();
             }
             BackupObserverUtils.sendBackupFinished(mObserver,
                     BackupManager.ERROR_BACKUP_CANCELLED);
         } else {
+            mListener.onFinished(callerLogString);
             mFullBackupTask.unregisterTask();
             switch (mStatus) {
                 case BackupTransport.TRANSPORT_OK:
@@ -619,8 +631,7 @@
 
     // Invoke an agent's doBackup() and start a timeout message spinning on the main
     // handler in case it doesn't get back to us.
-    int invokeAgentForBackup(String packageName, IBackupAgent agent,
-            IBackupTransport transport) {
+    int invokeAgentForBackup(String packageName, IBackupAgent agent) {
         if (DEBUG) {
             Slog.d(TAG, "invokeAgentForBackup on " + packageName);
         }
@@ -671,7 +682,10 @@
                             ParcelFileDescriptor.MODE_CREATE |
                             ParcelFileDescriptor.MODE_TRUNCATE);
 
-            final long quota = mTransport.getBackupQuota(packageName, false /* isFullBackup */);
+            IBackupTransport transport =
+                    mTransportClient.connectOrThrow("PBT.invokeAgentForBackup()");
+
+            final long quota = transport.getBackupQuota(packageName, false /* isFullBackup */);
             callingAgent = true;
 
             // Initiate the target's backup pass
@@ -888,10 +902,12 @@
             clearAgentState();
             backupManagerService.addBackupTrace("operation complete");
 
+            IBackupTransport transport = mTransportClient.connect("PBT.operationComplete()");
             ParcelFileDescriptor backupData = null;
             mStatus = BackupTransport.TRANSPORT_OK;
             long size = 0;
             try {
+                TransportUtils.checkTransport(transport);
                 size = mBackupDataName.length();
                 if (size > 0) {
                     if (mStatus == BackupTransport.TRANSPORT_OK) {
@@ -899,7 +915,7 @@
                                 ParcelFileDescriptor.MODE_READ_ONLY);
                         backupManagerService.addBackupTrace("sending data to transport");
                         int flags = mUserInitiated ? BackupTransport.FLAG_USER_INITIATED : 0;
-                        mStatus = mTransport.performBackup(mCurrentPackage, backupData, flags);
+                        mStatus = transport.performBackup(mCurrentPackage, backupData, flags);
                     }
 
                     // TODO - We call finishBackup() for each application backed up, because
@@ -910,7 +926,7 @@
                     backupManagerService.addBackupTrace("data delivered: " + mStatus);
                     if (mStatus == BackupTransport.TRANSPORT_OK) {
                         backupManagerService.addBackupTrace("finishing op on transport");
-                        mStatus = mTransport.finishBackup();
+                        mStatus = transport.finishBackup();
                         backupManagerService.addBackupTrace("finished: " + mStatus);
                     } else if (mStatus == BackupTransport.TRANSPORT_PACKAGE_REJECTED) {
                         backupManagerService.addBackupTrace("transport rejected package");
@@ -981,8 +997,8 @@
                 }
                 if (mAgentBinder != null) {
                     try {
-                        long quota = mTransport.getBackupQuota(mCurrentPackage.packageName,
-                                false);
+                        TransportUtils.checkTransport(transport);
+                        long quota = transport.getBackupQuota(mCurrentPackage.packageName, false);
                         mAgentBinder.doQuotaExceeded(size, quota);
                     } catch (Exception e) {
                         Slog.e(TAG, "Unable to notify about quota exceeded: " + e.getMessage());
@@ -1052,7 +1068,9 @@
         // by way of retry/backoff time.
         long delay;
         try {
-            delay = mTransport.requestBackupTime();
+            IBackupTransport transport =
+                    mTransportClient.connectOrThrow("PBT.revertAndEndBackup()");
+            delay = transport.requestBackupTime();
         } catch (Exception e) {
             Slog.w(TAG, "Unable to contact transport for recommended backoff: " + e.getMessage());
             delay = 0;  // use the scheduler's default
diff --git a/com/android/server/backup/params/BackupParams.java b/com/android/server/backup/params/BackupParams.java
index 4fd7ddb..2ba8ec1 100644
--- a/com/android/server/backup/params/BackupParams.java
+++ b/com/android/server/backup/params/BackupParams.java
@@ -19,30 +19,34 @@
 import android.app.backup.IBackupManagerMonitor;
 import android.app.backup.IBackupObserver;
 
-import com.android.internal.backup.IBackupTransport;
+import com.android.server.backup.internal.OnTaskFinishedListener;
+import com.android.server.backup.transport.TransportClient;
 
 import java.util.ArrayList;
 
 public class BackupParams {
 
-    public IBackupTransport transport;
+    public TransportClient transportClient;
     public String dirName;
     public ArrayList<String> kvPackages;
     public ArrayList<String> fullPackages;
     public IBackupObserver observer;
     public IBackupManagerMonitor monitor;
+    public OnTaskFinishedListener listener;
     public boolean userInitiated;
     public boolean nonIncrementalBackup;
 
-    public BackupParams(IBackupTransport transport, String dirName, ArrayList<String> kvPackages,
-            ArrayList<String> fullPackages, IBackupObserver observer,
-            IBackupManagerMonitor monitor, boolean userInitiated, boolean nonIncrementalBackup) {
-        this.transport = transport;
+    public BackupParams(TransportClient transportClient, String dirName,
+            ArrayList<String> kvPackages, ArrayList<String> fullPackages, IBackupObserver observer,
+            IBackupManagerMonitor monitor, OnTaskFinishedListener listener, boolean userInitiated,
+            boolean nonIncrementalBackup) {
+        this.transportClient = transportClient;
         this.dirName = dirName;
         this.kvPackages = kvPackages;
         this.fullPackages = fullPackages;
         this.observer = observer;
         this.monitor = monitor;
+        this.listener = listener;
         this.userInitiated = userInitiated;
         this.nonIncrementalBackup = nonIncrementalBackup;
     }
diff --git a/com/android/server/backup/transport/TransportClient.java b/com/android/server/backup/transport/TransportClient.java
new file mode 100644
index 0000000..65f9502
--- /dev/null
+++ b/com/android/server/backup/transport/TransportClient.java
@@ -0,0 +1,487 @@
+/*
+ * 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.backup.transport;
+
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.annotation.WorkerThread;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.DeadObjectException;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.backup.IBackupTransport;
+import com.android.internal.util.Preconditions;
+import com.android.server.backup.TransportManager;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+
+/**
+ * A {@link TransportClient} manages the connection to an {@link IBackupTransport} service, obtained
+ * via the {@param bindIntent} parameter provided in the constructor. A {@link TransportClient} is
+ * responsible for only one connection to the transport service, not more.
+ *
+ * <p>After retrieved using {@link TransportManager#getTransportClient(String, String)}, you can
+ * call either {@link #connect(String)}, if you can block your thread, or {@link
+ * #connectAsync(TransportConnectionListener, String)}, otherwise, to obtain a {@link
+ * IBackupTransport} instance. It's meant to be passed around as a token to a connected transport.
+ * When the connection is not needed anymore you should call {@link #unbind(String)} or indirectly
+ * via {@link TransportManager#disposeOfTransportClient(TransportClient, String)}.
+ *
+ * <p>DO NOT forget to unbind otherwise there will be dangling connections floating around.
+ *
+ * <p>This class is thread-safe.
+ *
+ * @see TransportManager
+ */
+public class TransportClient {
+    private static final String TAG = "TransportClient";
+
+    private final Context mContext;
+    private final Intent mBindIntent;
+    private final String mIdentifier;
+    private final ComponentName mTransportComponent;
+    private final Handler mListenerHandler;
+    private final String mPrefixForLog;
+    private final Object mStateLock = new Object();
+
+    @GuardedBy("mStateLock")
+    private final Map<TransportConnectionListener, String> mListeners = new ArrayMap<>();
+
+    @GuardedBy("mStateLock")
+    @State
+    private int mState = State.IDLE;
+
+    @GuardedBy("mStateLock")
+    private volatile IBackupTransport mTransport;
+
+    TransportClient(
+            Context context,
+            Intent bindIntent,
+            ComponentName transportComponent,
+            String identifier) {
+        this(context, bindIntent, transportComponent, identifier, Handler.getMain());
+    }
+
+    @VisibleForTesting
+    TransportClient(
+            Context context,
+            Intent bindIntent,
+            ComponentName transportComponent,
+            String identifier,
+            Handler listenerHandler) {
+        mContext = context;
+        mTransportComponent = transportComponent;
+        mBindIntent = bindIntent;
+        mIdentifier = identifier;
+        mListenerHandler = listenerHandler;
+
+        // For logging
+        String classNameForLog = mTransportComponent.getShortClassName().replaceFirst(".*\\.", "");
+        mPrefixForLog = classNameForLog + "#" + mIdentifier + ": ";
+    }
+
+    public ComponentName getTransportComponent() {
+        return mTransportComponent;
+    }
+
+    // Calls to onServiceDisconnected() or onBindingDied() turn TransportClient UNUSABLE. After one
+    // of these calls, if a binding happen again the new service can be a different instance. Since
+    // transports are stateful, we don't want a new instance responding for an old instance's state.
+    private ServiceConnection mConnection =
+            new ServiceConnection() {
+                @Override
+                public void onServiceConnected(ComponentName componentName, IBinder binder) {
+                    IBackupTransport transport = IBackupTransport.Stub.asInterface(binder);
+                    synchronized (mStateLock) {
+                        checkStateIntegrityLocked();
+
+                        if (mState != State.UNUSABLE) {
+                            log(Log.DEBUG, "Transport connected");
+                            setStateLocked(State.CONNECTED, transport);
+                            notifyListenersAndClearLocked(transport);
+                        }
+                    }
+                }
+
+                @Override
+                public void onServiceDisconnected(ComponentName componentName) {
+                    synchronized (mStateLock) {
+                        log(Log.ERROR, "Service disconnected: client UNUSABLE");
+                        setStateLocked(State.UNUSABLE, null);
+                        // After unbindService() no calls back to mConnection
+                        mContext.unbindService(this);
+                    }
+                }
+
+                @Override
+                public void onBindingDied(ComponentName name) {
+                    synchronized (mStateLock) {
+                        checkStateIntegrityLocked();
+
+                        log(Log.ERROR, "Binding died: client UNUSABLE");
+                        // After unbindService() no calls back to mConnection
+                        switch (mState) {
+                            case State.UNUSABLE:
+                                break;
+                            case State.IDLE:
+                                log(Log.ERROR, "Unexpected state transition IDLE => UNUSABLE");
+                                setStateLocked(State.UNUSABLE, null);
+                                break;
+                            case State.BOUND_AND_CONNECTING:
+                                setStateLocked(State.UNUSABLE, null);
+                                mContext.unbindService(this);
+                                notifyListenersAndClearLocked(null);
+                                break;
+                            case State.CONNECTED:
+                                setStateLocked(State.UNUSABLE, null);
+                                mContext.unbindService(this);
+                                break;
+                        }
+                    }
+                }
+            };
+
+    /**
+     * Attempts to connect to the transport (if needed).
+     *
+     * <p>Note that being bound is not the same as connected. To be connected you also need to be
+     * bound. You go from nothing to bound, then to bound and connected. To have a usable transport
+     * binder instance you need to be connected. This method will attempt to connect and return an
+     * usable transport binder regardless of the state of the object, it may already be connected,
+     * or bound but not connected, not bound at all or even unusable.
+     *
+     * <p>So, a {@link Context#bindServiceAsUser(Intent, ServiceConnection, int, UserHandle)} (or
+     * one of its variants) can be called or not depending on the inner state. However, it won't be
+     * called again if we're already bound. For example, if one was already requested but the
+     * framework has not yet returned (meaning we're bound but still trying to connect) it won't
+     * trigger another one, just piggyback on the original request.
+     *
+     * <p>It's guaranteed that you are going to get a call back to {@param listener} after this
+     * call. However, the {@param IBackupTransport} parameter, the transport binder, is not
+     * guaranteed to be non-null, or if it's non-null it's not guaranteed to be usable - i.e. it can
+     * throw {@link DeadObjectException}s on method calls. You should check for both in your code.
+     * The reasons for a null transport binder are:
+     *
+     * <ul>
+     *   <li>Some code called {@link #unbind(String)} before you got a callback.
+     *   <li>The framework had already called {@link
+     *       ServiceConnection#onServiceDisconnected(ComponentName)} or {@link
+     *       ServiceConnection#onBindingDied(ComponentName)} on this object's connection before.
+     *       Check the documentation of those methods for when that happens.
+     *   <li>The framework returns false for {@link Context#bindServiceAsUser(Intent,
+     *       ServiceConnection, int, UserHandle)} (or one of its variants). Check documentation for
+     *       when this happens.
+     * </ul>
+     *
+     * For unusable transport binders check {@link DeadObjectException}.
+     *
+     * @param listener The listener that will be called with the (possibly null or unusable) {@link
+     *     IBackupTransport} instance and this {@link TransportClient} object.
+     * @param caller A {@link String} identifying the caller for logging/debugging purposes. This
+     *     should be a human-readable short string that is easily identifiable in the logs. Ideally
+     *     TAG.methodName(), where TAG is the one used in logcat. In cases where this is is not very
+     *     descriptive like MyHandler.handleMessage() you should put something that someone reading
+     *     the code would understand, like MyHandler/MSG_FOO.
+     * @see #connect(String)
+     * @see DeadObjectException
+     * @see ServiceConnection#onServiceConnected(ComponentName, IBinder)
+     * @see ServiceConnection#onServiceDisconnected(ComponentName)
+     * @see Context#bindServiceAsUser(Intent, ServiceConnection, int, UserHandle)
+     */
+    public void connectAsync(TransportConnectionListener listener, String caller) {
+        synchronized (mStateLock) {
+            checkStateIntegrityLocked();
+
+            switch (mState) {
+                case State.UNUSABLE:
+                    log(Log.DEBUG, caller, "Async connect: UNUSABLE client");
+                    notifyListener(listener, null, caller);
+                    break;
+                case State.IDLE:
+                    boolean hasBound =
+                            mContext.bindServiceAsUser(
+                                    mBindIntent,
+                                    mConnection,
+                                    Context.BIND_AUTO_CREATE,
+                                    TransportManager.createSystemUserHandle());
+                    if (hasBound) {
+                        // We don't need to set a time-out because we are guaranteed to get a call
+                        // back in ServiceConnection, either an onServiceConnected() or
+                        // onBindingDied().
+                        log(Log.DEBUG, caller, "Async connect: service bound, connecting");
+                        setStateLocked(State.BOUND_AND_CONNECTING, null);
+                        mListeners.put(listener, caller);
+                    } else {
+                        log(Log.ERROR, "Async connect: bindService returned false");
+                        // mState remains State.IDLE
+                        mContext.unbindService(mConnection);
+                        notifyListener(listener, null, caller);
+                    }
+                    break;
+                case State.BOUND_AND_CONNECTING:
+                    log(Log.DEBUG, caller, "Async connect: already connecting, adding listener");
+                    mListeners.put(listener, caller);
+                    break;
+                case State.CONNECTED:
+                    log(Log.DEBUG, caller, "Async connect: reusing transport");
+                    notifyListener(listener, mTransport, caller);
+                    break;
+            }
+        }
+    }
+
+    /**
+     * Removes the transport binding.
+     *
+     * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
+     *     {@link #connectAsync(TransportConnectionListener, String)} for more details.
+     */
+    public void unbind(String caller) {
+        synchronized (mStateLock) {
+            checkStateIntegrityLocked();
+
+            log(Log.DEBUG, caller, "Unbind requested (was " + stateToString(mState) + ")");
+            switch (mState) {
+                case State.UNUSABLE:
+                case State.IDLE:
+                    break;
+                case State.BOUND_AND_CONNECTING:
+                    setStateLocked(State.IDLE, null);
+                    // After unbindService() no calls back to mConnection
+                    mContext.unbindService(mConnection);
+                    notifyListenersAndClearLocked(null);
+                    break;
+                case State.CONNECTED:
+                    setStateLocked(State.IDLE, null);
+                    mContext.unbindService(mConnection);
+                    break;
+            }
+        }
+    }
+
+    /**
+     * Attempts to connect to the transport (if needed) and returns it.
+     *
+     * <p>Synchronous version of {@link #connectAsync(TransportConnectionListener, String)}. The
+     * same observations about state are valid here. Also, what was said about the {@link
+     * IBackupTransport} parameter of {@link TransportConnectionListener} now apply to the return
+     * value of this method.
+     *
+     * <p>This is a potentially blocking operation, so be sure to call this carefully on the correct
+     * threads. You can't call this from the process main-thread (it throws an exception if you do
+     * so).
+     *
+     * <p>In most cases only the first call to this method will block, the following calls should
+     * return instantly. However, this is not guaranteed.
+     *
+     * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
+     *     {@link #connectAsync(TransportConnectionListener, String)} for more details.
+     * @return A {@link IBackupTransport} transport binder instance or null. If it's non-null it can
+     *     still be unusable - throws {@link DeadObjectException} on method calls
+     */
+    @WorkerThread
+    @Nullable
+    public IBackupTransport connect(String caller) {
+        // If called on the main-thread this could deadlock waiting because calls to
+        // ServiceConnection are on the main-thread as well
+        Preconditions.checkState(
+                !Looper.getMainLooper().isCurrentThread(), "Can't call connect() on main thread");
+
+        IBackupTransport transport = mTransport;
+        if (transport != null) {
+            log(Log.DEBUG, caller, "Sync connect: reusing transport");
+            return transport;
+        }
+
+        // If it's already UNUSABLE we return straight away, no need to go to main-thread
+        synchronized (mStateLock) {
+            if (mState == State.UNUSABLE) {
+                log(Log.DEBUG, caller, "Sync connect: UNUSABLE client");
+                return null;
+            }
+        }
+
+        CompletableFuture<IBackupTransport> transportFuture = new CompletableFuture<>();
+        TransportConnectionListener requestListener =
+                (requestedTransport, transportClient) ->
+                        transportFuture.complete(requestedTransport);
+
+        log(Log.DEBUG, caller, "Sync connect: calling async");
+        connectAsync(requestListener, caller);
+
+        try {
+            return transportFuture.get();
+        } catch (InterruptedException | ExecutionException e) {
+            String error = e.getClass().getSimpleName();
+            log(Log.ERROR, caller, error + " while waiting for transport: " + e.getMessage());
+            return null;
+        }
+    }
+
+    /**
+     * Tries to connect to the transport, if it fails throws {@link TransportNotAvailableException}.
+     *
+     * <p>Same as {@link #connect(String)} except it throws instead of returning null.
+     *
+     * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
+     *     {@link #connectAsync(TransportConnectionListener, String)} for more details.
+     * @return A {@link IBackupTransport} transport binder instance.
+     * @see #connect(String)
+     * @throws TransportNotAvailableException if connection attempt fails.
+     */
+    @WorkerThread
+    public IBackupTransport connectOrThrow(String caller) throws TransportNotAvailableException {
+        IBackupTransport transport = connect(caller);
+        if (transport == null) {
+            log(Log.ERROR, caller, "Transport connection failed");
+            throw new TransportNotAvailableException();
+        }
+        return transport;
+    }
+
+    /**
+     * If the {@link TransportClient} is already connected to the transport, returns the transport,
+     * otherwise throws {@link TransportNotAvailableException}.
+     *
+     * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
+     *     {@link #connectAsync(TransportConnectionListener, String)} for more details.
+     * @return A {@link IBackupTransport} transport binder instance.
+     * @throws TransportNotAvailableException if not connected.
+     */
+    public IBackupTransport getConnectedTransport(String caller)
+            throws TransportNotAvailableException {
+        IBackupTransport transport = mTransport;
+        if (transport == null) {
+            log(Log.ERROR, caller, "Transport not connected");
+            throw new TransportNotAvailableException();
+        }
+        return transport;
+    }
+
+    @Override
+    public String toString() {
+        return "TransportClient{"
+                + mTransportComponent.flattenToShortString()
+                + "#"
+                + mIdentifier
+                + "}";
+    }
+
+    private void notifyListener(
+            TransportConnectionListener listener, IBackupTransport transport, String caller) {
+        log(Log.VERBOSE, caller, "Notifying listener of transport = " + transport);
+        mListenerHandler.post(() -> listener.onTransportConnectionResult(transport, this));
+    }
+
+    @GuardedBy("mStateLock")
+    private void notifyListenersAndClearLocked(IBackupTransport transport) {
+        for (Map.Entry<TransportConnectionListener, String> entry : mListeners.entrySet()) {
+            TransportConnectionListener listener = entry.getKey();
+            String caller = entry.getValue();
+            notifyListener(listener, transport, caller);
+        }
+        mListeners.clear();
+    }
+
+    @GuardedBy("mStateLock")
+    private void setStateLocked(@State int state, @Nullable IBackupTransport transport) {
+        log(Log.VERBOSE, "State: " + stateToString(mState) + " => " + stateToString(state));
+        mState = state;
+        mTransport = transport;
+    }
+
+    @GuardedBy("mStateLock")
+    private void checkStateIntegrityLocked() {
+        switch (mState) {
+            case State.UNUSABLE:
+                checkState(mListeners.isEmpty(), "Unexpected listeners when state = UNUSABLE");
+                checkState(
+                        mTransport == null, "Transport expected to be null when state = UNUSABLE");
+            case State.IDLE:
+                checkState(mListeners.isEmpty(), "Unexpected listeners when state = IDLE");
+                checkState(mTransport == null, "Transport expected to be null when state = IDLE");
+                break;
+            case State.BOUND_AND_CONNECTING:
+                checkState(
+                        mTransport == null,
+                        "Transport expected to be null when state = BOUND_AND_CONNECTING");
+                break;
+            case State.CONNECTED:
+                checkState(mListeners.isEmpty(), "Unexpected listeners when state = CONNECTED");
+                checkState(
+                        mTransport != null,
+                        "Transport expected to be non-null when state = CONNECTED");
+                break;
+            default:
+                checkState(false, "Unexpected state = " + stateToString(mState));
+        }
+    }
+
+    private void checkState(boolean assertion, String message) {
+        if (!assertion) {
+            log(Log.ERROR, message);
+        }
+    }
+
+    private String stateToString(@State int state) {
+        switch (state) {
+            case State.UNUSABLE:
+                return "UNUSABLE";
+            case State.IDLE:
+                return "IDLE";
+            case State.BOUND_AND_CONNECTING:
+                return "BOUND_AND_CONNECTING";
+            case State.CONNECTED:
+                return "CONNECTED";
+            default:
+                return "<UNKNOWN = " + state + ">";
+        }
+    }
+
+    private void log(int priority, String message) {
+        TransportUtils.log(priority, TAG, message);
+    }
+
+    private void log(int priority, String caller, String msg) {
+        TransportUtils.log(priority, TAG, mPrefixForLog, caller, msg);
+        // TODO(brufino): Log in internal list for dump
+        // CharSequence time = DateFormat.format("yyyy-MM-dd HH:mm:ss", System.currentTimeMillis());
+    }
+
+    @IntDef({State.UNUSABLE, State.IDLE, State.BOUND_AND_CONNECTING, State.CONNECTED})
+    @Retention(RetentionPolicy.SOURCE)
+    private @interface State {
+        int UNUSABLE = 0;
+        int IDLE = 1;
+        int BOUND_AND_CONNECTING = 2;
+        int CONNECTED = 3;
+    }
+}
diff --git a/com/android/server/backup/transport/TransportClientManager.java b/com/android/server/backup/transport/TransportClientManager.java
new file mode 100644
index 0000000..1cbe747
--- /dev/null
+++ b/com/android/server/backup/transport/TransportClientManager.java
@@ -0,0 +1,83 @@
+/*
+ * 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.backup.transport;
+
+import static com.android.server.backup.TransportManager.SERVICE_ACTION_TRANSPORT_HOST;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+import com.android.server.backup.TransportManager;
+
+/**
+ * Manages the creation and disposal of {@link TransportClient}s. The only class that should use
+ * this is {@link TransportManager}, all the other usages should go to {@link TransportManager}.
+ *
+ * <p>TODO(brufino): Implement pool of TransportClients
+ */
+public class TransportClientManager {
+    private static final String TAG = "TransportClientManager";
+
+    private final Context mContext;
+    private final Object mTransportClientsLock = new Object();
+    private int mTransportClientsCreated = 0;
+
+    public TransportClientManager(Context context) {
+        mContext = context;
+    }
+
+    /**
+     * Retrieves a {@link TransportClient} for the transport identified by {@param
+     * transportComponent}.
+     *
+     * @param transportComponent The {@link ComponentName} of the transport.
+     * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
+     *     {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more
+     *     details.
+     * @return A {@link TransportClient}.
+     */
+    public TransportClient getTransportClient(ComponentName transportComponent, String caller) {
+        Intent bindIntent =
+                new Intent(SERVICE_ACTION_TRANSPORT_HOST).setComponent(transportComponent);
+        synchronized (mTransportClientsLock) {
+            TransportClient transportClient =
+                    new TransportClient(
+                            mContext,
+                            bindIntent,
+                            transportComponent,
+                            Integer.toString(mTransportClientsCreated));
+            mTransportClientsCreated++;
+            TransportUtils.log(Log.DEBUG, TAG, caller, "Retrieving " + transportClient);
+            return transportClient;
+        }
+    }
+
+    /**
+     * Disposes of the {@link TransportClient}.
+     *
+     * @param transportClient The {@link TransportClient} to be disposed of.
+     * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
+     *     {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more
+     *     details.
+     */
+    public void disposeOfTransportClient(TransportClient transportClient, String caller) {
+        TransportUtils.log(Log.DEBUG, TAG, caller, "Disposing of " + transportClient);
+        transportClient.unbind(caller);
+    }
+}
diff --git a/com/android/server/backup/transport/TransportClientTest.java b/com/android/server/backup/transport/TransportClientTest.java
new file mode 100644
index 0000000..54d233a
--- /dev/null
+++ b/com/android/server/backup/transport/TransportClientTest.java
@@ -0,0 +1,240 @@
+/*
+ * 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.backup.transport;
+
+import static com.android.server.backup.TransportManager.SERVICE_ACTION_TRANSPORT_HOST;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.robolectric.Shadows.shadowOf;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.platform.test.annotations.Presubmit;
+
+import com.android.internal.backup.IBackupTransport;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowLooper;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(manifest = Config.NONE, sdk = 23)
+@Presubmit
+public class TransportClientTest {
+    private static final String PACKAGE_NAME = "some.package.name";
+    private static final ComponentName TRANSPORT_COMPONENT =
+            new ComponentName(PACKAGE_NAME, PACKAGE_NAME + ".transport.Transport");
+
+    @Mock private Context mContext;
+    @Mock private TransportConnectionListener mTransportConnectionListener;
+    @Mock private TransportConnectionListener mTransportConnectionListener2;
+    @Mock private IBackupTransport.Stub mIBackupTransport;
+    private TransportClient mTransportClient;
+    private Intent mBindIntent;
+    private ShadowLooper mShadowLooper;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        Looper mainLooper = Looper.getMainLooper();
+        mShadowLooper = shadowOf(mainLooper);
+        mBindIntent = new Intent(SERVICE_ACTION_TRANSPORT_HOST).setComponent(TRANSPORT_COMPONENT);
+        mTransportClient =
+                new TransportClient(
+                        mContext, mBindIntent, TRANSPORT_COMPONENT, "1", new Handler(mainLooper));
+
+        when(mContext.bindServiceAsUser(
+                        eq(mBindIntent),
+                        any(ServiceConnection.class),
+                        anyInt(),
+                        any(UserHandle.class)))
+                .thenReturn(true);
+    }
+
+    // TODO: Testing implementation? Remove?
+    @Test
+    public void testConnectAsync_callsBindService() throws Exception {
+        mTransportClient.connectAsync(mTransportConnectionListener, "caller");
+
+        verify(mContext)
+                .bindServiceAsUser(
+                        eq(mBindIntent),
+                        any(ServiceConnection.class),
+                        anyInt(),
+                        any(UserHandle.class));
+    }
+
+    @Test
+    public void testConnectAsync_callsListenerWhenConnected() throws Exception {
+        mTransportClient.connectAsync(mTransportConnectionListener, "caller");
+
+        // Simulate framework connecting
+        ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
+        connection.onServiceConnected(TRANSPORT_COMPONENT, mIBackupTransport);
+
+        mShadowLooper.runToEndOfTasks();
+        verify(mTransportConnectionListener)
+                .onTransportConnectionResult(any(IBackupTransport.class), eq(mTransportClient));
+    }
+
+    @Test
+    public void testConnectAsync_whenPendingConnection_callsAllListenersWhenConnected()
+            throws Exception {
+        mTransportClient.connectAsync(mTransportConnectionListener, "caller1");
+        ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
+
+        mTransportClient.connectAsync(mTransportConnectionListener2, "caller2");
+
+        connection.onServiceConnected(TRANSPORT_COMPONENT, mIBackupTransport);
+
+        mShadowLooper.runToEndOfTasks();
+        verify(mTransportConnectionListener)
+                .onTransportConnectionResult(any(IBackupTransport.class), eq(mTransportClient));
+        verify(mTransportConnectionListener2)
+                .onTransportConnectionResult(any(IBackupTransport.class), eq(mTransportClient));
+    }
+
+    @Test
+    public void testConnectAsync_whenAlreadyConnected_callsListener() throws Exception {
+        mTransportClient.connectAsync(mTransportConnectionListener, "caller1");
+        ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
+        connection.onServiceConnected(TRANSPORT_COMPONENT, mIBackupTransport);
+
+        mTransportClient.connectAsync(mTransportConnectionListener2, "caller2");
+
+        mShadowLooper.runToEndOfTasks();
+        verify(mTransportConnectionListener2)
+                .onTransportConnectionResult(any(IBackupTransport.class), eq(mTransportClient));
+    }
+
+    @Test
+    public void testConnectAsync_whenFrameworkDoesntBind_callsListener() throws Exception {
+        when(mContext.bindServiceAsUser(
+                        eq(mBindIntent),
+                        any(ServiceConnection.class),
+                        anyInt(),
+                        any(UserHandle.class)))
+                .thenReturn(false);
+
+        mTransportClient.connectAsync(mTransportConnectionListener, "caller");
+
+        mShadowLooper.runToEndOfTasks();
+        verify(mTransportConnectionListener)
+                .onTransportConnectionResult(isNull(), eq(mTransportClient));
+    }
+
+    @Test
+    public void testConnectAsync_whenFrameworkDoesntBind_releasesConnection() throws Exception {
+        when(mContext.bindServiceAsUser(
+                        eq(mBindIntent),
+                        any(ServiceConnection.class),
+                        anyInt(),
+                        any(UserHandle.class)))
+                .thenReturn(false);
+
+        mTransportClient.connectAsync(mTransportConnectionListener, "caller");
+
+        ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
+        verify(mContext).unbindService(eq(connection));
+    }
+
+    @Test
+    public void testConnectAsync_afterServiceDisconnectedBeforeNewConnection_callsListener()
+            throws Exception {
+        mTransportClient.connectAsync(mTransportConnectionListener, "caller1");
+        ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
+        connection.onServiceConnected(TRANSPORT_COMPONENT, mIBackupTransport);
+        connection.onServiceDisconnected(TRANSPORT_COMPONENT);
+
+        mTransportClient.connectAsync(mTransportConnectionListener2, "caller1");
+
+        verify(mTransportConnectionListener2)
+                .onTransportConnectionResult(isNull(), eq(mTransportClient));
+    }
+
+    @Test
+    public void testConnectAsync_afterServiceDisconnectedAfterNewConnection_callsListener()
+            throws Exception {
+        mTransportClient.connectAsync(mTransportConnectionListener, "caller1");
+        ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
+        connection.onServiceConnected(TRANSPORT_COMPONENT, mIBackupTransport);
+        connection.onServiceDisconnected(TRANSPORT_COMPONENT);
+        connection.onServiceConnected(TRANSPORT_COMPONENT, mIBackupTransport);
+
+        mTransportClient.connectAsync(mTransportConnectionListener2, "caller1");
+
+        // Yes, it should return null because the object became unusable, check design doc
+        verify(mTransportConnectionListener2)
+                .onTransportConnectionResult(isNull(), eq(mTransportClient));
+    }
+
+    // TODO(b/69153972): Support SDK 26 API (ServiceConnection.inBindingDied) for transport tests
+    /*@Test
+    public void testConnectAsync_callsListenerIfBindingDies() throws Exception {
+        mTransportClient.connectAsync(mTransportListener, "caller");
+
+        ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
+        connection.onBindingDied(TRANSPORT_COMPONENT);
+
+        mShadowLooper.runToEndOfTasks();
+        verify(mTransportListener).onTransportBound(isNull(), eq(mTransportClient));
+    }
+
+    @Test
+    public void testConnectAsync_whenPendingConnection_callsListenersIfBindingDies()
+            throws Exception {
+        mTransportClient.connectAsync(mTransportListener, "caller1");
+        ServiceConnection connection = verifyBindServiceAsUserAndCaptureServiceConnection(mContext);
+
+        mTransportClient.connectAsync(mTransportListener2, "caller2");
+
+        connection.onBindingDied(TRANSPORT_COMPONENT);
+
+        mShadowLooper.runToEndOfTasks();
+        verify(mTransportListener).onTransportBound(isNull(), eq(mTransportClient));
+        verify(mTransportListener2).onTransportBound(isNull(), eq(mTransportClient));
+    }*/
+
+    private ServiceConnection verifyBindServiceAsUserAndCaptureServiceConnection(Context context) {
+        ArgumentCaptor<ServiceConnection> connectionCaptor =
+                ArgumentCaptor.forClass(ServiceConnection.class);
+        verify(context)
+                .bindServiceAsUser(
+                        any(Intent.class),
+                        connectionCaptor.capture(),
+                        anyInt(),
+                        any(UserHandle.class));
+        return connectionCaptor.getValue();
+    }
+}
diff --git a/com/android/server/backup/transport/TransportConnectionListener.java b/com/android/server/backup/transport/TransportConnectionListener.java
new file mode 100644
index 0000000..1ccffd0
--- /dev/null
+++ b/com/android/server/backup/transport/TransportConnectionListener.java
@@ -0,0 +1,37 @@
+/*
+ * 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.backup.transport;
+
+import android.annotation.Nullable;
+
+import com.android.internal.backup.IBackupTransport;
+
+/**
+ * Listener to be called by {@link TransportClient#connectAsync(TransportConnectionListener,
+ * String)}.
+ */
+public interface TransportConnectionListener {
+    /**
+     * Called when {@link TransportClient} has a transport binder available or that it decided it
+     * couldn't obtain one, in which case {@param transport} is null.
+     *
+     * @param transport A {@link IBackupTransport} transport binder or null.
+     * @param transportClient The {@link TransportClient} used to retrieve this transport binder.
+     */
+    void onTransportConnectionResult(
+            @Nullable IBackupTransport transport, TransportClient transportClient);
+}
diff --git a/com/android/server/backup/transport/TransportNotAvailableException.java b/com/android/server/backup/transport/TransportNotAvailableException.java
new file mode 100644
index 0000000..c4e5a1d
--- /dev/null
+++ b/com/android/server/backup/transport/TransportNotAvailableException.java
@@ -0,0 +1,32 @@
+/*
+ * 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.backup.transport;
+
+import com.android.internal.backup.IBackupTransport;
+
+/**
+ * Exception thrown when the {@link IBackupTransport} is not available. This happen when a {@link
+ * TransportClient} connection attempt fails. Check {@link
+ * TransportClient#connectAsync(TransportConnectionListener, String)} for when that happens.
+ *
+ * @see TransportClient#connectAsync(TransportConnectionListener, String)
+ */
+public class TransportNotAvailableException extends Exception {
+    TransportNotAvailableException() {
+        super("Transport not available");
+    }
+}
diff --git a/com/android/server/backup/transport/TransportUtils.java b/com/android/server/backup/transport/TransportUtils.java
new file mode 100644
index 0000000..85599b7
--- /dev/null
+++ b/com/android/server/backup/transport/TransportUtils.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.backup.transport;
+
+import android.annotation.Nullable;
+import android.os.DeadObjectException;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.backup.IBackupTransport;
+
+/** Utility methods for transport-related operations. */
+public class TransportUtils {
+    private static final String TAG = "TransportUtils";
+
+    /**
+     * Throws {@link TransportNotAvailableException} if {@param transport} is null. The semantics is
+     * similar to a {@link DeadObjectException} coming from a dead transport binder.
+     */
+    public static IBackupTransport checkTransport(@Nullable IBackupTransport transport)
+            throws TransportNotAvailableException {
+        if (transport == null) {
+            log(Log.ERROR, TAG, "Transport not available");
+            throw new TransportNotAvailableException();
+        }
+        return transport;
+    }
+
+    static void log(int priority, String tag, String message) {
+        log(priority, tag, null, message);
+    }
+
+    static void log(int priority, String tag, @Nullable String caller, String message) {
+        log(priority, tag, "", caller, message);
+    }
+
+    static void log(
+            int priority, String tag, String prefix, @Nullable String caller, String message) {
+        if (Log.isLoggable(tag, priority)) {
+            if (caller != null) {
+                prefix += "[" + caller + "] ";
+            }
+            Slog.println(priority, tag, prefix + message);
+        }
+    }
+
+    private TransportUtils() {}
+}
diff --git a/com/android/server/broadcastradio/Tuner.java b/com/android/server/broadcastradio/Tuner.java
index e6ae320..2ea4271 100644
--- a/com/android/server/broadcastradio/Tuner.java
+++ b/com/android/server/broadcastradio/Tuner.java
@@ -27,8 +27,10 @@
 import android.os.RemoteException;
 import android.util.Slog;
 
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 
 class Tuner extends ITuner.Stub {
     private static final String TAG = "BroadcastRadioService.Tuner";
@@ -96,6 +98,10 @@
     private native boolean nativeIsAnalogForced(long nativeContext);
     private native void nativeSetAnalogForced(long nativeContext, boolean isForced);
 
+    private native Map<String, String> nativeSetParameters(long nativeContext,
+            Map<String, String> parameters);
+    private native Map<String, String> nativeGetParameters(long nativeContext, List<String> keys);
+
     private native boolean nativeIsAntennaConnected(long nativeContext);
 
     @Override
@@ -273,6 +279,31 @@
     }
 
     @Override
+    public Map setParameters(Map parameters) {
+        Map<String, String> results;
+        synchronized (mLock) {
+            checkNotClosedLocked();
+            results = nativeSetParameters(mNativeContext, Objects.requireNonNull(parameters));
+        }
+        if (results == null) return Collections.emptyMap();
+        return results;
+    }
+
+    @Override
+    public Map getParameters(List<String> keys) {
+        if (keys == null) {
+            throw new IllegalArgumentException("The argument must not be a null pointer");
+        }
+        Map<String, String> results;
+        synchronized (mLock) {
+            checkNotClosedLocked();
+            results = nativeGetParameters(mNativeContext, keys);
+        }
+        if (results == null) return Collections.emptyMap();
+        return results;
+    }
+
+    @Override
     public boolean isAntennaConnected() {
         synchronized (mLock) {
             checkNotClosedLocked();
diff --git a/com/android/server/broadcastradio/TunerCallback.java b/com/android/server/broadcastradio/TunerCallback.java
index a87ae8d..2460c67 100644
--- a/com/android/server/broadcastradio/TunerCallback.java
+++ b/com/android/server/broadcastradio/TunerCallback.java
@@ -26,6 +26,9 @@
 import android.os.RemoteException;
 import android.util.Slog;
 
+import java.util.List;
+import java.util.Map;
+
 class TunerCallback implements ITunerCallback {
     private static final String TAG = "BroadcastRadioService.TunerCallback";
 
@@ -121,6 +124,11 @@
     }
 
     @Override
+    public void onParametersUpdated(Map parameters) {
+        dispatch(() -> mClientCallback.onParametersUpdated(parameters));
+    }
+
+    @Override
     public IBinder asBinder() {
         throw new RuntimeException("Not a binder");
     }
diff --git a/com/android/server/connectivity/NetdEventListenerService.java b/com/android/server/connectivity/NetdEventListenerService.java
index 4bdbbe3..e243e56 100644
--- a/com/android/server/connectivity/NetdEventListenerService.java
+++ b/com/android/server/connectivity/NetdEventListenerService.java
@@ -21,6 +21,7 @@
 import android.content.Context;
 import android.net.ConnectivityManager;
 import android.net.INetdEventCallback;
+import android.net.MacAddress;
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.metrics.ConnectStats;
@@ -35,6 +36,7 @@
 import android.util.Log;
 import android.util.ArrayMap;
 import android.util.SparseArray;
+import android.util.StatsLog;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -242,13 +244,17 @@
         event.timestampMs = timestampMs;
         event.uid = uid;
         event.ethertype = ethertype;
-        event.dstHwAddr = dstHw;
+        event.dstHwAddr = new MacAddress(dstHw);
         event.srcIp = srcIp;
         event.dstIp = dstIp;
         event.ipNextHeader = ipNextHeader;
         event.srcPort = srcPort;
         event.dstPort = dstPort;
         addWakeupEvent(event);
+
+        String dstMac = event.dstHwAddr.toString();
+        StatsLog.write(StatsLog.PACKET_WAKEUP_OCCURRED,
+                uid, iface, ethertype, dstMac, srcIp, dstIp, ipNextHeader, srcPort, dstPort);
     }
 
     private void addWakeupEvent(WakeupEvent event) {
diff --git a/com/android/server/devicepolicy/DevicePolicyManagerService.java b/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 2d8a0ee..60c36d1 100644
--- a/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -4917,7 +4917,8 @@
 
     @Override
     public boolean installKeyPair(ComponentName who, String callerPackage, byte[] privKey,
-            byte[] cert, byte[] chain, String alias, boolean requestAccess) {
+            byte[] cert, byte[] chain, String alias, boolean requestAccess,
+            boolean isUserSelectable) {
         enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER,
                 DELEGATION_CERT_INSTALL);
 
@@ -4935,6 +4936,7 @@
                 if (requestAccess) {
                     keyChain.setGrant(callingUid, alias, true);
                 }
+                keyChain.setUserSelectable(alias, isUserSelectable);
                 return true;
             } catch (RemoteException e) {
                 Log.e(LOG_TAG, "Installing certificate", e);
diff --git a/com/android/server/devicepolicy/NetworkLoggingHandler.java b/com/android/server/devicepolicy/NetworkLoggingHandler.java
index 6086354..4a6bee5 100644
--- a/com/android/server/devicepolicy/NetworkLoggingHandler.java
+++ b/com/android/server/devicepolicy/NetworkLoggingHandler.java
@@ -64,6 +64,8 @@
     private final DevicePolicyManagerService mDpm;
     private final AlarmManager mAlarmManager;
 
+    private long mId;
+
     private final OnAlarmListener mBatchTimeoutAlarmListener = new OnAlarmListener() {
         @Override
         public void onAlarm() {
@@ -185,6 +187,10 @@
     private Bundle finalizeBatchAndBuildDeviceOwnerMessageLocked() {
         Bundle notificationExtras = null;
         if (mNetworkEvents.size() > 0) {
+            // Assign ids to the events.
+            for (NetworkEvent event : mNetworkEvents) {
+                event.setId(mId++);
+            }
             // Finalize the batch and start a new one from scratch.
             if (mBatches.size() >= MAX_BATCHES) {
                 // Remove the oldest batch if we hit the limit.
diff --git a/com/android/server/display/BrightnessIdleJob.java b/com/android/server/display/BrightnessIdleJob.java
new file mode 100644
index 0000000..876acf4
--- /dev/null
+++ b/com/android/server/display/BrightnessIdleJob.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 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.display;
+
+
+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.hardware.display.DisplayManagerInternal;
+import android.util.Slog;
+
+import com.android.server.LocalServices;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * JobService used to persists brightness slider events when the device
+ * is idle and charging.
+ */
+public class BrightnessIdleJob extends JobService {
+
+    // Must be unique within the system server uid.
+    private static final int JOB_ID = 3923512;
+
+    public static void scheduleJob(Context context) {
+        JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
+
+        JobInfo pending = jobScheduler.getPendingJob(JOB_ID);
+        JobInfo jobInfo =
+                new JobInfo.Builder(JOB_ID, new ComponentName(context, BrightnessIdleJob.class))
+                        .setRequiresDeviceIdle(true)
+                        .setRequiresCharging(true)
+                        .setPeriodic(TimeUnit.HOURS.toMillis(24)).build();
+
+        if (pending != null && !pending.equals(jobInfo)) {
+            jobScheduler.cancel(JOB_ID);
+            pending = null;
+        }
+
+        if (pending == null) {
+            jobScheduler.schedule(jobInfo);
+        }
+    }
+
+    public static void cancelJob(Context context) {
+        JobScheduler jobScheduler = context.getSystemService(JobScheduler.class);
+        jobScheduler.cancel(JOB_ID);
+    }
+
+    @Override
+    public boolean onStartJob(JobParameters params) {
+        if (BrightnessTracker.DEBUG) {
+            Slog.d(BrightnessTracker.TAG, "Scheduled write of brightness events");
+        }
+        DisplayManagerInternal dmi = LocalServices.getService(DisplayManagerInternal.class);
+        dmi.persistBrightnessSliderEvents();
+        return false;
+    }
+
+    @Override
+    public boolean onStopJob(JobParameters params) {
+        return false;
+    }
+}
\ No newline at end of file
diff --git a/com/android/server/display/BrightnessTracker.java b/com/android/server/display/BrightnessTracker.java
index 361d928..2c6fe94 100644
--- a/com/android/server/display/BrightnessTracker.java
+++ b/com/android/server/display/BrightnessTracker.java
@@ -34,6 +34,7 @@
 import android.os.BatteryManager;
 import android.os.Environment;
 import android.os.Handler;
+import android.os.PowerManager;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.UserHandle;
@@ -61,12 +62,12 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.io.PrintWriter;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
 
 import java.util.Deque;
-import java.util.List;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -75,8 +76,8 @@
  */
 public class BrightnessTracker {
 
-    private static final String TAG = "BrightnessTracker";
-    private static final boolean DEBUG = false;
+    static final String TAG = "BrightnessTracker";
+    static final boolean DEBUG = false;
 
     private static final String EVENTS_FILE = "brightness_events.xml";
     private static final int MAX_EVENTS = 100;
@@ -103,6 +104,8 @@
     @GuardedBy("mEventsLock")
     private RingBuffer<BrightnessChangeEvent> mEvents
             = new RingBuffer<>(BrightnessChangeEvent.class, MAX_EVENTS);
+    @GuardedBy("mEventsLock")
+    private boolean mEventsDirty;
     private final Runnable mEventsWriter = () -> writeEvents();
     private volatile boolean mWriteEventsScheduled;
 
@@ -160,7 +163,10 @@
                 UserHandle.USER_CURRENT);
 
         mSensorListener = new SensorListener();
-        mInjector.registerSensorListener(mContext, mSensorListener);
+
+        if (mInjector.isInteractive(mContext)) {
+            mInjector.registerSensorListener(mContext, mSensorListener, mBgHandler);
+        }
 
         mSettingsObserver = new SettingsObserver(mBgHandler);
         mInjector.registerBrightnessObserver(mContentResolver, mSettingsObserver);
@@ -168,8 +174,12 @@
         final IntentFilter intentFilter = new IntentFilter();
         intentFilter.addAction(Intent.ACTION_SHUTDOWN);
         intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED);
+        intentFilter.addAction(Intent.ACTION_SCREEN_ON);
+        intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
         mBroadcastReceiver = new Receiver();
         mInjector.registerReceiver(mContext, mBroadcastReceiver, intentFilter);
+
+        mInjector.scheduleIdleJob(mContext);
     }
 
     /** Stop listening for events */
@@ -181,6 +191,7 @@
         mInjector.unregisterSensorListener(mContext, mSensorListener);
         mInjector.unregisterReceiver(mContext, mBroadcastReceiver);
         mInjector.unregisterBrightnessObserver(mContext, mSettingsObserver);
+        mInjector.cancelIdleJob(mContext);
     }
 
     /**
@@ -211,6 +222,10 @@
                 brightness, userId);
     }
 
+    public void persistEvents() {
+        scheduleWriteEvents();
+    }
+
     private void handleBrightnessChanged() {
         if (DEBUG) {
             Slog.d(TAG, "Brightness change");
@@ -278,6 +293,7 @@
             Slog.d(TAG, "Event " + event.brightness + " " + event.packageName);
         }
         synchronized (mEventsLock) {
+            mEventsDirty = true;
             mEvents.append(event);
         }
     }
@@ -291,8 +307,12 @@
 
     private void writeEvents() {
         mWriteEventsScheduled = false;
-        // TODO kick off write on handler thread e.g. every 24 hours.
         synchronized (mEventsLock) {
+            if (!mEventsDirty) {
+                // Nothing to write
+                return;
+            }
+
             final AtomicFile writeTo = mInjector.getFile();
             if (writeTo == null) {
                 return;
@@ -301,12 +321,14 @@
                 if (writeTo.exists()) {
                     writeTo.delete();
                 }
+                mEventsDirty = false;
             } else {
                 FileOutputStream output = null;
                 try {
                     output = writeTo.startWrite();
                     writeEventsLocked(output);
                     writeTo.finishWrite(output);
+                    mEventsDirty = false;
                 } catch (IOException e) {
                     writeTo.failWrite(output);
                     Slog.e(TAG, "Failed to write change mEvents.", e);
@@ -317,6 +339,8 @@
 
     private void readEvents() {
         synchronized (mEventsLock) {
+            // Read might prune events so mark as dirty.
+            mEventsDirty = true;
             mEvents.clear();
             final AtomicFile readFrom = mInjector.getFile();
             if (readFrom != null && readFrom.exists()) {
@@ -344,13 +368,16 @@
 
         out.startTag(null, TAG_EVENTS);
         BrightnessChangeEvent[] toWrite = mEvents.toArray();
+        // Clear events, code below will add back the ones that are still within the time window.
+        mEvents.clear();
         if (DEBUG) {
             Slog.d(TAG, "Writing events " + toWrite.length);
         }
-        final long timeCutOff = System.currentTimeMillis() - MAX_EVENT_AGE;
+        final long timeCutOff = mInjector.currentTimeMillis() - MAX_EVENT_AGE;
         for (int i = 0; i < toWrite.length; ++i) {
             int userSerialNo = mInjector.getUserSerialNumber(mUserManager, toWrite[i].userId);
             if (userSerialNo != -1 && toWrite[i].timeStamp > timeCutOff) {
+                mEvents.append(toWrite[i]);
                 out.startTag(null, TAG_EVENT);
                 out.attribute(null, ATTR_BRIGHTNESS, Integer.toString(toWrite[i].brightness));
                 out.attribute(null, ATTR_TIMESTAMP, Long.toString(toWrite[i].timeStamp));
@@ -465,6 +492,17 @@
         }
     }
 
+    public void dump(PrintWriter pw) {
+        synchronized (mEventsLock) {
+            pw.println("BrightnessTracker state:");
+            pw.println("  mEvents.size=" + mEvents.size());
+            pw.println("  mEventsDirty=" + mEventsDirty);
+        }
+        synchronized (mDataCollectionLock) {
+            pw.println("  mLastSensorReadings.size=" + mLastSensorReadings.size());
+        }
+    }
+
     // Not allowed to keep the SensorEvent so used to copy the data we care about.
     private static class LightData {
         public float lux;
@@ -552,6 +590,11 @@
                 if (level != -1 && scale != 0) {
                     batteryLevelChanged(level, scale);
                 }
+            } else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
+                mInjector.unregisterSensorListener(mContext, mSensorListener);
+            } else if (Intent.ACTION_SCREEN_ON.equals(action)) {
+                mInjector.registerSensorListener(mContext, mSensorListener,
+                        mInjector.getBackgroundHandler());
             }
         }
     }
@@ -559,11 +602,11 @@
     @VisibleForTesting
     static class Injector {
         public void registerSensorListener(Context context,
-                SensorEventListener sensorListener) {
+                SensorEventListener sensorListener, Handler handler) {
             SensorManager sensorManager = context.getSystemService(SensorManager.class);
             Sensor lightSensor = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
             sensorManager.registerListener(sensorListener,
-                    lightSensor, SensorManager.SENSOR_DELAY_NORMAL);
+                    lightSensor, SensorManager.SENSOR_DELAY_NORMAL, handler);
         }
 
         public void unregisterSensorListener(Context context, SensorEventListener sensorListener) {
@@ -635,5 +678,17 @@
         public ActivityManager.StackInfo getFocusedStack() throws RemoteException {
             return ActivityManager.getService().getFocusedStackInfo();
         }
+
+        public void scheduleIdleJob(Context context) {
+            BrightnessIdleJob.scheduleJob(context);
+        }
+
+        public void cancelIdleJob(Context context) {
+            BrightnessIdleJob.cancelJob(context);
+        }
+
+        public boolean isInteractive(Context context) {
+            return context.getSystemService(PowerManager.class).isInteractive();
+        }
     }
 }
diff --git a/com/android/server/display/DisplayManagerService.java b/com/android/server/display/DisplayManagerService.java
index f1e2011..379aaad 100644
--- a/com/android/server/display/DisplayManagerService.java
+++ b/com/android/server/display/DisplayManagerService.java
@@ -68,13 +68,13 @@
 import android.view.Display;
 import android.view.DisplayInfo;
 import android.view.Surface;
-import android.view.WindowManagerInternal;
 
 import com.android.server.AnimationThread;
 import com.android.server.DisplayThread;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.UiThread;
+import com.android.server.wm.WindowManagerInternal;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -1285,6 +1285,9 @@
 
             pw.println();
             mPersistentDataStore.dump(pw);
+
+            pw.println();
+            mBrightnessTracker.dump(pw);
         }
     }
 
@@ -1921,5 +1924,10 @@
         public boolean isUidPresentOnDisplay(int uid, int displayId) {
             return isUidPresentOnDisplayInternal(uid, displayId);
         }
+
+        @Override
+        public void persistBrightnessSliderEvents() {
+            mBrightnessTracker.persistEvents();
+        }
     }
 }
diff --git a/com/android/server/display/DisplayPowerController.java b/com/android/server/display/DisplayPowerController.java
index 600bc42..29a007a 100644
--- a/com/android/server/display/DisplayPowerController.java
+++ b/com/android/server/display/DisplayPowerController.java
@@ -20,6 +20,7 @@
 import com.android.internal.app.IBatteryStats;
 import com.android.server.LocalServices;
 import com.android.server.am.BatteryStatsService;
+import com.android.server.policy.WindowManagerPolicy;
 
 import android.animation.Animator;
 import android.animation.ObjectAnimator;
@@ -43,7 +44,6 @@
 import android.util.Spline;
 import android.util.TimeUtils;
 import android.view.Display;
-import android.view.WindowManagerPolicy;
 
 import java.io.PrintWriter;
 
diff --git a/com/android/server/input/InputManagerService.java b/com/android/server/input/InputManagerService.java
index fa9b107..4050790 100644
--- a/com/android/server/input/InputManagerService.java
+++ b/com/android/server/input/InputManagerService.java
@@ -33,6 +33,7 @@
 import com.android.server.DisplayThread;
 import com.android.server.LocalServices;
 import com.android.server.Watchdog;
+import com.android.server.policy.WindowManagerPolicy;
 
 import org.xmlpull.v1.XmlPullParser;
 
@@ -98,7 +99,6 @@
 import android.view.PointerIcon;
 import android.view.Surface;
 import android.view.ViewConfiguration;
-import android.view.WindowManagerPolicy;
 import android.view.inputmethod.InputMethodInfo;
 import android.view.inputmethod.InputMethodSubtype;
 
diff --git a/com/android/server/job/JobSchedulerInternal.java b/com/android/server/job/JobSchedulerInternal.java
index 095526d..9bcf208 100644
--- a/com/android/server/job/JobSchedulerInternal.java
+++ b/com/android/server/job/JobSchedulerInternal.java
@@ -26,6 +26,18 @@
  */
 public interface JobSchedulerInternal {
 
+    // Bookkeeping about app standby bucket scheduling
+
+    /**
+     * The current bucket heartbeat ordinal
+     */
+    long currentHeartbeat();
+
+    /**
+     * Heartbeat ordinal at which the given standby bucket's jobs next become runnable
+     */
+    long nextHeartbeatForBucket(int bucket);
+
     /**
      * Returns a list of pending jobs scheduled by the system service.
      */
diff --git a/com/android/server/job/JobSchedulerService.java b/com/android/server/job/JobSchedulerService.java
index b549768..4af86a0 100644
--- a/com/android/server/job/JobSchedulerService.java
+++ b/com/android/server/job/JobSchedulerService.java
@@ -29,6 +29,9 @@
 import android.app.job.JobScheduler;
 import android.app.job.JobService;
 import android.app.job.JobWorkItem;
+import android.app.usage.AppStandby;
+import android.app.usage.UsageStatsManagerInternal;
+import android.app.usage.UsageStatsManagerInternal.AppIdleStateChangeListener;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.ContentResolver;
@@ -37,6 +40,7 @@
 import android.content.IntentFilter;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ServiceInfo;
 import android.database.ContentObserver;
@@ -46,7 +50,6 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
-import android.os.PowerManager;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
@@ -65,6 +68,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.app.procstats.ProcessStats;
+import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.DumpUtils;
 import com.android.server.DeviceIdleController;
@@ -111,6 +115,7 @@
         implements StateChangedListener, JobCompletedListener {
     static final String TAG = "JobSchedulerService";
     public static final boolean DEBUG = false;
+    public static final boolean DEBUG_STANDBY = DEBUG || false;
 
     /** The maximum number of concurrent jobs we run at one time. */
     private static final int MAX_JOB_CONTEXTS_COUNT = 16;
@@ -130,6 +135,8 @@
     final Object mLock = new Object();
     /** Master list of jobs. */
     final JobStore mJobs;
+    /** Tracking the standby bucket state of each app */
+    final StandbyTracker mStandbyTracker;
     /** Tracking amount of time each package runs for. */
     final JobPackageTracker mJobPackageTracker = new JobPackageTracker();
 
@@ -151,6 +158,7 @@
     StorageController mStorageController;
     /** Need directly for sending uid state changes */
     private BackgroundJobsController mBackgroundJobsController;
+    private DeviceIdleJobsController mDeviceIdleJobsController;
     /**
      * Queue of pending jobs. The JobServiceContext class will receive jobs from this list
      * when ready to execute them.
@@ -162,8 +170,8 @@
     final JobHandler mHandler;
     final JobSchedulerStub mJobSchedulerStub;
 
+    PackageManagerInternal mLocalPM;
     IBatteryStats mBatteryStats;
-    PowerManager mPowerManager;
     DeviceIdleController.LocalService mLocalDeviceIdleController;
 
     /**
@@ -192,6 +200,17 @@
      */
     final SparseIntArray mBackingUpUids = new SparseIntArray();
 
+    /**
+     * Count standby heartbeats, and keep track of which beat each bucket's jobs will
+     * next become runnable.  Index into this array is by normalized bucket:
+     * { ACTIVE, WORKING, FREQUENT, RARE, NEVER }.  The ACTIVE and NEVER bucket
+     * milestones are not updated: ACTIVE apps get jobs whenever they ask for them,
+     * and NEVER apps don't get them at all.
+     */
+    final long[] mNextBucketHeartbeat = { 0, 0, 0, 0, Long.MAX_VALUE };
+    long mHeartbeat = 0;
+    long mLastHeartbeatTime = 0;
+
     // -- Pre-allocated temporaries only for use in assignJobsToContextsLocked --
 
     /**
@@ -236,6 +255,10 @@
         private static final String KEY_MAX_WORK_RESCHEDULE_COUNT = "max_work_reschedule_count";
         private static final String KEY_MIN_LINEAR_BACKOFF_TIME = "min_linear_backoff_time";
         private static final String KEY_MIN_EXP_BACKOFF_TIME = "min_exp_backoff_time";
+        private static final String KEY_STANDBY_HEARTBEAT_TIME = "standby_heartbeat_time";
+        private static final String KEY_STANDBY_WORKING_BEATS = "standby_working_beats";
+        private static final String KEY_STANDBY_FREQUENT_BEATS = "standby_frequent_beats";
+        private static final String KEY_STANDBY_RARE_BEATS = "standby_rare_beats";
 
         private static final int DEFAULT_MIN_IDLE_COUNT = 1;
         private static final int DEFAULT_MIN_CHARGING_COUNT = 1;
@@ -255,6 +278,10 @@
         private static final int DEFAULT_MAX_WORK_RESCHEDULE_COUNT = Integer.MAX_VALUE;
         private static final long DEFAULT_MIN_LINEAR_BACKOFF_TIME = JobInfo.MIN_BACKOFF_MILLIS;
         private static final long DEFAULT_MIN_EXP_BACKOFF_TIME = JobInfo.MIN_BACKOFF_MILLIS;
+        private static final long DEFAULT_STANDBY_HEARTBEAT_TIME = 11 * 60 * 1000L;
+        private static final int DEFAULT_STANDBY_WORKING_BEATS = 5;  // ~ 1 hour, with 11-min beats
+        private static final int DEFAULT_STANDBY_FREQUENT_BEATS = 31; // ~ 6 hours
+        private static final int DEFAULT_STANDBY_RARE_BEATS = 130; // ~ 24 hours
 
         /**
          * Minimum # of idle jobs that must be ready in order to force the JMS to schedule things
@@ -343,6 +370,27 @@
          * The minimum backoff time to allow for exponential backoff.
          */
         long MIN_EXP_BACKOFF_TIME = DEFAULT_MIN_EXP_BACKOFF_TIME;
+        /**
+         * How often we recalculate runnability based on apps' standby bucket assignment.
+         * This should be prime relative to common time interval lengths such as a quarter-
+         * hour or day, so that the heartbeat drifts relative to wall-clock milestones.
+         */
+        long STANDBY_HEARTBEAT_TIME = DEFAULT_STANDBY_HEARTBEAT_TIME;
+
+        /**
+         * Mapping: standby bucket -> number of heartbeats between each sweep of that
+         * bucket's jobs.
+         *
+         * Bucket assignments as recorded in the JobStatus objects are normalized to be
+         * indices into this array, rather than the raw constants used
+         * by AppIdleHistory.
+         */
+        final int[] STANDBY_BEATS = {
+                0,
+                DEFAULT_STANDBY_WORKING_BEATS,
+                DEFAULT_STANDBY_FREQUENT_BEATS,
+                DEFAULT_STANDBY_RARE_BEATS
+        };
 
         private ContentResolver mResolver;
         private final KeyValueListParser mParser = new KeyValueListParser(',');
@@ -422,6 +470,14 @@
                         DEFAULT_MIN_LINEAR_BACKOFF_TIME);
                 MIN_EXP_BACKOFF_TIME = mParser.getLong(KEY_MIN_EXP_BACKOFF_TIME,
                         DEFAULT_MIN_EXP_BACKOFF_TIME);
+                STANDBY_HEARTBEAT_TIME = mParser.getLong(KEY_STANDBY_HEARTBEAT_TIME,
+                        DEFAULT_STANDBY_HEARTBEAT_TIME);
+                STANDBY_BEATS[1] = mParser.getInt(KEY_STANDBY_WORKING_BEATS,
+                        DEFAULT_STANDBY_WORKING_BEATS);
+                STANDBY_BEATS[2] = mParser.getInt(KEY_STANDBY_FREQUENT_BEATS,
+                        DEFAULT_STANDBY_FREQUENT_BEATS);
+                STANDBY_BEATS[3] = mParser.getInt(KEY_STANDBY_RARE_BEATS,
+                        DEFAULT_STANDBY_RARE_BEATS);
             }
         }
 
@@ -481,6 +537,17 @@
 
             pw.print("    "); pw.print(KEY_MIN_EXP_BACKOFF_TIME); pw.print("=");
             pw.print(MIN_EXP_BACKOFF_TIME); pw.println();
+
+            pw.print("    "); pw.print(KEY_STANDBY_HEARTBEAT_TIME); pw.print("=");
+            pw.print(STANDBY_HEARTBEAT_TIME); pw.println();
+
+            pw.print("    standby_beats={");
+            pw.print(STANDBY_BEATS[0]);
+            for (int i = 1; i < STANDBY_BEATS.length; i++) {
+                pw.print(", ");
+                pw.print(STANDBY_BEATS[i]);
+            }
+            pw.println('}');
         }
     }
 
@@ -623,13 +690,13 @@
                 cancelJobsForUid(uid, "uid gone");
             }
             synchronized (mLock) {
-                mBackgroundJobsController.setUidActiveLocked(uid, false);
+                mDeviceIdleJobsController.setUidActiveLocked(uid, false);
             }
         }
 
         @Override public void onUidActive(int uid) throws RemoteException {
             synchronized (mLock) {
-                mBackgroundJobsController.setUidActiveLocked(uid, true);
+                mDeviceIdleJobsController.setUidActiveLocked(uid, true);
             }
         }
 
@@ -638,7 +705,7 @@
                 cancelJobsForUid(uid, "app uid idle");
             }
             synchronized (mLock) {
-                mBackgroundJobsController.setUidActiveLocked(uid, false);
+                mDeviceIdleJobsController.setUidActiveLocked(uid, false);
             }
         }
 
@@ -683,6 +750,7 @@
             }
         } catch (RemoteException e) {
         }
+
         synchronized (mLock) {
             final JobStatus toCancel = mJobs.getJobByUidAndJobId(uId, job.getId());
 
@@ -934,9 +1002,22 @@
      */
     public JobSchedulerService(Context context) {
         super(context);
+
+        mLocalPM = LocalServices.getService(PackageManagerInternal.class);
+
         mHandler = new JobHandler(context.getMainLooper());
         mConstants = new Constants(mHandler);
         mJobSchedulerStub = new JobSchedulerStub();
+
+        // Set up the app standby bucketing tracker
+        UsageStatsManagerInternal usageStats = LocalServices.getService(UsageStatsManagerInternal.class);
+        mStandbyTracker = new StandbyTracker(usageStats);
+        usageStats.addAppIdleStateChangeListener(mStandbyTracker);
+
+        // The job store needs to call back
+        publishLocalService(JobSchedulerInternal.class, new LocalService());
+
+        // Initialize the job store and set up any persisted jobs
         mJobs = JobStore.initAndGet(this);
 
         // Create the controllers.
@@ -948,11 +1029,11 @@
         mControllers.add(mBatteryController);
         mStorageController = StorageController.get(this);
         mControllers.add(mStorageController);
-        mBackgroundJobsController = BackgroundJobsController.get(this);
-        mControllers.add(mBackgroundJobsController);
+        mControllers.add(BackgroundJobsController.get(this));
         mControllers.add(AppIdleController.get(this));
         mControllers.add(ContentObserverController.get(this));
-        mControllers.add(DeviceIdleJobsController.get(this));
+        mDeviceIdleJobsController = DeviceIdleJobsController.get(this);
+        mControllers.add(mDeviceIdleJobsController);
 
         // If the job store determined that it can't yet reschedule persisted jobs,
         // we need to start watching the clock.
@@ -1006,7 +1087,6 @@
 
     @Override
     public void onStart() {
-        publishLocalService(JobSchedulerInternal.class, new LocalService());
         publishBinderService(Context.JOB_SCHEDULER_SERVICE, mJobSchedulerStub);
     }
 
@@ -1026,7 +1106,6 @@
             final IntentFilter userFilter = new IntentFilter(Intent.ACTION_USER_REMOVED);
             getContext().registerReceiverAsUser(
                     mBroadcastReceiver, UserHandle.ALL, userFilter, null, null);
-            mPowerManager = (PowerManager)getContext().getSystemService(Context.POWER_SERVICE);
             try {
                 ActivityManager.getService().registerUidObserver(mUidObserver,
                         ActivityManager.UID_OBSERVER_PROCSTATE | ActivityManager.UID_OBSERVER_GONE
@@ -1206,7 +1285,8 @@
         }
         delayMillis =
                 Math.min(delayMillis, JobInfo.MAX_BACKOFF_DELAY_MILLIS);
-        JobStatus newJob = new JobStatus(failureToReschedule, elapsedNowMillis + delayMillis,
+        JobStatus newJob = new JobStatus(failureToReschedule, getCurrentHeartbeat(),
+                elapsedNowMillis + delayMillis,
                 JobStatus.NO_LATEST_RUNTIME, backoffAttempts,
                 failureToReschedule.getLastSuccessfulRunTime(), sSystemClock.millis());
         for (int ic=0; ic<mControllers.size(); ic++) {
@@ -1220,10 +1300,12 @@
      * Called after a periodic has executed so we can reschedule it. We take the last execution
      * time of the job to be the time of completion (i.e. the time at which this function is
      * called).
-     * This could be inaccurate b/c the job can run for as long as
+     * <p>This could be inaccurate b/c the job can run for as long as
      * {@link com.android.server.job.JobServiceContext#EXECUTING_TIMESLICE_MILLIS}, but will lead
      * to underscheduling at least, rather than if we had taken the last execution time to be the
      * start of the execution.
+     * <p>Unlike a reschedule prior to execution, in this case we advance the next-heartbeat
+     * tracking as though the job were newly-scheduled.
      * @return A new job representing the execution criteria for this instantiation of the
      * recurring job.
      */
@@ -1245,8 +1327,9 @@
             Slog.v(TAG, "Rescheduling executed periodic. New execution window [" +
                     newEarliestRunTimeElapsed/1000 + ", " + newLatestRuntimeElapsed/1000 + "]s");
         }
-        return new JobStatus(periodicToReschedule, newEarliestRunTimeElapsed,
-                newLatestRuntimeElapsed, 0 /* backoffAttempt */,
+        return new JobStatus(periodicToReschedule, getCurrentHeartbeat(),
+                newEarliestRunTimeElapsed, newLatestRuntimeElapsed,
+                0 /* backoffAttempt */,
                 sSystemClock.millis() /* lastSuccessfulRunTime */,
                 periodicToReschedule.getLastFailedRunTime());
     }
@@ -1393,7 +1476,9 @@
         noteJobsNonpending(mPendingJobs);
         mPendingJobs.clear();
         stopNonReadyActiveJobsLocked();
+        boolean updated = updateStandbyHeartbeatLocked();
         mJobs.forEachJob(mReadyQueueFunctor);
+        if (updated) updateNextStandbyHeartbeatsLocked();
         mReadyQueueFunctor.postProcess();
 
         if (DEBUG) {
@@ -1547,16 +1632,45 @@
         noteJobsNonpending(mPendingJobs);
         mPendingJobs.clear();
         stopNonReadyActiveJobsLocked();
+        boolean updated = updateStandbyHeartbeatLocked();
         mJobs.forEachJob(mMaybeQueueFunctor);
+        if (updated) updateNextStandbyHeartbeatsLocked();
         mMaybeQueueFunctor.postProcess();
     }
 
+    private boolean updateStandbyHeartbeatLocked() {
+        final long sinceLast = sElapsedRealtimeClock.millis() - mLastHeartbeatTime;
+        final long beatsElapsed = sinceLast / mConstants.STANDBY_HEARTBEAT_TIME;
+        if (beatsElapsed > 0) {
+            mHeartbeat += beatsElapsed;
+            mLastHeartbeatTime += beatsElapsed * mConstants.STANDBY_HEARTBEAT_TIME;
+            if (DEBUG_STANDBY) {
+                Slog.v(TAG, "Advancing standby heartbeat by " + beatsElapsed + " to " + mHeartbeat);
+            }
+            return true;
+        }
+        return false;
+    }
+
+    private void updateNextStandbyHeartbeatsLocked() {
+        // don't update ACTIVE or NEVER bucket milestones
+        for (int i = 1; i < mNextBucketHeartbeat.length - 1; i++) {
+            while (mHeartbeat >= mNextBucketHeartbeat[i]) {
+                mNextBucketHeartbeat[i] += mConstants.STANDBY_BEATS[i];
+            }
+            if (DEBUG_STANDBY) {
+                Slog.v(TAG, "   Bucket " + i + " next heartbeat " + mNextBucketHeartbeat[i]);
+            }
+        }
+    }
+
     /**
      * Criteria for moving a job into the pending queue:
      *      - It's ready.
      *      - It's not pending.
      *      - It's not already running on a JSC.
      *      - The user that requested the job is running.
+     *      - The job's standby bucket has come due to be runnable.
      *      - The component is enabled and runnable.
      */
     private boolean isReadyToBeExecutedLocked(JobStatus job) {
@@ -1605,6 +1719,22 @@
             return false;
         }
 
+        // If the app is in a non-active standby bucket, make sure we've waited
+        // an appropriate amount of time since the last invocation
+        if (mHeartbeat < mNextBucketHeartbeat[job.getStandbyBucket()]) {
+            // TODO: log/trace that we're deferring the job due to bucketing if we hit this
+            if (job.getWhenStandbyDeferred() == 0) {
+                if (DEBUG_STANDBY) {
+                    Slog.v(TAG, "Bucket deferral: " + mHeartbeat + " < "
+                            + mNextBucketHeartbeat[job.getStandbyBucket()] + " for " + job);
+                }
+                job.setWhenStandbyDeferred(sElapsedRealtimeClock.millis());
+            }
+            return false;
+        }
+
+        // The expensive check last: validate that the defined package+service is
+        // still present & viable.
         final boolean componentPresent;
         try {
             componentPresent = (AppGlobals.getPackageManager().getServiceInfo(
@@ -1818,6 +1948,22 @@
     final class LocalService implements JobSchedulerInternal {
 
         /**
+         * The current bucket heartbeat ordinal
+         */
+        public long currentHeartbeat() {
+            return getCurrentHeartbeat();
+        }
+
+        /**
+         * Heartbeat ordinal at which the given standby bucket's jobs next become runnable
+         */
+        public long nextHeartbeatForBucket(int bucket) {
+            synchronized (mLock) {
+                return mNextBucketHeartbeat[bucket];
+            }
+        }
+
+        /**
          * Returns a list of all pending jobs. A running job is not considered pending. Periodic
          * jobs are always considered pending.
          */
@@ -1878,6 +2024,79 @@
     }
 
     /**
+     * Tracking of app assignments to standby buckets
+     */
+    final class StandbyTracker extends AppIdleStateChangeListener {
+        final UsageStatsManagerInternal mUsageStats;
+
+        StandbyTracker(UsageStatsManagerInternal usageStats) {
+            mUsageStats = usageStats;
+        }
+
+        // AppIdleStateChangeListener interface for live updates
+
+        @Override
+        public void onAppIdleStateChanged(final String packageName, final int userId,
+                boolean idle, int bucket) {
+            final int uid = mLocalPM.getPackageUid(packageName,
+                    PackageManager.MATCH_UNINSTALLED_PACKAGES, userId);
+            if (uid < 0) {
+                if (DEBUG_STANDBY) {
+                    Slog.i(TAG, "App idle state change for unknown app "
+                            + packageName + "/" + userId);
+                }
+                return;
+            }
+
+            final int bucketIndex = standbyBucketToBucketIndex(bucket);
+            // update job bookkeeping out of band
+            BackgroundThread.getHandler().post(() -> {
+                if (DEBUG_STANDBY) {
+                    Slog.i(TAG, "Moving uid " + uid + " to bucketIndex " + bucketIndex);
+                }
+                synchronized (mLock) {
+                    // TODO: update to be more efficient once we can slice by source UID
+                    mJobs.forEachJob((JobStatus job) -> {
+                        if (job.getSourceUid() == uid) {
+                            job.setStandbyBucket(bucketIndex);
+                        }
+                    });
+                    onControllerStateChanged();
+                }
+            });
+        }
+
+        @Override
+        public void onParoleStateChanged(boolean isParoleOn) {
+            // Unused
+        }
+    }
+
+    public static int standbyBucketToBucketIndex(int bucket) {
+        // Normalize AppStandby constants to indices into our bookkeeping
+        if (bucket == AppStandby.STANDBY_BUCKET_NEVER) return 4;
+        else if (bucket >= AppStandby.STANDBY_BUCKET_RARE) return 3;
+        else if (bucket >= AppStandby.STANDBY_BUCKET_FREQUENT) return 2;
+        else if (bucket >= AppStandby.STANDBY_BUCKET_WORKING_SET) return 1;
+        else return 0;
+    }
+
+    public static int standbyBucketForPackage(String packageName, int userId, long elapsedNow) {
+        UsageStatsManagerInternal usageStats = LocalServices.getService(
+                UsageStatsManagerInternal.class);
+        int bucket = usageStats != null
+                ? usageStats.getAppStandbyBucket(packageName, userId, elapsedNow)
+                : 0;
+
+        bucket = standbyBucketToBucketIndex(bucket);
+
+        if (DEBUG_STANDBY) {
+            Slog.v(TAG, packageName + "/" + userId + " standby bucket index: " + bucket);
+        }
+        return bucket;
+    }
+
+    /**
      * Binder stub trampoline implementation
      */
     final class JobSchedulerStub extends IJobScheduler.Stub {
@@ -1942,6 +2161,7 @@
             }
             final int pid = Binder.getCallingPid();
             final int uid = Binder.getCallingUid();
+            final int userId = UserHandle.getUserId(uid);
 
             enforceValidJobRequest(uid, job);
             if (job.isPersisted()) {
@@ -1958,7 +2178,8 @@
 
             long ident = Binder.clearCallingIdentity();
             try {
-                return JobSchedulerService.this.scheduleAsPackage(job, null, uid, null, -1, null);
+                return JobSchedulerService.this.scheduleAsPackage(job, null, uid, null, userId,
+                        null);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -1970,8 +2191,8 @@
             if (DEBUG) {
                 Slog.d(TAG, "Enqueueing job: " + job.toString() + " work: " + work);
             }
-            final int pid = Binder.getCallingPid();
             final int uid = Binder.getCallingUid();
+            final int userId = UserHandle.getUserId(uid);
 
             enforceValidJobRequest(uid, job);
             if (job.isPersisted()) {
@@ -1988,7 +2209,8 @@
 
             long ident = Binder.clearCallingIdentity();
             try {
-                return JobSchedulerService.this.scheduleAsPackage(job, work, uid, null, -1, null);
+                return JobSchedulerService.this.scheduleAsPackage(job, work, uid, null, userId,
+                        null);
             } finally {
                 Binder.restoreCallingIdentity(ident);
             }
@@ -2000,7 +2222,7 @@
             final int callerUid = Binder.getCallingUid();
             if (DEBUG) {
                 Slog.d(TAG, "Caller uid " + callerUid + " scheduling job: " + job.toString()
-                        + " on behalf of " + packageName);
+                        + " on behalf of " + packageName + "/");
             }
 
             if (packageName == null) {
@@ -2234,6 +2456,12 @@
         }
     }
 
+    long getCurrentHeartbeat() {
+        synchronized (mLock) {
+            return mHeartbeat;
+        }
+    }
+
     int getJobState(PrintWriter pw, String pkgName, int userId, int jobId) {
         try {
             final int uid = AppGlobals.getPackageManager().getPackageUid(pkgName, 0,
diff --git a/com/android/server/job/JobServiceContext.java b/com/android/server/job/JobServiceContext.java
index ac7297e..6a3fd04 100644
--- a/com/android/server/job/JobServiceContext.java
+++ b/com/android/server/job/JobServiceContext.java
@@ -64,6 +64,8 @@
  */
 public final class JobServiceContext implements ServiceConnection {
     private static final boolean DEBUG = JobSchedulerService.DEBUG;
+    private static final boolean DEBUG_STANDBY = JobSchedulerService.DEBUG_STANDBY;
+
     private static final String TAG = "JobServiceContext";
     /** Amount of time a job is allowed to execute for before being considered timed-out. */
     public static final long EXECUTING_TIMESLICE_MILLIS = 10 * 60 * 1000;  // 10mins.
@@ -220,6 +222,17 @@
                     isDeadlineExpired, triggeredUris, triggeredAuthorities, job.network);
             mExecutionStartTimeElapsed = sElapsedRealtimeClock.millis();
 
+            if (DEBUG_STANDBY) {
+                final long whenDeferred = job.getWhenStandbyDeferred();
+                if (whenDeferred > 0) {
+                    StringBuilder sb = new StringBuilder(128);
+                    sb.append("Starting job deferred for standby by ");
+                    TimeUtils.formatDuration(mExecutionStartTimeElapsed - whenDeferred, sb);
+                    sb.append(" : ");
+                    sb.append(job.toShortString());
+                    Slog.v(TAG, sb.toString());
+                }
+            }
             // Once we'e begun executing a job, we by definition no longer care whether
             // it was inflated from disk with not-yet-coherent delay/deadline bounds.
             job.clearPersistedUtcTimes();
diff --git a/com/android/server/job/JobStore.java b/com/android/server/job/JobStore.java
index 1af3b39..219bc61 100644
--- a/com/android/server/job/JobStore.java
+++ b/com/android/server/job/JobStore.java
@@ -43,6 +43,7 @@
 import com.android.internal.util.BitUtils;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.server.IoThread;
+import com.android.server.LocalServices;
 import com.android.server.job.JobSchedulerInternal.JobStorePersistStats;
 import com.android.server.job.controllers.JobStatus;
 
@@ -174,7 +175,8 @@
             if (utcTimes != null) {
                 Pair<Long, Long> elapsedRuntimes =
                         convertRtcBoundsToElapsed(utcTimes, elapsedNow);
-                toAdd.add(new JobStatus(job, elapsedRuntimes.first, elapsedRuntimes.second,
+                toAdd.add(new JobStatus(job, job.getBaseHeartbeat(),
+                        elapsedRuntimes.first, elapsedRuntimes.second,
                         0, job.getLastSuccessfulRunTime(), job.getLastFailedRunTime()));
                 toRemove.add(job);
             }
@@ -250,7 +252,7 @@
 
     /**
      * @param userHandle User for whom we are querying the list of jobs.
-     * @return A list of all the jobs scheduled by the provided user. Never null.
+     * @return A list of all the jobs scheduled for the provided user. Never null.
      */
     public List<JobStatus> getJobsByUser(int userHandle) {
         return mJobSet.getJobsByUser(userHandle);
@@ -287,6 +289,10 @@
         mJobSet.forEachJob(uid, functor);
     }
 
+    public void forEachJobForSourceUid(int sourceUid, JobStatusFunctor functor) {
+        mJobSet.forEachJobForSourceUid(sourceUid, functor);
+    }
+
     public interface JobStatusFunctor {
         public void process(JobStatus jobStatus);
     }
@@ -842,8 +848,13 @@
             }
 
             // And now we're done
+            JobSchedulerInternal service = LocalServices.getService(JobSchedulerInternal.class);
+            final int appBucket = JobSchedulerService.standbyBucketForPackage(sourcePackageName,
+                    sourceUserId, elapsedNow);
+            long currentHeartbeat = service != null ? service.currentHeartbeat() : 0;
             JobStatus js = new JobStatus(
-                    jobBuilder.build(), uid, sourcePackageName, sourceUserId, sourceTag,
+                    jobBuilder.build(), uid, sourcePackageName, sourceUserId,
+                    appBucket, currentHeartbeat, sourceTag,
                     elapsedRuntimes.first, elapsedRuntimes.second,
                     lastSuccessfulRunTime, lastFailedRunTime,
                     (rtcIsGood) ? null : rtcRuntimes);
@@ -979,9 +990,12 @@
     static final class JobSet {
         // Key is the getUid() originator of the jobs in each sheaf
         private SparseArray<ArraySet<JobStatus>> mJobs;
+        // Same data but with the key as getSourceUid() of the jobs in each sheaf
+        private SparseArray<ArraySet<JobStatus>> mJobsPerSourceUid;
 
         public JobSet() {
             mJobs = new SparseArray<ArraySet<JobStatus>>();
+            mJobsPerSourceUid = new SparseArray<>();
         }
 
         public List<JobStatus> getJobsByUid(int uid) {
@@ -995,10 +1009,10 @@
 
         // By user, not by uid, so we need to traverse by key and check
         public List<JobStatus> getJobsByUser(int userId) {
-            ArrayList<JobStatus> result = new ArrayList<JobStatus>();
-            for (int i = mJobs.size() - 1; i >= 0; i--) {
-                if (UserHandle.getUserId(mJobs.keyAt(i)) == userId) {
-                    ArraySet<JobStatus> jobs = mJobs.valueAt(i);
+            final ArrayList<JobStatus> result = new ArrayList<JobStatus>();
+            for (int i = mJobsPerSourceUid.size() - 1; i >= 0; i--) {
+                if (UserHandle.getUserId(mJobsPerSourceUid.keyAt(i)) == userId) {
+                    final ArraySet<JobStatus> jobs = mJobsPerSourceUid.valueAt(i);
                     if (jobs != null) {
                         result.addAll(jobs);
                     }
@@ -1009,32 +1023,60 @@
 
         public boolean add(JobStatus job) {
             final int uid = job.getUid();
+            final int sourceUid = job.getSourceUid();
             ArraySet<JobStatus> jobs = mJobs.get(uid);
             if (jobs == null) {
                 jobs = new ArraySet<JobStatus>();
                 mJobs.put(uid, jobs);
             }
-            return jobs.add(job);
+            ArraySet<JobStatus> jobsForSourceUid = mJobsPerSourceUid.get(sourceUid);
+            if (jobsForSourceUid == null) {
+                jobsForSourceUid = new ArraySet<>();
+                mJobsPerSourceUid.put(sourceUid, jobsForSourceUid);
+            }
+            return jobs.add(job) && jobsForSourceUid.add(job);
         }
 
         public boolean remove(JobStatus job) {
             final int uid = job.getUid();
-            ArraySet<JobStatus> jobs = mJobs.get(uid);
-            boolean didRemove = (jobs != null) ? jobs.remove(job) : false;
-            if (didRemove && jobs.size() == 0) {
-                // no more jobs for this uid; let the now-empty set object be GC'd.
-                mJobs.remove(uid);
+            final ArraySet<JobStatus> jobs = mJobs.get(uid);
+            final int sourceUid = job.getSourceUid();
+            final ArraySet<JobStatus> jobsForSourceUid = mJobsPerSourceUid.get(sourceUid);
+            boolean didRemove = jobs != null && jobs.remove(job) && jobsForSourceUid.remove(job);
+            if (didRemove) {
+                if (jobs.size() == 0) {
+                    // no more jobs for this uid; let the now-empty set object be GC'd.
+                    mJobs.remove(uid);
+                }
+                if (jobsForSourceUid.size() == 0) {
+                    mJobsPerSourceUid.remove(sourceUid);
+                }
+                return true;
             }
-            return didRemove;
+            return false;
         }
 
-        // Remove the jobs all users not specified by the whitelist of user ids
+        /**
+         * Removes the jobs of all users not specified by the whitelist of user ids.
+         * The jobs scheduled by non existent users will not be removed if they were
+         */
         public void removeJobsOfNonUsers(int[] whitelist) {
-            for (int jobIndex = mJobs.size() - 1; jobIndex >= 0; jobIndex--) {
-                int jobUserId = UserHandle.getUserId(mJobs.keyAt(jobIndex));
-                // check if job's user id is not in the whitelist
+            for (int jobSetIndex = mJobsPerSourceUid.size() - 1; jobSetIndex >= 0; jobSetIndex--) {
+                final int jobUserId = UserHandle.getUserId(mJobsPerSourceUid.keyAt(jobSetIndex));
                 if (!ArrayUtils.contains(whitelist, jobUserId)) {
-                    mJobs.removeAt(jobIndex);
+                    mJobsPerSourceUid.removeAt(jobSetIndex);
+                }
+            }
+            for (int jobSetIndex = mJobs.size() - 1; jobSetIndex >= 0; jobSetIndex--) {
+                final ArraySet<JobStatus> jobsForUid = mJobs.valueAt(jobSetIndex);
+                for (int jobIndex = jobsForUid.size() - 1; jobIndex >= 0; jobIndex--) {
+                    final int jobUserId = jobsForUid.valueAt(jobIndex).getUserId();
+                    if (!ArrayUtils.contains(whitelist, jobUserId)) {
+                        jobsForUid.removeAt(jobIndex);
+                    }
+                }
+                if (jobsForUid.size() == 0) {
+                    mJobs.removeAt(jobSetIndex);
                 }
             }
         }
@@ -1077,6 +1119,7 @@
 
         public void clear() {
             mJobs.clear();
+            mJobsPerSourceUid.clear();
         }
 
         public int size() {
@@ -1112,8 +1155,17 @@
             }
         }
 
-        public void forEachJob(int uid, JobStatusFunctor functor) {
-            ArraySet<JobStatus> jobs = mJobs.get(uid);
+        public void forEachJob(int callingUid, JobStatusFunctor functor) {
+            ArraySet<JobStatus> jobs = mJobs.get(callingUid);
+            if (jobs != null) {
+                for (int i = jobs.size() - 1; i >= 0; i--) {
+                    functor.process(jobs.valueAt(i));
+                }
+            }
+        }
+
+        public void forEachJobForSourceUid(int sourceUid, JobStatusFunctor functor) {
+            final ArraySet<JobStatus> jobs = mJobsPerSourceUid.get(sourceUid);
             if (jobs != null) {
                 for (int i = jobs.size() - 1; i >= 0; i--) {
                     functor.process(jobs.valueAt(i));
diff --git a/com/android/server/job/controllers/AppIdleController.java b/com/android/server/job/controllers/AppIdleController.java
index caa8522..a7ed2f5 100644
--- a/com/android/server/job/controllers/AppIdleController.java
+++ b/com/android/server/job/controllers/AppIdleController.java
@@ -180,6 +180,7 @@
                 if (mAppIdleParoleOn) {
                     return;
                 }
+
                 PackageUpdateFunc update = new PackageUpdateFunc(userId, packageName, idle);
                 mJobSchedulerService.getJobStore().forEachJob(update);
                 if (update.mChanged) {
diff --git a/com/android/server/job/controllers/BackgroundJobsController.java b/com/android/server/job/controllers/BackgroundJobsController.java
index 78b4160..fc4015d 100644
--- a/com/android/server/job/controllers/BackgroundJobsController.java
+++ b/com/android/server/job/controllers/BackgroundJobsController.java
@@ -16,24 +16,16 @@
 
 package com.android.server.job.controllers;
 
-import android.app.AppOpsManager;
-import android.content.BroadcastReceiver;
 import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
 import android.os.IDeviceIdleController;
-import android.os.PowerManager;
-import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.SystemClock;
 import android.os.UserHandle;
-import android.util.ArraySet;
 import android.util.Slog;
-import android.util.SparseArray;
-import android.util.SparseBooleanArray;
 
-import com.android.internal.app.IAppOpsCallback;
-import com.android.internal.app.IAppOpsService;
 import com.android.internal.util.ArrayUtils;
+import com.android.server.ForceAppStandbyTracker;
+import com.android.server.ForceAppStandbyTracker.Listener;
 import com.android.server.job.JobSchedulerService;
 import com.android.server.job.JobStore;
 
@@ -49,18 +41,10 @@
     private static volatile BackgroundJobsController sController;
 
     private final JobSchedulerService mJobSchedulerService;
-    private final IAppOpsService mAppOpsService;
     private final IDeviceIdleController mDeviceIdleController;
 
-    private final SparseBooleanArray mForegroundUids;
-    private int[] mPowerWhitelistedUserAppIds;
-    private int[] mTempWhitelistedAppIds;
-    /**
-     * Only tracks jobs for which source package app op RUN_ANY_IN_BACKGROUND is not ALLOWED.
-     * Maps jobs to the sourceUid unlike the global {@link JobSchedulerService#mJobs JobStore}
-     * which uses callingUid.
-     */
-    private SparseArray<ArraySet<JobStatus>> mTrackedJobs;
+    private final ForceAppStandbyTracker mForceAppStandbyTracker;
+
 
     public static BackgroundJobsController get(JobSchedulerService service) {
         synchronized (sCreationLock) {
@@ -72,232 +56,148 @@
         }
     }
 
-    private BroadcastReceiver mDozeWhitelistReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            synchronized (mLock) {
-                try {
-                    switch (intent.getAction()) {
-                        case PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED:
-                            mPowerWhitelistedUserAppIds =
-                                    mDeviceIdleController.getAppIdUserWhitelist();
-                            break;
-                        case PowerManager.ACTION_POWER_SAVE_TEMP_WHITELIST_CHANGED:
-                            mTempWhitelistedAppIds = mDeviceIdleController.getAppIdTempWhitelist();
-                            break;
-                    }
-                } catch (RemoteException rexc) {
-                    Slog.e(LOG_TAG, "Device idle controller not reachable");
-                }
-                if (checkAllTrackedJobsLocked()) {
-                    mStateChangedListener.onControllerStateChanged();
-                }
-            }
-        }
-    };
-
     private BackgroundJobsController(JobSchedulerService service, Context context, Object lock) {
         super(service, context, lock);
         mJobSchedulerService = service;
-        mAppOpsService = IAppOpsService.Stub.asInterface(
-                ServiceManager.getService(Context.APP_OPS_SERVICE));
         mDeviceIdleController = IDeviceIdleController.Stub.asInterface(
                 ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER));
 
-        mForegroundUids = new SparseBooleanArray();
-        mTrackedJobs = new SparseArray<>();
-        try {
-            mAppOpsService.startWatchingMode(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND, null,
-                    new AppOpsWatcher());
-            mPowerWhitelistedUserAppIds = mDeviceIdleController.getAppIdUserWhitelist();
-            mTempWhitelistedAppIds = mDeviceIdleController.getAppIdTempWhitelist();
-        } catch (RemoteException rexc) {
-            // Shouldn't happen as they are in the same process.
-            Slog.e(LOG_TAG, "AppOps or DeviceIdle service not reachable", rexc);
-        }
-        IntentFilter powerWhitelistFilter = new IntentFilter();
-        powerWhitelistFilter.addAction(PowerManager.ACTION_POWER_SAVE_WHITELIST_CHANGED);
-        powerWhitelistFilter.addAction(PowerManager.ACTION_POWER_SAVE_TEMP_WHITELIST_CHANGED);
-        context.registerReceiverAsUser(mDozeWhitelistReceiver, UserHandle.ALL, powerWhitelistFilter,
-                null, null);
+        mForceAppStandbyTracker = ForceAppStandbyTracker.getInstance(context);
+
+        mForceAppStandbyTracker.addListener(mForceAppStandbyListener);
+        mForceAppStandbyTracker.start();
     }
 
     @Override
     public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
-        final int uid = jobStatus.getSourceUid();
-        final String packageName = jobStatus.getSourcePackageName();
-        try {
-            final int mode = mAppOpsService.checkOperation(AppOpsManager.OP_RUN_ANY_IN_BACKGROUND,
-                    uid, packageName);
-            if (mode == AppOpsManager.MODE_ALLOWED) {
-                jobStatus.setBackgroundNotRestrictedConstraintSatisfied(true);
-                return;
-            }
-        } catch (RemoteException rexc) {
-            Slog.e(LOG_TAG, "Cannot reach app ops service", rexc);
-        }
-        jobStatus.setBackgroundNotRestrictedConstraintSatisfied(canRunJobLocked(uid));
-        startTrackingJobLocked(jobStatus);
+        updateSingleJobRestrictionLocked(jobStatus);
     }
 
     @Override
     public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
             boolean forUpdate) {
-        stopTrackingJobLocked(jobStatus);
-    }
-
-    /* Called by JobSchedulerService to report uid state changes between active and idle */
-    public void setUidActiveLocked(int uid, boolean active) {
-        final boolean changed = (active != mForegroundUids.get(uid));
-        if (!changed) {
-            return;
-        }
-        if (DEBUG) {
-            Slog.d(LOG_TAG, "uid " + uid + " going to " + (active ? "fg" : "bg"));
-        }
-        if (active) {
-            mForegroundUids.put(uid, true);
-        } else {
-            mForegroundUids.delete(uid);
-        }
-        if (checkTrackedJobsForUidLocked(uid)) {
-            mStateChangedListener.onControllerStateChanged();
-        }
     }
 
     @Override
     public void dumpControllerStateLocked(final PrintWriter pw, final int filterUid) {
         pw.println("BackgroundJobsController");
-        pw.print("Foreground uids: [");
-        for (int i = 0; i < mForegroundUids.size(); i++) {
-            if (mForegroundUids.valueAt(i)) pw.print(mForegroundUids.keyAt(i) + " ");
-        }
-        pw.println("]");
-        mJobSchedulerService.getJobStore().forEachJob(new JobStore.JobStatusFunctor() {
-            @Override
-            public void process(JobStatus jobStatus) {
-                if (!jobStatus.shouldDump(filterUid)) {
-                    return;
-                }
-                final int uid = jobStatus.getSourceUid();
-                pw.print("  #");
-                jobStatus.printUniqueId(pw);
-                pw.print(" from ");
-                UserHandle.formatUid(pw, uid);
-                pw.print(mForegroundUids.get(uid) ? " foreground" : " background");
-                if (isWhitelistedLocked(uid)) {
-                    pw.print(", whitelisted");
-                }
-                pw.print(": ");
-                pw.print(jobStatus.getSourcePackageName());
-                pw.print(" [background restrictions");
-                final ArraySet<JobStatus> jobsForUid = mTrackedJobs.get(uid);
-                pw.print(jobsForUid != null && jobsForUid.contains(jobStatus) ? " on]" : " off]");
-                if ((jobStatus.satisfiedConstraints
-                        & JobStatus.CONSTRAINT_BACKGROUND_NOT_RESTRICTED) != 0) {
-                    pw.println(" RUNNABLE");
-                } else {
-                    pw.println(" WAITING");
-                }
+
+        mForceAppStandbyTracker.dump(pw, "");
+
+        pw.println("Job state:");
+        mJobSchedulerService.getJobStore().forEachJob((jobStatus) -> {
+            if (!jobStatus.shouldDump(filterUid)) {
+                return;
+            }
+            final int uid = jobStatus.getSourceUid();
+            pw.print("  #");
+            jobStatus.printUniqueId(pw);
+            pw.print(" from ");
+            UserHandle.formatUid(pw, uid);
+            pw.print(mForceAppStandbyTracker.isInForeground(uid) ? " foreground" : " background");
+            if (mForceAppStandbyTracker.isUidPowerSaveWhitelisted(uid) ||
+                    mForceAppStandbyTracker.isUidTempPowerSaveWhitelisted(uid)) {
+                pw.print(", whitelisted");
+            }
+            pw.print(": ");
+            pw.print(jobStatus.getSourcePackageName());
+
+            pw.print(" [RUN_ANY_IN_BACKGROUND ");
+            pw.print(mForceAppStandbyTracker.isRunAnyInBackgroundAppOpsAllowed(
+                    jobStatus.getSourceUid(), jobStatus.getSourcePackageName())
+                    ? "allowed]" : "disallowed]");
+
+            if ((jobStatus.satisfiedConstraints
+                    & JobStatus.CONSTRAINT_BACKGROUND_NOT_RESTRICTED) != 0) {
+                pw.println(" RUNNABLE");
+            } else {
+                pw.println(" WAITING");
             }
         });
     }
 
-    void startTrackingJobLocked(JobStatus jobStatus) {
+    private void updateAllJobRestrictionsLocked() {
+        updateJobRestrictionsLocked(/*filterUid=*/ -1);
+    }
+
+    private void updateJobRestrictionsForUidLocked(int uid) {
+
+        // TODO Use forEachJobForSourceUid() once we have it.
+
+        updateJobRestrictionsLocked(/*filterUid=*/ uid);
+    }
+
+    private void updateJobRestrictionsLocked(int filterUid) {
+        final UpdateJobFunctor updateTrackedJobs =
+                new UpdateJobFunctor(filterUid);
+
+        final long start = DEBUG ? SystemClock.elapsedRealtimeNanos() : 0;
+
+        mJobSchedulerService.getJobStore().forEachJob(updateTrackedJobs);
+
+        final long time = DEBUG ? (SystemClock.elapsedRealtimeNanos() - start) : 0;
+        if (DEBUG) {
+            Slog.d(LOG_TAG, String.format(
+                    "Job status updated: %d/%d checked/total jobs, %d us",
+                    updateTrackedJobs.mCheckedCount,
+                    updateTrackedJobs.mTotalCount,
+                    (time / 1000)
+                    ));
+        }
+
+        if (updateTrackedJobs.mChanged) {
+            mStateChangedListener.onControllerStateChanged();
+        }
+    }
+
+    boolean updateSingleJobRestrictionLocked(JobStatus jobStatus) {
+
         final int uid = jobStatus.getSourceUid();
-        ArraySet<JobStatus> jobsForUid = mTrackedJobs.get(uid);
-        if (jobsForUid == null) {
-            jobsForUid = new ArraySet<>();
-            mTrackedJobs.put(uid, jobsForUid);
-        }
-        jobsForUid.add(jobStatus);
+        final String packageName = jobStatus.getSourcePackageName();
+
+        final boolean canRun = !mForceAppStandbyTracker.areJobsRestricted(uid, packageName);
+
+        return jobStatus.setBackgroundNotRestrictedConstraintSatisfied(canRun);
     }
 
-    void stopTrackingJobLocked(JobStatus jobStatus) {
-        final int uid = jobStatus.getSourceUid();
-        ArraySet<JobStatus> jobsForUid = mTrackedJobs.get(uid);
-        if (jobsForUid != null) {
-            jobsForUid.remove(jobStatus);
-        }
-    }
+    private final class UpdateJobFunctor implements JobStore.JobStatusFunctor {
+        private final int mFilterUid;
 
-    boolean checkAllTrackedJobsLocked() {
-        boolean changed = false;
-        for (int i = 0; i < mTrackedJobs.size(); i++) {
-            changed |= checkTrackedJobsForUidLocked(mTrackedJobs.keyAt(i));
-        }
-        return changed;
-    }
+        boolean mChanged = false;
+        int mTotalCount = 0;
+        int mCheckedCount = 0;
 
-    private boolean checkTrackedJobsForUidLocked(int uid) {
-        final ArraySet<JobStatus> jobsForUid = mTrackedJobs.get(uid);
-        boolean changed = false;
-        if (jobsForUid != null) {
-            for (int i = 0; i < jobsForUid.size(); i++) {
-                JobStatus jobStatus = jobsForUid.valueAt(i);
-                changed |= jobStatus.setBackgroundNotRestrictedConstraintSatisfied(
-                        canRunJobLocked(uid));
-            }
-        }
-        return changed;
-    }
-
-    boolean isWhitelistedLocked(int uid) {
-        return ArrayUtils.contains(mTempWhitelistedAppIds, UserHandle.getAppId(uid))
-                || ArrayUtils.contains(mPowerWhitelistedUserAppIds, UserHandle.getAppId(uid));
-    }
-
-    boolean canRunJobLocked(int uid) {
-        return mForegroundUids.get(uid) || isWhitelistedLocked(uid);
-    }
-
-    private final class AppOpsWatcher extends IAppOpsCallback.Stub {
-        @Override
-        public void opChanged(int op, int uid, String packageName) throws RemoteException {
-            synchronized (mLock) {
-                final int mode = mAppOpsService.checkOperation(op, uid, packageName);
-                if (DEBUG) {
-                    Slog.d(LOG_TAG,
-                            "Appop changed for " + uid + ", " + packageName + " to " + mode);
-                }
-                final boolean shouldTrack = (mode != AppOpsManager.MODE_ALLOWED);
-                UpdateTrackedJobsFunc updateTrackedJobs = new UpdateTrackedJobsFunc(uid,
-                        packageName, shouldTrack);
-                mJobSchedulerService.getJobStore().forEachJob(updateTrackedJobs);
-                if (updateTrackedJobs.mChanged) {
-                    mStateChangedListener.onControllerStateChanged();
-                }
-            }
-        }
-    }
-
-    private final class UpdateTrackedJobsFunc implements JobStore.JobStatusFunctor {
-        private final String mPackageName;
-        private final int mUid;
-        private final boolean mShouldTrack;
-        private boolean mChanged = false;
-
-        UpdateTrackedJobsFunc(int uid, String packageName, boolean shouldTrack) {
-            mUid = uid;
-            mPackageName = packageName;
-            mShouldTrack = shouldTrack;
+        UpdateJobFunctor(int filterUid) {
+            mFilterUid = filterUid;
         }
 
         @Override
         public void process(JobStatus jobStatus) {
-            final String packageName = jobStatus.getSourcePackageName();
-            final int uid = jobStatus.getSourceUid();
-            if (mUid != uid || !mPackageName.equals(packageName)) {
+            mTotalCount++;
+            if ((mFilterUid > 0) && (mFilterUid != jobStatus.getSourceUid())) {
                 return;
             }
-            if (mShouldTrack) {
-                mChanged |= jobStatus.setBackgroundNotRestrictedConstraintSatisfied(
-                        canRunJobLocked(uid));
-                startTrackingJobLocked(jobStatus);
-            } else {
-                mChanged |= jobStatus.setBackgroundNotRestrictedConstraintSatisfied(true);
-                stopTrackingJobLocked(jobStatus);
+            mCheckedCount++;
+            if (updateSingleJobRestrictionLocked(jobStatus)) {
+                mChanged = true;
             }
         }
     }
+
+    private final Listener mForceAppStandbyListener = new Listener() {
+        @Override
+        public void updateAllJobs() {
+            updateAllJobRestrictionsLocked();
+        }
+
+        @Override
+        public void updateJobsForUid(int uid) {
+            updateJobRestrictionsForUidLocked(uid);
+        }
+
+        @Override
+        public void updateJobsForUidPackage(int uid, String packageName) {
+            updateJobRestrictionsForUidLocked(uid);
+        }
+    };
 }
diff --git a/com/android/server/job/controllers/DeviceIdleJobsController.java b/com/android/server/job/controllers/DeviceIdleJobsController.java
index 374ab43..b7eb9e0 100644
--- a/com/android/server/job/controllers/DeviceIdleJobsController.java
+++ b/com/android/server/job/controllers/DeviceIdleJobsController.java
@@ -16,14 +16,19 @@
 
 package com.android.server.job.controllers;
 
+import android.app.job.JobInfo;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
 import android.os.PowerManager;
 import android.os.UserHandle;
 import android.util.ArraySet;
 import android.util.Slog;
+import android.util.SparseBooleanArray;
 
 import com.android.internal.util.ArrayUtils;
 import com.android.server.DeviceIdleController;
@@ -42,11 +47,22 @@
 
     private static final String LOG_TAG = "DeviceIdleJobsController";
     private static final boolean LOG_DEBUG = false;
+    private static final long BACKGROUND_JOBS_DELAY = 3000;
+
+    static final int PROCESS_BACKGROUND_JOBS = 1;
 
     // Singleton factory
     private static Object sCreationLock = new Object();
     private static DeviceIdleJobsController sController;
 
+    /**
+     * These are jobs added with a special flag to indicate that they should be exempted from doze
+     * when the app is temp whitelisted or in the foreground.
+     */
+    private final ArraySet<JobStatus> mAllowInIdleJobs;
+    private final SparseBooleanArray mForegroundUids;
+    private final DeviceIdleUpdateFunctor mDeviceIdleUpdateFunctor;
+    private final DeviceIdleJobsDelayHandler mHandler;
     private final JobSchedulerService mJobSchedulerService;
     private final PowerManager mPowerManager;
     private final DeviceIdleController.LocalService mLocalDeviceIdleController;
@@ -57,14 +73,6 @@
     private boolean mDeviceIdleMode;
     private int[] mDeviceIdleWhitelistAppIds;
     private int[] mPowerSaveTempWhitelistAppIds;
-    // These jobs were added when the app was in temp whitelist, these should be exempted from doze
-    private final ArraySet<JobStatus> mTempWhitelistedJobs;
-
-    final JobStore.JobStatusFunctor mUpdateFunctor = new JobStore.JobStatusFunctor() {
-        @Override public void process(JobStatus jobStatus) {
-            updateTaskStateLocked(jobStatus);
-        }
-    };
 
     /**
      * Returns a singleton for the DeviceIdleJobsController
@@ -108,8 +116,8 @@
                                     + Arrays.toString(mPowerSaveTempWhitelistAppIds));
                         }
                         boolean changed = false;
-                        for (int i = 0; i < mTempWhitelistedJobs.size(); i ++) {
-                            changed |= updateTaskStateLocked(mTempWhitelistedJobs.valueAt(i));
+                        for (int i = 0; i < mAllowInIdleJobs.size(); i++) {
+                            changed |= updateTaskStateLocked(mAllowInIdleJobs.valueAt(i));
                         }
                         if (changed) {
                             mStateChangedListener.onControllerStateChanged();
@@ -125,6 +133,7 @@
         super(jobSchedulerService, context, lock);
 
         mJobSchedulerService = jobSchedulerService;
+        mHandler = new DeviceIdleJobsDelayHandler(context.getMainLooper());
         // Register for device idle mode changes
         mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
         mLocalDeviceIdleController =
@@ -132,7 +141,9 @@
         mDeviceIdleWhitelistAppIds = mLocalDeviceIdleController.getPowerSaveWhitelistUserAppIds();
         mPowerSaveTempWhitelistAppIds =
                 mLocalDeviceIdleController.getPowerSaveTempWhitelistAppIds();
-        mTempWhitelistedJobs = new ArraySet<>();
+        mDeviceIdleUpdateFunctor = new DeviceIdleUpdateFunctor();
+        mAllowInIdleJobs = new ArraySet<>();
+        mForegroundUids = new SparseBooleanArray();
         final IntentFilter filter = new IntentFilter();
         filter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
         filter.addAction(PowerManager.ACTION_LIGHT_DEVICE_IDLE_MODE_CHANGED);
@@ -150,7 +161,20 @@
             }
             mDeviceIdleMode = enabled;
             if (LOG_DEBUG) Slog.d(LOG_TAG, "mDeviceIdleMode=" + mDeviceIdleMode);
-            mJobSchedulerService.getJobStore().forEachJob(mUpdateFunctor);
+            if (enabled) {
+                mHandler.removeMessages(PROCESS_BACKGROUND_JOBS);
+                mJobSchedulerService.getJobStore().forEachJob(mDeviceIdleUpdateFunctor);
+            } else {
+                // When coming out of doze, process all foreground uids immediately, while others
+                // will be processed after a delay of 3 seconds.
+                for (int i = 0; i < mForegroundUids.size(); i++) {
+                    if (mForegroundUids.valueAt(i)) {
+                        mJobSchedulerService.getJobStore().forEachJobForSourceUid(
+                                mForegroundUids.keyAt(i), mDeviceIdleUpdateFunctor);
+                    }
+                }
+                mHandler.sendEmptyMessageDelayed(PROCESS_BACKGROUND_JOBS, BACKGROUND_JOBS_DELAY);
+            }
         }
         // Inform the job scheduler service about idle mode changes
         if (changed) {
@@ -159,11 +183,30 @@
     }
 
     /**
+     *  Called by jobscheduler service to report uid state changes between active and idle
+     */
+    public void setUidActiveLocked(int uid, boolean active) {
+        final boolean changed = (active != mForegroundUids.get(uid));
+        if (!changed) {
+            return;
+        }
+        if (LOG_DEBUG) {
+            Slog.d(LOG_TAG, "uid " + uid + " going " + (active ? "active" : "inactive"));
+        }
+        mForegroundUids.put(uid, active);
+        mDeviceIdleUpdateFunctor.mChanged = false;
+        mJobSchedulerService.getJobStore().forEachJobForSourceUid(uid, mDeviceIdleUpdateFunctor);
+        if (mDeviceIdleUpdateFunctor.mChanged) {
+            mStateChangedListener.onControllerStateChanged();
+        }
+    }
+
+    /**
      * Checks if the given job's scheduling app id exists in the device idle user whitelist.
      */
     boolean isWhitelistedLocked(JobStatus job) {
-        return ArrayUtils.contains(mDeviceIdleWhitelistAppIds,
-                UserHandle.getAppId(job.getSourceUid()));
+        return Arrays.binarySearch(mDeviceIdleWhitelistAppIds,
+                UserHandle.getAppId(job.getSourceUid())) >= 0;
     }
 
     /**
@@ -175,31 +218,33 @@
     }
 
     private boolean updateTaskStateLocked(JobStatus task) {
-        final boolean whitelisted = isWhitelistedLocked(task)
-                || (mTempWhitelistedJobs.contains(task) && isTempWhitelistedLocked(task));
-        final boolean enableTask = !mDeviceIdleMode || whitelisted;
+        final boolean allowInIdle = ((task.getFlags()&JobInfo.FLAG_IMPORTANT_WHILE_FOREGROUND) != 0)
+                && (mForegroundUids.get(task.getSourceUid()) || isTempWhitelistedLocked(task));
+        final boolean whitelisted = isWhitelistedLocked(task);
+        final boolean enableTask = !mDeviceIdleMode || whitelisted || allowInIdle;
         return task.setDeviceNotDozingConstraintSatisfied(enableTask, whitelisted);
     }
 
     @Override
     public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
-        if (isTempWhitelistedLocked(jobStatus)) {
-            mTempWhitelistedJobs.add(jobStatus);
-            jobStatus.setDeviceNotDozingConstraintSatisfied(true, true);
-        } else {
-            updateTaskStateLocked(jobStatus);
+        if ((jobStatus.getFlags()&JobInfo.FLAG_IMPORTANT_WHILE_FOREGROUND) != 0) {
+            mAllowInIdleJobs.add(jobStatus);
         }
+        updateTaskStateLocked(jobStatus);
     }
 
     @Override
     public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
             boolean forUpdate) {
-        mTempWhitelistedJobs.remove(jobStatus);
+        if ((jobStatus.getFlags()&JobInfo.FLAG_IMPORTANT_WHILE_FOREGROUND) != 0) {
+            mAllowInIdleJobs.remove(jobStatus);
+        }
     }
 
     @Override
     public void dumpControllerStateLocked(final PrintWriter pw, final int filterUid) {
         pw.println("DeviceIdleJobsController");
+        pw.println("mDeviceIdleMode=" + mDeviceIdleMode);
         mJobSchedulerService.getJobStore().forEachJob(new JobStore.JobStatusFunctor() {
             @Override public void process(JobStatus jobStatus) {
                 if (!jobStatus.shouldDump(filterUid)) {
@@ -217,8 +262,42 @@
                 if (jobStatus.dozeWhitelisted) {
                     pw.print(" WHITELISTED");
                 }
+                if (mAllowInIdleJobs.contains(jobStatus)) {
+                    pw.print(" ALLOWED_IN_DOZE");
+                }
                 pw.println();
             }
         });
     }
+
+    final class DeviceIdleUpdateFunctor implements JobStore.JobStatusFunctor {
+        boolean mChanged;
+
+        @Override
+        public void process(JobStatus jobStatus) {
+            mChanged |= updateTaskStateLocked(jobStatus);
+        }
+    }
+
+    final class DeviceIdleJobsDelayHandler extends Handler {
+        public DeviceIdleJobsDelayHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case PROCESS_BACKGROUND_JOBS:
+                    // Just process all the jobs, the ones in foreground should already be running.
+                    synchronized (mLock) {
+                        mDeviceIdleUpdateFunctor.mChanged = false;
+                        mJobSchedulerService.getJobStore().forEachJob(mDeviceIdleUpdateFunctor);
+                        if (mDeviceIdleUpdateFunctor.mChanged) {
+                            mStateChangedListener.onControllerStateChanged();
+                        }
+                    }
+                    break;
+            }
+        }
+    }
 }
\ No newline at end of file
diff --git a/com/android/server/job/controllers/JobStatus.java b/com/android/server/job/controllers/JobStatus.java
index a5906cb..e71b8ec 100644
--- a/com/android/server/job/controllers/JobStatus.java
+++ b/com/android/server/job/controllers/JobStatus.java
@@ -34,7 +34,9 @@
 import android.util.Slog;
 import android.util.TimeUtils;
 
+import com.android.server.LocalServices;
 import com.android.server.job.GrantedUriPermissions;
+import com.android.server.job.JobSchedulerInternal;
 import com.android.server.job.JobSchedulerService;
 
 import java.io.PrintWriter;
@@ -120,6 +122,24 @@
     /** How many times this job has failed, used to compute back-off. */
     private final int numFailures;
 
+    /**
+     * Current standby heartbeat when this job was scheduled or last ran.  Used to
+     * pin the runnability check regardless of the job's app moving between buckets.
+     */
+    private final long baseHeartbeat;
+
+    /**
+     * Which app standby bucket this job's app is in.  Updated when the app is moved to a
+     * different bucket.
+     */
+    private int standbyBucket;
+
+    /**
+     * Debugging: timestamp if we ever defer this job based on standby bucketing, this
+     * is when we did so.
+     */
+    private long whenStandbyDeferred;
+
     // Constraints.
     final int requiredConstraints;
     int satisfiedConstraints = 0;
@@ -221,10 +241,13 @@
     }
 
     private JobStatus(JobInfo job, int callingUid, String sourcePackageName,
-            int sourceUserId, String tag, int numFailures, long earliestRunTimeElapsedMillis,
-            long latestRunTimeElapsedMillis, long lastSuccessfulRunTime, long lastFailedRunTime) {
+            int sourceUserId, int standbyBucket, long heartbeat, String tag, int numFailures,
+            long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis,
+            long lastSuccessfulRunTime, long lastFailedRunTime) {
         this.job = job;
         this.callingUid = callingUid;
+        this.standbyBucket = standbyBucket;
+        this.baseHeartbeat = heartbeat;
 
         int tempSourceUid = -1;
         if (sourceUserId != -1 && sourcePackageName != null) {
@@ -283,6 +306,7 @@
     public JobStatus(JobStatus jobStatus) {
         this(jobStatus.getJob(), jobStatus.getUid(),
                 jobStatus.getSourcePackageName(), jobStatus.getSourceUserId(),
+                jobStatus.getStandbyBucket(), jobStatus.getBaseHeartbeat(),
                 jobStatus.getSourceTag(), jobStatus.getNumFailures(),
                 jobStatus.getEarliestRunTime(), jobStatus.getLatestRunTimeElapsed(),
                 jobStatus.getLastSuccessfulRunTime(), jobStatus.getLastFailedRunTime());
@@ -299,13 +323,17 @@
      * {@link android.app.job.JobInfo} time criteria because we can load a persisted periodic job
      * from the {@link com.android.server.job.JobStore} and still want to respect its
      * wallclock runtime rather than resetting it on every boot.
-     * We consider a freshly loaded job to no longer be in back-off.
+     * We consider a freshly loaded job to no longer be in back-off, and the associated
+     * standby bucket is whatever the OS thinks it should be at this moment.
      */
-    public JobStatus(JobInfo job, int callingUid, String sourcePackageName, int sourceUserId,
-            String sourceTag, long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis,
+    public JobStatus(JobInfo job, int callingUid, String sourcePkgName, int sourceUserId,
+            int standbyBucket, long baseHeartbeat, String sourceTag,
+            long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis,
             long lastSuccessfulRunTime, long lastFailedRunTime,
             Pair<Long, Long> persistedExecutionTimesUTC) {
-        this(job, callingUid, sourcePackageName, sourceUserId, sourceTag, 0,
+        this(job, callingUid, sourcePkgName, sourceUserId,
+                standbyBucket, baseHeartbeat,
+                sourceTag, 0,
                 earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis,
                 lastSuccessfulRunTime, lastFailedRunTime);
 
@@ -322,11 +350,13 @@
     }
 
     /** Create a new job to be rescheduled with the provided parameters. */
-    public JobStatus(JobStatus rescheduling, long newEarliestRuntimeElapsedMillis,
+    public JobStatus(JobStatus rescheduling, long newBaseHeartbeat,
+            long newEarliestRuntimeElapsedMillis,
             long newLatestRuntimeElapsedMillis, int backoffAttempt,
             long lastSuccessfulRunTime, long lastFailedRunTime) {
         this(rescheduling.job, rescheduling.getUid(),
                 rescheduling.getSourcePackageName(), rescheduling.getSourceUserId(),
+                rescheduling.getStandbyBucket(), newBaseHeartbeat,
                 rescheduling.getSourceTag(), backoffAttempt, newEarliestRuntimeElapsedMillis,
                 newLatestRuntimeElapsedMillis,
                 lastSuccessfulRunTime, lastFailedRunTime);
@@ -335,11 +365,12 @@
     /**
      * Create a newly scheduled job.
      * @param callingUid Uid of the package that scheduled this job.
-     * @param sourcePackageName Package name on whose behalf this job is scheduled. Null indicates
+     * @param sourcePkg Package name on whose behalf this job is scheduled. Null indicates
      *                          the calling package is the source.
      * @param sourceUserId User id for whom this job is scheduled. -1 indicates this is same as the
+     *     caller.
      */
-    public static JobStatus createFromJobInfo(JobInfo job, int callingUid, String sourcePackageName,
+    public static JobStatus createFromJobInfo(JobInfo job, int callingUid, String sourcePkg,
             int sourceUserId, String tag) {
         final long elapsedNow = sElapsedRealtimeClock.millis();
         final long earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis;
@@ -352,7 +383,14 @@
             latestRunTimeElapsedMillis = job.hasLateConstraint() ?
                     elapsedNow + job.getMaxExecutionDelayMillis() : NO_LATEST_RUNTIME;
         }
-        return new JobStatus(job, callingUid, sourcePackageName, sourceUserId, tag, 0,
+        String jobPackage = (sourcePkg != null) ? sourcePkg : job.getService().getPackageName();
+
+        int standbyBucket = JobSchedulerService.standbyBucketForPackage(jobPackage,
+                sourceUserId, elapsedNow);
+        JobSchedulerInternal js = LocalServices.getService(JobSchedulerInternal.class);
+        long currentHeartbeat = js != null ? js.currentHeartbeat() : 0;
+        return new JobStatus(job, callingUid, sourcePkg, sourceUserId,
+                standbyBucket, currentHeartbeat, tag, 0,
                 earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis,
                 0 /* lastSuccessfulRunTime */, 0 /* lastFailedRunTime */);
     }
@@ -528,6 +566,29 @@
         return UserHandle.getUserId(callingUid);
     }
 
+    public int getStandbyBucket() {
+        return standbyBucket;
+    }
+
+    public long getBaseHeartbeat() {
+        return baseHeartbeat;
+    }
+
+    // Called only by the standby monitoring code
+    public void setStandbyBucket(int newBucket) {
+        standbyBucket = newBucket;
+    }
+
+    // Called only by the standby monitoring code
+    public long getWhenStandbyDeferred() {
+        return whenStandbyDeferred;
+    }
+
+    // Called only by the standby monitoring code
+    public void setWhenStandbyDeferred(long now) {
+        whenStandbyDeferred = now;
+    }
+
     public String getSourceTag() {
         return sourceTag;
     }
@@ -950,6 +1011,19 @@
         }
     }
 
+    // normalized bucket indices, not the AppStandby constants
+    private String bucketName(int bucket) {
+        switch (bucket) {
+            case 0: return "ACTIVE";
+            case 1: return "WORKING_SET";
+            case 2: return "FREQUENT";
+            case 3: return "RARE";
+            case 4: return "NEVER";
+            default:
+                return "Unknown: " + bucket;
+        }
+    }
+
     // Dumpsys infrastructure
     public void dump(PrintWriter pw, String prefix, boolean full, long elapsedRealtimeMillis) {
         pw.print(prefix); UserHandle.formatUid(pw, callingUid);
@@ -1098,6 +1172,8 @@
                 dumpJobWorkItem(pw, prefix, executingWork.get(i), i);
             }
         }
+        pw.print(prefix); pw.print("Standby bucket: ");
+        pw.println(bucketName(standbyBucket));
         pw.print(prefix); pw.print("Enqueue time: ");
         TimeUtils.formatDuration(enqueueTime, elapsedRealtimeMillis, pw);
         pw.println();
diff --git a/com/android/server/location/ContextHubClientBroker.java b/com/android/server/location/ContextHubClientBroker.java
new file mode 100644
index 0000000..41d9feb
--- /dev/null
+++ b/com/android/server/location/ContextHubClientBroker.java
@@ -0,0 +1,226 @@
+/*
+ * Copyright 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.location;
+
+import android.content.Context;
+import android.hardware.contexthub.V1_0.ContextHubMsg;
+import android.hardware.contexthub.V1_0.IContexthub;
+import android.hardware.contexthub.V1_0.Result;
+import android.hardware.location.ContextHubTransaction;
+import android.hardware.location.IContextHubClient;
+import android.hardware.location.IContextHubClientCallback;
+import android.hardware.location.NanoAppMessage;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * A class that acts as a broker for the ContextHubClient, which handles messaging and life-cycle
+ * notification callbacks. This class implements the IContextHubClient object, and the implemented
+ * APIs must be thread-safe.
+ *
+ * @hide
+ */
+public class ContextHubClientBroker extends IContextHubClient.Stub
+        implements IBinder.DeathRecipient {
+    private static final String TAG = "ContextHubClientBroker";
+
+    /*
+     * The context of the service.
+     */
+    private final Context mContext;
+
+    /*
+     * The proxy to talk to the Context Hub HAL.
+     */
+    private final IContexthub mContextHubProxy;
+
+    /*
+     * The manager that registered this client.
+     */
+    private final ContextHubClientManager mClientManager;
+
+    /*
+     * The ID of the hub that this client is attached to.
+     */
+    private final int mAttachedContextHubId;
+
+    /*
+     * The host end point ID of this client.
+     */
+    private final short mHostEndPointId;
+
+    /*
+     * The remote callback interface for this client.
+     */
+    private final IContextHubClientCallback mCallbackInterface;
+
+    /*
+     * false if the connection has been closed by the client, true otherwise.
+     */
+    private final AtomicBoolean mConnectionOpen = new AtomicBoolean(true);
+
+    /* package */ ContextHubClientBroker(
+            Context context, IContexthub contextHubProxy, ContextHubClientManager clientManager,
+            int contextHubId, short hostEndPointId, IContextHubClientCallback callback) {
+        mContext = context;
+        mContextHubProxy = contextHubProxy;
+        mClientManager = clientManager;
+        mAttachedContextHubId = contextHubId;
+        mHostEndPointId = hostEndPointId;
+        mCallbackInterface = callback;
+    }
+
+    /**
+     * Attaches a death recipient for this client
+     *
+     * @throws RemoteException if the client has already died
+     */
+    /* package */ void attachDeathRecipient() throws RemoteException {
+        mCallbackInterface.asBinder().linkToDeath(this, 0 /* flags */);
+    }
+
+    /**
+     * Sends from this client to a nanoapp.
+     *
+     * @param message the message to send
+     * @return the error code of sending the message
+     */
+    @ContextHubTransaction.Result
+    @Override
+    public int sendMessageToNanoApp(NanoAppMessage message) {
+        ContextHubServiceUtil.checkPermissions(mContext);
+
+        int result;
+        if (mConnectionOpen.get()) {
+            ContextHubMsg messageToNanoApp = ContextHubServiceUtil.createHidlContextHubMessage(
+                    mHostEndPointId, message);
+
+            try {
+                result = mContextHubProxy.sendMessageToHub(mAttachedContextHubId, messageToNanoApp);
+            } catch (RemoteException e) {
+                Log.e(TAG, "RemoteException in sendMessageToNanoApp (target hub ID = "
+                        + mAttachedContextHubId + ")", e);
+                result = Result.UNKNOWN_FAILURE;
+            }
+        } else {
+            Log.e(TAG, "Failed to send message to nanoapp: client connection is closed");
+            result = Result.UNKNOWN_FAILURE;
+        }
+
+        return ContextHubServiceUtil.toTransactionResult(result);
+    }
+
+    /**
+     * Closes the connection for this client with the service.
+     */
+    @Override
+    public void close() {
+        if (mConnectionOpen.getAndSet(false)) {
+            mClientManager.unregisterClient(mHostEndPointId);
+        }
+    }
+
+    /**
+     * Invoked when the underlying binder of this broker has died at the client process.
+     */
+    public void binderDied() {
+        try {
+            IContextHubClient.Stub.asInterface(this).close();
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException while closing client on death", e);
+        }
+    }
+
+    /**
+     * @return the ID of the context hub this client is attached to
+     */
+    /* package */ int getAttachedContextHubId() {
+        return mAttachedContextHubId;
+    }
+
+    /**
+     * @return the host endpoint ID of this client
+     */
+    /* package */ short getHostEndPointId() {
+        return mHostEndPointId;
+    }
+
+    /**
+     * Sends a message to the client associated with this object.
+     *
+     * @param message the message that came from a nanoapp
+     */
+    /* package */ void sendMessageToClient(NanoAppMessage message) {
+        if (mConnectionOpen.get()) {
+            try {
+                mCallbackInterface.onMessageFromNanoApp(message);
+            } catch (RemoteException e) {
+                Log.e(TAG, "RemoteException while sending message to client (host endpoint ID = "
+                        + mHostEndPointId + ")", e);
+            }
+        }
+    }
+
+    /**
+     * Handles a nanoapp load event.
+     *
+     * @param nanoAppId the ID of the nanoapp that was loaded.
+     */
+    /* package */ void onNanoAppLoaded(long nanoAppId) {
+        if (mConnectionOpen.get()) {
+            try {
+                mCallbackInterface.onNanoAppLoaded(nanoAppId);
+            } catch (RemoteException e) {
+                Log.e(TAG, "RemoteException while calling onNanoAppLoaded on client"
+                        + " (host endpoint ID = " + mHostEndPointId + ")", e);
+            }
+        }
+    }
+
+    /**
+     * Handles a nanoapp unload event.
+     *
+     * @param nanoAppId the ID of the nanoapp that was unloaded.
+     */
+    /* package */ void onNanoAppUnloaded(long nanoAppId) {
+        if (mConnectionOpen.get()) {
+            try {
+                mCallbackInterface.onNanoAppUnloaded(nanoAppId);
+            } catch (RemoteException e) {
+                Log.e(TAG, "RemoteException while calling onNanoAppUnloaded on client"
+                        + " (host endpoint ID = " + mHostEndPointId + ")", e);
+            }
+        }
+    }
+
+    /**
+     * Handles a hub reset for this client.
+     */
+    /* package */ void onHubReset() {
+        if (mConnectionOpen.get()) {
+            try {
+                mCallbackInterface.onHubReset();
+            } catch (RemoteException e) {
+                Log.e(TAG, "RemoteException while calling onHubReset on client" +
+                        " (host endpoint ID = " + mHostEndPointId + ")", e);
+            }
+        }
+    }
+}
diff --git a/com/android/server/location/ContextHubClientManager.java b/com/android/server/location/ContextHubClientManager.java
new file mode 100644
index 0000000..d58a746
--- /dev/null
+++ b/com/android/server/location/ContextHubClientManager.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright 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.location;
+
+import android.content.Context;
+import android.hardware.contexthub.V1_0.ContextHubMsg;
+import android.hardware.contexthub.V1_0.IContexthub;
+import android.hardware.location.IContextHubClient;
+import android.hardware.location.IContextHubClientCallback;
+import android.hardware.location.NanoAppMessage;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.NoSuchElementException;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Consumer;
+
+/**
+ * A class that manages registration/unregistration of clients and manages messages to/from clients.
+ *
+ * @hide
+ */
+/* package */ class ContextHubClientManager {
+    private static final String TAG = "ContextHubClientManager";
+
+    /*
+     * The maximum host endpoint ID value that a client can be assigned.
+     */
+    private static final int MAX_CLIENT_ID = 0x7fff;
+
+    /*
+     * Local flag to enable debug logging.
+     */
+    private static final boolean DEBUG_LOG_ENABLED = true;
+
+    /*
+     * The context of the service.
+     */
+    private final Context mContext;
+
+    /*
+     * The proxy to talk to the Context Hub.
+     */
+    private final IContexthub mContextHubProxy;
+
+    /*
+     * A mapping of host endpoint IDs to the ContextHubClientBroker object of registered clients.
+     * A concurrent data structure is used since the registration/unregistration can occur in
+     * multiple threads.
+     */
+    private final ConcurrentHashMap<Short, ContextHubClientBroker> mHostEndPointIdToClientMap =
+            new ConcurrentHashMap<>();
+
+    /*
+     * The next host endpoint ID to start iterating for the next available host endpoint ID.
+     */
+    private int mNextHostEndpointId = 0;
+
+    /* package */ ContextHubClientManager(
+            Context context, IContexthub contextHubProxy) {
+        mContext = context;
+        mContextHubProxy = contextHubProxy;
+    }
+
+    /**
+     * Registers a new client with the service.
+     *
+     * @param clientCallback the callback interface of the client to register
+     * @param contextHubId   the ID of the hub this client is attached to
+     *
+     * @return the client interface
+     *
+     * @throws IllegalStateException if max number of clients have already registered
+     */
+    /* package */ IContextHubClient registerClient(
+            IContextHubClientCallback clientCallback, int contextHubId) {
+        ContextHubClientBroker broker = createNewClientBroker(clientCallback, contextHubId);
+
+        try {
+            broker.attachDeathRecipient();
+        } catch (RemoteException e) {
+            // The client process has died, so we close the connection and return null.
+            Log.e(TAG, "Failed to attach death recipient to client");
+            broker.close();
+            return null;
+        }
+
+        Log.d(TAG, "Registered client with host endpoint ID " + broker.getHostEndPointId());
+        return IContextHubClient.Stub.asInterface(broker);
+    }
+
+    /**
+     * Handles a message sent from a nanoapp.
+     *
+     * @param contextHubId the ID of the hub where the nanoapp sent the message from
+     * @param message      the message send by a nanoapp
+     */
+    /* package */ void onMessageFromNanoApp(int contextHubId, ContextHubMsg message) {
+        NanoAppMessage clientMessage = ContextHubServiceUtil.createNanoAppMessage(message);
+
+        if (DEBUG_LOG_ENABLED) {
+            String targetAudience = clientMessage.isBroadcastMessage() ? "broadcast" : "unicast";
+            Log.v(TAG, "Received a " + targetAudience + " message from nanoapp 0x"
+                    + Long.toHexString(clientMessage.getNanoAppId()));
+        }
+
+        if (clientMessage.isBroadcastMessage()) {
+            broadcastMessage(contextHubId, clientMessage);
+        } else {
+            ContextHubClientBroker proxy = mHostEndPointIdToClientMap.get(message.hostEndPoint);
+            if (proxy != null) {
+                proxy.sendMessageToClient(clientMessage);
+            } else {
+                Log.e(TAG, "Cannot send message to unregistered client (host endpoint ID = "
+                        + message.hostEndPoint + ")");
+            }
+        }
+    }
+
+    /**
+     * Unregisters a client from the service.
+     *
+     * This method should be invoked as a result of a client calling the ContextHubClient.close(),
+     * or if the client process has died.
+     *
+     * @param hostEndPointId the host endpoint ID of the client that has died
+     */
+    /* package */ void unregisterClient(short hostEndPointId) {
+        if (mHostEndPointIdToClientMap.remove(hostEndPointId) != null) {
+            Log.d(TAG, "Unregistered client with host endpoint ID " + hostEndPointId);
+        } else {
+            Log.e(TAG, "Cannot unregister non-existing client with host endpoint ID "
+                    + hostEndPointId);
+        }
+    }
+
+    /**
+     * Handles a nanoapp load event.
+     *
+     * @param contextHubId the ID of the hub where the nanoapp was loaded.
+     * @param nanoAppId    the ID of the nanoapp that was loaded.
+     */
+    /* package */ void onNanoAppLoaded(int contextHubId, long nanoAppId) {
+        forEachClientOfHub(contextHubId, client -> client.onNanoAppLoaded(nanoAppId));
+    }
+
+    /**
+     * Handles a nanoapp unload event.
+     *
+     * @param contextHubId the ID of the hub where the nanoapp was unloaded.
+     * @param nanoAppId    the ID of the nanoapp that was unloaded.
+     */
+    /* package */ void onNanoAppUnloaded(int contextHubId, long nanoAppId) {
+        forEachClientOfHub(contextHubId, client -> client.onNanoAppUnloaded(nanoAppId));
+    }
+
+    /**
+     * Handles a hub reset.
+     *
+     * @param contextHubId the ID of the hub that has reset.
+     */
+    /* package */ void onHubReset(int contextHubId) {
+        forEachClientOfHub(contextHubId, client -> client.onHubReset());
+    }
+
+    /**
+     * Creates a new ContextHubClientBroker object for a client and registers it with the client
+     * manager.
+     *
+     * @param clientCallback the callback interface of the client to register
+     * @param contextHubId   the ID of the hub this client is attached to
+     *
+     * @return the ContextHubClientBroker object
+     *
+     * @throws IllegalStateException if max number of clients have already registered
+     */
+    private synchronized ContextHubClientBroker createNewClientBroker(
+            IContextHubClientCallback clientCallback, int contextHubId) {
+        if (mHostEndPointIdToClientMap.size() == MAX_CLIENT_ID + 1) {
+            throw new IllegalStateException("Could not register client - max limit exceeded");
+        }
+
+        ContextHubClientBroker broker = null;
+        int id = mNextHostEndpointId;
+        for (int i = 0; i <= MAX_CLIENT_ID; i++) {
+            if (!mHostEndPointIdToClientMap.containsKey(id)) {
+                broker = new ContextHubClientBroker(
+                        mContext, mContextHubProxy, this, contextHubId, (short)id, clientCallback);
+                mHostEndPointIdToClientMap.put((short)id, broker);
+                mNextHostEndpointId = (id == MAX_CLIENT_ID) ? 0 : id + 1;
+                break;
+            }
+
+            id = (id == MAX_CLIENT_ID) ? 0 : id + 1;
+        }
+
+        return broker;
+    }
+
+    /**
+     * Broadcasts a message from a nanoapp to all clients attached to the associated hub.
+     *
+     * @param contextHubId the ID of the hub where the nanoapp sent the message from
+     * @param message      the message send by a nanoapp
+     */
+    private void broadcastMessage(int contextHubId, NanoAppMessage message) {
+        forEachClientOfHub(contextHubId, client -> client.sendMessageToClient(message));
+    }
+
+    /**
+     * Runs a command for each client that is attached to a hub with the given ID.
+     *
+     * @param contextHubId the ID of the hub
+     * @param callback     the command to invoke for the client
+     */
+    private void forEachClientOfHub(int contextHubId, Consumer<ContextHubClientBroker> callback) {
+        for (ContextHubClientBroker broker : mHostEndPointIdToClientMap.values()) {
+            if (broker.getAttachedContextHubId() == contextHubId) {
+                callback.accept(broker);
+            }
+        }
+    }
+}
diff --git a/com/android/server/location/ContextHubService.java b/com/android/server/location/ContextHubService.java
index 5e9f355..e08c659 100644
--- a/com/android/server/location/ContextHubService.java
+++ b/com/android/server/location/ContextHubService.java
@@ -16,17 +16,29 @@
 
 package com.android.server.location;
 
-import android.Manifest;
 import android.content.Context;
-import android.content.pm.PackageManager;
+import android.hardware.contexthub.V1_0.AsyncEventType;
+import android.hardware.contexthub.V1_0.ContextHub;
+import android.hardware.contexthub.V1_0.ContextHubMsg;
+import android.hardware.contexthub.V1_0.HubAppInfo;
+import android.hardware.contexthub.V1_0.IContexthub;
+import android.hardware.contexthub.V1_0.IContexthubCallback;
+import android.hardware.contexthub.V1_0.Result;
+import android.hardware.contexthub.V1_0.TransactionResult;
 import android.hardware.location.ContextHubInfo;
-import android.hardware.location.ContextHubManager;
 import android.hardware.location.ContextHubMessage;
-import android.hardware.location.IContextHubService;
+import android.hardware.location.ContextHubTransaction;
 import android.hardware.location.IContextHubCallback;
-import android.hardware.location.NanoAppFilter;
+import android.hardware.location.IContextHubClient;
+import android.hardware.location.IContextHubClientCallback;
+import android.hardware.location.IContextHubService;
+import android.hardware.location.IContextHubTransactionCallback;
 import android.hardware.location.NanoApp;
+import android.hardware.location.NanoAppBinary;
+import android.hardware.location.NanoAppFilter;
 import android.hardware.location.NanoAppInstanceInfo;
+import android.hardware.location.NanoAppMessage;
+import android.hardware.location.NanoAppState;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.util.Log;
@@ -38,65 +50,230 @@
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.util.ArrayList;
-import java.util.concurrent.ConcurrentHashMap;
+import java.util.Collections;
 import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.concurrent.ConcurrentHashMap;
 
 /**
  * @hide
  */
 public class ContextHubService extends IContextHubService.Stub {
     private static final String TAG = "ContextHubService";
-    private static final String HARDWARE_PERMISSION = Manifest.permission.LOCATION_HARDWARE;
-    private static final String ENFORCE_HW_PERMISSION_MESSAGE = "Permission '"
-        + HARDWARE_PERMISSION + "' not granted to access ContextHub Hardware";
 
-    public static final int ANY_HUB             = -1;
-    public static final int MSG_LOAD_NANO_APP   = 3;
+    /*
+     * Constants for the type of transaction that is defined by ContextHubService.
+     * This is used to report the transaction callback to clients, and is different from
+     * ContextHubTransaction.Type.
+     */
+    public static final int MSG_ENABLE_NANO_APP = 1;
+    public static final int MSG_DISABLE_NANO_APP = 2;
+    public static final int MSG_LOAD_NANO_APP = 3;
     public static final int MSG_UNLOAD_NANO_APP = 4;
+    public static final int MSG_QUERY_NANO_APPS = 5;
+    public static final int MSG_QUERY_MEMORY = 6;
+    public static final int MSG_HUB_RESET = 7;
 
     private static final String PRE_LOADED_GENERIC_UNKNOWN = "Preloaded app, unknown";
     private static final String PRE_LOADED_APP_NAME = PRE_LOADED_GENERIC_UNKNOWN;
     private static final String PRE_LOADED_APP_PUBLISHER = PRE_LOADED_GENERIC_UNKNOWN;
     private static final int PRE_LOADED_APP_MEM_REQ = 0;
 
-    private static final int MSG_HEADER_SIZE = 4;
-    private static final int HEADER_FIELD_MSG_TYPE = 0;
-    private static final int HEADER_FIELD_MSG_VERSION = 1;
-    private static final int HEADER_FIELD_HUB_HANDLE = 2;
-    private static final int HEADER_FIELD_APP_INSTANCE = 3;
-
-    private static final int HEADER_FIELD_LOAD_APP_ID_LO = MSG_HEADER_SIZE;
-    private static final int HEADER_FIELD_LOAD_APP_ID_HI = MSG_HEADER_SIZE + 1;
-    private static final int MSG_LOAD_APP_HEADER_SIZE = MSG_HEADER_SIZE + 2;
-
     private static final int OS_APP_INSTANCE = -1;
 
     private final Context mContext;
+
+    // TODO(b/69270990): Remove once old ContextHubManager API is deprecated
+    // Service cache maintaining of instance ID to nanoapp infos
     private final ConcurrentHashMap<Integer, NanoAppInstanceInfo> mNanoAppHash =
             new ConcurrentHashMap<>();
+    // The next available instance ID (managed by the service) to assign to a nanoapp
+    private int mNextAvailableInstanceId = 0;
+    // A map of the long nanoapp ID to instance ID managed by the service
+    private final ConcurrentHashMap<Long, Integer> mNanoAppIdToInstanceMap =
+            new ConcurrentHashMap<>();
+
     private final ContextHubInfo[] mContextHubInfo;
     private final RemoteCallbackList<IContextHubCallback> mCallbacksList =
             new RemoteCallbackList<>();
 
-    private native int nativeSendMessage(int[] header, byte[] data);
-    private native ContextHubInfo[] nativeInitialize();
+    // Proxy object to communicate with the Context Hub HAL
+    private final IContexthub mContextHubProxy;
+
+    // The manager for transaction queue
+    private final ContextHubTransactionManager mTransactionManager;
+
+    // The manager for sending messages to/from clients
+    private final ContextHubClientManager mClientManager;
+
+    // The default client for old API clients
+    private final Map<Integer, IContextHubClient> mDefaultClientMap;
+
+    /**
+     * Class extending the callback to register with a Context Hub.
+     */
+    private class ContextHubServiceCallback extends IContexthubCallback.Stub {
+        private final int mContextHubId;
+
+        ContextHubServiceCallback(int contextHubId) {
+            mContextHubId = contextHubId;
+        }
+
+        @Override
+        public void handleClientMsg(ContextHubMsg message) {
+            handleClientMessageCallback(mContextHubId, message);
+        }
+
+        @Override
+        public void handleTxnResult(int transactionId, int result) {
+            handleTransactionResultCallback(mContextHubId, transactionId, result);
+        }
+
+        @Override
+        public void handleHubEvent(int eventType) {
+            handleHubEventCallback(mContextHubId, eventType);
+        }
+
+        @Override
+        public void handleAppAbort(long nanoAppId, int abortCode) {
+            handleAppAbortCallback(mContextHubId, nanoAppId, abortCode);
+        }
+
+        @Override
+        public void handleAppsInfo(ArrayList<HubAppInfo> nanoAppInfoList) {
+            handleQueryAppsCallback(mContextHubId, nanoAppInfoList);
+        }
+    }
 
     public ContextHubService(Context context) {
         mContext = context;
-        mContextHubInfo = nativeInitialize();
+
+        mContextHubProxy = getContextHubProxy();
+        if (mContextHubProxy == null) {
+            mTransactionManager = null;
+            mClientManager = null;
+            mDefaultClientMap = Collections.EMPTY_MAP;
+            mContextHubInfo = new ContextHubInfo[0];
+            return;
+        }
+
+        mClientManager = new ContextHubClientManager(mContext, mContextHubProxy);
+        mTransactionManager = new ContextHubTransactionManager(mContextHubProxy, mClientManager);
+
+        List<ContextHub> hubList;
+        try {
+            hubList = mContextHubProxy.getHubs();
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException while getting Context Hub info", e);
+            hubList = Collections.emptyList();
+        }
+        mContextHubInfo = ContextHubServiceUtil.createContextHubInfoArray(hubList);
+
+        HashMap<Integer, IContextHubClient> defaultClientMap = new HashMap<>();
+        for (ContextHubInfo contextHubInfo : mContextHubInfo) {
+            int contextHubId = contextHubInfo.getId();
+
+            IContextHubClient client = mClientManager.registerClient(
+                    createDefaultClientCallback(contextHubId), contextHubId);
+            defaultClientMap.put(contextHubId, client);
+        }
+        mDefaultClientMap = Collections.unmodifiableMap(defaultClientMap);
+
+        for (ContextHubInfo contextHubInfo : mContextHubInfo) {
+            int contextHubId = contextHubInfo.getId();
+            try {
+                mContextHubProxy.registerCallback(
+                        contextHubId, new ContextHubServiceCallback(contextHubId));
+            } catch (RemoteException e) {
+                Log.e(TAG, "RemoteException while registering service callback for hub (ID = "
+                        + contextHubId + ")", e);
+            }
+        }
+
+        // Do a query to initialize the service cache list of nanoapps
+        // TODO(b/69270990): Remove this when old API is deprecated
+        for (ContextHubInfo contextHubInfo : mContextHubInfo) {
+            queryNanoAppsInternal(contextHubInfo.getId());
+        }
 
         for (int i = 0; i < mContextHubInfo.length; i++) {
             Log.d(TAG, "ContextHub[" + i + "] id: " + mContextHubInfo[i].getId()
-                  + ", name:  " + mContextHubInfo[i].getName());
+                    + ", name:  " + mContextHubInfo[i].getName());
         }
     }
 
+    /**
+     * Creates a default client callback for old API clients.
+     *
+     * @param contextHubId the ID of the hub to attach this client to
+     * @return the internal callback interface
+     */
+    private IContextHubClientCallback createDefaultClientCallback(int contextHubId) {
+        return new IContextHubClientCallback.Stub() {
+            @Override
+            public void onMessageFromNanoApp(NanoAppMessage message) {
+                int nanoAppInstanceId =
+                        mNanoAppIdToInstanceMap.containsKey(message.getNanoAppId()) ?
+                        mNanoAppIdToInstanceMap.get(message.getNanoAppId()) : -1;
+
+                onMessageReceiptOldApi(
+                        message.getMessageType(), contextHubId, nanoAppInstanceId,
+                        message.getMessageBody());
+            }
+
+            @Override
+            public void onHubReset() {
+                byte[] data = {TransactionResult.SUCCESS};
+                onMessageReceiptOldApi(MSG_HUB_RESET, contextHubId, OS_APP_INSTANCE, data);
+            }
+
+            @Override
+            public void onNanoAppAborted(long nanoAppId, int abortCode) {
+            }
+
+            @Override
+            public void onNanoAppLoaded(long nanoAppId) {
+            }
+
+            @Override
+            public void onNanoAppUnloaded(long nanoAppId) {
+            }
+
+            @Override
+            public void onNanoAppEnabled(long nanoAppId) {
+            }
+
+            @Override
+            public void onNanoAppDisabled(long nanoAppId) {
+            }
+        };
+    }
+
+    /**
+     * @return the IContexthub proxy interface
+     */
+    private IContexthub getContextHubProxy() {
+        IContexthub proxy = null;
+        try {
+            proxy = IContexthub.getService(true /* retry */);
+        } catch (RemoteException e) {
+            Log.e(TAG, "RemoteException while attaching to Context Hub HAL proxy", e);
+        } catch (NoSuchElementException e) {
+            Log.i(TAG, "Context Hub HAL service not found");
+        }
+
+        return proxy;
+    }
+
     @Override
     public int registerCallback(IContextHubCallback callback) throws RemoteException {
         checkPermissions();
         mCallbacksList.register(callback);
+
         Log.d(TAG, "Added callback, total callbacks " +
-              mCallbacksList.getRegisteredCallbackCount());
+                mCallbacksList.getRegisteredCallbackCount());
         return 0;
     }
 
@@ -109,29 +286,112 @@
         for (int i = 0; i < returnArray.length; ++i) {
             returnArray[i] = i;
             Log.d(TAG, String.format("Hub %s is mapped to %d",
-                                     mContextHubInfo[i].getName(), returnArray[i]));
+                    mContextHubInfo[i].getName(), returnArray[i]));
         }
 
         return returnArray;
     }
 
     @Override
-    public ContextHubInfo getContextHubInfo(int contextHubHandle) throws RemoteException {
+    public ContextHubInfo getContextHubInfo(int contextHubId) throws RemoteException {
         checkPermissions();
-        if (!(contextHubHandle >= 0 && contextHubHandle < mContextHubInfo.length)) {
-            Log.e(TAG, "Invalid context hub handle " + contextHubHandle);
+        if (!(contextHubId >= 0 && contextHubId < mContextHubInfo.length)) {
+            Log.e(TAG, "Invalid context hub handle " + contextHubId);
             return null; // null means fail
         }
 
-        return mContextHubInfo[contextHubHandle];
+        return mContextHubInfo[contextHubId];
+    }
+
+    /**
+     * Creates an internal load transaction callback to be used for old API clients
+     *
+     * @param contextHubId  the ID of the hub to load the binary
+     * @param nanoAppBinary the binary to load
+     * @return the callback interface
+     */
+    private IContextHubTransactionCallback createLoadTransactionCallback(
+            int contextHubId, NanoAppBinary nanoAppBinary) {
+        return new IContextHubTransactionCallback.Stub() {
+            @Override
+            public void onTransactionComplete(int result) {
+                handleLoadResponseOldApi(contextHubId, result, nanoAppBinary);
+            }
+
+            @Override
+            public void onQueryResponse(int result, List<NanoAppState> nanoAppStateList) {
+            }
+        };
+    }
+
+    /**
+     * Creates an internal unload transaction callback to be used for old API clients
+     *
+     * @param contextHubId the ID of the hub to unload the nanoapp
+     * @param nanoAppId    the ID of the nanoapp to unload
+     * @return the callback interface
+     */
+    private IContextHubTransactionCallback createUnloadTransactionCallback(
+            int contextHubId, long nanoAppId) {
+        return new IContextHubTransactionCallback.Stub() {
+            @Override
+            public void onTransactionComplete(int result) {
+                handleUnloadResponseOldApi(contextHubId, result, nanoAppId);
+            }
+
+            @Override
+            public void onQueryResponse(int result, List<NanoAppState> nanoAppStateList) {
+            }
+        };
+    }
+
+    /**
+     * Creates an internal query transaction callback to be used for old API clients
+     *
+     * @param contextHubId the ID of the hub to query
+     * @return the callback interface
+     */
+    private IContextHubTransactionCallback createQueryTransactionCallback(int contextHubId) {
+        return new IContextHubTransactionCallback.Stub() {
+            @Override
+            public void onTransactionComplete(int result) {
+            }
+
+            @Override
+            public void onQueryResponse(int result, List<NanoAppState> nanoAppStateList) {
+                byte[] data = {(byte) result};
+                onMessageReceiptOldApi(MSG_QUERY_NANO_APPS, contextHubId, OS_APP_INSTANCE, data);
+            }
+        };
+    }
+
+    /**
+     * Adds a new transaction to the transaction manager queue
+     *
+     * @param transaction the transaction to add
+     * @return the result of adding the transaction
+     */
+    private int addTransaction(ContextHubServiceTransaction transaction) {
+        int result = Result.OK;
+        try {
+            mTransactionManager.addTransaction(transaction);
+        } catch (IllegalStateException e) {
+            Log.e(TAG, e.getMessage());
+            result = Result.TRANSACTION_PENDING; /* failed */
+        }
+
+        return result;
     }
 
     @Override
-    public int loadNanoApp(int contextHubHandle, NanoApp app) throws RemoteException {
+    public int loadNanoApp(int contextHubId, NanoApp app) throws RemoteException {
         checkPermissions();
+        if (mContextHubProxy == null) {
+            return -1;
+        }
 
-        if (!(contextHubHandle >= 0 && contextHubHandle < mContextHubInfo.length)) {
-            Log.e(TAG, "Invalid contextHubhandle " + contextHubHandle);
+        if (!(contextHubId >= 0 && contextHubId < mContextHubInfo.length)) {
+            Log.e(TAG, "Invalid contextHubhandle " + contextHubId);
             return -1;
         }
         if (app == null) {
@@ -139,20 +399,17 @@
             return -1;
         }
 
-        int[] msgHeader = new int[MSG_LOAD_APP_HEADER_SIZE];
-        msgHeader[HEADER_FIELD_HUB_HANDLE] = contextHubHandle;
-        msgHeader[HEADER_FIELD_APP_INSTANCE] = OS_APP_INSTANCE;
-        msgHeader[HEADER_FIELD_MSG_VERSION] = 0;
-        msgHeader[HEADER_FIELD_MSG_TYPE] = MSG_LOAD_NANO_APP;
+        // Create an internal IContextHubTransactionCallback for the old API clients
+        NanoAppBinary nanoAppBinary = new NanoAppBinary(app.getAppBinary());
+        IContextHubTransactionCallback onCompleteCallback =
+                createLoadTransactionCallback(contextHubId, nanoAppBinary);
 
-        long appId = app.getAppId();
+        ContextHubServiceTransaction transaction = mTransactionManager.createLoadTransaction(
+                contextHubId, nanoAppBinary, onCompleteCallback);
 
-        msgHeader[HEADER_FIELD_LOAD_APP_ID_LO] = (int)(appId & 0xFFFFFFFF);
-        msgHeader[HEADER_FIELD_LOAD_APP_ID_HI] = (int)((appId >> 32) & 0xFFFFFFFF);
-
-        int errVal = nativeSendMessage(msgHeader, app.getAppBinary());
-        if (errVal != 0) {
-            Log.e(TAG, "Send Message returns error" + contextHubHandle);
+        int result = addTransaction(transaction);
+        if (result != Result.OK) {
+            Log.e(TAG, "Failed to load nanoapp with error code " + result);
             return -1;
         }
 
@@ -163,23 +420,26 @@
     @Override
     public int unloadNanoApp(int nanoAppInstanceHandle) throws RemoteException {
         checkPermissions();
+        if (mContextHubProxy == null) {
+            return -1;
+        }
+
         NanoAppInstanceInfo info = mNanoAppHash.get(nanoAppInstanceHandle);
         if (info == null) {
             Log.e(TAG, "Cannot find app with handle " + nanoAppInstanceHandle);
             return -1; //means failed
         }
 
-        // Call Native interface here
-        int[] msgHeader = new int[MSG_HEADER_SIZE];
-        msgHeader[HEADER_FIELD_HUB_HANDLE] = ANY_HUB;
-        msgHeader[HEADER_FIELD_APP_INSTANCE] = nanoAppInstanceHandle;
-        msgHeader[HEADER_FIELD_MSG_VERSION] = 0;
-        msgHeader[HEADER_FIELD_MSG_TYPE] = MSG_UNLOAD_NANO_APP;
+        int contextHubId = info.getContexthubId();
+        long nanoAppId = info.getAppId();
+        IContextHubTransactionCallback onCompleteCallback =
+                createUnloadTransactionCallback(contextHubId, nanoAppId);
+        ContextHubServiceTransaction transaction = mTransactionManager.createUnloadTransaction(
+                contextHubId, nanoAppId, onCompleteCallback);
 
-        byte msg[] = new byte[0];
-
-        if (nativeSendMessage(msgHeader, msg) != 0) {
-            Log.e(TAG, "native send message fails");
+        int result = addTransaction(transaction);
+        if (result != Result.OK) {
+            Log.e(TAG, "Failed to unload nanoapp with error code " + result);
             return -1;
         }
 
@@ -189,7 +449,7 @@
 
     @Override
     public NanoAppInstanceInfo getNanoAppInstanceInfo(int nanoAppInstanceHandle)
-                                                      throws RemoteException {
+            throws RemoteException {
         checkPermissions();
         // This assumes that all the nanoAppInfo is current. This is reasonable
         // for the use cases for tightly controlled nanoApps.
@@ -206,7 +466,7 @@
         checkPermissions();
         ArrayList<Integer> foundInstances = new ArrayList<Integer>();
 
-        for (Integer nanoAppInstance: mNanoAppHash.keySet()) {
+        for (Integer nanoAppInstance : mNanoAppHash.keySet()) {
             NanoAppInstanceInfo info = mNanoAppHash.get(nanoAppInstance);
 
             if (filter.testMatch(info)) {
@@ -223,23 +483,259 @@
         return retArray;
     }
 
-    @Override
-    public int sendMessage(int hubHandle, int nanoAppHandle, ContextHubMessage msg)
-                           throws RemoteException {
-        checkPermissions();
+    /**
+     * Performs a query at the specified hub.
+     *
+     * This method should only be invoked internally by the service, either to update the service
+     * cache or as a result of an explicit query requested by a client through the sendMessage API.
+     *
+     * @param contextHubId the ID of the hub to do the query
+     * @return the result of the query
+     */
+    private int queryNanoAppsInternal(int contextHubId) {
+        if (mContextHubProxy == null) {
+            return Result.UNKNOWN_FAILURE;
+        }
 
-        if (msg == null || msg.getData() == null) {
-            Log.w(TAG, "null ptr");
+        IContextHubTransactionCallback onCompleteCallback =
+                createQueryTransactionCallback(contextHubId);
+        ContextHubServiceTransaction transaction = mTransactionManager.createQueryTransaction(
+                contextHubId, onCompleteCallback);
+
+        return addTransaction(transaction);
+    }
+
+    @Override
+    public int sendMessage(
+            int hubHandle, int nanoAppHandle, ContextHubMessage msg) throws RemoteException {
+        checkPermissions();
+        if (mContextHubProxy == null) {
+            return -1;
+        }
+        if (msg == null) {
+            Log.e(TAG, "ContextHubMessage cannot be null");
+            return -1;
+        }
+        if (msg.getData() == null) {
+            Log.e(TAG, "ContextHubMessage message body cannot be null");
+            return -1;
+        }
+        if (!mDefaultClientMap.containsKey(hubHandle)) {
+            Log.e(TAG, "Hub with ID " + hubHandle + " does not exist");
             return -1;
         }
 
-        int[] msgHeader = new int[MSG_HEADER_SIZE];
-        msgHeader[HEADER_FIELD_HUB_HANDLE] = hubHandle;
-        msgHeader[HEADER_FIELD_APP_INSTANCE] = nanoAppHandle;
-        msgHeader[HEADER_FIELD_MSG_VERSION] = msg.getVersion();
-        msgHeader[HEADER_FIELD_MSG_TYPE] = msg.getMsgType();
+        boolean success = false;
+        if (nanoAppHandle == OS_APP_INSTANCE) {
+            if (msg.getMsgType() == MSG_QUERY_NANO_APPS) {
+                success = (queryNanoAppsInternal(hubHandle) == Result.OK);
+            } else {
+                Log.e(TAG, "Invalid OS message params of type " + msg.getMsgType());
+            }
+        } else {
+            NanoAppInstanceInfo info = getNanoAppInstanceInfo(nanoAppHandle);
+            if (info != null) {
+                NanoAppMessage message = NanoAppMessage.createMessageToNanoApp(
+                        info.getAppId(), msg.getMsgType(), msg.getData());
 
-        return nativeSendMessage(msgHeader, msg.getData());
+                IContextHubClient client = mDefaultClientMap.get(hubHandle);
+                success = (client.sendMessageToNanoApp(message) ==
+                        ContextHubTransaction.TRANSACTION_SUCCESS);
+            } else {
+                Log.e(TAG, "Failed to send nanoapp message - nanoapp with instance ID "
+                        + nanoAppHandle + " does not exist.");
+            }
+        }
+
+        return success ? 0 : -1;
+    }
+
+    /**
+     * Handles a unicast or broadcast message from a nanoapp.
+     *
+     * @param contextHubId the ID of the hub the message came from
+     * @param message      the message contents
+     */
+    private void handleClientMessageCallback(int contextHubId, ContextHubMsg message) {
+        mClientManager.onMessageFromNanoApp(contextHubId, message);
+    }
+
+    /**
+     * A helper function to handle a load response from the Context Hub for the old API.
+     *
+     * TODO(b/69270990): Remove this once the old APIs are obsolete.
+     */
+    private void handleLoadResponseOldApi(
+            int contextHubId, int result, NanoAppBinary nanoAppBinary) {
+        if (nanoAppBinary == null) {
+            Log.e(TAG, "Nanoapp binary field was null for a load transaction");
+            return;
+        }
+
+        // NOTE: The legacy JNI code used to do a query right after a load success
+        // to synchronize the service cache. Instead store the binary that was requested to
+        // load to update the cache later without doing a query.
+        int instanceId = 0;
+        long nanoAppId = nanoAppBinary.getNanoAppId();
+        int nanoAppVersion = nanoAppBinary.getNanoAppVersion();
+        if (result == TransactionResult.SUCCESS) {
+            if (mNanoAppIdToInstanceMap.containsKey(nanoAppId)) {
+                instanceId = mNanoAppIdToInstanceMap.get(nanoAppId);
+            } else {
+                instanceId = mNextAvailableInstanceId++;
+                mNanoAppIdToInstanceMap.put(nanoAppId, instanceId);
+            }
+
+            addAppInstance(contextHubId, instanceId, nanoAppId, nanoAppVersion);
+        }
+
+        byte[] data = new byte[5];
+        data[0] = (byte) result;
+        ByteBuffer.wrap(data, 1, 4).order(ByteOrder.nativeOrder()).putInt(instanceId);
+
+        onMessageReceiptOldApi(MSG_LOAD_NANO_APP, contextHubId, OS_APP_INSTANCE, data);
+    }
+
+    /**
+     * A helper function to handle an unload response from the Context Hub for the old API.
+     *
+     * TODO(b/69270990): Remove this once the old APIs are obsolete.
+     */
+    private void handleUnloadResponseOldApi(
+            int contextHubId, int result, long nanoAppId) {
+        if (result == TransactionResult.SUCCESS) {
+            int instanceId = mNanoAppIdToInstanceMap.get(nanoAppId);
+            deleteAppInstance(instanceId);
+            mNanoAppIdToInstanceMap.remove(nanoAppId);
+        }
+
+        byte[] data = new byte[1];
+        data[0] = (byte) result;
+        onMessageReceiptOldApi(MSG_UNLOAD_NANO_APP, contextHubId, OS_APP_INSTANCE, data);
+    }
+
+    /**
+     * Handles a transaction response from a Context Hub.
+     *
+     * @param contextHubId  the ID of the hub the response came from
+     * @param transactionId the ID of the transaction
+     * @param result        the result of the transaction reported by the hub
+     */
+    private void handleTransactionResultCallback(int contextHubId, int transactionId, int result) {
+        mTransactionManager.onTransactionResponse(transactionId, result);
+    }
+
+    /**
+     * Handles an asynchronous event from a Context Hub.
+     *
+     * @param contextHubId the ID of the hub the response came from
+     * @param eventType    the type of the event as defined in Context Hub HAL AsyncEventType
+     */
+    private void handleHubEventCallback(int contextHubId, int eventType) {
+        if (eventType == AsyncEventType.RESTARTED) {
+            mTransactionManager.onHubReset();
+            queryNanoAppsInternal(contextHubId);
+
+            mClientManager.onHubReset(contextHubId);
+        } else {
+            Log.i(TAG, "Received unknown hub event (hub ID = " + contextHubId + ", type = "
+                    + eventType + ")");
+        }
+    }
+
+    /**
+     * Handles an asynchronous abort event of a nanoapp.
+     *
+     * @param contextHubId the ID of the hub that the nanoapp aborted in
+     * @param nanoAppId    the ID of the aborted nanoapp
+     * @param abortCode    the nanoapp-specific abort code
+     */
+    private void handleAppAbortCallback(int contextHubId, long nanoAppId, int abortCode) {
+        // TODO(b/31049861): Implement this
+    }
+
+    /**
+     * Handles a query response from a Context Hub.
+     *
+     * @param contextHubId    the ID of the hub of the response
+     * @param nanoAppInfoList the list of loaded nanoapps
+     */
+    private void handleQueryAppsCallback(int contextHubId, List<HubAppInfo> nanoAppInfoList) {
+        List<NanoAppState> nanoAppStateList =
+                ContextHubServiceUtil.createNanoAppStateList(nanoAppInfoList);
+
+        updateServiceCache(contextHubId, nanoAppInfoList);
+        mTransactionManager.onQueryResponse(nanoAppStateList);
+    }
+
+    /**
+     * Updates the service's cache of the list of loaded nanoapps using a nanoapp list response.
+     *
+     * TODO(b/69270990): Remove this when the old API functionality is removed.
+     *
+     * @param contextHubId    the ID of the hub the response came from
+     * @param nanoAppInfoList the list of loaded nanoapps
+     */
+    private void updateServiceCache(int contextHubId, List<HubAppInfo> nanoAppInfoList) {
+        synchronized (mNanoAppHash) {
+            for (int instanceId : mNanoAppHash.keySet()) {
+                if (mNanoAppHash.get(instanceId).getContexthubId() == contextHubId) {
+                    deleteAppInstance(instanceId);
+                }
+            }
+
+            for (HubAppInfo appInfo : nanoAppInfoList) {
+                int instanceId;
+                long nanoAppId = appInfo.appId;
+                if (mNanoAppIdToInstanceMap.containsKey(nanoAppId)) {
+                    instanceId = mNanoAppIdToInstanceMap.get(nanoAppId);
+                } else {
+                    instanceId = mNextAvailableInstanceId++;
+                    mNanoAppIdToInstanceMap.put(nanoAppId, instanceId);
+                }
+
+                addAppInstance(contextHubId, instanceId, nanoAppId, appInfo.version);
+            }
+        }
+    }
+
+    /**
+     * @param contextHubId the hub ID to validate
+     * @return {@code true} if the ID represents that of an available hub, {@code false} otherwise
+     */
+    private boolean isValidContextHubId(int contextHubId) {
+        for (ContextHubInfo hubInfo : mContextHubInfo) {
+            if (hubInfo.getId() == contextHubId) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Creates and registers a client at the service for the specified Context Hub.
+     *
+     * @param clientCallback the client interface to register with the service
+     * @param contextHubId   the ID of the hub this client is attached to
+     * @return the generated client interface, null if registration was unsuccessful
+     *
+     * @throws IllegalArgumentException if contextHubId is not a valid ID
+     * @throws IllegalStateException if max number of clients have already registered
+     * @throws NullPointerException if clientCallback is null
+     */
+    @Override
+    public IContextHubClient createClient(
+            IContextHubClientCallback clientCallback, int contextHubId) throws RemoteException {
+        checkPermissions();
+        if (!isValidContextHubId(contextHubId)) {
+            throw new IllegalArgumentException("Invalid context hub ID " + contextHubId);
+        }
+        if (clientCallback == null) {
+            throw new NullPointerException("Cannot register client with null callback");
+        }
+
+        return mClientManager.registerClient(clientCallback, contextHubId);
     }
 
     @Override
@@ -257,7 +753,7 @@
         pw.println("");
         pw.println("=================== NANOAPPS ====================");
         // Dump nanoAppHash
-        for (Integer nanoAppInstance: mNanoAppHash.keySet()) {
+        for (Integer nanoAppInstance : mNanoAppHash.keySet()) {
             pw.println(nanoAppInstance + " : " + mNanoAppHash.get(nanoAppInstance).toString());
         }
 
@@ -265,22 +761,18 @@
     }
 
     private void checkPermissions() {
-        mContext.enforceCallingPermission(HARDWARE_PERMISSION, ENFORCE_HW_PERMISSION_MESSAGE);
+        ContextHubServiceUtil.checkPermissions(mContext);
     }
 
-    private int onMessageReceipt(int[] header, byte[] data) {
-        if (header == null || data == null || header.length < MSG_HEADER_SIZE) {
-            return  -1;
+    private int onMessageReceiptOldApi(int msgType, int hubHandle, int appInstance, byte[] data) {
+        if (data == null) {
+            return -1;
         }
 
+        int msgVersion = 0;
         int callbacksCount = mCallbacksList.beginBroadcast();
-        int msgType = header[HEADER_FIELD_MSG_TYPE];
-        int msgVersion = header[HEADER_FIELD_MSG_VERSION];
-        int hubHandle = header[HEADER_FIELD_HUB_HANDLE];
-        int appInstance = header[HEADER_FIELD_APP_INSTANCE];
-
         Log.d(TAG, "Sending message " + msgType + " version " + msgVersion + " from hubHandle " +
-              hubHandle + ", appInstance " + appInstance + ", callBackCount " + callbacksCount);
+                hubHandle + ", appInstance " + appInstance + ", callBackCount " + callbacksCount);
 
         if (callbacksCount < 1) {
             Log.v(TAG, "No message callbacks registered.");
@@ -323,8 +815,8 @@
         }
 
         mNanoAppHash.put(appInstanceHandle, appInfo);
-        Log.d(TAG, action + " app instance " + appInstanceHandle + " with id "
-              + appId + " version " + appVersion);
+        Log.d(TAG, action + " app instance " + appInstanceHandle + " with id 0x"
+                + Long.toHexString(appId) + " version 0x" + Integer.toHexString(appVersion));
 
         return 0;
     }
diff --git a/com/android/server/location/ContextHubServiceTransaction.java b/com/android/server/location/ContextHubServiceTransaction.java
new file mode 100644
index 0000000..66145bb
--- /dev/null
+++ b/com/android/server/location/ContextHubServiceTransaction.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright 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.location;
+
+import android.hardware.location.ContextHubTransaction;
+import android.hardware.location.NanoAppState;
+
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * An abstract class representing transactions requested to the Context Hub Service.
+ *
+ * @hide
+ */
+/* package */ abstract class ContextHubServiceTransaction {
+    private final int mTransactionId;
+    @ContextHubTransaction.Type
+    private final int mTransactionType;
+
+    /*
+     * true if the transaction has already completed, false otherwise
+     */
+    private boolean mIsComplete = false;
+
+    /* package */ ContextHubServiceTransaction(int id, int type) {
+        mTransactionId = id;
+        mTransactionType = type;
+    }
+
+    /**
+     * Starts this transaction with a Context Hub.
+     *
+     * All instances of this class must implement this method by making an asynchronous request to
+     * a hub.
+     *
+     * @return the synchronous error code of the transaction start
+     */
+    /* package */
+    abstract int onTransact();
+
+    /**
+     * A function to invoke when a transaction times out.
+     *
+     * All instances of this class must implement this method by reporting the timeout to the
+     * client.
+     */
+    /* package */
+    abstract void onTimeout();
+
+    /**
+     * A function to invoke when the transaction completes.
+     *
+     * Only relevant for load, unload, enable, or disable transactions.
+     *
+     * @param result the result of the transaction
+     */
+    /* package */ void onTransactionComplete(int result) {
+    }
+
+    /**
+     * A function to invoke when a query transaction completes.
+     *
+     * Only relevant for query transactions.
+     *
+     * @param result           the result of the query
+     * @param nanoAppStateList the list of nanoapps given by the query response
+     */
+    /* package */ void onQueryResponse(int result, List<NanoAppState> nanoAppStateList) {
+    }
+
+    /**
+     * @return the ID of this transaction
+     */
+    /* package */ int getTransactionId() {
+        return mTransactionId;
+    }
+
+    /**
+     * @return the type of this transaction
+     * @see ContextHubTransaction.Type
+     */
+    @ContextHubTransaction.Type
+    /* package */ int getTransactionType() {
+        return mTransactionType;
+    }
+
+    /**
+     * Gets the timeout period as defined in IContexthub.hal
+     *
+     * @return the timeout of this transaction in the specified time unit
+     */
+    /* package */ long getTimeout(TimeUnit unit) {
+        switch (mTransactionType) {
+            case ContextHubTransaction.TYPE_LOAD_NANOAPP:
+                return unit.convert(30L, TimeUnit.SECONDS);
+            case ContextHubTransaction.TYPE_UNLOAD_NANOAPP:
+            case ContextHubTransaction.TYPE_ENABLE_NANOAPP:
+            case ContextHubTransaction.TYPE_DISABLE_NANOAPP:
+            case ContextHubTransaction.TYPE_QUERY_NANOAPPS:
+                // Note: query timeout is not specified at the HAL
+            default: /* fall through */
+                return unit.convert(5L, TimeUnit.SECONDS);
+        }
+    }
+
+    /**
+     * Marks the transaction as complete.
+     *
+     * Should only be called as a result of a response from a Context Hub callback
+     */
+    /* package */ void setComplete() {
+        mIsComplete = true;
+    }
+
+    /**
+     * @return true if the transaction has already completed, false otherwise
+     */
+    /* package */ boolean isComplete() {
+        return mIsComplete;
+    }
+
+    /**
+     * @return the human-readable string of this transaction's type
+     */
+    private String getTransactionTypeString() {
+        switch (mTransactionType) {
+            case ContextHubTransaction.TYPE_LOAD_NANOAPP:
+                return "Load";
+            case ContextHubTransaction.TYPE_UNLOAD_NANOAPP:
+                return "Unload";
+            case ContextHubTransaction.TYPE_ENABLE_NANOAPP:
+                return "Enable";
+            case ContextHubTransaction.TYPE_DISABLE_NANOAPP:
+                return "Disable";
+            case ContextHubTransaction.TYPE_QUERY_NANOAPPS:
+                return "Query";
+            default:
+                return "Unknown";
+        }
+    }
+
+    @Override
+    public String toString() {
+        return getTransactionTypeString() + " transaction (ID = " + mTransactionId + ")";
+    }
+}
diff --git a/com/android/server/location/ContextHubServiceUtil.java b/com/android/server/location/ContextHubServiceUtil.java
new file mode 100644
index 0000000..6faeb72
--- /dev/null
+++ b/com/android/server/location/ContextHubServiceUtil.java
@@ -0,0 +1,211 @@
+/*
+ * Copyright 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.location;
+
+import android.Manifest;
+import android.content.Context;
+import android.hardware.contexthub.V1_0.ContextHub;
+import android.hardware.contexthub.V1_0.ContextHubMsg;
+import android.hardware.contexthub.V1_0.HostEndPoint;
+import android.hardware.contexthub.V1_0.HubAppInfo;
+import android.hardware.contexthub.V1_0.Result;
+import android.hardware.location.ContextHubInfo;
+import android.hardware.location.ContextHubTransaction;
+import android.hardware.location.NanoAppBinary;
+import android.hardware.location.NanoAppMessage;
+import android.hardware.location.NanoAppState;
+import android.util.Log;
+
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * A class encapsulating helper functions used by the ContextHubService class
+ */
+/* package */ class ContextHubServiceUtil {
+    private static final String TAG = "ContextHubServiceUtil";
+    private static final String HARDWARE_PERMISSION = Manifest.permission.LOCATION_HARDWARE;
+    private static final String ENFORCE_HW_PERMISSION_MESSAGE = "Permission '"
+            + HARDWARE_PERMISSION + "' not granted to access ContextHub Hardware";
+
+    /**
+     * Creates a ContextHubInfo array from an ArrayList of HIDL ContextHub objects.
+     *
+     * @param hubList the ContextHub ArrayList
+     * @return the ContextHubInfo array
+     */
+    /* package */
+    static ContextHubInfo[] createContextHubInfoArray(List<ContextHub> hubList) {
+        ContextHubInfo[] contextHubInfoList = new ContextHubInfo[hubList.size()];
+        for (int i = 0; i < hubList.size(); i++) {
+            contextHubInfoList[i] = new ContextHubInfo(hubList.get(i));
+        }
+
+        return contextHubInfoList;
+    }
+
+    /**
+     * Copies a primitive byte array to a ArrayList<Byte>.
+     *
+     * @param inputArray  the primitive byte array
+     * @param outputArray the ArrayList<Byte> array to append
+     */
+    /* package */
+    static void copyToByteArrayList(byte[] inputArray, ArrayList<Byte> outputArray) {
+        outputArray.clear();
+        outputArray.ensureCapacity(inputArray.length);
+        for (byte element : inputArray) {
+            outputArray.add(element);
+        }
+    }
+
+    /**
+     * Creates a byte array given a ArrayList<Byte> and copies its contents.
+     *
+     * @param array the ArrayList<Byte> object
+     * @return the byte array
+     */
+    /* package */
+    static byte[] createPrimitiveByteArray(ArrayList<Byte> array) {
+        byte[] primitiveArray = new byte[array.size()];
+        for (int i = 0; i < array.size(); i++) {
+            primitiveArray[i] = array.get(i);
+        }
+
+        return primitiveArray;
+    }
+
+    /**
+     * Generates the Context Hub HAL's NanoAppBinary object from the client-facing
+     * android.hardware.location.NanoAppBinary object.
+     *
+     * @param nanoAppBinary the client-facing NanoAppBinary object
+     * @return the Context Hub HAL's NanoAppBinary object
+     */
+    /* package */
+    static android.hardware.contexthub.V1_0.NanoAppBinary createHidlNanoAppBinary(
+            NanoAppBinary nanoAppBinary) {
+        android.hardware.contexthub.V1_0.NanoAppBinary hidlNanoAppBinary =
+                new android.hardware.contexthub.V1_0.NanoAppBinary();
+
+        hidlNanoAppBinary.appId = nanoAppBinary.getNanoAppId();
+        hidlNanoAppBinary.appVersion = nanoAppBinary.getNanoAppVersion();
+        hidlNanoAppBinary.flags = nanoAppBinary.getFlags();
+        hidlNanoAppBinary.targetChreApiMajorVersion = nanoAppBinary.getTargetChreApiMajorVersion();
+        hidlNanoAppBinary.targetChreApiMinorVersion = nanoAppBinary.getTargetChreApiMinorVersion();
+
+        // Log exceptions while processing the binary, but continue to pass down the binary
+        // since the error checking is deferred to the Context Hub.
+        try {
+            copyToByteArrayList(nanoAppBinary.getBinaryNoHeader(), hidlNanoAppBinary.customBinary);
+        } catch (IndexOutOfBoundsException e) {
+            Log.w(TAG, e.getMessage());
+        } catch (NullPointerException e) {
+            Log.w(TAG, "NanoApp binary was null");
+        }
+
+        return hidlNanoAppBinary;
+    }
+
+    /**
+     * Generates a client-facing NanoAppState array from a HAL HubAppInfo array.
+     *
+     * @param nanoAppInfoList the array of HubAppInfo objects
+     * @return the corresponding array of NanoAppState objects
+     */
+    /* package */
+    static List<NanoAppState> createNanoAppStateList(
+            List<HubAppInfo> nanoAppInfoList) {
+        ArrayList<NanoAppState> nanoAppStateList = new ArrayList<>();
+        for (HubAppInfo appInfo : nanoAppInfoList) {
+            nanoAppStateList.add(
+                    new NanoAppState(appInfo.appId, appInfo.version, appInfo.enabled));
+        }
+
+        return nanoAppStateList;
+    }
+
+    /**
+     * Creates a HIDL ContextHubMsg object to send to a nanoapp.
+     *
+     * @param hostEndPoint the ID of the client sending the message
+     * @param message      the client-facing NanoAppMessage object describing the message
+     * @return the HIDL ContextHubMsg object
+     */
+    /* package */
+    static ContextHubMsg createHidlContextHubMessage(short hostEndPoint, NanoAppMessage message) {
+        ContextHubMsg hidlMessage = new ContextHubMsg();
+
+        hidlMessage.appName = message.getNanoAppId();
+        hidlMessage.hostEndPoint = hostEndPoint;
+        hidlMessage.msgType = message.getMessageType();
+        copyToByteArrayList(message.getMessageBody(), hidlMessage.msg);
+
+        return hidlMessage;
+    }
+
+    /**
+     * Creates a client-facing NanoAppMessage object to send to a client.
+     *
+     * @param message the HIDL ContextHubMsg object from a nanoapp
+     * @return the NanoAppMessage object
+     */
+    /* package */
+    static NanoAppMessage createNanoAppMessage(ContextHubMsg message) {
+        byte[] messageArray = createPrimitiveByteArray(message.msg);
+
+        return NanoAppMessage.createMessageFromNanoApp(
+                message.appName, message.msgType, messageArray,
+                message.hostEndPoint == HostEndPoint.BROADCAST);
+    }
+
+    /**
+     * Checks for location hardware permissions.
+     *
+     * @param context the context of the service
+     */
+    /* package */
+    static void checkPermissions(Context context) {
+        context.enforceCallingPermission(HARDWARE_PERMISSION, ENFORCE_HW_PERMISSION_MESSAGE);
+    }
+
+    /**
+     * Helper function to convert from the HAL Result enum error code to the
+     * ContextHubTransaction.Result type.
+     *
+     * @param halResult the Result enum error code
+     * @return the ContextHubTransaction.Result equivalent
+     */
+    @ContextHubTransaction.Result
+    /* package */
+    static int toTransactionResult(int halResult) {
+        switch (halResult) {
+            case Result.OK:
+                return ContextHubTransaction.TRANSACTION_SUCCESS;
+            case Result.BAD_PARAMS:
+                return ContextHubTransaction.TRANSACTION_FAILED_BAD_PARAMS;
+            case Result.NOT_INIT:
+                return ContextHubTransaction.TRANSACTION_FAILED_UNINITIALIZED;
+            case Result.TRANSACTION_PENDING:
+                return ContextHubTransaction.TRANSACTION_FAILED_PENDING;
+            case Result.TRANSACTION_FAILED:
+            case Result.UNKNOWN_FAILURE:
+            default: /* fall through */
+                return ContextHubTransaction.TRANSACTION_FAILED_UNKNOWN;
+        }
+    }
+}
diff --git a/com/android/server/location/ContextHubTransactionManager.java b/com/android/server/location/ContextHubTransactionManager.java
new file mode 100644
index 0000000..47d9d56
--- /dev/null
+++ b/com/android/server/location/ContextHubTransactionManager.java
@@ -0,0 +1,351 @@
+/*
+ * Copyright 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.location;
+
+import android.hardware.contexthub.V1_0.IContexthub;
+import android.hardware.contexthub.V1_0.Result;
+import android.hardware.contexthub.V1_0.TransactionResult;
+import android.hardware.location.ContextHubTransaction;
+import android.hardware.location.IContextHubTransactionCallback;
+import android.hardware.location.NanoAppBinary;
+import android.hardware.location.NanoAppState;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.ArrayDeque;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Manages transactions at the Context Hub Service.
+ *
+ * This class maintains a queue of transaction requests made to the ContextHubService by clients,
+ * and executes them through the Context Hub. At any point in time, either the transaction queue is
+ * empty, or there is a pending transaction that is waiting for an asynchronous response from the
+ * hub. This class also handles synchronous errors and timeouts of each transaction.
+ *
+ * @hide
+ */
+/* package */ class ContextHubTransactionManager {
+    private static final String TAG = "ContextHubTransactionManager";
+
+    /*
+     * Maximum number of transaction requests that can be pending at a time
+     */
+    private static final int MAX_PENDING_REQUESTS = 10;
+
+    /*
+     * The proxy to talk to the Context Hub
+     */
+    private final IContexthub mContextHubProxy;
+
+    /*
+     * The manager for all clients for the service.
+     */
+    private final ContextHubClientManager mClientManager;
+
+    /*
+     * A queue containing the current transactions
+     */
+    private final ArrayDeque<ContextHubServiceTransaction> mTransactionQueue = new ArrayDeque<>();
+
+    /*
+     * The next available transaction ID
+     */
+    private final AtomicInteger mNextAvailableId = new AtomicInteger();
+
+    /*
+     * An executor and the future object for scheduling timeout timers
+     */
+    private final ScheduledThreadPoolExecutor mTimeoutExecutor = new ScheduledThreadPoolExecutor(1);
+    private ScheduledFuture<?> mTimeoutFuture = null;
+
+    /* package */ ContextHubTransactionManager(
+            IContexthub contextHubProxy, ContextHubClientManager clientManager) {
+        mContextHubProxy = contextHubProxy;
+        mClientManager = clientManager;
+    }
+
+    /**
+     * Creates a transaction for loading a nanoapp.
+     *
+     * @param contextHubId       the ID of the hub to load the nanoapp to
+     * @param nanoAppBinary      the binary of the nanoapp to load
+     * @param onCompleteCallback the client on complete callback
+     * @return the generated transaction
+     */
+    /* package */ ContextHubServiceTransaction createLoadTransaction(
+            int contextHubId, NanoAppBinary nanoAppBinary,
+            IContextHubTransactionCallback onCompleteCallback) {
+        return new ContextHubServiceTransaction(
+                mNextAvailableId.getAndIncrement(), ContextHubTransaction.TYPE_LOAD_NANOAPP) {
+            @Override
+            /* package */ int onTransact() {
+                android.hardware.contexthub.V1_0.NanoAppBinary hidlNanoAppBinary =
+                        ContextHubServiceUtil.createHidlNanoAppBinary(nanoAppBinary);
+                try {
+                    return mContextHubProxy.loadNanoApp(
+                            contextHubId, hidlNanoAppBinary, this.getTransactionId());
+                } catch (RemoteException e) {
+                    Log.e(TAG, "RemoteException while trying to load nanoapp with ID 0x" +
+                            Long.toHexString(nanoAppBinary.getNanoAppId()));
+                    return Result.UNKNOWN_FAILURE;
+                }
+            }
+
+            @Override
+            /* package */ void onTimeout() {
+                onTransactionComplete(ContextHubTransaction.TRANSACTION_FAILED_TIMEOUT);
+            }
+
+            @Override
+            /* package */ void onTransactionComplete(int result) {
+                try {
+                    onCompleteCallback.onTransactionComplete(result);
+                    if (result == Result.OK) {
+                        mClientManager.onNanoAppLoaded(contextHubId, nanoAppBinary.getNanoAppId());
+                    }
+                } catch (RemoteException e) {
+                    Log.e(TAG, "RemoteException while calling client onTransactionComplete");
+                }
+            }
+        };
+    }
+
+    /**
+     * Creates a transaction for unloading a nanoapp.
+     *
+     * @param contextHubId       the ID of the hub to load the nanoapp to
+     * @param nanoAppId          the ID of the nanoapp to unload
+     * @param onCompleteCallback the client on complete callback
+     * @return the generated transaction
+     */
+    /* package */ ContextHubServiceTransaction createUnloadTransaction(
+            int contextHubId, long nanoAppId, IContextHubTransactionCallback onCompleteCallback) {
+        return new ContextHubServiceTransaction(
+                mNextAvailableId.getAndIncrement(), ContextHubTransaction.TYPE_UNLOAD_NANOAPP) {
+            @Override
+            /* package */ int onTransact() {
+                try {
+                    return mContextHubProxy.unloadNanoApp(
+                            contextHubId, nanoAppId, this.getTransactionId());
+                } catch (RemoteException e) {
+                    Log.e(TAG, "RemoteException while trying to unload nanoapp with ID 0x" +
+                            Long.toHexString(nanoAppId));
+                    return Result.UNKNOWN_FAILURE;
+                }
+            }
+
+            @Override
+            /* package */ void onTimeout() {
+                onTransactionComplete(ContextHubTransaction.TRANSACTION_FAILED_TIMEOUT);
+            }
+
+            @Override
+            /* package */ void onTransactionComplete(int result) {
+                try {
+                    onCompleteCallback.onTransactionComplete(result);
+                    if (result == Result.OK) {
+                        mClientManager.onNanoAppUnloaded(contextHubId, nanoAppId);
+                    }
+                } catch (RemoteException e) {
+                    Log.e(TAG, "RemoteException while calling client onTransactionComplete");
+                }
+            }
+        };
+    }
+
+    /**
+     * Creates a transaction for querying for a list of nanoapps.
+     *
+     * @param contextHubId       the ID of the hub to query
+     * @param onCompleteCallback the client on complete callback
+     * @return the generated transaction
+     */
+    /* package */ ContextHubServiceTransaction createQueryTransaction(
+            int contextHubId, IContextHubTransactionCallback onCompleteCallback) {
+        return new ContextHubServiceTransaction(
+                mNextAvailableId.getAndIncrement(), ContextHubTransaction.TYPE_QUERY_NANOAPPS) {
+            @Override
+            /* package */ int onTransact() {
+                try {
+                    return mContextHubProxy.queryApps(contextHubId);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "RemoteException while trying to query for nanoapps");
+                    return Result.UNKNOWN_FAILURE;
+                }
+            }
+
+            @Override
+            /* package */ void onTimeout() {
+                onQueryResponse(ContextHubTransaction.TRANSACTION_FAILED_TIMEOUT,
+                        Collections.emptyList());
+            }
+
+            @Override
+            /* package */ void onQueryResponse(int result, List<NanoAppState> nanoAppStateList) {
+                try {
+                    onCompleteCallback.onQueryResponse(result, nanoAppStateList);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "RemoteException while calling client onQueryComplete");
+                }
+            }
+        };
+    }
+
+    /**
+     * Adds a new transaction to the queue.
+     *
+     * If there was no pending transaction at the time, the transaction that was added will be
+     * started in this method.
+     *
+     * @param transaction the transaction to add
+     * @throws IllegalStateException if the queue is full
+     */
+    /* package */
+    synchronized void addTransaction(
+            ContextHubServiceTransaction transaction) throws IllegalStateException {
+        if (mTransactionQueue.size() == MAX_PENDING_REQUESTS) {
+            throw new IllegalStateException("Transaction transaction queue is full (capacity = "
+                    + MAX_PENDING_REQUESTS + ")");
+        }
+        mTransactionQueue.add(transaction);
+
+        if (mTransactionQueue.size() == 1) {
+            startNextTransaction();
+        }
+    }
+
+    /**
+     * Handles a transaction response from a Context Hub.
+     *
+     * @param transactionId the transaction ID of the response
+     * @param result        the result of the transaction
+     */
+    /* package */
+    synchronized void onTransactionResponse(int transactionId, int result) {
+        ContextHubServiceTransaction transaction = mTransactionQueue.peek();
+        if (transaction == null) {
+            Log.w(TAG, "Received unexpected transaction response (no transaction pending)");
+            return;
+        }
+        if (transaction.getTransactionId() != transactionId) {
+            Log.w(TAG, "Received unexpected transaction response (expected ID = "
+                    + transaction.getTransactionId() + ", received ID = " + transactionId + ")");
+            return;
+        }
+
+        transaction.onTransactionComplete(result);
+        removeTransactionAndStartNext();
+    }
+
+    /**
+     * Handles a query response from a Context Hub.
+     *
+     * @param nanoAppStateList the list of nanoapps included in the response
+     */
+    /* package */
+    synchronized void onQueryResponse(List<NanoAppState> nanoAppStateList) {
+        ContextHubServiceTransaction transaction = mTransactionQueue.peek();
+        if (transaction == null) {
+            Log.w(TAG, "Received unexpected query response (no transaction pending)");
+            return;
+        }
+        if (transaction.getTransactionType() != ContextHubTransaction.TYPE_QUERY_NANOAPPS) {
+            Log.w(TAG, "Received unexpected query response (expected " + transaction + ")");
+            return;
+        }
+
+        transaction.onQueryResponse(TransactionResult.SUCCESS, nanoAppStateList);
+        removeTransactionAndStartNext();
+    }
+
+    /**
+     * Handles a hub reset event by stopping a pending transaction and starting the next.
+     */
+    /* package */
+    synchronized void onHubReset() {
+        ContextHubServiceTransaction transaction = mTransactionQueue.peek();
+        if (transaction == null) {
+            return;
+        }
+
+        removeTransactionAndStartNext();
+    }
+
+    /**
+     * Pops the front transaction from the queue and starts the next pending transaction request.
+     *
+     * Removing elements from the transaction queue must only be done through this method. When a
+     * pending transaction is removed, the timeout timer is cancelled and the transaction is marked
+     * complete.
+     *
+     * It is assumed that the transaction queue is non-empty when this method is invoked, and that
+     * the caller has obtained a lock on this ContextHubTransactionManager object.
+     */
+    private void removeTransactionAndStartNext() {
+        mTimeoutFuture.cancel(false /* mayInterruptIfRunning */);
+
+        ContextHubServiceTransaction transaction = mTransactionQueue.remove();
+        transaction.setComplete();
+
+        if (!mTransactionQueue.isEmpty()) {
+            startNextTransaction();
+        }
+    }
+
+    /**
+     * Starts the next pending transaction request.
+     *
+     * Starting new transactions must only be done through this method. This method continues to
+     * process the transaction queue as long as there are pending requests, and no transaction is
+     * pending.
+     *
+     * It is assumed that the caller has obtained a lock on this ContextHubTransactionManager
+     * object.
+     */
+    private void startNextTransaction() {
+        int result = Result.UNKNOWN_FAILURE;
+        while (result != Result.OK && !mTransactionQueue.isEmpty()) {
+            ContextHubServiceTransaction transaction = mTransactionQueue.peek();
+            result = transaction.onTransact();
+
+            if (result == Result.OK) {
+                Runnable onTimeoutFunc = () -> {
+                    synchronized (this) {
+                        if (!transaction.isComplete()) {
+                            Log.d(TAG, transaction + " timed out");
+                            transaction.onTimeout();
+
+                            removeTransactionAndStartNext();
+                        }
+                    }
+                };
+
+                long timeoutSeconds = transaction.getTimeout(TimeUnit.SECONDS);
+                mTimeoutFuture = mTimeoutExecutor.schedule(onTimeoutFunc, timeoutSeconds,
+                        TimeUnit.SECONDS);
+            } else {
+                mTransactionQueue.remove();
+            }
+        }
+    }
+}
diff --git a/com/android/server/locksettings/LockSettingsService.java b/com/android/server/locksettings/LockSettingsService.java
index a1a0106..60f451a 100644
--- a/com/android/server/locksettings/LockSettingsService.java
+++ b/com/android/server/locksettings/LockSettingsService.java
@@ -603,160 +603,156 @@
     }
 
     private void migrateOldData() {
-        try {
-            // These Settings moved before multi-user was enabled, so we only have to do it for the
-            // root user.
-            if (getString("migrated", null, 0) == null) {
-                final ContentResolver cr = mContext.getContentResolver();
-                for (String validSetting : VALID_SETTINGS) {
-                    String value = Settings.Secure.getString(cr, validSetting);
-                    if (value != null) {
-                        setString(validSetting, value, 0);
-                    }
+        // These Settings moved before multi-user was enabled, so we only have to do it for the
+        // root user.
+        if (getString("migrated", null, 0) == null) {
+            final ContentResolver cr = mContext.getContentResolver();
+            for (String validSetting : VALID_SETTINGS) {
+                String value = Settings.Secure.getString(cr, validSetting);
+                if (value != null) {
+                    setString(validSetting, value, 0);
                 }
-                // No need to move the password / pattern files. They're already in the right place.
-                setString("migrated", "true", 0);
-                Slog.i(TAG, "Migrated lock settings to new location");
             }
+            // No need to move the password / pattern files. They're already in the right place.
+            setString("migrated", "true", 0);
+            Slog.i(TAG, "Migrated lock settings to new location");
+        }
 
-            // These Settings changed after multi-user was enabled, hence need to be moved per user.
-            if (getString("migrated_user_specific", null, 0) == null) {
-                final ContentResolver cr = mContext.getContentResolver();
-                List<UserInfo> users = mUserManager.getUsers();
-                for (int user = 0; user < users.size(); user++) {
-                    // Migrate owner info
-                    final int userId = users.get(user).id;
-                    final String OWNER_INFO = Secure.LOCK_SCREEN_OWNER_INFO;
-                    String ownerInfo = Settings.Secure.getStringForUser(cr, OWNER_INFO, userId);
-                    if (!TextUtils.isEmpty(ownerInfo)) {
-                        setString(OWNER_INFO, ownerInfo, userId);
-                        Settings.Secure.putStringForUser(cr, OWNER_INFO, "", userId);
-                    }
-
-                    // Migrate owner info enabled. Note there was a bug where older platforms only
-                    // stored this value if the checkbox was toggled at least once. The code detects
-                    // this case by handling the exception.
-                    final String OWNER_INFO_ENABLED = Secure.LOCK_SCREEN_OWNER_INFO_ENABLED;
-                    boolean enabled;
-                    try {
-                        int ivalue = Settings.Secure.getIntForUser(cr, OWNER_INFO_ENABLED, userId);
-                        enabled = ivalue != 0;
-                        setLong(OWNER_INFO_ENABLED, enabled ? 1 : 0, userId);
-                    } catch (SettingNotFoundException e) {
-                        // Setting was never stored. Store it if the string is not empty.
-                        if (!TextUtils.isEmpty(ownerInfo)) {
-                            setLong(OWNER_INFO_ENABLED, 1, userId);
-                        }
-                    }
-                    Settings.Secure.putIntForUser(cr, OWNER_INFO_ENABLED, 0, userId);
-                }
-                // No need to move the password / pattern files. They're already in the right place.
-                setString("migrated_user_specific", "true", 0);
-                Slog.i(TAG, "Migrated per-user lock settings to new location");
-            }
-
-            // Migrates biometric weak such that the fallback mechanism becomes the primary.
-            if (getString("migrated_biometric_weak", null, 0) == null) {
-                List<UserInfo> users = mUserManager.getUsers();
-                for (int i = 0; i < users.size(); i++) {
-                    int userId = users.get(i).id;
-                    long type = getLong(LockPatternUtils.PASSWORD_TYPE_KEY,
-                            DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
-                            userId);
-                    long alternateType = getLong(LockPatternUtils.PASSWORD_TYPE_ALTERNATE_KEY,
-                            DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
-                            userId);
-                    if (type == DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK) {
-                        setLong(LockPatternUtils.PASSWORD_TYPE_KEY,
-                                alternateType,
-                                userId);
-                    }
-                    setLong(LockPatternUtils.PASSWORD_TYPE_ALTERNATE_KEY,
-                            DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
-                            userId);
-                }
-                setString("migrated_biometric_weak", "true", 0);
-                Slog.i(TAG, "Migrated biometric weak to use the fallback instead");
-            }
-
-            // Migrates lockscreen.disabled. Prior to M, the flag was ignored when more than one
-            // user was present on the system, so if we're upgrading to M and there is more than one
-            // user we disable the flag to remain consistent.
-            if (getString("migrated_lockscreen_disabled", null, 0) == null) {
-                final List<UserInfo> users = mUserManager.getUsers();
-                final int userCount = users.size();
-                int switchableUsers = 0;
-                for (int i = 0; i < userCount; i++) {
-                    if (users.get(i).supportsSwitchTo()) {
-                        switchableUsers++;
-                    }
+        // These Settings changed after multi-user was enabled, hence need to be moved per user.
+        if (getString("migrated_user_specific", null, 0) == null) {
+            final ContentResolver cr = mContext.getContentResolver();
+            List<UserInfo> users = mUserManager.getUsers();
+            for (int user = 0; user < users.size(); user++) {
+                // Migrate owner info
+                final int userId = users.get(user).id;
+                final String OWNER_INFO = Secure.LOCK_SCREEN_OWNER_INFO;
+                String ownerInfo = Settings.Secure.getStringForUser(cr, OWNER_INFO, userId);
+                if (!TextUtils.isEmpty(ownerInfo)) {
+                    setString(OWNER_INFO, ownerInfo, userId);
+                    Settings.Secure.putStringForUser(cr, OWNER_INFO, "", userId);
                 }
 
-                if (switchableUsers > 1) {
-                    for (int i = 0; i < userCount; i++) {
-                        int id = users.get(i).id;
-
-                        if (getBoolean(LockPatternUtils.DISABLE_LOCKSCREEN_KEY, false, id)) {
-                            setBoolean(LockPatternUtils.DISABLE_LOCKSCREEN_KEY, false, id);
-                        }
-                    }
-                }
-
-                setString("migrated_lockscreen_disabled", "true", 0);
-                Slog.i(TAG, "Migrated lockscreen disabled flag");
-            }
-
-            final List<UserInfo> users = mUserManager.getUsers();
-            for (int i = 0; i < users.size(); i++) {
-                final UserInfo userInfo = users.get(i);
-                if (userInfo.isManagedProfile() && mStorage.hasChildProfileLock(userInfo.id)) {
-                    // When managed profile has a unified lock, the password quality stored has 2
-                    // possibilities only.
-                    // 1). PASSWORD_QUALITY_UNSPECIFIED, which is upgraded from dp2, and we are
-                    // going to set it back to PASSWORD_QUALITY_ALPHANUMERIC.
-                    // 2). PASSWORD_QUALITY_ALPHANUMERIC, which is the actual password quality for
-                    // unified lock.
-                    final long quality = getLong(LockPatternUtils.PASSWORD_TYPE_KEY,
-                            DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, userInfo.id);
-                    if (quality == DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
-                        // Only possible when it's upgraded from nyc dp3
-                        Slog.i(TAG, "Migrated tied profile lock type");
-                        setLong(LockPatternUtils.PASSWORD_TYPE_KEY,
-                                DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, userInfo.id);
-                    } else if (quality != DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC) {
-                        // It should not happen
-                        Slog.e(TAG, "Invalid tied profile lock type: " + quality);
-                    }
-                }
+                // Migrate owner info enabled. Note there was a bug where older platforms only
+                // stored this value if the checkbox was toggled at least once. The code detects
+                // this case by handling the exception.
+                final String OWNER_INFO_ENABLED = Secure.LOCK_SCREEN_OWNER_INFO_ENABLED;
+                boolean enabled;
                 try {
-                    final String alias = LockPatternUtils.PROFILE_KEY_NAME_ENCRYPT + userInfo.id;
-                    java.security.KeyStore keyStore =
-                            java.security.KeyStore.getInstance("AndroidKeyStore");
-                    keyStore.load(null);
-                    if (keyStore.containsAlias(alias)) {
-                        keyStore.deleteEntry(alias);
+                    int ivalue = Settings.Secure.getIntForUser(cr, OWNER_INFO_ENABLED, userId);
+                    enabled = ivalue != 0;
+                    setLong(OWNER_INFO_ENABLED, enabled ? 1 : 0, userId);
+                } catch (SettingNotFoundException e) {
+                    // Setting was never stored. Store it if the string is not empty.
+                    if (!TextUtils.isEmpty(ownerInfo)) {
+                        setLong(OWNER_INFO_ENABLED, 1, userId);
                     }
-                } catch (KeyStoreException | NoSuchAlgorithmException |
-                        CertificateException | IOException e) {
-                    Slog.e(TAG, "Unable to remove tied profile key", e);
+                }
+                Settings.Secure.putIntForUser(cr, OWNER_INFO_ENABLED, 0, userId);
+            }
+            // No need to move the password / pattern files. They're already in the right place.
+            setString("migrated_user_specific", "true", 0);
+            Slog.i(TAG, "Migrated per-user lock settings to new location");
+        }
+
+        // Migrates biometric weak such that the fallback mechanism becomes the primary.
+        if (getString("migrated_biometric_weak", null, 0) == null) {
+            List<UserInfo> users = mUserManager.getUsers();
+            for (int i = 0; i < users.size(); i++) {
+                int userId = users.get(i).id;
+                long type = getLong(LockPatternUtils.PASSWORD_TYPE_KEY,
+                        DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
+                        userId);
+                long alternateType = getLong(LockPatternUtils.PASSWORD_TYPE_ALTERNATE_KEY,
+                        DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
+                        userId);
+                if (type == DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK) {
+                    setLong(LockPatternUtils.PASSWORD_TYPE_KEY,
+                            alternateType,
+                            userId);
+                }
+                setLong(LockPatternUtils.PASSWORD_TYPE_ALTERNATE_KEY,
+                        DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED,
+                        userId);
+            }
+            setString("migrated_biometric_weak", "true", 0);
+            Slog.i(TAG, "Migrated biometric weak to use the fallback instead");
+        }
+
+        // Migrates lockscreen.disabled. Prior to M, the flag was ignored when more than one
+        // user was present on the system, so if we're upgrading to M and there is more than one
+        // user we disable the flag to remain consistent.
+        if (getString("migrated_lockscreen_disabled", null, 0) == null) {
+            final List<UserInfo> users = mUserManager.getUsers();
+            final int userCount = users.size();
+            int switchableUsers = 0;
+            for (int i = 0; i < userCount; i++) {
+                if (users.get(i).supportsSwitchTo()) {
+                    switchableUsers++;
                 }
             }
 
-            boolean isWatch = mContext.getPackageManager().hasSystemFeature(
-                    PackageManager.FEATURE_WATCH);
-            // Wear used to set DISABLE_LOCKSCREEN to 'true', but because Wear now allows accounts
-            // and device management the lockscreen must be re-enabled now for users that upgrade.
-            if (isWatch && getString("migrated_wear_lockscreen_disabled", null, 0) == null) {
-                final int userCount = users.size();
+            if (switchableUsers > 1) {
                 for (int i = 0; i < userCount; i++) {
                     int id = users.get(i).id;
-                    setBoolean(LockPatternUtils.DISABLE_LOCKSCREEN_KEY, false, id);
+
+                    if (getBoolean(LockPatternUtils.DISABLE_LOCKSCREEN_KEY, false, id)) {
+                        setBoolean(LockPatternUtils.DISABLE_LOCKSCREEN_KEY, false, id);
+                    }
                 }
-                setString("migrated_wear_lockscreen_disabled", "true", 0);
-                Slog.i(TAG, "Migrated lockscreen_disabled for Wear devices");
             }
-        } catch (RemoteException re) {
-            Slog.e(TAG, "Unable to migrate old data", re);
+
+            setString("migrated_lockscreen_disabled", "true", 0);
+            Slog.i(TAG, "Migrated lockscreen disabled flag");
+        }
+
+        final List<UserInfo> users = mUserManager.getUsers();
+        for (int i = 0; i < users.size(); i++) {
+            final UserInfo userInfo = users.get(i);
+            if (userInfo.isManagedProfile() && mStorage.hasChildProfileLock(userInfo.id)) {
+                // When managed profile has a unified lock, the password quality stored has 2
+                // possibilities only.
+                // 1). PASSWORD_QUALITY_UNSPECIFIED, which is upgraded from dp2, and we are
+                // going to set it back to PASSWORD_QUALITY_ALPHANUMERIC.
+                // 2). PASSWORD_QUALITY_ALPHANUMERIC, which is the actual password quality for
+                // unified lock.
+                final long quality = getLong(LockPatternUtils.PASSWORD_TYPE_KEY,
+                        DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED, userInfo.id);
+                if (quality == DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) {
+                    // Only possible when it's upgraded from nyc dp3
+                    Slog.i(TAG, "Migrated tied profile lock type");
+                    setLong(LockPatternUtils.PASSWORD_TYPE_KEY,
+                            DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC, userInfo.id);
+                } else if (quality != DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC) {
+                    // It should not happen
+                    Slog.e(TAG, "Invalid tied profile lock type: " + quality);
+                }
+            }
+            try {
+                final String alias = LockPatternUtils.PROFILE_KEY_NAME_ENCRYPT + userInfo.id;
+                java.security.KeyStore keyStore =
+                        java.security.KeyStore.getInstance("AndroidKeyStore");
+                keyStore.load(null);
+                if (keyStore.containsAlias(alias)) {
+                    keyStore.deleteEntry(alias);
+                }
+            } catch (KeyStoreException | NoSuchAlgorithmException |
+                    CertificateException | IOException e) {
+                Slog.e(TAG, "Unable to remove tied profile key", e);
+            }
+        }
+
+        boolean isWatch = mContext.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_WATCH);
+        // Wear used to set DISABLE_LOCKSCREEN to 'true', but because Wear now allows accounts
+        // and device management the lockscreen must be re-enabled now for users that upgrade.
+        if (isWatch && getString("migrated_wear_lockscreen_disabled", null, 0) == null) {
+            final int userCount = users.size();
+            for (int i = 0; i < userCount; i++) {
+                int id = users.get(i).id;
+                setBoolean(LockPatternUtils.DISABLE_LOCKSCREEN_KEY, false, id);
+            }
+            setString("migrated_wear_lockscreen_disabled", "true", 0);
+            Slog.i(TAG, "Migrated lockscreen_disabled for Wear devices");
         }
     }
 
@@ -868,7 +864,7 @@
     }
 
     @Override
-    public boolean getSeparateProfileChallengeEnabled(int userId) throws RemoteException {
+    public boolean getSeparateProfileChallengeEnabled(int userId) {
         checkReadPermission(SEPARATE_PROFILE_CHALLENGE_KEY, userId);
         synchronized (mSeparateChallengeLock) {
             return getBoolean(SEPARATE_PROFILE_CHALLENGE_KEY, false, userId);
@@ -877,7 +873,7 @@
 
     @Override
     public void setSeparateProfileChallengeEnabled(int userId, boolean enabled,
-            String managedUserPassword) throws RemoteException {
+            String managedUserPassword) {
         checkWritePermission(userId);
         synchronized (mSeparateChallengeLock) {
             setBoolean(SEPARATE_PROFILE_CHALLENGE_KEY, enabled, userId);
@@ -891,19 +887,19 @@
     }
 
     @Override
-    public void setBoolean(String key, boolean value, int userId) throws RemoteException {
+    public void setBoolean(String key, boolean value, int userId) {
         checkWritePermission(userId);
         setStringUnchecked(key, userId, value ? "1" : "0");
     }
 
     @Override
-    public void setLong(String key, long value, int userId) throws RemoteException {
+    public void setLong(String key, long value, int userId) {
         checkWritePermission(userId);
         setStringUnchecked(key, userId, Long.toString(value));
     }
 
     @Override
-    public void setString(String key, String value, int userId) throws RemoteException {
+    public void setString(String key, String value, int userId) {
         checkWritePermission(userId);
         setStringUnchecked(key, userId, value);
     }
@@ -2103,7 +2099,7 @@
 
             long handle = getSyntheticPasswordHandleLocked(userId);
             authResult = mSpManager.unwrapPasswordBasedSyntheticPassword(
-                    getGateKeeperService(), handle, userCredential, userId);
+                    getGateKeeperService(), handle, userCredential, userId, progressCallback);
 
             if (authResult.credentialType != credentialType) {
                 Slog.e(TAG, "Credential type mismatch.");
@@ -2126,9 +2122,6 @@
         }
 
         if (response.getResponseCode() == VerifyCredentialResponse.RESPONSE_OK) {
-            if (progressCallback != null) {
-                progressCallback.onCredentialVerified();
-            }
             notifyActivePasswordMetricsAvailable(userCredential, userId);
             unlockKeystore(authResult.authToken.deriveKeyStorePassword(), userId);
 
@@ -2227,7 +2220,7 @@
         }
         long handle = getSyntheticPasswordHandleLocked(userId);
         AuthenticationResult authResult = mSpManager.unwrapPasswordBasedSyntheticPassword(
-                getGateKeeperService(), handle, savedCredential, userId);
+                getGateKeeperService(), handle, savedCredential, userId, null);
         VerifyCredentialResponse response = authResult.gkResponse;
         AuthenticationToken auth = authResult.authToken;
 
@@ -2281,7 +2274,7 @@
                 } else /* isSyntheticPasswordBasedCredentialLocked(userId) */ {
                     long pwdHandle = getSyntheticPasswordHandleLocked(userId);
                     auth = mSpManager.unwrapPasswordBasedSyntheticPassword(getGateKeeperService(),
-                            pwdHandle, null, userId).authToken;
+                            pwdHandle, null, userId, null).authToken;
                 }
             }
             if (isSyntheticPasswordBasedCredentialLocked(userId)) {
diff --git a/com/android/server/locksettings/SyntheticPasswordManager.java b/com/android/server/locksettings/SyntheticPasswordManager.java
index 1a1aa56..7a3a746 100644
--- a/com/android/server/locksettings/SyntheticPasswordManager.java
+++ b/com/android/server/locksettings/SyntheticPasswordManager.java
@@ -781,7 +781,8 @@
      * unknown. Caller might choose to validate it by examining AuthenticationResult.credentialType
      */
     public AuthenticationResult unwrapPasswordBasedSyntheticPassword(IGateKeeperService gatekeeper,
-            long handle, String credential, int userId) throws RemoteException {
+            long handle, String credential, int userId,
+            ICheckCredentialProgressCallback progressCallback) throws RemoteException {
         if (credential == null) {
             credential = DEFAULT_PASSWORD;
         }
@@ -841,7 +842,11 @@
             applicationId = transformUnderSecdiscardable(pwdToken,
                     loadSecdiscardable(handle, userId));
         }
-
+        // Supplied credential passes first stage weaver/gatekeeper check so it should be correct.
+        // Notify the callback so the keyguard UI can proceed immediately.
+        if (progressCallback != null) {
+            progressCallback.onCredentialVerified();
+        }
         result.authToken = unwrapSyntheticPasswordBlob(handle, SYNTHETIC_PASSWORD_PASSWORD_BASED,
                 applicationId, sid, userId);
 
diff --git a/com/android/server/media/AudioPlayerStateMonitor.java b/com/android/server/media/AudioPlayerStateMonitor.java
index be223f1..7881a95 100644
--- a/com/android/server/media/AudioPlayerStateMonitor.java
+++ b/com/android/server/media/AudioPlayerStateMonitor.java
@@ -16,7 +16,7 @@
 
 package com.android.server.media;
 
-import android.annotation.Nullable;
+import android.annotation.NonNull;
 import android.content.Context;
 import android.media.AudioPlaybackConfiguration;
 import android.media.IAudioService;
@@ -27,14 +27,14 @@
 import android.os.Message;
 import android.os.RemoteException;
 import android.os.UserHandle;
+import android.util.ArrayMap;
+import android.util.ArraySet;
 import android.util.IntArray;
 import android.util.Log;
 
 import com.android.internal.annotations.GuardedBy;
 
 import java.io.PrintWriter;
-import java.util.HashSet;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -49,47 +49,57 @@
     private static AudioPlayerStateMonitor sInstance = new AudioPlayerStateMonitor();
 
     /**
-     * Called when the state of audio player is changed.
+     * Listener for handling the active state changes of audio players.
      */
-    interface OnAudioPlayerStateChangedListener {
-        void onAudioPlayerStateChanged(
-                int uid, int prevState, @Nullable AudioPlaybackConfiguration config);
+    interface OnAudioPlayerActiveStateChangedListener {
+        /**
+         * Called when the active state of audio player is changed.
+         *
+         * @param config The audio playback configuration for the audio player for which active
+         *              state was changed. If {@param isRemoved} is {@code true}, this holds
+         *              outdated information.
+         * @param isRemoved {@code true} if the audio player is removed.
+         */
+        void onAudioPlayerActiveStateChanged(
+                @NonNull AudioPlaybackConfiguration config, boolean isRemoved);
     }
 
     private final static class MessageHandler extends Handler {
-        private static final int MSG_AUDIO_PLAYER_STATE_CHANGED = 1;
+        private static final int MSG_AUDIO_PLAYER_ACTIVE_STATE_CHANGED = 1;
 
-        private final OnAudioPlayerStateChangedListener mListsner;
+        private final OnAudioPlayerActiveStateChangedListener mListener;
 
-        public MessageHandler(Looper looper, OnAudioPlayerStateChangedListener listener) {
+        MessageHandler(Looper looper, OnAudioPlayerActiveStateChangedListener listener) {
             super(looper);
-            mListsner = listener;
+            mListener = listener;
         }
 
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
-                case MSG_AUDIO_PLAYER_STATE_CHANGED:
-                    mListsner.onAudioPlayerStateChanged(
-                            msg.arg1, msg.arg2, (AudioPlaybackConfiguration) msg.obj);
+                case MSG_AUDIO_PLAYER_ACTIVE_STATE_CHANGED:
+                    mListener.onAudioPlayerActiveStateChanged((AudioPlaybackConfiguration) msg.obj,
+                            msg.arg1 != 0);
                     break;
             }
         }
 
-        public void sendAudioPlayerStateChangedMessage(int uid, int prevState,
-                AudioPlaybackConfiguration config) {
-            obtainMessage(MSG_AUDIO_PLAYER_STATE_CHANGED, uid, prevState, config).sendToTarget();
+        void sendAudioPlayerActiveStateChangedMessage(
+                final AudioPlaybackConfiguration config, final boolean isRemoved) {
+            obtainMessage(MSG_AUDIO_PLAYER_ACTIVE_STATE_CHANGED,
+                    isRemoved ? 1 : 0, 0 /* unused */, config).sendToTarget();
         }
     }
 
     private final Object mLock = new Object();
     @GuardedBy("mLock")
-    private final Map<OnAudioPlayerStateChangedListener, MessageHandler> mListenerMap =
-            new HashMap<>();
+    private final Map<OnAudioPlayerActiveStateChangedListener, MessageHandler> mListenerMap =
+            new ArrayMap<>();
     @GuardedBy("mLock")
-    private final Map<Integer, Integer> mAudioPlayerStates = new HashMap<>();
+    private final Set<Integer> mActiveAudioUids = new ArraySet<>();
     @GuardedBy("mLock")
-    private final Map<Integer, HashSet<Integer>> mAudioPlayersForUid = new HashMap<>();
+    private ArrayMap<Integer, AudioPlaybackConfiguration> mPrevActiveAudioPlaybackConfigs =
+            new ArrayMap<>();
     // Sorted array of UIDs that had active audio playback. (i.e. playing an audio/video)
     // The UID whose audio playback becomes active at the last comes first.
     // TODO(b/35278867): Find and use unique identifier for apps because apps may share the UID.
@@ -122,32 +132,24 @@
         }
         final long token = Binder.clearCallingIdentity();
         try {
-            final Map<Integer, Integer> prevAudioPlayerStates = new HashMap<>(mAudioPlayerStates);
-            final Map<Integer, HashSet<Integer>> prevAudioPlayersForUid =
-                    new HashMap<>(mAudioPlayersForUid);
             synchronized (mLock) {
-                mAudioPlayerStates.clear();
-                mAudioPlayersForUid.clear();
+                // Update mActiveAudioUids
+                mActiveAudioUids.clear();
+                ArrayMap<Integer, AudioPlaybackConfiguration> activeAudioPlaybackConfigs =
+                        new ArrayMap<>();
                 for (AudioPlaybackConfiguration config : configs) {
-                    int pii = config.getPlayerInterfaceId();
-                    int uid = config.getClientUid();
-                    mAudioPlayerStates.put(pii, config.getPlayerState());
-                    HashSet<Integer> players = mAudioPlayersForUid.get(uid);
-                    if (players == null) {
-                        players = new HashSet<Integer>();
-                        players.add(pii);
-                        mAudioPlayersForUid.put(uid, players);
-                    } else {
-                        players.add(pii);
+                    if (config.isActive()) {
+                        mActiveAudioUids.add(config.getClientUid());
+                        activeAudioPlaybackConfigs.put(config.getPlayerInterfaceId(), config);
                     }
                 }
-                for (AudioPlaybackConfiguration config : configs) {
-                    if (!config.isActive()) {
-                        continue;
-                    }
 
-                    int uid = config.getClientUid();
-                    if (!isActiveState(prevAudioPlayerStates.get(config.getPlayerInterfaceId()))) {
+                // Update mSortedAuioPlaybackClientUids.
+                for (int i = 0; i < activeAudioPlaybackConfigs.size(); ++i) {
+                    AudioPlaybackConfiguration config = activeAudioPlaybackConfigs.valueAt(i);
+                    final int uid = config.getClientUid();
+                    if (!mPrevActiveAudioPlaybackConfigs.containsKey(
+                            config.getPlayerInterfaceId())) {
                         if (DEBUG) {
                             Log.d(TAG, "Found a new active media playback. " +
                                     AudioPlaybackConfiguration.toLogFriendlyString(config));
@@ -163,40 +165,21 @@
                         mSortedAudioPlaybackClientUids.add(0, uid);
                     }
                 }
-                // Notify the change of audio player states.
+                // Notify the active state change of audio players.
                 for (AudioPlaybackConfiguration config : configs) {
-                    final Integer prevState = prevAudioPlayerStates.get(config.getPlayerInterfaceId());
-                    final int prevStateInt =
-                            (prevState == null) ? AudioPlaybackConfiguration.PLAYER_STATE_UNKNOWN :
-                                prevState.intValue();
-                    if (prevStateInt != config.getPlayerState()) {
-                        sendAudioPlayerStateChangedMessageLocked(
-                                config.getClientUid(), prevStateInt, config);
+                    final int pii = config.getPlayerInterfaceId();
+                    boolean wasActive = mPrevActiveAudioPlaybackConfigs.remove(pii) != null;
+                    if (wasActive != config.isActive()) {
+                        sendAudioPlayerActiveStateChangedMessageLocked(
+                                config, /* isRemoved */ false);
                     }
                 }
-                for (Integer prevUid : prevAudioPlayersForUid.keySet()) {
-                    // If all players for prevUid is removed, notify the prev state was
-                    // PLAYER_STATE_STARTED only when there were a player whose state was
-                    // PLAYER_STATE_STARTED, otherwise any inactive state is okay to notify.
-                    if (!mAudioPlayersForUid.containsKey(prevUid)) {
-                        Set<Integer> prevPlayers = prevAudioPlayersForUid.get(prevUid);
-                        int prevState = AudioPlaybackConfiguration.PLAYER_STATE_UNKNOWN;
-                        for (int pii : prevPlayers) {
-                            Integer state = prevAudioPlayerStates.get(pii);
-                            if (state == null) {
-                                continue;
-                            }
-                            if (state == AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
-                                prevState = state;
-                                break;
-                            } else if (prevState
-                                    == AudioPlaybackConfiguration.PLAYER_STATE_UNKNOWN) {
-                                prevState = state;
-                            }
-                        }
-                        sendAudioPlayerStateChangedMessageLocked(prevUid, prevState, null);
-                    }
+                for (AudioPlaybackConfiguration config : mPrevActiveAudioPlaybackConfigs.values()) {
+                    sendAudioPlayerActiveStateChangedMessageLocked(config, /* isRemoved */ true);
                 }
+
+                // Update mPrevActiveAudioPlaybackConfigs
+                mPrevActiveAudioPlaybackConfigs = activeAudioPlaybackConfigs;
             }
         } finally {
             Binder.restoreCallingIdentity(token);
@@ -204,9 +187,10 @@
     }
 
     /**
-     * Registers OnAudioPlayerStateChangedListener.
+     * Registers OnAudioPlayerActiveStateChangedListener.
      */
-    public void registerListener(OnAudioPlayerStateChangedListener listener, Handler handler) {
+    public void registerListener(
+            OnAudioPlayerActiveStateChangedListener listener, Handler handler) {
         synchronized (mLock) {
             mListenerMap.put(listener, new MessageHandler((handler == null) ?
                     Looper.myLooper() : handler.getLooper(), listener));
@@ -214,9 +198,9 @@
     }
 
     /**
-     * Unregisters OnAudioPlayerStateChangedListener.
+     * Unregisters OnAudioPlayerActiveStateChangedListener.
      */
-    public void unregisterListener(OnAudioPlayerStateChangedListener listener) {
+    public void unregisterListener(OnAudioPlayerActiveStateChangedListener listener) {
         synchronized (mLock) {
             mListenerMap.remove(listener);
         }
@@ -239,16 +223,7 @@
      */
     public boolean isPlaybackActive(int uid) {
         synchronized (mLock) {
-            Set<Integer> players = mAudioPlayersForUid.get(uid);
-            if (players == null) {
-                return false;
-            }
-            for (Integer pii : players) {
-                if (isActiveState(mAudioPlayerStates.get(pii))) {
-                    return true;
-                }
-            }
-            return false;
+            return mActiveAudioUids.contains(uid);
         }
     }
 
@@ -314,14 +289,10 @@
         }
     }
 
-    private void sendAudioPlayerStateChangedMessageLocked(
-            final int uid, final int prevState, final AudioPlaybackConfiguration config) {
+    private void sendAudioPlayerActiveStateChangedMessageLocked(
+            final AudioPlaybackConfiguration config, final boolean isRemoved) {
         for (MessageHandler messageHandler : mListenerMap.values()) {
-            messageHandler.sendAudioPlayerStateChangedMessage(uid, prevState, config);
+            messageHandler.sendAudioPlayerActiveStateChangedMessage(config, isRemoved);
         }
     }
-
-    private static boolean isActiveState(Integer state) {
-        return state != null && state.equals(AudioPlaybackConfiguration.PLAYER_STATE_STARTED);
-    }
 }
diff --git a/com/android/server/media/MediaRouterService.java b/com/android/server/media/MediaRouterService.java
index 3c9e1d4..0b089fb 100644
--- a/com/android/server/media/MediaRouterService.java
+++ b/com/android/server/media/MediaRouterService.java
@@ -19,7 +19,7 @@
 import com.android.internal.util.DumpUtils;
 import com.android.server.Watchdog;
 
-import android.annotation.Nullable;
+import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -100,7 +100,9 @@
     private final IAudioService mAudioService;
     private final AudioPlayerStateMonitor mAudioPlayerStateMonitor;
     private final Handler mHandler = new Handler();
-    private final AudioRoutesInfo mCurAudioRoutesInfo = new AudioRoutesInfo();
+    private final AudioRoutesInfo mAudioRoutesInfo = new AudioRoutesInfo();
+    private final IntArray mActivePlayerMinPriorityQueue = new IntArray();
+    private final IntArray mActivePlayerUidMinPriorityQueue = new IntArray();
 
     public MediaRouterService(Context context) {
         mContext = context;
@@ -111,7 +113,7 @@
 
         mAudioPlayerStateMonitor = AudioPlayerStateMonitor.getInstance();
         mAudioPlayerStateMonitor.registerListener(
-                new AudioPlayerStateMonitor.OnAudioPlayerStateChangedListener() {
+                new AudioPlayerStateMonitor.OnAudioPlayerActiveStateChangedListener() {
             static final long WAIT_MS = 500;
             final Runnable mRestoreBluetoothA2dpRunnable = new Runnable() {
                 @Override
@@ -121,39 +123,41 @@
             };
 
             @Override
-            public void onAudioPlayerStateChanged(
-                    int uid, int prevState, @Nullable AudioPlaybackConfiguration config) {
+            public void onAudioPlayerActiveStateChanged(
+                    @NonNull AudioPlaybackConfiguration config, boolean isRemoved) {
+                final boolean active = !isRemoved && config.isActive();
+                final int pii = config.getPlayerInterfaceId();
+                final int uid = config.getClientUid();
+
+                final int idx = mActivePlayerMinPriorityQueue.indexOf(pii);
+                // Keep the latest active player and its uid at the end of the queue.
+                if (idx >= 0) {
+                    mActivePlayerMinPriorityQueue.remove(idx);
+                    mActivePlayerUidMinPriorityQueue.remove(idx);
+                }
+
                 int restoreUid = -1;
-                boolean active = config == null ? false : config.isActive();
                 if (active) {
+                    mActivePlayerMinPriorityQueue.add(config.getPlayerInterfaceId());
+                    mActivePlayerUidMinPriorityQueue.add(uid);
                     restoreUid = uid;
-                } else if (prevState != AudioPlaybackConfiguration.PLAYER_STATE_STARTED) {
-                    // Noting to do if the prev state is not an active state.
-                    return;
-                } else {
-                    IntArray sortedAudioPlaybackClientUids =
-                            mAudioPlayerStateMonitor.getSortedAudioPlaybackClientUids();
-                    for (int i = 0; i < sortedAudioPlaybackClientUids.size(); ++i) {
-                        if (mAudioPlayerStateMonitor.isPlaybackActive(
-                                sortedAudioPlaybackClientUids.get(i))) {
-                            restoreUid = sortedAudioPlaybackClientUids.get(i);
-                            break;
-                        }
-                    }
+                } else if (mActivePlayerUidMinPriorityQueue.size() > 0) {
+                    restoreUid = mActivePlayerUidMinPriorityQueue.get(
+                            mActivePlayerUidMinPriorityQueue.size() - 1);
                 }
 
                 mHandler.removeCallbacks(mRestoreBluetoothA2dpRunnable);
                 if (restoreUid >= 0) {
                     restoreRoute(restoreUid);
                     if (DEBUG) {
-                        Slog.d(TAG, "onAudioPlayerStateChanged: " + "uid " + uid
-                                + " active " + active + " restoring " + restoreUid);
+                        Slog.d(TAG, "onAudioPlayerActiveStateChanged: " + "uid=" + uid
+                                + ", active=" + active + ", restoreUid=" + restoreUid);
                     }
                 } else {
                     mHandler.postDelayed(mRestoreBluetoothA2dpRunnable, WAIT_MS);
                     if (DEBUG) {
-                        Slog.d(TAG, "onAudioPlayerStateChanged: " + "uid " + uid
-                                + " active " + active + " delaying");
+                        Slog.d(TAG, "onAudioPlayerActiveStateChanged: " + "uid=" + uid
+                                + ", active=" + active + ", delaying");
                     }
                 }
             }
@@ -166,7 +170,7 @@
                 @Override
                 public void dispatchAudioRoutesChanged(final AudioRoutesInfo newRoutes) {
                     synchronized (mLock) {
-                        if (newRoutes.mainType != mCurAudioRoutesInfo.mainType) {
+                        if (newRoutes.mainType != mAudioRoutesInfo.mainType) {
                             if ((newRoutes.mainType & (AudioRoutesInfo.MAIN_HEADSET
                                     | AudioRoutesInfo.MAIN_HEADPHONES
                                     | AudioRoutesInfo.MAIN_USB)) == 0) {
@@ -176,10 +180,10 @@
                                 // headset was plugged in.
                                 mGlobalBluetoothA2dpOn = false;
                             }
-                            mCurAudioRoutesInfo.mainType = newRoutes.mainType;
+                            mAudioRoutesInfo.mainType = newRoutes.mainType;
                         }
                         if (!TextUtils.equals(
-                                newRoutes.bluetoothName, mCurAudioRoutesInfo.bluetoothName)) {
+                                newRoutes.bluetoothName, mAudioRoutesInfo.bluetoothName)) {
                             if (newRoutes.bluetoothName == null) {
                                 // BT was disconnected.
                                 mGlobalBluetoothA2dpOn = false;
@@ -187,8 +191,14 @@
                                 // BT was connected or changed.
                                 mGlobalBluetoothA2dpOn = true;
                             }
-                            mCurAudioRoutesInfo.bluetoothName = newRoutes.bluetoothName;
+                            mAudioRoutesInfo.bluetoothName = newRoutes.bluetoothName;
                         }
+                        // Although a Bluetooth device is connected before a new audio playback is
+                        // started, dispatchAudioRoutChanged() can be called after
+                        // onAudioPlayerActiveStateChanged(). That causes restoreBluetoothA2dp()
+                        // is called before mGlobalBluetoothA2dpOn is updated.
+                        // Calling restoreBluetoothA2dp() here could prevent that.
+                        restoreBluetoothA2dp();
                     }
                 }
             });
@@ -405,12 +415,17 @@
 
     void restoreBluetoothA2dp() {
         try {
+            boolean btConnected = false;
             boolean a2dpOn = false;
             synchronized (mLock) {
+                btConnected = mAudioRoutesInfo.bluetoothName != null;
                 a2dpOn = mGlobalBluetoothA2dpOn;
             }
-            Slog.v(TAG, "restoreBluetoothA2dp(" + a2dpOn + ")");
-            mAudioService.setBluetoothA2dpOn(a2dpOn);
+            // We don't need to change a2dp status when bluetooth is not connected.
+            if (btConnected) {
+                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/MediaSessionService.java b/com/android/server/media/MediaSessionService.java
index f6a81d0..06f4f5e 100644
--- a/com/android/server/media/MediaSessionService.java
+++ b/com/android/server/media/MediaSessionService.java
@@ -16,7 +16,6 @@
 
 package com.android.server.media;
 
-import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.INotificationManager;
 import android.app.KeyguardManager;
@@ -138,23 +137,19 @@
         mAudioService = getAudioService();
         mAudioPlayerStateMonitor = AudioPlayerStateMonitor.getInstance();
         mAudioPlayerStateMonitor.registerListener(
-                new AudioPlayerStateMonitor.OnAudioPlayerStateChangedListener() {
-            @Override
-            public void onAudioPlayerStateChanged(
-                    int uid, int prevState, @Nullable AudioPlaybackConfiguration config) {
-                if (config == null || !config.isActive() || config.getPlayerType()
-                        == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL) {
-                    return;
-                }
-                synchronized (mLock) {
-                    FullUserRecord user =
-                            getFullUserRecordLocked(UserHandle.getUserId(uid));
-                    if (user != null) {
-                        user.mPriorityStack.updateMediaButtonSessionIfNeeded();
+                (config, isRemoved) -> {
+                    if (isRemoved || !config.isActive() || config.getPlayerType()
+                            == AudioPlaybackConfiguration.PLAYER_TYPE_JAM_SOUNDPOOL) {
+                        return;
                     }
-                }
-            }
-        }, null /* handler */);
+                    synchronized (mLock) {
+                        FullUserRecord user = getFullUserRecordLocked(
+                                UserHandle.getUserId(config.getClientUid()));
+                        if (user != null) {
+                            user.mPriorityStack.updateMediaButtonSessionIfNeeded();
+                        }
+                    }
+                }, null /* handler */);
         mAudioPlayerStateMonitor.registerSelfIntoAudioServiceIfNeeded(mAudioService);
         mContentResolver = getContext().getContentResolver();
         mSettingsObserver = new SettingsObserver();
diff --git a/com/android/server/net/NetworkPolicyLogger.java b/com/android/server/net/NetworkPolicyLogger.java
new file mode 100644
index 0000000..2bd9cab
--- /dev/null
+++ b/com/android/server/net/NetworkPolicyLogger.java
@@ -0,0 +1,514 @@
+/*
+ * 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.net;
+
+import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_DOZABLE;
+import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_DOZABLE;
+import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_POWERSAVE;
+import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_NAME_STANDBY;
+import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_POWERSAVE;
+import static android.net.NetworkPolicyManager.FIREWALL_CHAIN_STANDBY;
+import static android.net.NetworkPolicyManager.FIREWALL_RULE_ALLOW;
+import static android.net.NetworkPolicyManager.FIREWALL_RULE_DEFAULT;
+import static android.net.NetworkPolicyManager.FIREWALL_RULE_DENY;
+
+import android.app.ActivityManager;
+import android.net.NetworkPolicyManager;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.util.IndentingPrintWriter;
+import com.android.internal.util.RingBuffer;
+import com.android.server.am.ProcessList;
+
+import java.text.SimpleDateFormat;
+import java.util.Arrays;
+import java.util.Date;
+
+public class NetworkPolicyLogger {
+    static final String TAG = "NetworkPolicy";
+
+    static final boolean LOGD = Log.isLoggable(TAG, Log.DEBUG);
+    static final boolean LOGV = Log.isLoggable(TAG, Log.VERBOSE);
+
+    private static final int MAX_LOG_SIZE =
+            ActivityManager.isLowRamDeviceStatic() ? 20 : 50;
+    private static final int MAX_NETWORK_BLOCKED_LOG_SIZE =
+            ActivityManager.isLowRamDeviceStatic() ? 50 : 100;
+
+    private static final int EVENT_TYPE_GENERIC = 0;
+    private static final int EVENT_NETWORK_BLOCKED = 1;
+    private static final int EVENT_UID_STATE_CHANGED = 2;
+    private static final int EVENT_POLICIES_CHANGED = 3;
+    private static final int EVENT_METEREDNESS_CHANGED = 4;
+    private static final int EVENT_USER_STATE_REMOVED = 5;
+    private static final int EVENT_RESTRICT_BG_CHANGED = 6;
+    private static final int EVENT_DEVICE_IDLE_MODE_ENABLED = 7;
+    private static final int EVENT_APP_IDLE_STATE_CHANGED = 8;
+    private static final int EVENT_PAROLE_STATE_CHANGED = 9;
+    private static final int EVENT_TEMP_POWER_SAVE_WL_CHANGED = 10;
+    private static final int EVENT_UID_FIREWALL_RULE_CHANGED = 11;
+    private static final int EVENT_FIREWALL_CHAIN_ENABLED = 12;
+
+    static final int NTWK_BLOCKED_POWER = 0;
+    static final int NTWK_ALLOWED_NON_METERED = 1;
+    static final int NTWK_BLOCKED_BLACKLIST = 2;
+    static final int NTWK_ALLOWED_WHITELIST = 3;
+    static final int NTWK_ALLOWED_TMP_WHITELIST = 4;
+    static final int NTWK_BLOCKED_BG_RESTRICT = 5;
+    static final int NTWK_ALLOWED_DEFAULT = 6;
+
+    private final LogBuffer mNetworkBlockedBuffer = new LogBuffer(MAX_NETWORK_BLOCKED_LOG_SIZE);
+    private final LogBuffer mUidStateChangeBuffer = new LogBuffer(MAX_LOG_SIZE);
+    private final LogBuffer mEventsBuffer = new LogBuffer(MAX_LOG_SIZE);
+
+    private final Object mLock = new Object();
+
+    void networkBlocked(int uid, int reason) {
+        synchronized (mLock) {
+            if (LOGD) Slog.d(TAG, uid + " is " + getBlockedReason(reason));
+            mNetworkBlockedBuffer.networkBlocked(uid, reason);
+        }
+    }
+
+    void uidStateChanged(int uid, int procState, long procStateSeq) {
+        synchronized (mLock) {
+            if (LOGV) Slog.v(TAG,
+                    uid + " state changed to " + procState + " with seq=" + procStateSeq);
+            mUidStateChangeBuffer.uidStateChanged(uid, procState, procStateSeq);
+        }
+    }
+
+    void event(String msg) {
+        synchronized (mLock) {
+            if (LOGV) Slog.v(TAG, msg);
+            mEventsBuffer.event(msg);
+        }
+    }
+
+    void uidPolicyChanged(int uid, int oldPolicy, int newPolicy) {
+        synchronized (mLock) {
+            if (LOGV) Slog.v(TAG, getPolicyChangedLog(uid, oldPolicy, newPolicy));
+            mEventsBuffer.uidPolicyChanged(uid, oldPolicy, newPolicy);
+        }
+    }
+
+    void meterednessChanged(int netId, boolean newMetered) {
+        synchronized (mLock) {
+            if (LOGD) Slog.d(TAG, getMeterednessChangedLog(netId, newMetered));
+            mEventsBuffer.meterednessChanged(netId, newMetered);
+        }
+    }
+
+    void removingUserState(int userId) {
+        synchronized (mLock) {
+            if (LOGD) Slog.d(TAG, getUserRemovedLog(userId));
+            mEventsBuffer.userRemoved(userId);
+        }
+    }
+
+    void restrictBackgroundChanged(boolean oldValue, boolean newValue) {
+        synchronized (mLock) {
+            if (LOGD) Slog.d(TAG,
+                    getRestrictBackgroundChangedLog(oldValue, newValue));
+            mEventsBuffer.restrictBackgroundChanged(oldValue, newValue);
+        }
+    }
+
+    void deviceIdleModeEnabled(boolean enabled) {
+        synchronized (mLock) {
+            if (LOGD) Slog.d(TAG, getDeviceIdleModeEnabled(enabled));
+            mEventsBuffer.deviceIdleModeEnabled(enabled);
+        }
+    }
+
+    void appIdleStateChanged(int uid, boolean idle) {
+        synchronized (mLock) {
+            if (LOGD) Slog.d(TAG, getAppIdleChangedLog(uid, idle));
+            mEventsBuffer.appIdleStateChanged(uid, idle);
+        }
+    }
+
+    void paroleStateChanged(boolean paroleOn) {
+        synchronized (mLock) {
+            if (LOGD) Slog.d(TAG, getParoleStateChanged(paroleOn));
+            mEventsBuffer.paroleStateChanged(paroleOn);
+        }
+    }
+
+    void tempPowerSaveWlChanged(int appId, boolean added) {
+        synchronized (mLock) {
+            if (LOGV) Slog.v(TAG, getTempPowerSaveWlChangedLog(appId, added));
+            mEventsBuffer.tempPowerSaveWlChanged(appId, added);
+        }
+    }
+
+    void uidFirewallRuleChanged(int chain, int uid, int rule) {
+        synchronized (mLock) {
+            if (LOGV) Slog.v(TAG, getUidFirewallRuleChangedLog(chain, uid, rule));
+            mEventsBuffer.uidFirewallRuleChanged(chain, uid, rule);
+        }
+    }
+
+    void firewallChainEnabled(int chain, boolean enabled) {
+        synchronized (mLock) {
+            if (LOGD) Slog.d(TAG, getFirewallChainEnabledLog(chain, enabled));
+            mEventsBuffer.firewallChainEnabled(chain, enabled);
+        }
+    }
+
+    void firewallRulesChanged(int chain, int[] uids, int[] rules) {
+        synchronized (mLock) {
+            final String log = "Firewall rules changed for " + getFirewallChainName(chain)
+                    + "; uids=" + Arrays.toString(uids) + "; rules=" + Arrays.toString(rules);
+            if (LOGD) Slog.d(TAG, log);
+            mEventsBuffer.event(log);
+        }
+    }
+
+    void dumpLogs(IndentingPrintWriter pw) {
+        synchronized (mLock) {
+            pw.println();
+            pw.println("mEventLogs (most recent first):");
+            pw.increaseIndent();
+            mEventsBuffer.reverseDump(pw);
+            pw.decreaseIndent();
+
+            pw.println();
+            pw.println("mNetworkBlockedLogs (most recent first):");
+            pw.increaseIndent();
+            mNetworkBlockedBuffer.reverseDump(pw);
+            pw.decreaseIndent();
+
+            pw.println();
+            pw.println("mUidStateChangeLogs (most recent first):");
+            pw.increaseIndent();
+            mUidStateChangeBuffer.reverseDump(pw);
+            pw.decreaseIndent();
+        }
+    }
+
+    private static String getBlockedReason(int reason) {
+        switch (reason) {
+            case NTWK_BLOCKED_POWER:
+                return "blocked by power restrictions";
+            case NTWK_ALLOWED_NON_METERED:
+                return "allowed on unmetered network";
+            case NTWK_BLOCKED_BLACKLIST:
+                return "blacklisted on metered network";
+            case NTWK_ALLOWED_WHITELIST:
+                return "whitelisted on metered network";
+            case NTWK_ALLOWED_TMP_WHITELIST:
+                return "temporary whitelisted on metered network";
+            case NTWK_BLOCKED_BG_RESTRICT:
+                return "blocked when background is restricted";
+            case NTWK_ALLOWED_DEFAULT:
+                return "allowed by default";
+            default:
+                return String.valueOf(reason);
+        }
+    }
+
+    private static String getPolicyChangedLog(int uid, int oldPolicy, int newPolicy) {
+        return "Policy for " + uid + " changed from "
+                + NetworkPolicyManager.uidPoliciesToString(oldPolicy) + " to "
+                + NetworkPolicyManager.uidPoliciesToString(newPolicy);
+    }
+
+    private static String getMeterednessChangedLog(int netId, boolean newMetered) {
+        return "Meteredness of netId=" + netId + " changed to " + newMetered;
+    }
+
+    private static String getUserRemovedLog(int userId) {
+        return "Remove state for u" + userId;
+    }
+
+    private static String getRestrictBackgroundChangedLog(boolean oldValue, boolean newValue) {
+        return "Changed restrictBackground: " + oldValue + "->" + newValue;
+    }
+
+    private static String getDeviceIdleModeEnabled(boolean enabled) {
+        return "DeviceIdleMode enabled: " + enabled;
+    }
+
+    private static String getAppIdleChangedLog(int uid, boolean idle) {
+        return "App idle state of uid " + uid + ": " + idle;
+    }
+
+    private static String getParoleStateChanged(boolean paroleOn) {
+        return "Parole state: " + paroleOn;
+    }
+
+    private static String getTempPowerSaveWlChangedLog(int appId, boolean added) {
+        return "temp-power-save whitelist for " + appId + " changed to: " + added;
+    }
+
+    private static String getUidFirewallRuleChangedLog(int chain, int uid, int rule) {
+        return String.format("Firewall rule changed: %d-%s-%s",
+                uid, getFirewallChainName(chain), getFirewallRuleName(rule));
+    }
+
+    private static String getFirewallChainEnabledLog(int chain, boolean enabled) {
+        return "Firewall chain " + getFirewallChainName(chain) + " state: " + enabled;
+    }
+
+    private static String getFirewallChainName(int chain) {
+        switch (chain) {
+            case FIREWALL_CHAIN_DOZABLE:
+                return FIREWALL_CHAIN_NAME_DOZABLE;
+            case FIREWALL_CHAIN_STANDBY:
+                return FIREWALL_CHAIN_NAME_STANDBY;
+            case FIREWALL_CHAIN_POWERSAVE:
+                return FIREWALL_CHAIN_NAME_POWERSAVE;
+            default:
+                return String.valueOf(chain);
+        }
+    }
+
+    private static String getFirewallRuleName(int rule) {
+        switch (rule) {
+            case FIREWALL_RULE_DEFAULT:
+                return "default";
+            case FIREWALL_RULE_ALLOW:
+                return "allow";
+            case FIREWALL_RULE_DENY:
+                return "deny";
+            default:
+                return String.valueOf(rule);
+        }
+    }
+
+    private final static class LogBuffer extends RingBuffer<Data> {
+        private static final SimpleDateFormat sFormatter
+                = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss:SSS");
+        private static final Date sDate = new Date();
+
+        public LogBuffer(int capacity) {
+            super(Data.class, capacity);
+        }
+
+        public void uidStateChanged(int uid, int procState, long procStateSeq) {
+            final Data data = getNextSlot();
+            if (data == null) return;
+
+            data.reset();
+            data.type = EVENT_UID_STATE_CHANGED;
+            data.ifield1 = uid;
+            data.ifield2 = procState;
+            data.lfield1 = procStateSeq;
+            data.timeStamp = System.currentTimeMillis();
+        }
+
+        public void event(String msg) {
+            final Data data = getNextSlot();
+            if (data == null) return;
+
+            data.reset();
+            data.type = EVENT_TYPE_GENERIC;
+            data.sfield1 = msg;
+            data.timeStamp = System.currentTimeMillis();
+        }
+
+        public void networkBlocked(int uid, int reason) {
+            final Data data = getNextSlot();
+            if (data == null) return;
+
+            data.reset();
+            data.type = EVENT_NETWORK_BLOCKED;
+            data.ifield1 = uid;
+            data.ifield2 = reason;
+            data.timeStamp = System.currentTimeMillis();
+        }
+
+        public void uidPolicyChanged(int uid, int oldPolicy, int newPolicy) {
+            final Data data = getNextSlot();
+            if (data == null) return;
+
+            data.reset();
+            data.type = EVENT_POLICIES_CHANGED;
+            data.ifield1 = uid;
+            data.ifield2 = oldPolicy;
+            data.ifield3 = newPolicy;
+            data.timeStamp = System.currentTimeMillis();
+        }
+
+        public void meterednessChanged(int netId, boolean newMetered) {
+            final Data data = getNextSlot();
+            if (data == null) return;
+
+            data.reset();
+            data.type = EVENT_METEREDNESS_CHANGED;
+            data.ifield1 = netId;
+            data.bfield1 = newMetered;
+            data.timeStamp = System.currentTimeMillis();
+        }
+
+        public void userRemoved(int userId) {
+            final Data data = getNextSlot();
+            if (data == null) return;
+
+            data.reset();
+            data.type = EVENT_USER_STATE_REMOVED;
+            data.ifield1 = userId;
+            data.timeStamp = System.currentTimeMillis();
+        }
+
+        public void restrictBackgroundChanged(boolean oldValue, boolean newValue) {
+            final Data data = getNextSlot();
+            if (data == null) return;
+
+            data.reset();
+            data.type = EVENT_RESTRICT_BG_CHANGED;
+            data.bfield1 = oldValue;
+            data.bfield2 = newValue;
+            data.timeStamp = System.currentTimeMillis();
+        }
+
+        public void deviceIdleModeEnabled(boolean enabled) {
+            final Data data = getNextSlot();
+            if (data == null) return;
+
+            data.reset();
+            data.type = EVENT_DEVICE_IDLE_MODE_ENABLED;
+            data.bfield1 = enabled;
+            data.timeStamp = System.currentTimeMillis();
+        }
+
+        public void appIdleStateChanged(int uid, boolean idle) {
+            final Data data = getNextSlot();
+            if (data == null) return;
+
+            data.reset();
+            data.type = EVENT_APP_IDLE_STATE_CHANGED;
+            data.ifield1 = uid;
+            data.bfield1 = idle;
+            data.timeStamp = System.currentTimeMillis();
+        }
+
+        public void paroleStateChanged(boolean paroleOn) {
+            final Data data = getNextSlot();
+            if (data == null) return;
+
+            data.reset();
+            data.type = EVENT_PAROLE_STATE_CHANGED;
+            data.bfield1 = paroleOn;
+            data.timeStamp = System.currentTimeMillis();
+        }
+
+        public void tempPowerSaveWlChanged(int appId, boolean added) {
+            final Data data = getNextSlot();
+            if (data == null) return;
+
+            data.reset();
+            data.type = EVENT_TEMP_POWER_SAVE_WL_CHANGED;
+            data.ifield1 = appId;
+            data.bfield1 = added;
+            data.timeStamp = System.currentTimeMillis();
+        }
+
+        public void uidFirewallRuleChanged(int chain, int uid, int rule) {
+            final Data data = getNextSlot();
+            if (data == null) return;
+
+            data.reset();
+            data.type = EVENT_UID_FIREWALL_RULE_CHANGED;
+            data.ifield1 = chain;
+            data.ifield2 = uid;
+            data.ifield3 = rule;
+            data.timeStamp = System.currentTimeMillis();
+        }
+
+        public void firewallChainEnabled(int chain, boolean enabled) {
+            final Data data = getNextSlot();
+            if (data == null) return;
+
+            data.reset();
+            data.type = EVENT_FIREWALL_CHAIN_ENABLED;
+            data.ifield1 = chain;
+            data.bfield1 = enabled;
+            data.timeStamp = System.currentTimeMillis();
+        }
+
+        public void reverseDump(IndentingPrintWriter pw) {
+            final Data[] allData = toArray();
+            for (int i = allData.length - 1; i >= 0; --i) {
+                if (allData[i] == null) {
+                    pw.println("NULL");
+                    continue;
+                }
+                pw.print(formatDate(allData[i].timeStamp));
+                pw.print(" - ");
+                pw.println(getContent(allData[i]));
+            }
+        }
+
+        public String getContent(Data data) {
+            switch (data.type) {
+                case EVENT_TYPE_GENERIC:
+                    return data.sfield1;
+                case EVENT_NETWORK_BLOCKED:
+                    return data.ifield1 + "-" + getBlockedReason(data.ifield2);
+                case EVENT_UID_STATE_CHANGED:
+                    return data.ifield1 + "-" + ProcessList.makeProcStateString(data.ifield2)
+                            + "-" + data.lfield1;
+                case EVENT_POLICIES_CHANGED:
+                    return getPolicyChangedLog(data.ifield1, data.ifield2, data.ifield3);
+                case EVENT_METEREDNESS_CHANGED:
+                    return getMeterednessChangedLog(data.ifield1, data.bfield1);
+                case EVENT_USER_STATE_REMOVED:
+                    return getUserRemovedLog(data.ifield1);
+                case EVENT_RESTRICT_BG_CHANGED:
+                    return getRestrictBackgroundChangedLog(data.bfield1, data.bfield2);
+                case EVENT_DEVICE_IDLE_MODE_ENABLED:
+                    return getDeviceIdleModeEnabled(data.bfield1);
+                case EVENT_APP_IDLE_STATE_CHANGED:
+                    return getAppIdleChangedLog(data.ifield1, data.bfield1);
+                case EVENT_PAROLE_STATE_CHANGED:
+                    return getParoleStateChanged(data.bfield1);
+                case EVENT_TEMP_POWER_SAVE_WL_CHANGED:
+                    return getTempPowerSaveWlChangedLog(data.ifield1, data.bfield1);
+                case EVENT_UID_FIREWALL_RULE_CHANGED:
+                    return getUidFirewallRuleChangedLog(data.ifield1, data.ifield2, data.ifield3);
+                case EVENT_FIREWALL_CHAIN_ENABLED:
+                    return getFirewallChainEnabledLog(data.ifield1, data.bfield1);
+                default:
+                    return String.valueOf(data.type);
+            }
+        }
+
+        private String formatDate(long millis) {
+            sDate.setTime(millis);
+            return sFormatter.format(sDate);
+        }
+    }
+
+    public final static class Data {
+        int type;
+        long timeStamp;
+
+        int ifield1;
+        int ifield2;
+        int ifield3;
+        long lfield1;
+        boolean bfield1;
+        boolean bfield2;
+        String sfield1;
+
+        public void reset(){
+            sfield1 = null;
+        }
+    }
+}
diff --git a/com/android/server/net/NetworkPolicyManagerService.java b/com/android/server/net/NetworkPolicyManagerService.java
index 3fa3cd4..fdfe241 100644
--- a/com/android/server/net/NetworkPolicyManagerService.java
+++ b/com/android/server/net/NetworkPolicyManagerService.java
@@ -82,6 +82,13 @@
 import static com.android.internal.util.XmlUtils.writeLongAttribute;
 import static com.android.internal.util.XmlUtils.writeStringAttribute;
 import static com.android.server.NetworkManagementService.LIMIT_GLOBAL_ALERT;
+import static com.android.server.net.NetworkPolicyLogger.NTWK_ALLOWED_DEFAULT;
+import static com.android.server.net.NetworkPolicyLogger.NTWK_ALLOWED_NON_METERED;
+import static com.android.server.net.NetworkPolicyLogger.NTWK_ALLOWED_TMP_WHITELIST;
+import static com.android.server.net.NetworkPolicyLogger.NTWK_ALLOWED_WHITELIST;
+import static com.android.server.net.NetworkPolicyLogger.NTWK_BLOCKED_BG_RESTRICT;
+import static com.android.server.net.NetworkPolicyLogger.NTWK_BLOCKED_BLACKLIST;
+import static com.android.server.net.NetworkPolicyLogger.NTWK_BLOCKED_POWER;
 import static com.android.server.net.NetworkStatsService.ACTION_NETWORK_STATS_UPDATED;
 
 import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
@@ -248,9 +255,9 @@
  * </ul>
  */
 public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub {
-    static final String TAG = "NetworkPolicy";
-    private static final boolean LOGD = false;
-    private static final boolean LOGV = false;
+    static final String TAG = NetworkPolicyLogger.TAG;
+    private static final boolean LOGD = NetworkPolicyLogger.LOGD;
+    private static final boolean LOGV = NetworkPolicyLogger.LOGV;
 
     private static final int VERSION_INIT = 1;
     private static final int VERSION_ADDED_SNOOZE = 2;
@@ -265,13 +272,6 @@
     private static final int VERSION_ADDED_CYCLE = 11;
     private static final int VERSION_LATEST = VERSION_ADDED_CYCLE;
 
-    /**
-     * Max items written to {@link #ProcStateSeqHistory}.
-     */
-    @VisibleForTesting
-    public static final int MAX_PROC_STATE_SEQ_HISTORY =
-            ActivityManager.isLowRamDeviceStatic() ? 50 : 200;
-
     @VisibleForTesting
     public static final int TYPE_WARNING = SystemMessage.NOTE_NET_WARNING;
     @VisibleForTesting
@@ -471,13 +471,7 @@
 
     private ActivityManagerInternal mActivityManagerInternal;
 
-    /**
-     * This is used for debugging purposes. Whenever the IUidObserver.onUidStateChanged is called,
-     * the uid and procStateSeq will be written to this and will be printed as part of dump.
-     */
-    @VisibleForTesting
-    public ProcStateSeqHistory mObservedHistory
-            = new ProcStateSeqHistory(MAX_PROC_STATE_SEQ_HISTORY);
+    private final NetworkPolicyLogger mLogger = new NetworkPolicyLogger();
 
     // TODO: keep whitelist of system-critical services that should never have
     // rules enforced, such as system, phone, and radio UIDs.
@@ -966,6 +960,7 @@
                         .hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED);
 
                 if ((oldMetered != newMetered) || mNetworkMetered.indexOfKey(network.netId) < 0) {
+                    mLogger.meterednessChanged(network.netId, newMetered);
                     mNetworkMetered.put(network.netId, newMetered);
                     updateNetworkRulesNL();
                 }
@@ -2148,6 +2143,7 @@
                 final int oldPolicy = mUidPolicy.get(uid, POLICY_NONE);
                 if (oldPolicy != policy) {
                     setUidPolicyUncheckedUL(uid, oldPolicy, policy, true);
+                    mLogger.uidPolicyChanged(uid, oldPolicy, policy);
                 }
             } finally {
                 Binder.restoreCallingIdentity(token);
@@ -2168,6 +2164,7 @@
             policy |= oldPolicy;
             if (oldPolicy != policy) {
                 setUidPolicyUncheckedUL(uid, oldPolicy, policy, true);
+                mLogger.uidPolicyChanged(uid, oldPolicy, policy);
             }
         }
     }
@@ -2185,6 +2182,7 @@
             policy = oldPolicy & ~policy;
             if (oldPolicy != policy) {
                 setUidPolicyUncheckedUL(uid, oldPolicy, policy, true);
+                mLogger.uidPolicyChanged(uid, oldPolicy, policy);
             }
         }
     }
@@ -2264,7 +2262,7 @@
      */
     boolean removeUserStateUL(int userId, boolean writePolicy) {
 
-        if (LOGV) Slog.v(TAG, "removeUserStateUL()");
+        mLogger.removingUserState(userId);
         boolean changed = false;
 
         // Remove entries from revoked default restricted background UID whitelist
@@ -2429,7 +2427,6 @@
     @Override
     public void onTetheringChanged(String iface, boolean tethering) {
         // No need to enforce permission because setRestrictBackground() will do it.
-        if (LOGD) Log.d(TAG, "onTetherStateChanged(" + iface + ", " + tethering + ")");
         synchronized (mUidRulesFirstLock) {
             if (mRestrictBackground && tethering) {
                 Log.d(TAG, "Tethering on (" + iface +"); disable Data Saver");
@@ -2486,6 +2483,7 @@
             }
 
             sendRestrictBackgroundChangedMsg();
+            mLogger.restrictBackgroundChanged(oldRestrictBackground, mRestrictBackground);
 
             if (mRestrictBackgroundPowerState.globalBatterySaverEnabled) {
                 mRestrictBackgroundChangedInBsm = true;
@@ -2551,6 +2549,7 @@
                     return;
                 }
                 mDeviceIdleMode = enabled;
+                mLogger.deviceIdleModeEnabled(enabled);
                 if (mSystemReady) {
                     // Device idle change means we need to rebuild rules for all
                     // known apps, so do a global refresh.
@@ -2964,10 +2963,7 @@
                 }
                 fout.decreaseIndent();
 
-                fout.println("Observed uid state changes:");
-                fout.increaseIndent();
-                mObservedHistory.dumpUL(fout);
-                fout.decreaseIndent();
+                mLogger.dumpLogs(fout);
             }
         }
     }
@@ -3750,8 +3746,8 @@
             try {
                 final int uid = mContext.getPackageManager().getPackageUidAsUser(packageName,
                         PackageManager.MATCH_UNINSTALLED_PACKAGES, userId);
-                if (LOGV) Log.v(TAG, "onAppIdleStateChanged(): uid=" + uid + ", idle=" + idle);
                 synchronized (mUidRulesFirstLock) {
+                    mLogger.appIdleStateChanged(uid, idle);
                     updateRuleForAppIdleUL(uid);
                     updateRulesForPowerRestrictionsUL(uid);
                 }
@@ -3762,6 +3758,7 @@
         @Override
         public void onParoleStateChanged(boolean isParoleOn) {
             synchronized (mUidRulesFirstLock) {
+                mLogger.paroleStateChanged(isParoleOn);
                 updateRulesForAppIdleParoleUL();
             }
         }
@@ -3947,7 +3944,7 @@
             synchronized (mUidRulesFirstLock) {
                 // We received a uid state change callback, add it to the history so that it
                 // will be useful for debugging.
-                mObservedHistory.addProcStateSeqUL(uid, procStateSeq);
+                mLogger.uidStateChanged(uid, procState, procStateSeq);
                 // Now update the network policy rules as per the updated uid state.
                 updateUidStateUL(uid, procState);
                 // Updating the network rules is done, so notify AMS about this.
@@ -4081,6 +4078,7 @@
                 rules[index] = uidRules.valueAt(index);
             }
             mNetworkManager.setFirewallUidRules(chain, uids, rules);
+            mLogger.firewallRulesChanged(chain, uids, rules);
         } catch (IllegalStateException e) {
             Log.wtf(TAG, "problem setting firewall uid rules", e);
         } catch (RemoteException e) {
@@ -4107,6 +4105,7 @@
 
             try {
                 mNetworkManager.setFirewallUidRule(chain, uid, rule);
+                mLogger.uidFirewallRuleChanged(chain, uid, rule);
             } catch (IllegalStateException e) {
                 Log.wtf(TAG, "problem setting firewall uid rules", e);
             } catch (RemoteException e) {
@@ -4129,6 +4128,7 @@
         mFirewallChainStates.put(chain, enable);
         try {
             mNetworkManager.setFirewallChainEnabled(chain, enable);
+            mLogger.firewallChainEnabled(chain, enable);
         } catch (IllegalStateException e) {
             Log.wtf(TAG, "problem enable firewall chain", e);
         } catch (RemoteException e) {
@@ -4305,30 +4305,30 @@
             isBackgroundRestricted = mRestrictBackground;
         }
         if (hasRule(uidRules, RULE_REJECT_ALL)) {
-            if (LOGV) logUidStatus(uid, "blocked by power restrictions");
+            mLogger.networkBlocked(uid, NTWK_BLOCKED_POWER);
             return true;
         }
         if (!isNetworkMetered) {
-            if (LOGV) logUidStatus(uid, "allowed on unmetered network");
+            mLogger.networkBlocked(uid, NTWK_ALLOWED_NON_METERED);
             return false;
         }
         if (hasRule(uidRules, RULE_REJECT_METERED)) {
-            if (LOGV) logUidStatus(uid, "blacklisted on metered network");
+            mLogger.networkBlocked(uid, NTWK_BLOCKED_BLACKLIST);
             return true;
         }
         if (hasRule(uidRules, RULE_ALLOW_METERED)) {
-            if (LOGV) logUidStatus(uid, "whitelisted on metered network");
+            mLogger.networkBlocked(uid, NTWK_ALLOWED_WHITELIST);
             return false;
         }
         if (hasRule(uidRules, RULE_TEMPORARY_ALLOW_METERED)) {
-            if (LOGV) logUidStatus(uid, "temporary whitelisted on metered network");
+            mLogger.networkBlocked(uid, NTWK_ALLOWED_TMP_WHITELIST);
             return false;
         }
         if (isBackgroundRestricted) {
-            if (LOGV) logUidStatus(uid, "blocked when background is restricted");
+            mLogger.networkBlocked(uid, NTWK_BLOCKED_BG_RESTRICT);
             return true;
         }
-        if (LOGV) logUidStatus(uid, "allowed by default");
+        mLogger.networkBlocked(uid, NTWK_ALLOWED_DEFAULT);
         return false;
     }
 
@@ -4379,6 +4379,7 @@
         @Override
         public void onTempPowerSaveWhitelistChange(int appId, boolean added) {
             synchronized (mUidRulesFirstLock) {
+                mLogger.tempPowerSaveWlChanged(appId, added);
                 if (added) {
                     mPowerSaveTempWhitelistAppIds.put(appId, true);
                 } else {
@@ -4393,80 +4394,6 @@
         return (uidRules & rule) != 0;
     }
 
-    private static void logUidStatus(int uid, String descr) {
-        Slog.d(TAG, String.format("uid %d is %s", uid, descr));
-    }
-
-    /**
-     * This class is used for storing and dumping the last {@link #MAX_PROC_STATE_SEQ_HISTORY}
-     * (uid, procStateSeq) pairs.
-     */
-    @VisibleForTesting
-    public static final class ProcStateSeqHistory {
-        private static final int INVALID_UID = -1;
-
-        /**
-         * Denotes maximum number of items this history can hold.
-         */
-        private final int mMaxCapacity;
-        /**
-         * Used for storing the uid information.
-         */
-        private final int[] mUids;
-        /**
-         * Used for storing the sequence numbers associated with {@link #mUids}.
-         */
-        private final long[] mProcStateSeqs;
-        /**
-         * Points to the next available slot for writing (uid, procStateSeq) pair.
-         */
-        private int mHistoryNext;
-
-        public ProcStateSeqHistory(int maxCapacity) {
-            mMaxCapacity = maxCapacity;
-            mUids = new int[mMaxCapacity];
-            Arrays.fill(mUids, INVALID_UID);
-            mProcStateSeqs = new long[mMaxCapacity];
-        }
-
-        @GuardedBy("mUidRulesFirstLock")
-        public void addProcStateSeqUL(int uid, long procStateSeq) {
-            mUids[mHistoryNext] = uid;
-            mProcStateSeqs[mHistoryNext] = procStateSeq;
-            mHistoryNext = increaseNext(mHistoryNext, 1);
-        }
-
-        @GuardedBy("mUidRulesFirstLock")
-        public void dumpUL(IndentingPrintWriter fout) {
-            if (mUids[0] == INVALID_UID) {
-                fout.println("NONE");
-                return;
-            }
-            int index = mHistoryNext;
-            do {
-                index = increaseNext(index, -1);
-                if (mUids[index] == INVALID_UID) {
-                    break;
-                }
-                fout.println(getString(mUids[index], mProcStateSeqs[index]));
-            } while (index != mHistoryNext);
-        }
-
-        public static String getString(int uid, long procStateSeq) {
-            return "UID=" + uid + " Seq=" + procStateSeq;
-        }
-
-        private int increaseNext(int next, int increment) {
-            next += increment;
-            if (next >= mMaxCapacity) {
-                next = 0;
-            } else if (next < 0) {
-                next = mMaxCapacity - 1;
-            }
-            return next;
-        }
-    }
-
     private class NotificationId {
         private final String mTag;
         private final int mId;
diff --git a/com/android/server/notification/NotificationManagerService.java b/com/android/server/notification/NotificationManagerService.java
index 557ba42..bec6fc2 100644
--- a/com/android/server/notification/NotificationManagerService.java
+++ b/com/android/server/notification/NotificationManagerService.java
@@ -16,15 +16,21 @@
 
 package com.android.server.notification;
 
+import static android.app.NotificationManager.ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED;
+import static android.app.NotificationManager.ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED;
 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;
 import static android.os.UserHandle.USER_NULL;
 import static android.service.notification.NotificationListenerService
+        .HINT_HOST_DISABLE_CALL_EFFECTS;
+import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_EFFECTS;
+import static android.service.notification.NotificationListenerService
+        .HINT_HOST_DISABLE_NOTIFICATION_EFFECTS;
+import static android.service.notification.NotificationListenerService
         .NOTIFICATION_CHANNEL_OR_GROUP_ADDED;
 import static android.service.notification.NotificationListenerService
         .NOTIFICATION_CHANNEL_OR_GROUP_DELETED;
@@ -32,12 +38,13 @@
         .NOTIFICATION_CHANNEL_OR_GROUP_UPDATED;
 import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
 import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL_ALL;
-import static android.service.notification.NotificationListenerService.REASON_CHANNEL_BANNED;
 import static android.service.notification.NotificationListenerService.REASON_CANCEL;
 import static android.service.notification.NotificationListenerService.REASON_CANCEL_ALL;
+import static android.service.notification.NotificationListenerService.REASON_CHANNEL_BANNED;
 import static android.service.notification.NotificationListenerService.REASON_CLICK;
 import static android.service.notification.NotificationListenerService.REASON_ERROR;
-import static android.service.notification.NotificationListenerService.REASON_GROUP_SUMMARY_CANCELED;
+import static android.service.notification.NotificationListenerService
+        .REASON_GROUP_SUMMARY_CANCELED;
 import static android.service.notification.NotificationListenerService.REASON_LISTENER_CANCEL;
 import static android.service.notification.NotificationListenerService.REASON_LISTENER_CANCEL_ALL;
 import static android.service.notification.NotificationListenerService.REASON_PACKAGE_BANNED;
@@ -48,14 +55,10 @@
 import static android.service.notification.NotificationListenerService.REASON_TIMEOUT;
 import static android.service.notification.NotificationListenerService.REASON_UNAUTOBUNDLED;
 import static android.service.notification.NotificationListenerService.REASON_USER_STOPPED;
-import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_EFFECTS;
-import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_NOTIFICATION_EFFECTS;
-import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS;
 import static android.service.notification.NotificationListenerService.SUPPRESSED_EFFECT_SCREEN_OFF;
 import static android.service.notification.NotificationListenerService.SUPPRESSED_EFFECT_SCREEN_ON;
 import static android.service.notification.NotificationListenerService.TRIM_FULL;
 import static android.service.notification.NotificationListenerService.TRIM_LIGHT;
-
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
 
@@ -68,17 +71,17 @@
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
 import android.app.AutomaticZenRule;
-import android.app.NotificationChannelGroup;
-import android.app.backup.BackupManager;
 import android.app.IActivityManager;
 import android.app.INotificationManager;
 import android.app.ITransientNotification;
 import android.app.Notification;
 import android.app.NotificationChannel;
-import android.app.NotificationManager.Policy;
+import android.app.NotificationChannelGroup;
 import android.app.NotificationManager;
+import android.app.NotificationManager.Policy;
 import android.app.PendingIntent;
 import android.app.StatusBarManager;
+import android.app.backup.BackupManager;
 import android.app.usage.UsageEvents;
 import android.app.usage.UsageStatsManagerInternal;
 import android.companion.ICompanionDeviceManager;
@@ -119,8 +122,8 @@
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
-import android.os.Vibrator;
 import android.os.VibrationEffect;
+import android.os.Vibrator;
 import android.provider.Settings;
 import android.service.notification.Adjustment;
 import android.service.notification.Condition;
@@ -149,7 +152,6 @@
 import android.util.SparseArray;
 import android.util.Xml;
 import android.util.proto.ProtoOutputStream;
-import android.view.WindowManagerInternal;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
 import android.widget.Toast;
@@ -174,9 +176,10 @@
 import com.android.server.lights.Light;
 import com.android.server.lights.LightsManager;
 import com.android.server.notification.ManagedServices.ManagedServiceInfo;
+import com.android.server.notification.ManagedServices.UserProfiles;
 import com.android.server.policy.PhoneWindowManager;
 import com.android.server.statusbar.StatusBarManagerInternal;
-import com.android.server.notification.ManagedServices.UserProfiles;
+import com.android.server.wm.WindowManagerInternal;
 
 import libcore.io.IoUtils;
 
@@ -196,7 +199,6 @@
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.io.PrintWriter;
-import java.net.URI;
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
@@ -736,6 +738,11 @@
                 for (NotificationVisibility nv : newlyVisibleKeys) {
                     NotificationRecord r = mNotificationsByKey.get(nv.key);
                     if (r == null) continue;
+                    if (!r.isSeen()) {
+                        // Report to usage stats that notification was made visible
+                        if (DBG) Slog.d(TAG, "Marking notification as visible " + nv.key);
+                        reportSeen(r);
+                    }
                     r.setVisibility(true, nv.rank);
                     nv.recycle();
                 }
@@ -766,7 +773,7 @@
                                 .setType(expanded ? MetricsEvent.TYPE_DETAIL
                                         : MetricsEvent.TYPE_COLLAPSE));
                     }
-                    if (expanded) {
+                    if (expanded && userAction) {
                         r.recordExpanded();
                     }
                     EventLogTags.writeNotificationExpansion(key,
@@ -1515,7 +1522,11 @@
                 }
             }
         }
+        final NotificationChannel preUpdate =
+                mRankingHelper.getNotificationChannel(pkg, uid, channel.getId(), true);
+
         mRankingHelper.updateNotificationChannel(pkg, uid, channel, true);
+        maybeNotifyChannelOwner(pkg, uid, preUpdate, channel);
 
         if (!fromListener) {
             final NotificationChannel modifiedChannel =
@@ -1528,12 +1539,40 @@
         savePolicyFile();
     }
 
+    private void maybeNotifyChannelOwner(String pkg, int uid, NotificationChannel preUpdate,
+            NotificationChannel update) {
+        try {
+            if ((preUpdate.getImportance() == IMPORTANCE_NONE
+                    && update.getImportance() != IMPORTANCE_NONE)
+                    || (preUpdate.getImportance() != IMPORTANCE_NONE
+                    && update.getImportance() == IMPORTANCE_NONE)) {
+                getContext().sendBroadcastAsUser(
+                        new Intent(ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED)
+                                .putExtra(NotificationManager.EXTRA_BLOCK_STATE_CHANGED_ID,
+                                        update.getId())
+                                .putExtra(NotificationManager.EXTRA_BLOCKED_STATE,
+                                        update.getImportance() == IMPORTANCE_NONE)
+                                .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+                                .setPackage(pkg),
+                        UserHandle.of(UserHandle.getUserId(uid)), null);
+            }
+        } catch (SecurityException e) {
+            Slog.w(TAG, "Can't notify app about channel change", e);
+        }
+    }
+
     private void createNotificationChannelGroup(String pkg, int uid, NotificationChannelGroup group,
             boolean fromApp, boolean fromListener) {
         Preconditions.checkNotNull(group);
         Preconditions.checkNotNull(pkg);
+
+        final NotificationChannelGroup preUpdate =
+                mRankingHelper.getNotificationChannelGroup(group.getId(), pkg, uid);
         mRankingHelper.createNotificationChannelGroup(pkg, uid, group,
                 fromApp);
+        if (!fromApp) {
+            maybeNotifyChannelGroupOwner(pkg, uid, preUpdate, group);
+        }
         if (!fromListener) {
             mListeners.notifyNotificationChannelGroupChanged(pkg,
                     UserHandle.of(UserHandle.getCallingUserId()), group,
@@ -1541,6 +1580,25 @@
         }
     }
 
+    private void maybeNotifyChannelGroupOwner(String pkg, int uid,
+            NotificationChannelGroup preUpdate, NotificationChannelGroup update) {
+        try {
+            if (preUpdate.isBlocked() != update.isBlocked()) {
+                getContext().sendBroadcastAsUser(
+                        new Intent(ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED)
+                                .putExtra(NotificationManager.EXTRA_BLOCK_STATE_CHANGED_ID,
+                                        update.getId())
+                                .putExtra(NotificationManager.EXTRA_BLOCKED_STATE,
+                                        update.isBlocked())
+                                .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+                                .setPackage(pkg),
+                        UserHandle.of(UserHandle.getUserId(uid)), null);
+            }
+        } catch (SecurityException e) {
+            Slog.w(TAG, "Can't notify app about group change", e);
+        }
+    }
+
     private ArrayList<ComponentName> getSuppressors() {
         ArrayList<ComponentName> names = new ArrayList<ComponentName>();
         for (int i = mListenersDisablingEffects.size() - 1; i >= 0; --i) {
@@ -1643,6 +1701,14 @@
         return INotificationManager.Stub.asInterface(mService);
     }
 
+    protected void reportSeen(NotificationRecord r) {
+        final int userId = r.sbn.getUserId();
+        mAppUsageStats.reportEvent(r.sbn.getPackageName(),
+                userId == UserHandle.USER_ALL ? UserHandle.USER_SYSTEM
+                        : userId,
+                UsageEvents.Event.NOTIFICATION_SEEN);
+    }
+
     @VisibleForTesting
     NotificationManagerInternal getInternalService() {
         return mInternalService;
@@ -1911,11 +1977,18 @@
         }
 
         @Override
+        public NotificationChannelGroup getNotificationChannelGroup(String pkg, String groupId) {
+            checkCallerIsSystemOrSameApp(pkg);
+            return mRankingHelper.getNotificationChannelGroupWithChannels(
+                    pkg, Binder.getCallingUid(), groupId, false);
+        }
+
+        @Override
         public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(
                 String pkg) {
             checkCallerIsSystemOrSameApp(pkg);
-            return new ParceledListSlice<>(new ArrayList(
-                    mRankingHelper.getNotificationChannelGroups(pkg, Binder.getCallingUid())));
+            return mRankingHelper.getNotificationChannelGroups(
+                    pkg, Binder.getCallingUid(), false, false);
         }
 
         @Override
@@ -1985,7 +2058,7 @@
         public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroupsForPackage(
                 String pkg, int uid, boolean includeDeleted) {
             checkCallerIsSystem();
-            return mRankingHelper.getNotificationChannelGroups(pkg, uid, includeDeleted);
+            return mRankingHelper.getNotificationChannelGroups(pkg, uid, includeDeleted, true);
         }
 
         @Override
@@ -2269,10 +2342,7 @@
                             }
                             if (!r.isSeen()) {
                                 if (DBG) Slog.d(TAG, "Marking notification as seen " + keys[i]);
-                                mAppUsageStats.reportEvent(r.sbn.getPackageName(),
-                                        userId == UserHandle.USER_ALL ? UserHandle.USER_SYSTEM
-                                                : userId,
-                                        UsageEvents.Event.USER_INTERACTION);
+                                reportSeen(r);
                                 r.setSeen();
                             }
                         }
@@ -4673,7 +4743,8 @@
     }
 
     void sendAccessibilityEvent(Notification notification, CharSequence packageName) {
-        if (!mAccessibilityManager.isEnabled()) {
+        if (!mAccessibilityManager.isObservedEventType(
+                AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED)) {
             return;
         }
 
diff --git a/com/android/server/notification/RankingConfig.java b/com/android/server/notification/RankingConfig.java
index b9c0d90..b1b0bf2 100644
--- a/com/android/server/notification/RankingConfig.java
+++ b/com/android/server/notification/RankingConfig.java
@@ -36,7 +36,7 @@
     void createNotificationChannelGroup(String pkg, int uid, NotificationChannelGroup group,
             boolean fromTargetApp);
     ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
-            int uid, boolean includeDeleted);
+            int uid, boolean includeDeleted, boolean includeNonGrouped);
     void createNotificationChannel(String pkg, int uid, NotificationChannel channel,
             boolean fromTargetApp);
     void updateNotificationChannel(String pkg, int uid, NotificationChannel channel, boolean fromUser);
diff --git a/com/android/server/notification/RankingHelper.java b/com/android/server/notification/RankingHelper.java
index d566a45..c0dccb5 100644
--- a/com/android/server/notification/RankingHelper.java
+++ b/com/android/server/notification/RankingHelper.java
@@ -750,12 +750,15 @@
             int uid) {
         Preconditions.checkNotNull(pkg);
         Record r = getRecord(pkg, uid);
+        if (r == null) {
+            return null;
+        }
         return r.groups.get(groupId);
     }
 
     @Override
     public ParceledListSlice<NotificationChannelGroup> getNotificationChannelGroups(String pkg,
-            int uid, boolean includeDeleted) {
+            int uid, boolean includeDeleted, boolean includeNonGrouped) {
         Preconditions.checkNotNull(pkg);
         Map<String, NotificationChannelGroup> groups = new ArrayMap<>();
         Record r = getRecord(pkg, uid);
@@ -783,7 +786,7 @@
                 }
             }
         }
-        if (nonGrouped.getChannels().size() > 0) {
+        if (includeNonGrouped && nonGrouped.getChannels().size() > 0) {
             groups.put(null, nonGrouped);
         }
         return new ParceledListSlice<>(new ArrayList<>(groups.values()));
diff --git a/com/android/server/pm/BackgroundDexOptService.java b/com/android/server/pm/BackgroundDexOptService.java
index 679250c..8591304 100644
--- a/com/android/server/pm/BackgroundDexOptService.java
+++ b/com/android/server/pm/BackgroundDexOptService.java
@@ -18,6 +18,7 @@
 
 import static com.android.server.pm.PackageManagerService.DEBUG_DEXOPT;
 
+import android.annotation.Nullable;
 import android.app.job.JobInfo;
 import android.app.job.JobParameters;
 import android.app.job.JobScheduler;
@@ -40,6 +41,7 @@
 import com.android.server.pm.dex.DexoptOptions;
 
 import java.io.File;
+import java.util.List;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.TimeUnit;
@@ -402,14 +404,22 @@
     }
 
     /**
-     * Execute the idle optimizations immediately.
+     * Execute idle optimizations immediately on packages in packageNames. If packageNames is null,
+     * then execute on all packages.
      */
-    public static boolean runIdleOptimizationsNow(PackageManagerService pm, Context context) {
+    public static boolean runIdleOptimizationsNow(PackageManagerService pm, Context context,
+            @Nullable List<String> packageNames) {
         // Create a new object to make sure we don't interfere with the scheduled jobs.
         // Note that this may still run at the same time with the job scheduled by the
         // JobScheduler but the scheduler will not be able to cancel it.
         BackgroundDexOptService bdos = new BackgroundDexOptService();
-        int result = bdos.idleOptimization(pm, pm.getOptimizablePackages(), context);
+        ArraySet<String> packagesToOptimize;
+        if (packageNames == null) {
+            packagesToOptimize = pm.getOptimizablePackages();
+        } else {
+            packagesToOptimize = new ArraySet<>(packageNames);
+        }
+        int result = bdos.idleOptimization(pm, packagesToOptimize, context);
         return result == OPTIMIZE_PROCESSED;
     }
 
diff --git a/com/android/server/pm/Installer.java b/com/android/server/pm/Installer.java
index 210eb13..6a06be2 100644
--- a/com/android/server/pm/Installer.java
+++ b/com/android/server/pm/Installer.java
@@ -486,6 +486,16 @@
         }
     }
 
+    public byte[] hashSecondaryDexFile(String dexPath, String packageName, int uid,
+            @Nullable String volumeUuid, int flags) throws InstallerException {
+        if (!checkBeforeRemote()) return new byte[0];
+        try {
+            return mInstalld.hashSecondaryDexFile(dexPath, packageName, uid, volumeUuid, flags);
+        } catch (Exception e) {
+            throw InstallerException.from(e);
+        }
+    }
+
     public void invalidateMounts() throws InstallerException {
         if (!checkBeforeRemote()) return;
         try {
diff --git a/com/android/server/pm/PackageDexOptimizer.java b/com/android/server/pm/PackageDexOptimizer.java
index 29f48ee..00cfa31 100644
--- a/com/android/server/pm/PackageDexOptimizer.java
+++ b/com/android/server/pm/PackageDexOptimizer.java
@@ -154,7 +154,13 @@
                 targetInstructionSets : getAppDexInstructionSets(pkg.applicationInfo);
         final String[] dexCodeInstructionSets = getDexCodeInstructionSets(instructionSets);
         final List<String> paths = pkg.getAllCodePaths();
-        final int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
+
+        int sharedGid = UserHandle.getSharedAppGid(pkg.applicationInfo.uid);
+        if (sharedGid == -1) {
+            Slog.wtf(TAG, "Well this is awkward; package " + pkg.applicationInfo.name + " had UID "
+                    + pkg.applicationInfo.uid, new Throwable());
+            sharedGid = android.os.Process.NOBODY_UID;
+        }
 
         // Get the class loader context dependencies.
         // For each code path in the package, this array contains the class loader context that
diff --git a/com/android/server/pm/PackageManagerService.java b/com/android/server/pm/PackageManagerService.java
index 83cffe5..2d5f7c7 100644
--- a/com/android/server/pm/PackageManagerService.java
+++ b/com/android/server/pm/PackageManagerService.java
@@ -81,15 +81,12 @@
 import static android.content.pm.PackageManager.MOVE_FAILED_SYSTEM_PACKAGE;
 import static android.content.pm.PackageManager.PERMISSION_DENIED;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-import static android.content.pm.PackageParser.PARSE_IS_OEM;
-import static android.content.pm.PackageParser.PARSE_IS_PRIVILEGED;
 import static android.content.pm.PackageParser.isApkFile;
 import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
 import static android.os.storage.StorageManager.FLAG_STORAGE_CE;
 import static android.os.storage.StorageManager.FLAG_STORAGE_DE;
 import static android.system.OsConstants.O_CREAT;
 import static android.system.OsConstants.O_RDWR;
-
 import static com.android.internal.app.IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE;
 import static com.android.internal.app.IntentForwarderActivity.FORWARD_INTENT_TO_PARENT;
 import static com.android.internal.content.NativeLibraryHelper.LIB64_DIR_NAME;
@@ -169,11 +166,13 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.PackageManager.LegacyPackageDeleteObserver;
+import android.content.pm.PackageManager.PackageInfoFlags;
 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.PackageParser.ParseFlags;
 import android.content.pm.PackageStats;
 import android.content.pm.PackageUserState;
 import android.content.pm.ParceledListSlice;
@@ -293,6 +292,7 @@
 import com.android.server.pm.Installer.InstallerException;
 import com.android.server.pm.Settings.DatabaseVersion;
 import com.android.server.pm.Settings.VersionInfo;
+import com.android.server.pm.dex.DexLogger;
 import com.android.server.pm.dex.DexManager;
 import com.android.server.pm.dex.DexoptOptions;
 import com.android.server.pm.dex.PackageDexUsage;
@@ -447,27 +447,48 @@
     // package apks to install directory.
     private static final String INSTALL_PACKAGE_SUFFIX = "-";
 
-    static final int SCAN_NO_DEX = 1<<1;
-    static final int SCAN_FORCE_DEX = 1<<2;
-    static final int SCAN_UPDATE_SIGNATURE = 1<<3;
-    static final int SCAN_NEW_INSTALL = 1<<4;
-    static final int SCAN_UPDATE_TIME = 1<<5;
-    static final int SCAN_BOOTING = 1<<6;
-    static final int SCAN_TRUSTED_OVERLAY = 1<<7;
-    static final int SCAN_DELETE_DATA_ON_FAILURES = 1<<8;
-    static final int SCAN_REPLACING = 1<<9;
-    static final int SCAN_REQUIRE_KNOWN = 1<<10;
-    static final int SCAN_MOVE = 1<<11;
-    static final int SCAN_INITIAL = 1<<12;
-    static final int SCAN_CHECK_ONLY = 1<<13;
-    static final int SCAN_DONT_KILL_APP = 1<<14;
-    static final int SCAN_IGNORE_FROZEN = 1<<15;
-    static final int SCAN_FIRST_BOOT_OR_UPGRADE = 1<<16;
-    static final int SCAN_AS_INSTANT_APP = 1<<17;
-    static final int SCAN_AS_FULL_APP = 1<<18;
-    static final int SCAN_AS_VIRTUAL_PRELOAD = 1<<19;
-    /** Should not be with the scan flags */
-    static final int FLAGS_REMOVE_CHATTY = 1<<31;
+    static final int SCAN_NO_DEX = 1<<0;
+    static final int SCAN_UPDATE_SIGNATURE = 1<<1;
+    static final int SCAN_NEW_INSTALL = 1<<2;
+    static final int SCAN_UPDATE_TIME = 1<<3;
+    static final int SCAN_BOOTING = 1<<4;
+    static final int SCAN_TRUSTED_OVERLAY = 1<<5;
+    static final int SCAN_DELETE_DATA_ON_FAILURES = 1<<6;
+    static final int SCAN_REQUIRE_KNOWN = 1<<7;
+    static final int SCAN_MOVE = 1<<8;
+    static final int SCAN_INITIAL = 1<<9;
+    static final int SCAN_CHECK_ONLY = 1<<10;
+    static final int SCAN_DONT_KILL_APP = 1<<11;
+    static final int SCAN_IGNORE_FROZEN = 1<<12;
+    static final int SCAN_FIRST_BOOT_OR_UPGRADE = 1<<13;
+    static final int SCAN_AS_INSTANT_APP = 1<<14;
+    static final int SCAN_AS_FULL_APP = 1<<15;
+    static final int SCAN_AS_VIRTUAL_PRELOAD = 1<<16;
+    static final int SCAN_AS_SYSTEM = 1<<17;
+    static final int SCAN_AS_PRIVILEGED = 1<<18;
+    static final int SCAN_AS_OEM = 1<<19;
+
+    @IntDef(flag = true, prefix = { "SCAN_" }, value = {
+            SCAN_NO_DEX,
+            SCAN_UPDATE_SIGNATURE,
+            SCAN_NEW_INSTALL,
+            SCAN_UPDATE_TIME,
+            SCAN_BOOTING,
+            SCAN_TRUSTED_OVERLAY,
+            SCAN_DELETE_DATA_ON_FAILURES,
+            SCAN_REQUIRE_KNOWN,
+            SCAN_MOVE,
+            SCAN_INITIAL,
+            SCAN_CHECK_ONLY,
+            SCAN_DONT_KILL_APP,
+            SCAN_IGNORE_FROZEN,
+            SCAN_FIRST_BOOT_OR_UPGRADE,
+            SCAN_AS_INSTANT_APP,
+            SCAN_AS_FULL_APP,
+            SCAN_AS_VIRTUAL_PRELOAD,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ScanFlags {}
 
     private static final String STATIC_SHARED_LIB_DELIMITER = "_";
     /** Extension of the compressed packages */
@@ -2380,7 +2401,10 @@
 
         mPackageDexOptimizer = new PackageDexOptimizer(installer, mInstallLock, context,
                 "*dexopt*");
-        mDexManager = new DexManager(this, mPackageDexOptimizer, installer, mInstallLock);
+        DexManager.Listener dexManagerListener = DexLogger.getListener(this,
+                installer, mInstallLock);
+        mDexManager = new DexManager(this, mPackageDexOptimizer, installer, mInstallLock,
+                dexManagerListener);
         mMoveCallbacks = new MoveCallbacks(FgThread.get().getLooper());
 
         mOnPermissionChangeListeners = new OnPermissionChangeListeners(
@@ -2511,32 +2535,44 @@
             // Collect vendor overlay packages. (Do this before scanning any apps.)
             // For security and version matching reason, only consider
             // overlay packages if they reside in the right directory.
-            scanDirTracedLI(new File(VENDOR_OVERLAY_DIR), mDefParseFlags
-                    | PackageParser.PARSE_IS_SYSTEM
-                    | PackageParser.PARSE_IS_SYSTEM_DIR
-                    | PackageParser.PARSE_TRUSTED_OVERLAY, scanFlags | SCAN_TRUSTED_OVERLAY, 0);
+            scanDirTracedLI(new File(VENDOR_OVERLAY_DIR),
+                    mDefParseFlags
+                    | PackageParser.PARSE_IS_SYSTEM_DIR,
+                    scanFlags
+                    | SCAN_AS_SYSTEM
+                    | SCAN_TRUSTED_OVERLAY,
+                    0);
 
             mParallelPackageParserCallback.findStaticOverlayPackages();
 
             // Find base frameworks (resource packages without code).
-            scanDirTracedLI(frameworkDir, mDefParseFlags
-                    | PackageParser.PARSE_IS_SYSTEM
-                    | PackageParser.PARSE_IS_SYSTEM_DIR
-                    | PackageParser.PARSE_IS_PRIVILEGED,
-                    scanFlags | SCAN_NO_DEX, 0);
+            scanDirTracedLI(frameworkDir,
+                    mDefParseFlags
+                    | PackageParser.PARSE_IS_SYSTEM_DIR,
+                    scanFlags
+                    | SCAN_NO_DEX
+                    | SCAN_AS_SYSTEM
+                    | SCAN_AS_PRIVILEGED,
+                    0);
 
             // Collected privileged system packages.
             final File privilegedAppDir = new File(Environment.getRootDirectory(), "priv-app");
-            scanDirTracedLI(privilegedAppDir, mDefParseFlags
-                    | PackageParser.PARSE_IS_SYSTEM
-                    | PackageParser.PARSE_IS_SYSTEM_DIR
-                    | PackageParser.PARSE_IS_PRIVILEGED, scanFlags, 0);
+            scanDirTracedLI(privilegedAppDir,
+                    mDefParseFlags
+                    | PackageParser.PARSE_IS_SYSTEM_DIR,
+                    scanFlags
+                    | SCAN_AS_SYSTEM
+                    | SCAN_AS_PRIVILEGED,
+                    0);
 
             // Collect ordinary system packages.
             final File systemAppDir = new File(Environment.getRootDirectory(), "app");
-            scanDirTracedLI(systemAppDir, mDefParseFlags
-                    | PackageParser.PARSE_IS_SYSTEM
-                    | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);
+            scanDirTracedLI(systemAppDir,
+                    mDefParseFlags
+                    | PackageParser.PARSE_IS_SYSTEM_DIR,
+                    scanFlags
+                    | SCAN_AS_SYSTEM,
+                    0);
 
             // Collect all vendor packages.
             File vendorAppDir = new File("/vendor/app");
@@ -2545,16 +2581,22 @@
             } catch (IOException e) {
                 // failed to look up canonical path, continue with original one
             }
-            scanDirTracedLI(vendorAppDir, mDefParseFlags
-                    | PackageParser.PARSE_IS_SYSTEM
-                    | PackageParser.PARSE_IS_SYSTEM_DIR, scanFlags, 0);
+            scanDirTracedLI(vendorAppDir,
+                    mDefParseFlags
+                    | PackageParser.PARSE_IS_SYSTEM_DIR,
+                    scanFlags
+                    | SCAN_AS_SYSTEM,
+                    0);
 
             // Collect all OEM packages.
             final File oemAppDir = new File(Environment.getOemDirectory(), "app");
-            scanDirTracedLI(oemAppDir, mDefParseFlags
-                    | PackageParser.PARSE_IS_SYSTEM
-                    | PackageParser.PARSE_IS_SYSTEM_DIR
-                    | PackageParser.PARSE_IS_OEM, scanFlags, 0);
+            scanDirTracedLI(oemAppDir,
+                    mDefParseFlags
+                    | PackageParser.PARSE_IS_SYSTEM_DIR,
+                    scanFlags
+                    | SCAN_AS_SYSTEM
+                    | SCAN_AS_OEM,
+                    0);
 
             // Prune any system packages that no longer exist.
             final List<String> possiblyDeletedUpdatedSystemApps = new ArrayList<>();
@@ -2711,21 +2753,38 @@
                         logCriticalInfo(Log.WARN, "Expected better " + packageName
                                 + " but never showed up; reverting to system");
 
-                        int reparseFlags = mDefParseFlags;
+                        final @ParseFlags int reparseFlags;
+                        final @ScanFlags int rescanFlags;
                         if (FileUtils.contains(privilegedAppDir, scanFile)) {
-                            reparseFlags = PackageParser.PARSE_IS_SYSTEM
-                                    | PackageParser.PARSE_IS_SYSTEM_DIR
-                                    | PackageParser.PARSE_IS_PRIVILEGED;
+                            reparseFlags =
+                                    mDefParseFlags |
+                                    PackageParser.PARSE_IS_SYSTEM_DIR;
+                            rescanFlags =
+                                    scanFlags
+                                    | SCAN_AS_SYSTEM
+                                    | SCAN_AS_PRIVILEGED;
                         } else if (FileUtils.contains(systemAppDir, scanFile)) {
-                            reparseFlags = PackageParser.PARSE_IS_SYSTEM
-                                    | PackageParser.PARSE_IS_SYSTEM_DIR;
+                            reparseFlags =
+                                    mDefParseFlags |
+                                    PackageParser.PARSE_IS_SYSTEM_DIR;
+                            rescanFlags =
+                                    scanFlags
+                                    | SCAN_AS_SYSTEM;
                         } else if (FileUtils.contains(vendorAppDir, scanFile)) {
-                            reparseFlags = PackageParser.PARSE_IS_SYSTEM
-                                    | PackageParser.PARSE_IS_SYSTEM_DIR;
+                            reparseFlags =
+                                    mDefParseFlags |
+                                    PackageParser.PARSE_IS_SYSTEM_DIR;
+                            rescanFlags =
+                                    scanFlags
+                                    | SCAN_AS_SYSTEM;
                         } else if (FileUtils.contains(oemAppDir, scanFile)) {
-                            reparseFlags = PackageParser.PARSE_IS_SYSTEM
-                                    | PackageParser.PARSE_IS_SYSTEM_DIR
-                                    | PackageParser.PARSE_IS_OEM;
+                            reparseFlags =
+                                    mDefParseFlags |
+                                    PackageParser.PARSE_IS_SYSTEM_DIR;
+                            rescanFlags =
+                                    scanFlags
+                                    | SCAN_AS_SYSTEM
+                                    | SCAN_AS_OEM;
                         } else {
                             Slog.e(TAG, "Ignoring unexpected fallback path " + scanFile);
                             continue;
@@ -2734,7 +2793,7 @@
                         mSettings.enableSystemPackageLPw(packageName);
 
                         try {
-                            scanPackageTracedLI(scanFile, reparseFlags, scanFlags, 0, null);
+                            scanPackageTracedLI(scanFile, reparseFlags, rescanFlags, 0, null);
                         } catch (PackageManagerException e) {
                             Slog.e(TAG, "Failed to parse original system package: "
                                     + e.getMessage());
@@ -3581,7 +3640,7 @@
         final int N = list.size();
         for (int i = 0; i < N; i++) {
             ResolveInfo info = list.get(i);
-            if (packageName.equals(info.activityInfo.packageName)) {
+            if (info.priority >= 0 && packageName.equals(info.activityInfo.packageName)) {
                 return true;
             }
         }
@@ -7986,96 +8045,95 @@
         return finalList;
     }
 
-    private void scanDirTracedLI(File dir, final int parseFlags, int scanFlags, long currentTime) {
-        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanDir [" + dir.getAbsolutePath() + "]");
+    private void scanDirTracedLI(File scanDir, final int parseFlags, int scanFlags, long currentTime) {
+        Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanDir [" + scanDir.getAbsolutePath() + "]");
         try {
-            scanDirLI(dir, parseFlags, scanFlags, currentTime);
+            scanDirLI(scanDir, parseFlags, scanFlags, currentTime);
         } finally {
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
         }
     }
 
-    private void scanDirLI(File dir, int parseFlags, int scanFlags, long currentTime) {
-        final File[] files = dir.listFiles();
+    private void scanDirLI(File scanDir, int parseFlags, int scanFlags, long currentTime) {
+        final File[] files = scanDir.listFiles();
         if (ArrayUtils.isEmpty(files)) {
-            Log.d(TAG, "No files in app dir " + dir);
+            Log.d(TAG, "No files in app dir " + scanDir);
             return;
         }
 
         if (DEBUG_PACKAGE_SCANNING) {
-            Log.d(TAG, "Scanning app dir " + dir + " scanFlags=" + scanFlags
+            Log.d(TAG, "Scanning app dir " + scanDir + " scanFlags=" + scanFlags
                     + " flags=0x" + Integer.toHexString(parseFlags));
         }
-        ParallelPackageParser parallelPackageParser = new ParallelPackageParser(
+        try (ParallelPackageParser parallelPackageParser = new ParallelPackageParser(
                 mSeparateProcesses, mOnlyCore, mMetrics, mCacheDir,
-                mParallelPackageParserCallback);
-
-        // Submit files for parsing in parallel
-        int fileCount = 0;
-        for (File file : files) {
-            final boolean isPackage = (isApkFile(file) || file.isDirectory())
-                    && !PackageInstallerService.isStageName(file.getName());
-            if (!isPackage) {
-                // Ignore entries which are not packages
-                continue;
-            }
-            parallelPackageParser.submit(file, parseFlags);
-            fileCount++;
-        }
-
-        // Process results one by one
-        for (; fileCount > 0; fileCount--) {
-            ParallelPackageParser.ParseResult parseResult = parallelPackageParser.take();
-            Throwable throwable = parseResult.throwable;
-            int errorCode = PackageManager.INSTALL_SUCCEEDED;
-
-            if (throwable == null) {
-                // Static shared libraries have synthetic package names
-                if (parseResult.pkg.applicationInfo.isStaticSharedLibrary()) {
-                    renameStaticSharedLibraryPackage(parseResult.pkg);
+                mParallelPackageParserCallback)) {
+            // Submit files for parsing in parallel
+            int fileCount = 0;
+            for (File file : files) {
+                final boolean isPackage = (isApkFile(file) || file.isDirectory())
+                        && !PackageInstallerService.isStageName(file.getName());
+                if (!isPackage) {
+                    // Ignore entries which are not packages
+                    continue;
                 }
-                try {
-                    if (errorCode == PackageManager.INSTALL_SUCCEEDED) {
-                        scanPackageLI(parseResult.pkg, parseResult.scanFile, parseFlags, scanFlags,
-                                currentTime, null);
+                parallelPackageParser.submit(file, parseFlags);
+                fileCount++;
+            }
+
+            // Process results one by one
+            for (; fileCount > 0; fileCount--) {
+                ParallelPackageParser.ParseResult parseResult = parallelPackageParser.take();
+                Throwable throwable = parseResult.throwable;
+                int errorCode = PackageManager.INSTALL_SUCCEEDED;
+
+                if (throwable == null) {
+                    // Static shared libraries have synthetic package names
+                    if (parseResult.pkg.applicationInfo.isStaticSharedLibrary()) {
+                        renameStaticSharedLibraryPackage(parseResult.pkg);
                     }
-                } catch (PackageManagerException e) {
+                    try {
+                        if (errorCode == PackageManager.INSTALL_SUCCEEDED) {
+                            scanPackageChildLI(parseResult.pkg, parseFlags, scanFlags,
+                                    currentTime, null);
+                        }
+                    } catch (PackageManagerException e) {
+                        errorCode = e.error;
+                        Slog.w(TAG, "Failed to scan " + parseResult.scanFile + ": " + e.getMessage());
+                    }
+                } else if (throwable instanceof PackageParser.PackageParserException) {
+                    PackageParser.PackageParserException e = (PackageParser.PackageParserException)
+                            throwable;
                     errorCode = e.error;
-                    Slog.w(TAG, "Failed to scan " + parseResult.scanFile + ": " + e.getMessage());
+                    Slog.w(TAG, "Failed to parse " + parseResult.scanFile + ": " + e.getMessage());
+                } else {
+                    throw new IllegalStateException("Unexpected exception occurred while parsing "
+                            + parseResult.scanFile, throwable);
                 }
-            } else if (throwable instanceof PackageParser.PackageParserException) {
-                PackageParser.PackageParserException e = (PackageParser.PackageParserException)
-                        throwable;
-                errorCode = e.error;
-                Slog.w(TAG, "Failed to parse " + parseResult.scanFile + ": " + e.getMessage());
-            } else {
-                throw new IllegalStateException("Unexpected exception occurred while parsing "
-                        + parseResult.scanFile, throwable);
-            }
 
-            // Delete invalid userdata apps
-            if ((parseFlags & PackageParser.PARSE_IS_SYSTEM) == 0 &&
-                    errorCode == PackageManager.INSTALL_FAILED_INVALID_APK) {
-                logCriticalInfo(Log.WARN,
-                        "Deleting invalid package at " + parseResult.scanFile);
-                removeCodePathLI(parseResult.scanFile);
+                // Delete invalid userdata apps
+                if ((scanFlags & SCAN_AS_SYSTEM) == 0 &&
+                        errorCode == PackageManager.INSTALL_FAILED_INVALID_APK) {
+                    logCriticalInfo(Log.WARN,
+                            "Deleting invalid package at " + parseResult.scanFile);
+                    removeCodePathLI(parseResult.scanFile);
+                }
             }
         }
-        parallelPackageParser.close();
     }
 
     public static void reportSettingsProblem(int priority, String msg) {
         logCriticalInfo(priority, msg);
     }
 
-    private void collectCertificatesLI(PackageSetting ps, PackageParser.Package pkg, File srcFile,
-            final int policyFlags) throws PackageManagerException {
+    private void collectCertificatesLI(PackageSetting ps, PackageParser.Package pkg,
+            final @ParseFlags int parseFlags) throws PackageManagerException {
         // When upgrading from pre-N MR1, verify the package time stamp using the package
         // directory and not the APK file.
         final long lastModifiedTime = mIsPreNMR1Upgrade
-                ? new File(pkg.codePath).lastModified() : getLastModifiedTime(pkg, srcFile);
+                ? new File(pkg.codePath).lastModified() : getLastModifiedTime(pkg);
         if (ps != null
-                && ps.codePath.equals(srcFile)
+                && ps.codePathString.equals(pkg.codePath)
                 && ps.timeStamp == lastModifiedTime
                 && !isCompatSignatureUpdateNeeded(pkg)
                 && !isRecoverSignatureUpdateNeeded(pkg)) {
@@ -8098,12 +8156,12 @@
             Slog.w(TAG, "PackageSetting for " + ps.name
                     + " is missing signatures.  Collecting certs again to recover them.");
         } else {
-            Slog.i(TAG, srcFile.toString() + " changed; collecting certs");
+            Slog.i(TAG, toString() + " changed; collecting certs");
         }
 
         try {
             Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "collectCertificates");
-            PackageParser.collectCertificates(pkg, policyFlags);
+            PackageParser.collectCertificates(pkg, parseFlags);
         } catch (PackageParserException e) {
             throw PackageManagerException.from(e);
         } finally {
@@ -8138,10 +8196,6 @@
         pp.setDisplayMetrics(mMetrics);
         pp.setCallback(mPackageParserCallback);
 
-        if ((scanFlags & SCAN_TRUSTED_OVERLAY) != 0) {
-            parseFlags |= PackageParser.PARSE_TRUSTED_OVERLAY;
-        }
-
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "parsePackage");
         final PackageParser.Package pkg;
         try {
@@ -8157,16 +8211,17 @@
             renameStaticSharedLibraryPackage(pkg);
         }
 
-        return scanPackageLI(pkg, scanFile, parseFlags, scanFlags, currentTime, user);
+        return scanPackageChildLI(pkg, parseFlags, scanFlags, currentTime, user);
     }
 
     /**
      *  Scans a package and returns the newly parsed package.
      *  @throws PackageManagerException on a parse error.
      */
-    private PackageParser.Package scanPackageLI(PackageParser.Package pkg, File scanFile,
-            final int policyFlags, int scanFlags, long currentTime, @Nullable UserHandle user)
-            throws PackageManagerException {
+    private PackageParser.Package scanPackageChildLI(PackageParser.Package pkg,
+            final @ParseFlags int parseFlags, @ScanFlags int scanFlags, long currentTime,
+            @Nullable UserHandle user)
+                    throws PackageManagerException {
         // If the package has children and this is the first dive in the function
         // we scan the package with the SCAN_CHECK_ONLY flag set to see whether all
         // packages (parent and children) would be successfully scanned before the
@@ -8181,20 +8236,20 @@
         }
 
         // Scan the parent
-        PackageParser.Package scannedPkg = scanPackageInternalLI(pkg, scanFile, policyFlags,
+        PackageParser.Package scannedPkg = scanPackageInternalLI(pkg, parseFlags,
                 scanFlags, currentTime, user);
 
         // Scan the children
         final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0;
         for (int i = 0; i < childCount; i++) {
             PackageParser.Package childPackage = pkg.childPackages.get(i);
-            scanPackageInternalLI(childPackage, scanFile, policyFlags, scanFlags,
+            scanPackageInternalLI(childPackage, parseFlags, scanFlags,
                     currentTime, user);
         }
 
 
         if ((scanFlags & SCAN_CHECK_ONLY) != 0) {
-            return scanPackageLI(pkg, scanFile, policyFlags, scanFlags, currentTime, user);
+            return scanPackageChildLI(pkg, parseFlags, scanFlags, currentTime, user);
         }
 
         return scannedPkg;
@@ -8204,11 +8259,12 @@
      *  Scans a package and returns the newly parsed package.
      *  @throws PackageManagerException on a parse error.
      */
-    private PackageParser.Package scanPackageInternalLI(PackageParser.Package pkg, File scanFile,
-            int policyFlags, int scanFlags, long currentTime, @Nullable UserHandle user)
-            throws PackageManagerException {
+    private PackageParser.Package scanPackageInternalLI(PackageParser.Package pkg,
+            @ParseFlags int parseFlags, @ScanFlags int scanFlags, long currentTime,
+            @Nullable UserHandle user)
+                    throws PackageManagerException {
         PackageSetting ps = null;
-        PackageSetting updatedPkg;
+        PackageSetting updatedPs;
         // reader
         synchronized (mPackages) {
             // Look to see if we already know about this package.
@@ -8225,14 +8281,14 @@
             // Check to see if this package could be hiding/updating a system
             // package.  Must look for it either under the original or real
             // package name depending on our state.
-            updatedPkg = mSettings.getDisabledSystemPkgLPr(ps != null ? ps.name : pkg.packageName);
-            if (DEBUG_INSTALL && updatedPkg != null) Slog.d(TAG, "updatedPkg = " + updatedPkg);
+            updatedPs = mSettings.getDisabledSystemPkgLPr(ps != null ? ps.name : pkg.packageName);
+            if (DEBUG_INSTALL && updatedPs != null) Slog.d(TAG, "updatedPkg = " + updatedPs);
 
             // If this is a package we don't know about on the system partition, we
             // may need to remove disabled child packages on the system partition
             // or may need to not add child packages if the parent apk is updated
             // on the data partition and no longer defines this child package.
-            if ((policyFlags & PackageParser.PARSE_IS_SYSTEM) != 0) {
+            if ((scanFlags & SCAN_AS_SYSTEM) != 0) {
                 // If this is a parent package for an updated system app and this system
                 // app got an OTA update which no longer defines some of the child packages
                 // we have to prune them from the disabled system packages.
@@ -8260,28 +8316,27 @@
             }
         }
 
-        final boolean isUpdatedPkg = updatedPkg != null;
-        final boolean isUpdatedSystemPkg = isUpdatedPkg
-                && (policyFlags & PackageParser.PARSE_IS_SYSTEM) != 0;
+        final boolean isUpdatedPkg = updatedPs != null;
+        final boolean isUpdatedSystemPkg = isUpdatedPkg && (scanFlags & SCAN_AS_SYSTEM) != 0;
         boolean isUpdatedPkgBetter = false;
         // First check if this is a system package that may involve an update
         if (isUpdatedSystemPkg) {
             // If new package is not located in "/system/priv-app" (e.g. due to an OTA),
             // it needs to drop FLAG_PRIVILEGED.
-            if (locationIsPrivileged(scanFile)) {
-                updatedPkg.pkgPrivateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
+            if (locationIsPrivileged(pkg.codePath)) {
+                updatedPs.pkgPrivateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
             } else {
-                updatedPkg.pkgPrivateFlags &= ~ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
+                updatedPs.pkgPrivateFlags &= ~ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
             }
             // If new package is not located in "/oem" (e.g. due to an OTA),
             // it needs to drop FLAG_OEM.
-            if (locationIsOem(scanFile)) {
-                updatedPkg.pkgPrivateFlags |= ApplicationInfo.PRIVATE_FLAG_OEM;
+            if (locationIsOem(pkg.codePath)) {
+                updatedPs.pkgPrivateFlags |= ApplicationInfo.PRIVATE_FLAG_OEM;
             } else {
-                updatedPkg.pkgPrivateFlags &= ~ApplicationInfo.PRIVATE_FLAG_OEM;
+                updatedPs.pkgPrivateFlags &= ~ApplicationInfo.PRIVATE_FLAG_OEM;
             }
 
-            if (ps != null && !ps.codePath.equals(scanFile)) {
+            if (ps != null && !ps.codePathString.equals(pkg.codePath)) {
                 // The path has changed from what was last scanned...  check the
                 // version of the new path against what we have stored to determine
                 // what to do.
@@ -8289,26 +8344,27 @@
                 if (pkg.mVersionCode <= ps.versionCode) {
                     // The system package has been updated and the code path does not match
                     // Ignore entry. Skip it.
-                    if (DEBUG_INSTALL) Slog.i(TAG, "Package " + ps.name + " at " + scanFile
+                    if (DEBUG_INSTALL) Slog.i(TAG, "Package " + ps.name + " at " + pkg.codePath
                             + " ignored: updated version " + ps.versionCode
                             + " better than this " + pkg.mVersionCode);
-                    if (!updatedPkg.codePath.equals(scanFile)) {
+                    if (!updatedPs.codePathString.equals(pkg.codePath)) {
                         Slog.w(PackageManagerService.TAG, "Code path for hidden system pkg "
-                                + ps.name + " changing from " + updatedPkg.codePathString
-                                + " to " + scanFile);
-                        updatedPkg.codePath = scanFile;
-                        updatedPkg.codePathString = scanFile.toString();
-                        updatedPkg.resourcePath = scanFile;
-                        updatedPkg.resourcePathString = scanFile.toString();
+                                + ps.name + " changing from " + updatedPs.codePathString
+                                + " to " + pkg.codePath);
+                        final File codePath = new File(pkg.codePath);
+                        updatedPs.codePath = codePath;
+                        updatedPs.codePathString = pkg.codePath;
+                        updatedPs.resourcePath = codePath;
+                        updatedPs.resourcePathString = pkg.codePath;
                     }
-                    updatedPkg.pkg = pkg;
-                    updatedPkg.versionCode = pkg.mVersionCode;
+                    updatedPs.pkg = pkg;
+                    updatedPs.versionCode = pkg.mVersionCode;
 
                     // Update the disabled system child packages to point to the package too.
-                    final int childCount = updatedPkg.childPackageNames != null
-                            ? updatedPkg.childPackageNames.size() : 0;
+                    final int childCount = updatedPs.childPackageNames != null
+                            ? updatedPs.childPackageNames.size() : 0;
                     for (int i = 0; i < childCount; i++) {
-                        String childPackageName = updatedPkg.childPackageNames.get(i);
+                        String childPackageName = updatedPs.childPackageNames.get(i);
                         PackageSetting updatedChildPkg = mSettings.getDisabledSystemPkgLPr(
                                 childPackageName);
                         if (updatedChildPkg != null) {
@@ -8329,7 +8385,7 @@
                         mPackages.remove(ps.name);
                     }
 
-                    logCriticalInfo(Log.WARN, "Package " + ps.name + " at " + scanFile
+                    logCriticalInfo(Log.WARN, "Package " + ps.name + " at " + pkg.codePath
                             + " reverting from " + ps.codePathString
                             + ": new version " + pkg.mVersionCode
                             + " better than installed " + ps.versionCode);
@@ -8349,7 +8405,7 @@
 
         String resourcePath = null;
         String baseResourcePath = null;
-        if ((policyFlags & PackageParser.PARSE_FORWARD_LOCK) != 0 && !isUpdatedPkgBetter) {
+        if ((parseFlags & PackageParser.PARSE_FORWARD_LOCK) != 0 && !isUpdatedPkgBetter) {
             if (ps != null && ps.resourcePathString != null) {
                 resourcePath = ps.resourcePathString;
                 baseResourcePath = ps.resourcePathString;
@@ -8376,39 +8432,38 @@
         if (isUpdatedSystemPkg && !isUpdatedPkgBetter) {
             // Set CPU Abis to application info.
             if ((scanFlags & SCAN_FIRST_BOOT_OR_UPGRADE) != 0) {
-                final String cpuAbiOverride = deriveAbiOverride(pkg.cpuAbiOverride, updatedPkg);
-                derivePackageAbi(pkg, scanFile, cpuAbiOverride, false, mAppLib32InstallDir);
+                final String cpuAbiOverride = deriveAbiOverride(pkg.cpuAbiOverride, updatedPs);
+                derivePackageAbi(pkg, cpuAbiOverride, false, mAppLib32InstallDir);
             } else {
-                pkg.applicationInfo.primaryCpuAbi = updatedPkg.primaryCpuAbiString;
-                pkg.applicationInfo.secondaryCpuAbi = updatedPkg.secondaryCpuAbiString;
+                pkg.applicationInfo.primaryCpuAbi = updatedPs.primaryCpuAbiString;
+                pkg.applicationInfo.secondaryCpuAbi = updatedPs.secondaryCpuAbiString;
             }
-            pkg.mExtras = updatedPkg;
+            pkg.mExtras = updatedPs;
 
             throw new PackageManagerException(Log.WARN, "Package " + ps.name + " at "
-                    + scanFile + " ignored: updated version " + ps.versionCode
+                    + pkg.codePath + " ignored: updated version " + ps.versionCode
                     + " better than this " + pkg.mVersionCode);
         }
 
         if (isUpdatedPkg) {
-            // An updated system app will not have the PARSE_IS_SYSTEM flag set
-            // initially
-            policyFlags |= PackageParser.PARSE_IS_SYSTEM;
+            // updated system applications don't initially have the SCAN_AS_SYSTEM flag set
+            scanFlags |= SCAN_AS_SYSTEM;
 
-            // An updated privileged app will not have the PARSE_IS_PRIVILEGED
+            // An updated privileged application will not have the PARSE_IS_PRIVILEGED
             // flag set initially
-            if ((updatedPkg.pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0) {
-                policyFlags |= PackageParser.PARSE_IS_PRIVILEGED;
+            if ((updatedPs.pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED) != 0) {
+                scanFlags |= SCAN_AS_PRIVILEGED;
             }
 
             // An updated OEM app will not have the PARSE_IS_OEM
             // flag set initially
-            if ((updatedPkg.pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_OEM) != 0) {
-                policyFlags |= PackageParser.PARSE_IS_OEM;
+            if ((updatedPs.pkgPrivateFlags & ApplicationInfo.PRIVATE_FLAG_OEM) != 0) {
+                scanFlags |= SCAN_AS_OEM;
             }
         }
 
         // Verify certificates against what was last scanned
-        collectCertificatesLI(ps, pkg, scanFile, policyFlags);
+        collectCertificatesLI(ps, pkg, parseFlags);
 
         /*
          * A new system app appeared, but we already had a non-system one of the
@@ -8416,7 +8471,7 @@
          */
         boolean shouldHideSystemApp = false;
         if (!isUpdatedPkg && ps != null
-                && (policyFlags & PackageParser.PARSE_IS_SYSTEM_DIR) != 0 && !isSystemApp(ps)) {
+                && (parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) != 0 && !isSystemApp(ps)) {
             /*
              * Check to make sure the signatures match first. If they don't,
              * wipe the installed application and its data.
@@ -8438,7 +8493,7 @@
                  */
                 if (pkg.mVersionCode <= ps.versionCode) {
                     shouldHideSystemApp = true;
-                    logCriticalInfo(Log.INFO, "Package " + ps.name + " appeared at " + scanFile
+                    logCriticalInfo(Log.INFO, "Package " + ps.name + " appeared at " + pkg.codePath
                             + " but new version " + pkg.mVersionCode + " better than installed "
                             + ps.versionCode + "; hiding system");
                 } else {
@@ -8448,7 +8503,7 @@
                      * already-installed application and replace it with our own
                      * while keeping the application data.
                      */
-                    logCriticalInfo(Log.WARN, "Package " + ps.name + " at " + scanFile
+                    logCriticalInfo(Log.WARN, "Package " + ps.name + " at " + pkg.codePath
                             + " reverting from " + ps.codePathString + ": new version "
                             + pkg.mVersionCode + " better than installed " + ps.versionCode);
                     InstallArgs args = createInstallArgsForExisting(packageFlagsToInstallFlags(ps),
@@ -8464,9 +8519,9 @@
         // are kept in different files. (except for app in either system or
         // vendor path).
         // TODO grab this value from PackageSettings
-        if ((policyFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
+        if ((parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
             if (ps != null && !ps.codePath.equals(ps.resourcePath)) {
-                policyFlags |= PackageParser.PARSE_FORWARD_LOCK;
+                parseFlags |= PackageParser.PARSE_FORWARD_LOCK;
             }
         }
 
@@ -8479,7 +8534,7 @@
         }
 
         // Note that we invoke the following method only if we are about to unpack an application
-        PackageParser.Package scannedPkg = scanPackageLI(pkg, policyFlags, scanFlags
+        PackageParser.Package scannedPkg = scanPackageLI(pkg, parseFlags, scanFlags
                 | SCAN_UPDATE_SIGNATURE, currentTime, user);
 
         /*
@@ -8985,11 +9040,11 @@
      * Execute the background dexopt job immediately.
      */
     @Override
-    public boolean runBackgroundDexoptJob() {
+    public boolean runBackgroundDexoptJob(@Nullable List<String> packageNames) {
         if (getInstantAppPackageName(Binder.getCallingUid()) != null) {
             return false;
         }
-        return BackgroundDexOptService.runIdleOptimizationsNow(this, mContext);
+        return BackgroundDexOptService.runIdleOptimizationsNow(this, mContext, packageNames);
     }
 
     List<PackageParser.Package> findSharedNonSystemLibraries(PackageParser.Package p) {
@@ -9481,8 +9536,8 @@
     }
 
     private PackageParser.Package scanPackageTracedLI(PackageParser.Package pkg,
-            final int policyFlags, int scanFlags, long currentTime, @Nullable UserHandle user)
-                    throws PackageManagerException {
+            final @ParseFlags int parseFlags, @ScanFlags int scanFlags, long currentTime,
+            @Nullable UserHandle user) throws PackageManagerException {
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanPackage");
         // If the package has children and this is the first dive in the function
         // we recursively scan the package with the SCAN_CHECK_ONLY flag set to see
@@ -9500,12 +9555,12 @@
         final PackageParser.Package scannedPkg;
         try {
             // Scan the parent
-            scannedPkg = scanPackageLI(pkg, policyFlags, scanFlags, currentTime, user);
+            scannedPkg = scanPackageLI(pkg, parseFlags, scanFlags, currentTime, user);
             // Scan the children
             final int childCount = (pkg.childPackages != null) ? pkg.childPackages.size() : 0;
             for (int i = 0; i < childCount; i++) {
                 PackageParser.Package childPkg = pkg.childPackages.get(i);
-                scanPackageLI(childPkg, policyFlags,
+                scanPackageLI(childPkg, parseFlags,
                         scanFlags, currentTime, user);
             }
         } finally {
@@ -9513,18 +9568,18 @@
         }
 
         if ((scanFlags & SCAN_CHECK_ONLY) != 0) {
-            return scanPackageTracedLI(pkg, policyFlags, scanFlags, currentTime, user);
+            return scanPackageTracedLI(pkg, parseFlags, scanFlags, currentTime, user);
         }
 
         return scannedPkg;
     }
 
-    private PackageParser.Package scanPackageLI(PackageParser.Package pkg, final int policyFlags,
-            int scanFlags, long currentTime, @Nullable UserHandle user)
-                    throws PackageManagerException {
+    private PackageParser.Package scanPackageLI(PackageParser.Package pkg,
+            final @ParseFlags int parseFlags, final @ScanFlags int scanFlags, long currentTime,
+            @Nullable UserHandle user) throws PackageManagerException {
         boolean success = false;
         try {
-            final PackageParser.Package res = scanPackageDirtyLI(pkg, policyFlags, scanFlags,
+            final PackageParser.Package res = scanPackageDirtyLI(pkg, parseFlags, scanFlags,
                     currentTime, user);
             success = true;
             return res;
@@ -9587,16 +9642,17 @@
     }
 
     private PackageParser.Package scanPackageDirtyLI(PackageParser.Package pkg,
-            final int policyFlags, final int scanFlags, long currentTime, @Nullable UserHandle user)
+            final @ParseFlags int parseFlags, final @ScanFlags int scanFlags, long currentTime,
+            @Nullable UserHandle user)
                     throws PackageManagerException {
         if (DEBUG_PACKAGE_SCANNING) {
-            if ((policyFlags & PackageParser.PARSE_CHATTY) != 0)
+            if ((parseFlags & PackageParser.PARSE_CHATTY) != 0)
                 Log.d(TAG, "Scanning package " + pkg.packageName);
         }
 
-        applyPolicy(pkg, policyFlags);
+        applyPolicy(pkg, parseFlags, scanFlags);
 
-        assertPackageIsValid(pkg, policyFlags, scanFlags);
+        assertPackageIsValid(pkg, parseFlags, scanFlags);
 
         if (Build.IS_DEBUGGABLE &&
                 pkg.isPrivileged() &&
@@ -9629,7 +9685,7 @@
                 suid = mSettings.getSharedUserLPw(
                         pkg.mSharedUserId, 0 /*pkgFlags*/, 0 /*pkgPrivateFlags*/, true /*create*/);
                 if (DEBUG_PACKAGE_SCANNING) {
-                    if ((policyFlags & PackageParser.PARSE_CHATTY) != 0)
+                    if ((parseFlags & PackageParser.PARSE_CHATTY) != 0)
                         Log.d(TAG, "Shared UserID " + pkg.mSharedUserId + " (uid=" + suid.userId
                                 + "): packages=" + suid.packages);
                 }
@@ -9797,7 +9853,7 @@
             }
 
             if ((scanFlags & SCAN_BOOTING) == 0
-                    && (policyFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
+                    && (parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
                 // Check all shared libraries and map to their actual file path.
                 // We only do this here for apps not on a system dir, because those
                 // are the only ones that can fail an install due to this.  We
@@ -9834,7 +9890,7 @@
                     // over the latest parsed certs.
                     pkgSetting.signatures.mSignatures = pkg.mSignatures;
                 } else {
-                    if ((policyFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
+                    if ((parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
                         throw new PackageManagerException(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
                                 "Package " + pkg.packageName + " upgrade keys do not match the "
                                 + "previously installed version");
@@ -9861,7 +9917,7 @@
                     // over the latest parsed certs.
                     pkgSetting.signatures.mSignatures = pkg.mSignatures;
                 } catch (PackageManagerException e) {
-                    if ((policyFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
+                    if ((parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
                         throw e;
                     }
                     // The signature has changed, but this package is in the system
@@ -9921,8 +9977,7 @@
             if ((scanFlags & SCAN_FIRST_BOOT_OR_UPGRADE) != 0) {
                 Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "derivePackageAbi");
                 final boolean extractNativeLibs = !pkg.isLibrary();
-                derivePackageAbi(pkg, scanFile, cpuAbiOverride, extractNativeLibs,
-                        mAppLib32InstallDir);
+                derivePackageAbi(pkg, cpuAbiOverride, extractNativeLibs, mAppLib32InstallDir);
                 Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
 
                 // Some system apps still use directory structure for native libraries
@@ -10029,7 +10084,7 @@
         }
 
         // Take care of first install / last update times.
-        final long scanFileTime = getLastModifiedTime(pkg, scanFile);
+        final long scanFileTime = getLastModifiedTime(pkg);
         if (currentTime != 0) {
             if (pkgSetting.firstInstallTime == 0) {
                 pkgSetting.firstInstallTime = pkgSetting.lastUpdateTime = currentTime;
@@ -10039,7 +10094,7 @@
         } else if (pkgSetting.firstInstallTime == 0) {
             // We need *something*.  Take time time stamp of the file.
             pkgSetting.firstInstallTime = pkgSetting.lastUpdateTime = scanFileTime;
-        } else if ((policyFlags & PackageParser.PARSE_IS_SYSTEM_DIR) != 0) {
+        } else if ((parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) != 0) {
             if (scanFileTime != pkgSetting.timeStamp) {
                 // A package on the system image has changed; consider this
                 // to be an update.
@@ -10058,7 +10113,7 @@
             final int userId = user == null ? 0 : user.getIdentifier();
             // Modify state for the given package setting
             commitPackageSettings(pkg, pkgSetting, user, scanFlags,
-                    (policyFlags & PackageParser.PARSE_CHATTY) != 0 /*chatty*/);
+                    (parseFlags & PackageParser.PARSE_CHATTY) != 0 /*chatty*/);
             if (pkgSetting.getInstantApp(userId)) {
                 mInstantAppRegistry.addInstantAppLPw(userId, pkgSetting.appId);
             }
@@ -10073,8 +10128,9 @@
      * Implementation detail: This method must NOT have any side effect. It would
      * ideally be static, but, it requires locks to read system state.
      */
-    private void applyPolicy(PackageParser.Package pkg, int policyFlags) {
-        if ((policyFlags&PackageParser.PARSE_IS_SYSTEM) != 0) {
+    private void applyPolicy(PackageParser.Package pkg, final @ParseFlags int parseFlags,
+            final @ScanFlags int scanFlags) {
+        if ((scanFlags & SCAN_AS_SYSTEM) != 0) {
             pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
             if (pkg.applicationInfo.isDirectBootAware()) {
                 // we're direct boot aware; set for all components
@@ -10095,21 +10151,60 @@
                 pkg.isStub = true;
             }
         } else {
-            // Only allow system apps to be flagged as core apps.
+            // non system apps can't be flagged as core
             pkg.coreApp = false;
             // clear flags not applicable to regular apps
+            pkg.applicationInfo.flags &=
+                    ~ApplicationInfo.FLAG_PERSISTENT;
             pkg.applicationInfo.privateFlags &=
                     ~ApplicationInfo.PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE;
             pkg.applicationInfo.privateFlags &=
                     ~ApplicationInfo.PRIVATE_FLAG_DIRECT_BOOT_AWARE;
+            // clear protected broadcasts
+            pkg.protectedBroadcasts = null;
+            // cap permission priorities
+            if (pkg.permissionGroups != null && pkg.permissionGroups.size() > 0) {
+                for (int i = pkg.permissionGroups.size() - 1; i >= 0; --i) {
+                    pkg.permissionGroups.get(i).info.priority = 0;
+                }
+            }
         }
-        pkg.mTrustedOverlay = (policyFlags&PackageParser.PARSE_TRUSTED_OVERLAY) != 0;
+        if ((scanFlags & SCAN_AS_PRIVILEGED) == 0) {
+            // ignore export request for single user receivers
+            if (pkg.receivers != null) {
+                for (int i = pkg.receivers.size() - 1; i >= 0; --i) {
+                    final PackageParser.Activity receiver = pkg.receivers.get(i);
+                    if ((receiver.info.flags & ActivityInfo.FLAG_SINGLE_USER) != 0) {
+                        receiver.info.exported = false;
+                    }
+                }
+            }
+            // ignore export request for single user services
+            if (pkg.services != null) {
+                for (int i = pkg.services.size() - 1; i >= 0; --i) {
+                    final PackageParser.Service service = pkg.services.get(i);
+                    if ((service.info.flags & ServiceInfo.FLAG_SINGLE_USER) != 0) {
+                        service.info.exported = false;
+                    }
+                }
+            }
+            // ignore export request for single user providers
+            if (pkg.providers != null) {
+                for (int i = pkg.providers.size() - 1; i >= 0; --i) {
+                    final PackageParser.Provider provider = pkg.providers.get(i);
+                    if ((provider.info.flags & ProviderInfo.FLAG_SINGLE_USER) != 0) {
+                        provider.info.exported = false;
+                    }
+                }
+            }
+        }
+        pkg.mTrustedOverlay = (scanFlags & SCAN_TRUSTED_OVERLAY) != 0;
 
-        if ((policyFlags&PackageParser.PARSE_IS_PRIVILEGED) != 0) {
+        if ((scanFlags & SCAN_AS_PRIVILEGED) != 0) {
             pkg.applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
         }
 
-        if ((policyFlags&PackageParser.PARSE_IS_OEM) != 0) {
+        if ((scanFlags & SCAN_AS_OEM) != 0) {
             pkg.applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_OEM;
         }
 
@@ -10130,9 +10225,10 @@
      *
      * @throws PackageManagerException If the package fails any of the validation checks
      */
-    private void assertPackageIsValid(PackageParser.Package pkg, int policyFlags, int scanFlags)
-            throws PackageManagerException {
-        if ((policyFlags & PackageParser.PARSE_ENFORCE_CODE) != 0) {
+    private void assertPackageIsValid(PackageParser.Package pkg, final @ParseFlags int parseFlags,
+            final @ScanFlags int scanFlags)
+                    throws PackageManagerException {
+        if ((parseFlags & PackageParser.PARSE_ENFORCE_CODE) != 0) {
             assertCodePolicy(pkg);
         }
 
@@ -10290,7 +10386,7 @@
 
             // Only privileged apps and updated privileged apps can add child packages.
             if (pkg.childPackages != null && !pkg.childPackages.isEmpty()) {
-                if ((policyFlags & PARSE_IS_PRIVILEGED) == 0) {
+                if ((scanFlags & SCAN_AS_PRIVILEGED) == 0) {
                     throw new PackageManagerException("Only privileged apps can add child "
                             + "packages. Ignoring package " + pkg.packageName);
                 }
@@ -10416,7 +10512,8 @@
      * be available for query, resolution, etc...
      */
     private void commitPackageSettings(PackageParser.Package pkg, PackageSetting pkgSetting,
-            UserHandle user, int scanFlags, boolean chatty) throws PackageManagerException {
+            UserHandle user, final @ScanFlags int scanFlags, boolean chatty)
+                    throws PackageManagerException {
         final String pkgName = pkg.packageName;
         if (mCustomResolverComponentName != null &&
                 mCustomResolverComponentName.getPackageName().equals(pkg.packageName)) {
@@ -10767,10 +10864,9 @@
      *
      * If {@code extractLibs} is true, native libraries are extracted from the app if required.
      */
-    private static void derivePackageAbi(PackageParser.Package pkg, File scanFile,
-                                 String cpuAbiOverride, boolean extractLibs,
-                                 File appLib32InstallDir)
-            throws PackageManagerException {
+    private static void derivePackageAbi(PackageParser.Package pkg, String cpuAbiOverride,
+            boolean extractLibs, File appLib32InstallDir)
+                    throws PackageManagerException {
         // Give ourselves some initial paths; we'll come back for another
         // pass once we've determined ABI below.
         setNativeLibraryPaths(pkg, appLib32InstallDir);
@@ -14903,7 +14999,12 @@
             resourceFile = afterCodeFile;
 
             // Reflect the rename in scanned details
-            pkg.setCodePath(afterCodeFile.getAbsolutePath());
+            try {
+                pkg.setCodePath(afterCodeFile.getCanonicalPath());
+            } catch (IOException e) {
+                Slog.e(TAG, "Failed to get path: " + afterCodeFile, e);
+                return false;
+            }
             pkg.setBaseCodePath(FileUtils.rewriteAfterRename(beforeCodeFile,
                     afterCodeFile, pkg.baseCodePath));
             pkg.setSplitCodePaths(FileUtils.rewriteAfterRename(beforeCodeFile,
@@ -15248,9 +15349,9 @@
     /*
      * Install a non-existing package.
      */
-    private void installNewPackageLIF(PackageParser.Package pkg, final int policyFlags,
-            int scanFlags, UserHandle user, String installerPackageName, String volumeUuid,
-            PackageInstalledInfo res, int installReason) {
+    private void installNewPackageLIF(PackageParser.Package pkg, final @ParseFlags int parseFlags,
+            final @ScanFlags int scanFlags, UserHandle user, String installerPackageName,
+            String volumeUuid, PackageInstalledInfo res, int installReason) {
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "installNewPackage");
 
         // Remember this for later, in case we need to rollback this install
@@ -15279,7 +15380,7 @@
         }
 
         try {
-            PackageParser.Package newPackage = scanPackageTracedLI(pkg, policyFlags, scanFlags,
+            PackageParser.Package newPackage = scanPackageTracedLI(pkg, parseFlags, scanFlags,
                     System.currentTimeMillis(), user);
 
             updateSettingsLI(newPackage, installerPackageName, null, res, user, installReason);
@@ -15307,9 +15408,9 @@
         }
     }
 
-    private void replacePackageLIF(PackageParser.Package pkg, final int policyFlags, int scanFlags,
-            UserHandle user, String installerPackageName, PackageInstalledInfo res,
-            int installReason) {
+    private void replacePackageLIF(PackageParser.Package pkg, final @ParseFlags int parseFlags,
+            final @ScanFlags int scanFlags, UserHandle user, String installerPackageName,
+            PackageInstalledInfo res, int installReason) {
         final boolean isInstantApp = (scanFlags & SCAN_AS_INSTANT_APP) != 0;
 
         final PackageParser.Package oldPackage;
@@ -15329,7 +15430,7 @@
                     == android.os.Build.VERSION_CODES.CUR_DEVELOPMENT;
             if (oldTargetsPreRelease
                     && !newTargetsPreRelease
-                    && ((policyFlags & PackageParser.PARSE_FORCE_SDK) == 0)) {
+                    && ((parseFlags & PackageParser.PARSE_FORCE_SDK) == 0)) {
                 Slog.w(TAG, "Can't install package targeting released sdk");
                 res.setReturnCode(PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE);
                 return;
@@ -15480,15 +15581,16 @@
             final boolean oem =
                     (oldPackage.applicationInfo.privateFlags
                             & ApplicationInfo.PRIVATE_FLAG_OEM) != 0;
-            final int systemPolicyFlags = policyFlags
-                    | PackageParser.PARSE_IS_SYSTEM
-                    | (privileged ? PARSE_IS_PRIVILEGED : 0)
-                    | (oem ? PARSE_IS_OEM : 0);
+            final @ParseFlags int systemParseFlags = parseFlags;
+            final @ScanFlags int systemScanFlags = scanFlags
+                    | SCAN_AS_SYSTEM
+                    | (privileged ? SCAN_AS_PRIVILEGED : 0)
+                    | (oem ? SCAN_AS_OEM : 0);
 
-            replaceSystemPackageLIF(oldPackage, pkg, systemPolicyFlags, scanFlags,
+            replaceSystemPackageLIF(oldPackage, pkg, systemParseFlags, systemScanFlags,
                     user, allUsers, installerPackageName, res, installReason);
         } else {
-            replaceNonSystemPackageLIF(oldPackage, pkg, policyFlags, scanFlags,
+            replaceNonSystemPackageLIF(oldPackage, pkg, parseFlags, scanFlags,
                     user, allUsers, installerPackageName, res, installReason);
         }
     }
@@ -15510,9 +15612,9 @@
     }
 
     private void replaceNonSystemPackageLIF(PackageParser.Package deletedPackage,
-            PackageParser.Package pkg, final int policyFlags, int scanFlags, UserHandle user,
-            int[] allUsers, String installerPackageName, PackageInstalledInfo res,
-            int installReason) {
+            PackageParser.Package pkg, final @ParseFlags int parseFlags,
+            final @ScanFlags int scanFlags, UserHandle user, int[] allUsers,
+            String installerPackageName, PackageInstalledInfo res, int installReason) {
         if (DEBUG_INSTALL) Slog.d(TAG, "replaceNonSystemPackageLI: new=" + pkg + ", old="
                 + deletedPackage);
 
@@ -15553,7 +15655,7 @@
             clearAppProfilesLIF(deletedPackage, UserHandle.USER_ALL);
 
             try {
-                final PackageParser.Package newPackage = scanPackageTracedLI(pkg, policyFlags,
+                final PackageParser.Package newPackage = scanPackageTracedLI(pkg, parseFlags,
                         scanFlags | SCAN_UPDATE_TIME, System.currentTimeMillis(), user);
                 updateSettingsLI(newPackage, installerPackageName, allUsers, res, user,
                         installReason);
@@ -15659,7 +15761,8 @@
     }
 
     private void replaceSystemPackageLIF(PackageParser.Package deletedPackage,
-            PackageParser.Package pkg, final int policyFlags, int scanFlags, UserHandle user,
+            PackageParser.Package pkg, final @ParseFlags int parseFlags,
+            final @ScanFlags int scanFlags, UserHandle user,
             int[] allUsers, String installerPackageName, PackageInstalledInfo res,
             int installReason) {
         if (DEBUG_INSTALL) Slog.d(TAG, "replaceSystemPackageLI: new=" + pkg
@@ -15697,7 +15800,7 @@
         PackageParser.Package newPackage = null;
         try {
             // Add the package to the internal data structures
-            newPackage = scanPackageTracedLI(pkg, policyFlags, scanFlags, 0, user);
+            newPackage = scanPackageTracedLI(pkg, parseFlags, scanFlags, 0, user);
 
             // Set the update and install times
             PackageSetting deletedPkgSetting = (PackageSetting) deletedPackage.mExtras;
@@ -15754,7 +15857,7 @@
             }
             // Add back the old system package
             try {
-                scanPackageTracedLI(deletedPackage, policyFlags, SCAN_UPDATE_SIGNATURE, 0, user);
+                scanPackageTracedLI(deletedPackage, parseFlags, SCAN_UPDATE_SIGNATURE, 0, user);
             } catch (PackageManagerException e) {
                 Slog.e(TAG, "Failed to restore original package: " + e.getMessage());
             }
@@ -16008,7 +16111,7 @@
         final boolean virtualPreload =
                 ((installFlags & PackageManager.INSTALL_VIRTUAL_PRELOAD) != 0);
         boolean replace = false;
-        int scanFlags = SCAN_NEW_INSTALL | SCAN_UPDATE_SIGNATURE;
+        @ScanFlags int scanFlags = SCAN_NEW_INSTALL | SCAN_UPDATE_SIGNATURE;
         if (args.move != null) {
             // moving a complete application; perform an initial scan on the new install location
             scanFlags |= SCAN_INITIAL;
@@ -16041,7 +16144,7 @@
         }
 
         // Retrieve PackageSettings and parse package
-        final int parseFlags = mDefParseFlags | PackageParser.PARSE_CHATTY
+        @ParseFlags final int parseFlags = mDefParseFlags | PackageParser.PARSE_CHATTY
                 | PackageParser.PARSE_ENFORCE_CODE
                 | (forwardLocked ? PackageParser.PARSE_FORWARD_LOCK : 0)
                 | (onExternal ? PackageParser.PARSE_EXTERNAL_STORAGE : 0)
@@ -16383,8 +16486,7 @@
                 String abiOverride = (TextUtils.isEmpty(pkg.cpuAbiOverride) ?
                     args.abiOverride : pkg.cpuAbiOverride);
                 final boolean extractNativeLibs = !pkg.isLibrary();
-                derivePackageAbi(pkg, new File(pkg.codePath), abiOverride,
-                        extractNativeLibs, mAppLib32InstallDir);
+                derivePackageAbi(pkg, abiOverride, extractNativeLibs, mAppLib32InstallDir);
             } catch (PackageManagerException pme) {
                 Slog.e(TAG, "Error deriving application ABI", pme);
                 res.setError(INSTALL_FAILED_INTERNAL_ERROR, "Error deriving application ABI");
@@ -16473,7 +16575,7 @@
                         return;
                     }
                 }
-                replacePackageLIF(pkg, parseFlags, scanFlags | SCAN_REPLACING, args.user,
+                replacePackageLIF(pkg, parseFlags, scanFlags, args.user,
                         installerPackageName, res, args.installReason);
             } else {
                 installNewPackageLIF(pkg, parseFlags, scanFlags | SCAN_DELETE_DATA_ON_FAILURES,
@@ -17099,7 +17201,7 @@
             try (PackageFreezer freezer = freezePackageForDelete(packageName, freezeUser,
                     deleteFlags, "deletePackageX")) {
                 res = deletePackageLIF(packageName, UserHandle.of(removeUser), true, allUsers,
-                        deleteFlags | FLAGS_REMOVE_CHATTY, info, true, null);
+                        deleteFlags | PackageManager.DELETE_CHATTY, info, true, null);
             }
             synchronized (mPackages) {
                 if (res) {
@@ -17291,7 +17393,7 @@
             }
         }
 
-        removePackageLI(ps, (flags & FLAGS_REMOVE_CHATTY) != 0);
+        removePackageLI(ps, (flags & PackageManager.DELETE_CHATTY) != 0);
 
         if ((flags & PackageManager.DELETE_KEEP_DATA) == 0) {
             final PackageParser.Package resolvedPkg;
@@ -17388,21 +17490,19 @@
         }
     }
 
-    static boolean locationIsPrivileged(File path) {
+    static boolean locationIsPrivileged(String path) {
         try {
-            final String privilegedAppDir = new File(Environment.getRootDirectory(), "priv-app")
-                    .getCanonicalPath();
-            return path.getCanonicalPath().startsWith(privilegedAppDir);
+            final File privilegedAppDir = new File(Environment.getRootDirectory(), "priv-app");
+            return path.startsWith(privilegedAppDir.getCanonicalPath());
         } catch (IOException e) {
             Slog.e(TAG, "Unable to access code path " + path);
         }
         return false;
     }
 
-    static boolean locationIsOem(File path) {
+    static boolean locationIsOem(String path) {
         try {
-            return path.getCanonicalPath().startsWith(
-                    Environment.getOemDirectory().getCanonicalPath());
+            return path.startsWith(Environment.getOemDirectory().getCanonicalPath());
         } catch (IOException e) {
             Slog.e(TAG, "Unable to access code path " + path);
         }
@@ -17498,7 +17598,7 @@
         // Install the system package
         if (DEBUG_REMOVE) Slog.d(TAG, "Re-installing system package: " + disabledPs);
         try {
-            installPackageFromSystemLIF(disabledPs.codePath, false /*isPrivileged*/, allUserHandles,
+            installPackageFromSystemLIF(disabledPs.codePathString, false, allUserHandles,
                     outInfo.origUsers, deletedPs.getPermissionsState(), writeSettings);
         } catch (PackageManagerException e) {
             Slog.w(TAG, "Failed to restore system package:" + deletedPkg.packageName + ": "
@@ -17515,23 +17615,25 @@
     /**
      * Installs a package that's already on the system partition.
      */
-    private PackageParser.Package installPackageFromSystemLIF(@NonNull File codePath,
+    private PackageParser.Package installPackageFromSystemLIF(@NonNull String codePathString,
             boolean isPrivileged, @Nullable int[] allUserHandles, @Nullable int[] origUserHandles,
             @Nullable PermissionsState origPermissionState, boolean writeSettings)
                     throws PackageManagerException {
-        int parseFlags = mDefParseFlags
+        @ParseFlags int parseFlags =
+                mDefParseFlags
                 | PackageParser.PARSE_MUST_BE_APK
-                | PackageParser.PARSE_IS_SYSTEM
                 | PackageParser.PARSE_IS_SYSTEM_DIR;
-        if (isPrivileged || locationIsPrivileged(codePath)) {
-            parseFlags |= PackageParser.PARSE_IS_PRIVILEGED;
+        @ScanFlags int scanFlags = SCAN_AS_SYSTEM;
+        if (isPrivileged || locationIsPrivileged(codePathString)) {
+            scanFlags |= SCAN_AS_PRIVILEGED;
         }
-        if (locationIsOem(codePath)) {
-            parseFlags |= PackageParser.PARSE_IS_OEM;
+        if (locationIsOem(codePathString)) {
+            scanFlags |= SCAN_AS_OEM;
         }
 
+        final File codePath = new File(codePathString);
         final PackageParser.Package pkg =
-                scanPackageTracedLI(codePath, parseFlags, 0 /*scanFlags*/, 0 /*currentTime*/, null);
+                scanPackageTracedLI(codePath, parseFlags, scanFlags, 0 /*currentTime*/, null);
 
         try {
             // update shared libraries for the newly re-installed system package
@@ -19587,9 +19689,8 @@
                 pp.setCallback(mPackageParserCallback);
                 final PackageParser.Package tmpPkg;
                 try {
-                    final int parseFlags = mDefParseFlags
+                    final @ParseFlags int parseFlags = mDefParseFlags
                             | PackageParser.PARSE_MUST_BE_APK
-                            | PackageParser.PARSE_IS_SYSTEM
                             | PackageParser.PARSE_IS_SYSTEM_DIR;
                     tmpPkg = pp.parsePackage(codePath, parseFlags);
                 } catch (PackageParserException e) {
@@ -19642,7 +19743,7 @@
                                 // until we can disable the package later.
                                 enableSystemPackageLPw(deletedPkg);
                             }
-                            installPackageFromSystemLIF(new File(deletedPkg.codePath),
+                            installPackageFromSystemLIF(deletedPkg.codePath,
                                     false /*isPrivileged*/, null /*allUserHandles*/,
                                     null /*origUserHandles*/, null /*origPermissionsState*/,
                                     true /*writeSettings*/);
@@ -22607,6 +22708,12 @@
         }
 
         @Override
+        public int getPackageUid(String packageName, int flags, int userId) {
+            return PackageManagerService.this
+                    .getPackageUid(packageName, flags, userId);
+        }
+
+        @Override
         public ApplicationInfo getApplicationInfo(
                 String packageName, int flags, int filterCallingUid, int userId) {
             return PackageManagerService.this
diff --git a/com/android/server/pm/PackageManagerServiceUtils.java b/com/android/server/pm/PackageManagerServiceUtils.java
index 758abd7..20ec9b5 100644
--- a/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/com/android/server/pm/PackageManagerServiceUtils.java
@@ -295,19 +295,20 @@
         return false;
     }
 
-    public static long getLastModifiedTime(PackageParser.Package pkg, File srcFile) {
-        if (srcFile.isDirectory()) {
-            final File baseFile = new File(pkg.baseCodePath);
-            long maxModifiedTime = baseFile.lastModified();
-            if (pkg.splitCodePaths != null) {
-                for (int i = pkg.splitCodePaths.length - 1; i >=0; --i) {
-                    final File splitFile = new File(pkg.splitCodePaths[i]);
-                    maxModifiedTime = Math.max(maxModifiedTime, splitFile.lastModified());
-                }
-            }
-            return maxModifiedTime;
+    public static long getLastModifiedTime(PackageParser.Package pkg) {
+        final File srcFile = new File(pkg.codePath);
+        if (!srcFile.isDirectory()) {
+            return srcFile.lastModified();
         }
-        return srcFile.lastModified();
+        final File baseFile = new File(pkg.baseCodePath);
+        long maxModifiedTime = baseFile.lastModified();
+        if (pkg.splitCodePaths != null) {
+            for (int i = pkg.splitCodePaths.length - 1; i >=0; --i) {
+                final File splitFile = new File(pkg.splitCodePaths[i]);
+                maxModifiedTime = Math.max(maxModifiedTime, splitFile.lastModified());
+            }
+        }
+        return maxModifiedTime;
     }
 
     /**
@@ -570,7 +571,7 @@
             if (!match) {
                 throw new PackageManagerException(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
                         "Package " + packageName +
-                        " signatures don't match previously installed version; ignoring!");
+                        " signatures do not match previously installed version; ignoring!");
             }
         }
         // Check for shared user signatures
@@ -578,16 +579,16 @@
             // Already existing package. Make sure signatures match
             boolean match = compareSignatures(pkgSetting.sharedUser.signatures.mSignatures,
                     parsedSignatures) == PackageManager.SIGNATURE_MATCH;
-            if (!match) {
+            if (!match && compareCompat) {
                 match = matchSignaturesCompat(
                         packageName, pkgSetting.sharedUser.signatures, parsedSignatures);
             }
-            if (!match && compareCompat) {
+            if (!match && compareRecover) {
                 match = matchSignaturesRecover(
                         packageName, pkgSetting.sharedUser.signatures.mSignatures, parsedSignatures);
                 compatMatch |= match;
             }
-            if (!match && compareRecover) {
+            if (!match) {
                 throw new PackageManagerException(INSTALL_FAILED_SHARED_USER_INCOMPATIBLE,
                         "Package " + packageName
                         + " has no signatures that match those in shared user "
diff --git a/com/android/server/pm/PackageManagerShellCommand.java b/com/android/server/pm/PackageManagerShellCommand.java
index 807eb1a..44f36d1 100644
--- a/com/android/server/pm/PackageManagerShellCommand.java
+++ b/com/android/server/pm/PackageManagerShellCommand.java
@@ -264,7 +264,7 @@
                 PackageLite pkgLite = new PackageLite(null, baseApk, null, null, null, null,
                         null, null);
                 params.sessionParams.setSize(PackageHelper.calculateInstalledSize(
-                        pkgLite, params.sessionParams.abiOverride));
+                        pkgLite, params.sessionParams.abiOverride, fd.getFileDescriptor()));
             } catch (PackageParserException | IOException e) {
                 getErrPrintWriter().println("Error: Failed to parse APK file: " + inPath);
                 throw new IllegalArgumentException(
@@ -1169,11 +1169,17 @@
         }
 
         List<String> failedPackages = new ArrayList<>();
+        int index = 0;
         for (String packageName : packageNames) {
             if (clearProfileData) {
                 mInterface.clearApplicationProfileData(packageName);
             }
 
+            if (allPackages) {
+                pw.println(++index + "/" + packageNames.size() + ": " + packageName);
+                pw.flush();
+            }
+
             boolean result = secondaryDex
                     ? mInterface.performDexOptSecondary(packageName,
                             targetCompilerFilter, forceCompilation)
@@ -1219,7 +1225,13 @@
     }
 
     private int runDexoptJob() throws RemoteException {
-        boolean result = mInterface.runBackgroundDexoptJob();
+        String arg;
+        List<String> packageNames = new ArrayList<>();
+        while ((arg = getNextArg()) != null) {
+            packageNames.add(arg);
+        }
+        boolean result = mInterface.runBackgroundDexoptJob(packageNames.isEmpty() ? null :
+                packageNames);
         return result ? 0 : -1;
     }
 
diff --git a/com/android/server/pm/Settings.java b/com/android/server/pm/Settings.java
index 7077fd1..ddad677 100644
--- a/com/android/server/pm/Settings.java
+++ b/com/android/server/pm/Settings.java
@@ -3572,11 +3572,10 @@
         int pkgFlags = 0;
         int pkgPrivateFlags = 0;
         pkgFlags |= ApplicationInfo.FLAG_SYSTEM;
-        final File codePathFile = new File(codePathStr);
-        if (PackageManagerService.locationIsPrivileged(codePathFile)) {
+        if (PackageManagerService.locationIsPrivileged(codePathStr)) {
             pkgPrivateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
         }
-        PackageSetting ps = new PackageSetting(name, realName, codePathFile,
+        PackageSetting ps = new PackageSetting(name, realName, new File(codePathStr),
                 new File(resourcePathStr), legacyNativeLibraryPathStr, primaryCpuAbiStr,
                 secondaryCpuAbiStr, cpuAbiOverrideStr, versionCode, pkgFlags, pkgPrivateFlags,
                 parentPackageName, null /*childPackageNames*/, 0 /*sharedUserId*/, null, null);
diff --git a/com/android/server/pm/UserManagerService.java b/com/android/server/pm/UserManagerService.java
index 1152310..dbf413f 100644
--- a/com/android/server/pm/UserManagerService.java
+++ b/com/android/server/pm/UserManagerService.java
@@ -717,6 +717,19 @@
         }
     }
 
+    @Override
+    public int getProfileParentId(int userHandle) {
+        checkManageUsersPermission("get the profile parent");
+        synchronized (mUsersLock) {
+            UserInfo profileParent = getProfileParentLU(userHandle);
+            if (profileParent == null) {
+                return userHandle;
+            }
+
+            return profileParent.id;
+        }
+    }
+
     private UserInfo getProfileParentLU(int userHandle) {
         UserInfo profile = getUserInfoLU(userHandle);
         if (profile == null) {
diff --git a/com/android/server/pm/dex/DexLogger.java b/com/android/server/pm/dex/DexLogger.java
new file mode 100644
index 0000000..88d9e52
--- /dev/null
+++ b/com/android/server/pm/dex/DexLogger.java
@@ -0,0 +1,123 @@
+/*
+ * 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.dex;
+
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
+import android.os.RemoteException;
+
+import android.util.ArraySet;
+import android.util.ByteStringUtils;
+import android.util.EventLog;
+import android.util.PackageUtils;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.pm.Installer;
+import com.android.server.pm.Installer.InstallerException;
+
+import java.io.File;
+import java.util.Set;
+
+import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo;
+
+/**
+ * This class is responsible for logging data about secondary dex files.
+ * The data logged includes hashes of the name and content of each file.
+ */
+public class DexLogger implements DexManager.Listener {
+    private static final String TAG = "DexLogger";
+
+    // Event log tag & subtag used for SafetyNet logging of dynamic
+    // code loading (DCL) - see b/63927552.
+    private static final int SNET_TAG = 0x534e4554;
+    private static final String DCL_SUBTAG = "dcl";
+
+    private final IPackageManager mPackageManager;
+    private final Object mInstallLock;
+    @GuardedBy("mInstallLock")
+    private final Installer mInstaller;
+
+    public static DexManager.Listener getListener(IPackageManager pms,
+            Installer installer, Object installLock) {
+        return new DexLogger(pms, installer, installLock);
+    }
+
+    @VisibleForTesting
+    /*package*/ DexLogger(IPackageManager pms, Installer installer, Object installLock) {
+        mPackageManager = pms;
+        mInstaller = installer;
+        mInstallLock = installLock;
+    }
+
+    /**
+     * Compute and log hashes of the name and content of a secondary dex file.
+     */
+    @Override
+    public void onReconcileSecondaryDexFile(ApplicationInfo appInfo, DexUseInfo dexUseInfo,
+            String dexPath, int storageFlags) {
+        int ownerUid = appInfo.uid;
+
+        byte[] hash = null;
+        synchronized(mInstallLock) {
+            try {
+                hash = mInstaller.hashSecondaryDexFile(dexPath, appInfo.packageName,
+                        ownerUid, appInfo.volumeUuid, storageFlags);
+            } catch (InstallerException e) {
+                Slog.e(TAG, "Got InstallerException when hashing dex " + dexPath +
+                        " : " + e.getMessage());
+            }
+        }
+        if (hash == null) {
+            return;
+        }
+
+        String dexFileName = new File(dexPath).getName();
+        String message = PackageUtils.computeSha256Digest(dexFileName.getBytes());
+        // Valid SHA256 will be 256 bits, 32 bytes.
+        if (hash.length == 32) {
+            message = message + ' ' + ByteStringUtils.toHexString(hash);
+        }
+
+        writeDclEvent(ownerUid, message);
+
+        if (dexUseInfo.isUsedByOtherApps()) {
+            Set<String> otherPackages = dexUseInfo.getLoadingPackages();
+            Set<Integer> otherUids = new ArraySet<>(otherPackages.size());
+            for (String otherPackageName : otherPackages) {
+                try {
+                    int otherUid = mPackageManager.getPackageUid(
+                        otherPackageName, /*flags*/0, dexUseInfo.getOwnerUserId());
+                    if (otherUid != -1 && otherUid != ownerUid) {
+                        otherUids.add(otherUid);
+                    }
+                } catch (RemoteException ignore) {
+                    // Can't happen, we're local.
+                }
+            }
+            for (int otherUid : otherUids) {
+                writeDclEvent(otherUid, message);
+            }
+        }
+    }
+
+    @VisibleForTesting
+    /*package*/ void writeDclEvent(int uid, String message) {
+        EventLog.writeEvent(SNET_TAG, DCL_SUBTAG, uid, message);
+    }
+}
diff --git a/com/android/server/pm/dex/DexManager.java b/com/android/server/pm/dex/DexManager.java
index 6274754..0e2730c 100644
--- a/com/android/server/pm/dex/DexManager.java
+++ b/com/android/server/pm/dex/DexManager.java
@@ -76,6 +76,7 @@
     private final Object mInstallLock;
     @GuardedBy("mInstallLock")
     private final Installer mInstaller;
+    private final Listener mListener;
 
     // Possible outcomes of a dex search.
     private static int DEX_SEARCH_NOT_FOUND = 0;  // dex file not found
@@ -96,14 +97,24 @@
      */
     private final static PackageUseInfo DEFAULT_USE_INFO = new PackageUseInfo();
 
+    public interface Listener {
+        /**
+         * Invoked just before the secondary dex file {@code dexPath} for the specified application
+         * is reconciled.
+         */
+        void onReconcileSecondaryDexFile(ApplicationInfo appInfo, DexUseInfo dexUseInfo,
+                String dexPath, int storageFlags);
+    }
+
     public DexManager(IPackageManager pms, PackageDexOptimizer pdo,
-            Installer installer, Object installLock) {
+            Installer installer, Object installLock, Listener listener) {
       mPackageCodeLocationsCache = new HashMap<>();
       mPackageDexUsage = new PackageDexUsage();
       mPackageManager = pms;
       mPackageDexOptimizer = pdo;
       mInstaller = installer;
       mInstallLock = installLock;
+      mListener = listener;
     }
 
     /**
@@ -389,7 +400,7 @@
                 : mPackageDexOptimizer;
         String packageName = options.getPackageName();
         PackageUseInfo useInfo = getPackageUseInfoOrDefault(packageName);
-        if (useInfo == null || useInfo.getDexUseInfoMap().isEmpty()) {
+        if (useInfo.getDexUseInfoMap().isEmpty()) {
             if (DEBUG) {
                 Slog.d(TAG, "No secondary dex use for package:" + packageName);
             }
@@ -433,7 +444,7 @@
      */
     public void reconcileSecondaryDexFiles(String packageName) {
         PackageUseInfo useInfo = getPackageUseInfoOrDefault(packageName);
-        if (useInfo == null || useInfo.getDexUseInfoMap().isEmpty()) {
+        if (useInfo.getDexUseInfoMap().isEmpty()) {
             if (DEBUG) {
                 Slog.d(TAG, "No secondary dex use for package:" + packageName);
             }
@@ -481,12 +492,16 @@
                 continue;
             }
 
+            if (mListener != null) {
+                mListener.onReconcileSecondaryDexFile(info, dexUseInfo, dexPath, flags);
+            }
+
             boolean dexStillExists = true;
             synchronized(mInstallLock) {
                 try {
                     String[] isas = dexUseInfo.getLoaderIsas().toArray(new String[0]);
                     dexStillExists = mInstaller.reconcileSecondaryDexFile(dexPath, packageName,
-                            pkg.applicationInfo.uid, isas, pkg.applicationInfo.volumeUuid, flags);
+                            info.uid, isas, info.volumeUuid, flags);
                 } catch (InstallerException e) {
                     Slog.e(TAG, "Got InstallerException when reconciling dex " + dexPath +
                             " : " + e.getMessage());
diff --git a/com/android/server/policy/BarController.java b/com/android/server/policy/BarController.java
index b179235..10d9565 100644
--- a/com/android/server/policy/BarController.java
+++ b/com/android/server/policy/BarController.java
@@ -24,9 +24,9 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.WindowManager;
-import android.view.WindowManagerPolicy.WindowState;
 
 import com.android.server.LocalServices;
+import com.android.server.policy.WindowManagerPolicy.WindowState;
 import com.android.server.statusbar.StatusBarManagerInternal;
 
 import java.io.PrintWriter;
diff --git a/com/android/server/policy/GlobalActions.java b/com/android/server/policy/GlobalActions.java
index 3707a5e..108b6b2 100644
--- a/com/android/server/policy/GlobalActions.java
+++ b/com/android/server/policy/GlobalActions.java
@@ -14,14 +14,14 @@
 
 package com.android.server.policy;
 
-import com.android.server.LocalServices;
-import com.android.server.statusbar.StatusBarManagerInternal;
-import com.android.server.statusbar.StatusBarManagerInternal.GlobalActionsListener;
-
 import android.content.Context;
 import android.os.Handler;
 import android.util.Slog;
-import android.view.WindowManagerPolicy.WindowManagerFuncs;
+
+import com.android.server.LocalServices;
+import com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs;
+import com.android.server.statusbar.StatusBarManagerInternal;
+import com.android.server.statusbar.StatusBarManagerInternal.GlobalActionsListener;
 
 class GlobalActions implements GlobalActionsListener {
 
diff --git a/com/android/server/policy/LegacyGlobalActions.java b/com/android/server/policy/LegacyGlobalActions.java
index 8eb6d06..96d062d 100644
--- a/com/android/server/policy/LegacyGlobalActions.java
+++ b/com/android/server/policy/LegacyGlobalActions.java
@@ -25,6 +25,7 @@
 import com.android.internal.telephony.TelephonyProperties;
 import com.android.internal.R;
 import com.android.internal.widget.LockPatternUtils;
+import com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs;
 
 import android.app.ActivityManager;
 import android.app.Dialog;
@@ -64,7 +65,6 @@
 import android.view.ViewGroup;
 import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
-import android.view.WindowManagerPolicy.WindowManagerFuncs;
 import android.view.accessibility.AccessibilityEvent;
 import android.widget.AdapterView;
 import android.widget.BaseAdapter;
@@ -919,7 +919,7 @@
         /**
          * @param enabledIconResId The icon for when this action is on.
          * @param disabledIconResid The icon for when this action is off.
-         * @param essage The general information message, e.g 'Silent Mode'
+         * @param message The general information message, e.g 'Silent Mode'
          * @param enabledStatusMessageResId The on status message, e.g 'sound disabled'
          * @param disabledStatusMessageResId The off status message, e.g. 'sound enabled'
          */
diff --git a/com/android/server/policy/PhoneWindowManager.java b/com/android/server/policy/PhoneWindowManager.java
index 9162a97..7415ec3 100644
--- a/com/android/server/policy/PhoneWindowManager.java
+++ b/com/android/server/policy/PhoneWindowManager.java
@@ -119,12 +119,13 @@
 import static android.view.WindowManager.TAKE_SCREENSHOT_SELECTED_REGION;
 import static android.view.WindowManagerGlobal.ADD_OKAY;
 import static android.view.WindowManagerGlobal.ADD_PERMISSION_DENIED;
-import static android.view.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVERED;
-import static android.view.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVER_ABSENT;
-import static android.view.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_UNCOVERED;
-import static android.view.WindowManagerPolicy.WindowManagerFuncs.LID_ABSENT;
-import static android.view.WindowManagerPolicy.WindowManagerFuncs.LID_CLOSED;
-import static android.view.WindowManagerPolicy.WindowManagerFuncs.LID_OPEN;
+
+import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVERED;
+import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVER_ABSENT;
+import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_UNCOVERED;
+import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.LID_ABSENT;
+import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.LID_CLOSED;
+import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.LID_OPEN;
 
 import android.annotation.Nullable;
 import android.app.ActivityManager;
@@ -210,7 +211,6 @@
 import android.util.SparseArray;
 import android.util.proto.ProtoOutputStream;
 import android.view.Display;
-import android.view.DisplayFrames;
 import android.view.Gravity;
 import android.view.HapticFeedbackConstants;
 import android.view.IApplicationToken;
@@ -230,9 +230,6 @@
 import android.view.WindowManager;
 import android.view.WindowManager.LayoutParams;
 import android.view.WindowManagerGlobal;
-import android.view.WindowManagerInternal;
-import android.view.WindowManagerInternal.AppTransitionListener;
-import android.view.WindowManagerPolicy;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
 import android.view.animation.Animation;
@@ -243,6 +240,7 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.policy.IKeyguardDismissCallback;
 import com.android.internal.policy.IShortcutService;
@@ -259,6 +257,9 @@
 import com.android.server.statusbar.StatusBarManagerInternal;
 import com.android.server.vr.VrManagerInternal;
 import com.android.server.wm.AppTransition;
+import com.android.server.wm.DisplayFrames;
+import com.android.server.wm.WindowManagerInternal;
+import com.android.server.wm.WindowManagerInternal.AppTransitionListener;
 
 import java.io.File;
 import java.io.FileReader;
@@ -304,6 +305,10 @@
     static final int LONG_PRESS_POWER_GLOBAL_ACTIONS = 1;
     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_POWER_GO_TO_VOICE_ASSIST = 4;
+
+    static final int VERY_LONG_PRESS_POWER_NOTHING = 0;
+    static final int VERY_LONG_PRESS_POWER_GLOBAL_ACTIONS = 1;
 
     static final int MULTI_PRESS_POWER_NOTHING = 0;
     static final int MULTI_PRESS_POWER_THEATER_MODE = 1;
@@ -569,6 +574,7 @@
     boolean mLidControlsSleep;
     int mShortPressOnPowerBehavior;
     int mLongPressOnPowerBehavior;
+    int mVeryLongPressOnPowerBehavior;
     int mDoublePressOnPowerBehavior;
     int mTriplePressOnPowerBehavior;
     int mLongPressOnBackBehavior;
@@ -586,6 +592,7 @@
     boolean mHasSoftInput = false;
     boolean mTranslucentDecorEnabled = true;
     boolean mUseTvRouting;
+    int mVeryLongPressTimeout;
 
     private boolean mHandleVolumeKeysInWM;
 
@@ -796,6 +803,7 @@
     private static final int MSG_HANDLE_ALL_APPS = 26;
     private static final int MSG_LAUNCH_ASSIST = 27;
     private static final int MSG_LAUNCH_ASSIST_LONG_PRESS = 28;
+    private static final int MSG_POWER_VERY_LONG_PRESS = 29;
 
     private static final int MSG_REQUEST_TRANSIENT_BARS_ARG_STATUS = 0;
     private static final int MSG_REQUEST_TRANSIENT_BARS_ARG_NAVIGATION = 1;
@@ -855,6 +863,9 @@
                 case MSG_POWER_LONG_PRESS:
                     powerLongPress();
                     break;
+                case MSG_POWER_VERY_LONG_PRESS:
+                    powerVeryLongPress();
+                    break;
                 case MSG_UPDATE_DREAMING_SLEEP_TOKEN:
                     updateDreamingSleepToken(msg.arg1 != 0);
                     break;
@@ -1043,7 +1054,8 @@
 
     private ImmersiveModeConfirmation mImmersiveModeConfirmation;
 
-    private SystemGesturesPointerEventListener mSystemGestures;
+    @VisibleForTesting
+    SystemGesturesPointerEventListener mSystemGestures;
 
     IStatusBarService getStatusBarService() {
         synchronized (mServiceAquireLock) {
@@ -1299,6 +1311,12 @@
                     msg.setAsynchronous(true);
                     mHandler.sendMessageDelayed(msg,
                             ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout());
+
+                    if (hasVeryLongPressOnPowerBehavior()) {
+                        Message longMsg = mHandler.obtainMessage(MSG_POWER_VERY_LONG_PRESS);
+                        longMsg.setAsynchronous(true);
+                        mHandler.sendMessageDelayed(longMsg, mVeryLongPressTimeout);
+                    }
                 }
             } else {
                 wakeUpFromPowerKey(event.getDownTime());
@@ -1308,6 +1326,13 @@
                     msg.setAsynchronous(true);
                     mHandler.sendMessageDelayed(msg,
                             ViewConfiguration.get(mContext).getDeviceGlobalActionKeyTimeout());
+
+                    if (hasVeryLongPressOnPowerBehavior()) {
+                        Message longMsg = mHandler.obtainMessage(MSG_POWER_VERY_LONG_PRESS);
+                        longMsg.setAsynchronous(true);
+                        mHandler.sendMessageDelayed(longMsg, mVeryLongPressTimeout);
+                    }
+
                     mBeganFromNonInteractive = true;
                 } else {
                     final int maxCount = getMaxMultiPressPowerCount();
@@ -1369,6 +1394,9 @@
             mPowerKeyHandled = true;
             mHandler.removeMessages(MSG_POWER_LONG_PRESS);
         }
+        if (hasVeryLongPressOnPowerBehavior()) {
+            mHandler.removeMessages(MSG_POWER_VERY_LONG_PRESS);
+        }
     }
 
     private void cancelPendingBackKeyAction() {
@@ -1516,6 +1544,29 @@
             sendCloseSystemWindows(SYSTEM_DIALOG_REASON_GLOBAL_ACTIONS);
             mWindowManagerFuncs.shutdown(behavior == LONG_PRESS_POWER_SHUT_OFF);
             break;
+        case LONG_PRESS_POWER_GO_TO_VOICE_ASSIST:
+            mPowerKeyHandled = true;
+            performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false);
+            final boolean keyguardActive = mKeyguardDelegate == null
+                    ? false
+                    : mKeyguardDelegate.isShowing();
+            if (!keyguardActive) {
+                Intent intent = new Intent(Intent.ACTION_VOICE_ASSIST);
+                startActivityAsUser(intent, UserHandle.CURRENT_OR_SELF);
+            }
+            break;
+        }
+    }
+
+    private void powerVeryLongPress() {
+        switch (mVeryLongPressOnPowerBehavior) {
+        case VERY_LONG_PRESS_POWER_NOTHING:
+            break;
+        case VERY_LONG_PRESS_POWER_GLOBAL_ACTIONS:
+            mPowerKeyHandled = true;
+            performHapticFeedbackLw(null, HapticFeedbackConstants.LONG_PRESS, false);
+            showGlobalActionsInternal();
+            break;
         }
     }
 
@@ -1574,6 +1625,10 @@
         return getResolvedLongPressOnPowerBehavior() != LONG_PRESS_POWER_NOTHING;
     }
 
+    private boolean hasVeryLongPressOnPowerBehavior() {
+        return mVeryLongPressOnPowerBehavior != VERY_LONG_PRESS_POWER_NOTHING;
+    }
+
     private boolean hasLongPressOnBackBehavior() {
         return mLongPressOnBackBehavior != LONG_PRESS_BACK_NOTHING;
     }
@@ -1979,12 +2034,16 @@
                 com.android.internal.R.integer.config_shortPressOnPowerBehavior);
         mLongPressOnPowerBehavior = mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_longPressOnPowerBehavior);
+        mVeryLongPressOnPowerBehavior = mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_veryLongPressOnPowerBehavior);
         mDoublePressOnPowerBehavior = mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_doublePressOnPowerBehavior);
         mTriplePressOnPowerBehavior = mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_triplePressOnPowerBehavior);
         mShortPressOnSleepBehavior = mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_shortPressOnSleepBehavior);
+        mVeryLongPressTimeout = mContext.getResources().getInteger(
+                com.android.internal.R.integer.config_veryLongPressTimeout);
 
         mUseTvRouting = AudioSystem.getPlatformType(mContext) == AudioSystem.PLATFORM_TELEVISION;
 
@@ -2607,17 +2666,21 @@
             // The status bar is the only window allowed to exhibit keyguard behavior.
             attrs.privateFlags &= ~WindowManager.LayoutParams.PRIVATE_FLAG_KEYGUARD;
         }
+    }
 
+    private int getImpliedSysUiFlagsForLayout(LayoutParams attrs) {
+        int impliedFlags = 0;
         if ((attrs.flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0) {
-            attrs.subtreeSystemUiVisibility |= View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
+            impliedFlags |= 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;
+            impliedFlags |= View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
         }
+        return impliedFlags;
     }
 
     void readLidState() {
@@ -2667,7 +2730,7 @@
     @Override
     public void onConfigurationChanged() {
         // TODO(multi-display): Define policy for secondary displays.
-        Context uiContext = ActivityThread.currentActivityThread().getSystemUiContext();
+        Context uiContext = getSystemUiContext();
         final Resources res = uiContext.getResources();
 
         mStatusBarHeight =
@@ -2708,6 +2771,11 @@
         }
     }
 
+    @VisibleForTesting
+    Context getSystemUiContext() {
+        return ActivityThread.currentActivityThread().getSystemUiContext();
+    }
+
     @Override
     public int getMaxWallpaperLayer() {
         return getWindowLayerFromTypeLw(TYPE_STATUS_BAR);
@@ -4768,7 +4836,8 @@
         final int fl = PolicyControl.getWindowFlags(win, attrs);
         final int pfl = attrs.privateFlags;
         final int sim = attrs.softInputMode;
-        final int sysUiFl = PolicyControl.getSystemUiVisibility(win, null);
+        final int requestedSysUiFl = PolicyControl.getSystemUiVisibility(win, null);
+        final int sysUiFl = requestedSysUiFl | getImpliedSysUiFlagsForLayout(attrs);
 
         final Rect pf = mTmpParentFrame;
         final Rect df = mTmpDisplayFrame;
@@ -8062,19 +8131,6 @@
     }
 
     @Override
-    public boolean canMagnifyWindow(int windowType) {
-        switch (windowType) {
-            case WindowManager.LayoutParams.TYPE_INPUT_METHOD:
-            case WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG:
-            case WindowManager.LayoutParams.TYPE_NAVIGATION_BAR:
-            case WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY: {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    @Override
     public boolean isTopLevelWindow(int windowType) {
         if (windowType >= WindowManager.LayoutParams.FIRST_SUB_WINDOW
                 && windowType <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
@@ -8206,6 +8262,9 @@
                 pw.print("mLongPressOnPowerBehavior=");
                 pw.println(longPressOnPowerBehaviorToString(mLongPressOnPowerBehavior));
         pw.print(prefix);
+                pw.print("mVeryLongPressOnPowerBehavior=");
+                pw.println(veryLongPressOnPowerBehaviorToString(mVeryLongPressOnPowerBehavior));
+        pw.print(prefix);
                 pw.print("mDoublePressOnPowerBehavior=");
                 pw.println(multiPressOnPowerBehaviorToString(mDoublePressOnPowerBehavior));
         pw.print(prefix);
@@ -8458,6 +8517,18 @@
                 return Integer.toString(behavior);
         }
     }
+
+    private static String veryLongPressOnPowerBehaviorToString(int behavior) {
+        switch (behavior) {
+            case VERY_LONG_PRESS_POWER_NOTHING:
+                return "VERY_LONG_PRESS_POWER_NOTHING";
+            case VERY_LONG_PRESS_POWER_GLOBAL_ACTIONS:
+                return "VERY_LONG_PRESS_POWER_GLOBAL_ACTIONS";
+            default:
+                return Integer.toString(behavior);
+        }
+    }
+
     private static String multiPressOnPowerBehaviorToString(int behavior) {
         switch (behavior) {
             case MULTI_PRESS_POWER_NOTHING:
diff --git a/com/android/server/policy/PolicyControl.java b/com/android/server/policy/PolicyControl.java
index dbafc42..3f26d86 100644
--- a/com/android/server/policy/PolicyControl.java
+++ b/com/android/server/policy/PolicyControl.java
@@ -25,7 +25,8 @@
 import android.view.View;
 import android.view.WindowManager;
 import android.view.WindowManager.LayoutParams;
-import android.view.WindowManagerPolicy.WindowState;
+
+import com.android.server.policy.WindowManagerPolicy.WindowState;
 
 import java.io.PrintWriter;
 import java.io.StringWriter;
@@ -36,7 +37,7 @@
  * This includes forcing immersive mode behavior for one or both system bars (based on a package
  * list) and permanently disabling immersive mode confirmations for specific packages.
  *
- * Control by setting {@link Settings.Global.POLICY_CONTROL} to one or more name-value pairs.
+ * Control by setting {@link Settings.Global#POLICY_CONTROL} to one or more name-value pairs.
  * e.g.
  *   to force immersive mode everywhere:
  *     "immersive.full=*"
diff --git a/com/android/server/policy/SplashScreenSurface.java b/com/android/server/policy/SplashScreenSurface.java
index 37d6c0b..b9202c3 100644
--- a/com/android/server/policy/SplashScreenSurface.java
+++ b/com/android/server/policy/SplashScreenSurface.java
@@ -23,11 +23,10 @@
 import android.util.Slog;
 import android.view.View;
 import android.view.WindowManager;
-import android.view.WindowManagerPolicy;
-import android.view.WindowManagerPolicy.StartingSurface;
 
 import com.android.internal.policy.DecorView;
 import com.android.internal.policy.PhoneWindow;
+import com.android.server.policy.WindowManagerPolicy.StartingSurface;
 
 /**
  * Holds the contents of a splash screen starting window, i.e. the {@link DecorView} of a
diff --git a/com/android/server/policy/StatusBarController.java b/com/android/server/policy/StatusBarController.java
index ecc88b5..af7e91c 100644
--- a/com/android/server/policy/StatusBarController.java
+++ b/com/android/server/policy/StatusBarController.java
@@ -18,7 +18,7 @@
 
 import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
 import static android.view.WindowManager.LayoutParams.MATCH_PARENT;
-import static android.view.WindowManagerInternal.AppTransitionListener;
+import static com.android.server.wm.WindowManagerInternal.AppTransitionListener;
 
 import android.app.StatusBarManager;
 import android.os.IBinder;
diff --git a/com/android/server/policy/SystemGesturesPointerEventListener.java b/com/android/server/policy/SystemGesturesPointerEventListener.java
index 598c58e..d3cc8ef 100644
--- a/com/android/server/policy/SystemGesturesPointerEventListener.java
+++ b/com/android/server/policy/SystemGesturesPointerEventListener.java
@@ -24,7 +24,7 @@
 import android.view.GestureDetector;
 import android.view.InputDevice;
 import android.view.MotionEvent;
-import android.view.WindowManagerPolicy.PointerEventListener;
+import android.view.WindowManagerPolicyConstants.PointerEventListener;
 import android.widget.OverScroller;
 
 /*
diff --git a/com/android/server/policy/WindowManagerPolicy.java b/com/android/server/policy/WindowManagerPolicy.java
new file mode 100644
index 0000000..5f067d4
--- /dev/null
+++ b/com/android/server/policy/WindowManagerPolicy.java
@@ -0,0 +1,1690 @@
+/*
+ * 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.policy;
+
+import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW;
+import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
+import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL;
+import static android.view.WindowManager.LayoutParams.TYPE_BOOT_PROGRESS;
+import static android.view.WindowManager.LayoutParams.TYPE_DISPLAY_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
+import static android.view.WindowManager.LayoutParams.TYPE_DRAG;
+import static android.view.WindowManager.LayoutParams.TYPE_DREAM;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_CONSUMER;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
+import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
+import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
+import static android.view.WindowManager.LayoutParams.TYPE_PHONE;
+import static android.view.WindowManager.LayoutParams.TYPE_POINTER;
+import static android.view.WindowManager.LayoutParams.TYPE_PRESENTATION;
+import static android.view.WindowManager.LayoutParams.TYPE_PRIORITY_PHONE;
+import static android.view.WindowManager.LayoutParams.TYPE_PRIVATE_PRESENTATION;
+import static android.view.WindowManager.LayoutParams.TYPE_QS_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_SCREENSHOT;
+import static android.view.WindowManager.LayoutParams.TYPE_SEARCH_BAR;
+import static android.view.WindowManager.LayoutParams.TYPE_SECURE_SYSTEM_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
+import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
+import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
+import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION_STARTING;
+import static android.view.WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+import static android.view.WindowManager.LayoutParams.isSystemAlertWindowType;
+
+import android.Manifest;
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.res.CompatibilityInfo;
+import android.content.res.Configuration;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.util.Slog;
+import android.util.proto.ProtoOutputStream;
+import android.view.Display;
+import android.view.IApplicationToken;
+import android.view.IWindowManager;
+import android.view.InputEventReceiver;
+import android.view.KeyEvent;
+import android.view.Surface;
+import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
+import android.view.WindowManagerPolicyConstants;
+import android.view.animation.Animation;
+
+import com.android.internal.policy.IKeyguardDismissCallback;
+import com.android.internal.policy.IShortcutService;
+import com.android.server.wm.DisplayFrames;
+
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * This interface supplies all UI-specific behavior of the window manager.  An
+ * instance of it is created by the window manager when it starts up, and allows
+ * customization of window layering, special window types, key dispatching, and
+ * layout.
+ *
+ * <p>Because this provides deep interaction with the system window manager,
+ * specific methods on this interface can be called from a variety of contexts
+ * with various restrictions on what they can do.  These are encoded through
+ * a suffixes at the end of a method encoding the thread the method is called
+ * from and any locks that are held when it is being called; if no suffix
+ * is attached to a method, then it is not called with any locks and may be
+ * called from the main window manager thread or another thread calling into
+ * the window manager.
+ *
+ * <p>The current suffixes are:
+ *
+ * <dl>
+ * <dt> Ti <dd> Called from the input thread.  This is the thread that
+ * collects pending input events and dispatches them to the appropriate window.
+ * It may block waiting for events to be processed, so that the input stream is
+ * properly serialized.
+ * <dt> Tq <dd> Called from the low-level input queue thread.  This is the
+ * thread that reads events out of the raw input devices and places them
+ * into the global input queue that is read by the <var>Ti</var> thread.
+ * This thread should not block for a long period of time on anything but the
+ * key driver.
+ * <dt> Lw <dd> Called with the main window manager lock held.  Because the
+ * window manager is a very low-level system service, there are few other
+ * system services you can call with this lock held.  It is explicitly okay to
+ * make calls into the package manager and power manager; it is explicitly not
+ * okay to make calls into the activity manager or most other services.  Note that
+ * {@link android.content.Context#checkPermission(String, int, int)} and
+ * variations require calling into the activity manager.
+ * <dt> Li <dd> Called with the input thread lock held.  This lock can be
+ * acquired by the window manager while it holds the window lock, so this is
+ * even more restrictive than <var>Lw</var>.
+ * </dl>
+ */
+public interface WindowManagerPolicy extends WindowManagerPolicyConstants {
+    // Navigation bar position values
+    int NAV_BAR_LEFT = 1 << 0;
+    int NAV_BAR_RIGHT = 1 << 1;
+    int NAV_BAR_BOTTOM = 1 << 2;
+
+    /**
+     * Pass this event to the user / app.  To be returned from
+     * {@link #interceptKeyBeforeQueueing}.
+     */
+    int ACTION_PASS_TO_USER = 0x00000001;
+    /** Layout state may have changed (so another layout will be performed) */
+    int FINISH_LAYOUT_REDO_LAYOUT = 0x0001;
+    /** Configuration state may have changed */
+    int FINISH_LAYOUT_REDO_CONFIG = 0x0002;
+    /** Wallpaper may need to move */
+    int FINISH_LAYOUT_REDO_WALLPAPER = 0x0004;
+    /** Need to recompute animations */
+    int FINISH_LAYOUT_REDO_ANIM = 0x0008;
+
+    /**
+     * Register shortcuts for window manager to dispatch.
+     * Shortcut code is packed as (metaState << Integer.SIZE) | keyCode
+     * @hide
+     */
+    void registerShortcutKey(long shortcutCode, IShortcutService shortcutKeyReceiver)
+            throws RemoteException;
+
+    /**
+     * Called when the Keyguard occluded state changed.
+     * @param occluded Whether Keyguard is currently occluded or not.
+     */
+    void onKeyguardOccludedChangedLw(boolean occluded);
+
+    /**
+     * Interface to the Window Manager state associated with a particular
+     * window.  You can hold on to an instance of this interface from the call
+     * to prepareAddWindow() until removeWindow().
+     */
+    public interface WindowState {
+        /**
+         * Return the uid of the app that owns this window.
+         */
+        int getOwningUid();
+
+        /**
+         * Return the package name of the app that owns this window.
+         */
+        String getOwningPackage();
+
+        /**
+         * Perform standard frame computation.  The result can be obtained with
+         * getFrame() if so desired.  Must be called with the window manager
+         * lock held.
+         *
+         * @param parentFrame The frame of the parent container this window
+         * is in, used for computing its basic position.
+         * @param displayFrame The frame of the overall display in which this
+         * window can appear, used for constraining the overall dimensions
+         * of the window.
+         * @param overlayFrame The frame within the display that is inside
+         * of the overlay region.
+         * @param contentFrame The frame within the display in which we would
+         * like active content to appear.  This will cause windows behind to
+         * be resized to match the given content frame.
+         * @param visibleFrame The frame within the display that the window
+         * is actually visible, used for computing its visible insets to be
+         * given to windows behind.
+         * This can be used as a hint for scrolling (avoiding resizing)
+         * the window to make certain that parts of its content
+         * are visible.
+         * @param decorFrame The decor frame specified by policy specific to this window,
+         * to use for proper cropping during animation.
+         * @param stableFrame The frame around which stable system decoration is positioned.
+         * @param outsetFrame The frame that includes areas that aren't part of the surface but we
+         * want to treat them as such.
+         */
+        public void computeFrameLw(Rect parentFrame, Rect displayFrame,
+                Rect overlayFrame, Rect contentFrame, Rect visibleFrame, Rect decorFrame,
+                Rect stableFrame, @Nullable Rect outsetFrame);
+
+        /**
+         * Retrieve the current frame of the window that has been assigned by
+         * the window manager.  Must be called with the window manager lock held.
+         *
+         * @return Rect The rectangle holding the window frame.
+         */
+        public Rect getFrameLw();
+
+        /**
+         * Retrieve the current position of the window that is actually shown.
+         * Must be called with the window manager lock held.
+         *
+         * @return Point The point holding the shown window position.
+         */
+        public Point getShownPositionLw();
+
+        /**
+         * Retrieve the frame of the display that this window was last
+         * laid out in.  Must be called with the
+         * window manager lock held.
+         *
+         * @return Rect The rectangle holding the display frame.
+         */
+        public Rect getDisplayFrameLw();
+
+        /**
+         * Retrieve the frame of the area inside the overscan region of the
+         * display that this window was last laid out in.  Must be called with the
+         * window manager lock held.
+         *
+         * @return Rect The rectangle holding the display overscan frame.
+         */
+        public Rect getOverscanFrameLw();
+
+        /**
+         * Retrieve the frame of the content area that this window was last
+         * laid out in.  This is the area in which the content of the window
+         * should be placed.  It will be smaller than the display frame to
+         * account for screen decorations such as a status bar or soft
+         * keyboard.  Must be called with the
+         * window manager lock held.
+         *
+         * @return Rect The rectangle holding the content frame.
+         */
+        public Rect getContentFrameLw();
+
+        /**
+         * Retrieve the frame of the visible area that this window was last
+         * laid out in.  This is the area of the screen in which the window
+         * will actually be fully visible.  It will be smaller than the
+         * content frame to account for transient UI elements blocking it
+         * such as an input method's candidates UI.  Must be called with the
+         * window manager lock held.
+         *
+         * @return Rect The rectangle holding the visible frame.
+         */
+        public Rect getVisibleFrameLw();
+
+        /**
+         * Returns true if this window is waiting to receive its given
+         * internal insets from the client app, and so should not impact the
+         * layout of other windows.
+         */
+        public boolean getGivenInsetsPendingLw();
+
+        /**
+         * Retrieve the insets given by this window's client for the content
+         * area of windows behind it.  Must be called with the
+         * window manager lock held.
+         *
+         * @return Rect The left, top, right, and bottom insets, relative
+         * to the window's frame, of the actual contents.
+         */
+        public Rect getGivenContentInsetsLw();
+
+        /**
+         * Retrieve the insets given by this window's client for the visible
+         * area of windows behind it.  Must be called with the
+         * window manager lock held.
+         *
+         * @return Rect The left, top, right, and bottom insets, relative
+         * to the window's frame, of the actual visible area.
+         */
+        public Rect getGivenVisibleInsetsLw();
+
+        /**
+         * Retrieve the current LayoutParams of the window.
+         *
+         * @return WindowManager.LayoutParams The window's internal LayoutParams
+         *         instance.
+         */
+        public WindowManager.LayoutParams getAttrs();
+
+        /**
+         * Return whether this window needs the menu key shown.  Must be called
+         * with window lock held, because it may need to traverse down through
+         * window list to determine the result.
+         * @param bottom The bottom-most window to consider when determining this.
+         */
+        public boolean getNeedsMenuLw(WindowState bottom);
+
+        /**
+         * Retrieve the current system UI visibility flags associated with
+         * this window.
+         */
+        public int getSystemUiVisibility();
+
+        /**
+         * Get the layer at which this window's surface will be Z-ordered.
+         */
+        public int getSurfaceLayer();
+
+        /**
+         * Retrieve the type of the top-level window.
+         *
+         * @return the base type of the parent window if attached or its own type otherwise
+         */
+        public int getBaseType();
+
+        /**
+         * Return the token for the application (actually activity) that owns
+         * this window.  May return null for system windows.
+         *
+         * @return An IApplicationToken identifying the owning activity.
+         */
+        public IApplicationToken getAppToken();
+
+        /**
+         * Return true if this window is participating in voice interaction.
+         */
+        public boolean isVoiceInteraction();
+
+        /**
+         * Return true if, at any point, the application token associated with
+         * this window has actually displayed any windows.  This is most useful
+         * with the "starting up" window to determine if any windows were
+         * displayed when it is closed.
+         *
+         * @return Returns true if one or more windows have been displayed,
+         *         else false.
+         */
+        public boolean hasAppShownWindows();
+
+        /**
+         * Is this window visible?  It is not visible if there is no
+         * surface, or we are in the process of running an exit animation
+         * that will remove the surface.
+         */
+        boolean isVisibleLw();
+
+        /**
+         * Is this window currently visible to the user on-screen?  It is
+         * displayed either if it is visible or it is currently running an
+         * animation before no longer being visible.  Must be called with the
+         * window manager lock held.
+         */
+        boolean isDisplayedLw();
+
+        /**
+         * Return true if this window (or a window it is attached to, but not
+         * considering its app token) is currently animating.
+         */
+        boolean isAnimatingLw();
+
+        /**
+         * @return Whether the window can affect SystemUI flags, meaning that SystemUI (system bars,
+         *         for example) will be  affected by the flags specified in this window. This is the
+         *         case when the surface is on screen but not exiting.
+         */
+        boolean canAffectSystemUiFlags();
+
+        /**
+         * Is this window considered to be gone for purposes of layout?
+         */
+        boolean isGoneForLayoutLw();
+
+        /**
+         * Returns true if the window has a surface that it has drawn a
+         * complete UI in to. Note that this is different from {@link #hasDrawnLw()}
+         * in that it also returns true if the window is READY_TO_SHOW, but was not yet
+         * promoted to HAS_DRAWN.
+         */
+        boolean isDrawnLw();
+
+        /**
+         * Returns true if this window has been shown on screen at some time in
+         * the past.  Must be called with the window manager lock held.
+         */
+        public boolean hasDrawnLw();
+
+        /**
+         * Can be called by the policy to force a window to be hidden,
+         * regardless of whether the client or window manager would like
+         * it shown.  Must be called with the window manager lock held.
+         * Returns true if {@link #showLw} was last called for the window.
+         */
+        public boolean hideLw(boolean doAnimation);
+
+        /**
+         * Can be called to undo the effect of {@link #hideLw}, allowing a
+         * window to be shown as long as the window manager and client would
+         * also like it to be shown.  Must be called with the window manager
+         * lock held.
+         * Returns true if {@link #hideLw} was last called for the window.
+         */
+        public boolean showLw(boolean doAnimation);
+
+        /**
+         * Check whether the process hosting this window is currently alive.
+         */
+        public boolean isAlive();
+
+        /**
+         * Check if window is on {@link Display#DEFAULT_DISPLAY}.
+         * @return true if window is on default display.
+         */
+        public boolean isDefaultDisplay();
+
+        /**
+         * Check whether the window is currently dimming.
+         */
+        public boolean isDimming();
+
+        /** @return the current windowing mode of this window. */
+        int getWindowingMode();
+
+        /**
+         * Returns true if the window is current in multi-windowing mode. i.e. it shares the
+         * screen with other application windows.
+         */
+        public boolean isInMultiWindowMode();
+
+        public int getRotationAnimationHint();
+
+        public boolean isInputMethodWindow();
+
+        public int getDisplayId();
+
+        /**
+         * Returns true if the window owner can add internal system windows.
+         * That is, they have {@link Manifest.permission#INTERNAL_SYSTEM_WINDOW}.
+         */
+        default boolean canAddInternalSystemWindow() {
+            return false;
+        }
+
+        /**
+         * Returns true if the window owner has the permission to acquire a sleep token when it's
+         * visible. That is, they have the permission {@link Manifest.permission#DEVICE_POWER}.
+         */
+        boolean canAcquireSleepToken();
+    }
+
+    /**
+     * Representation of a input consumer that the policy has added to the
+     * window manager to consume input events going to windows below it.
+     */
+    public interface InputConsumer {
+        /**
+         * Remove the input consumer from the window manager.
+         */
+        void dismiss();
+    }
+
+    /**
+     * Holds the contents of a starting window. {@link #addSplashScreen} needs to wrap the
+     * contents of the starting window into an class implementing this interface, which then will be
+     * held by WM and released with {@link #remove} when no longer needed.
+     */
+    interface StartingSurface {
+
+        /**
+         * Removes the starting window surface. Do not hold the window manager lock when calling
+         * this method!
+         */
+        void remove();
+    }
+
+    /**
+     * Interface for calling back in to the window manager that is private
+     * between it and the policy.
+     */
+    public interface WindowManagerFuncs {
+        public static final int LID_ABSENT = -1;
+        public static final int LID_CLOSED = 0;
+        public static final int LID_OPEN = 1;
+
+        public static final int CAMERA_LENS_COVER_ABSENT = -1;
+        public static final int CAMERA_LENS_UNCOVERED = 0;
+        public static final int CAMERA_LENS_COVERED = 1;
+
+        /**
+         * Ask the window manager to re-evaluate the system UI flags.
+         */
+        public void reevaluateStatusBarVisibility();
+
+        /**
+         * Add a input consumer which will consume all input events going to any window below it.
+         */
+        public InputConsumer createInputConsumer(Looper looper, String name,
+                InputEventReceiver.Factory inputEventReceiverFactory);
+
+        /**
+         * Returns a code that describes the current state of the lid switch.
+         */
+        public int getLidState();
+
+        /**
+         * Lock the device now.
+         */
+        public void lockDeviceNow();
+
+        /**
+         * Returns a code that descripbes whether the camera lens is covered or not.
+         */
+        public int getCameraLensCoverState();
+
+        /**
+         * Switch the input method, to be precise, input method subtype.
+         *
+         * @param forwardDirection {@code true} to rotate in a forward direction.
+         */
+        public void switchInputMethod(boolean forwardDirection);
+
+        public void shutdown(boolean confirm);
+        public void reboot(boolean confirm);
+        public void rebootSafeMode(boolean confirm);
+
+        /**
+         * Return the window manager lock needed to correctly call "Lw" methods.
+         */
+        public Object getWindowManagerLock();
+
+        /** Register a system listener for touch events */
+        void registerPointerEventListener(PointerEventListener listener);
+
+        /** Unregister a system listener for touch events */
+        void unregisterPointerEventListener(PointerEventListener listener);
+
+        /**
+         * @return The content insets of the docked divider window.
+         */
+        int getDockedDividerInsetsLw();
+
+        /**
+         * Retrieves the {@param outBounds} from the stack matching the {@param windowingMode} and
+         * {@param activityType}.
+         */
+        void getStackBounds(int windowingMode, int activityType, Rect outBounds);
+
+        /**
+         * Notifies window manager that {@link #isShowingDreamLw} has changed.
+         */
+        void notifyShowingDreamChanged();
+
+        /**
+         * @return The currently active input method window.
+         */
+        WindowState getInputMethodWindowLw();
+
+        /**
+         * Notifies window manager that {@link #isKeyguardTrustedLw} has changed.
+         */
+        void notifyKeyguardTrustedChanged();
+
+        /**
+         * Notifies the window manager that screen is being turned off.
+         *
+         * @param listener callback to call when display can be turned off
+         */
+        void screenTurningOff(ScreenOffListener listener);
+
+        /**
+         * Convert the lid state to a human readable format.
+         */
+        static String lidStateToString(int lid) {
+            switch (lid) {
+                case LID_ABSENT:
+                    return "LID_ABSENT";
+                case LID_CLOSED:
+                    return "LID_CLOSED";
+                case LID_OPEN:
+                    return "LID_OPEN";
+                default:
+                    return Integer.toString(lid);
+            }
+        }
+
+        /**
+         * Convert the camera lens state to a human readable format.
+         */
+        static String cameraLensStateToString(int lens) {
+            switch (lens) {
+                case CAMERA_LENS_COVER_ABSENT:
+                    return "CAMERA_LENS_COVER_ABSENT";
+                case CAMERA_LENS_UNCOVERED:
+                    return "CAMERA_LENS_UNCOVERED";
+                case CAMERA_LENS_COVERED:
+                    return "CAMERA_LENS_COVERED";
+                default:
+                    return Integer.toString(lens);
+            }
+        }
+    }
+
+    /** Window has been added to the screen. */
+    public static final int TRANSIT_ENTER = 1;
+    /** Window has been removed from the screen. */
+    public static final int TRANSIT_EXIT = 2;
+    /** Window has been made visible. */
+    public static final int TRANSIT_SHOW = 3;
+    /** Window has been made invisible.
+     * TODO: Consider removal as this is unused. */
+    public static final int TRANSIT_HIDE = 4;
+    /** The "application starting" preview window is no longer needed, and will
+     * animate away to show the real window. */
+    public static final int TRANSIT_PREVIEW_DONE = 5;
+
+    // NOTE: screen off reasons are in order of significance, with more
+    // important ones lower than less important ones.
+
+    /** @hide */
+    @IntDef({USER_ROTATION_FREE, USER_ROTATION_LOCKED})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface UserRotationMode {}
+
+    /** When not otherwise specified by the activity's screenOrientation, rotation should be
+     * determined by the system (that is, using sensors). */
+    public final int USER_ROTATION_FREE = 0;
+    /** When not otherwise specified by the activity's screenOrientation, rotation is set by
+     * the user. */
+    public final int USER_ROTATION_LOCKED = 1;
+
+    /**
+     * Perform initialization of the policy.
+     *
+     * @param context The system context we are running in.
+     */
+    public void init(Context context, IWindowManager windowManager,
+            WindowManagerFuncs windowManagerFuncs);
+
+    /**
+     * @return true if com.android.internal.R.bool#config_forceDefaultOrientation is true.
+     */
+    public boolean isDefaultOrientationForced();
+
+    /**
+     * Called by window manager once it has the initial, default native
+     * display dimensions.
+     */
+    public void setInitialDisplaySize(Display display, int width, int height, int density);
+
+    /**
+     * Check permissions when adding a window.
+     *
+     * @param attrs The window's LayoutParams.
+     * @param outAppOp First element will be filled with the app op corresponding to
+     *                 this window, or OP_NONE.
+     *
+     * @return {@link WindowManagerGlobal#ADD_OKAY} if the add can proceed;
+     *      else an error code, usually
+     *      {@link WindowManagerGlobal#ADD_PERMISSION_DENIED}, to abort the add.
+     */
+    public int checkAddPermission(WindowManager.LayoutParams attrs, int[] outAppOp);
+
+    /**
+     * Check permissions when adding a window.
+     *
+     * @param attrs The window's LayoutParams.
+     *
+     * @return True if the window may only be shown to the current user, false if the window can
+     * be shown on all users' windows.
+     */
+    public boolean checkShowToOwnerOnly(WindowManager.LayoutParams attrs);
+
+    /**
+     * Sanitize the layout parameters coming from a client.  Allows the policy
+     * to do things like ensure that windows of a specific type can't take
+     * input focus.
+     *
+     * @param attrs The window layout parameters to be modified.  These values
+     * are modified in-place.
+     */
+    public void adjustWindowParamsLw(WindowState win, WindowManager.LayoutParams attrs,
+            boolean hasStatusBarServicePermission);
+
+    /**
+     * After the window manager has computed the current configuration based
+     * on its knowledge of the display and input devices, it gives the policy
+     * a chance to adjust the information contained in it.  If you want to
+     * leave it as-is, simply do nothing.
+     *
+     * <p>This method may be called by any thread in the window manager, but
+     * no internal locks in the window manager will be held.
+     *
+     * @param config The Configuration being computed, for you to change as
+     * desired.
+     * @param keyboardPresence Flags that indicate whether internal or external
+     * keyboards are present.
+     * @param navigationPresence Flags that indicate whether internal or external
+     * navigation devices are present.
+     */
+    public void adjustConfigurationLw(Configuration config, int keyboardPresence,
+            int navigationPresence);
+
+    /**
+     * Returns the layer assignment for the window state. Allows you to control how different
+     * kinds of windows are ordered on-screen.
+     *
+     * @param win The window state
+     * @return int An arbitrary integer used to order windows, with lower numbers below higher ones.
+     */
+    default int getWindowLayerLw(WindowState win) {
+        return getWindowLayerFromTypeLw(win.getBaseType(), win.canAddInternalSystemWindow());
+    }
+
+    /**
+     * Returns the layer assignment for the window type. Allows you to control how different
+     * kinds of windows are ordered on-screen.
+     *
+     * @param type The type of window being assigned.
+     * @return int An arbitrary integer used to order windows, with lower numbers below higher ones.
+     */
+    default int getWindowLayerFromTypeLw(int type) {
+        if (isSystemAlertWindowType(type)) {
+            throw new IllegalArgumentException("Use getWindowLayerFromTypeLw() or"
+                    + " getWindowLayerLw() for alert window types");
+        }
+        return getWindowLayerFromTypeLw(type, false /* canAddInternalSystemWindow */);
+    }
+
+    /**
+     * Returns the layer assignment for the window type. Allows you to control how different
+     * kinds of windows are ordered on-screen.
+     *
+     * @param type The type of window being assigned.
+     * @param canAddInternalSystemWindow If the owner window associated with the type we are
+     *        evaluating can add internal system windows. I.e they have
+     *        {@link Manifest.permission#INTERNAL_SYSTEM_WINDOW}. If true, alert window
+     *        types {@link android.view.WindowManager.LayoutParams#isSystemAlertWindowType(int)}
+     *        can be assigned layers greater than the layer for
+     *        {@link android.view.WindowManager.LayoutParams#TYPE_APPLICATION_OVERLAY} Else, their
+     *        layers would be lesser.
+     * @return int An arbitrary integer used to order windows, with lower numbers below higher ones.
+     */
+    default int getWindowLayerFromTypeLw(int type, boolean canAddInternalSystemWindow) {
+        if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) {
+            return APPLICATION_LAYER;
+        }
+
+        switch (type) {
+            case TYPE_WALLPAPER:
+                // wallpaper is at the bottom, though the window manager may move it.
+                return  1;
+            case TYPE_PRESENTATION:
+            case TYPE_PRIVATE_PRESENTATION:
+                return  APPLICATION_LAYER;
+            case TYPE_DOCK_DIVIDER:
+                return  APPLICATION_LAYER;
+            case TYPE_QS_DIALOG:
+                return  APPLICATION_LAYER;
+            case TYPE_PHONE:
+                return  3;
+            case TYPE_SEARCH_BAR:
+            case TYPE_VOICE_INTERACTION_STARTING:
+                return  4;
+            case TYPE_VOICE_INTERACTION:
+                // voice interaction layer is almost immediately above apps.
+                return  5;
+            case TYPE_INPUT_CONSUMER:
+                return  6;
+            case TYPE_SYSTEM_DIALOG:
+                return  7;
+            case TYPE_TOAST:
+                // toasts and the plugged-in battery thing
+                return  8;
+            case TYPE_PRIORITY_PHONE:
+                // SIM errors and unlock.  Not sure if this really should be in a high layer.
+                return  9;
+            case TYPE_SYSTEM_ALERT:
+                // like the ANR / app crashed dialogs
+                return  canAddInternalSystemWindow ? 11 : 10;
+            case TYPE_APPLICATION_OVERLAY:
+                return  12;
+            case TYPE_DREAM:
+                // used for Dreams (screensavers with TYPE_DREAM windows)
+                return  13;
+            case TYPE_INPUT_METHOD:
+                // on-screen keyboards and other such input method user interfaces go here.
+                return  14;
+            case TYPE_INPUT_METHOD_DIALOG:
+                // on-screen keyboards and other such input method user interfaces go here.
+                return  15;
+            case TYPE_STATUS_BAR:
+                return  17;
+            case TYPE_STATUS_BAR_PANEL:
+                return  18;
+            case TYPE_STATUS_BAR_SUB_PANEL:
+                return  19;
+            case TYPE_KEYGUARD_DIALOG:
+                return  20;
+            case TYPE_VOLUME_OVERLAY:
+                // the on-screen volume indicator and controller shown when the user
+                // changes the device volume
+                return  21;
+            case TYPE_SYSTEM_OVERLAY:
+                // the on-screen volume indicator and controller shown when the user
+                // changes the device volume
+                return  canAddInternalSystemWindow ? 22 : 11;
+            case TYPE_NAVIGATION_BAR:
+                // the navigation bar, if available, shows atop most things
+                return  23;
+            case TYPE_NAVIGATION_BAR_PANEL:
+                // some panels (e.g. search) need to show on top of the navigation bar
+                return  24;
+            case TYPE_SCREENSHOT:
+                // screenshot selection layer shouldn't go above system error, but it should cover
+                // navigation bars at the very least.
+                return  25;
+            case TYPE_SYSTEM_ERROR:
+                // system-level error dialogs
+                return  canAddInternalSystemWindow ? 26 : 10;
+            case TYPE_MAGNIFICATION_OVERLAY:
+                // used to highlight the magnified portion of a display
+                return  27;
+            case TYPE_DISPLAY_OVERLAY:
+                // used to simulate secondary display devices
+                return  28;
+            case TYPE_DRAG:
+                // the drag layer: input for drag-and-drop is associated with this window,
+                // which sits above all other focusable windows
+                return  29;
+            case TYPE_ACCESSIBILITY_OVERLAY:
+                // overlay put by accessibility services to intercept user interaction
+                return  30;
+            case TYPE_SECURE_SYSTEM_OVERLAY:
+                return  31;
+            case TYPE_BOOT_PROGRESS:
+                return  32;
+            case TYPE_POINTER:
+                // the (mouse) pointer layer
+                return  33;
+            default:
+                Slog.e("WindowManager", "Unknown window type: " + type);
+                return APPLICATION_LAYER;
+        }
+    }
+
+    /**
+     * Return how to Z-order sub-windows in relation to the window they are attached to.
+     * Return positive to have them ordered in front, negative for behind.
+     *
+     * @param type The sub-window type code.
+     *
+     * @return int Layer in relation to the attached window, where positive is
+     *         above and negative is below.
+     */
+    default int getSubWindowLayerFromTypeLw(int type) {
+        switch (type) {
+            case TYPE_APPLICATION_PANEL:
+            case TYPE_APPLICATION_ATTACHED_DIALOG:
+                return APPLICATION_PANEL_SUBLAYER;
+            case TYPE_APPLICATION_MEDIA:
+                return APPLICATION_MEDIA_SUBLAYER;
+            case TYPE_APPLICATION_MEDIA_OVERLAY:
+                return APPLICATION_MEDIA_OVERLAY_SUBLAYER;
+            case TYPE_APPLICATION_SUB_PANEL:
+                return APPLICATION_SUB_PANEL_SUBLAYER;
+            case TYPE_APPLICATION_ABOVE_SUB_PANEL:
+                return APPLICATION_ABOVE_SUB_PANEL_SUBLAYER;
+        }
+        Slog.e("WindowManager", "Unknown sub-window type: " + type);
+        return 0;
+    }
+
+    /**
+     * Get the highest layer (actually one more than) that the wallpaper is
+     * allowed to be in.
+     */
+    public int getMaxWallpaperLayer();
+
+    /**
+     * Return the display width available after excluding any screen
+     * decorations that could never be removed in Honeycomb. That is, system bar or
+     * button bar.
+     */
+    public int getNonDecorDisplayWidth(int fullWidth, int fullHeight, int rotation,
+            int uiMode, int displayId);
+
+    /**
+     * Return the display height available after excluding any screen
+     * decorations that could never be removed in Honeycomb. That is, system bar or
+     * button bar.
+     */
+    public int getNonDecorDisplayHeight(int fullWidth, int fullHeight, int rotation,
+            int uiMode, int displayId);
+
+    /**
+     * Return the available screen width that we should report for the
+     * configuration.  This must be no larger than
+     * {@link #getNonDecorDisplayWidth(int, int, int, int int, int)}; it may be smaller than
+     * that to account for more transient decoration like a status bar.
+     */
+    public int getConfigDisplayWidth(int fullWidth, int fullHeight, int rotation,
+            int uiMode, int displayId);
+
+    /**
+     * Return the available screen height that we should report for the
+     * configuration.  This must be no larger than
+     * {@link #getNonDecorDisplayHeight(int, int, int, int, int)}; it may be smaller than
+     * that to account for more transient decoration like a status bar.
+     */
+    public int getConfigDisplayHeight(int fullWidth, int fullHeight, int rotation,
+            int uiMode, int displayId);
+
+    /**
+     * Return whether the given window can become the Keyguard window. Typically returns true for
+     * the StatusBar.
+     */
+    public boolean isKeyguardHostWindow(WindowManager.LayoutParams attrs);
+
+    /**
+     * @return whether {@param win} can be hidden by Keyguard
+     */
+    public boolean canBeHiddenByKeyguardLw(WindowState win);
+
+    /**
+     * Called when the system would like to show a UI to indicate that an
+     * application is starting.  You can use this to add a
+     * APPLICATION_STARTING_TYPE window with the given appToken to the window
+     * manager (using the normal window manager APIs) that will be shown until
+     * the application displays its own window.  This is called without the
+     * window manager locked so that you can call back into it.
+     *
+     * @param appToken Token of the application being started.
+     * @param packageName The name of the application package being started.
+     * @param theme Resource defining the application's overall visual theme.
+     * @param nonLocalizedLabel The default title label of the application if
+     *        no data is found in the resource.
+     * @param labelRes The resource ID the application would like to use as its name.
+     * @param icon The resource ID the application would like to use as its icon.
+     * @param windowFlags Window layout flags.
+     * @param overrideConfig override configuration to consider when generating
+     *        context to for resources.
+     * @param displayId Id of the display to show the splash screen at.
+     *
+     * @return The starting surface.
+     *
+     */
+    public StartingSurface addSplashScreen(IBinder appToken, String packageName, int theme,
+            CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes, int icon,
+            int logo, int windowFlags, Configuration overrideConfig, int displayId);
+
+    /**
+     * Prepare for a window being added to the window manager.  You can throw an
+     * exception here to prevent the window being added, or do whatever setup
+     * you need to keep track of the window.
+     *
+     * @param win The window being added.
+     * @param attrs The window's LayoutParams.
+     *
+     * @return {@link WindowManagerGlobal#ADD_OKAY} if the add can proceed, else an
+     *         error code to abort the add.
+     */
+    public int prepareAddWindowLw(WindowState win,
+            WindowManager.LayoutParams attrs);
+
+    /**
+     * Called when a window is being removed from a window manager.  Must not
+     * throw an exception -- clean up as much as possible.
+     *
+     * @param win The window being removed.
+     */
+    public void removeWindowLw(WindowState win);
+
+    /**
+     * Control the animation to run when a window's state changes.  Return a
+     * non-0 number to force the animation to a specific resource ID, or 0
+     * to use the default animation.
+     *
+     * @param win The window that is changing.
+     * @param transit What is happening to the window: {@link #TRANSIT_ENTER},
+     *                {@link #TRANSIT_EXIT}, {@link #TRANSIT_SHOW}, or
+     *                {@link #TRANSIT_HIDE}.
+     *
+     * @return Resource ID of the actual animation to use, or 0 for none.
+     */
+    public int selectAnimationLw(WindowState win, int transit);
+
+    /**
+     * Determine the animation to run for a rotation transition based on the
+     * top fullscreen windows {@link WindowManager.LayoutParams#rotationAnimation}
+     * and whether it is currently fullscreen and frontmost.
+     *
+     * @param anim The exiting animation resource id is stored in anim[0], the
+     * entering animation resource id is stored in anim[1].
+     */
+    public void selectRotationAnimationLw(int anim[]);
+
+    /**
+     * Validate whether the current top fullscreen has specified the same
+     * {@link WindowManager.LayoutParams#rotationAnimation} value as that
+     * being passed in from the previous top fullscreen window.
+     *
+     * @param exitAnimId exiting resource id from the previous window.
+     * @param enterAnimId entering resource id from the previous window.
+     * @param forceDefault For rotation animations only, if true ignore the
+     * animation values and just return false.
+     * @return true if the previous values are still valid, false if they
+     * should be replaced with the default.
+     */
+    public boolean validateRotationAnimationLw(int exitAnimId, int enterAnimId,
+            boolean forceDefault);
+
+    /**
+     * Create and return an animation to re-display a window that was force hidden by Keyguard.
+     */
+    public Animation createHiddenByKeyguardExit(boolean onWallpaper,
+            boolean goingToNotificationShade);
+
+    /**
+     * Create and return an animation to let the wallpaper disappear after being shown behind
+     * Keyguard.
+     */
+    public Animation createKeyguardWallpaperExit(boolean goingToNotificationShade);
+
+    /**
+     * Called from the input reader thread before a key is enqueued.
+     *
+     * <p>There are some actions that need to be handled here because they
+     * affect the power state of the device, for example, the power keys.
+     * Generally, it's best to keep as little as possible in the queue thread
+     * because it's the most fragile.
+     * @param event The key event.
+     * @param policyFlags The policy flags associated with the key.
+     *
+     * @return Actions flags: may be {@link #ACTION_PASS_TO_USER}.
+     */
+    public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags);
+
+    /**
+     * Called from the input reader thread before a motion is enqueued when the device is in a
+     * non-interactive state.
+     *
+     * <p>There are some actions that need to be handled here because they
+     * affect the power state of the device, for example, waking on motions.
+     * Generally, it's best to keep as little as possible in the queue thread
+     * because it's the most fragile.
+     * @param policyFlags The policy flags associated with the motion.
+     *
+     * @return Actions flags: may be {@link #ACTION_PASS_TO_USER}.
+     */
+    public int interceptMotionBeforeQueueingNonInteractive(long whenNanos, int policyFlags);
+
+    /**
+     * Called from the input dispatcher thread before a key is dispatched to a window.
+     *
+     * <p>Allows you to define
+     * behavior for keys that can not be overridden by applications.
+     * This method is called from the input thread, with no locks held.
+     *
+     * @param win The window that currently has focus.  This is where the key
+     *            event will normally go.
+     * @param event The key event.
+     * @param policyFlags The policy flags associated with the key.
+     * @return 0 if the key should be dispatched immediately, -1 if the key should
+     * not be dispatched ever, or a positive value indicating the number of
+     * milliseconds by which the key dispatch should be delayed before trying
+     * again.
+     */
+    public long interceptKeyBeforeDispatching(WindowState win, KeyEvent event, int policyFlags);
+
+    /**
+     * Called from the input dispatcher thread when an application did not handle
+     * a key that was dispatched to it.
+     *
+     * <p>Allows you to define default global behavior for keys that were not handled
+     * by applications.  This method is called from the input thread, with no locks held.
+     *
+     * @param win The window that currently has focus.  This is where the key
+     *            event will normally go.
+     * @param event The key event.
+     * @param policyFlags The policy flags associated with the key.
+     * @return Returns an alternate key event to redispatch as a fallback, or null to give up.
+     * The caller is responsible for recycling the key event.
+     */
+    public KeyEvent dispatchUnhandledKey(WindowState win, KeyEvent event, int policyFlags);
+
+    /**
+     * Called when layout of the windows is about to start.
+     *
+     * @param displayFrames frames of the display we are doing layout on.
+     * @param uiMode The current uiMode in configuration.
+     */
+    default void beginLayoutLw(DisplayFrames displayFrames, int uiMode) {}
+
+    /**
+     * Returns the bottom-most layer of the system decor, above which no policy decor should
+     * be applied.
+     */
+    public int getSystemDecorLayerLw();
+
+    /**
+     * Called for each window attached to the window manager as layout is proceeding. The
+     * implementation of this function must take care of setting the window's frame, either here or
+     * in finishLayout().
+     *
+     * @param win The window being positioned.
+     * @param attached For sub-windows, the window it is attached to; this
+     *                 window will already have had layoutWindow() called on it
+     *                 so you can use its Rect.  Otherwise null.
+     * @param displayFrames The display frames.
+     */
+    default void layoutWindowLw(
+            WindowState win, WindowState attached, DisplayFrames displayFrames) {}
+
+
+    /**
+     * Return the insets for the areas covered by system windows. These values are computed on the
+     * most recent layout, so they are not guaranteed to be correct.
+     *
+     * @param attrs The LayoutParams of the window.
+     * @param taskBounds The bounds of the task this window is on or {@code null} if no task is
+     *                   associated with the window.
+     * @param displayFrames display frames.
+     * @param outContentInsets The areas covered by system windows, expressed as positive insets.
+     * @param outStableInsets The areas covered by stable system windows irrespective of their
+     *                        current visibility. Expressed as positive insets.
+     * @param outOutsets The areas that are not real display, but we would like to treat as such.
+     * @return Whether to always consume the navigation bar.
+     *         See {@link #isNavBarForcedShownLw(WindowState)}.
+     */
+    default boolean getInsetHintLw(WindowManager.LayoutParams attrs, Rect taskBounds,
+            DisplayFrames displayFrames, Rect outContentInsets, Rect outStableInsets,
+            Rect outOutsets) {
+        return false;
+    }
+
+    /**
+     * Called following layout of all windows before each window has policy applied.
+     *
+     * @param displayWidth The current full width of the screen.
+     * @param displayHeight The current full height of the screen.
+     */
+    public void beginPostLayoutPolicyLw(int displayWidth, int displayHeight);
+
+    /**
+     * Called following layout of all window to apply policy to each window.
+     *
+     * @param win The window being positioned.
+     * @param attrs The LayoutParams of the window.
+     * @param attached For sub-windows, the window it is attached to. Otherwise null.
+     */
+    public void applyPostLayoutPolicyLw(WindowState win,
+            WindowManager.LayoutParams attrs, WindowState attached, WindowState imeTarget);
+
+    /**
+     * Called following layout of all windows and after policy has been applied
+     * to each window. If in this function you do
+     * something that may have modified the animation state of another window,
+     * be sure to return non-zero in order to perform another pass through layout.
+     *
+     * @return Return any bit set of {@link #FINISH_LAYOUT_REDO_LAYOUT},
+     * {@link #FINISH_LAYOUT_REDO_CONFIG}, {@link #FINISH_LAYOUT_REDO_WALLPAPER},
+     * or {@link #FINISH_LAYOUT_REDO_ANIM}.
+     */
+    public int finishPostLayoutPolicyLw();
+
+    /**
+     * Return true if it is okay to perform animations for an app transition
+     * that is about to occur.  You may return false for this if, for example,
+     * the lock screen is currently displayed so the switch should happen
+     * immediately.
+     */
+    public boolean allowAppAnimationsLw();
+
+
+    /**
+     * A new window has been focused.
+     */
+    public int focusChangedLw(WindowState lastFocus, WindowState newFocus);
+
+    /**
+     * Called when the device has started waking up.
+     */
+    public void startedWakingUp();
+
+    /**
+     * Called when the device has finished waking up.
+     */
+    public void finishedWakingUp();
+
+    /**
+     * Called when the device has started going to sleep.
+     *
+     * @param why {@link #OFF_BECAUSE_OF_USER}, {@link #OFF_BECAUSE_OF_ADMIN},
+     * or {@link #OFF_BECAUSE_OF_TIMEOUT}.
+     */
+    public void startedGoingToSleep(int why);
+
+    /**
+     * Called when the device has finished going to sleep.
+     *
+     * @param why {@link #OFF_BECAUSE_OF_USER}, {@link #OFF_BECAUSE_OF_ADMIN},
+     * or {@link #OFF_BECAUSE_OF_TIMEOUT}.
+     */
+    public void finishedGoingToSleep(int why);
+
+    /**
+     * Called when the device is about to turn on the screen to show content.
+     * When waking up, this method will be called once after the call to wakingUp().
+     * When dozing, the method will be called sometime after the call to goingToSleep() and
+     * may be called repeatedly in the case where the screen is pulsing on and off.
+     *
+     * Must call back on the listener to tell it when the higher-level system
+     * is ready for the screen to go on (i.e. the lock screen is shown).
+     */
+    public void screenTurningOn(ScreenOnListener screenOnListener);
+
+    /**
+     * Called when the device has actually turned on the screen, i.e. the display power state has
+     * been set to ON and the screen is unblocked.
+     */
+    public void screenTurnedOn();
+
+    /**
+     * Called when the display would like to be turned off. This gives policy a chance to do some
+     * things before the display power state is actually changed to off.
+     *
+     * @param screenOffListener Must be called to tell that the display power state can actually be
+     *                          changed now after policy has done its work.
+     */
+    public void screenTurningOff(ScreenOffListener screenOffListener);
+
+    /**
+     * Called when the device has turned the screen off.
+     */
+    public void screenTurnedOff();
+
+    public interface ScreenOnListener {
+        void onScreenOn();
+    }
+
+    /**
+     * See {@link #screenTurnedOff}
+     */
+    public interface ScreenOffListener {
+        void onScreenOff();
+    }
+
+    /**
+     * Return whether the default display is on and not blocked by a black surface.
+     */
+    public boolean isScreenOn();
+
+    /**
+     * @return whether the device is currently allowed to animate.
+     *
+     * Note: this can be true even if it is not appropriate to animate for reasons that are outside
+     *       of the policy's authority.
+     */
+    boolean okToAnimate();
+
+    /**
+     * Tell the policy that the lid switch has changed state.
+     * @param whenNanos The time when the change occurred in uptime nanoseconds.
+     * @param lidOpen True if the lid is now open.
+     */
+    public void notifyLidSwitchChanged(long whenNanos, boolean lidOpen);
+
+    /**
+     * Tell the policy that the camera lens has been covered or uncovered.
+     * @param whenNanos The time when the change occurred in uptime nanoseconds.
+     * @param lensCovered True if the lens is covered.
+     */
+    public void notifyCameraLensCoverSwitchChanged(long whenNanos, boolean lensCovered);
+
+    /**
+     * Tell the policy if anyone is requesting that keyguard not come on.
+     *
+     * @param enabled Whether keyguard can be on or not.  does not actually
+     * turn it on, unless it was previously disabled with this function.
+     *
+     * @see android.app.KeyguardManager.KeyguardLock#disableKeyguard()
+     * @see android.app.KeyguardManager.KeyguardLock#reenableKeyguard()
+     */
+    @SuppressWarnings("javadoc")
+    public void enableKeyguard(boolean enabled);
+
+    /**
+     * Callback used by {@link #exitKeyguardSecurely}
+     */
+    interface OnKeyguardExitResult {
+        void onKeyguardExitResult(boolean success);
+    }
+
+    /**
+     * Tell the policy if anyone is requesting the keyguard to exit securely
+     * (this would be called after the keyguard was disabled)
+     * @param callback Callback to send the result back.
+     * @see android.app.KeyguardManager#exitKeyguardSecurely(android.app.KeyguardManager.OnKeyguardExitResult)
+     */
+    @SuppressWarnings("javadoc")
+    void exitKeyguardSecurely(OnKeyguardExitResult callback);
+
+    /**
+     * isKeyguardLocked
+     *
+     * Return whether the keyguard is currently locked.
+     *
+     * @return true if in keyguard is locked.
+     */
+    public boolean isKeyguardLocked();
+
+    /**
+     * isKeyguardSecure
+     *
+     * Return whether the keyguard requires a password to unlock.
+     * @param userId
+     *
+     * @return true if in keyguard is secure.
+     */
+    public boolean isKeyguardSecure(int userId);
+
+    /**
+     * Return whether the keyguard is currently occluded.
+     *
+     * @return true if in keyguard is occluded, false otherwise
+     */
+    public boolean isKeyguardOccluded();
+
+    /**
+     * @return true if in keyguard is on and not occluded.
+     */
+    public boolean isKeyguardShowingAndNotOccluded();
+
+    /**
+     * @return whether Keyguard is in trusted state and can be dismissed without credentials
+     */
+    public boolean isKeyguardTrustedLw();
+
+    /**
+     * inKeyguardRestrictedKeyInputMode
+     *
+     * if keyguard screen is showing or in restricted key input mode (i.e. in
+     * keyguard password emergency screen). When in such mode, certain keys,
+     * such as the Home key and the right soft keys, don't work.
+     *
+     * @return true if in keyguard restricted input mode.
+     */
+    public boolean inKeyguardRestrictedKeyInputMode();
+
+    /**
+     * Ask the policy to dismiss the keyguard, if it is currently shown.
+     *
+     * @param callback Callback to be informed about the result.
+     */
+    public void dismissKeyguardLw(@Nullable IKeyguardDismissCallback callback);
+
+    /**
+     * Ask the policy whether the Keyguard has drawn. If the Keyguard is disabled, this method
+     * returns true as soon as we know that Keyguard is disabled.
+     *
+     * @return true if the keyguard has drawn.
+     */
+    public boolean isKeyguardDrawnLw();
+
+    public boolean isShowingDreamLw();
+
+    /**
+     * Given an orientation constant, returns the appropriate surface rotation,
+     * taking into account sensors, docking mode, rotation lock, and other factors.
+     *
+     * @param orientation An orientation constant, such as
+     * {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_LANDSCAPE}.
+     * @param lastRotation The most recently used rotation.
+     * @return The surface rotation to use.
+     */
+    public int rotationForOrientationLw(@ActivityInfo.ScreenOrientation int orientation,
+            int lastRotation);
+
+    /**
+     * Given an orientation constant and a rotation, returns true if the rotation
+     * has compatible metrics to the requested orientation.  For example, if
+     * the application requested landscape and got seascape, then the rotation
+     * has compatible metrics; if the application requested portrait and got landscape,
+     * then the rotation has incompatible metrics; if the application did not specify
+     * a preference, then anything goes.
+     *
+     * @param orientation An orientation constant, such as
+     * {@link android.content.pm.ActivityInfo#SCREEN_ORIENTATION_LANDSCAPE}.
+     * @param rotation The rotation to check.
+     * @return True if the rotation is compatible with the requested orientation.
+     */
+    public boolean rotationHasCompatibleMetricsLw(@ActivityInfo.ScreenOrientation int orientation,
+            int rotation);
+
+    /**
+     * Called by the window manager when the rotation changes.
+     *
+     * @param rotation The new rotation.
+     */
+    public void setRotationLw(int rotation);
+
+    /**
+     * Called when the system is mostly done booting to set whether
+     * the system should go into safe mode.
+     */
+    public void setSafeMode(boolean safeMode);
+
+    /**
+     * Called when the system is mostly done booting.
+     */
+    public void systemReady();
+
+    /**
+     * Called when the system is done booting to the point where the
+     * user can start interacting with it.
+     */
+    public void systemBooted();
+
+    /**
+     * Show boot time message to the user.
+     */
+    public void showBootMessage(final CharSequence msg, final boolean always);
+
+    /**
+     * Hide the UI for showing boot messages, never to be displayed again.
+     */
+    public void hideBootMessages();
+
+    /**
+     * Called when userActivity is signalled in the power manager.
+     * This is safe to call from any thread, with any window manager locks held or not.
+     */
+    public void userActivity();
+
+    /**
+     * Called when we have finished booting and can now display the home
+     * screen to the user.  This will happen after systemReady(), and at
+     * this point the display is active.
+     */
+    public void enableScreenAfterBoot();
+
+    public void setCurrentOrientationLw(@ActivityInfo.ScreenOrientation int newOrientation);
+
+    /**
+     * Call from application to perform haptic feedback on its window.
+     */
+    public boolean performHapticFeedbackLw(WindowState win, int effectId, boolean always);
+
+    /**
+     * Called when we have started keeping the screen on because a window
+     * requesting this has become visible.
+     */
+    public void keepScreenOnStartedLw();
+
+    /**
+     * Called when we have stopped keeping the screen on because the last window
+     * requesting this is no longer visible.
+     */
+    public void keepScreenOnStoppedLw();
+
+    /**
+     * Gets the current user rotation mode.
+     *
+     * @return The rotation mode.
+     *
+     * @see #USER_ROTATION_LOCKED
+     * @see #USER_ROTATION_FREE
+     */
+    @UserRotationMode
+    public int getUserRotationMode();
+
+    /**
+     * Inform the policy that the user has chosen a preferred orientation ("rotation lock").
+     *
+     * @param mode One of {@link #USER_ROTATION_LOCKED} or {@link #USER_ROTATION_FREE}.
+     * @param rotation One of {@link Surface#ROTATION_0}, {@link Surface#ROTATION_90},
+     *                 {@link Surface#ROTATION_180}, {@link Surface#ROTATION_270}.
+     */
+    public void setUserRotationMode(@UserRotationMode int mode, @Surface.Rotation int rotation);
+
+    /**
+     * Called when a new system UI visibility is being reported, allowing
+     * the policy to adjust what is actually reported.
+     * @param visibility The raw visibility reported by the status bar.
+     * @return The new desired visibility.
+     */
+    public int adjustSystemUiVisibilityLw(int visibility);
+
+    /**
+     * Called by System UI to notify of changes to the visibility of Recents.
+     */
+    public void setRecentsVisibilityLw(boolean visible);
+
+    /**
+     * Called by System UI to notify of changes to the visibility of PIP.
+     */
+    void setPipVisibilityLw(boolean visible);
+
+    /**
+     * Specifies whether there is an on-screen navigation bar separate from the status bar.
+     */
+    public boolean hasNavigationBar();
+
+    /**
+     * Lock the device now.
+     */
+    public void lockNow(Bundle options);
+
+    /**
+     * Set the last used input method window state. This state is used to make IME transition
+     * smooth.
+     * @hide
+     */
+    public void setLastInputMethodWindowLw(WindowState ime, WindowState target);
+
+    /**
+     * An internal callback (from InputMethodManagerService) to notify a state change regarding
+     * whether the back key should dismiss the software keyboard (IME) or not.
+     *
+     * @param newValue {@code true} if the software keyboard is shown and the back key is expected
+     *                 to dismiss the software keyboard.
+     * @hide
+     */
+    default void setDismissImeOnBackKeyPressed(boolean newValue) {
+        // Default implementation does nothing.
+    }
+
+    /**
+     * Show the recents task list app.
+     * @hide
+     */
+    public void showRecentApps(boolean fromHome);
+
+    /**
+     * Show the global actions dialog.
+     * @hide
+     */
+    public void showGlobalActions();
+
+    /**
+     * Called when the current user changes. Guaranteed to be called before the broadcast
+     * of the new user id is made to all listeners.
+     *
+     * @param newUserId The id of the incoming user.
+     */
+    public void setCurrentUserLw(int newUserId);
+
+    /**
+     * For a given user-switch operation, this will be called once with switching=true before the
+     * user-switch and once with switching=false afterwards (or if the user-switch was cancelled).
+     * This gives the policy a chance to alter its behavior for the duration of a user-switch.
+     *
+     * @param switching true if a user-switch is in progress
+     */
+    void setSwitchingUser(boolean switching);
+
+    /**
+     * Print the WindowManagerPolicy's state into the given stream.
+     *
+     * @param prefix Text to print at the front of each line.
+     * @param writer The PrintWriter to which you should dump your state.  This will be
+     * closed for you after you return.
+     * @param args additional arguments to the dump request.
+     */
+    public void dump(String prefix, PrintWriter writer, String[] args);
+
+    /**
+     * Write the WindowManagerPolicy's state into the protocol buffer.
+     * The message is described in {@link com.android.server.wm.proto.WindowManagerPolicyProto}
+     *
+     * @param proto The protocol buffer output stream to write to.
+     */
+    void writeToProto(ProtoOutputStream proto, long fieldId);
+
+    /**
+     * Returns whether a given window type is considered a top level one.
+     * A top level window does not have a container, i.e. attached window,
+     * or if it has a container it is laid out as a top-level window, not
+     * as a child of its container.
+     *
+     * @param windowType The window type.
+     * @return True if the window is a top level one.
+     */
+    public boolean isTopLevelWindow(int windowType);
+
+    /**
+     * Notifies the keyguard to start fading out.
+     *
+     * @param startTime the start time of the animation in uptime milliseconds
+     * @param fadeoutDuration the duration of the exit animation, in milliseconds
+     */
+    public void startKeyguardExitAnimation(long startTime, long fadeoutDuration);
+
+    /**
+     * Calculates the stable insets without running a layout.
+     *
+     * @param displayRotation the current display rotation
+     * @param displayWidth the current display width
+     * @param displayHeight the current display height
+     * @param outInsets the insets to return
+     */
+    public void getStableInsetsLw(int displayRotation, int displayWidth, int displayHeight,
+            Rect outInsets);
+
+
+    /**
+     * @return true if the navigation bar is forced to stay visible
+     */
+    public boolean isNavBarForcedShownLw(WindowState win);
+
+    /**
+     * @return The side of the screen where navigation bar is positioned.
+     * @see #NAV_BAR_LEFT
+     * @see #NAV_BAR_RIGHT
+     * @see #NAV_BAR_BOTTOM
+     */
+    int getNavBarPosition();
+
+    /**
+     * Calculates the insets for the areas that could never be removed in Honeycomb, i.e. system
+     * bar or button bar. See {@link #getNonDecorDisplayWidth}.
+     *
+     * @param displayRotation the current display rotation
+     * @param displayWidth the current display width
+     * @param displayHeight the current display height
+     * @param outInsets the insets to return
+     */
+    public void getNonDecorInsetsLw(int displayRotation, int displayWidth, int displayHeight,
+            Rect outInsets);
+
+    /**
+     * @return True if a specified {@param dockSide} is allowed on the current device, or false
+     *         otherwise. It is guaranteed that at least one dock side for a particular orientation
+     *         is allowed, so for example, if DOCKED_RIGHT is not allowed, DOCKED_LEFT is allowed.
+     */
+    public boolean isDockSideAllowed(int dockSide);
+
+    /**
+     * Called when the configuration has changed, and it's safe to load new values from resources.
+     */
+    public void onConfigurationChanged();
+
+    public boolean shouldRotateSeamlessly(int oldRotation, int newRotation);
+
+    /**
+     * Called when System UI has been started.
+     */
+    void onSystemUiStarted();
+
+    /**
+     * Checks whether the policy is ready for dismissing the boot animation and completing the boot.
+     *
+     * @return true if ready; false otherwise.
+     */
+    boolean canDismissBootAnimation();
+
+    /**
+     * Convert the user rotation mode to a human readable format.
+     */
+    static String userRotationModeToString(int mode) {
+        switch(mode) {
+            case USER_ROTATION_FREE:
+                return "USER_ROTATION_FREE";
+            case USER_ROTATION_LOCKED:
+                return "USER_ROTATION_LOCKED";
+            default:
+                return Integer.toString(mode);
+        }
+    }
+}
diff --git a/com/android/server/policy/keyguard/KeyguardServiceDelegate.java b/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
index 70cd54f..58002bc 100644
--- a/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
+++ b/com/android/server/policy/keyguard/KeyguardServiceDelegate.java
@@ -15,14 +15,14 @@
 import android.os.UserHandle;
 import android.util.Log;
 import android.util.Slog;
-import android.view.WindowManagerPolicy;
-import android.view.WindowManagerPolicy.OnKeyguardExitResult;
+import android.view.WindowManagerPolicyConstants;
 
 import com.android.internal.policy.IKeyguardDismissCallback;
 import com.android.internal.policy.IKeyguardDrawnCallback;
 import com.android.internal.policy.IKeyguardExitCallback;
 import com.android.internal.policy.IKeyguardService;
 import com.android.server.UiThread;
+import com.android.server.policy.WindowManagerPolicy.OnKeyguardExitResult;
 
 import java.io.PrintWriter;
 
@@ -419,7 +419,7 @@
         pw.println(prefix + "deviceHasKeyguard=" + mKeyguardState.deviceHasKeyguard);
         pw.println(prefix + "enabled=" + mKeyguardState.enabled);
         pw.println(prefix + "offReason=" +
-                WindowManagerPolicy.offReasonToString(mKeyguardState.offReason));
+                WindowManagerPolicyConstants.offReasonToString(mKeyguardState.offReason));
         pw.println(prefix + "currentUser=" + mKeyguardState.currentUser);
         pw.println(prefix + "bootCompleted=" + mKeyguardState.bootCompleted);
         pw.println(prefix + "screenState=" + screenStateToString(mKeyguardState.screenState));
diff --git a/com/android/server/power/BatterySaverPolicy.java b/com/android/server/power/BatterySaverPolicy.java
index 15121b8..7c234f9 100644
--- a/com/android/server/power/BatterySaverPolicy.java
+++ b/com/android/server/power/BatterySaverPolicy.java
@@ -29,21 +29,26 @@
 import android.util.KeyValueListParser;
 import android.util.Slog;
 
+import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.R;
+import com.android.server.power.batterysaver.CpuFrequencies;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Class to decide whether to turn on battery saver mode for specific service
  *
- * Test: atest BatterySaverPolicyTest
+ * Test:
+ atest ${ANDROID_BUILD_TOP}/frameworks/base/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java
  */
 public class BatterySaverPolicy extends ContentObserver {
     private static final String TAG = "BatterySaverPolicy";
 
+    public static final boolean DEBUG = false; // DO NOT SUBMIT WITH TRUE.
+
     // Value of batterySaverGpsMode such that GPS isn't affected by battery saver mode.
     public static final int GPS_MODE_NO_CHANGE = 0;
     // Value of batterySaverGpsMode such that GPS is disabled when battery saver mode
@@ -59,19 +64,27 @@
     private static final String KEY_FIREWALL_DISABLED = "firewall_disabled";
     private static final String KEY_ADJUST_BRIGHTNESS_DISABLED = "adjust_brightness_disabled";
     private static final String KEY_DATASAVER_DISABLED = "datasaver_disabled";
+    private static final String KEY_LAUNCH_BOOST_DISABLED = "launch_boost_disabled";
     private static final String KEY_ADJUST_BRIGHTNESS_FACTOR = "adjust_brightness_factor";
     private static final String KEY_FULLBACKUP_DEFERRED = "fullbackup_deferred";
     private static final String KEY_KEYVALUE_DEFERRED = "keyvaluebackup_deferred";
-    private static final String KEY_FORCE_ALL_APPS_STANDBY_JOBS = "force_all_apps_standby_jobs";
-    private static final String KEY_FORCE_ALL_APPS_STANDBY_ALARMS = "force_all_apps_standby_alarms";
+    private static final String KEY_FORCE_ALL_APPS_STANDBY = "force_all_apps_standby";
+    private static final String KEY_FORCE_BACKGROUND_CHECK = "force_background_check";
     private static final String KEY_OPTIONAL_SENSORS_DISABLED = "optional_sensors_disabled";
 
-    private static final String KEY_SCREEN_ON_FILE_PREFIX = "file-on:";
-    private static final String KEY_SCREEN_OFF_FILE_PREFIX = "file-off:";
+    private static final String KEY_CPU_FREQ_INTERACTIVE = "cpufreq-i";
+    private static final String KEY_CPU_FREQ_NONINTERACTIVE = "cpufreq-n";
 
-    private static String mSettings;
-    private static String mDeviceSpecificSettings;
-    private static String mDeviceSpecificSettingsSource; // For dump() only.
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
+    private String mSettings;
+
+    @GuardedBy("mLock")
+    private String mDeviceSpecificSettings;
+
+    @GuardedBy("mLock")
+    private String mDeviceSpecificSettingsSource; // For dump() only.
 
     /**
      * {@code true} if vibration is disabled in battery saver mode.
@@ -79,6 +92,7 @@
      * @see Settings.Global#BATTERY_SAVER_CONSTANTS
      * @see #KEY_VIBRATION_DISABLED
      */
+    @GuardedBy("mLock")
     private boolean mVibrationDisabled;
 
     /**
@@ -87,6 +101,7 @@
      * @see Settings.Global#BATTERY_SAVER_CONSTANTS
      * @see #KEY_ANIMATION_DISABLED
      */
+    @GuardedBy("mLock")
     private boolean mAnimationDisabled;
 
     /**
@@ -96,6 +111,7 @@
      * @see Settings.Global#BATTERY_SAVER_CONSTANTS
      * @see #KEY_SOUNDTRIGGER_DISABLED
      */
+    @GuardedBy("mLock")
     private boolean mSoundTriggerDisabled;
 
     /**
@@ -104,6 +120,7 @@
      * @see Settings.Global#BATTERY_SAVER_CONSTANTS
      * @see #KEY_FULLBACKUP_DEFERRED
      */
+    @GuardedBy("mLock")
     private boolean mFullBackupDeferred;
 
     /**
@@ -112,6 +129,7 @@
      * @see Settings.Global#BATTERY_SAVER_CONSTANTS
      * @see #KEY_KEYVALUE_DEFERRED
      */
+    @GuardedBy("mLock")
     private boolean mKeyValueBackupDeferred;
 
     /**
@@ -120,6 +138,7 @@
      * @see Settings.Global#BATTERY_SAVER_CONSTANTS
      * @see #KEY_FIREWALL_DISABLED
      */
+    @GuardedBy("mLock")
     private boolean mFireWallDisabled;
 
     /**
@@ -128,6 +147,7 @@
      * @see Settings.Global#BATTERY_SAVER_CONSTANTS
      * @see #KEY_ADJUST_BRIGHTNESS_DISABLED
      */
+    @GuardedBy("mLock")
     private boolean mAdjustBrightnessDisabled;
 
     /**
@@ -136,14 +156,22 @@
      * @see Settings.Global#BATTERY_SAVER_CONSTANTS
      * @see #KEY_DATASAVER_DISABLED
      */
+    @GuardedBy("mLock")
     private boolean mDataSaverDisabled;
 
     /**
+     * {@code true} if launch boost should be disabled on battery saver.
+     */
+    @GuardedBy("mLock")
+    private boolean mLaunchBoostDisabled;
+
+    /**
      * This is the flag to decide the gps mode in battery saver mode.
      *
      * @see Settings.Global#BATTERY_SAVER_CONSTANTS
      * @see #KEY_GPS_MODE
      */
+    @GuardedBy("mLock")
     private int mGpsMode;
 
     /**
@@ -153,25 +181,27 @@
      * @see Settings.Global#BATTERY_SAVER_CONSTANTS
      * @see #KEY_ADJUST_BRIGHTNESS_FACTOR
      */
+    @GuardedBy("mLock")
     private float mAdjustBrightnessFactor;
 
     /**
-     * Whether to put all apps in the stand-by mode or not for job scheduler.
+     * Whether to put all apps in the stand-by mode.
      */
-    private boolean mForceAllAppsStandbyJobs;
+    @GuardedBy("mLock")
+    private boolean mForceAllAppsStandby;
 
     /**
-     * Whether to put all apps in the stand-by mode or not for alarms.
+     * Whether to put all apps in the stand-by mode.
      */
-    private boolean mForceAllAppsStandbyAlarms;
+    @GuardedBy("mLock")
+    private boolean mForceBackgroundCheck;
 
     /**
      * Weather to show non-essential sensors (e.g. edge sensors) or not.
      */
+    @GuardedBy("mLock")
     private boolean mOptionalSensorsDisabled;
 
-    private final Object mLock = new Object();
-
     @GuardedBy("mLock")
     private Context mContext;
 
@@ -179,25 +209,25 @@
     private ContentResolver mContentResolver;
 
     @GuardedBy("mLock")
-    private final ArrayList<BatterySaverPolicyListener> mListeners = new ArrayList<>();
+    private final List<BatterySaverPolicyListener> mListeners = new ArrayList<>();
 
     /**
      * List of [Filename -> content] that should be written when battery saver is activated
-     * and the screen is on.
+     * and the device is interactive.
      *
      * We use this to change the max CPU frequencies.
      */
     @GuardedBy("mLock")
-    private ArrayMap<String, String> mScreenOnFiles;
+    private ArrayMap<String, String> mFilesForInteractive;
 
     /**
      * List of [Filename -> content] that should be written when battery saver is activated
-     * and the screen is off.
+     * and the device is non-interactive.
      *
      * We use this to change the max CPU frequencies.
      */
     @GuardedBy("mLock")
-    private ArrayMap<String, String> mScreenOffFiles;
+    private ArrayMap<String, String> mFilesForNoninteractive;
 
     public interface BatterySaverPolicyListener {
         void onBatterySaverPolicyChanged(BatterySaverPolicy policy);
@@ -228,7 +258,11 @@
 
     @VisibleForTesting
     String getGlobalSetting(String key) {
-        return Settings.Global.getString(mContentResolver, key);
+        final ContentResolver cr;
+        synchronized (mLock) {
+            cr = mContentResolver;
+        }
+        return Settings.Global.getString(cr, key);
     }
 
     @VisibleForTesting
@@ -236,11 +270,6 @@
         return R.string.config_batterySaverDeviceSpecificConfig;
     }
 
-    @VisibleForTesting
-    void onChangeForTest() {
-        onChange(true, null);
-    }
-
     @Override
     public void onChange(boolean selfChange, Uri uri) {
         final BatterySaverPolicyListener[] listeners;
@@ -279,6 +308,11 @@
         mSettings = setting;
         mDeviceSpecificSettings = deviceSpecificSetting;
 
+        if (DEBUG) {
+            Slog.i(TAG, "mSettings=" + mSettings);
+            Slog.i(TAG, "mDeviceSpecificSettings=" + mDeviceSpecificSettings);
+        }
+
         final KeyValueListParser parser = new KeyValueListParser(',');
 
         // Non-device-specific parameters.
@@ -297,9 +331,9 @@
         mAdjustBrightnessDisabled = parser.getBoolean(KEY_ADJUST_BRIGHTNESS_DISABLED, false);
         mAdjustBrightnessFactor = parser.getFloat(KEY_ADJUST_BRIGHTNESS_FACTOR, 0.5f);
         mDataSaverDisabled = parser.getBoolean(KEY_DATASAVER_DISABLED, true);
-        mForceAllAppsStandbyJobs = parser.getBoolean(KEY_FORCE_ALL_APPS_STANDBY_JOBS, true);
-        mForceAllAppsStandbyAlarms =
-                parser.getBoolean(KEY_FORCE_ALL_APPS_STANDBY_ALARMS, true);
+        mLaunchBoostDisabled = parser.getBoolean(KEY_LAUNCH_BOOST_DISABLED, true);
+        mForceAllAppsStandby = parser.getBoolean(KEY_FORCE_ALL_APPS_STANDBY, true);
+        mForceBackgroundCheck = parser.getBoolean(KEY_FORCE_BACKGROUND_CHECK, true);
         mOptionalSensorsDisabled = parser.getBoolean(KEY_OPTIONAL_SENSORS_DISABLED, true);
 
         // Get default value from Settings.Secure
@@ -315,29 +349,11 @@
                     + deviceSpecificSetting);
         }
 
-        mScreenOnFiles = collectParams(parser, KEY_SCREEN_ON_FILE_PREFIX);
-        mScreenOffFiles = collectParams(parser, KEY_SCREEN_OFF_FILE_PREFIX);
-    }
+        mFilesForInteractive = (new CpuFrequencies()).parseString(
+                parser.getString(KEY_CPU_FREQ_INTERACTIVE, "")).toSysFileMap();
 
-    private static ArrayMap<String, String> collectParams(
-            KeyValueListParser parser, String prefix) {
-        final ArrayMap<String, String> ret = new ArrayMap<>();
-
-        for (int i = parser.size() - 1; i >= 0; i--) {
-            final String key = parser.keyAt(i);
-            if (!key.startsWith(prefix)) {
-                continue;
-            }
-            final String path = key.substring(prefix.length());
-
-            if (!(path.startsWith("/sys/") || path.startsWith("/proc"))) {
-                Slog.wtf(TAG, "Invalid path: " + path);
-                continue;
-            }
-
-            ret.put(path, parser.getString(key, ""));
-        }
-        return ret;
+        mFilesForNoninteractive = (new CpuFrequencies()).parseString(
+                parser.getString(KEY_CPU_FREQ_NONINTERACTIVE, "")).toSysFileMap();
     }
 
     /**
@@ -387,11 +403,11 @@
                 case ServiceType.VIBRATION:
                     return builder.setBatterySaverEnabled(mVibrationDisabled)
                             .build();
-                case ServiceType.FORCE_ALL_APPS_STANDBY_JOBS:
-                    return builder.setBatterySaverEnabled(mForceAllAppsStandbyJobs)
+                case ServiceType.FORCE_ALL_APPS_STANDBY:
+                    return builder.setBatterySaverEnabled(mForceAllAppsStandby)
                             .build();
-                case ServiceType.FORCE_ALL_APPS_STANDBY_ALARMS:
-                    return builder.setBatterySaverEnabled(mForceAllAppsStandbyAlarms)
+                case ServiceType.FORCE_BACKGROUND_CHECK:
+                    return builder.setBatterySaverEnabled(mForceBackgroundCheck)
                             .build();
                 case ServiceType.OPTIONAL_SENSORS:
                     return builder.setBatterySaverEnabled(mOptionalSensorsDisabled)
@@ -403,9 +419,15 @@
         }
     }
 
-    public ArrayMap<String, String> getFileValues(boolean screenOn) {
+    public ArrayMap<String, String> getFileValues(boolean interactive) {
         synchronized (mLock) {
-            return screenOn ? mScreenOnFiles : mScreenOffFiles;
+            return interactive ? mFilesForInteractive : mFilesForNoninteractive;
+        }
+    }
+
+    public boolean isLaunchBoostDisabled() {
+        synchronized (mLock) {
+            return mLaunchBoostDisabled;
         }
     }
 
@@ -413,10 +435,10 @@
         synchronized (mLock) {
             pw.println();
             pw.println("Battery saver policy");
-            pw.println("  Settings " + Settings.Global.BATTERY_SAVER_CONSTANTS);
-            pw.println("  value: " + mSettings);
-            pw.println("  Settings " + mDeviceSpecificSettingsSource);
-            pw.println("  value: " + mDeviceSpecificSettings);
+            pw.println("  Settings: " + Settings.Global.BATTERY_SAVER_CONSTANTS);
+            pw.println("    value: " + mSettings);
+            pw.println("  Settings: " + mDeviceSpecificSettingsSource);
+            pw.println("    value: " + mDeviceSpecificSettings);
 
             pw.println();
             pw.println("  " + KEY_VIBRATION_DISABLED + "=" + mVibrationDisabled);
@@ -425,20 +447,21 @@
             pw.println("  " + KEY_KEYVALUE_DEFERRED + "=" + mKeyValueBackupDeferred);
             pw.println("  " + KEY_FIREWALL_DISABLED + "=" + mFireWallDisabled);
             pw.println("  " + KEY_DATASAVER_DISABLED + "=" + mDataSaverDisabled);
+            pw.println("  " + KEY_LAUNCH_BOOST_DISABLED + "=" + mLaunchBoostDisabled);
             pw.println("  " + KEY_ADJUST_BRIGHTNESS_DISABLED + "=" + mAdjustBrightnessDisabled);
             pw.println("  " + KEY_ADJUST_BRIGHTNESS_FACTOR + "=" + mAdjustBrightnessFactor);
             pw.println("  " + KEY_GPS_MODE + "=" + mGpsMode);
-            pw.println("  " + KEY_FORCE_ALL_APPS_STANDBY_JOBS + "=" + mForceAllAppsStandbyJobs);
-            pw.println("  " + KEY_FORCE_ALL_APPS_STANDBY_ALARMS + "=" + mForceAllAppsStandbyAlarms);
+            pw.println("  " + KEY_FORCE_ALL_APPS_STANDBY + "=" + mForceAllAppsStandby);
+            pw.println("  " + KEY_FORCE_BACKGROUND_CHECK + "=" + mForceBackgroundCheck);
             pw.println("  " + KEY_OPTIONAL_SENSORS_DISABLED + "=" + mOptionalSensorsDisabled);
             pw.println();
 
-            pw.print("  Screen On Files:\n");
-            dumpMap(pw, "    ", mScreenOnFiles);
+            pw.print("  Interactive File values:\n");
+            dumpMap(pw, "    ", mFilesForInteractive);
             pw.println();
 
-            pw.print("  Screen Off Files:\n");
-            dumpMap(pw, "    ", mScreenOffFiles);
+            pw.print("  Noninteractive File values:\n");
+            dumpMap(pw, "    ", mFilesForNoninteractive);
             pw.println();
         }
     }
diff --git a/com/android/server/power/Notifier.java b/com/android/server/power/Notifier.java
index 0ecf0e1..8ee26f2 100644
--- a/com/android/server/power/Notifier.java
+++ b/com/android/server/power/Notifier.java
@@ -25,6 +25,7 @@
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.server.EventLogTags;
 import com.android.server.LocalServices;
+import com.android.server.policy.WindowManagerPolicy;
 
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -49,7 +50,6 @@
 import android.provider.Settings;
 import android.util.EventLog;
 import android.util.Slog;
-import android.view.WindowManagerPolicy;
 import android.view.inputmethod.InputMethodManagerInternal;
 
 /**
diff --git a/com/android/server/power/PowerManagerService.java b/com/android/server/power/PowerManagerService.java
index a47b809..7f1a534 100644
--- a/com/android/server/power/PowerManagerService.java
+++ b/com/android/server/power/PowerManagerService.java
@@ -69,7 +69,6 @@
 import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
 import android.view.Display;
-import android.view.WindowManagerPolicy;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IAppOpsService;
@@ -90,6 +89,7 @@
 import com.android.server.am.BatteryStatsService;
 import com.android.server.lights.Light;
 import com.android.server.lights.LightsManager;
+import com.android.server.policy.WindowManagerPolicy;
 import com.android.server.power.batterysaver.BatterySaverController;
 
 import libcore.util.Objects;
@@ -1457,6 +1457,10 @@
                 case PowerManager.GO_TO_SLEEP_REASON_HDMI:
                     Slog.i(TAG, "Going to sleep due to HDMI standby (uid " + uid +")...");
                     break;
+                case PowerManager.GO_TO_SLEEP_REASON_ACCESSIBILITY:
+                    Slog.i(TAG, "Going to sleep by an accessibility service request (uid "
+                            + uid +")...");
+                    break;
                 default:
                     Slog.i(TAG, "Going to sleep by application request (uid " + uid +")...");
                     reason = PowerManager.GO_TO_SLEEP_REASON_APPLICATION;
@@ -3105,7 +3109,16 @@
         mIsVrModeEnabled = enabled;
     }
 
-    public static void powerHintInternal(int hintId, int data) {
+    private void powerHintInternal(int hintId, int data) {
+        // Maybe filter the event.
+        switch (hintId) {
+            case PowerHint.LAUNCH: // 1: activate launch boost 0: deactivate.
+                if (data == 1 && mBatterySaverController.isLaunchBoostDisabled()) {
+                    return;
+                }
+                break;
+        }
+
         nativeSendPowerHint(hintId, data);
     }
 
diff --git a/com/android/server/power/ShutdownThread.java b/com/android/server/power/ShutdownThread.java
index 6bf725e..6fb345b 100644
--- a/com/android/server/power/ShutdownThread.java
+++ b/com/android/server/power/ShutdownThread.java
@@ -20,10 +20,7 @@
 import android.app.AlertDialog;
 import android.app.Dialog;
 import android.app.IActivityManager;
-import android.app.KeyguardManager;
 import android.app.ProgressDialog;
-import android.app.WallpaperColors;
-import android.app.WallpaperManager;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.IBluetoothManager;
 import android.content.BroadcastReceiver;
@@ -31,8 +28,6 @@
 import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.graphics.Color;
-import android.graphics.drawable.ColorDrawable;
 import android.media.AudioAttributes;
 import android.os.FileUtils;
 import android.os.Handler;
@@ -47,8 +42,6 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.os.Vibrator;
-import android.os.storage.IStorageManager;
-import android.os.storage.IStorageShutdownObserver;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.util.TimingsTraceLog;
@@ -123,7 +116,6 @@
     private static String METRIC_RADIOS = "shutdown_radios";
     private static String METRIC_BT = "shutdown_bt";
     private static String METRIC_RADIO = "shutdown_radio";
-    private static String METRIC_SM = "shutdown_storage_manager";
 
     private final Object mActionDoneSync = new Object();
     private boolean mActionDone;
@@ -526,54 +518,6 @@
         shutdownTimingLog.traceEnd(); // ShutdownRadios
         metricEnded(METRIC_RADIOS);
 
-        // Shutdown StorageManagerService to ensure media is in a safe state
-        IStorageShutdownObserver observer = new IStorageShutdownObserver.Stub() {
-            public void onShutDownComplete(int statusCode) throws RemoteException {
-                Log.w(TAG, "Result code " + statusCode + " from StorageManagerService.shutdown");
-                actionDone();
-            }
-        };
-
-        Log.i(TAG, "Shutting down StorageManagerService");
-        shutdownTimingLog.traceBegin("ShutdownStorageManager");
-        metricStarted(METRIC_SM);
-
-        // Set initial variables and time out time.
-        mActionDone = false;
-        final long endShutTime = SystemClock.elapsedRealtime() + MAX_SHUTDOWN_WAIT_TIME;
-        synchronized (mActionDoneSync) {
-            try {
-                final IStorageManager storageManager = IStorageManager.Stub.asInterface(
-                        ServiceManager.checkService("mount"));
-                if (storageManager != null) {
-                    storageManager.shutdown(observer);
-                } else {
-                    Log.w(TAG, "StorageManagerService unavailable for shutdown");
-                }
-            } catch (Exception e) {
-                Log.e(TAG, "Exception during StorageManagerService shutdown", e);
-            }
-            while (!mActionDone) {
-                long delay = endShutTime - SystemClock.elapsedRealtime();
-                if (delay <= 0) {
-                    Log.w(TAG, "StorageManager shutdown wait timed out");
-                    break;
-                } else if (mRebootHasProgressBar) {
-                    int status = (int)((MAX_SHUTDOWN_WAIT_TIME - delay) * 1.0 *
-                            (MOUNT_SERVICE_STOP_PERCENT - RADIO_STOP_PERCENT) /
-                            MAX_SHUTDOWN_WAIT_TIME);
-                    status += RADIO_STOP_PERCENT;
-                    sInstance.setRebootProgress(status, null);
-                }
-                try {
-                    mActionDoneSync.wait(Math.min(delay, ACTION_DONE_POLL_WAIT_MS));
-                } catch (InterruptedException e) {
-                }
-            }
-        }
-        shutdownTimingLog.traceEnd(); // ShutdownStorageManager
-        metricEnded(METRIC_SM);
-
         if (mRebootHasProgressBar) {
             sInstance.setRebootProgress(MOUNT_SERVICE_STOP_PERCENT, null);
 
@@ -585,6 +529,7 @@
         shutdownTimingLog.traceEnd(); // SystemServerShutdown
         metricEnded(METRIC_SYSTEM_SERVER);
         saveMetrics(mReboot);
+        // Remaining work will be done by init, including vold shutdown
         rebootOrShutdown(mContext, mReboot, mReason);
     }
 
diff --git a/com/android/server/power/batterysaver/BatterySaverController.java b/com/android/server/power/batterysaver/BatterySaverController.java
index b3e8538..80bc935 100644
--- a/com/android/server/power/batterysaver/BatterySaverController.java
+++ b/com/android/server/power/batterysaver/BatterySaverController.java
@@ -16,6 +16,7 @@
 package com.android.server.power.batterysaver;
 
 import android.Manifest;
+import android.app.ActivityManagerInternal;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -25,6 +26,7 @@
 import android.os.Looper;
 import android.os.Message;
 import android.os.PowerManager;
+import android.os.PowerManagerInternal;
 import android.os.PowerManagerInternal.LowPowerModeListener;
 import android.os.PowerSaveState;
 import android.os.UserHandle;
@@ -33,7 +35,9 @@
 import android.widget.Toast;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.Preconditions;
+import com.android.server.LocalServices;
 import com.android.server.power.BatterySaverPolicy;
 import com.android.server.power.BatterySaverPolicy.BatterySaverPolicyListener;
 import com.android.server.power.PowerManagerService;
@@ -46,7 +50,7 @@
 public class BatterySaverController implements BatterySaverPolicyListener {
     static final String TAG = "BatterySaverController";
 
-    static final boolean DEBUG = false; // DO NOT MERGE WITH TRUE
+    static final boolean DEBUG = BatterySaverPolicy.DEBUG;
 
     private final Object mLock = new Object();
     private final Context mContext;
@@ -63,20 +67,17 @@
     @GuardedBy("mLock")
     private boolean mEnabled;
 
-    /**
-     * Keep track of the previous enabled state, which we use to decide when to send broadcasts,
-     * which we don't want to send only when the screen state changes.
-     */
-    @GuardedBy("mLock")
-    private boolean mWasEnabled;
-
     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
             switch (intent.getAction()) {
                 case Intent.ACTION_SCREEN_ON:
                 case Intent.ACTION_SCREEN_OFF:
-                    mHandler.postStateChanged();
+                    if (!isEnabled()) {
+                        return; // No need to send it if not enabled.
+                    }
+                    // Don't send the broadcast, because we never did so in this case.
+                    mHandler.postStateChanged(/*sendBroadcast=*/ false);
                     break;
             }
         }
@@ -109,6 +110,9 @@
         final IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON);
         filter.addAction(Intent.ACTION_SCREEN_OFF);
         mContext.registerReceiver(mReceiver, filter);
+
+        mFileUpdater.systemReady(LocalServices.getService(ActivityManagerInternal.class)
+                .isRuntimeRestarted());
     }
 
     private PowerManager getPowerManager() {
@@ -121,25 +125,32 @@
 
     @Override
     public void onBatterySaverPolicyChanged(BatterySaverPolicy policy) {
-        mHandler.postStateChanged();
+        if (!isEnabled()) {
+            return; // No need to send it if not enabled.
+        }
+        mHandler.postStateChanged(/*sendBroadcast=*/ true);
     }
 
     private class MyHandler extends Handler {
-        private final int MSG_STATE_CHANGED = 1;
+        private static final int MSG_STATE_CHANGED = 1;
+
+        private static final int ARG_DONT_SEND_BROADCAST = 0;
+        private static final int ARG_SEND_BROADCAST = 1;
 
         public MyHandler(Looper looper) {
             super(looper);
         }
 
-        public void postStateChanged() {
-            obtainMessage(MSG_STATE_CHANGED).sendToTarget();
+        public void postStateChanged(boolean sendBroadcast) {
+            obtainMessage(MSG_STATE_CHANGED, sendBroadcast ?
+                    ARG_SEND_BROADCAST : ARG_DONT_SEND_BROADCAST, 0).sendToTarget();
         }
 
         @Override
         public void dispatchMessage(Message msg) {
             switch (msg.what) {
                 case MSG_STATE_CHANGED:
-                    handleBatterySaverStateChanged();
+                    handleBatterySaverStateChanged(msg.arg1 == ARG_SEND_BROADCAST);
                     break;
             }
         }
@@ -155,53 +166,75 @@
             }
             mEnabled = enable;
 
-            mHandler.postStateChanged();
+            mHandler.postStateChanged(/*sendBroadcast=*/ true);
         }
     }
 
+    /** @return whether battery saver is enabled or not. */
+    boolean isEnabled() {
+        synchronized (mLock) {
+            return mEnabled;
+        }
+    }
+
+    /**
+     * @return true if launch boost should currently be disabled.
+     */
+    public boolean isLaunchBoostDisabled() {
+        return isEnabled() && mBatterySaverPolicy.isLaunchBoostDisabled();
+    }
+
     /**
      * Dispatch power save events to the listeners.
      *
-     * This is always called on the handler thread.
+     * This method is always called on the handler thread.
+     *
+     * This method is called only in the following cases:
+     * - When battery saver becomes activated.
+     * - When battery saver becomes deactivated.
+     * - When battery saver is on the interactive state changes.
+     * - When battery saver is on the battery saver policy changes.
      */
-    void handleBatterySaverStateChanged() {
+    void handleBatterySaverStateChanged(boolean sendBroadcast) {
         final LowPowerModeListener[] listeners;
 
-        final boolean wasEnabled;
         final boolean enabled;
-        final boolean isScreenOn = getPowerManager().isInteractive();
+        final boolean isInteractive = getPowerManager().isInteractive();
         final ArrayMap<String, String> fileValues;
 
         synchronized (mLock) {
-            Slog.i(TAG, "Battery saver enabled: screen on=" + isScreenOn);
+            Slog.i(TAG, "Battery saver " + (mEnabled ? "enabled" : "disabled")
+                    + ": isInteractive=" + isInteractive);
 
             listeners = mListeners.toArray(new LowPowerModeListener[mListeners.size()]);
-            wasEnabled = mWasEnabled;
             enabled = mEnabled;
 
             if (enabled) {
-                fileValues = mBatterySaverPolicy.getFileValues(isScreenOn);
+                fileValues = mBatterySaverPolicy.getFileValues(isInteractive);
             } else {
                 fileValues = null;
             }
         }
 
-        PowerManagerService.powerHintInternal(PowerHint.LOW_POWER, enabled ? 1 : 0);
-
-        if (enabled) {
-            // STOPSHIP Remove the toast.
-            Toast.makeText(mContext,
-                    com.android.internal.R.string.battery_saver_warning,
-                    Toast.LENGTH_LONG).show();
+        final PowerManagerInternal pmi = LocalServices.getService(PowerManagerInternal.class);
+        if (pmi != null) {
+            pmi.powerHint(PowerHint.LOW_POWER, enabled ? 1 : 0);
         }
 
-        if (fileValues == null || fileValues.size() == 0) {
+        if (ArrayUtils.isEmpty(fileValues)) {
             mFileUpdater.restoreDefault();
         } else {
             mFileUpdater.writeFiles(fileValues);
         }
 
-        if (enabled != wasEnabled) {
+        if (sendBroadcast) {
+            if (enabled) {
+                // STOPSHIP Remove the toast.
+                Toast.makeText(mContext,
+                        com.android.internal.R.string.battery_saver_warning,
+                        Toast.LENGTH_LONG).show();
+            }
+
             if (DEBUG) {
                 Slog.i(TAG, "Sending broadcasts for mode: " + enabled);
             }
@@ -231,9 +264,5 @@
                 listener.onLowPowerModeChanged(result);
             }
         }
-
-        synchronized (mLock) {
-            mWasEnabled = enabled;
-        }
     }
 }
diff --git a/com/android/server/power/batterysaver/CpuFrequencies.java b/com/android/server/power/batterysaver/CpuFrequencies.java
new file mode 100644
index 0000000..1629486
--- /dev/null
+++ b/com/android/server/power/batterysaver/CpuFrequencies.java
@@ -0,0 +1,101 @@
+/*
+ * 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.power.batterysaver;
+
+import android.util.ArrayMap;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.Map;
+
+
+/**
+ * Helper to parse a list of "core-number:frequency" pairs concatenated with / as a separator,
+ * and convert them into a map of "filename -> value" that should be written to
+ * /sys/.../scaling_max_freq.
+ *
+ * Example input: "0:1900800/4:2500000", which will be converted into:
+ *   "/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq" "1900800"
+ *   "/sys/devices/system/cpu/cpu4/cpufreq/scaling_max_freq" "2500000"
+ *
+ * Test:
+ atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/power/batterysaver/CpuFrequenciesTest.java
+ */
+public class CpuFrequencies {
+    private static final String TAG = "CpuFrequencies";
+
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
+    private final ArrayMap<Integer, Long> mCoreAndFrequencies = new ArrayMap<>();
+
+    public CpuFrequencies() {
+    }
+
+    /**
+     * Parse a string.
+     */
+    public CpuFrequencies parseString(String cpuNumberAndFrequencies) {
+        synchronized (mLock) {
+            mCoreAndFrequencies.clear();
+            try {
+                for (String pair : cpuNumberAndFrequencies.split("/")) {
+                    final String[] coreAndFreq = pair.split(":", 2);
+
+                    if (coreAndFreq.length != 2) {
+                        throw new IllegalArgumentException("Wrong format");
+                    }
+                    final int core = Integer.parseInt(coreAndFreq[0]);
+                    final long freq = Long.parseLong(coreAndFreq[1]);
+
+                    mCoreAndFrequencies.put(core, freq);
+                }
+            } catch (IllegalArgumentException e) {
+                Slog.wtf(TAG, "Invalid configuration: " + cpuNumberAndFrequencies, e);
+            }
+        }
+        return this;
+    }
+
+    /**
+     * Return a new map containing the filename-value pairs.
+     */
+    public ArrayMap<String, String> toSysFileMap() {
+        final ArrayMap<String, String> map = new ArrayMap<>();
+        addToSysFileMap(map);
+        return map;
+    }
+
+    /**
+     * Add the filename-value pairs to an existing map.
+     */
+    public void addToSysFileMap(Map<String, String> map) {
+        synchronized (mLock) {
+            final int size = mCoreAndFrequencies.size();
+
+            for (int i = 0; i < size; i++) {
+                final int core = mCoreAndFrequencies.keyAt(i);
+                final long freq = mCoreAndFrequencies.valueAt(i);
+
+                final String file = "/sys/devices/system/cpu/cpu" + Integer.toString(core) +
+                        "/cpufreq/scaling_max_freq";
+
+                map.put(file, Long.toString(freq));
+            }
+        }
+    }
+}
diff --git a/com/android/server/power/batterysaver/FileUpdater.java b/com/android/server/power/batterysaver/FileUpdater.java
index cfe8fc4..e0ab9e9 100644
--- a/com/android/server/power/batterysaver/FileUpdater.java
+++ b/com/android/server/power/batterysaver/FileUpdater.java
@@ -16,40 +16,389 @@
 package com.android.server.power.batterysaver;
 
 import android.content.Context;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.SystemProperties;
 import android.util.ArrayMap;
+import android.util.AtomicFile;
 import android.util.Slog;
+import android.util.Xml;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.XmlUtils;
+import com.android.server.IoThread;
+
+import libcore.io.IoUtils;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Map;
 
 /**
  * Used by {@link BatterySaverController} to write values to /sys/ (and possibly /proc/ too) files
- * with retry and to restore the original values.
+ * with retries. It also support restoring to the file original values.
  *
- * TODO Implement it
+ * Retries are needed because writing to "/sys/.../scaling_max_freq" returns EIO when the current
+ * frequency happens to be above the new max frequency.
+ *
+ * Test:
+ atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/power/batterysaver/FileUpdaterTest.java
  */
 public class FileUpdater {
     private static final String TAG = BatterySaverController.TAG;
 
     private static final boolean DEBUG = BatterySaverController.DEBUG;
 
+    /**
+     * If this system property is set to 1, it'll skip all file writes. This can be used when
+     * one needs to change max CPU frequency for benchmarking, for example.
+     */
+    private static final String PROP_SKIP_WRITE = "debug.batterysaver.no_write_files";
+
+    private static final String TAG_DEFAULT_ROOT = "defaults";
+
+    // Don't do disk access with this lock held.
     private final Object mLock = new Object();
+
     private final Context mContext;
 
+    private final Handler mHandler;
+
+    /**
+     * Filename -> value map that holds pending writes.
+     */
+    @GuardedBy("mLock")
+    private final ArrayMap<String, String> mPendingWrites = new ArrayMap<>();
+
+    /**
+     * Filename -> value that holds the original value of each file.
+     */
+    @GuardedBy("mLock")
+    private final ArrayMap<String, String> mDefaultValues = new ArrayMap<>();
+
+    /** Number of retries. We give up on writing after {@link #MAX_RETRIES} retries. */
+    @GuardedBy("mLock")
+    private int mRetries = 0;
+
+    private final int MAX_RETRIES;
+
+    private final long RETRY_INTERVAL_MS;
+
+    /**
+     * "Official" constructor. Don't use the other constructor in the production code.
+     */
     public FileUpdater(Context context) {
-        mContext = context;
+        this(context, IoThread.get().getLooper(), 10, 5000);
     }
 
-    public void writeFiles(ArrayMap<String, String> fileValues) {
-        if (DEBUG) {
-            final int size = fileValues.size();
-            for (int i = 0; i < size; i++) {
-                Slog.d(TAG, "Writing '" + fileValues.valueAt(i)
-                        + "' to '" + fileValues.keyAt(i) + "'");
+    /**
+     * Constructor for test.
+     */
+    @VisibleForTesting
+    FileUpdater(Context context, Looper looper, int maxRetries, int retryIntervalMs) {
+        mContext = context;
+        mHandler = new Handler(looper);
+
+        MAX_RETRIES = maxRetries;
+        RETRY_INTERVAL_MS = retryIntervalMs;
+    }
+
+    public void systemReady(boolean runtimeRestarted) {
+        synchronized (mLock) {
+            if (runtimeRestarted) {
+                // If it runtime restarted, read the original values from the disk and apply.
+                if (loadDefaultValuesLocked()) {
+                    Slog.d(TAG, "Default values loaded after runtime restart; writing them...");
+                    restoreDefault();
+                }
+            } else {
+                // Delete it, without checking the result. (file-not-exist is not an exception.)
+                injectDefaultValuesFilename().delete();
             }
         }
     }
 
-    public void restoreDefault() {
-        if (DEBUG) {
-            Slog.d(TAG, "Resetting file default values");
+    /**
+     * Write values to files. (Note the actual writes happen ASAP but asynchronously.)
+     */
+    public void writeFiles(ArrayMap<String, String> fileValues) {
+        synchronized (mLock) {
+            for (int i = fileValues.size() - 1; i >= 0; i--) {
+                final String file = fileValues.keyAt(i);
+                final String value = fileValues.valueAt(i);
+
+                if (DEBUG) {
+                    Slog.d(TAG, "Scheduling write: '" + value + "' to '" + file + "'");
+                }
+
+                mPendingWrites.put(file, value);
+
+            }
+            mRetries = 0;
+
+            mHandler.removeCallbacks(mHandleWriteOnHandlerRunnable);
+            mHandler.post(mHandleWriteOnHandlerRunnable);
         }
     }
+
+    /**
+     * Restore the default values.
+     */
+    public void restoreDefault() {
+        synchronized (mLock) {
+            if (DEBUG) {
+                Slog.d(TAG, "Resetting file default values.");
+            }
+            mPendingWrites.clear();
+
+            writeFiles(mDefaultValues);
+        }
+    }
+
+    private Runnable mHandleWriteOnHandlerRunnable = () -> handleWriteOnHandler();
+
+    /** Convert map keys into a single string for debug messages. */
+    private String getKeysString(Map<String, String> source) {
+        return new ArrayList<>(source.keySet()).toString();
+    }
+
+    /** Clone an ArrayMap. */
+    private ArrayMap<String, String> cloneMap(ArrayMap<String, String> source) {
+        return new ArrayMap<>(source);
+    }
+
+    /**
+     * Called on the handler and writes {@link #mPendingWrites} to the disk.
+     *
+     * When it about to write to each file for the first time, it'll read the file and store
+     * the original value in {@link #mDefaultValues}.
+     */
+    private void handleWriteOnHandler() {
+        // We don't want to access the disk with the lock held, so copy the pending writes to
+        // a local map.
+        final ArrayMap<String, String> writes;
+        synchronized (mLock) {
+            if (mPendingWrites.size() == 0) {
+                return;
+            }
+
+            if (DEBUG) {
+                Slog.d(TAG, "Writing files: (# retries=" + mRetries + ") " +
+                        getKeysString(mPendingWrites));
+            }
+
+            writes = cloneMap(mPendingWrites);
+        }
+
+        // Then write.
+
+        boolean needRetry = false;
+
+        final int size = writes.size();
+        for (int i = 0; i < size; i++) {
+            final String file = writes.keyAt(i);
+            final String value = writes.valueAt(i);
+
+            // Make sure the default value is loaded.
+            if (!ensureDefaultLoaded(file)) {
+                continue;
+            }
+
+            // Write to the file. When succeeded, remove it from the pending list.
+            // Otherwise, schedule a retry.
+            try {
+                injectWriteToFile(file, value);
+
+                removePendingWrite(file);
+            } catch (IOException e) {
+                needRetry = true;
+            }
+        }
+        if (needRetry) {
+            scheduleRetry();
+        }
+    }
+
+    private void removePendingWrite(String file) {
+        synchronized (mLock) {
+            mPendingWrites.remove(file);
+        }
+    }
+
+    private void scheduleRetry() {
+        synchronized (mLock) {
+            if (mPendingWrites.size() == 0) {
+                return; // Shouldn't happen but just in case.
+            }
+
+            mRetries++;
+            if (mRetries > MAX_RETRIES) {
+                doWtf("Gave up writing files: " + getKeysString(mPendingWrites));
+                return;
+            }
+
+            mHandler.removeCallbacks(mHandleWriteOnHandlerRunnable);
+            mHandler.postDelayed(mHandleWriteOnHandlerRunnable, RETRY_INTERVAL_MS);
+        }
+    }
+
+    /**
+     * Make sure {@link #mDefaultValues} has the default value loaded for {@code file}.
+     *
+     * @return true if the default value is loaded. false if the file cannot be read.
+     */
+    private boolean ensureDefaultLoaded(String file) {
+        // Has the default already?
+        synchronized (mLock) {
+            if (mDefaultValues.containsKey(file)) {
+                return true;
+            }
+        }
+        final String originalValue;
+        try {
+            originalValue = injectReadFromFileTrimmed(file);
+        } catch (IOException e) {
+            // If the file is not readable, assume can't write too.
+            injectWtf("Unable to read from file", e);
+
+            removePendingWrite(file);
+            return false;
+        }
+        synchronized (mLock) {
+            mDefaultValues.put(file, originalValue);
+            saveDefaultValuesLocked();
+        }
+        return true;
+    }
+
+    @VisibleForTesting
+    String injectReadFromFileTrimmed(String file) throws IOException {
+        return IoUtils.readFileAsString(file).trim();
+    }
+
+    @VisibleForTesting
+    void injectWriteToFile(String file, String value) throws IOException {
+        if (injectShouldSkipWrite()) {
+            Slog.i(TAG, "Skipped writing to '" + file + "'");
+            return;
+        }
+        if (DEBUG) {
+            Slog.d(TAG, "Writing: '" + value + "' to '" + file + "'");
+        }
+        try (FileWriter out = new FileWriter(file)) {
+            out.write(value);
+        } catch (IOException | RuntimeException e) {
+            Slog.w(TAG, "Failed writing '" + value + "' to '" + file + "': " + e.getMessage());
+            throw e;
+        }
+    }
+
+    private void saveDefaultValuesLocked() {
+        final AtomicFile file = new AtomicFile(injectDefaultValuesFilename());
+
+        FileOutputStream outs = null;
+        try {
+            file.getBaseFile().getParentFile().mkdirs();
+            outs = file.startWrite();
+
+            // Write to XML
+            XmlSerializer out = new FastXmlSerializer();
+            out.setOutput(outs, StandardCharsets.UTF_8.name());
+            out.startDocument(null, true);
+            out.startTag(null, TAG_DEFAULT_ROOT);
+
+            XmlUtils.writeMapXml(mDefaultValues, out, null);
+
+            // Epilogue.
+            out.endTag(null, TAG_DEFAULT_ROOT);
+            out.endDocument();
+
+            // Close.
+            file.finishWrite(outs);
+        } catch (IOException | XmlPullParserException | RuntimeException e) {
+            Slog.e(TAG, "Failed to write to file " + file.getBaseFile(), e);
+            file.failWrite(outs);
+        }
+    }
+
+    @VisibleForTesting
+    boolean loadDefaultValuesLocked() {
+        final AtomicFile file = new AtomicFile(injectDefaultValuesFilename());
+        if (DEBUG) {
+            Slog.d(TAG, "Loading from " + file.getBaseFile());
+        }
+        Map<String, String> read = null;
+        try (FileInputStream in = file.openRead()) {
+            XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(in, StandardCharsets.UTF_8.name());
+
+            int type;
+            while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) {
+                if (type != XmlPullParser.START_TAG) {
+                    continue;
+                }
+                final int depth = parser.getDepth();
+                // Check the root tag
+                final String tag = parser.getName();
+                if (depth == 1) {
+                    if (!TAG_DEFAULT_ROOT.equals(tag)) {
+                        Slog.e(TAG, "Invalid root tag: " + tag);
+                        return false;
+                    }
+                    continue;
+                }
+                final String[] tagName = new String[1];
+                read = (ArrayMap<String, String>) XmlUtils.readThisArrayMapXml(parser,
+                        TAG_DEFAULT_ROOT, tagName, null);
+            }
+        } catch (FileNotFoundException e) {
+            read = null;
+        } catch (IOException | XmlPullParserException | RuntimeException e) {
+            Slog.e(TAG, "Failed to read file " + file.getBaseFile(), e);
+        }
+        if (read != null) {
+            mDefaultValues.clear();
+            mDefaultValues.putAll(read);
+            return true;
+        }
+        return false;
+    }
+
+    private void doWtf(String message) {
+        injectWtf(message, null);
+    }
+
+    @VisibleForTesting
+    void injectWtf(String message, Throwable e) {
+        Slog.wtf(TAG, message, e);
+    }
+
+    File injectDefaultValuesFilename() {
+        final File dir = new File(Environment.getDataSystemDirectory(), "battery-saver");
+        dir.mkdirs();
+        return new File(dir, "default-values.xml");
+    }
+
+    @VisibleForTesting
+    boolean injectShouldSkipWrite() {
+        return SystemProperties.getBoolean(PROP_SKIP_WRITE, false);
+    }
+
+    @VisibleForTesting
+    ArrayMap<String, String> getDefaultValuesForTest() {
+        return mDefaultValues;
+    }
 }
diff --git a/com/android/server/stats/StatsCompanionService.java b/com/android/server/stats/StatsCompanionService.java
index dafab77..e240ec5 100644
--- a/com/android/server/stats/StatsCompanionService.java
+++ b/com/android/server/stats/StatsCompanionService.java
@@ -75,6 +75,7 @@
     private final PendingIntent mPullingAlarmIntent;
     private final BroadcastReceiver mAppUpdateReceiver;
     private final BroadcastReceiver mUserUpdateReceiver;
+    private final ShutdownEventReceiver mShutdownEventReceiver;
     private final KernelWakelockReader mKernelWakelockReader = new KernelWakelockReader();
     private final KernelWakelockStats mTmpWakelockStats = new KernelWakelockStats();
     private final KernelCpuSpeedReader[] mKernelCpuSpeedReaders;
@@ -109,6 +110,7 @@
                 }
             }
         };
+        mShutdownEventReceiver = new ShutdownEventReceiver();
         Slog.w(TAG, "Registered receiver for ACTION_PACKAGE_REPLACE AND ADDED.");
         PowerProfile powerProfile = new PowerProfile(context);
         final int numClusters = powerProfile.getNumCpuClusters();
@@ -239,20 +241,45 @@
           Slog.d(TAG, "Time to poll something.");
         synchronized (sStatsdLock) {
           if (sStatsd == null) {
-            Slog.w(TAG, "Could not access statsd to inform it of pulling alarm firing");
+            Slog.w(TAG, "Could not access statsd to inform it of pulling 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 pulling alarm firing", e);
+            Slog.w(TAG, "Failed to inform statsd of pulling alarm firing.", e);
           }
         }
         // AlarmManager releases its own wakelock here.
       }
     }
 
+    public final static class ShutdownEventReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            /**
+             * Skip immediately if intent is not relevant to device shutdown.
+             */
+            if (!intent.getAction().equals(Intent.ACTION_REBOOT)
+                    && !intent.getAction().equals(Intent.ACTION_SHUTDOWN)) {
+                return;
+            }
+            Slog.i(TAG, "StatsCompanionService noticed a shutdown.");
+            synchronized (sStatsdLock) {
+                if (sStatsd == null) {
+                    Slog.w(TAG, "Could not access statsd to inform it of a shutdown event.");
+                    return;
+                }
+                try {
+                    sStatsd.writeDataToDisk();
+                } catch (Exception e) {
+                    Slog.w(TAG, "Failed to inform statsd of a shutdown event.", e);
+                }
+            }
+        }
+    }
+
     @Override // Binder call
     public void setAnomalyAlarm(long timestampMs) {
         enforceCallingPermission();
@@ -496,6 +523,18 @@
         sayHiToStatsd(); // tell statsd that we're ready too and link to it
     }
 
+    @Override
+    public void triggerUidSnapshot() {
+      enforceCallingPermission();
+      synchronized (sStatsdLock) {
+        try {
+          informAllUidsLocked(mContext);
+        } catch (RemoteException e) {
+          Slog.e(TAG, "Failed to trigger uid snapshot.", e);
+        }
+      }
+    }
+
     private void enforceCallingPermission() {
         if (Binder.getCallingPid() == Process.myPid()) {
             return;
@@ -572,7 +611,7 @@
                     Slog.e(TAG, "linkToDeath(StatsdDeathRecipient) failed", e);
                     forgetEverything();
                 }
-                // Setup broadcast receiver for updates
+                // Setup broadcast receiver for updates.
                 IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_REPLACED);
                 filter.addAction(Intent.ACTION_PACKAGE_ADDED);
                 filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
@@ -587,6 +626,12 @@
                 mContext.registerReceiverAsUser(mUserUpdateReceiver, UserHandle.ALL,
                         filter, null, null);
 
+                // Setup receiver for device reboots or shutdowns.
+                filter = new IntentFilter(Intent.ACTION_REBOOT);
+                filter.addAction(Intent.ACTION_SHUTDOWN);
+                mContext.registerReceiverAsUser(
+                        mShutdownEventReceiver, UserHandle.ALL, filter, null, null);
+
                 // Pull the latest state of UID->app name, version mapping when statsd starts.
                 informAllUidsLocked(mContext);
             } catch (RemoteException e) {
@@ -609,6 +654,7 @@
             sStatsd = null;
             mContext.unregisterReceiver(mAppUpdateReceiver);
             mContext.unregisterReceiver(mUserUpdateReceiver);
+            mContext.unregisterReceiver(mShutdownEventReceiver);
             cancelAnomalyAlarm();
             cancelPullingAlarms();
         }
diff --git a/com/android/server/statusbar/StatusBarManagerInternal.java b/com/android/server/statusbar/StatusBarManagerInternal.java
index b07fe98..3792bc6 100644
--- a/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -90,6 +90,13 @@
 
     boolean showShutdownUi(boolean isReboot, String requestString);
 
+    /**
+     * Show a rotation suggestion that a user may approve to rotate the screen.
+     *
+     * @param rotation rotation suggestion
+     */
+    void onProposedRotationChanged(int rotation);
+
     public interface GlobalActionsListener {
         /**
          * Called when sysui starts and connects its status bar, or when the status bar binder
diff --git a/com/android/server/statusbar/StatusBarManagerService.java b/com/android/server/statusbar/StatusBarManagerService.java
index c78a340..c7c03b4 100644
--- a/com/android/server/statusbar/StatusBarManagerService.java
+++ b/com/android/server/statusbar/StatusBarManagerService.java
@@ -406,6 +406,15 @@
             }
             return false;
         }
+
+        @Override
+        public void onProposedRotationChanged(int rotation) {
+            if (mBar != null){
+                try {
+                    mBar.onProposedRotationChanged(rotation);
+                } catch (RemoteException ex) {}
+            }
+        }
     };
 
     // ================================================================================
diff --git a/com/android/server/usage/AppIdleHistory.java b/com/android/server/usage/AppIdleHistory.java
index c5ca330..ee11241 100644
--- a/com/android/server/usage/AppIdleHistory.java
+++ b/com/android/server/usage/AppIdleHistory.java
@@ -91,8 +91,6 @@
     private long mScreenOnSnapshot; // Elapsed time snapshot when last write of mScreenOnDuration
     private long mScreenOnDuration; // Total screen on duration since device was "born"
 
-    private long mElapsedTimeThreshold;
-    private long mScreenOnTimeThreshold;
     private final File mStorageDir;
 
     private boolean mScreenOn;
@@ -113,11 +111,6 @@
         readScreenOnTime();
     }
 
-    public void setThresholds(long elapsedTimeThreshold, long screenOnTimeThreshold) {
-        mElapsedTimeThreshold = elapsedTimeThreshold;
-        mScreenOnTimeThreshold = screenOnTimeThreshold;
-    }
-
     public void updateDisplay(boolean screenOn, long elapsedRealtime) {
         if (screenOn == mScreenOn) return;
 
@@ -186,7 +179,7 @@
         writeScreenOnTime();
     }
 
-    public void reportUsage(String packageName, int userId, long elapsedRealtime) {
+    public int reportUsage(String packageName, int userId, long elapsedRealtime) {
         ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
         AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName,
                 elapsedRealtime, true);
@@ -197,12 +190,33 @@
                 + (elapsedRealtime - mElapsedSnapshot);
         appUsageHistory.lastUsedScreenTime = getScreenOnTime(elapsedRealtime);
         appUsageHistory.recent[HISTORY_SIZE - 1] = FLAG_LAST_STATE | FLAG_PARTIAL_ACTIVE;
-        appUsageHistory.currentBucket = AppStandby.STANDBY_BUCKET_ACTIVE;
-        appUsageHistory.bucketingReason = AppStandby.REASON_USAGE;
-        if (DEBUG) {
-            Slog.d(TAG, "Moved " + packageName + " to bucket=" + appUsageHistory.currentBucket
-                    + ", reason=" + appUsageHistory.bucketingReason);
+        if (appUsageHistory.currentBucket > STANDBY_BUCKET_ACTIVE) {
+            appUsageHistory.currentBucket = STANDBY_BUCKET_ACTIVE;
+            if (DEBUG) {
+                Slog.d(TAG, "Moved " + packageName + " to bucket=" + appUsageHistory.currentBucket
+                        + ", reason=" + appUsageHistory.bucketingReason);
+            }
         }
+        appUsageHistory.bucketingReason = AppStandby.REASON_USAGE;
+
+        return appUsageHistory.currentBucket;
+    }
+
+    public int reportMildUsage(String packageName, int userId, long elapsedRealtime) {
+        ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
+        AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName,
+                elapsedRealtime, true);
+        if (appUsageHistory.currentBucket > STANDBY_BUCKET_WORKING_SET) {
+            appUsageHistory.currentBucket = STANDBY_BUCKET_WORKING_SET;
+            if (DEBUG) {
+                Slog.d(TAG, "Moved " + packageName + " to bucket=" + appUsageHistory.currentBucket
+                        + ", reason=" + appUsageHistory.bucketingReason);
+            }
+        }
+        // TODO: Should this be a different reason for partial usage?
+        appUsageHistory.bucketingReason = AppStandby.REASON_USAGE;
+
+        return appUsageHistory.currentBucket;
     }
 
     public void setIdle(String packageName, int userId, long elapsedRealtime) {
@@ -313,7 +327,8 @@
         return (elapsedRealtime - mElapsedSnapshot + mElapsedDuration);
     }
 
-    public void setIdle(String packageName, int userId, boolean idle, long elapsedRealtime) {
+    /* Returns the new standby bucket the app is assigned to */
+    public int setIdle(String packageName, int userId, boolean idle, long elapsedRealtime) {
         ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
         AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName,
                 elapsedRealtime, true);
@@ -325,6 +340,7 @@
             // This is to pretend that the app was just used, don't freeze the state anymore.
             appUsageHistory.bucketingReason = REASON_USAGE;
         }
+        return appUsageHistory.currentBucket;
     }
 
     public void clearUsage(String packageName, int userId) {
diff --git a/com/android/server/usage/AppStandbyController.java b/com/android/server/usage/AppStandbyController.java
index 5623a68..3c099c2 100644
--- a/com/android/server/usage/AppStandbyController.java
+++ b/com/android/server/usage/AppStandbyController.java
@@ -91,14 +91,14 @@
             0,
             0,
             COMPRESS_TIME ? 120 * 1000 : 1 * ONE_HOUR,
-            COMPRESS_TIME ? 240 * 1000 : 8 * ONE_HOUR
+            COMPRESS_TIME ? 240 * 1000 : 2 * ONE_HOUR
     };
 
     static final long[] ELAPSED_TIME_THRESHOLDS = {
             0,
             COMPRESS_TIME ?  1 * ONE_MINUTE : 12 * ONE_HOUR,
-            COMPRESS_TIME ?  4 * ONE_MINUTE :  2 * ONE_DAY,
-            COMPRESS_TIME ? 16 * ONE_MINUTE :  8 * ONE_DAY
+            COMPRESS_TIME ?  4 * ONE_MINUTE : 24 * ONE_HOUR,
+            COMPRESS_TIME ? 16 * ONE_MINUTE : 48 * ONE_HOUR
     };
 
     static final int[] THRESHOLD_BUCKETS = {
@@ -140,9 +140,7 @@
     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;
     long[] mAppStandbyScreenThresholds = SCREEN_TIME_THRESHOLDS;
@@ -156,7 +154,7 @@
 
     private volatile boolean mPendingOneTimeCheckIdleStates;
 
-    private final Handler mHandler;
+    private final AppStandbyHandler mHandler;
     private final Context mContext;
 
     // TODO: Provide a mechanism to set an external bucketing service
@@ -229,6 +227,7 @@
         // Get sync adapters for the authority
         String[] packages = ContentResolver.getSyncAdapterPackagesForAuthorityAsUser(
                 authority, userId);
+        final long elapsedRealtime = SystemClock.elapsedRealtime();
         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.
@@ -239,7 +238,12 @@
                     continue;
                 }
                 if (!packageName.equals(providerPkgName)) {
-                    setAppIdleAsync(packageName, false, userId);
+                    synchronized (mAppIdleLock) {
+                        int newBucket = mAppIdleHistory.reportMildUsage(packageName, userId,
+                                    elapsedRealtime);
+                        maybeInformListeners(packageName, userId, elapsedRealtime,
+                                newBucket);
+                    }
                 }
             } catch (PackageManager.NameNotFoundException e) {
                 // Shouldn't happen
@@ -408,6 +412,7 @@
     private void maybeInformListeners(String packageName, int userId,
             long elapsedRealtime, int bucket) {
         synchronized (mAppIdleLock) {
+            // TODO: fold these into one call + lookup for efficiency if needed
             if (mAppIdleHistory.shouldInformListeners(packageName, userId,
                     elapsedRealtime, bucket)) {
                 mHandler.sendMessage(mHandler.obtainMessage(MSG_INFORM_LISTENERS,
@@ -493,11 +498,21 @@
             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);
+                    || event.mEventType == UsageEvents.Event.USER_INTERACTION
+                    || event.mEventType == UsageEvents.Event.NOTIFICATION_SEEN)) {
+
+                final int newBucket;
+                if (event.mEventType == UsageEvents.Event.NOTIFICATION_SEEN) {
+                    newBucket = mAppIdleHistory.reportMildUsage(event.mPackage, userId,
+                            elapsedRealtime);
+                } else {
+                    newBucket = mAppIdleHistory.reportUsage(event.mPackage, userId,
+                            elapsedRealtime);
+                }
+
+                maybeInformListeners(event.mPackage, userId, elapsedRealtime,
+                        newBucket);
                 if (previouslyIdle) {
-                    maybeInformListeners(event.mPackage, userId, elapsedRealtime,
-                            AppStandby.STANDBY_BUCKET_ACTIVE);
                     notifyBatteryStats(event.mPackage, userId, false);
                 }
             }
@@ -519,15 +534,15 @@
 
         final boolean previouslyIdle = isAppIdleFiltered(packageName, appId,
                 userId, elapsedRealtime);
+        final int standbyBucket;
         synchronized (mAppIdleLock) {
-            mAppIdleHistory.setIdle(packageName, userId, idle, elapsedRealtime);
+            standbyBucket = 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));
+            maybeInformListeners(packageName, userId, elapsedRealtime, standbyBucket);
             if (!stillIdle) {
                 notifyBatteryStats(packageName, userId, idle);
             }
@@ -723,7 +738,7 @@
                 .sendToTarget();
     }
 
-    @StandbyBuckets int getAppStandbyBucket(String packageName, int userId,
+    @StandbyBuckets public int getAppStandbyBucket(String packageName, int userId,
             long elapsedRealtime, boolean shouldObfuscateInstantApps) {
         if (shouldObfuscateInstantApps &&
                 mInjector.isPackageEphemeral(userId, packageName)) {
@@ -737,6 +752,8 @@
             String reason, long elapsedRealtime) {
         mAppIdleHistory.setAppStandbyBucket(packageName, userId, elapsedRealtime, newBucket,
                 reason);
+        maybeInformListeners(packageName, userId, elapsedRealtime,
+                newBucket);
     }
 
     private boolean isActiveDeviceAdmin(String packageName, int userId) {
@@ -896,14 +913,6 @@
         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();
@@ -1033,6 +1042,11 @@
                 int userId) {
             return appWidgetManager.isBoundWidgetPackage(packageName, userId);
         }
+
+        String getAppIdleSettings() {
+            return Settings.Global.getString(mContext.getContentResolver(),
+                    Settings.Global.APP_IDLE_CONSTANTS);
+        }
     }
 
     class AppStandbyHandler extends Handler {
@@ -1165,31 +1179,18 @@
                 // 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));
+                    mParser.setString(mInjector.getAppIdleSettings());
                 } 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 : 4 * 60 * ONE_MINUTE); // 4 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);
 
                 String screenThresholdsValue = mParser.getString(KEY_SCREEN_TIME_THRESHOLDS, null);
                 mAppStandbyScreenThresholds = parseLongArray(screenThresholdsValue,
@@ -1199,6 +1200,9 @@
                         null);
                 mAppStandbyElapsedThresholds = parseLongArray(elapsedThresholdsValue,
                         ELAPSED_TIME_THRESHOLDS);
+                mCheckIdleIntervalMillis = Math.min(mAppStandbyElapsedThresholds[1] / 4,
+                        COMPRESS_TIME ? ONE_MINUTE : 4 * 60 * ONE_MINUTE); // 4 hours
+
             }
         }
 
diff --git a/com/android/server/usage/UsageStatsService.java b/com/android/server/usage/UsageStatsService.java
index 44e6a6c..65c1cef 100644
--- a/com/android/server/usage/UsageStatsService.java
+++ b/com/android/server/usage/UsageStatsService.java
@@ -24,6 +24,7 @@
 import android.app.usage.ConfigurationStats;
 import android.app.usage.IUsageStatsManager;
 import android.app.usage.UsageEvents;
+import android.app.usage.AppStandby.StandbyBuckets;
 import android.app.usage.UsageEvents.Event;
 import android.app.usage.UsageStats;
 import android.app.usage.UsageStatsManagerInternal;
@@ -867,6 +868,12 @@
         }
 
         @Override
+        @StandbyBuckets public int getAppStandbyBucket(String packageName, int userId,
+                long nowElapsed) {
+            return mAppStandby.getAppStandbyBucket(packageName, userId, nowElapsed, false);
+        }
+
+        @Override
         public int[] getIdleUidsForUser(int userId) {
             return mAppStandby.getIdleUidsForUser(userId);
         }
diff --git a/com/android/server/usage/UserUsageStatsService.java b/com/android/server/usage/UserUsageStatsService.java
index 0b10590..f02221c 100644
--- a/com/android/server/usage/UserUsageStatsService.java
+++ b/com/android/server/usage/UserUsageStatsService.java
@@ -643,6 +643,8 @@
                 return "SHORTCUT_INVOCATION";
             case UsageEvents.Event.CHOOSER_ACTION:
                 return "CHOOSER_ACTION";
+            case UsageEvents.Event.NOTIFICATION_SEEN:
+                return "NOTIFICATION_SEEN";
             default:
                 return "UNKNOWN";
         }
diff --git a/com/android/server/usb/UsbAlsaManager.java b/com/android/server/usb/UsbAlsaManager.java
index d359b70..7bea8a1 100644
--- a/com/android/server/usb/UsbAlsaManager.java
+++ b/com/android/server/usb/UsbAlsaManager.java
@@ -255,6 +255,7 @@
     }
 
     private void alsaFileAdded(String name) {
+        Slog.i(TAG, "alsaFileAdded(" + name + ")");
         int type = AlsaDevice.TYPE_UNKNOWN;
         int card = -1, device = -1;
 
diff --git a/com/android/server/usb/UsbHostManager.java b/com/android/server/usb/UsbHostManager.java
index 095fdc6..dd2e192 100644
--- a/com/android/server/usb/UsbHostManager.java
+++ b/com/android/server/usb/UsbHostManager.java
@@ -19,13 +19,8 @@
 import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.content.Context;
-import android.hardware.usb.UsbConfiguration;
 import android.hardware.usb.UsbConstants;
 import android.hardware.usb.UsbDevice;
-import android.hardware.usb.UsbDeviceConnection;
-import android.hardware.usb.UsbEndpoint;
-import android.hardware.usb.UsbInterface;
-import android.hardware.usb.UsbManager;
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
 import android.text.TextUtils;
@@ -37,7 +32,6 @@
 import com.android.server.usb.descriptors.report.TextReportCanvas;
 import com.android.server.usb.descriptors.tree.UsbDescriptorsTree;
 
-import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
 
@@ -50,28 +44,23 @@
 
     private final Context mContext;
 
-    // contains all connected USB devices
-    private final HashMap<String, UsbDevice> mDevices = new HashMap<>();
-
     // USB busses to exclude from USB host support
     private final String[] mHostBlacklist;
 
-    private final Object mLock = new Object();
-
-    private UsbDevice mNewDevice;
-    private UsbConfiguration mNewConfiguration;
-    private UsbInterface mNewInterface;
-    private ArrayList<UsbConfiguration> mNewConfigurations;
-    private ArrayList<UsbInterface> mNewInterfaces;
-    private ArrayList<UsbEndpoint> mNewEndpoints;
-
     private final UsbAlsaManager mUsbAlsaManager;
     private final UsbSettingsManager mSettingsManager;
 
+    private final Object mLock = new Object();
     @GuardedBy("mLock")
+    // contains all connected USB devices
+    private final HashMap<String, UsbDevice> mDevices = new HashMap<>();
+
+    private Object mSettingsLock = new Object();
+    @GuardedBy("mSettingsLock")
     private UsbProfileGroupSettingsManager mCurrentSettings;
 
-    @GuardedBy("mLock")
+    private Object mHandlerLock = new Object();
+    @GuardedBy("mHandlerLock")
     private ComponentName mUsbDeviceConnectionHandler;
 
     public UsbHostManager(Context context, UsbAlsaManager alsaManager,
@@ -91,33 +80,33 @@
     }
 
     public void setCurrentUserSettings(UsbProfileGroupSettingsManager settings) {
-        synchronized (mLock) {
+        synchronized (mSettingsLock) {
             mCurrentSettings = settings;
         }
     }
 
     private UsbProfileGroupSettingsManager getCurrentUserSettings() {
-        synchronized (mLock) {
+        synchronized (mSettingsLock) {
             return mCurrentSettings;
         }
     }
 
     public void setUsbDeviceConnectionHandler(@Nullable ComponentName usbDeviceConnectionHandler) {
-        synchronized (mLock) {
+        synchronized (mHandlerLock) {
             mUsbDeviceConnectionHandler = usbDeviceConnectionHandler;
         }
     }
 
     private @Nullable ComponentName getUsbDeviceConnectionHandler() {
-        synchronized (mLock) {
+        synchronized (mHandlerLock) {
             return mUsbDeviceConnectionHandler;
         }
     }
 
-    private boolean isBlackListed(String deviceName) {
+    private boolean isBlackListed(String deviceAddress) {
         int count = mHostBlacklist.length;
         for (int i = 0; i < count; i++) {
-            if (deviceName.startsWith(mHostBlacklist[i])) {
+            if (deviceAddress.startsWith(mHostBlacklist[i])) {
                 return true;
             }
         }
@@ -136,166 +125,73 @@
     }
 
     /* Called from JNI in monitorUsbHostBus() to report new USB devices
-       Returns true if successful, in which case the JNI code will continue adding configurations,
-       interfaces and endpoints, and finally call endUsbDeviceAdded after all descriptors
-       have been processed
+       Returns true if successful, i.e. the USB Audio device descriptors are
+       correctly parsed and the unique device is added to the audio device list.
      */
     @SuppressWarnings("unused")
-    private boolean beginUsbDeviceAdded(String deviceName, int vendorID, int productID,
-            int deviceClass, int deviceSubclass, int deviceProtocol,
-            String manufacturerName, String productName, int version, String serialNumber) {
-
+    private boolean usbDeviceAdded(String deviceAddress, int deviceClass, int deviceSubclass,
+            byte[] descriptors) {
         if (DEBUG) {
-            Slog.d(TAG, "usb:UsbHostManager.beginUsbDeviceAdded(" + deviceName + ")");
-            // Audio Class Codes:
-            // Audio: 0x01
-            // Audio Subclass Codes:
-            // undefined: 0x00
-            // audio control: 0x01
-            // audio streaming: 0x02
-            // midi streaming: 0x03
-
-            // some useful debugging info
-            Slog.d(TAG, "usb: nm:" + deviceName + " vnd:" + vendorID + " prd:" + productID + " cls:"
-                    + deviceClass + " sub:" + deviceSubclass + " proto:" + deviceProtocol);
+            Slog.d(TAG, "usbDeviceAdded(" + deviceAddress + ") - start");
         }
 
-        // OK this is non-obvious, but true. One can't tell if the device being attached is even
-        // potentially an audio device without parsing the interface descriptors, so punt on any
-        // such test until endUsbDeviceAdded() when we have that info.
-
-        if (isBlackListed(deviceName) ||
-                isBlackListed(deviceClass, deviceSubclass)) {
+        // check class/subclass first as it is more likely to be blacklisted
+        if (isBlackListed(deviceClass, deviceSubclass) || isBlackListed(deviceAddress)) {
+            if (DEBUG) {
+                Slog.d(TAG, "device is black listed");
+            }
             return false;
         }
 
         synchronized (mLock) {
-            if (mDevices.get(deviceName) != null) {
-                Slog.w(TAG, "device already on mDevices list: " + deviceName);
+            if (mDevices.get(deviceAddress) != null) {
+                Slog.w(TAG, "device already on mDevices list: " + deviceAddress);
+                //TODO If this is the same peripheral as is being connected, replace
+                // it with the new connection.
                 return false;
             }
 
-            if (mNewDevice != null) {
-                Slog.e(TAG, "mNewDevice is not null in endUsbDeviceAdded");
-                return false;
-            }
+            UsbDescriptorParser parser = new UsbDescriptorParser(deviceAddress);
+            if (parser.parseDescriptors(descriptors)) {
 
-            // Create version string in "%.%" format
-            String versionString = Integer.toString(version >> 8) + "." + (version & 0xFF);
-
-            mNewDevice = new UsbDevice(deviceName, vendorID, productID,
-                    deviceClass, deviceSubclass, deviceProtocol,
-                    manufacturerName, productName, versionString, serialNumber);
-
-            mNewConfigurations = new ArrayList<>();
-            mNewInterfaces = new ArrayList<>();
-            mNewEndpoints = new ArrayList<>();
-        }
-
-        return true;
-    }
-
-    /* Called from JNI in monitorUsbHostBus() to report new USB configuration for the device
-       currently being added.  Returns true if successful, false in case of error.
-     */
-    @SuppressWarnings("unused")
-    private void addUsbConfiguration(int id, String name, int attributes, int maxPower) {
-        if (mNewConfiguration != null) {
-            mNewConfiguration.setInterfaces(
-                    mNewInterfaces.toArray(new UsbInterface[mNewInterfaces.size()]));
-            mNewInterfaces.clear();
-        }
-
-        mNewConfiguration = new UsbConfiguration(id, name, attributes, maxPower);
-        mNewConfigurations.add(mNewConfiguration);
-    }
-
-    /* Called from JNI in monitorUsbHostBus() to report new USB interface for the device
-       currently being added.  Returns true if successful, false in case of error.
-     */
-    @SuppressWarnings("unused")
-    private void addUsbInterface(int id, String name, int altSetting,
-            int Class, int subClass, int protocol) {
-        if (mNewInterface != null) {
-            mNewInterface.setEndpoints(
-                    mNewEndpoints.toArray(new UsbEndpoint[mNewEndpoints.size()]));
-            mNewEndpoints.clear();
-        }
-
-        mNewInterface = new UsbInterface(id, altSetting, name, Class, subClass, protocol);
-        mNewInterfaces.add(mNewInterface);
-    }
-
-    /* Called from JNI in monitorUsbHostBus() to report new USB endpoint for the device
-       currently being added.  Returns true if successful, false in case of error.
-     */
-    @SuppressWarnings("unused")
-    private void addUsbEndpoint(int address, int attributes, int maxPacketSize, int interval) {
-        mNewEndpoints.add(new UsbEndpoint(address, attributes, maxPacketSize, interval));
-    }
-
-    /* Called from JNI in monitorUsbHostBus() to finish adding a new device */
-    @SuppressWarnings("unused")
-    private void endUsbDeviceAdded() {
-        if (DEBUG) {
-            Slog.d(TAG, "usb:UsbHostManager.endUsbDeviceAdded()");
-        }
-        if (mNewInterface != null) {
-            mNewInterface.setEndpoints(
-                    mNewEndpoints.toArray(new UsbEndpoint[mNewEndpoints.size()]));
-        }
-        if (mNewConfiguration != null) {
-            mNewConfiguration.setInterfaces(
-                    mNewInterfaces.toArray(new UsbInterface[mNewInterfaces.size()]));
-        }
-
-
-        synchronized (mLock) {
-            if (mNewDevice != null) {
-                mNewDevice.setConfigurations(
-                        mNewConfigurations.toArray(
-                                new UsbConfiguration[mNewConfigurations.size()]));
-                mDevices.put(mNewDevice.getDeviceName(), mNewDevice);
-                Slog.d(TAG, "Added device " + mNewDevice);
+                UsbDevice newDevice = parser.toAndroidUsbDevice();
+                mDevices.put(deviceAddress, newDevice);
 
                 // It is fine to call this only for the current user as all broadcasts are sent to
                 // all profiles of the user and the dialogs should only show once.
                 ComponentName usbDeviceConnectionHandler = getUsbDeviceConnectionHandler();
                 if (usbDeviceConnectionHandler == null) {
-                    getCurrentUserSettings().deviceAttached(mNewDevice);
+                    getCurrentUserSettings().deviceAttached(newDevice);
                 } else {
-                    getCurrentUserSettings().deviceAttachedForFixedHandler(mNewDevice,
+                    getCurrentUserSettings().deviceAttachedForFixedHandler(newDevice,
                             usbDeviceConnectionHandler);
                 }
-                // deviceName is something like: "/dev/bus/usb/001/001"
-                UsbDescriptorParser parser = new UsbDescriptorParser();
-                boolean isInputHeadset = false;
-                boolean isOutputHeadset = false;
-                if (parser.parseDevice(mNewDevice.getDeviceName())) {
-                    isInputHeadset = parser.isInputHeadset();
-                    isOutputHeadset = parser.isOutputHeadset();
-                    Slog.i(TAG, "---- isHeadset[in: " + isInputHeadset
-                            + " , out: " + isOutputHeadset + "]");
-                }
-                mUsbAlsaManager.usbDeviceAdded(mNewDevice,
-                        isInputHeadset, isOutputHeadset);
+
+                // Headset?
+                boolean isInputHeadset = parser.isInputHeadset();
+                boolean isOutputHeadset = parser.isOutputHeadset();
+                Slog.i(TAG, "---- isHeadset[in: " + isInputHeadset
+                        + " , out: " + isOutputHeadset + "]");
+
+                mUsbAlsaManager.usbDeviceAdded(newDevice, isInputHeadset, isOutputHeadset);
             } else {
-                Slog.e(TAG, "mNewDevice is null in endUsbDeviceAdded");
+                Slog.e(TAG, "Error parsing USB device descriptors for " + deviceAddress);
+                return false;
             }
-            mNewDevice = null;
-            mNewConfigurations = null;
-            mNewInterfaces = null;
-            mNewEndpoints = null;
-            mNewConfiguration = null;
-            mNewInterface = null;
         }
+
+        if (DEBUG) {
+            Slog.d(TAG, "beginUsbDeviceAdded(" + deviceAddress + ") end");
+        }
+
+        return true;
     }
 
     /* Called from JNI in monitorUsbHostBus to report USB device removal */
     @SuppressWarnings("unused")
-    private void usbDeviceRemoved(String deviceName) {
+    private void usbDeviceRemoved(String deviceAddress) {
         synchronized (mLock) {
-            UsbDevice device = mDevices.remove(deviceName);
+            UsbDevice device = mDevices.remove(deviceAddress);
             if (device != null) {
                 mUsbAlsaManager.usbDeviceRemoved(device);
                 mSettingsManager.usbDeviceRemoved(device);
@@ -323,31 +219,35 @@
     }
 
     /* Opens the specified USB device */
-    public ParcelFileDescriptor openDevice(String deviceName, UsbUserSettingsManager settings) {
+    public ParcelFileDescriptor openDevice(String deviceAddress, UsbUserSettingsManager settings,
+            String packageName, int uid) {
         synchronized (mLock) {
-            if (isBlackListed(deviceName)) {
+            if (isBlackListed(deviceAddress)) {
                 throw new SecurityException("USB device is on a restricted bus");
             }
-            UsbDevice device = mDevices.get(deviceName);
+            UsbDevice device = mDevices.get(deviceAddress);
             if (device == null) {
                 // if it is not in mDevices, it either does not exist or is blacklisted
                 throw new IllegalArgumentException(
-                        "device " + deviceName + " does not exist or is restricted");
+                        "device " + deviceAddress + " does not exist or is restricted");
             }
-            settings.checkPermission(device);
-            return nativeOpenDevice(deviceName);
+
+            settings.checkPermission(device, packageName, uid);
+            return nativeOpenDevice(deviceAddress);
         }
     }
 
     public void dump(IndentingPrintWriter pw) {
-        synchronized (mLock) {
-            pw.println("USB Host State:");
-            for (String name : mDevices.keySet()) {
-                pw.println("  " + name + ": " + mDevices.get(name));
-            }
+        pw.println("USB Host State:");
+        synchronized (mHandlerLock) {
             if (mUsbDeviceConnectionHandler != null) {
                 pw.println("Default USB Host Connection handler: " + mUsbDeviceConnectionHandler);
             }
+        }
+        synchronized (mLock) {
+            for (String name : mDevices.keySet()) {
+                pw.println("  " + name + ": " + mDevices.get(name));
+            }
 
             Collection<UsbDevice> devices = mDevices.values();
             if (devices.size() != 0) {
@@ -355,17 +255,12 @@
                 for (UsbDevice device : devices) {
                     StringBuilder stringBuilder = new StringBuilder();
 
-                    UsbDescriptorParser parser = new UsbDescriptorParser();
-                    if (parser.parseDevice(device.getDeviceName())) {
+                    UsbDescriptorParser parser = new UsbDescriptorParser(device.getDeviceName());
+                    if (parser.parseDevice()) {
                         UsbDescriptorsTree descriptorTree = new UsbDescriptorsTree();
                         descriptorTree.parse(parser);
 
-                        UsbManager usbManager =
-                                (UsbManager) mContext.getSystemService(Context.USB_SERVICE);
-                        UsbDeviceConnection connection = usbManager.openDevice(device);
-
-                        descriptorTree.report(new TextReportCanvas(connection, stringBuilder));
-                        connection.close();
+                        descriptorTree.report(new TextReportCanvas(parser, stringBuilder));
 
                         stringBuilder.append("isHeadset[in: " + parser.isInputHeadset()
                                 + " , out: " + parser.isOutputHeadset() + "]");
@@ -381,5 +276,5 @@
     }
 
     private native void monitorUsbHostBus();
-    private native ParcelFileDescriptor nativeOpenDevice(String deviceName);
+    private native ParcelFileDescriptor nativeOpenDevice(String deviceAddress);
 }
diff --git a/com/android/server/usb/UsbService.java b/com/android/server/usb/UsbService.java
index e4fcea7..17de83f 100644
--- a/com/android/server/usb/UsbService.java
+++ b/com/android/server/usb/UsbService.java
@@ -232,7 +232,7 @@
 
     /* Opens the specified USB device (host mode) */
     @Override
-    public ParcelFileDescriptor openDevice(String deviceName) {
+    public ParcelFileDescriptor openDevice(String deviceName, String packageName) {
         ParcelFileDescriptor fd = null;
 
         if (mHostManager != null) {
@@ -242,7 +242,8 @@
                     boolean isCurrentUser = isCallerInCurrentUserProfileGroupLocked();
 
                     if (isCurrentUser) {
-                        fd = mHostManager.openDevice(deviceName, getSettingsForUser(userIdInt));
+                        fd = mHostManager.openDevice(deviceName, getSettingsForUser(userIdInt),
+                                packageName, Binder.getCallingUid());
                     } else {
                         Slog.w(TAG, "Cannot open " + deviceName + " for user " + userIdInt +
                                " as user is not active.");
@@ -308,9 +309,10 @@
     }
 
     @Override
-    public boolean hasDevicePermission(UsbDevice device) {
+    public boolean hasDevicePermission(UsbDevice device, String packageName) {
         final int userId = UserHandle.getCallingUserId();
-        return getSettingsForUser(userId).hasPermission(device);
+        return getSettingsForUser(userId).hasPermission(device, packageName,
+                Binder.getCallingUid());
     }
 
     @Override
@@ -322,7 +324,8 @@
     @Override
     public void requestDevicePermission(UsbDevice device, String packageName, PendingIntent pi) {
         final int userId = UserHandle.getCallingUserId();
-        getSettingsForUser(userId).requestPermission(device, packageName, pi);
+        getSettingsForUser(userId).requestPermission(device, packageName, pi,
+                Binder.getCallingUid());
     }
 
     @Override
diff --git a/com/android/server/usb/UsbUserSettingsManager.java b/com/android/server/usb/UsbUserSettingsManager.java
index 96c5211..11e43e3 100644
--- a/com/android/server/usb/UsbUserSettingsManager.java
+++ b/com/android/server/usb/UsbUserSettingsManager.java
@@ -26,6 +26,8 @@
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.hardware.usb.UsbAccessory;
 import android.hardware.usb.UsbDevice;
+import android.hardware.usb.UsbInterface;
+import android.hardware.usb.UsbConstants;
 import android.hardware.usb.UsbManager;
 import android.os.Binder;
 import android.os.Process;
@@ -95,10 +97,70 @@
         }
     }
 
+    /**
+     * Check whether a particular device or any of its interfaces
+     * is of class VIDEO.
+     *
+     * @param device The device that needs to get scanned
+     * @return True in case a VIDEO device or interface is present,
+     *         False otherwise.
+     */
+    private boolean isCameraDevicePresent(UsbDevice device) {
+        if (device.getDeviceClass() == UsbConstants.USB_CLASS_VIDEO) {
+            return true;
+        }
 
-    public boolean hasPermission(UsbDevice device) {
+        for (int i = 0; i < device.getInterfaceCount(); i++) {
+            UsbInterface iface = device.getInterface(i);
+            if (iface.getInterfaceClass() == UsbConstants.USB_CLASS_VIDEO) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Check for camera permission of the calling process.
+     *
+     * @param packageName Package name of the caller.
+     * @param uid Linux uid of the calling process.
+     *
+     * @return True in case camera permission is available, False otherwise.
+     */
+    private boolean isCameraPermissionGranted(String packageName, int uid) {
+        int targetSdkVersion = android.os.Build.VERSION_CODES.P;
+        try {
+            ApplicationInfo aInfo = mPackageManager.getApplicationInfo(packageName, 0);
+            // compare uid with packageName to foil apps pretending to be someone else
+            if (aInfo.uid != uid) {
+                Slog.i(TAG, "Package " + packageName + " does not match caller's uid " + uid);
+                return false;
+            }
+            targetSdkVersion = aInfo.targetSdkVersion;
+        } catch (PackageManager.NameNotFoundException e) {
+            Slog.i(TAG, "Package not found, likely due to invalid package name!");
+            return false;
+        }
+
+        if (targetSdkVersion >= android.os.Build.VERSION_CODES.P) {
+            int allowed = mUserContext.checkCallingPermission(android.Manifest.permission.CAMERA);
+            if (android.content.pm.PackageManager.PERMISSION_DENIED == allowed) {
+                Slog.i(TAG, "Camera permission required for USB video class devices");
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    public boolean hasPermission(UsbDevice device, String packageName, int uid) {
         synchronized (mLock) {
-            int uid = Binder.getCallingUid();
+            if (isCameraDevicePresent(device)) {
+                if (!isCameraPermissionGranted(packageName, uid)) {
+                    return false;
+                }
+            }
             if (uid == Process.SYSTEM_UID || mDisablePermissionDialogs) {
                 return true;
             }
@@ -124,8 +186,8 @@
         }
     }
 
-    public void checkPermission(UsbDevice device) {
-        if (!hasPermission(device)) {
+    public void checkPermission(UsbDevice device, String packageName, int uid) {
+        if (!hasPermission(device, packageName, uid)) {
             throw new SecurityException("User has not given permission to device " + device);
         }
     }
@@ -166,11 +228,11 @@
         }
     }
 
-    public void requestPermission(UsbDevice device, String packageName, PendingIntent pi) {
+    public void requestPermission(UsbDevice device, String packageName, PendingIntent pi, int uid) {
       Intent intent = new Intent();
 
         // respond immediately if permission has already been granted
-      if (hasPermission(device)) {
+      if (hasPermission(device, packageName, uid)) {
             intent.putExtra(UsbManager.EXTRA_DEVICE, device);
             intent.putExtra(UsbManager.EXTRA_PERMISSION_GRANTED, true);
             try {
@@ -180,6 +242,18 @@
             }
             return;
         }
+        if (isCameraDevicePresent(device)) {
+            if (!isCameraPermissionGranted(packageName, uid)) {
+                intent.putExtra(UsbManager.EXTRA_DEVICE, device);
+                intent.putExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false);
+                try {
+                    pi.send(mUserContext, 0, intent);
+                } catch (PendingIntent.CanceledException e) {
+                    if (DEBUG) Slog.d(TAG, "requestPermission PendingIntent was cancelled");
+                }
+                return;
+            }
+        }
 
         // start UsbPermissionActivity so user can choose an activity
         intent.putExtra(UsbManager.EXTRA_DEVICE, device);
diff --git a/com/android/server/usb/descriptors/Usb10ACHeader.java b/com/android/server/usb/descriptors/Usb10ACHeader.java
index a35b463..7763150 100644
--- a/com/android/server/usb/descriptors/Usb10ACHeader.java
+++ b/com/android/server/usb/descriptors/Usb10ACHeader.java
@@ -32,7 +32,7 @@
                                             // numbers associate with this endpoint
     private byte mControls;                 // Vers 2.0 thing
 
-    public Usb10ACHeader(int length, byte type, byte subtype, byte subclass, int spec) {
+    public Usb10ACHeader(int length, byte type, byte subtype, int subclass, int spec) {
         super(length, type, subtype, subclass, spec);
     }
 
diff --git a/com/android/server/usb/descriptors/Usb10ACInputTerminal.java b/com/android/server/usb/descriptors/Usb10ACInputTerminal.java
index 2363c4d..75531d1 100644
--- a/com/android/server/usb/descriptors/Usb10ACInputTerminal.java
+++ b/com/android/server/usb/descriptors/Usb10ACInputTerminal.java
@@ -32,7 +32,7 @@
     private byte mChannelNames;     // 10:1 Unused (0x00)
     private byte mTerminal;         // 11:1 Unused (0x00)
 
-    public Usb10ACInputTerminal(int length, byte type, byte subtype, byte subclass) {
+    public Usb10ACInputTerminal(int length, byte type, byte subtype, int subclass) {
         super(length, type, subtype, subclass);
     }
 
diff --git a/com/android/server/usb/descriptors/Usb10ACMixerUnit.java b/com/android/server/usb/descriptors/Usb10ACMixerUnit.java
index d348664..c7634ba 100644
--- a/com/android/server/usb/descriptors/Usb10ACMixerUnit.java
+++ b/com/android/server/usb/descriptors/Usb10ACMixerUnit.java
@@ -30,7 +30,7 @@
     private byte[] mControls;   // bitmasks of which controls are present for each channel
     private byte mNameID;       // string descriptor ID of mixer name
 
-    public Usb10ACMixerUnit(int length, byte type, byte subtype, byte subClass) {
+    public Usb10ACMixerUnit(int length, byte type, byte subtype, int subClass) {
         super(length, type, subtype, subClass);
     }
 
diff --git a/com/android/server/usb/descriptors/Usb10ACOutputTerminal.java b/com/android/server/usb/descriptors/Usb10ACOutputTerminal.java
index 9f2f09e..468ae57 100644
--- a/com/android/server/usb/descriptors/Usb10ACOutputTerminal.java
+++ b/com/android/server/usb/descriptors/Usb10ACOutputTerminal.java
@@ -28,7 +28,7 @@
     private byte mSourceID;         // 7:1 From Input Terminal. (0x01)
     private byte mTerminal;         // 8:1 Unused.
 
-    public Usb10ACOutputTerminal(int length, byte type, byte subtype, byte subClass) {
+    public Usb10ACOutputTerminal(int length, byte type, byte subtype, int subClass) {
         super(length, type, subtype, subClass);
     }
 
diff --git a/com/android/server/usb/descriptors/Usb10ASFormatI.java b/com/android/server/usb/descriptors/Usb10ASFormatI.java
index 1523bb5..1d8498a 100644
--- a/com/android/server/usb/descriptors/Usb10ASFormatI.java
+++ b/com/android/server/usb/descriptors/Usb10ASFormatI.java
@@ -33,7 +33,7 @@
                                     // min & max rates otherwise mSamFreqType rates.
                                     // All 3-byte values. All rates in Hz
 
-    public Usb10ASFormatI(int length, byte type, byte subtype, byte formatType, byte subclass) {
+    public Usb10ASFormatI(int length, byte type, byte subtype, byte formatType, int subclass) {
         super(length, type, subtype, formatType, subclass);
     }
 
diff --git a/com/android/server/usb/descriptors/Usb10ASFormatII.java b/com/android/server/usb/descriptors/Usb10ASFormatII.java
index b1e7680..3c45790 100644
--- a/com/android/server/usb/descriptors/Usb10ASFormatII.java
+++ b/com/android/server/usb/descriptors/Usb10ASFormatII.java
@@ -38,7 +38,7 @@
                                 // the min & max rates. otherwise mSamFreqType rates.
                                 // All 3-byte values. All rates in Hz
 
-    public Usb10ASFormatII(int length, byte type, byte subtype, byte formatType, byte subclass) {
+    public Usb10ASFormatII(int length, byte type, byte subtype, byte formatType, int subclass) {
         super(length, type, subtype, formatType, subclass);
     }
 
diff --git a/com/android/server/usb/descriptors/Usb10ASGeneral.java b/com/android/server/usb/descriptors/Usb10ASGeneral.java
index 2d4f604..4fbbb21 100644
--- a/com/android/server/usb/descriptors/Usb10ASGeneral.java
+++ b/com/android/server/usb/descriptors/Usb10ASGeneral.java
@@ -34,7 +34,7 @@
     private int mFormatTag;     // 5:2 The Audio Data Format that has to be used to communicate
                                 // with this interface.
 
-    public Usb10ASGeneral(int length, byte type, byte subtype, byte subclass) {
+    public Usb10ASGeneral(int length, byte type, byte subtype, int subclass) {
         super(length, type, subtype, subclass);
     }
 
diff --git a/com/android/server/usb/descriptors/Usb20ACHeader.java b/com/android/server/usb/descriptors/Usb20ACHeader.java
index eefae3d..fe1b502 100644
--- a/com/android/server/usb/descriptors/Usb20ACHeader.java
+++ b/com/android/server/usb/descriptors/Usb20ACHeader.java
@@ -29,7 +29,7 @@
                                 // See audio20.pdf Appendix A.7, “Audio Function Category Codes.”
     private byte mControls;     // 8:1 See audio20.pdf Table 4-5.
 
-    public Usb20ACHeader(int length, byte type, byte subtype, byte subclass, int spec) {
+    public Usb20ACHeader(int length, byte type, byte subtype, int subclass, int spec) {
         super(length, type, subtype, subclass, spec);
     }
 
diff --git a/com/android/server/usb/descriptors/Usb20ACInputTerminal.java b/com/android/server/usb/descriptors/Usb20ACInputTerminal.java
index 3e2ac39..ee1b32c 100644
--- a/com/android/server/usb/descriptors/Usb20ACInputTerminal.java
+++ b/com/android/server/usb/descriptors/Usb20ACInputTerminal.java
@@ -39,7 +39,7 @@
     private byte mTerminalName; // 16:1 - Index of a string descriptor, describing the
                                 // Input Terminal.
 
-    public Usb20ACInputTerminal(int length, byte type, byte subtype, byte subclass) {
+    public Usb20ACInputTerminal(int length, byte type, byte subtype, int subclass) {
         super(length, type, subtype, subclass);
     }
 
diff --git a/com/android/server/usb/descriptors/Usb20ACMixerUnit.java b/com/android/server/usb/descriptors/Usb20ACMixerUnit.java
index 1b267a6..ab96585 100644
--- a/com/android/server/usb/descriptors/Usb20ACMixerUnit.java
+++ b/com/android/server/usb/descriptors/Usb20ACMixerUnit.java
@@ -33,7 +33,7 @@
     private byte mNameID;       // 12+p+N:1 Index of a string descriptor, describing the
                                 // Mixer Unit.
 
-    public Usb20ACMixerUnit(int length, byte type, byte subtype, byte subClass) {
+    public Usb20ACMixerUnit(int length, byte type, byte subtype, int subClass) {
         super(length, type, subtype, subClass);
     }
 
diff --git a/com/android/server/usb/descriptors/Usb20ACOutputTerminal.java b/com/android/server/usb/descriptors/Usb20ACOutputTerminal.java
index 67478aa..20a97af 100644
--- a/com/android/server/usb/descriptors/Usb20ACOutputTerminal.java
+++ b/com/android/server/usb/descriptors/Usb20ACOutputTerminal.java
@@ -34,7 +34,7 @@
     private int mControls;      // 9:2 - see Audio20.pdf Table 4-10
     private byte mTerminalID;   // 11:1 - Index of a string descriptor, describing the
 
-    public Usb20ACOutputTerminal(int length, byte type, byte subtype, byte subClass) {
+    public Usb20ACOutputTerminal(int length, byte type, byte subtype, int subClass) {
         super(length, type, subtype, subClass);
     }
 
diff --git a/com/android/server/usb/descriptors/Usb20ASFormatI.java b/com/android/server/usb/descriptors/Usb20ASFormatI.java
index c031996..7310a3e 100644
--- a/com/android/server/usb/descriptors/Usb20ASFormatI.java
+++ b/com/android/server/usb/descriptors/Usb20ASFormatI.java
@@ -31,7 +31,7 @@
     private byte mBitResolution;    // 5:1 The number of effectively used bits from
                                     // the available bits in an audio subslot.
 
-    public Usb20ASFormatI(int length, byte type, byte subtype, byte formatType, byte subclass) {
+    public Usb20ASFormatI(int length, byte type, byte subtype, byte formatType, int subclass) {
         super(length, type, subtype, formatType, subclass);
     }
 
diff --git a/com/android/server/usb/descriptors/Usb20ASFormatII.java b/com/android/server/usb/descriptors/Usb20ASFormatII.java
index dc44ff0..fe88743 100644
--- a/com/android/server/usb/descriptors/Usb20ASFormatII.java
+++ b/com/android/server/usb/descriptors/Usb20ASFormatII.java
@@ -34,7 +34,7 @@
     /**
      * TBD
      */
-    public Usb20ASFormatII(int length, byte type, byte subtype, byte formatType, byte subclass) {
+    public Usb20ASFormatII(int length, byte type, byte subtype, byte formatType, int subclass) {
         super(length, type, subtype, formatType, subclass);
     }
 
diff --git a/com/android/server/usb/descriptors/Usb20ASFormatIII.java b/com/android/server/usb/descriptors/Usb20ASFormatIII.java
index b44a216..b0ba02f 100644
--- a/com/android/server/usb/descriptors/Usb20ASFormatIII.java
+++ b/com/android/server/usb/descriptors/Usb20ASFormatIII.java
@@ -31,7 +31,7 @@
     private byte mBitResolution;    // 5:1 The number of effectively used bits from
                                     // the available bits in an audio subframe.
 
-    public Usb20ASFormatIII(int length, byte type, byte subtype, byte formatType, byte subclass) {
+    public Usb20ASFormatIII(int length, byte type, byte subtype, byte formatType, int subclass) {
         super(length, type, subtype, formatType, subclass);
     }
 
diff --git a/com/android/server/usb/descriptors/Usb20ASGeneral.java b/com/android/server/usb/descriptors/Usb20ASGeneral.java
index 18d48a0..de20738 100644
--- a/com/android/server/usb/descriptors/Usb20ASGeneral.java
+++ b/com/android/server/usb/descriptors/Usb20ASGeneral.java
@@ -41,7 +41,7 @@
     private byte mChannelNames; // 15:1 Index of a string descriptor, describing the
                                 // name of the first physical channel.
 
-    public Usb20ASGeneral(int length, byte type, byte subtype, byte subclass) {
+    public Usb20ASGeneral(int length, byte type, byte subtype, int subclass) {
         super(length, type, subtype, subclass);
     }
 
diff --git a/com/android/server/usb/descriptors/UsbACAudioControlEndpoint.java b/com/android/server/usb/descriptors/UsbACAudioControlEndpoint.java
index 6e1ce07..409e605 100644
--- a/com/android/server/usb/descriptors/UsbACAudioControlEndpoint.java
+++ b/com/android/server/usb/descriptors/UsbACAudioControlEndpoint.java
@@ -38,7 +38,7 @@
     static final byte ATTRIBSMASK_SYNC  = 0x0C;
     static final byte ATTRIBMASK_TRANS  = 0x03;
 
-    public UsbACAudioControlEndpoint(int length, byte type, byte subclass) {
+    public UsbACAudioControlEndpoint(int length, byte type, int subclass) {
         super(length, type, subclass);
     }
 
diff --git a/com/android/server/usb/descriptors/UsbACAudioStreamEndpoint.java b/com/android/server/usb/descriptors/UsbACAudioStreamEndpoint.java
index d351902..e63bb74 100644
--- a/com/android/server/usb/descriptors/UsbACAudioStreamEndpoint.java
+++ b/com/android/server/usb/descriptors/UsbACAudioStreamEndpoint.java
@@ -24,7 +24,7 @@
     private static final String TAG = "UsbACAudioStreamEndpoint";
 
     //TODO data fields...
-    public UsbACAudioStreamEndpoint(int length, byte type, byte subclass) {
+    public UsbACAudioStreamEndpoint(int length, byte type, int subclass) {
         super(length, type, subclass);
     }
 
diff --git a/com/android/server/usb/descriptors/UsbACEndpoint.java b/com/android/server/usb/descriptors/UsbACEndpoint.java
index 4a6839d..7ebccf3 100644
--- a/com/android/server/usb/descriptors/UsbACEndpoint.java
+++ b/com/android/server/usb/descriptors/UsbACEndpoint.java
@@ -25,16 +25,16 @@
 abstract class UsbACEndpoint extends UsbDescriptor {
     private static final String TAG = "UsbACEndpoint";
 
-    protected final byte mSubclass; // from the mSubclass member of the "enclosing"
-                                    // Interface Descriptor, not the stream.
-    protected byte mSubtype;        // 2:1 HEADER descriptor subtype
+    protected final int mSubclass; // from the mSubclass member of the "enclosing"
+                                   // Interface Descriptor, not the stream.
+    protected byte mSubtype;       // 2:1 HEADER descriptor subtype
 
-    UsbACEndpoint(int length, byte type, byte subclass) {
+    UsbACEndpoint(int length, byte type, int subclass) {
         super(length, type);
         mSubclass = subclass;
     }
 
-    public byte getSubclass() {
+    public int getSubclass() {
         return mSubclass;
     }
 
@@ -52,7 +52,7 @@
     public static UsbDescriptor allocDescriptor(UsbDescriptorParser parser,
                                                 int length, byte type) {
         UsbInterfaceDescriptor interfaceDesc = parser.getCurInterface();
-        byte subClass = interfaceDesc.getUsbSubclass();
+        int subClass = interfaceDesc.getUsbSubclass();
         switch (subClass) {
             case AUDIO_AUDIOCONTROL:
                 return new UsbACAudioControlEndpoint(length, type, subClass);
diff --git a/com/android/server/usb/descriptors/UsbACFeatureUnit.java b/com/android/server/usb/descriptors/UsbACFeatureUnit.java
index ab3903b..2c7ef79 100644
--- a/com/android/server/usb/descriptors/UsbACFeatureUnit.java
+++ b/com/android/server/usb/descriptors/UsbACFeatureUnit.java
@@ -46,7 +46,7 @@
                                 // logical channel
     private byte mUnitName;     // ?:1 Index of a string descriptor, describing this Feature Unit.
 
-    public UsbACFeatureUnit(int length, byte type, byte subtype, byte subClass) {
+    public UsbACFeatureUnit(int length, byte type, byte subtype, int subClass) {
         super(length, type, subtype, subClass);
     }
 
diff --git a/com/android/server/usb/descriptors/UsbACHeaderInterface.java b/com/android/server/usb/descriptors/UsbACHeaderInterface.java
index 01a355e..88d026e 100644
--- a/com/android/server/usb/descriptors/UsbACHeaderInterface.java
+++ b/com/android/server/usb/descriptors/UsbACHeaderInterface.java
@@ -31,7 +31,7 @@
                                 // of this descriptor header and all Unit and Terminal descriptors.
 
     public UsbACHeaderInterface(
-            int length, byte type, byte subtype, byte subclass, int adcRelease) {
+            int length, byte type, byte subtype, int subclass, int adcRelease) {
         super(length, type, subtype, subclass);
         mADCRelease = adcRelease;
     }
diff --git a/com/android/server/usb/descriptors/UsbACInterface.java b/com/android/server/usb/descriptors/UsbACInterface.java
index df6c53f..38c12a1 100644
--- a/com/android/server/usb/descriptors/UsbACInterface.java
+++ b/com/android/server/usb/descriptors/UsbACInterface.java
@@ -78,10 +78,10 @@
     public static final int FORMAT_III_IEC1937_MPEG2_Layer1LS = 0x2005;
 
     protected final byte mSubtype;  // 2:1 HEADER descriptor subtype
-    protected final byte mSubclass; // from the mSubclass member of the
+    protected final int mSubclass;  // from the mSubclass member of the
                                     // "enclosing" Interface Descriptor
 
-    public UsbACInterface(int length, byte type, byte subtype, byte subclass) {
+    public UsbACInterface(int length, byte type, byte subtype, int subclass) {
         super(length, type);
         mSubtype = subtype;
         mSubclass = subclass;
@@ -91,12 +91,12 @@
         return mSubtype;
     }
 
-    public byte getSubclass() {
+    public int getSubclass() {
         return mSubclass;
     }
 
     private static UsbDescriptor allocAudioControlDescriptor(UsbDescriptorParser parser,
-            ByteStream stream, int length, byte type, byte subtype, byte subClass) {
+            ByteStream stream, int length, byte type, byte subtype, int subClass) {
         switch (subtype) {
             case ACI_HEADER:
             {
@@ -157,7 +157,7 @@
     }
 
     private static UsbDescriptor allocAudioStreamingDescriptor(UsbDescriptorParser parser,
-            ByteStream stream, int length, byte type, byte subtype, byte subClass) {
+            ByteStream stream, int length, byte type, byte subtype, int subClass) {
         //int spec = parser.getUsbSpec();
         int acInterfaceSpec = parser.getACInterfaceSpec();
         switch (subtype) {
@@ -182,7 +182,7 @@
     }
 
     private static UsbDescriptor allocMidiStreamingDescriptor(int length, byte type,
-            byte subtype, byte subClass) {
+            byte subtype, int subClass) {
         switch (subtype) {
             case MSI_HEADER:
                 return new UsbMSMidiHeader(length, type, subtype, subClass);
@@ -212,7 +212,7 @@
             int length, byte type) {
         byte subtype = stream.getByte();
         UsbInterfaceDescriptor interfaceDesc = parser.getCurInterface();
-        byte subClass = interfaceDesc.getUsbSubclass();
+        int subClass = interfaceDesc.getUsbSubclass();
         switch (subClass) {
             case AUDIO_AUDIOCONTROL:
                 return allocAudioControlDescriptor(
@@ -236,7 +236,7 @@
     public void report(ReportCanvas canvas) {
         super.report(canvas);
 
-        byte subClass = getSubclass();
+        int subClass = getSubclass();
         String subClassName = UsbStrings.getACInterfaceSubclassName(subClass);
 
         byte subtype = getSubtype();
diff --git a/com/android/server/usb/descriptors/UsbACInterfaceUnparsed.java b/com/android/server/usb/descriptors/UsbACInterfaceUnparsed.java
index 9e00a79..bd027ae 100644
--- a/com/android/server/usb/descriptors/UsbACInterfaceUnparsed.java
+++ b/com/android/server/usb/descriptors/UsbACInterfaceUnparsed.java
@@ -22,7 +22,7 @@
 public final class UsbACInterfaceUnparsed extends UsbACInterface {
     private static final String TAG = "UsbACInterfaceUnparsed";
 
-    public UsbACInterfaceUnparsed(int length, byte type, byte subtype, byte subClass) {
+    public UsbACInterfaceUnparsed(int length, byte type, byte subtype, int subClass) {
         super(length, type, subtype, subClass);
     }
 }
diff --git a/com/android/server/usb/descriptors/UsbACMidiEndpoint.java b/com/android/server/usb/descriptors/UsbACMidiEndpoint.java
index 9c31457..42ee889 100644
--- a/com/android/server/usb/descriptors/UsbACMidiEndpoint.java
+++ b/com/android/server/usb/descriptors/UsbACMidiEndpoint.java
@@ -28,7 +28,7 @@
     private byte mNumJacks;
     private byte[] mJackIds;
 
-    public UsbACMidiEndpoint(int length, byte type, byte subclass) {
+    public UsbACMidiEndpoint(int length, byte type, int subclass) {
         super(length, type, subclass);
     }
 
diff --git a/com/android/server/usb/descriptors/UsbACMixerUnit.java b/com/android/server/usb/descriptors/UsbACMixerUnit.java
index 88faed9..606fa2b 100644
--- a/com/android/server/usb/descriptors/UsbACMixerUnit.java
+++ b/com/android/server/usb/descriptors/UsbACMixerUnit.java
@@ -24,7 +24,7 @@
                                     // are connected.
     protected byte mNumOutputs;     // The number of output channels
 
-    public UsbACMixerUnit(int length, byte type, byte subtype, byte subClass) {
+    public UsbACMixerUnit(int length, byte type, byte subtype, int subClass) {
         super(length, type, subtype, subClass);
     }
 
diff --git a/com/android/server/usb/descriptors/UsbACSelectorUnit.java b/com/android/server/usb/descriptors/UsbACSelectorUnit.java
index b16bc57..4644fe1 100644
--- a/com/android/server/usb/descriptors/UsbACSelectorUnit.java
+++ b/com/android/server/usb/descriptors/UsbACSelectorUnit.java
@@ -32,7 +32,7 @@
                                 // Input Pin of this Selector Unit is connected.
     private byte mNameIndex;    // Index of a string descriptor, describing the Selector Unit.
 
-    public UsbACSelectorUnit(int length, byte type, byte subtype, byte subClass) {
+    public UsbACSelectorUnit(int length, byte type, byte subtype, int subClass) {
         super(length, type, subtype, subClass);
     }
 
diff --git a/com/android/server/usb/descriptors/UsbACTerminal.java b/com/android/server/usb/descriptors/UsbACTerminal.java
index 2836508..36139d6 100644
--- a/com/android/server/usb/descriptors/UsbACTerminal.java
+++ b/com/android/server/usb/descriptors/UsbACTerminal.java
@@ -32,7 +32,7 @@
     protected int mTerminalType;      // 4:2 USB Streaming. (0x0101)
     protected byte mAssocTerminal;    // 6:1 Unused (0x00)
 
-    public UsbACTerminal(int length, byte type, byte subtype, byte subclass) {
+    public UsbACTerminal(int length, byte type, byte subtype, int subclass) {
         super(length, type, subtype, subclass);
     }
 
diff --git a/com/android/server/usb/descriptors/UsbASFormat.java b/com/android/server/usb/descriptors/UsbASFormat.java
index 7a92f9e..5e515a1 100644
--- a/com/android/server/usb/descriptors/UsbASFormat.java
+++ b/com/android/server/usb/descriptors/UsbASFormat.java
@@ -40,7 +40,7 @@
     public static final byte EXT_FORMAT_TYPE_II     = (byte) 0x82;
     public static final byte EXT_FORMAT_TYPE_III    = (byte) 0x83;
 
-    public UsbASFormat(int length, byte type, byte subtype, byte formatType, byte mSubclass) {
+    public UsbASFormat(int length, byte type, byte subtype, byte formatType, int mSubclass) {
         super(length, type, subtype, mSubclass);
         mFormatType = formatType;
     }
@@ -66,8 +66,8 @@
      * stream.
      */
     public static UsbDescriptor allocDescriptor(UsbDescriptorParser parser,
-                                                ByteStream stream, int length, byte type,
-            byte subtype, byte subclass) {
+            ByteStream stream, int length, byte type,
+            byte subtype, int subclass) {
 
         byte formatType = stream.getByte();
         int acInterfaceSpec = parser.getACInterfaceSpec();
diff --git a/com/android/server/usb/descriptors/UsbConfigDescriptor.java b/com/android/server/usb/descriptors/UsbConfigDescriptor.java
index 75279c6..993778f 100644
--- a/com/android/server/usb/descriptors/UsbConfigDescriptor.java
+++ b/com/android/server/usb/descriptors/UsbConfigDescriptor.java
@@ -15,8 +15,13 @@
  */
 package com.android.server.usb.descriptors;
 
+import android.hardware.usb.UsbConfiguration;
+import android.hardware.usb.UsbInterface;
+
 import com.android.server.usb.descriptors.report.ReportCanvas;
 
+import java.util.ArrayList;
+
 /**
  * @hide
  * An USB Config Descriptor.
@@ -25,15 +30,18 @@
 public final class UsbConfigDescriptor extends UsbDescriptor {
     private static final String TAG = "UsbConfigDescriptor";
 
-    private int mTotalLength;   // 2:2 Total length in bytes of data returned
+    private int mTotalLength;    // 2:2 Total length in bytes of data returned
     private byte mNumInterfaces; // 4:1 Number of Interfaces
-    private byte mConfigValue;  // 5:1 Value to use as an argument to select this configuration
-    private byte mConfigIndex;  // 6:1 Index of String Descriptor describing this configuration
-    private byte mAttribs;      // 7:1 D7 Reserved, set to 1. (USB 1.0 Bus Powered)
-                                //     D6 Self Powered
-                                //     D5 Remote Wakeup
-                                //     D4..0 Reserved, set to 0.
-    private byte mMaxPower;     // 8:1 Maximum Power Consumption in 2mA units
+    private int mConfigValue;    // 5:1 Value to use as an argument to select this configuration
+    private byte mConfigIndex;   // 6:1 Index of String Descriptor describing this configuration
+    private int mAttribs;        // 7:1 D7 Reserved, set to 1. (USB 1.0 Bus Powered)
+                                 //     D6 Self Powered
+                                 //     D5 Remote Wakeup
+                                 //     D4..0 Reserved, set to 0.
+    private int mMaxPower;       // 8:1 Maximum Power Consumption in 2mA units
+
+    private ArrayList<UsbInterfaceDescriptor> mInterfaceDescriptors =
+            new ArrayList<UsbInterfaceDescriptor>();
 
     UsbConfigDescriptor(int length, byte type) {
         super(length, type);
@@ -48,7 +56,7 @@
         return mNumInterfaces;
     }
 
-    public byte getConfigValue() {
+    public int getConfigValue() {
         return mConfigValue;
     }
 
@@ -56,22 +64,38 @@
         return mConfigIndex;
     }
 
-    public byte getAttribs() {
+    public int getAttribs() {
         return mAttribs;
     }
 
-    public byte getMaxPower() {
+    public int getMaxPower() {
         return mMaxPower;
     }
 
+    void addInterfaceDescriptor(UsbInterfaceDescriptor interfaceDesc) {
+        mInterfaceDescriptors.add(interfaceDesc);
+    }
+
+    UsbConfiguration toAndroid(UsbDescriptorParser parser) {
+        String name = parser.getDescriptorString(mConfigIndex);
+        UsbConfiguration config = new
+                UsbConfiguration(mConfigValue, name, mAttribs, mMaxPower);
+        UsbInterface[] interfaces = new UsbInterface[mInterfaceDescriptors.size()];
+        for (int index = 0; index < mInterfaceDescriptors.size(); index++) {
+            interfaces[index] = mInterfaceDescriptors.get(index).toAndroid(parser);
+        }
+        config.setInterfaces(interfaces);
+        return config;
+    }
+
     @Override
     public int parseRawDescriptors(ByteStream stream) {
         mTotalLength = stream.unpackUsbShort();
         mNumInterfaces = stream.getByte();
-        mConfigValue = stream.getByte();
+        mConfigValue = stream.getUnsignedByte();
         mConfigIndex = stream.getByte();
-        mAttribs = stream.getByte();
-        mMaxPower = stream.getByte();
+        mAttribs = stream.getUnsignedByte();
+        mMaxPower = stream.getUnsignedByte();
 
         return mLength;
     }
diff --git a/com/android/server/usb/descriptors/UsbDescriptor.java b/com/android/server/usb/descriptors/UsbDescriptor.java
index 8c7565b..3fc5fe3 100644
--- a/com/android/server/usb/descriptors/UsbDescriptor.java
+++ b/com/android/server/usb/descriptors/UsbDescriptor.java
@@ -85,36 +85,36 @@
     public static final byte DESCRIPTORTYPE_ENDPOINT_COMPANION = 0x30; // 48
 
     // Class IDs
-    public static final byte CLASSID_DEVICE  =      0x00;
-    public static final byte CLASSID_AUDIO =        0x01;
-    public static final byte CLASSID_COM =          0x02;
-    public static final byte CLASSID_HID =          0x03;
-    // public static final byte CLASSID_??? =       0x04;
-    public static final byte CLASSID_PHYSICAL =     0x05;
-    public static final byte CLASSID_IMAGE =        0x06;
-    public static final byte CLASSID_PRINTER =      0x07;
-    public static final byte CLASSID_STORAGE =      0x08;
-    public static final byte CLASSID_HUB =          0x09;
-    public static final byte CLASSID_CDC_CONTROL =  0x0A;
-    public static final byte CLASSID_SMART_CARD =   0x0B;
-    //public static final byte CLASSID_??? =        0x0C;
-    public static final byte CLASSID_SECURITY =     0x0D;
-    public static final byte CLASSID_VIDEO =        0x0E;
-    public static final byte CLASSID_HEALTHCARE =   0x0F;
-    public static final byte CLASSID_AUDIOVIDEO =   0x10;
-    public static final byte CLASSID_BILLBOARD =    0x11;
-    public static final byte CLASSID_TYPECBRIDGE =  0x12;
-    public static final byte CLASSID_DIAGNOSTIC =   (byte) 0xDC;
-    public static final byte CLASSID_WIRELESS =     (byte) 0xE0;
-    public static final byte CLASSID_MISC =         (byte) 0xEF;
-    public static final byte CLASSID_APPSPECIFIC =  (byte) 0xFE;
-    public static final byte CLASSID_VENDSPECIFIC = (byte) 0xFF;
+    public static final int CLASSID_DEVICE  =      0x00;
+    public static final int CLASSID_AUDIO =        0x01;
+    public static final int CLASSID_COM =          0x02;
+    public static final int CLASSID_HID =          0x03;
+    // public static final int CLASSID_??? =       0x04;
+    public static final int CLASSID_PHYSICAL =     0x05;
+    public static final int CLASSID_IMAGE =        0x06;
+    public static final int CLASSID_PRINTER =      0x07;
+    public static final int CLASSID_STORAGE =      0x08;
+    public static final int CLASSID_HUB =          0x09;
+    public static final int CLASSID_CDC_CONTROL =  0x0A;
+    public static final int CLASSID_SMART_CARD =   0x0B;
+    //public static final int CLASSID_??? =        0x0C;
+    public static final int CLASSID_SECURITY =     0x0D;
+    public static final int CLASSID_VIDEO =        0x0E;
+    public static final int CLASSID_HEALTHCARE =   0x0F;
+    public static final int CLASSID_AUDIOVIDEO =   0x10;
+    public static final int CLASSID_BILLBOARD =    0x11;
+    public static final int CLASSID_TYPECBRIDGE =  0x12;
+    public static final int CLASSID_DIAGNOSTIC =   0xDC;
+    public static final int CLASSID_WIRELESS =     0xE0;
+    public static final int CLASSID_MISC =         0xEF;
+    public static final int CLASSID_APPSPECIFIC =  0xFE;
+    public static final int CLASSID_VENDSPECIFIC = 0xFF;
 
     // Audio Subclass codes
-    public static final byte AUDIO_SUBCLASS_UNDEFINED   = 0x00;
-    public static final byte AUDIO_AUDIOCONTROL         = 0x01;
-    public static final byte AUDIO_AUDIOSTREAMING       = 0x02;
-    public static final byte AUDIO_MIDISTREAMING        = 0x03;
+    public static final int AUDIO_SUBCLASS_UNDEFINED   = 0x00;
+    public static final int AUDIO_AUDIOCONTROL         = 0x01;
+    public static final int AUDIO_AUDIOSTREAMING       = 0x02;
+    public static final int AUDIO_MIDISTREAMING        = 0x03;
 
     // Request IDs
     public static final int REQUEST_GET_STATUS         = 0x00;
diff --git a/com/android/server/usb/descriptors/UsbDescriptorParser.java b/com/android/server/usb/descriptors/UsbDescriptorParser.java
index ad7bde5..6c6bd01 100644
--- a/com/android/server/usb/descriptors/UsbDescriptorParser.java
+++ b/com/android/server/usb/descriptors/UsbDescriptorParser.java
@@ -15,6 +15,7 @@
  */
 package com.android.server.usb.descriptors;
 
+import android.hardware.usb.UsbDevice;
 import android.util.Log;
 
 import java.util.ArrayList;
@@ -25,11 +26,16 @@
  */
 public final class UsbDescriptorParser {
     private static final String TAG = "UsbDescriptorParser";
+    private static final boolean DEBUG = false;
+
+    private final String mDeviceAddr;
 
     // Descriptor Objects
+    private static final int DESCRIPTORS_ALLOC_SIZE = 128;
     private ArrayList<UsbDescriptor> mDescriptors = new ArrayList<UsbDescriptor>();
 
     private UsbDeviceDescriptor mDeviceDescriptor;
+    private UsbConfigDescriptor mCurConfigDescriptor;
     private UsbInterfaceDescriptor mCurInterfaceDescriptor;
 
     // The AudioClass spec implemented by the AudioClass Interfaces
@@ -37,7 +43,13 @@
     // Obtained from the first AudioClass Header descriptor.
     private int mACInterfacesSpec = UsbDeviceDescriptor.USBSPEC_1_0;
 
-    public UsbDescriptorParser() {}
+    public UsbDescriptorParser(String deviceAddr) {
+        mDeviceAddr = deviceAddr;
+    }
+
+    public String getDeviceAddr() {
+        return mDeviceAddr;
+    }
 
     /**
      * @return the USB Spec value associated with the Device descriptor for the
@@ -60,6 +72,18 @@
     public int getACInterfaceSpec() {
         return mACInterfacesSpec;
     }
+
+    private class UsbDescriptorsStreamFormatException extends Exception {
+        String mMessage;
+        UsbDescriptorsStreamFormatException(String message) {
+            mMessage = message;
+        }
+
+        public String toString() {
+            return "Descriptor Stream Format Exception: " + mMessage;
+        }
+    }
+
     /**
      * The probability (as returned by getHeadsetProbability() at which we conclude
      * the peripheral is a headset.
@@ -67,7 +91,8 @@
     private static final float IN_HEADSET_TRIGGER = 0.75f;
     private static final float OUT_HEADSET_TRIGGER = 0.75f;
 
-    private UsbDescriptor allocDescriptor(ByteStream stream) {
+    private UsbDescriptor allocDescriptor(ByteStream stream)
+            throws UsbDescriptorsStreamFormatException {
         stream.resetReadCount();
 
         int length = stream.getUnsignedByte();
@@ -83,15 +108,38 @@
                 break;
 
             case UsbDescriptor.DESCRIPTORTYPE_CONFIG:
-                descriptor = new UsbConfigDescriptor(length, type);
+                descriptor = mCurConfigDescriptor = new UsbConfigDescriptor(length, type);
+                if (mDeviceDescriptor != null) {
+                    mDeviceDescriptor.addConfigDescriptor(mCurConfigDescriptor);
+                } else {
+                    Log.e(TAG, "Config Descriptor found with no associated Device Descriptor!");
+                    throw new UsbDescriptorsStreamFormatException(
+                            "Config Descriptor found with no associated Device Descriptor!");
+                }
                 break;
 
             case UsbDescriptor.DESCRIPTORTYPE_INTERFACE:
                 descriptor = mCurInterfaceDescriptor = new UsbInterfaceDescriptor(length, type);
+                if (mCurConfigDescriptor != null) {
+                    mCurConfigDescriptor.addInterfaceDescriptor(mCurInterfaceDescriptor);
+                } else {
+                    Log.e(TAG, "Interface Descriptor found with no associated Config Descriptor!");
+                    throw new UsbDescriptorsStreamFormatException(
+                            "Interface Descriptor found with no associated Config Descriptor!");
+                }
                 break;
 
             case UsbDescriptor.DESCRIPTORTYPE_ENDPOINT:
                 descriptor = new UsbEndpointDescriptor(length, type);
+                if (mCurInterfaceDescriptor != null) {
+                    mCurInterfaceDescriptor.addEndpointDescriptor(
+                            (UsbEndpointDescriptor) descriptor);
+                } else {
+                    Log.e(TAG,
+                            "Endpoint Descriptor found with no associated Interface Descriptor!");
+                    throw new UsbDescriptorsStreamFormatException(
+                            "Endpoint Descriptor found with no associated Interface Descriptor!");
+                }
                 break;
 
             /*
@@ -144,8 +192,12 @@
     /**
      * @hide
      */
-    public void parseDescriptors(byte[] descriptors) {
-        mDescriptors.clear();
+    public boolean parseDescriptors(byte[] descriptors) {
+        if (DEBUG) {
+            Log.d(TAG, "parseDescriptors() - start");
+        }
+        // This will allow us to (probably) alloc mDescriptors just once.
+        mDescriptors = new ArrayList<UsbDescriptor>(DESCRIPTORS_ALLOC_SIZE);
 
         ByteStream stream = new ByteStream(descriptors);
         while (stream.available() > 0) {
@@ -173,21 +225,36 @@
                 }
             }
         }
+        if (DEBUG) {
+            Log.d(TAG, "parseDescriptors() - end " + mDescriptors.size() + " descriptors.");
+        }
+        return true;
     }
 
     /**
      * @hide
      */
-    public boolean parseDevice(String deviceAddr) {
-        byte[] rawDescriptors = getRawDescriptors(deviceAddr);
-        if (rawDescriptors != null) {
-            parseDescriptors(rawDescriptors);
-            return true;
-        }
-        return false;
+    public boolean parseDevice() {
+        byte[] rawDescriptors = getRawDescriptors();
+
+        return rawDescriptors != null
+            ? parseDescriptors(rawDescriptors) : false;
     }
 
-    private native byte[] getRawDescriptors(String deviceAddr);
+    private byte[] getRawDescriptors() {
+        return getRawDescriptors_native(mDeviceAddr);
+    }
+
+    private native byte[] getRawDescriptors_native(String deviceAddr);
+
+    /**
+     * @hide
+     */
+    public String getDescriptorString(int stringId) {
+        return getDescriptorString_native(mDeviceAddr, stringId);
+    }
+
+    private native String getDescriptorString_native(String deviceAddr, int stringId);
 
     public int getParsingSpec() {
         return mDeviceDescriptor != null ? mDeviceDescriptor.getSpec() : 0;
@@ -200,6 +267,17 @@
     /**
      * @hide
      */
+    public UsbDevice toAndroidUsbDevice() {
+        if (mDeviceDescriptor == null) {
+            return null;
+        }
+
+        return mDeviceDescriptor.toAndroid(this);
+    }
+
+    /**
+     * @hide
+     */
     public ArrayList<UsbDescriptor> getDescriptors(byte type) {
         ArrayList<UsbDescriptor> list = new ArrayList<UsbDescriptor>();
         for (UsbDescriptor descriptor : mDescriptors) {
@@ -213,7 +291,7 @@
     /**
      * @hide
      */
-    public ArrayList<UsbDescriptor> getInterfaceDescriptorsForClass(byte usbClass) {
+    public ArrayList<UsbDescriptor> getInterfaceDescriptorsForClass(int usbClass) {
         ArrayList<UsbDescriptor> list = new ArrayList<UsbDescriptor>();
         for (UsbDescriptor descriptor : mDescriptors) {
             // ensure that this isn't an unrecognized DESCRIPTORTYPE_INTERFACE
@@ -235,7 +313,7 @@
     /**
      * @hide
      */
-    public ArrayList<UsbDescriptor> getACInterfaceDescriptors(byte subtype, byte subclass) {
+    public ArrayList<UsbDescriptor> getACInterfaceDescriptors(byte subtype, int subclass) {
         ArrayList<UsbDescriptor> list = new ArrayList<UsbDescriptor>();
         for (UsbDescriptor descriptor : mDescriptors) {
             if (descriptor.getType() == UsbDescriptor.DESCRIPTORTYPE_AUDIO_INTERFACE) {
@@ -355,8 +433,6 @@
      * to count on the peripheral being a headset.
      */
     public boolean isInputHeadset() {
-        // TEMP
-        Log.i(TAG, "---- isInputHeadset() prob:" + (getInputHeadsetProbability() * 100f) + "%");
         return getInputHeadsetProbability() >= IN_HEADSET_TRIGGER;
     }
 
@@ -410,8 +486,6 @@
      * to count on the peripheral being a headset.
      */
     public boolean isOutputHeadset() {
-        // TEMP
-        Log.i(TAG, "---- isOutputHeadset() prob:" + (getOutputHeadsetProbability() * 100f) + "%");
         return getOutputHeadsetProbability() >= OUT_HEADSET_TRIGGER;
     }
 
diff --git a/com/android/server/usb/descriptors/UsbDeviceDescriptor.java b/com/android/server/usb/descriptors/UsbDeviceDescriptor.java
index d5cb89e..8e7f0fd 100644
--- a/com/android/server/usb/descriptors/UsbDeviceDescriptor.java
+++ b/com/android/server/usb/descriptors/UsbDeviceDescriptor.java
@@ -15,9 +15,14 @@
  */
 package com.android.server.usb.descriptors;
 
+import android.hardware.usb.UsbConfiguration;
+import android.hardware.usb.UsbDevice;
+
 import com.android.server.usb.descriptors.report.ReportCanvas;
 import com.android.server.usb.descriptors.report.UsbStrings;
 
+import java.util.ArrayList;
+
 /**
  * @hide
  * A USB Device Descriptor.
@@ -31,9 +36,9 @@
     public static final int USBSPEC_2_0 = 0x0200;
 
     private int mSpec;          // 2:2 bcdUSB 2 BCD USB Specification Number - BCD
-    private byte mDevClass;     // 4:1 class code
-    private byte mDevSubClass;  // 5:1 subclass code
-    private byte mProtocol;     // 6:1 protocol
+    private int mDevClass;      // 4:1 class code
+    private int mDevSubClass;   // 5:1 subclass code
+    private int mProtocol;      // 6:1 protocol
     private byte mPacketSize;   // 7:1 Maximum Packet Size for Zero Endpoint.
                                 // Valid Sizes are 8, 16, 32, 64
     private int mVendorID;      // 8:2 vendor ID
@@ -44,6 +49,9 @@
     private byte mSerialNum;    // 16:1 Index of Serial Number String Descriptor
     private byte mNumConfigs;   // 17:1 Number of Possible Configurations
 
+    private ArrayList<UsbConfigDescriptor> mConfigDescriptors =
+            new ArrayList<UsbConfigDescriptor>();
+
     UsbDeviceDescriptor(int length, byte type) {
         super(length, type);
         mHierarchyLevel = 1;
@@ -53,15 +61,15 @@
         return mSpec;
     }
 
-    public byte getDevClass() {
+    public int getDevClass() {
         return mDevClass;
     }
 
-    public byte getDevSubClass() {
+    public int getDevSubClass() {
         return mDevSubClass;
     }
 
-    public byte getProtocol() {
+    public int getProtocol() {
         return mProtocol;
     }
 
@@ -97,12 +105,41 @@
         return mNumConfigs;
     }
 
+    void addConfigDescriptor(UsbConfigDescriptor config) {
+        mConfigDescriptors.add(config);
+    }
+
+    /**
+     * @hide
+     */
+    public UsbDevice toAndroid(UsbDescriptorParser parser) {
+        String mfgName = parser.getDescriptorString(mMfgIndex);
+        String prodName = parser.getDescriptorString(mProductIndex);
+
+        // Create version string in "%.%" format
+        String versionString =
+                Integer.toString(mDeviceRelease >> 8) + "." + (mDeviceRelease & 0xFF);
+        String serialStr = parser.getDescriptorString(mSerialNum);
+
+        UsbDevice device = new UsbDevice(parser.getDeviceAddr(), mVendorID, mProductID,
+                mDevClass, mDevSubClass,
+                mProtocol, mfgName, prodName,
+                versionString, serialStr);
+        UsbConfiguration[] configs = new UsbConfiguration[mConfigDescriptors.size()];
+        for (int index = 0; index < mConfigDescriptors.size(); index++) {
+            configs[index] = mConfigDescriptors.get(index).toAndroid(parser);
+        }
+        device.setConfigurations(configs);
+
+        return device;
+    }
+
     @Override
     public int parseRawDescriptors(ByteStream stream) {
         mSpec = stream.unpackUsbShort();
-        mDevClass = stream.getByte();
-        mDevSubClass = stream.getByte();
-        mProtocol = stream.getByte();
+        mDevClass = stream.getUnsignedByte();
+        mDevSubClass = stream.getUnsignedByte();
+        mProtocol = stream.getUnsignedByte();
         mPacketSize = stream.getByte();
         mVendorID = stream.unpackUsbShort();
         mProductID = stream.unpackUsbShort();
@@ -124,9 +161,9 @@
         int spec = getSpec();
         canvas.writeListItem("Spec: " + ReportCanvas.getBCDString(spec));
 
-        byte devClass = getDevClass();
+        int devClass = getDevClass();
         String classStr = UsbStrings.getClassName(devClass);
-        byte devSubClass = getDevSubClass();
+        int devSubClass = getDevSubClass();
         String subClasStr = UsbStrings.getClassName(devSubClass);
         canvas.writeListItem("Class " + devClass + ": " + classStr + " Subclass"
                 + devSubClass + ": " + subClasStr);
@@ -134,12 +171,11 @@
                 + " Product ID: " + ReportCanvas.getHexString(getProductID())
                 + " Product Release: " + ReportCanvas.getBCDString(getDeviceRelease()));
 
+        UsbDescriptorParser parser = canvas.getParser();
         byte mfgIndex = getMfgIndex();
-        String manufacturer =
-                UsbDescriptor.getUsbDescriptorString(canvas.getConnection(), mfgIndex);
+        String manufacturer = parser.getDescriptorString(mfgIndex);
         byte productIndex = getProductIndex();
-        String product =
-                UsbDescriptor.getUsbDescriptorString(canvas.getConnection(), productIndex);
+        String product = parser.getDescriptorString(productIndex);
 
         canvas.writeListItem("Manufacturer " + mfgIndex + ": " + manufacturer
                 + " Product " + productIndex + ": " + product);
diff --git a/com/android/server/usb/descriptors/UsbEndpointDescriptor.java b/com/android/server/usb/descriptors/UsbEndpointDescriptor.java
index 6322fbe..1130238 100644
--- a/com/android/server/usb/descriptors/UsbEndpointDescriptor.java
+++ b/com/android/server/usb/descriptors/UsbEndpointDescriptor.java
@@ -15,6 +15,8 @@
  */
 package com.android.server.usb.descriptors;
 
+import android.hardware.usb.UsbEndpoint;
+
 import com.android.server.usb.descriptors.report.ReportCanvas;
 
 /**
@@ -25,16 +27,16 @@
 public class UsbEndpointDescriptor extends UsbDescriptor {
     private static final String TAG = "UsbEndpointDescriptor";
 
-    public static final byte MASK_ENDPOINT_ADDRESS = 0b0001111;
-    public static final byte MASK_ENDPOINT_DIRECTION = (byte) 0b10000000;
-    public static final byte DIRECTION_OUTPUT = 0x00;
-    public static final byte DIRECTION_INPUT = (byte) 0x80;
+    public static final int MASK_ENDPOINT_ADDRESS = 0b000000000001111;
+    public static final int MASK_ENDPOINT_DIRECTION = (byte) 0b0000000010000000;
+    public static final int DIRECTION_OUTPUT = 0x0000;
+    public static final int DIRECTION_INPUT = (byte) 0x0080;
 
-    public static final byte MASK_ATTRIBS_TRANSTYPE = 0b00000011;
-    public static final byte TRANSTYPE_CONTROL = 0x00;
-    public static final byte TRANSTYPE_ISO = 0x01;
-    public static final byte TRANSTYPE_BULK = 0x02;
-    public static final byte TRANSTYPE_INTERRUPT = 0x03;
+    public static final int MASK_ATTRIBS_TRANSTYPE = 0b00000011;
+    public static final int TRANSTYPE_CONTROL = 0x00;
+    public static final int TRANSTYPE_ISO = 0x01;
+    public static final int TRANSTYPE_BULK = 0x02;
+    public static final int TRANSTYPE_INTERRUPT = 0x03;
 
     public static final byte MASK_ATTRIBS_SYNCTYPE = 0b00001100;
     public static final byte SYNCTYPE_NONE = 0b00000000;
@@ -42,18 +44,18 @@
     public static final byte SYNCTYPE_ADAPTSYNC = 0b00001000;
     public static final byte SYNCTYPE_RESERVED = 0b00001100;
 
-    public static final byte MASK_ATTRIBS_USEAGE = 0b00110000;
-    public static final byte USEAGE_DATA = 0b00000000;
-    public static final byte USEAGE_FEEDBACK = 0b00010000;
-    public static final byte USEAGE_EXPLICIT = 0b00100000;
-    public static final byte USEAGE_RESERVED = 0b00110000;
+    public static final int MASK_ATTRIBS_USEAGE = 0b00110000;
+    public static final int USEAGE_DATA = 0b00000000;
+    public static final int USEAGE_FEEDBACK = 0b00010000;
+    public static final int USEAGE_EXPLICIT = 0b00100000;
+    public static final int USEAGE_RESERVED = 0b00110000;
 
-    private byte mEndpointAddress;  // 2:1 Endpoint Address
+    private int mEndpointAddress;   // 2:1 Endpoint Address
                                     // Bits 0..3b Endpoint Number.
                                     // Bits 4..6b Reserved. Set to Zero
                                     // Bits 7 Direction 0 = Out, 1 = In
                                     // (Ignored for Control Endpoints)
-    private byte mAttributes;   // 3:1 Various flags
+    private int mAttributes;    // 3:1 Various flags
                                 // Bits 0..1 Transfer Type:
                                 //     00 = Control, 01 = Isochronous, 10 = Bulk, 11 = Interrupt
                                 // Bits 2..7 are reserved. If Isochronous endpoint,
@@ -69,7 +71,7 @@
                                 //  11: Reserved
     private int mPacketSize;    // 4:2 Maximum Packet Size this endpoint is capable of
                                 // sending or receiving
-    private byte mInterval;     // 6:1 Interval for polling endpoint data transfers. Value in
+    private int mInterval;      // 6:1 Interval for polling endpoint data transfers. Value in
                                 // frame counts.
                                 // Ignored for Bulk & Control Endpoints. Isochronous must equal
                                 // 1 and field may range from 1 to 255 for interrupt endpoints.
@@ -81,11 +83,11 @@
         mHierarchyLevel = 4;
     }
 
-    public byte getEndpointAddress() {
+    public int getEndpointAddress() {
         return mEndpointAddress;
     }
 
-    public byte getAttributes() {
+    public int getAttributes() {
         return mAttributes;
     }
 
@@ -93,7 +95,7 @@
         return mPacketSize;
     }
 
-    public byte getInterval() {
+    public int getInterval() {
         return mInterval;
     }
 
@@ -105,12 +107,16 @@
         return mSyncAddress;
     }
 
+    /* package */ UsbEndpoint toAndroid(UsbDescriptorParser parser) {
+        return new UsbEndpoint(mEndpointAddress, mAttributes, mPacketSize, mInterval);
+    }
+
     @Override
     public int parseRawDescriptors(ByteStream stream) {
-        mEndpointAddress = stream.getByte();
-        mAttributes = stream.getByte();
+        mEndpointAddress = stream.getUnsignedByte();
+        mAttributes = stream.getUnsignedByte();
         mPacketSize = stream.unpackUsbShort();
-        mInterval = stream.getByte();
+        mInterval = stream.getUnsignedByte();
         if (mLength == 9) {
             mRefresh = stream.getByte();
             mSyncAddress = stream.getByte();
@@ -124,13 +130,13 @@
 
         canvas.openList();
 
-        byte address = getEndpointAddress();
+        int address = getEndpointAddress();
         canvas.writeListItem("Address: "
                 + ReportCanvas.getHexString(address & UsbEndpointDescriptor.MASK_ENDPOINT_ADDRESS)
                 + ((address & UsbEndpointDescriptor.MASK_ENDPOINT_DIRECTION)
                 == UsbEndpointDescriptor.DIRECTION_OUTPUT ? " [out]" : " [in]"));
 
-        byte attributes = getAttributes();
+        int attributes = getAttributes();
         canvas.openListItem();
         canvas.write("Attributes: " + ReportCanvas.getHexString(attributes) + " ");
         switch (attributes & UsbEndpointDescriptor.MASK_ATTRIBS_TRANSTYPE) {
diff --git a/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java b/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java
index 4eef6ca..d87b1af 100644
--- a/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java
+++ b/com/android/server/usb/descriptors/UsbInterfaceDescriptor.java
@@ -15,9 +15,14 @@
  */
 package com.android.server.usb.descriptors;
 
+import android.hardware.usb.UsbEndpoint;
+import android.hardware.usb.UsbInterface;
+
 import com.android.server.usb.descriptors.report.ReportCanvas;
 import com.android.server.usb.descriptors.report.UsbStrings;
 
+import java.util.ArrayList;
+
 /**
  * @hide
  * A common super-class for all USB Interface Descritor subtypes.
@@ -26,14 +31,17 @@
 public class UsbInterfaceDescriptor extends UsbDescriptor {
     private static final String TAG = "UsbInterfaceDescriptor";
 
-    protected byte mInterfaceNumber;  // 2:1 Number of Interface
+    protected int mInterfaceNumber;   // 2:1 Number of Interface
     protected byte mAlternateSetting; // 3:1 Value used to select alternative setting
     protected byte mNumEndpoints;     // 4:1 Number of Endpoints used for this interface
-    protected byte mUsbClass;         // 5:1 Class Code
-    protected byte mUsbSubclass;      // 6:1 Subclass Code
-    protected byte mProtocol;         // 7:1 Protocol Code
+    protected int mUsbClass;          // 5:1 Class Code
+    protected int mUsbSubclass;       // 6:1 Subclass Code
+    protected int mProtocol;          // 7:1 Protocol Code
     protected byte mDescrIndex;       // 8:1 Index of String Descriptor Describing this interface
 
+    private ArrayList<UsbEndpointDescriptor> mEndpointDescriptors =
+            new ArrayList<UsbEndpointDescriptor>();
+
     UsbInterfaceDescriptor(int length, byte type) {
         super(length, type);
         mHierarchyLevel = 3;
@@ -41,18 +49,18 @@
 
     @Override
     public int parseRawDescriptors(ByteStream stream) {
-        mInterfaceNumber = stream.getByte();
+        mInterfaceNumber = stream.getUnsignedByte();
         mAlternateSetting = stream.getByte();
         mNumEndpoints = stream.getByte();
-        mUsbClass = stream.getByte();
-        mUsbSubclass = stream.getByte();
-        mProtocol = stream.getByte();
+        mUsbClass = stream.getUnsignedByte();
+        mUsbSubclass = stream.getUnsignedByte();
+        mProtocol = stream.getUnsignedByte();
         mDescrIndex = stream.getByte();
 
         return mLength;
     }
 
-    public byte getInterfaceNumber() {
+    public int getInterfaceNumber() {
         return mInterfaceNumber;
     }
 
@@ -64,15 +72,15 @@
         return mNumEndpoints;
     }
 
-    public byte getUsbClass() {
+    public int getUsbClass() {
         return mUsbClass;
     }
 
-    public byte getUsbSubclass() {
+    public int getUsbSubclass() {
         return mUsbSubclass;
     }
 
-    public byte getProtocol() {
+    public int getProtocol() {
         return mProtocol;
     }
 
@@ -80,13 +88,29 @@
         return mDescrIndex;
     }
 
+    void addEndpointDescriptor(UsbEndpointDescriptor endpoint) {
+        mEndpointDescriptors.add(endpoint);
+    }
+
+    UsbInterface toAndroid(UsbDescriptorParser parser) {
+        String name = parser.getDescriptorString(mDescrIndex);
+        UsbInterface ntrface = new UsbInterface(
+                mInterfaceNumber, mAlternateSetting, name, mUsbClass, mUsbSubclass, mProtocol);
+        UsbEndpoint[] endpoints = new UsbEndpoint[mEndpointDescriptors.size()];
+        for (int index = 0; index < mEndpointDescriptors.size(); index++) {
+            endpoints[index] = mEndpointDescriptors.get(index).toAndroid(parser);
+        }
+        ntrface.setEndpoints(endpoints);
+        return ntrface;
+    }
+
     @Override
     public void report(ReportCanvas canvas) {
         super.report(canvas);
 
-        byte usbClass = getUsbClass();
-        byte usbSubclass = getUsbSubclass();
-        byte protocol = getProtocol();
+        int usbClass = getUsbClass();
+        int usbSubclass = getUsbSubclass();
+        int protocol = getProtocol();
         String className = UsbStrings.getClassName(usbClass);
         String subclassName = "";
         if (usbClass == UsbDescriptor.CLASSID_AUDIO) {
diff --git a/com/android/server/usb/descriptors/UsbMSMidiHeader.java b/com/android/server/usb/descriptors/UsbMSMidiHeader.java
index 85a3e68..d0ca6db 100644
--- a/com/android/server/usb/descriptors/UsbMSMidiHeader.java
+++ b/com/android/server/usb/descriptors/UsbMSMidiHeader.java
@@ -25,7 +25,7 @@
 public final class UsbMSMidiHeader extends UsbACInterface {
     private static final String TAG = "UsbMSMidiHeader";
 
-    public UsbMSMidiHeader(int length, byte type, byte subtype, byte subclass) {
+    public UsbMSMidiHeader(int length, byte type, byte subtype, int subclass) {
         super(length, type, subtype, subclass);
     }
 
diff --git a/com/android/server/usb/descriptors/UsbMSMidiInputJack.java b/com/android/server/usb/descriptors/UsbMSMidiInputJack.java
index 1d5cbf2..7df7cfc 100644
--- a/com/android/server/usb/descriptors/UsbMSMidiInputJack.java
+++ b/com/android/server/usb/descriptors/UsbMSMidiInputJack.java
@@ -25,7 +25,7 @@
 public final class UsbMSMidiInputJack extends UsbACInterface {
     private static final String TAG = "UsbMSMidiInputJack";
 
-    UsbMSMidiInputJack(int length, byte type, byte subtype, byte subclass) {
+    UsbMSMidiInputJack(int length, byte type, byte subtype, int subclass) {
         super(length, type, subtype, subclass);
     }
 
diff --git a/com/android/server/usb/descriptors/UsbMSMidiOutputJack.java b/com/android/server/usb/descriptors/UsbMSMidiOutputJack.java
index 9f50240..1879ac0 100644
--- a/com/android/server/usb/descriptors/UsbMSMidiOutputJack.java
+++ b/com/android/server/usb/descriptors/UsbMSMidiOutputJack.java
@@ -25,7 +25,7 @@
 public final class UsbMSMidiOutputJack extends UsbACInterface {
     private static final String TAG = "UsbMSMidiOutputJack";
 
-    public UsbMSMidiOutputJack(int length, byte type, byte subtype, byte subclass) {
+    public UsbMSMidiOutputJack(int length, byte type, byte subtype, int subclass) {
         super(length, type, subtype, subclass);
     }
 
diff --git a/com/android/server/usb/descriptors/report/HTMLReportCanvas.java b/com/android/server/usb/descriptors/report/HTMLReportCanvas.java
index 99ebcca..adfc514 100644
--- a/com/android/server/usb/descriptors/report/HTMLReportCanvas.java
+++ b/com/android/server/usb/descriptors/report/HTMLReportCanvas.java
@@ -15,7 +15,7 @@
  */
 package com.android.server.usb.descriptors.report;
 
-import android.hardware.usb.UsbDeviceConnection;
+import com.android.server.usb.descriptors.UsbDescriptorParser;
 
 /**
  * @hide
@@ -32,8 +32,8 @@
      * from the USB device.
      * @param stringBuilder Generated output gets written into this object.
      */
-    public HTMLReportCanvas(UsbDeviceConnection connection, StringBuilder stringBuilder) {
-        super(connection);
+    public HTMLReportCanvas(UsbDescriptorParser parser, StringBuilder stringBuilder) {
+        super(parser);
 
         mStringBuilder = stringBuilder;
     }
diff --git a/com/android/server/usb/descriptors/report/ReportCanvas.java b/com/android/server/usb/descriptors/report/ReportCanvas.java
index 9e0adf5..c34dc98 100644
--- a/com/android/server/usb/descriptors/report/ReportCanvas.java
+++ b/com/android/server/usb/descriptors/report/ReportCanvas.java
@@ -15,7 +15,7 @@
  */
 package com.android.server.usb.descriptors.report;
 
-import android.hardware.usb.UsbDeviceConnection;
+import com.android.server.usb.descriptors.UsbDescriptorParser;
 
 /**
  * @hide
@@ -24,22 +24,19 @@
 public abstract class ReportCanvas {
     private static final String TAG = "ReportCanvas";
 
-    private final UsbDeviceConnection mConnection;
+    private final UsbDescriptorParser mParser;
 
     /**
      * Constructor.
      * @param connection    The USB connection object used to retrieve strings
      * from the USB device.
      */
-    public ReportCanvas(UsbDeviceConnection connection) {
-        mConnection = connection;
+    public ReportCanvas(UsbDescriptorParser parser) {
+        mParser = parser;
     }
 
-    /**
-     * @returns the UsbDeviceConnection member (mConnection).
-     */
-    public UsbDeviceConnection getConnection() {
-        return mConnection;
+    public UsbDescriptorParser getParser() {
+        return mParser;
     }
 
     /**
diff --git a/com/android/server/usb/descriptors/report/TextReportCanvas.java b/com/android/server/usb/descriptors/report/TextReportCanvas.java
index a43569d..1e19ea1 100644
--- a/com/android/server/usb/descriptors/report/TextReportCanvas.java
+++ b/com/android/server/usb/descriptors/report/TextReportCanvas.java
@@ -15,7 +15,7 @@
  */
 package com.android.server.usb.descriptors.report;
 
-import android.hardware.usb.UsbDeviceConnection;
+import com.android.server.usb.descriptors.UsbDescriptorParser;
 
 /**
  * @hide
@@ -34,8 +34,8 @@
      * from the USB device.
      * @param stringBuilder Generated output gets written into this object.
      */
-    public TextReportCanvas(UsbDeviceConnection connection, StringBuilder stringBuilder) {
-        super(connection);
+    public TextReportCanvas(UsbDescriptorParser parser, StringBuilder stringBuilder) {
+        super(parser);
 
         mStringBuilder = stringBuilder;
     }
diff --git a/com/android/server/usb/descriptors/report/UsbStrings.java b/com/android/server/usb/descriptors/report/UsbStrings.java
index 64ecebc..fb4576a 100644
--- a/com/android/server/usb/descriptors/report/UsbStrings.java
+++ b/com/android/server/usb/descriptors/report/UsbStrings.java
@@ -32,8 +32,8 @@
     private static HashMap<Byte, String> sDescriptorNames;
     private static HashMap<Byte, String> sACControlInterfaceNames;
     private static HashMap<Byte, String> sACStreamingInterfaceNames;
-    private static HashMap<Byte, String> sClassNames;
-    private static HashMap<Byte, String> sAudioSubclassNames;
+    private static HashMap<Integer, String> sClassNames;
+    private static HashMap<Integer, String> sAudioSubclassNames;
     private static HashMap<Integer, String> sAudioEncodingNames;
     private static HashMap<Integer, String> sTerminalNames;
     private static HashMap<Integer, String> sFormatNames;
@@ -92,7 +92,7 @@
     }
 
     private static void initClassNames() {
-        sClassNames = new HashMap<Byte, String>();
+        sClassNames = new HashMap<Integer, String>();
         sClassNames.put(UsbDescriptor.CLASSID_DEVICE, "Device");
         sClassNames.put(UsbDescriptor.CLASSID_AUDIO, "Audio");
         sClassNames.put(UsbDescriptor.CLASSID_COM, "Communications");
@@ -118,7 +118,7 @@
     }
 
     private static void initAudioSubclassNames() {
-        sAudioSubclassNames = new HashMap<Byte, String>();
+        sAudioSubclassNames = new HashMap<Integer, String>();
         sAudioSubclassNames.put(UsbDescriptor.AUDIO_SUBCLASS_UNDEFINED, "Undefinded");
         sAudioSubclassNames.put(UsbDescriptor.AUDIO_AUDIOCONTROL, "Audio Control");
         sAudioSubclassNames.put(UsbDescriptor.AUDIO_AUDIOSTREAMING, "Audio Streaming");
@@ -300,7 +300,7 @@
     /**
      * Retrieves the name for the specified USB class ID.
      */
-    public static String getClassName(byte classID) {
+    public static String getClassName(int classID) {
         String name = sClassNames.get(classID);
         int iClassID = classID & 0xFF;
         return name != null
@@ -312,7 +312,7 @@
     /**
      * Retrieves the name for the specified USB audio subclass ID.
      */
-    public static String getAudioSubclassName(byte subClassID) {
+    public static String getAudioSubclassName(int subClassID) {
         String name = sAudioSubclassNames.get(subClassID);
         int iSubclassID = subClassID & 0xFF;
         return name != null
@@ -335,7 +335,7 @@
     /**
      * Retrieves the name for the specified USB audio interface subclass ID.
      */
-    public static String getACInterfaceSubclassName(byte subClassID) {
+    public static String getACInterfaceSubclassName(int subClassID) {
         return subClassID == UsbDescriptor.AUDIO_AUDIOCONTROL ? "AC Control" : "AC Streaming";
     }
 }
diff --git a/com/android/server/utils/AppInstallerUtil.java b/com/android/server/utils/AppInstallerUtil.java
new file mode 100644
index 0000000..af7ff41
--- /dev/null
+++ b/com/android/server/utils/AppInstallerUtil.java
@@ -0,0 +1,71 @@
+/*
+ * 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.utils;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+import android.util.Log;
+
+public class AppInstallerUtil {
+    private static final String LOG_TAG = "AppInstallerUtil";
+
+    private static Intent resolveIntent(Context context, Intent i) {
+        ResolveInfo result = context.getPackageManager().resolveActivity(i, 0);
+        return result != null ? new Intent(i.getAction())
+                .setClassName(result.activityInfo.packageName, result.activityInfo.name) : null;
+    }
+
+    /**
+     * Returns the package name of the app which installed a given packageName, if available.
+     */
+    public static String getInstallerPackageName(Context context, String packageName) {
+        String installerPackageName = null;
+        try {
+            installerPackageName =
+                    context.getPackageManager().getInstallerPackageName(packageName);
+        } catch (IllegalArgumentException e) {
+            Log.e(LOG_TAG, "Exception while retrieving the package installer of " + packageName, e);
+        }
+        if (installerPackageName == null) {
+            return null;
+        }
+        return installerPackageName;
+    }
+
+    /**
+     * Returns an intent to launcher the installer for a given package name.
+     */
+    public static Intent createIntent(Context context, String installerPackageName,
+            String packageName) {
+        Intent intent = new Intent(Intent.ACTION_SHOW_APP_INFO).setPackage(installerPackageName);
+        final Intent result = resolveIntent(context, intent);
+        if (result != null) {
+            result.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName);
+            return result;
+        }
+        return null;
+    }
+
+    /**
+     * Convenience method that looks up the installerPackageName.
+     */
+    public static Intent createIntent(Context context, String packageName) {
+        String installerPackageName = getInstallerPackageName(context, packageName);
+        return createIntent(context, installerPackageName, packageName);
+    }
+}
diff --git a/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 44e5314..2d93da9 100644
--- a/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -427,8 +427,11 @@
                     if (hasComponent) {
                         mShortcutServiceInternal.setShortcutHostPackage(TAG,
                                 serviceComponent.getPackageName(), mCurUser);
+                        mAmInternal.setAllowAppSwitches(TAG,
+                                serviceInfo.applicationInfo.uid, mCurUser);
                     } else {
                         mShortcutServiceInternal.setShortcutHostPackage(TAG, null, mCurUser);
+                        mAmInternal.setAllowAppSwitches(TAG, -1, mCurUser);
                     }
                 }
 
diff --git a/com/android/server/vr/Vr2dDisplay.java b/com/android/server/vr/Vr2dDisplay.java
index 95d03d4..7866e7d 100644
--- a/com/android/server/vr/Vr2dDisplay.java
+++ b/com/android/server/vr/Vr2dDisplay.java
@@ -24,9 +24,8 @@
 import android.service.vr.IVrManager;
 import android.util.Log;
 import android.view.Surface;
-import android.view.WindowManagerInternal;
 
-import com.android.server.vr.VrManagerService;
+import com.android.server.wm.WindowManagerInternal;
 
 /**
  * Creates a 2D Virtual Display while VR Mode is enabled. This display will be used to run and
diff --git a/com/android/server/vr/VrManagerService.java b/com/android/server/vr/VrManagerService.java
index 5493207..1f4e64e 100644
--- a/com/android/server/vr/VrManagerService.java
+++ b/com/android/server/vr/VrManagerService.java
@@ -58,7 +58,7 @@
 import android.util.ArraySet;
 import android.util.Slog;
 import android.util.SparseArray;
-import android.view.WindowManagerInternal;
+import com.android.server.wm.WindowManagerInternal;
 
 import com.android.internal.R;
 import com.android.internal.util.DumpUtils;
diff --git a/com/android/server/wifi/HalDeviceManager.java b/com/android/server/wifi/HalDeviceManager.java
index 0cc735a..ffc7113 100644
--- a/com/android/server/wifi/HalDeviceManager.java
+++ b/com/android/server/wifi/HalDeviceManager.java
@@ -16,6 +16,8 @@
 
 package com.android.server.wifi;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.hardware.wifi.V1_0.IWifi;
 import android.hardware.wifi.V1_0.IWifiApIface;
 import android.hardware.wifi.V1_0.IWifiChip;
@@ -34,7 +36,6 @@
 import android.hidl.manager.V1_0.IServiceNotification;
 import android.os.Handler;
 import android.os.HwRemoteBinder;
-import android.os.Looper;
 import android.os.RemoteException;
 import android.util.Log;
 import android.util.LongSparseArray;
@@ -102,13 +103,14 @@
      * single copy kept.
      *
      * @param listener ManagerStatusListener listener object.
-     * @param handler Handler on which to dispatch listener. Null implies a new Handler based on
-     *                the current looper.
+     * @param handler Handler on which to dispatch listener. Null implies the listener will be
+     *                invoked synchronously from the context of the client which triggered the
+     *                state change.
      */
-    public void registerStatusListener(ManagerStatusListener listener, Handler handler) {
+    public void registerStatusListener(@NonNull ManagerStatusListener listener,
+            @Nullable Handler handler) {
         synchronized (mLock) {
-            if (!mManagerStatusListeners.add(new ManagerStatusListenerProxy(listener,
-                    handler == null ? new Handler(Looper.myLooper()) : handler))) {
+            if (!mManagerStatusListeners.add(new ManagerStatusListenerProxy(listener, handler))) {
                 Log.w(TAG, "registerStatusListener: duplicate registration ignored");
             }
         }
@@ -197,36 +199,37 @@
      * @param destroyedListener Optional (nullable) listener to call when the allocated interface
      *                          is removed. Will only be registered and used if an interface is
      *                          created successfully.
-     * @param handler The Handler on which to dispatch the listener. A null implies a new Handler
-     *                based on the current looper.
+     * @param handler Handler on which to dispatch listener. Null implies the listener will be
+     *                invoked synchronously from the context of the client which triggered the
+     *                iface destruction.
      * @return A newly created interface - or null if the interface could not be created.
      */
-    public IWifiStaIface createStaIface(InterfaceDestroyedListener destroyedListener,
-            Handler handler) {
+    public IWifiStaIface createStaIface(@Nullable InterfaceDestroyedListener destroyedListener,
+            @Nullable Handler handler) {
         return (IWifiStaIface) createIface(IfaceType.STA, destroyedListener, handler);
     }
 
     /**
      * Create AP interface if possible (see createStaIface doc).
      */
-    public IWifiApIface createApIface(InterfaceDestroyedListener destroyedListener,
-            Handler handler) {
+    public IWifiApIface createApIface(@Nullable InterfaceDestroyedListener destroyedListener,
+            @Nullable Handler handler) {
         return (IWifiApIface) createIface(IfaceType.AP, destroyedListener, handler);
     }
 
     /**
      * Create P2P interface if possible (see createStaIface doc).
      */
-    public IWifiP2pIface createP2pIface(InterfaceDestroyedListener destroyedListener,
-            Handler handler) {
+    public IWifiP2pIface createP2pIface(@Nullable InterfaceDestroyedListener destroyedListener,
+            @Nullable Handler handler) {
         return (IWifiP2pIface) createIface(IfaceType.P2P, destroyedListener, handler);
     }
 
     /**
      * Create NAN interface if possible (see createStaIface doc).
      */
-    public IWifiNanIface createNanIface(InterfaceDestroyedListener destroyedListener,
-            Handler handler) {
+    public IWifiNanIface createNanIface(@Nullable InterfaceDestroyedListener destroyedListener,
+            @Nullable Handler handler) {
         return (IWifiNanIface) createIface(IfaceType.NAN, destroyedListener, handler);
     }
 
@@ -268,11 +271,16 @@
      * and false on failure. This listener is in addition to the one registered when the interface
      * was created - allowing non-creators to monitor interface status.
      *
-     * Listener called-back on the specified handler - or on the current looper if a null is passed.
+     * @param destroyedListener Listener to call when the allocated interface is removed.
+     *                          Will only be registered and used if an interface is created
+     *                          successfully.
+     * @param handler Handler on which to dispatch listener. Null implies the listener will be
+     *                invoked synchronously from the context of the client which triggered the
+     *                iface destruction.
      */
     public boolean registerDestroyedListener(IWifiIface iface,
-            InterfaceDestroyedListener destroyedListener,
-            Handler handler) {
+            @NonNull InterfaceDestroyedListener destroyedListener,
+            @Nullable Handler handler) {
         String name = getName(iface);
         if (DBG) Log.d(TAG, "registerDestroyedListener: iface(name)=" + name);
 
@@ -284,7 +292,7 @@
             }
 
             return cacheEntry.destroyedListeners.add(
-                    new InterfaceDestroyedListenerProxy(destroyedListener, handler));
+                    new InterfaceDestroyedListenerProxy(name, destroyedListener, handler));
         }
     }
 
@@ -303,11 +311,12 @@
      * @param ifaceType The interface type (IfaceType) to be monitored.
      * @param listener Listener to call when an interface of the requested
      *                 type could be created
-     * @param handler The Handler on which to dispatch the listener. A null implies a new Handler
-     *                on the current looper.
+     * @param handler Handler on which to dispatch listener. Null implies the listener will be
+     *                invoked synchronously from the context of the client which triggered the
+     *                mode change.
      */
     public void registerInterfaceAvailableForRequestListener(int ifaceType,
-            InterfaceAvailableForRequestListener listener, Handler handler) {
+            @NonNull InterfaceAvailableForRequestListener listener, @Nullable Handler handler) {
         if (DBG) Log.d(TAG, "registerInterfaceAvailableForRequestListener: ifaceType=" + ifaceType);
 
         synchronized (mLock) {
@@ -382,8 +391,10 @@
          *
          * Can be registered when the interface is requested with createXxxIface() - will
          * only be valid if the interface creation was successful - i.e. a non-null was returned.
+         *
+         * @param ifaceName Name of the interface that was destroyed.
          */
-        void onDestroyed();
+        void onDestroyed(@NonNull String ifaceName);
     }
 
     /**
@@ -1212,9 +1223,8 @@
 
     private class ManagerStatusListenerProxy  extends
             ListenerProxy<ManagerStatusListener> {
-        ManagerStatusListenerProxy(ManagerStatusListener statusListener,
-                Handler handler) {
-            super(statusListener, handler, true, "ManagerStatusListenerProxy");
+        ManagerStatusListenerProxy(ManagerStatusListener statusListener, Handler handler) {
+            super(statusListener, handler, "ManagerStatusListenerProxy");
         }
 
         @Override
@@ -1346,7 +1356,8 @@
                     cacheEntry.type = ifaceType;
                     if (destroyedListener != null) {
                         cacheEntry.destroyedListeners.add(
-                                new InterfaceDestroyedListenerProxy(destroyedListener, handler));
+                                new InterfaceDestroyedListenerProxy(
+                                        cacheEntry.name, destroyedListener, handler));
                     }
                     cacheEntry.creationTime = mClock.getUptimeSinceBootMillis();
 
@@ -1898,11 +1909,8 @@
     }
 
     private abstract class ListenerProxy<LISTENER>  {
-        private static final int LISTENER_TRIGGERED = 0;
-
         protected LISTENER mListener;
         private Handler mHandler;
-        private boolean mFrontOfQueue;
 
         // override equals & hash to make sure that the container HashSet is unique with respect to
         // the contained listener
@@ -1917,37 +1925,36 @@
         }
 
         void trigger() {
-            if (mFrontOfQueue) {
-                mHandler.postAtFrontOfQueue(() -> {
-                    action();
-                });
-            } else {
+            if (mHandler != null) {
                 mHandler.post(() -> {
                     action();
                 });
+            } else {
+                action();
             }
         }
 
         protected abstract void action();
 
-        ListenerProxy(LISTENER listener, Handler handler, boolean frontOfQueue, String tag) {
+        ListenerProxy(LISTENER listener, Handler handler, String tag) {
             mListener = listener;
             mHandler = handler;
-            mFrontOfQueue = frontOfQueue;
         }
     }
 
     private class InterfaceDestroyedListenerProxy extends
             ListenerProxy<InterfaceDestroyedListener> {
-        InterfaceDestroyedListenerProxy(InterfaceDestroyedListener destroyedListener,
-                Handler handler) {
-            super(destroyedListener, handler == null ? new Handler(Looper.myLooper()) : handler,
-                    true, "InterfaceDestroyedListenerProxy");
+        private final String mIfaceName;
+        InterfaceDestroyedListenerProxy(@NonNull String ifaceName,
+                                        InterfaceDestroyedListener destroyedListener,
+                                        Handler handler) {
+            super(destroyedListener, handler, "InterfaceDestroyedListenerProxy");
+            mIfaceName = ifaceName;
         }
 
         @Override
         protected void action() {
-            mListener.onDestroyed();
+            mListener.onDestroyed(mIfaceName);
         }
     }
 
@@ -1955,8 +1962,7 @@
             ListenerProxy<InterfaceAvailableForRequestListener> {
         InterfaceAvailableForRequestListenerProxy(
                 InterfaceAvailableForRequestListener destroyedListener, Handler handler) {
-            super(destroyedListener, handler == null ? new Handler(Looper.myLooper()) : handler,
-                    false, "InterfaceAvailableForRequestListenerProxy");
+            super(destroyedListener, handler, "InterfaceAvailableForRequestListenerProxy");
         }
 
         @Override
diff --git a/com/android/server/wifi/SoftApManager.java b/com/android/server/wifi/SoftApManager.java
index e045c39..cec9887 100644
--- a/com/android/server/wifi/SoftApManager.java
+++ b/com/android/server/wifi/SoftApManager.java
@@ -25,9 +25,7 @@
 import android.content.Intent;
 import android.net.InterfaceConfiguration;
 import android.net.wifi.IApInterface;
-import android.net.wifi.IApInterfaceEventCallback;
 import android.net.wifi.WifiConfiguration;
-import android.net.wifi.WifiConfiguration.KeyMgmt;
 import android.net.wifi.WifiManager;
 import android.os.INetworkManagementService;
 import android.os.Looper;
@@ -41,9 +39,9 @@
 import com.android.internal.util.State;
 import com.android.internal.util.StateMachine;
 import com.android.server.net.BaseNetworkObserver;
+import com.android.server.wifi.WifiNative.SoftApListener;
 import com.android.server.wifi.util.ApConfigUtil;
 
-import java.nio.charset.StandardCharsets;
 import java.util.Locale;
 
 /**
@@ -76,16 +74,14 @@
 
     private int mNumAssociatedStations = 0;
 
-    /**
-     * Listener for AP Interface events.
-     */
-    public class ApInterfaceListener extends IApInterfaceEventCallback.Stub {
+    private final SoftApListener mSoftApListener = new SoftApListener() {
         @Override
         public void onNumAssociatedStationsChanged(int numStations) {
             mStateMachine.sendMessage(
                     SoftApStateMachine.CMD_NUM_ASSOCIATED_STATIONS_CHANGED, numStations);
         }
-    }
+    };
+
 
     /**
      * Listener for soft AP state changes.
@@ -227,71 +223,24 @@
                 return ERROR_GENERIC;
             }
         }
-
-        int encryptionType = getIApInterfaceEncryptionType(localConfig);
-
         if (localConfig.hiddenSSID) {
             Log.d(TAG, "SoftAP is a hidden network");
         }
-
-        try {
-            // Note that localConfig.SSID is intended to be either a hex string or "double quoted".
-            // However, it seems that whatever is handing us these configurations does not obey
-            // this convention.
-            boolean success = mApInterface.writeHostapdConfig(
-                    localConfig.SSID.getBytes(StandardCharsets.UTF_8), localConfig.hiddenSSID,
-                    localConfig.apChannel, encryptionType,
-                    (localConfig.preSharedKey != null)
-                            ? localConfig.preSharedKey.getBytes(StandardCharsets.UTF_8)
-                            : new byte[0]);
-            if (!success) {
-                Log.e(TAG, "Failed to write hostapd configuration");
-                return ERROR_GENERIC;
-            }
-
-            success = mApInterface.startHostapd(new ApInterfaceListener());
-            if (!success) {
-                Log.e(TAG, "Failed to start hostapd.");
-                return ERROR_GENERIC;
-            }
-        } catch (RemoteException e) {
-            Log.e(TAG, "Exception in starting soft AP: " + e);
+        if (!mWifiNative.startSoftAp(localConfig, mSoftApListener)) {
+            Log.e(TAG, "Soft AP start failed");
+            return ERROR_GENERIC;
         }
-
         Log.d(TAG, "Soft AP is started");
 
         return SUCCESS;
     }
 
-    private static int getIApInterfaceEncryptionType(WifiConfiguration localConfig) {
-        int encryptionType;
-        switch (localConfig.getAuthType()) {
-            case KeyMgmt.NONE:
-                encryptionType = IApInterface.ENCRYPTION_TYPE_NONE;
-                break;
-            case KeyMgmt.WPA_PSK:
-                encryptionType = IApInterface.ENCRYPTION_TYPE_WPA;
-                break;
-            case KeyMgmt.WPA2_PSK:
-                encryptionType = IApInterface.ENCRYPTION_TYPE_WPA2;
-                break;
-            default:
-                // We really shouldn't default to None, but this was how NetworkManagementService
-                // used to do this.
-                encryptionType = IApInterface.ENCRYPTION_TYPE_NONE;
-                break;
-        }
-        return encryptionType;
-    }
-
     /**
      * Teardown soft AP.
      */
     private void stopSoftAp() {
-        try {
-            mApInterface.stopHostapd();
-        } catch (RemoteException e) {
-            Log.e(TAG, "Exception in stopping soft AP: " + e);
+        if (!mWifiNative.stopSoftAp()) {
+            Log.d(TAG, "Soft AP stop failed");
             return;
         }
         Log.d(TAG, "Soft AP is stopped");
diff --git a/com/android/server/wifi/WakeupController.java b/com/android/server/wifi/WakeupController.java
new file mode 100644
index 0000000..a3c095a
--- /dev/null
+++ b/com/android/server/wifi/WakeupController.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 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.database.ContentObserver;
+import android.os.Handler;
+import android.os.Looper;
+import android.provider.Settings;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * WakeupController is responsible managing Auto Wifi.
+ *
+ * <p>It determines if and when to re-enable wifi after it has been turned off by the user.
+ */
+public class WakeupController {
+
+    // TODO(b/69624403) propagate this to Settings
+    private static final boolean USE_PLATFORM_WIFI_WAKE = false;
+
+    private final Context mContext;
+    private final Handler mHandler;
+    private final FrameworkFacade mFrameworkFacade;
+    private final ContentObserver mContentObserver;
+
+    /** Whether this feature is enabled in Settings. */
+    private boolean mWifiWakeupEnabled;
+
+    public WakeupController(
+            Context context,
+            Looper looper,
+            FrameworkFacade frameworkFacade) {
+        mContext = context;
+        mHandler = new Handler(looper);
+        mFrameworkFacade = frameworkFacade;
+        mContentObserver = new ContentObserver(mHandler) {
+            @Override
+            public void onChange(boolean selfChange) {
+                mWifiWakeupEnabled = mFrameworkFacade.getIntegerSetting(
+                                mContext, Settings.Global.WIFI_WAKEUP_ENABLED, 0) == 1;
+            }
+        };
+        mFrameworkFacade.registerContentObserver(mContext, Settings.Global.getUriFor(
+                Settings.Global.WIFI_WAKEUP_ENABLED), true, mContentObserver);
+        mContentObserver.onChange(false /* selfChange */);
+    }
+
+    /**
+     * Whether the feature is enabled in settings.
+     *
+     * <p>Note: This method is only used to determine whether or not to actually enable wifi. All
+     * other aspects of the WakeupController lifecycle operate normally irrespective of this.
+     */
+    @VisibleForTesting
+    boolean isEnabled() {
+        return mWifiWakeupEnabled;
+    }
+}
diff --git a/com/android/server/wifi/WifiInjector.java b/com/android/server/wifi/WifiInjector.java
index cc55934..f14a57f 100644
--- a/com/android/server/wifi/WifiInjector.java
+++ b/com/android/server/wifi/WifiInjector.java
@@ -122,6 +122,7 @@
     private final WifiStateTracker mWifiStateTracker;
     private final Runtime mJavaRuntime;
     private final SelfRecovery mSelfRecovery;
+    private final WakeupController mWakeupController;
 
     private final boolean mUseRealLogger;
 
@@ -230,6 +231,8 @@
                 mWifiConfigManager, mWifiConfigStore, mWifiStateMachine,
                 new OpenNetworkRecommender(),
                 new ConnectToNetworkNotificationBuilder(mContext, mFrameworkFacade));
+        mWakeupController = new WakeupController(mContext,
+                mWifiStateMachineHandlerThread.getLooper(), mFrameworkFacade);
         mLockManager = new WifiLockManager(mContext, BatteryStatsService.getService());
         mWifiController = new WifiController(mContext, mWifiStateMachine, mSettingsStore,
                 mLockManager, mWifiServiceHandlerThread.getLooper(), mFrameworkFacade);
@@ -349,6 +352,10 @@
         return mPasspointManager;
     }
 
+    public WakeupController getWakeupController() {
+        return mWakeupController;
+    }
+
     public TelephonyManager makeTelephonyManager() {
         // may not be available when WiFi starts
         return (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
diff --git a/com/android/server/wifi/WifiNative.java b/com/android/server/wifi/WifiNative.java
index cda1cf6..cf80d22 100644
--- a/com/android/server/wifi/WifiNative.java
+++ b/com/android/server/wifi/WifiNative.java
@@ -273,6 +273,36 @@
         return mWificondControl.stopPnoScan();
     }
 
+    /**
+     * Callbacks for SoftAp interface.
+     */
+    public interface SoftApListener {
+        /**
+         * Invoked when the number of associated stations changes.
+         */
+        void onNumAssociatedStationsChanged(int numStations);
+    }
+
+    /**
+     * Start Soft AP operation using the provided configuration.
+     *
+     * @param config Configuration to use for the soft ap created.
+     * @param listener Callback for AP events.
+     * @return true on success, false otherwise.
+     */
+    public boolean startSoftAp(WifiConfiguration config, SoftApListener listener) {
+        return mWificondControl.startSoftAp(config, listener);
+    }
+
+    /**
+     * Stop the ongoing Soft AP operation.
+     *
+     * @return true on success, false otherwise.
+     */
+    public boolean stopSoftAp() {
+        return mWificondControl.stopSoftAp();
+    }
+
     /********************************************************
      * Supplicant operations
      ********************************************************/
diff --git a/com/android/server/wifi/WifiServiceImpl.java b/com/android/server/wifi/WifiServiceImpl.java
index e86bd54..8db180f 100644
--- a/com/android/server/wifi/WifiServiceImpl.java
+++ b/com/android/server/wifi/WifiServiceImpl.java
@@ -16,7 +16,6 @@
 
 package com.android.server.wifi;
 
-import static android.app.AppOpsManager.MODE_IGNORED;
 import static android.net.wifi.WifiManager.EXTRA_PREVIOUS_WIFI_AP_STATE;
 import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_FAILURE_REASON;
 import static android.net.wifi.WifiManager.EXTRA_WIFI_AP_INTERFACE_NAME;
@@ -43,7 +42,6 @@
 import static com.android.server.wifi.WifiController.CMD_WIFI_TOGGLED;
 
 import android.Manifest;
-import android.annotation.CheckResult;
 import android.app.ActivityManager;
 import android.app.ActivityManager.RunningAppProcessInfo;
 import android.app.AppOpsManager;
@@ -75,6 +73,7 @@
 import android.net.wifi.WifiManager;
 import android.net.wifi.WifiManager.LocalOnlyHotspotCallback;
 import android.net.wifi.WifiScanner;
+import android.net.wifi.hotspot2.IProvisioningCallback;
 import android.net.wifi.hotspot2.OsuProvider;
 import android.net.wifi.hotspot2.PasspointConfiguration;
 import android.os.AsyncTask;
@@ -143,11 +142,6 @@
     private static final boolean DBG = true;
     private static final boolean VDBG = false;
 
-    // Dumpsys argument to enable/disable disconnect on IP reachability failures.
-    private static final String DUMP_ARG_SET_IPREACH_DISCONNECT = "set-ipreach-disconnect";
-    private static final String DUMP_ARG_SET_IPREACH_DISCONNECT_ENABLED = "enabled";
-    private static final String DUMP_ARG_SET_IPREACH_DISCONNECT_DISABLED = "disabled";
-
     // Default scan background throttling interval if not overriden in settings
     private static final long DEFAULT_SCAN_BACKGROUND_THROTTLE_INTERVAL_MS = 30 * 60 * 1000;
 
@@ -591,9 +585,7 @@
      */
     @Override
     public void startScan(ScanSettings settings, WorkSource workSource, String packageName) {
-        if (enforceChangePermission(packageName) == MODE_IGNORED) {
-            return;
-        }
+        enforceChangePermission();
 
         mLog.info("startScan uid=%").c(Binder.getCallingUid()).flush();
         // Check and throttle background apps for wifi scan.
@@ -737,21 +729,9 @@
                 "WifiService");
     }
 
-    /**
-     * Checks whether the caller can change the wifi state.
-     * Possible results:
-     * 1. Operation is allowed. No exception thrown, and AppOpsManager.MODE_ALLOWED returned.
-     * 2. Operation is not allowed, and caller must be told about this. SecurityException is thrown.
-     * 3. Operation is not allowed, and caller must not be told about this (i.e. must silently
-     * ignore the operation). No exception is thrown, and AppOpsManager.MODE_IGNORED returned.
-     */
-    @CheckResult
-    private int enforceChangePermission(String callingPackage) {
+    private void enforceChangePermission() {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.CHANGE_WIFI_STATE,
                 "WifiService");
-
-        return mAppOps.noteOp(AppOpsManager.OP_CHANGE_WIFI_STATE, Binder.getCallingUid(),
-                callingPackage);
     }
 
     private void enforceLocationHardwarePermission() {
@@ -795,10 +775,7 @@
     @Override
     public synchronized boolean setWifiEnabled(String packageName, boolean enable)
             throws RemoteException {
-        if (enforceChangePermission(packageName) == MODE_IGNORED) {
-            return false;
-        }
-
+        enforceChangePermission();
         Slog.d(TAG, "setWifiEnabled: " + enable + " pid=" + Binder.getCallingPid()
                     + ", uid=" + Binder.getCallingUid() + ", package=" + packageName);
         mLog.info("setWifiEnabled package=% uid=% enable=%").c(packageName)
@@ -1192,9 +1169,7 @@
         final int uid = Binder.getCallingUid();
         final int pid = Binder.getCallingPid();
 
-        if (enforceChangePermission(packageName) == MODE_IGNORED) {
-            return LocalOnlyHotspotCallback.ERROR_GENERIC;
-        }
+        enforceChangePermission();
         enforceLocationPermission(packageName, uid);
         // also need to verify that Locations services are enabled.
         if (mSettingsStore.getLocationModeSetting(mContext) == Settings.Secure.LOCATION_MODE_OFF) {
@@ -1266,12 +1241,9 @@
      * Hotspot.
      */
     @Override
-    public void stopLocalOnlyHotspot(String packageName) {
+    public void stopLocalOnlyHotspot() {
         // first check if the caller has permission to stop a local only hotspot
-        if (enforceChangePermission(packageName) == MODE_IGNORED) {
-            // As this step is about cleaning up previously allocated resources, we'll allow the
-            // app to do this cleanup even if the op is configured to be ignored.
-        }
+        enforceChangePermission();
         final int uid = Binder.getCallingUid();
         final int pid = Binder.getCallingPid();
 
@@ -1373,10 +1345,8 @@
      * @throws SecurityException if the caller does not have permission to write the sotap config
      */
     @Override
-    public void setWifiApConfiguration(WifiConfiguration wifiConfig, String packageName) {
-        if (enforceChangePermission(packageName) == MODE_IGNORED) {
-            return;
-        }
+    public void setWifiApConfiguration(WifiConfiguration wifiConfig) {
+        enforceChangePermission();
         int uid = Binder.getCallingUid();
         // only allow Settings UI to write the stored SoftApConfig
         if (!mWifiPermissionsUtil.checkConfigOverridePermission(uid)) {
@@ -1408,10 +1378,8 @@
      * see {@link android.net.wifi.WifiManager#disconnect()}
      */
     @Override
-    public void disconnect(String packageName) {
-        if (enforceChangePermission(packageName) == MODE_IGNORED) {
-            return;
-        }
+    public void disconnect() {
+        enforceChangePermission();
         mLog.info("disconnect uid=%").c(Binder.getCallingUid()).flush();
         mWifiStateMachine.disconnectCommand();
     }
@@ -1420,10 +1388,8 @@
      * see {@link android.net.wifi.WifiManager#reconnect()}
      */
     @Override
-    public void reconnect(String packageName) {
-        if (enforceChangePermission(packageName) == MODE_IGNORED) {
-            return;
-        }
+    public void reconnect() {
+        enforceChangePermission();
         mLog.info("reconnect uid=%").c(Binder.getCallingUid()).flush();
         mWifiStateMachine.reconnectCommand(new WorkSource(Binder.getCallingUid()));
     }
@@ -1432,10 +1398,8 @@
      * see {@link android.net.wifi.WifiManager#reassociate()}
      */
     @Override
-    public void reassociate(String packageName) {
-        if (enforceChangePermission(packageName) == MODE_IGNORED) {
-            return;
-        }
+    public void reassociate() {
+        enforceChangePermission();
         mLog.info("reassociate uid=%").c(Binder.getCallingUid()).flush();
         mWifiStateMachine.reassociateCommand();
     }
@@ -1636,10 +1600,8 @@
      * network if the operation succeeds, or {@code -1} if it fails
      */
     @Override
-    public int addOrUpdateNetwork(WifiConfiguration config, String packageName) {
-        if (enforceChangePermission(packageName) == MODE_IGNORED) {
-            return -1;
-        }
+    public int addOrUpdateNetwork(WifiConfiguration config) {
+        enforceChangePermission();
         mLog.info("addOrUpdateNetwork uid=%").c(Binder.getCallingUid()).flush();
 
         // Previously, this API is overloaded for installing Passpoint profiles.  Now
@@ -1658,7 +1620,7 @@
                     config.enterpriseConfig.getClientCertificateChain());
             passpointConfig.getCredential().setClientPrivateKey(
                     config.enterpriseConfig.getClientPrivateKey());
-            if (!addOrUpdatePasspointConfiguration(passpointConfig, packageName)) {
+            if (!addOrUpdatePasspointConfiguration(passpointConfig)) {
                 Slog.e(TAG, "Failed to add Passpoint profile");
                 return -1;
             }
@@ -1709,10 +1671,8 @@
      * @return {@code true} if the operation succeeded
      */
     @Override
-    public boolean removeNetwork(int netId, String packageName) {
-        if (enforceChangePermission(packageName) == MODE_IGNORED) {
-            return false;
-        }
+    public boolean removeNetwork(int netId) {
+        enforceChangePermission();
         mLog.info("removeNetwork uid=%").c(Binder.getCallingUid()).flush();
         // TODO Add private logging for netId b/33807876
         if (mWifiStateMachineChannel != null) {
@@ -1731,10 +1691,8 @@
      * @return {@code true} if the operation succeeded
      */
     @Override
-    public boolean enableNetwork(int netId, boolean disableOthers, String packageName) {
-        if (enforceChangePermission(packageName) == MODE_IGNORED) {
-            return false;
-        }
+    public boolean enableNetwork(int netId, boolean disableOthers) {
+        enforceChangePermission();
         // TODO b/33807876 Log netId
         mLog.info("enableNetwork uid=% disableOthers=%")
                 .c(Binder.getCallingUid())
@@ -1756,10 +1714,8 @@
      * @return {@code true} if the operation succeeded
      */
     @Override
-    public boolean disableNetwork(int netId, String packageName) {
-        if (enforceChangePermission(packageName) == MODE_IGNORED) {
-            return false;
-        }
+    public boolean disableNetwork(int netId) {
+        enforceChangePermission();
         // TODO b/33807876 Log netId
         mLog.info("disableNetwork uid=%").c(Binder.getCallingUid()).flush();
 
@@ -1817,11 +1773,8 @@
      * @return true on success or false on failure
      */
     @Override
-    public boolean addOrUpdatePasspointConfiguration(
-            PasspointConfiguration config, String packageName) {
-        if (enforceChangePermission(packageName) == MODE_IGNORED) {
-            return false;
-        }
+    public boolean addOrUpdatePasspointConfiguration(PasspointConfiguration config) {
+        enforceChangePermission();
         mLog.info("addorUpdatePasspointConfiguration uid=%").c(Binder.getCallingUid()).flush();
         if (!mContext.getPackageManager().hasSystemFeature(
                 PackageManager.FEATURE_WIFI_PASSPOINT)) {
@@ -1838,10 +1791,8 @@
      * @return true on success or false on failure
      */
     @Override
-    public boolean removePasspointConfiguration(String fqdn, String packageName) {
-        if (enforceChangePermission(packageName) == MODE_IGNORED) {
-            return false;
-        }
+    public boolean removePasspointConfiguration(String fqdn) {
+        enforceChangePermission();
         mLog.info("removePasspointConfiguration uid=%").c(Binder.getCallingUid()).flush();
         if (!mContext.getPackageManager().hasSystemFeature(
                 PackageManager.FEATURE_WIFI_PASSPOINT)) {
@@ -1913,10 +1864,8 @@
      * TODO: deprecate this
      */
     @Override
-    public boolean saveConfiguration(String packageName) {
-        if (enforceChangePermission(packageName) == MODE_IGNORED) {
-            return false;
-        }
+    public boolean saveConfiguration() {
+        enforceChangePermission();
         mLog.info("saveConfiguration uid=%").c(Binder.getCallingUid()).flush();
         if (mWifiStateMachineChannel != null) {
             return mWifiStateMachine.syncSaveConfig(mWifiStateMachineChannel);
@@ -2112,13 +2061,9 @@
      * an AsyncChannel communication with WifiService
      */
     @Override
-    public Messenger getWifiServiceMessenger(String packageName) throws RemoteException {
+    public Messenger getWifiServiceMessenger() {
         enforceAccessPermission();
-        if (enforceChangePermission(packageName) == MODE_IGNORED) {
-            // We don't have a good way of creating a fake Messenger, and returning null would
-            // immediately break callers.
-            throw new RemoteException("Could not create wifi service messenger");
-        }
+        enforceChangePermission();
         mLog.info("getWifiServiceMessenger uid=%").c(Binder.getCallingUid()).flush();
         return new Messenger(mClientHandler);
     }
@@ -2127,11 +2072,9 @@
      * Disable an ephemeral network, i.e. network that is created thru a WiFi Scorer
      */
     @Override
-    public void disableEphemeralNetwork(String SSID, String packageName) {
+    public void disableEphemeralNetwork(String SSID) {
         enforceAccessPermission();
-        if (enforceChangePermission(packageName) == MODE_IGNORED) {
-            return;
-        }
+        enforceChangePermission();
         mLog.info("disableEphemeralNetwork uid=%").c(Binder.getCallingUid()).flush();
         mWifiStateMachine.disableEphemeralNetwork(SSID);
     }
@@ -2485,10 +2428,8 @@
     }
 
     @Override
-    public boolean setEnableAutoJoinWhenAssociated(boolean enabled, String packageName) {
-        if (enforceChangePermission(packageName) == MODE_IGNORED) {
-            return false;
-        }
+    public boolean setEnableAutoJoinWhenAssociated(boolean enabled) {
+        enforceChangePermission();
         mLog.info("setEnableAutoJoinWhenAssociated uid=% enabled=%")
                 .c(Binder.getCallingUid())
                 .c(enabled).flush();
@@ -2517,7 +2458,7 @@
     }
 
     @Override
-    public void factoryReset(String packageName) {
+    public void factoryReset() {
         enforceConnectivityInternalPermission();
         mLog.info("factoryReset uid=%").c(Binder.getCallingUid()).flush();
         if (mUserManager.hasUserRestriction(UserManager.DISALLOW_NETWORK_RESET)) {
@@ -2543,9 +2484,9 @@
                         Binder.getCallingUid(), mWifiStateMachineChannel);
                 if (networks != null) {
                     for (WifiConfiguration config : networks) {
-                        removeNetwork(config.networkId, packageName);
+                        removeNetwork(config.networkId);
                     }
-                    saveConfiguration(packageName);
+                    saveConfiguration();
                 }
             }
         }
@@ -2719,4 +2660,33 @@
         restoreNetworks(wifiConfigurations);
         Slog.d(TAG, "Restored supplicant backup data");
     }
+
+    /**
+     * Starts subscription provisioning with a provider
+     *
+     * @param provider {@link OsuProvider} the provider to provision with
+     * @param callback {@link IProvisoningCallback} the callback object to inform status
+     */
+    @Override
+    public void startSubscriptionProvisioning(OsuProvider provider,
+            IProvisioningCallback callback) {
+        if (provider == null) {
+            throw new IllegalArgumentException("Provider must not be null");
+        }
+        if (callback == null) {
+            throw new IllegalArgumentException("Callback must not be null");
+        }
+        enforceNetworkSettingsPermission();
+        if (!mContext.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_WIFI_PASSPOINT)) {
+            throw new UnsupportedOperationException("Passpoint not enabled");
+        }
+        final int uid = Binder.getCallingUid();
+        mLog.trace("startSubscriptionProvisioning uid=%").c(uid).flush();
+        if (mWifiStateMachine.syncStartSubscriptionProvisioning(uid, provider,
+                callback, mWifiStateMachineChannel)) {
+            mLog.trace("Subscription provisioning started with %")
+                    .c(provider.toString()).flush();
+        }
+    }
 }
diff --git a/com/android/server/wifi/WifiStateMachine.java b/com/android/server/wifi/WifiStateMachine.java
index 2c8c0b7..b005923 100644
--- a/com/android/server/wifi/WifiStateMachine.java
+++ b/com/android/server/wifi/WifiStateMachine.java
@@ -78,6 +78,7 @@
 import android.net.wifi.WpsInfo;
 import android.net.wifi.WpsResult;
 import android.net.wifi.WpsResult.Status;
+import android.net.wifi.hotspot2.IProvisioningCallback;
 import android.net.wifi.hotspot2.OsuProvider;
 import android.net.wifi.hotspot2.PasspointConfiguration;
 import android.net.wifi.p2p.IWifiP2pManager;
@@ -181,6 +182,7 @@
 
     private static final String EXTRA_OSU_ICON_QUERY_BSSID = "BSSID";
     private static final String EXTRA_OSU_ICON_QUERY_FILENAME = "FILENAME";
+    private static final String EXTRA_OSU_PROVIDER = "OsuProvider";
 
     private boolean mVerboseLoggingEnabled = false;
 
@@ -740,6 +742,9 @@
     /* Used to set the tx power limit for SAR during the start of a phone call. */
     private static final int CMD_SELECT_TX_POWER_SCENARIO               = BASE + 253;
 
+    // Start subscription provisioning with a given provider
+    private static final int CMD_START_SUBSCRIPTION_PROVISIONING        = BASE + 254;
+
     // For message logging.
     private static final Class[] sMessageClasses = {
             AsyncChannel.class, WifiStateMachine.class, DhcpClient.class };
@@ -1246,6 +1251,7 @@
         mWifiNative.enableVerboseLogging(verbose);
         mWifiConfigManager.enableVerboseLogging(verbose);
         mSupplicantStateTracker.enableVerboseLogging(verbose);
+        mPasspointManager.enableVerboseLogging(verbose);
     }
 
     private static final String SYSTEM_PROPERTY_LOG_CONTROL_WIFIHAL = "log.tag.WifiHAL";
@@ -2011,6 +2017,25 @@
     }
 
     /**
+     * Start subscription provisioning synchronously
+     *
+     * @param provider {@link OsuProvider} the provider to provision with
+     * @param callback {@link IProvisioningCallback} callback for provisioning status
+     * @return boolean true indicates provisioning was started, false otherwise
+     */
+    public boolean syncStartSubscriptionProvisioning(int callingUid, OsuProvider provider,
+            IProvisioningCallback callback, AsyncChannel channel) {
+        Message msg = Message.obtain();
+        msg.what = CMD_START_SUBSCRIPTION_PROVISIONING;
+        msg.arg1 = callingUid;
+        msg.obj = callback;
+        msg.getData().putParcelable(EXTRA_OSU_PROVIDER, provider);
+        Message resultMsg = channel.sendMessageSynchronously(msg);
+        boolean result = resultMsg.arg1 != 0;
+        resultMsg.recycle();
+        return result;
+    }
+    /**
      * Get connection statistics synchronously
      *
      * @param channel
@@ -3896,6 +3921,8 @@
                     break;
                 case CMD_INITIALIZE:
                     ok = mWifiNative.initializeVendorHal(mVendorHalDeathRecipient);
+                    mPasspointManager.initializeProvisioner(
+                            mWifiInjector.getWifiServiceHandlerThread().getLooper());
                     replyToMessage(message, message.what, ok ? SUCCESS : FAILURE);
                     break;
                 case CMD_BOOT_COMPLETED:
@@ -4028,6 +4055,9 @@
                 case CMD_GET_MATCHING_OSU_PROVIDERS:
                     replyToMessage(message, message.what, new ArrayList<OsuProvider>());
                     break;
+                case CMD_START_SUBSCRIPTION_PROVISIONING:
+                    replyToMessage(message, message.what, 0);
+                    break;
                 case CMD_IP_CONFIGURATION_SUCCESSFUL:
                 case CMD_IP_CONFIGURATION_LOST:
                 case CMD_IP_REACHABILITY_LOST:
@@ -5156,6 +5186,14 @@
                     replyToMessage(message, message.what,
                             mPasspointManager.getMatchingOsuProviders((ScanResult) message.obj));
                     break;
+                case CMD_START_SUBSCRIPTION_PROVISIONING:
+                    IProvisioningCallback callback = (IProvisioningCallback) message.obj;
+                    OsuProvider provider =
+                            (OsuProvider) message.getData().getParcelable(EXTRA_OSU_PROVIDER);
+                    int res = mPasspointManager.startSubscriptionProvisioning(
+                                    message.arg1, provider, callback) ? 1 : 0;
+                    replyToMessage(message, message.what, res);
+                    break;
                 case CMD_RECONNECT:
                     WorkSource workSource = (WorkSource) message.obj;
                     mWifiConnectivityManager.forceConnectivityScan(workSource);
diff --git a/com/android/server/wifi/WificondControl.java b/com/android/server/wifi/WificondControl.java
index aa723d6..3dd6311 100644
--- a/com/android/server/wifi/WificondControl.java
+++ b/com/android/server/wifi/WificondControl.java
@@ -18,18 +18,21 @@
 
 import android.annotation.NonNull;
 import android.net.wifi.IApInterface;
+import android.net.wifi.IApInterfaceEventCallback;
 import android.net.wifi.IClientInterface;
 import android.net.wifi.IPnoScanEvent;
 import android.net.wifi.IScanEvent;
 import android.net.wifi.IWifiScannerImpl;
 import android.net.wifi.IWificond;
 import android.net.wifi.ScanResult;
+import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiScanner;
 import android.net.wifi.WifiSsid;
 import android.os.Binder;
 import android.os.RemoteException;
 import android.util.Log;
 
+import com.android.server.wifi.WifiNative.SoftApListener;
 import com.android.server.wifi.hotspot2.NetworkDetail;
 import com.android.server.wifi.util.InformationElementUtil;
 import com.android.server.wifi.util.NativeUtil;
@@ -41,6 +44,7 @@
 import com.android.server.wifi.wificond.PnoSettings;
 import com.android.server.wifi.wificond.SingleScanSettings;
 
+import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Set;
 
@@ -70,6 +74,7 @@
     private IWifiScannerImpl mWificondScanner;
     private IScanEvent mScanEventHandler;
     private IPnoScanEvent mPnoScanEventHandler;
+    private IApInterfaceEventCallback mApInterfaceListener;
 
     private String mClientInterfaceName;
 
@@ -122,6 +127,22 @@
         }
     }
 
+    /**
+     * Listener for AP Interface events.
+     */
+    private class ApInterfaceEventCallback extends IApInterfaceEventCallback.Stub {
+        private SoftApListener mSoftApListener;
+
+        ApInterfaceEventCallback(SoftApListener listener) {
+            mSoftApListener = listener;
+        }
+
+        @Override
+        public void onNumAssociatedStationsChanged(int numStations) {
+            mSoftApListener.onNumAssociatedStationsChanged(numStations);
+        }
+    }
+
     /** Enable or disable verbose logging of WificondControl.
      *  @param enable True to enable verbose logging. False to disable verbose logging.
      */
@@ -251,12 +272,12 @@
     * @return Returns true on success.
     */
     public boolean disableSupplicant() {
-        if (mClientInterface == null) {
-            Log.e(TAG, "No valid wificond client interface handler");
+        if (mWificond == null) {
+            Log.e(TAG, "No valid handler");
             return false;
         }
         try {
-            return mClientInterface.disableSupplicant();
+            return mWificond.disableSupplicant();
         } catch (RemoteException e) {
             Log.e(TAG, "Failed to disable supplicant due to remote exception");
         }
@@ -268,13 +289,13 @@
     * @return Returns true on success.
     */
     public boolean enableSupplicant() {
-        if (mClientInterface == null) {
-            Log.e(TAG, "No valid wificond client interface handler");
+        if (mWificond == null) {
+            Log.e(TAG, "No valid wificond handler");
             return false;
         }
 
         try {
-            return mClientInterface.enableSupplicant();
+            return mWificond.enableSupplicant();
         } catch (RemoteException e) {
             Log.e(TAG, "Failed to enable supplicant due to remote exception");
         }
@@ -526,7 +547,6 @@
         }
     }
 
-
     /**
      * Query the list of valid frequencies for the provided band.
      * The result depends on the on the country code that has been set.
@@ -560,4 +580,90 @@
         }
         return null;
     }
+
+    /**
+     * Start Soft AP operation using the provided configuration.
+     *
+     * @param config Configuration to use for the soft ap created.
+     * @param listener Callback for AP events.
+     * @return true on success, false otherwise.
+     */
+    public boolean startSoftAp(WifiConfiguration config, SoftApListener listener) {
+        if (mApInterface == null) {
+            Log.e(TAG, "No valid ap interface handler");
+            return false;
+        }
+        int encryptionType = getIApInterfaceEncryptionType(config);
+        try {
+            // TODO(b/67745880) Note that config.SSID is intended to be either a
+            // hex string or "double quoted".
+            // However, it seems that whatever is handing us these configurations does not obey
+            // this convention.
+            boolean success = mApInterface.writeHostapdConfig(
+                    config.SSID.getBytes(StandardCharsets.UTF_8), config.hiddenSSID,
+                    config.apChannel, encryptionType,
+                    (config.preSharedKey != null)
+                            ? config.preSharedKey.getBytes(StandardCharsets.UTF_8)
+                            : new byte[0]);
+            if (!success) {
+                Log.e(TAG, "Failed to write hostapd configuration");
+                return false;
+            }
+            mApInterfaceListener = new ApInterfaceEventCallback(listener);
+            success = mApInterface.startHostapd(mApInterfaceListener);
+            if (!success) {
+                Log.e(TAG, "Failed to start hostapd.");
+                return false;
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Exception in starting soft AP: " + e);
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Stop the ongoing Soft AP operation.
+     *
+     * @return true on success, false otherwise.
+     */
+    public boolean stopSoftAp() {
+        if (mApInterface == null) {
+            Log.e(TAG, "No valid ap interface handler");
+            return false;
+        }
+        try {
+            boolean success = mApInterface.stopHostapd();
+            if (!success) {
+                Log.e(TAG, "Failed to stop hostapd.");
+                return false;
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "Exception in stopping soft AP: " + e);
+            return false;
+        }
+        mApInterfaceListener = null;
+        return true;
+    }
+
+    private static int getIApInterfaceEncryptionType(WifiConfiguration localConfig) {
+        int encryptionType;
+        switch (localConfig.getAuthType()) {
+            case WifiConfiguration.KeyMgmt.NONE:
+                encryptionType = IApInterface.ENCRYPTION_TYPE_NONE;
+                break;
+            case WifiConfiguration.KeyMgmt.WPA_PSK:
+                encryptionType = IApInterface.ENCRYPTION_TYPE_WPA;
+                break;
+            case WifiConfiguration.KeyMgmt.WPA2_PSK:
+                encryptionType = IApInterface.ENCRYPTION_TYPE_WPA2;
+                break;
+            default:
+                // We really shouldn't default to None, but this was how NetworkManagementService
+                // used to do this.
+                encryptionType = IApInterface.ENCRYPTION_TYPE_NONE;
+                break;
+        }
+        return encryptionType;
+    }
 }
diff --git a/com/android/server/wifi/aware/WifiAwareDiscoverySessionState.java b/com/android/server/wifi/aware/WifiAwareDiscoverySessionState.java
index 86f4e37..3358a4a 100644
--- a/com/android/server/wifi/aware/WifiAwareDiscoverySessionState.java
+++ b/com/android/server/wifi/aware/WifiAwareDiscoverySessionState.java
@@ -244,13 +244,20 @@
      *            (usually not used in the match decisions).
      * @param matchFilter The filter from the discovery advertisement (which was
      *            used in the match decision).
+     * @param rangingIndication Bit mask indicating the type of ranging event triggered.
+     * @param rangeMm The range to the peer in mm (valid if rangingIndication specifies ingress
+     *                or egress events - i.e. non-zero).
      */
     public void onMatch(int requestorInstanceId, byte[] peerMac, byte[] serviceSpecificInfo,
-            byte[] matchFilter) {
+            byte[] matchFilter, int rangingIndication, int rangeMm) {
         int peerId = getPeerIdOrAddIfNew(requestorInstanceId, peerMac);
 
         try {
-            mCallback.onMatch(peerId, serviceSpecificInfo, matchFilter);
+            if (rangingIndication == 0) {
+                mCallback.onMatch(peerId, serviceSpecificInfo, matchFilter);
+            } else {
+                mCallback.onMatchWithDistance(peerId, serviceSpecificInfo, matchFilter, rangeMm);
+            }
         } catch (RemoteException e) {
             Log.w(TAG, "onMatch: RemoteException (FYI): " + e);
         }
diff --git a/com/android/server/wifi/aware/WifiAwareNativeApi.java b/com/android/server/wifi/aware/WifiAwareNativeApi.java
index a6e724f..c8b5fce 100644
--- a/com/android/server/wifi/aware/WifiAwareNativeApi.java
+++ b/com/android/server/wifi/aware/WifiAwareNativeApi.java
@@ -26,6 +26,7 @@
 import android.hardware.wifi.V1_0.NanInitiateDataPathRequest;
 import android.hardware.wifi.V1_0.NanMatchAlg;
 import android.hardware.wifi.V1_0.NanPublishRequest;
+import android.hardware.wifi.V1_0.NanRangingIndication;
 import android.hardware.wifi.V1_0.NanRespondToDataPathIndicationRequest;
 import android.hardware.wifi.V1_0.NanSubscribeRequest;
 import android.hardware.wifi.V1_0.NanTransmitFollowupRequest;
@@ -430,11 +431,13 @@
         req.baseConfigs.disableMatchExpirationIndication = true;
         req.baseConfigs.disableFollowupReceivedIndication = false;
 
-        // TODO: configure ranging and security
-        req.baseConfigs.securityConfig.securityType = NanDataPathSecurityType.OPEN;
-        req.baseConfigs.rangingRequired = false;
         req.autoAcceptDataPathRequests = false;
 
+        req.baseConfigs.rangingRequired = publishConfig.mEnableRanging;
+
+        // TODO: configure security
+        req.baseConfigs.securityConfig.securityType = NanDataPathSecurityType.OPEN;
+
         req.publishType = publishConfig.mPublishType;
         req.txType = NanTxType.BROADCAST;
 
@@ -493,9 +496,23 @@
         req.baseConfigs.disableMatchExpirationIndication = true;
         req.baseConfigs.disableFollowupReceivedIndication = false;
 
-        // TODO: configure ranging and security
+        req.baseConfigs.rangingRequired =
+                subscribeConfig.mMinDistanceMmSet || subscribeConfig.mMaxDistanceMmSet;
+        req.baseConfigs.configRangingIndications = 0;
+        // TODO: b/69428593 remove correction factors once HAL converted from CM to MM
+        if (subscribeConfig.mMinDistanceMmSet) {
+            req.baseConfigs.distanceIngressCm = (short) Math.min(
+                    subscribeConfig.mMinDistanceMm / 10, Short.MAX_VALUE);
+            req.baseConfigs.configRangingIndications |= NanRangingIndication.INGRESS_MET_MASK;
+        }
+        if (subscribeConfig.mMaxDistanceMmSet) {
+            req.baseConfigs.distanceEgressCm = (short) Math.min(subscribeConfig.mMaxDistanceMm / 10,
+                    Short.MAX_VALUE);
+            req.baseConfigs.configRangingIndications |= NanRangingIndication.EGRESS_MET_MASK;
+        }
+
+        // TODO: configure security
         req.baseConfigs.securityConfig.securityType = NanDataPathSecurityType.OPEN;
-        req.baseConfigs.rangingRequired = false;
 
         req.subscribeType = subscribeConfig.mSubscribeType;
 
diff --git a/com/android/server/wifi/aware/WifiAwareNativeCallback.java b/com/android/server/wifi/aware/WifiAwareNativeCallback.java
index 2121160..b45978b 100644
--- a/com/android/server/wifi/aware/WifiAwareNativeCallback.java
+++ b/com/android/server/wifi/aware/WifiAwareNativeCallback.java
@@ -405,13 +405,17 @@
                     + (event.serviceSpecificInfo == null ? 0 : event.serviceSpecificInfo.size())
                     + ", matchFilter=" + Arrays.toString(
                     convertArrayListToNativeByteArray(event.matchFilter)) + ", mf.size()=" + (
-                    event.matchFilter == null ? 0 : event.matchFilter.size()));
+                    event.matchFilter == null ? 0 : event.matchFilter.size())
+                    + ", rangingIndicationType=" + event.rangingIndicationType
+                    + ", rangingMeasurementInCm=" + event.rangingMeasurementInCm);
         }
         incrementCbCount(CB_EV_MATCH);
 
+        // TODO: b/69428593 get rid of conversion once HAL moves from CM to MM
         mWifiAwareStateManager.onMatchNotification(event.discoverySessionId, event.peerId,
                 event.addr, convertArrayListToNativeByteArray(event.serviceSpecificInfo),
-                convertArrayListToNativeByteArray(event.matchFilter));
+                convertArrayListToNativeByteArray(event.matchFilter), event.rangingIndicationType,
+                event.rangingMeasurementInCm * 10);
     }
 
     @Override
diff --git a/com/android/server/wifi/aware/WifiAwareNativeManager.java b/com/android/server/wifi/aware/WifiAwareNativeManager.java
index 8659a77..d6bec5f 100644
--- a/com/android/server/wifi/aware/WifiAwareNativeManager.java
+++ b/com/android/server/wifi/aware/WifiAwareNativeManager.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wifi.aware;
 
+import android.annotation.NonNull;
 import android.hardware.wifi.V1_0.IWifiNanIface;
 import android.hardware.wifi.V1_0.IfaceType;
 import android.hardware.wifi.V1_0.WifiStatus;
@@ -150,7 +151,7 @@
     private class InterfaceDestroyedListener implements
             HalDeviceManager.InterfaceDestroyedListener {
         @Override
-        public void onDestroyed() {
+        public void onDestroyed(@NonNull String ifaceName) {
             if (DBG) Log.d(TAG, "Interface was destroyed");
             awareIsDown();
         }
diff --git a/com/android/server/wifi/aware/WifiAwareStateManager.java b/com/android/server/wifi/aware/WifiAwareStateManager.java
index 0efe736..89d9a90 100644
--- a/com/android/server/wifi/aware/WifiAwareStateManager.java
+++ b/com/android/server/wifi/aware/WifiAwareStateManager.java
@@ -183,6 +183,8 @@
     private static final String MESSAGE_BUNDLE_KEY_PMK = "pmk";
     private static final String MESSAGE_BUNDLE_KEY_PASSPHRASE = "passphrase";
     private static final String MESSAGE_BUNDLE_KEY_OOB = "out_of_band";
+    private static final String MESSAGE_RANGING_INDICATION = "ranging_indication";
+    private static final String MESSAGE_RANGE_MM = "range_mm";
 
     private WifiAwareNativeApi mWifiAwareNativeApi;
     private WifiAwareNativeManager mWifiAwareNativeManager;
@@ -944,7 +946,7 @@
      * matching service (to the one we were looking for).
      */
     public void onMatchNotification(int pubSubId, int requestorInstanceId, byte[] peerMac,
-            byte[] serviceSpecificInfo, byte[] matchFilter) {
+            byte[] serviceSpecificInfo, byte[] matchFilter, int rangingIndication, int rangeMm) {
         Message msg = mSm.obtainMessage(MESSAGE_TYPE_NOTIFICATION);
         msg.arg1 = NOTIFICATION_TYPE_MATCH;
         msg.arg2 = pubSubId;
@@ -952,6 +954,8 @@
         msg.getData().putByteArray(MESSAGE_BUNDLE_KEY_MAC_ADDRESS, peerMac);
         msg.getData().putByteArray(MESSAGE_BUNDLE_KEY_SSI_DATA, serviceSpecificInfo);
         msg.getData().putByteArray(MESSAGE_BUNDLE_KEY_FILTER_DATA, matchFilter);
+        msg.getData().putInt(MESSAGE_RANGING_INDICATION, rangingIndication);
+        msg.getData().putInt(MESSAGE_RANGE_MM, rangeMm);
         mSm.sendMessage(msg);
     }
 
@@ -1255,9 +1259,11 @@
                     byte[] serviceSpecificInfo = msg.getData()
                             .getByteArray(MESSAGE_BUNDLE_KEY_SSI_DATA);
                     byte[] matchFilter = msg.getData().getByteArray(MESSAGE_BUNDLE_KEY_FILTER_DATA);
+                    int rangingIndication = msg.getData().getInt(MESSAGE_RANGING_INDICATION);
+                    int rangeMm = msg.getData().getInt(MESSAGE_RANGE_MM);
 
                     onMatchLocal(pubSubId, requestorInstanceId, peerMac, serviceSpecificInfo,
-                            matchFilter);
+                            matchFilter, rangingIndication, rangeMm);
                     break;
                 }
                 case NOTIFICATION_TYPE_SESSION_TERMINATED: {
@@ -2788,13 +2794,14 @@
     }
 
     private void onMatchLocal(int pubSubId, int requestorInstanceId, byte[] peerMac,
-            byte[] serviceSpecificInfo, byte[] matchFilter) {
+            byte[] serviceSpecificInfo, byte[] matchFilter, int rangingIndication, int rangeMm) {
         if (VDBG) {
             Log.v(TAG,
                     "onMatch: pubSubId=" + pubSubId + ", requestorInstanceId=" + requestorInstanceId
                             + ", peerDiscoveryMac=" + String.valueOf(HexEncoding.encode(peerMac))
                             + ", serviceSpecificInfo=" + Arrays.toString(serviceSpecificInfo)
-                            + ", matchFilter=" + Arrays.toString(matchFilter));
+                            + ", matchFilter=" + Arrays.toString(matchFilter)
+                            + ", rangingIndication=" + rangingIndication + ", rangeMm=" + rangeMm);
         }
 
         Pair<WifiAwareClientState, WifiAwareDiscoverySessionState> data =
@@ -2804,7 +2811,8 @@
             return;
         }
 
-        data.second.onMatch(requestorInstanceId, peerMac, serviceSpecificInfo, matchFilter);
+        data.second.onMatch(requestorInstanceId, peerMac, serviceSpecificInfo, matchFilter,
+                rangingIndication, rangeMm);
     }
 
     private void onSessionTerminatedLocal(int pubSubId, boolean isPublish, int reason) {
diff --git a/com/android/server/wifi/hotspot2/OsuNetworkConnection.java b/com/android/server/wifi/hotspot2/OsuNetworkConnection.java
new file mode 100644
index 0000000..f1bf117
--- /dev/null
+++ b/com/android/server/wifi/hotspot2/OsuNetworkConnection.java
@@ -0,0 +1,270 @@
+/*
+ * Copyright 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.hotspot2;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.Network;
+import android.net.NetworkInfo;
+import android.net.wifi.WifiConfiguration;
+import android.net.wifi.WifiInfo;
+import android.net.wifi.WifiManager;
+import android.net.wifi.WifiSsid;
+import android.os.Handler;
+import android.text.TextUtils;
+import android.util.Log;
+
+/**
+ * Responsible for setup/monitor on Wi-Fi state and connection to the OSU AP.
+ */
+public class OsuNetworkConnection {
+    private static final String TAG = "OsuNetworkConnection";
+
+    private final Context mContext;
+
+    private boolean mVerboseLoggingEnabled = false;
+    private WifiManager mWifiManager;
+    private Callbacks mCallbacks;
+    private boolean mConnected = false;
+    private int mNetworkId = -1;
+    private boolean mWifiEnabled = false;
+
+    /**
+     * Callbacks on Wi-Fi connection state changes.
+     */
+    public interface Callbacks {
+        /**
+         * Invoked when network connection is established with IP connectivity.
+         *
+         * @param network {@link Network} associated with the connected network.
+         */
+        void onConnected(Network network);
+
+        /**
+         * Invoked when the targeted network is disconnected.
+         */
+        void onDisconnected();
+
+        /**
+         * Invoked when a timer tracking connection request is not reset by successfull connection.
+         */
+        void onTimeOut();
+
+        /**
+         * Invoked when Wifi is enabled.
+         */
+        void onWifiEnabled();
+
+        /**
+         * Invoked when Wifi is disabled.
+         */
+        void onWifiDisabled();
+    }
+
+    /**
+     * Create an instance of {@link NetworkConnection} for the specified Wi-Fi network.
+     * @param context The application context
+     */
+    public OsuNetworkConnection(Context context) {
+        mContext = context;
+    }
+
+    /**
+     * Called to initialize tracking of wifi state and network events by registering for the
+     * corresponding intents.
+     */
+    public void init(Handler handler) {
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
+        filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
+        BroadcastReceiver receiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                String action = intent.getAction();
+                if (action.equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
+                    handleNetworkStateChanged(
+                            intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO),
+                            intent.getParcelableExtra(WifiManager.EXTRA_WIFI_INFO));
+                } else if (action.equals(WifiManager.WIFI_STATE_CHANGED_ACTION)) {
+                    int state = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
+                            WifiManager.WIFI_STATE_UNKNOWN);
+                    handleWifiStateChanged(state);
+                }
+            }
+        };
+        mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
+        mContext.registerReceiver(receiver, filter, null, handler);
+        mWifiEnabled = mWifiManager.isWifiEnabled();
+    }
+
+    /**
+     * Disconnect, if required in the two cases
+     * - still connected to the OSU AP
+     * - connection to OSU AP was requested and in progress
+     */
+    public void disconnectIfNeeded() {
+        if (mNetworkId < 0) {
+            if (mVerboseLoggingEnabled) {
+                Log.v(TAG, "No connection to tear down");
+            }
+            return;
+        }
+        mWifiManager.removeNetwork(mNetworkId);
+        mNetworkId = -1;
+        mConnected = false;
+        if (mCallbacks != null) {
+            mCallbacks.onDisconnected();
+        }
+    }
+
+    /**
+     * Register for network and Wifi state events
+     * @param callbacks The callbacks to be invoked on network change events
+     */
+    public void setEventCallback(Callbacks callbacks) {
+        mCallbacks = callbacks;
+    }
+
+    /**
+     * Connect to a OSU Wi-Fi network specified by the given SSID. The security type of the Wi-Fi
+     * network is either open or OSEN (OSU Server-only authenticated layer 2 Encryption Network).
+     * When network access identifier is provided, OSEN is used.
+     *
+     * @param ssid The SSID to connect to
+     * @param nai Network access identifier of the network
+     *
+     * @return boolean true if connection was successfully initiated
+     */
+    public boolean connect(WifiSsid ssid, String nai) {
+        if (mConnected) {
+            if (mVerboseLoggingEnabled) {
+                // Already connected
+                Log.v(TAG, "Connect called twice");
+            }
+            return true;
+        }
+        if (!mWifiManager.isWifiEnabled()) {
+            Log.w(TAG, "Wifi is not enabled");
+            return false;
+        }
+        WifiConfiguration config = new WifiConfiguration();
+        config.SSID = "\"" + ssid.toString() + "\"";
+        if (TextUtils.isEmpty(nai)) {
+            config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
+        } else {
+            // TODO(sohanirao): Handle OSEN.
+            Log.w(TAG, "OSEN not supported");
+            return false;
+        }
+        mNetworkId = mWifiManager.addNetwork(config);
+        if (mNetworkId < 0) {
+            Log.e(TAG, "Unable to add network");
+            return false;
+        }
+        if (!mWifiManager.enableNetwork(mNetworkId, true)) {
+            Log.e(TAG, "Unable to enable network " + mNetworkId);
+            disconnectIfNeeded();
+            return false;
+        }
+        if (mVerboseLoggingEnabled) {
+            Log.v(TAG, "Current network ID " + mNetworkId);
+        }
+        // TODO(sohanirao): setup alarm to time out the connection attempt.
+        return true;
+    }
+
+    /**
+     * Method to update logging level in this class
+     * @param verbose more than 0 enables verbose logging
+     */
+    public void enableVerboseLogging(int verbose) {
+        mVerboseLoggingEnabled = verbose > 0 ? true : false;
+    }
+
+    /**
+     * Handle network state changed events.
+     *
+     * @param networkInfo {@link NetworkInfo} indicating the current network state
+     * @param wifiInfo {@link WifiInfo} associated with the current network when connected
+     */
+    private void handleNetworkStateChanged(NetworkInfo networkInfo, WifiInfo wifiInfo) {
+        if (networkInfo == null) {
+            Log.w(TAG, "NetworkInfo not provided for network state changed event");
+            return;
+        }
+        switch (networkInfo.getDetailedState()) {
+            case CONNECTED:
+                if (mVerboseLoggingEnabled) {
+                    Log.v(TAG, "Connected event received");
+                }
+                if (wifiInfo == null) {
+                    Log.w(TAG, "WifiInfo not provided for network state changed event");
+                    return;
+                }
+                handleConnectedEvent(wifiInfo);
+                break;
+            case DISCONNECTED:
+                if (mVerboseLoggingEnabled) {
+                    Log.v(TAG, "Disconnected event received");
+                }
+                disconnectIfNeeded();
+                break;
+            default:
+                if (mVerboseLoggingEnabled) {
+                    Log.v(TAG, "Ignore uninterested state: " + networkInfo.getDetailedState());
+                }
+                break;
+        }
+    }
+
+    /**
+     * Handle network connected event.
+     *
+     * @param wifiInfo {@link WifiInfo} associated with the current connection
+     */
+    private void handleConnectedEvent(WifiInfo wifiInfo) {
+        if (mVerboseLoggingEnabled) {
+            Log.v(TAG, "handleConnectedEvent " + wifiInfo.getNetworkId());
+        }
+        if (wifiInfo.getNetworkId() != mNetworkId) {
+            disconnectIfNeeded();
+            return;
+        }
+        if (!mConnected) {
+            mConnected = true;
+            if (mCallbacks != null) {
+                mCallbacks.onConnected(mWifiManager.getCurrentNetwork());
+            }
+        }
+    }
+
+    /**
+     * Handle Wifi state change event
+     */
+    private void handleWifiStateChanged(int state) {
+        if (state == WifiManager.WIFI_STATE_DISABLED && mWifiEnabled) {
+            mWifiEnabled = false;
+            if (mCallbacks != null) mCallbacks.onWifiDisabled();
+        }
+        if (state == WifiManager.WIFI_STATE_ENABLED && !mWifiEnabled) {
+            mWifiEnabled = true;
+            if (mCallbacks != null) mCallbacks.onWifiEnabled();
+        }
+    }
+}
diff --git a/com/android/server/wifi/hotspot2/PasspointManager.java b/com/android/server/wifi/hotspot2/PasspointManager.java
index 3580c83..fec3dd8 100644
--- a/com/android/server/wifi/hotspot2/PasspointManager.java
+++ b/com/android/server/wifi/hotspot2/PasspointManager.java
@@ -33,8 +33,10 @@
 import android.net.wifi.ScanResult;
 import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiEnterpriseConfig;
+import android.net.wifi.hotspot2.IProvisioningCallback;
 import android.net.wifi.hotspot2.OsuProvider;
 import android.net.wifi.hotspot2.PasspointConfiguration;
+import android.os.Looper;
 import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.Log;
@@ -99,6 +101,7 @@
     private final WifiConfigManager mWifiConfigManager;
     private final CertificateVerifier mCertVerifier;
     private final WifiMetrics mWifiMetrics;
+    private final PasspointProvisioner mPasspointProvisioner;
 
     // Counter used for assigning unique identifier to each provider.
     private long mProviderIndex;
@@ -212,10 +215,27 @@
         mProviderIndex = 0;
         wifiConfigStore.registerStoreData(objectFactory.makePasspointConfigStoreData(
                 mKeyStore, mSimAccessor, new DataSourceHandler()));
+        mPasspointProvisioner = objectFactory.makePasspointProvisioner(context,
+                objectFactory.makeOsuNetworkConnection(context));
         sPasspointManager = this;
     }
 
     /**
+     * Initializes the provisioning flow with a looper
+     */
+    public void initializeProvisioner(Looper looper) {
+        mPasspointProvisioner.init(looper);
+    }
+
+    /**
+     * Enable verbose logging
+     * @param verbose more than 0 enables verbose logging
+     */
+    public void enableVerboseLogging(int verbose) {
+        mPasspointProvisioner.enableVerboseLogging(verbose);
+    }
+
+    /**
      * Add or update a Passpoint provider with the given configuration.
      *
      * Each provider is uniquely identified by its FQDN (Fully Qualified Domain Name).
@@ -704,4 +724,16 @@
         mProviders.put(passpointConfig.getHomeSp().getFqdn(), provider);
         return true;
     }
+
+    /**
+     * Start the subscription provisioning flow with a provider.
+     * @param callingUid integer indicating the uid of the caller
+     * @param provider {@link OsuProvider} the provider to subscribe to
+     * @param callback {@link IProvisioningCallback} callback to update status to the caller
+     * @return boolean return value from the provisioning method
+     */
+    public boolean startSubscriptionProvisioning(int callingUid, OsuProvider provider,
+            IProvisioningCallback callback) {
+        return mPasspointProvisioner.startSubscriptionProvisioning(callingUid, provider, callback);
+    }
 }
diff --git a/com/android/server/wifi/hotspot2/PasspointObjectFactory.java b/com/android/server/wifi/hotspot2/PasspointObjectFactory.java
index c41c49a..71fc47a 100644
--- a/com/android/server/wifi/hotspot2/PasspointObjectFactory.java
+++ b/com/android/server/wifi/hotspot2/PasspointObjectFactory.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wifi.hotspot2;
 
+import android.content.Context;
 import android.net.wifi.hotspot2.PasspointConfiguration;
 
 import com.android.server.wifi.Clock;
@@ -95,4 +96,25 @@
     public CertificateVerifier makeCertificateVerifier() {
         return new CertificateVerifier();
     }
+
+    /**
+     * Create an instance of {@link PasspointProvisioner}.
+     *
+     * @param context
+     * @return {@link PasspointProvisioner}
+     */
+    public PasspointProvisioner makePasspointProvisioner(Context context,
+            OsuNetworkConnection osuNetworkConnection) {
+        return new PasspointProvisioner(context, osuNetworkConnection);
+    }
+
+    /**
+     * Create an instance of {@link OsuNetworkConnection}.
+     *
+     * @param context
+     * @return {@link OsuNetworkConnection}
+     */
+    public OsuNetworkConnection makeOsuNetworkConnection(Context context) {
+        return new OsuNetworkConnection(context);
+    }
 }
diff --git a/com/android/server/wifi/hotspot2/PasspointProvisioner.java b/com/android/server/wifi/hotspot2/PasspointProvisioner.java
new file mode 100644
index 0000000..d3bb773
--- /dev/null
+++ b/com/android/server/wifi/hotspot2/PasspointProvisioner.java
@@ -0,0 +1,309 @@
+/*
+ * Copyright 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.hotspot2;
+
+import android.content.Context;
+import android.net.Network;
+import android.net.wifi.hotspot2.IProvisioningCallback;
+import android.net.wifi.hotspot2.OsuProvider;
+import android.net.wifi.hotspot2.ProvisioningCallback;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * Provides methods to carry out provisioning flow
+ */
+public class PasspointProvisioner {
+    private static final String TAG = "PasspointProvisioner";
+
+    private static final int PROVISIONING_STATUS = 0;
+    private static final int PROVISIONING_FAILURE = 1;
+
+    private final Context mContext;
+    private final ProvisioningStateMachine mProvisioningStateMachine;
+    private final OsuNetworkCallbacks mOsuNetworkCallbacks;
+    private final OsuNetworkConnection mOsuNetworkConnection;
+
+    private int mCallingUid;
+    private boolean mVerboseLoggingEnabled = false;
+
+    PasspointProvisioner(Context context, OsuNetworkConnection osuNetworkConnection) {
+        mContext = context;
+        mOsuNetworkConnection = osuNetworkConnection;
+        mProvisioningStateMachine = new ProvisioningStateMachine();
+        mOsuNetworkCallbacks = new OsuNetworkCallbacks();
+    }
+
+    /**
+     * Sets up for provisioning
+     * @param looper Looper on which the Provisioning state machine will run
+     */
+    public void init(Looper looper) {
+        mProvisioningStateMachine.start(new Handler(looper));
+        mOsuNetworkConnection.init(mProvisioningStateMachine.getHandler());
+    }
+
+    /**
+     * Enable verbose logging to help debug failures
+     * @param level integer indicating verbose logging enabled if > 0
+     */
+    public void enableVerboseLogging(int level) {
+        mVerboseLoggingEnabled = (level > 0) ? true : false;
+        mOsuNetworkConnection.enableVerboseLogging(level);
+    }
+
+    /**
+     * Start provisioning flow with a given provider.
+     * @param callingUid calling uid.
+     * @param provider {@link OsuProvider} to provision with.
+     * @param callback {@link IProvisioningCallback} to provide provisioning status.
+     * @return boolean value, true if provisioning was started, false otherwise.
+     *
+     * Implements HS2.0 provisioning flow with a given HS2.0 provider.
+     */
+    public boolean startSubscriptionProvisioning(int callingUid, OsuProvider provider,
+            IProvisioningCallback callback) {
+        mCallingUid = callingUid;
+
+        Log.v(TAG, "Provisioning started with " + provider.toString());
+
+        mProvisioningStateMachine.getHandler().post(() -> {
+            mProvisioningStateMachine.startProvisioning(provider, callback);
+        });
+
+        return true;
+    }
+
+    /**
+     * Handles the provisioning flow state transitions
+     */
+    class ProvisioningStateMachine {
+        private static final String TAG = "ProvisioningStateMachine";
+
+        private static final int DEFAULT_STATE                             = 0;
+        private static final int INITIAL_STATE                             = 1;
+        private static final int WAITING_TO_CONNECT                        = 2;
+        private static final int OSU_AP_CONNECTED                          = 3;
+        private static final int FAILED_STATE                              = 4;
+
+        private OsuProvider mOsuProvider;
+        private IProvisioningCallback mProvisioningCallback;
+        private Handler mHandler;
+        private int mState;
+
+        ProvisioningStateMachine() {
+            mState = DEFAULT_STATE;
+        }
+
+        /**
+         * Initializes and starts the state machine with a handler to handle incoming events
+         */
+        public void start(Handler handler) {
+            mHandler = handler;
+            changeState(INITIAL_STATE);
+        }
+
+        /**
+         * Returns the handler on which a runnable can be posted
+         * @return Handler State Machine's handler
+         */
+        public Handler getHandler() {
+            return mHandler;
+        }
+
+        /**
+         * Start Provisioning with the Osuprovider and invoke callbacks
+         * @param provider OsuProvider to provision with
+         * @param callback IProvisioningCallback to invoke callbacks on
+         */
+        public void startProvisioning(OsuProvider provider, IProvisioningCallback callback) {
+            if (mVerboseLoggingEnabled) {
+                Log.v(TAG, "startProvisioning received in state=" + mState);
+            }
+            if (mState != INITIAL_STATE) {
+                Log.v(TAG, "State Machine needs to be reset before starting provisioning");
+                resetStateMachine();
+            }
+            mProvisioningCallback = callback;
+            mOsuProvider = provider;
+
+            // Register for network and wifi state events during provisioning flow
+            mOsuNetworkConnection.setEventCallback(mOsuNetworkCallbacks);
+
+            if (mOsuNetworkConnection.connect(mOsuProvider.getOsuSsid(),
+                    mOsuProvider.getNetworkAccessIdentifier())) {
+                invokeProvisioningCallback(PROVISIONING_STATUS,
+                        ProvisioningCallback.OSU_STATUS_AP_CONNECTING);
+                changeState(WAITING_TO_CONNECT);
+            } else {
+                invokeProvisioningCallback(PROVISIONING_FAILURE,
+                        ProvisioningCallback.OSU_FAILURE_AP_CONNECTION);
+                enterFailedState();
+            }
+        }
+
+        /**
+         * Handle Wifi Disable event
+         */
+        public void handleWifiDisabled() {
+            if (mVerboseLoggingEnabled) {
+                Log.v(TAG, "Wifi Disabled in state=" + mState);
+            }
+            if (mState == INITIAL_STATE || mState == FAILED_STATE) {
+                Log.w(TAG, "Wifi Disable unhandled in state=" + mState);
+                return;
+            }
+            invokeProvisioningCallback(PROVISIONING_FAILURE,
+                    ProvisioningCallback.OSU_FAILURE_AP_CONNECTION);
+            enterFailedState();
+        }
+
+        private void resetStateMachine() {
+            // Set to null so that no callbacks are invoked during reset
+            mProvisioningCallback = null;
+            if (mState != INITIAL_STATE || mState != FAILED_STATE) {
+                // Transition through Failed state to clean up
+                enterFailedState();
+            }
+            changeState(INITIAL_STATE);
+        }
+
+        /**
+         * Connected event received
+         * @param network Network object for this connection
+         */
+        public void handleConnectedEvent(Network network) {
+            if (mVerboseLoggingEnabled) {
+                Log.v(TAG, "Connected event received in state=" + mState);
+            }
+            if (mState != WAITING_TO_CONNECT) {
+                // Not waiting for a connection
+                Log.w(TAG, "Connection event unhandled in state=" + mState);
+                return;
+            }
+            invokeProvisioningCallback(PROVISIONING_STATUS,
+                    ProvisioningCallback.OSU_STATUS_AP_CONNECTED);
+            changeState(OSU_AP_CONNECTED);
+        }
+
+        /**
+         * Disconnect event received
+         */
+        public void handleDisconnect() {
+            if (mVerboseLoggingEnabled) {
+                Log.v(TAG, "Connection failed in state=" + mState);
+            }
+            if (mState == INITIAL_STATE || mState == FAILED_STATE) {
+                Log.w(TAG, "Disconnect event unhandled in state=" + mState);
+                return;
+            }
+            invokeProvisioningCallback(PROVISIONING_FAILURE,
+                    ProvisioningCallback.OSU_FAILURE_AP_CONNECTION);
+            enterFailedState();
+        }
+
+        private void changeState(int nextState) {
+            if (nextState != mState) {
+                if (mVerboseLoggingEnabled) {
+                    Log.v(TAG, "Changing state from " + mState + " -> " + nextState);
+                }
+                mState = nextState;
+            }
+        }
+
+        private void invokeProvisioningCallback(int callbackType, int status) {
+            if (mProvisioningCallback == null) {
+                Log.e(TAG, "Provisioning callback " + callbackType + " with status " + status
+                        + " not invoked, callback is null");
+                return;
+            }
+            try {
+                if (callbackType == PROVISIONING_STATUS) {
+                    mProvisioningCallback.onProvisioningStatus(status);
+                } else {
+                    mProvisioningCallback.onProvisioningFailure(status);
+                }
+            } catch (RemoteException e) {
+                if (callbackType == PROVISIONING_STATUS) {
+                    Log.e(TAG, "Remote Exception while posting Provisioning status " + status);
+                } else {
+                    Log.e(TAG, "Remote Exception while posting Provisioning failure " + status);
+                }
+            }
+        }
+
+        private void enterFailedState() {
+            changeState(FAILED_STATE);
+            mOsuNetworkConnection.setEventCallback(null);
+            mOsuNetworkConnection.disconnectIfNeeded();
+        }
+    }
+
+    /**
+     * Callbacks for network and wifi events
+     */
+    class OsuNetworkCallbacks implements OsuNetworkConnection.Callbacks {
+
+        OsuNetworkCallbacks() {
+        }
+
+        @Override
+        public void onConnected(Network network) {
+            Log.v(TAG, "onConnected to " + network);
+            if (network == null) {
+                mProvisioningStateMachine.getHandler().post(() -> {
+                    mProvisioningStateMachine.handleDisconnect();
+                });
+            } else {
+                mProvisioningStateMachine.getHandler().post(() -> {
+                    mProvisioningStateMachine.handleConnectedEvent(network);
+                });
+            }
+        }
+
+        @Override
+        public void onDisconnected() {
+            Log.v(TAG, "onDisconnected");
+            mProvisioningStateMachine.getHandler().post(() -> {
+                mProvisioningStateMachine.handleDisconnect();
+            });
+        }
+
+        @Override
+        public void onTimeOut() {
+            Log.v(TAG, "Timed out waiting for connection to OSU AP");
+            mProvisioningStateMachine.getHandler().post(() -> {
+                mProvisioningStateMachine.handleDisconnect();
+            });
+        }
+
+        @Override
+        public void onWifiEnabled() {
+            Log.v(TAG, "onWifiEnabled");
+        }
+
+        @Override
+        public void onWifiDisabled() {
+            Log.v(TAG, "onWifiDisabled");
+            mProvisioningStateMachine.getHandler().post(() -> {
+                mProvisioningStateMachine.handleWifiDisabled();
+            });
+        }
+    }
+}
diff --git a/com/android/server/wifi/p2p/WifiP2pServiceImpl.java b/com/android/server/wifi/p2p/WifiP2pServiceImpl.java
index da3da7c..751737f 100644
--- a/com/android/server/wifi/p2p/WifiP2pServiceImpl.java
+++ b/com/android/server/wifi/p2p/WifiP2pServiceImpl.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wifi.p2p;
 
+import android.annotation.NonNull;
 import android.app.AlertDialog;
 import android.content.Context;
 import android.content.DialogInterface;
@@ -524,7 +525,7 @@
                     }
                     mHalDeviceManager = mWifiInjector.getHalDeviceManager();
                 }
-                mIWifiP2pIface = mHalDeviceManager.createP2pIface(() -> {
+                mIWifiP2pIface = mHalDeviceManager.createP2pIface((@NonNull String ifaceName) -> {
                     if (DBG) Log.d(TAG, "IWifiP2pIface destroyedListener");
                     synchronized (mLock) {
                         mIWifiP2pIface = null;
diff --git a/com/android/server/wifi/rtt/RttNative.java b/com/android/server/wifi/rtt/RttNative.java
index fe1829f..dd54c24 100644
--- a/com/android/server/wifi/rtt/RttNative.java
+++ b/com/android/server/wifi/rtt/RttNative.java
@@ -46,7 +46,7 @@
  */
 public class RttNative extends IWifiRttControllerEventCallback.Stub {
     private static final String TAG = "RttNative";
-    private static final boolean VDBG = true; // STOPSHIP if true
+    private static final boolean VDBG = false; // STOPSHIP if true
 
     private final RttServiceImpl mRttService;
     private final HalDeviceManager mHalDeviceManager;
diff --git a/com/android/server/wifi/rtt/RttService.java b/com/android/server/wifi/rtt/RttService.java
index 0790dee..5c2cec1 100644
--- a/com/android/server/wifi/rtt/RttService.java
+++ b/com/android/server/wifi/rtt/RttService.java
@@ -66,7 +66,8 @@
                     Context.WIFI_AWARE_SERVICE);
 
             RttNative rttNative = new RttNative(mImpl, halDeviceManager);
-            mImpl.start(handlerThread.getLooper(), awareBinder, rttNative, wifiPermissionsUtil);
+            mImpl.start(handlerThread.getLooper(), wifiInjector.getClock(), awareBinder, rttNative,
+                    wifiPermissionsUtil);
         }
     }
 }
diff --git a/com/android/server/wifi/rtt/RttServiceImpl.java b/com/android/server/wifi/rtt/RttServiceImpl.java
index 36caab7..401daab 100644
--- a/com/android/server/wifi/rtt/RttServiceImpl.java
+++ b/com/android/server/wifi/rtt/RttServiceImpl.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wifi.rtt;
 
+import android.app.ActivityManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -39,13 +40,13 @@
 import android.os.Looper;
 import android.os.PowerManager;
 import android.os.RemoteException;
-import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.WorkSource;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.WakeupMessage;
+import com.android.server.wifi.Clock;
 import com.android.server.wifi.util.NativeUtil;
 import com.android.server.wifi.util.WifiPermissionsUtil;
 
@@ -66,12 +67,14 @@
  */
 public class RttServiceImpl extends IWifiRttManager.Stub {
     private static final String TAG = "RttServiceImpl";
-    private static final boolean VDBG = true; // STOPSHIP if true
+    private static final boolean VDBG = false; // STOPSHIP if true
 
     private final Context mContext;
+    private Clock mClock;
     private IWifiAwareManager mAwareBinder;
     private RttNative mRttNative;
     private WifiPermissionsUtil mWifiPermissionsUtil;
+    private ActivityManager mActivityManager;
     private PowerManager mPowerManager;
 
     private RttServiceSynchronized mRttServiceSynchronized;
@@ -79,7 +82,10 @@
     @VisibleForTesting
     public static final String HAL_RANGING_TIMEOUT_TAG = TAG + " HAL Ranging Timeout";
 
-    private static final long HAL_RANGING_TIMEOUT_MS = 5_000;
+    private static final long HAL_RANGING_TIMEOUT_MS = 5_000; // 5 sec
+
+    // TODO: b/69323456 convert to a settable value
+    /* package */ static final long BACKGROUND_PROCESS_EXEC_GAP_MS = 1_800_000; // 30 min
 
     public RttServiceImpl(Context context) {
         mContext = context;
@@ -93,17 +99,21 @@
      * Initializes the RTT service (usually with objects from an injector).
      *
      * @param looper The looper on which to synchronize operations.
+     * @param clock A mockable clock.
      * @param awareBinder The Wi-Fi Aware service (binder) if supported on the system.
      * @param rttNative The Native interface to the HAL.
      * @param wifiPermissionsUtil Utility for permission checks.
      */
-    public void start(Looper looper, IWifiAwareManager awareBinder, RttNative rttNative,
+    public void start(Looper looper, Clock clock, IWifiAwareManager awareBinder,
+            RttNative rttNative,
             WifiPermissionsUtil wifiPermissionsUtil) {
+        mClock = clock;
         mAwareBinder = awareBinder;
         mRttNative = rttNative;
         mWifiPermissionsUtil = wifiPermissionsUtil;
         mRttServiceSynchronized = new RttServiceSynchronized(looper, rttNative);
 
+        mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
         mPowerManager = mContext.getSystemService(PowerManager.class);
         IntentFilter intentFilter = new IntentFilter();
         intentFilter.addAction(PowerManager.ACTION_DEVICE_IDLE_MODE_CHANGED);
@@ -323,6 +333,7 @@
 
         private RttNative mRttNative;
         private int mNextCommandId = 1000;
+        private Map<Integer, RttRequesterInfo> mRttRequesterInfo = new HashMap<>();
         private List<RttRequestInfo> mRttRequestQueue = new LinkedList<>();
         private WakeupMessage mRangingTimeoutMessage = null;
 
@@ -540,10 +551,23 @@
                 return;
             }
 
+            if (!preExecThrottleCheck(nextRequest.workSource)) {
+                Log.w(TAG, "RttServiceSynchronized.startRanging: execution throttled - nextRequest="
+                        + nextRequest + ", mRttRequesterInfo=" + mRttRequesterInfo);
+                try {
+                    nextRequest.callback.onRangingFailure(RangingResultCallback.STATUS_CODE_FAIL);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "RttServiceSynchronized.startRanging: throttled, callback failed -- "
+                            + e);
+                }
+                executeNextRangingRequestIfPossible(true);
+                return;
+            }
+
             nextRequest.cmdId = mNextCommandId++;
             if (mRttNative.rangeRequest(nextRequest.cmdId, nextRequest.request)) {
                 mRangingTimeoutMessage.schedule(
-                        SystemClock.elapsedRealtime() + HAL_RANGING_TIMEOUT_MS);
+                        mClock.getElapsedSinceBootMillis() + HAL_RANGING_TIMEOUT_MS);
             } else {
                 Log.w(TAG, "RttServiceSynchronized.startRanging: native rangeRequest call failed");
                 try {
@@ -558,6 +582,64 @@
         }
 
         /**
+         * Perform pre-execution throttling checks:
+         * - If all uids in ws are in background then check last execution and block if request is
+         *   more frequent than permitted
+         * - If executing (i.e. permitted) then update execution time
+         *
+         * Returns true to permit execution, false to abort it.
+         */
+        private boolean preExecThrottleCheck(WorkSource ws) {
+            if (VDBG) Log.v(TAG, "preExecThrottleCheck: ws=" + ws);
+
+            // are all UIDs running in the background or is at least 1 in the foreground?
+            boolean allUidsInBackground = true;
+            for (int i = 0; i < ws.size(); ++i) {
+                int uidImportance = mActivityManager.getUidImportance(ws.get(i));
+                if (VDBG) {
+                    Log.v(TAG, "preExecThrottleCheck: uid=" + ws.get(i) + " -> importance="
+                            + uidImportance);
+                }
+                if (uidImportance
+                        <= ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE) {
+                    allUidsInBackground = false;
+                    break;
+                }
+            }
+
+            // if all UIDs are in background then check timestamp since last execution and see if
+            // any is permitted (infrequent enough)
+            boolean allowExecution = false;
+            long mostRecentExecutionPermitted =
+                    mClock.getElapsedSinceBootMillis() - BACKGROUND_PROCESS_EXEC_GAP_MS;
+            if (allUidsInBackground) {
+                for (int i = 0; i < ws.size(); ++i) {
+                    RttRequesterInfo info = mRttRequesterInfo.get(ws.get(i));
+                    if (info == null || info.lastRangingExecuted < mostRecentExecutionPermitted) {
+                        allowExecution = true;
+                        break;
+                    }
+                }
+            } else {
+                allowExecution = true;
+            }
+
+            // update exec time
+            if (allowExecution) {
+                for (int i = 0; i < ws.size(); ++i) {
+                    RttRequesterInfo info = mRttRequesterInfo.get(ws.get(i));
+                    if (info == null) {
+                        info = new RttRequesterInfo();
+                        mRttRequesterInfo.put(ws.get(i), info);
+                    }
+                    info.lastRangingExecuted = mClock.getElapsedSinceBootMillis();
+                }
+            }
+
+            return allowExecution;
+        }
+
+        /**
          * Check request for any PeerHandle Aware requests. If there are any: issue requests to
          * translate the peer ID to a MAC address and abort current execution of the range request.
          * The request will be re-attempted when response is received.
@@ -754,6 +836,7 @@
         // dump call (asynchronous most likely)
         protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
             pw.println("  mNextCommandId: " + mNextCommandId);
+            pw.println("  mRttRequesterInfo: " + mRttRequesterInfo);
             pw.println("  mRttRequestQueue: " + mRttRequestQueue);
             pw.println("  mRangingTimeoutMessage: " + mRangingTimeoutMessage);
             mRttNative.dump(fd, pw, args);
@@ -783,4 +866,14 @@
                     ", peerHandlesTranslated=").append(peerHandlesTranslated).toString();
         }
     }
+
+    private static class RttRequesterInfo {
+        public long lastRangingExecuted;
+
+        @Override
+        public String toString() {
+            return new StringBuilder("RttRequesterInfo: lastRangingExecuted=").append(
+                    lastRangingExecuted).toString();
+        }
+    }
 }
diff --git a/com/android/server/wm/AccessibilityController.java b/com/android/server/wm/AccessibilityController.java
index de4fd7c..95b139a 100644
--- a/com/android/server/wm/AccessibilityController.java
+++ b/com/android/server/wm/AccessibilityController.java
@@ -55,14 +55,14 @@
 import android.view.ViewConfiguration;
 import android.view.WindowInfo;
 import android.view.WindowManager;
-import android.view.WindowManagerInternal.MagnificationCallbacks;
-import android.view.WindowManagerInternal.WindowsForAccessibilityCallback;
-import android.view.WindowManagerPolicy;
 import android.view.animation.DecelerateInterpolator;
 import android.view.animation.Interpolator;
 
 import com.android.internal.R;
 import com.android.internal.os.SomeArgs;
+import com.android.server.policy.WindowManagerPolicy;
+import com.android.server.wm.WindowManagerInternal.MagnificationCallbacks;
+import com.android.server.wm.WindowManagerInternal.WindowsForAccessibilityCallback;
 
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -292,6 +292,8 @@
         public void setMagnificationSpecLocked(MagnificationSpec spec) {
             mMagnifedViewport.updateMagnificationSpecLocked(spec);
             mMagnifedViewport.recomputeBoundsLocked();
+
+            mService.applyMagnificationSpec(spec);
             mService.scheduleAnimationLocked();
         }
 
@@ -421,7 +423,7 @@
         public MagnificationSpec getMagnificationSpecForWindowLocked(WindowState windowState) {
             MagnificationSpec spec = mMagnifedViewport.getMagnificationSpecLocked();
             if (spec != null && !spec.isNop()) {
-                if (!mService.mPolicy.canMagnifyWindow(windowState.mAttrs.type)) {
+                if (!windowState.shouldMagnify()) {
                     return null;
                 }
             }
@@ -476,6 +478,7 @@
             private final ViewportWindow mWindow;
 
             private boolean mFullRedrawNeeded;
+            private int mTempLayer = 0;
 
             public MagnifiedViewport() {
                 mWindowManager = (WindowManager) mContext.getSystemService(Service.WINDOW_SERVICE);
@@ -565,7 +568,7 @@
                     portionOfWindowAlreadyAccountedFor.op(nonMagnifiedBounds, Region.Op.UNION);
                     windowBounds.op(portionOfWindowAlreadyAccountedFor, Region.Op.DIFFERENCE);
 
-                    if (mService.mPolicy.canMagnifyWindow(windowState.mAttrs.type)) {
+                    if (windowState.shouldMagnify()) {
                         mMagnificationRegion.op(windowBounds, Region.Op.UNION);
                         mMagnificationRegion.op(availableBounds, Region.Op.INTERSECT);
                     } else {
@@ -676,10 +679,12 @@
 
             private void populateWindowsOnScreenLocked(SparseArray<WindowState> outWindows) {
                 final DisplayContent dc = mService.getDefaultDisplayContentLocked();
+                mTempLayer = 0;
                 dc.forAllWindows((w) -> {
                     if (w.isOnScreen() && w.isVisibleLw()
                             && !w.mWinAnimator.mEnterAnimationPending) {
-                        outWindows.put(w.mLayer, w);
+                        mTempLayer++;
+                        outWindows.put(mTempLayer, w);
                     }
                 }, false /* traverseTopToBottom */ );
             }
@@ -705,7 +710,7 @@
                     SurfaceControl surfaceControl = null;
                     try {
                         mWindowManager.getDefaultDisplay().getRealSize(mTempPoint);
-                        surfaceControl = new SurfaceControl.Builder(mService.mFxSession)
+                        surfaceControl = mService.getDefaultDisplayContentLocked().makeOverlay()
                                 .setName(SURFACE_TITLE)
                                 .setSize(mTempPoint.x, mTempPoint.y) // not a typo
                                 .setFormat(PixelFormat.TRANSLUCENT)
@@ -714,8 +719,6 @@
                         /* ignore */
                     }
                     mSurfaceControl = surfaceControl;
-                    mSurfaceControl.setLayerStack(mWindowManager.getDefaultDisplay()
-                            .getLayerStack());
                     mSurfaceControl.setLayer(mService.mPolicy.getWindowLayerFromTypeLw(
                             TYPE_MAGNIFICATION_OVERLAY)
                             * WindowManagerService.TYPE_LAYER_MULTIPLIER);
@@ -1005,6 +1008,8 @@
 
         private final long mRecurringAccessibilityEventsIntervalMillis;
 
+        private int mTempLayer = 0;
+
         public WindowsForAccessibilityObserver(WindowManagerService windowManagerService,
                 WindowsForAccessibilityCallback callback) {
             mContext = windowManagerService.mContext;
@@ -1090,6 +1095,7 @@
                     if (isReportedWindowType(windowState.mAttrs.type)) {
                         // Add the window to the ones to be reported.
                         WindowInfo window = obtainPopulatedWindowInfo(windowState, boundsInScreen);
+                        window.layer = addedWindows.size();
                         addedWindows.add(window.token);
                         windows.add(window);
                         if (windowState.isFocused()) {
@@ -1323,9 +1329,10 @@
 
         private void populateVisibleWindowsOnScreenLocked(SparseArray<WindowState> outWindows) {
             final DisplayContent dc = mService.getDefaultDisplayContentLocked();
+            mTempLayer = 0;
             dc.forAllWindows((w) -> {
                 if (w.isVisibleLw()) {
-                    outWindows.put(w.mLayer, w);
+                    outWindows.put(mTempLayer++, w);
                 }
             }, false /* traverseTopToBottom */ );
         }
diff --git a/com/android/server/wm/AppTransition.java b/com/android/server/wm/AppTransition.java
index c19ede0..c2cbced 100644
--- a/com/android/server/wm/AppTransition.java
+++ b/com/android/server/wm/AppTransition.java
@@ -16,7 +16,6 @@
 
 package com.android.server.wm;
 
-import static android.view.WindowManagerInternal.AppTransitionListener;
 import static com.android.internal.R.styleable.WindowAnimation_activityCloseEnterAnimation;
 import static com.android.internal.R.styleable.WindowAnimation_activityCloseExitAnimation;
 import static com.android.internal.R.styleable.WindowAnimation_activityOpenEnterAnimation;
@@ -44,6 +43,7 @@
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
+import static com.android.server.wm.WindowManagerInternal.AppTransitionListener;
 import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_BEFORE_ANIM;
 import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_NONE;
 import static com.android.server.wm.WindowStateAnimator.STACK_CLIP_AFTER_ANIM;
diff --git a/com/android/server/wm/AppWindowAnimator.java b/com/android/server/wm/AppWindowAnimator.java
index 2ef7f25..5c1d5b2 100644
--- a/com/android/server/wm/AppWindowAnimator.java
+++ b/com/android/server/wm/AppWindowAnimator.java
@@ -16,7 +16,7 @@
 
 package com.android.server.wm;
 
-import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
+import static com.android.server.policy.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;
@@ -253,7 +253,6 @@
 
     private void updateLayers() {
         mAppToken.getDisplayContent().assignWindowLayers(false /* relayoutNeeded */);
-        updateThumbnailLayer();
     }
 
     private void stepThumbnailAnimation(long currentTime) {
@@ -283,27 +282,12 @@
                 + "][" + tmpFloats[Matrix.MSKEW_X]
                 + "," + tmpFloats[Matrix.MSCALE_Y] + "]");
         thumbnail.setAlpha(thumbnailTransformation.getAlpha());
-        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 (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.
diff --git a/com/android/server/wm/AppWindowContainerController.java b/com/android/server/wm/AppWindowContainerController.java
index 5841840..00a0d3d 100644
--- a/com/android/server/wm/AppWindowContainerController.java
+++ b/com/android/server/wm/AppWindowContainerController.java
@@ -45,10 +45,11 @@
 import android.util.Slog;
 import android.view.DisplayInfo;
 import android.view.IApplicationToken;
-import android.view.WindowManagerPolicy.StartingSurface;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.AttributeCache;
+import com.android.server.policy.WindowManagerPolicy.StartingSurface;
+
 /**
  * Controller for the app window token container. This is created by activity manager to link
  * activity records to the app window token container they use in window manager.
@@ -180,13 +181,12 @@
             IApplicationToken token, AppWindowContainerListener listener, int index,
             int requestedOrientation, boolean fullscreen, boolean showForAllUsers, int configChanges,
             boolean voiceInteraction, boolean launchTaskBehind, boolean alwaysFocusable,
-            int targetSdkVersion, int rotationAnimationHint, long inputDispatchingTimeoutNanos,
-            Rect bounds) {
+            int targetSdkVersion, int rotationAnimationHint, long inputDispatchingTimeoutNanos) {
         this(taskController, token, listener, index, requestedOrientation, fullscreen,
                 showForAllUsers,
                 configChanges, voiceInteraction, launchTaskBehind, alwaysFocusable,
                 targetSdkVersion, rotationAnimationHint, inputDispatchingTimeoutNanos,
-                WindowManagerService.getInstance(), bounds);
+                WindowManagerService.getInstance());
     }
 
     public AppWindowContainerController(TaskWindowContainerController taskController,
@@ -194,7 +194,7 @@
             int requestedOrientation, boolean fullscreen, boolean showForAllUsers, int configChanges,
             boolean voiceInteraction, boolean launchTaskBehind, boolean alwaysFocusable,
             int targetSdkVersion, int rotationAnimationHint, long inputDispatchingTimeoutNanos,
-            WindowManagerService service, Rect bounds) {
+            WindowManagerService service) {
         super(listener, service);
         mHandler = new H(service.mH.getLooper());
         mToken = token;
@@ -215,7 +215,7 @@
             atoken = createAppWindow(mService, token, voiceInteraction, task.getDisplayContent(),
                     inputDispatchingTimeoutNanos, fullscreen, showForAllUsers, targetSdkVersion,
                     requestedOrientation, rotationAnimationHint, configChanges, launchTaskBehind,
-                    alwaysFocusable, this, bounds);
+                    alwaysFocusable, this);
             if (DEBUG_TOKEN_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "addAppToken: " + atoken
                     + " controller=" + taskController + " at " + index);
             task.addChild(atoken, index);
@@ -227,11 +227,11 @@
             boolean voiceInteraction, DisplayContent dc, long inputDispatchingTimeoutNanos,
             boolean fullscreen, boolean showForAllUsers, int targetSdk, int orientation,
             int rotationAnimationHint, int configChanges, boolean launchTaskBehind,
-            boolean alwaysFocusable, AppWindowContainerController controller, Rect bounds) {
+            boolean alwaysFocusable, AppWindowContainerController controller) {
         return new AppWindowToken(service, token, voiceInteraction, dc,
                 inputDispatchingTimeoutNanos, fullscreen, showForAllUsers, targetSdk, orientation,
                 rotationAnimationHint, configChanges, launchTaskBehind, alwaysFocusable,
-                controller, bounds);
+                controller);
     }
 
     public void removeContainer(int displayId) {
@@ -298,17 +298,6 @@
         }
     }
 
-    // TODO(b/36505427): Maybe move to WindowContainerController so other sub-classes can use it as
-    // a generic way to set override config. Need to untangle current ways the override config is
-    // currently set for tasks and displays before we are doing that though.
-    public void onOverrideConfigurationChanged(Configuration overrideConfiguration, Rect bounds) {
-        synchronized(mWindowMap) {
-            if (mContainer != null) {
-                mContainer.onOverrideConfigurationChanged(overrideConfiguration, bounds);
-            }
-        }
-    }
-
     public void setDisablePreviewScreenshots(boolean disable) {
         synchronized (mWindowMap) {
             if (mContainer == null) {
diff --git a/com/android/server/wm/AppWindowToken.java b/com/android/server/wm/AppWindowToken.java
index 98db80e..c39ce98 100644
--- a/com/android/server/wm/AppWindowToken.java
+++ b/com/android/server/wm/AppWindowToken.java
@@ -16,7 +16,6 @@
 
 package com.android.server.wm;
 
-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;
@@ -28,8 +27,8 @@
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
 import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
-import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
-import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
+import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
+import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
 import static com.android.server.wm.AppTransition.TRANSIT_UNSET;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
@@ -67,10 +66,10 @@
 import android.view.SurfaceControl;
 import android.view.WindowManager;
 import android.view.WindowManager.LayoutParams;
-import android.view.WindowManagerPolicy.StartingSurface;
 
 import com.android.internal.util.ToBooleanFunction;
 import com.android.server.input.InputApplicationHandle;
+import com.android.server.policy.WindowManagerPolicy.StartingSurface;
 import com.android.server.wm.WindowManagerService.H;
 
 import java.io.PrintWriter;
@@ -176,11 +175,6 @@
     private boolean mLastContainsShowWhenLockedWindow;
     private boolean mLastContainsDismissKeyguardWindow;
 
-    // The bounds of this activity. Mainly used for aspect-ratio compatibility.
-    // TODO(b/36505427): Every level on WindowContainer now has bounds information, which directly
-    // affects the configuration. We should probably move this into that class.
-    private final Rect mBounds = new Rect();
-
     ArrayDeque<Rect> mFrozenBounds = new ArrayDeque<>();
     ArrayDeque<Configuration> mFrozenMergedConfig = new ArrayDeque<>();
 
@@ -197,8 +191,8 @@
             DisplayContent dc, long inputDispatchingTimeoutNanos, boolean fullscreen,
             boolean showForAllUsers, int targetSdk, int orientation, int rotationAnimationHint,
             int configChanges, boolean launchTaskBehind, boolean alwaysFocusable,
-            AppWindowContainerController controller, Rect bounds) {
-        this(service, token, voiceInteraction, dc, fullscreen, bounds);
+            AppWindowContainerController controller) {
+        this(service, token, voiceInteraction, dc, fullscreen);
         setController(controller);
         mInputDispatchingTimeoutNanos = inputDispatchingTimeoutNanos;
         mShowForAllUsers = showForAllUsers;
@@ -215,7 +209,7 @@
     }
 
     AppWindowToken(WindowManagerService service, IApplicationToken token, boolean voiceInteraction,
-            DisplayContent dc, boolean fillsParent, Rect bounds) {
+            DisplayContent dc, boolean fillsParent) {
         super(service, token != null ? token.asBinder() : null, TYPE_APPLICATION, true, dc,
                 false /* ownerCanManageAppTokens */);
         appToken = token;
@@ -223,27 +217,6 @@
         mFillsParent = fillsParent;
         mInputApplicationHandle = new InputApplicationHandle(this);
         mAppAnimator = new AppWindowAnimator(this, service);
-        if (bounds != null) {
-            mBounds.set(bounds);
-        }
-    }
-
-    void onOverrideConfigurationChanged(Configuration overrideConfiguration, Rect bounds) {
-        onOverrideConfigurationChanged(overrideConfiguration);
-        if (mBounds.equals(bounds)) {
-            return;
-        }
-        // TODO(b/36505427): If bounds is in WC, then we can automatically call onResize() when set.
-        mBounds.set(bounds);
-        onResize();
-    }
-
-    void getBounds(Rect outBounds) {
-        outBounds.set(mBounds);
-    }
-
-    boolean hasBounds() {
-        return !mBounds.isEmpty();
     }
 
     void onFirstWindowDrawn(WindowState win, WindowStateAnimator winAnimator) {
diff --git a/com/android/server/wm/BlackFrame.java b/com/android/server/wm/BlackFrame.java
index 9729e50..f19cd0f 100644
--- a/com/android/server/wm/BlackFrame.java
+++ b/com/android/server/wm/BlackFrame.java
@@ -30,7 +30,6 @@
 import android.util.Slog;
 import android.view.Surface.OutOfResourcesException;
 import android.view.SurfaceControl;
-import android.view.SurfaceSession;
 
 /**
  * Four black surfaces put together to make a black frame.
@@ -42,22 +41,22 @@
         final int layer;
         final SurfaceControl surface;
 
-        BlackSurface(SurfaceSession session, int layer, int l, int t, int r, int b, int layerStack)
-                throws OutOfResourcesException {
+        BlackSurface(int layer,
+                int l, int t, int r, int b, DisplayContent dc) throws OutOfResourcesException {
             left = l;
             top = t;
             this.layer = layer;
             int w = r-l;
             int h = b-t;
 
-            surface = new SurfaceControl.Builder(session)
+            surface = dc.makeOverlay()
                     .setName("BlackSurface")
                     .setSize(w, h)
                     .setColorLayer(true)
+                    .setParent(null) // TODO: Work-around for b/69259549
                     .build();
 
             surface.setAlpha(1);
-            surface.setLayerStack(layerStack);
             surface.setLayer(layer);
             surface.show();
             if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) Slog.i(TAG_WM,
@@ -114,30 +113,32 @@
         }
     }
 
-    public BlackFrame(SurfaceSession session, Rect outer, Rect inner, int layer, int layerStack,
+    public BlackFrame(Rect outer, Rect inner, int layer, DisplayContent dc,
             boolean forceDefaultOrientation) throws OutOfResourcesException {
         boolean success = false;
 
         mForceDefaultOrientation = forceDefaultOrientation;
 
+        // TODO: Why do we use 4 surfaces instead of just one big one behind the screenshot?
+        // b/68253229
         mOuterRect = new Rect(outer);
         mInnerRect = new Rect(inner);
         try {
             if (outer.top < inner.top) {
-                mBlackSurfaces[0] = new BlackSurface(session, layer,
-                        outer.left, outer.top, inner.right, inner.top, layerStack);
+                mBlackSurfaces[0] = new BlackSurface(layer,
+                        outer.left, outer.top, inner.right, inner.top, dc);
             }
             if (outer.left < inner.left) {
-                mBlackSurfaces[1] = new BlackSurface(session, layer,
-                        outer.left, inner.top, inner.left, outer.bottom, layerStack);
+                mBlackSurfaces[1] = new BlackSurface(layer,
+                        outer.left, inner.top, inner.left, outer.bottom, dc);
             }
             if (outer.bottom > inner.bottom) {
-                mBlackSurfaces[2] = new BlackSurface(session, layer,
-                        inner.left, inner.bottom, outer.right, outer.bottom, layerStack);
+                mBlackSurfaces[2] = new BlackSurface(layer,
+                        inner.left, inner.bottom, outer.right, outer.bottom, dc);
             }
             if (outer.right > inner.right) {
-                mBlackSurfaces[3] = new BlackSurface(session, layer,
-                        inner.right, outer.top, outer.right, inner.bottom, layerStack);
+                mBlackSurfaces[3] = new BlackSurface(layer,
+                        inner.right, outer.top, outer.right, inner.bottom, dc);
             }
             success = true;
         } finally {
diff --git a/com/android/server/wm/BoundsAnimationController.java b/com/android/server/wm/BoundsAnimationController.java
index 7953ee4..ba67ff6 100644
--- a/com/android/server/wm/BoundsAnimationController.java
+++ b/com/android/server/wm/BoundsAnimationController.java
@@ -33,7 +33,6 @@
 import android.util.Slog;
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
-import android.view.WindowManagerInternal;
 
 import com.android.internal.annotations.VisibleForTesting;
 
diff --git a/com/android/server/wm/CircularDisplayMask.java b/com/android/server/wm/CircularDisplayMask.java
index 2d5d1b2..2a216ab 100644
--- a/com/android/server/wm/CircularDisplayMask.java
+++ b/com/android/server/wm/CircularDisplayMask.java
@@ -33,7 +33,6 @@
 import android.view.Surface;
 import android.view.Surface.OutOfResourcesException;
 import android.view.SurfaceControl;
-import android.view.SurfaceSession;
 
 class CircularDisplayMask {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "CircularDisplayMask" : TAG_WM;
@@ -54,8 +53,10 @@
     private boolean mDimensionsUnequal = false;
     private int mMaskThickness;
 
-    public CircularDisplayMask(Display display, SurfaceSession session, int zOrder,
+    public CircularDisplayMask(DisplayContent dc, int zOrder,
             int screenOffset, int maskThickness) {
+        final Display display = dc.getDisplay();
+
         mScreenSize = new Point();
         display.getSize(mScreenSize);
         if (mScreenSize.x != mScreenSize.y + screenOffset) {
@@ -66,7 +67,7 @@
 
         SurfaceControl ctrl = null;
         try {
-            ctrl = new SurfaceControl.Builder(session)
+            ctrl = dc.makeOverlay()
                     .setName("CircularDisplayMask")
                     .setSize(mScreenSize.x, mScreenSize.y) // not a typo
                     .setFormat(PixelFormat.TRANSLUCENT)
diff --git a/com/android/server/wm/ConfigurationContainer.java b/com/android/server/wm/ConfigurationContainer.java
index cc94807..d340923 100644
--- a/com/android/server/wm/ConfigurationContainer.java
+++ b/com/android/server/wm/ConfigurationContainer.java
@@ -36,6 +36,7 @@
 import android.annotation.CallSuper;
 import android.app.WindowConfiguration;
 import android.content.res.Configuration;
+import android.graphics.Rect;
 import android.util.proto.ProtoOutputStream;
 
 import java.io.PrintWriter;
@@ -46,6 +47,11 @@
  * hierarchy.
  */
 public abstract class ConfigurationContainer<E extends ConfigurationContainer> {
+    /**
+     * {@link #Rect} returned from {@link #getOverrideBounds()} to prevent original value from being
+     * set directly.
+     */
+    private Rect mReturnBounds = new Rect();
 
     /** Contains override configuration settings applied to this configuration container. */
     private Configuration mOverrideConfiguration = new Configuration();
@@ -71,6 +77,16 @@
     // TODO: Can't have ag/2592611 soon enough!
     private final Configuration mTmpConfig = new Configuration();
 
+    // Used for setting bounds
+    private final Rect mTmpRect = new Rect();
+
+    static final int BOUNDS_CHANGE_NONE = 0;
+    // Return value from {@link setBounds} indicating the position of the override bounds changed.
+    static final int BOUNDS_CHANGE_POSITION = 1;
+    // Return value from {@link setBounds} indicating the size of the override bounds changed.
+    static final int BOUNDS_CHANGE_SIZE = 1 << 1;
+
+
     /**
      * Returns full configuration applied to this configuration container.
      * This method should be used for getting settings applied in each particular level of the
@@ -148,6 +164,118 @@
         }
     }
 
+    /**
+     * Indicates whether this container has not specified any bounds different from its parent. In
+     * this case, it will inherit the bounds of the first ancestor which specifies a bounds.
+     * @return {@code true} if no explicit bounds have been set at this container level.
+     *         {@code false} otherwise.
+     */
+    public boolean matchParentBounds() {
+        return getOverrideBounds().isEmpty();
+    }
+
+    /**
+     * Returns whether the bounds specified is considered the same as the existing override bounds.
+     * This is either when the two bounds are equal or the override bounds is empty and the
+     * specified bounds is null.
+     *
+     * @return {@code true} if the bounds are equivalent, {@code false} otherwise
+     */
+    public boolean equivalentOverrideBounds(Rect bounds) {
+        return equivalentBounds(getOverrideBounds(),  bounds);
+    }
+
+    /**
+     * Returns whether the two bounds are equal to each other or are a combination of null or empty.
+     */
+    public static boolean equivalentBounds(Rect bounds, Rect other) {
+        return bounds == other
+                || (bounds != null && (bounds.equals(other) || (bounds.isEmpty() && other == null)))
+                || (other != null && other.isEmpty() && bounds == null);
+    }
+
+    /**
+     * Returns the effective bounds of this container, inheriting the first non-empty bounds set in
+     * its ancestral hierarchy, including itself.
+     * @return
+     */
+    public Rect getBounds() {
+        mReturnBounds.set(getConfiguration().windowConfiguration.getBounds());
+        return mReturnBounds;
+    }
+
+    public void getBounds(Rect outBounds) {
+        outBounds.set(getBounds());
+    }
+
+    /**
+     * Returns the current bounds explicitly set on this container. The {@link Rect} handed back is
+     * shared for all calls to this method and should not be modified.
+     */
+    public Rect getOverrideBounds() {
+        mReturnBounds.set(getOverrideConfiguration().windowConfiguration.getBounds());
+
+        return mReturnBounds;
+    }
+
+    /**
+     * Sets the passed in {@link Rect} to the current bounds.
+     * @see {@link #getOverrideBounds()}.
+     */
+    public void getOverrideBounds(Rect outBounds) {
+        outBounds.set(getOverrideBounds());
+    }
+
+    /**
+     * Sets the bounds at the current hierarchy level, overriding any bounds set on an ancestor.
+     * This value will be reported when {@link #getBounds()} and {@link #getOverrideBounds()}. If
+     * an empty {@link Rect} or null is specified, this container will be considered to match its
+     * parent bounds {@see #matchParentBounds} and will inherit bounds from its parent.
+     * @param bounds The bounds defining the container size.
+     * @return a bitmask representing the types of changes made to the bounds.
+     */
+    public int setBounds(Rect bounds) {
+        int boundsChange = diffOverrideBounds(bounds);
+
+        if (boundsChange == BOUNDS_CHANGE_NONE) {
+            return boundsChange;
+        }
+
+
+        mTmpConfig.setTo(getOverrideConfiguration());
+        mTmpConfig.windowConfiguration.setBounds(bounds);
+        onOverrideConfigurationChanged(mTmpConfig);
+
+        return boundsChange;
+    }
+
+    public int setBounds(int left, int top, int right, int bottom) {
+        mTmpRect.set(left, top, right, bottom);
+        return setBounds(mTmpRect);
+    }
+
+    int diffOverrideBounds(Rect bounds) {
+        if (equivalentOverrideBounds(bounds)) {
+            return BOUNDS_CHANGE_NONE;
+        }
+
+        int boundsChange = BOUNDS_CHANGE_NONE;
+
+        final Rect existingBounds = getOverrideBounds();
+
+        if (bounds == null || existingBounds.left != bounds.left
+                || existingBounds.top != bounds.top) {
+            boundsChange |= BOUNDS_CHANGE_POSITION;
+        }
+
+        if (bounds == null || existingBounds.width() != bounds.width()
+                || existingBounds.height() != bounds.height()) {
+            boundsChange |= BOUNDS_CHANGE_SIZE;
+        }
+
+        return boundsChange;
+    }
+
     public WindowConfiguration getWindowConfiguration() {
         return mFullConfiguration.windowConfiguration;
     }
@@ -375,6 +503,10 @@
         return toString();
     }
 
+    boolean isAlwaysOnTop() {
+        return mFullConfiguration.windowConfiguration.isAlwaysOnTop();
+    }
+
     abstract protected int getChildCount();
 
     abstract protected E getChildAt(int index);
diff --git a/com/android/server/wm/DimLayer.java b/com/android/server/wm/DimLayer.java
deleted file mode 100644
index 8fb2be8..0000000
--- a/com/android/server/wm/DimLayer.java
+++ /dev/null
@@ -1,380 +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.server.wm;
-
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DIM_LAYER;
-import static com.android.server.wm.WindowManagerDebugConfig.SHOW_SURFACE_ALLOC;
-import static com.android.server.wm.WindowManagerDebugConfig.SHOW_TRANSACTIONS;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-
-import android.graphics.PixelFormat;
-import android.graphics.Rect;
-import android.os.SystemClock;
-import android.util.Slog;
-import android.view.DisplayInfo;
-import android.view.SurfaceControl;
-
-import java.io.PrintWriter;
-
-public class DimLayer {
-    private static final String TAG = TAG_WITH_CLASS_NAME ? "DimLayer" : TAG_WM;
-    private final WindowManagerService mService;
-
-    /** Actual surface that dims */
-    private SurfaceControl mDimSurface;
-
-    /** Last value passed to mDimSurface.setAlpha() */
-    private float mAlpha = 0;
-
-    /** Last value passed to mDimSurface.setLayer() */
-    private int mLayer = -1;
-
-    /** Next values to pass to mDimSurface.setPosition() and mDimSurface.setSize() */
-    private final Rect mBounds = new Rect();
-
-    /** Last values passed to mDimSurface.setPosition() and mDimSurface.setSize() */
-    private final Rect mLastBounds = new Rect();
-
-    /** True after mDimSurface.show() has been called, false after mDimSurface.hide(). */
-    private boolean mShowing = false;
-
-    /** Value of mAlpha when beginning transition to mTargetAlpha */
-    private float mStartAlpha = 0;
-
-    /** Final value of mAlpha following transition */
-    private float mTargetAlpha = 0;
-
-    /** Time in units of SystemClock.uptimeMillis() at which the current transition started */
-    private long mStartTime;
-
-    /** Time in milliseconds to take to transition from mStartAlpha to mTargetAlpha */
-    private long mDuration;
-
-    private boolean mDestroyed = false;
-
-    private final int mDisplayId;
-
-
-    /** Interface implemented by users of the dim layer */
-    interface DimLayerUser {
-        /** Returns true if the  dim should be fullscreen. */
-        boolean dimFullscreen();
-        /** Returns the display info. of the dim layer user. */
-        DisplayInfo getDisplayInfo();
-        /** Returns true if the dim layer user is currently attached to a display */
-        boolean isAttachedToDisplay();
-        /** Gets the bounds of the dim layer user. */
-        void getDimBounds(Rect outBounds);
-        /** Returns the layer to place a dim layer. */
-        default int getLayerForDim(WindowStateAnimator animator, int layerOffset,
-                int defaultLayer) {
-            return defaultLayer;
-        }
-
-        String toShortString();
-    }
-    /** The user of this dim layer. */
-    private final DimLayerUser mUser;
-
-    private final String mName;
-
-    DimLayer(WindowManagerService service, DimLayerUser user, int displayId, String name) {
-        mUser = user;
-        mDisplayId = displayId;
-        mService = service;
-        mName = name;
-        if (DEBUG_DIM_LAYER) Slog.v(TAG, "Ctor: displayId=" + displayId);
-    }
-
-    private void constructSurface(WindowManagerService service) {
-        service.openSurfaceTransaction();
-        try {
-            mDimSurface = new SurfaceControl.Builder(service.mFxSession)
-                    .setName(mName)
-                    .setSize(16, 16)
-                    .setColorLayer(true)
-                    .build();
-
-            if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) Slog.i(TAG,
-                    "  DIM " + mDimSurface + ": CREATE");
-            mDimSurface.setLayerStack(mDisplayId);
-            adjustBounds();
-            adjustAlpha(mAlpha);
-            adjustLayer(mLayer);
-        } catch (Exception e) {
-            Slog.e(TAG_WM, "Exception creating Dim surface", e);
-        } finally {
-            service.closeSurfaceTransaction("DimLayer.constructSurface");
-        }
-    }
-
-    /** Return true if dim layer is showing */
-    boolean isDimming() {
-        return mTargetAlpha != 0;
-    }
-
-    /** Return true if in a transition period */
-    boolean isAnimating() {
-        return mTargetAlpha != mAlpha;
-    }
-
-    float getTargetAlpha() {
-        return mTargetAlpha;
-    }
-
-    void setLayer(int layer) {
-        if (mLayer == layer) {
-            return;
-        }
-        mLayer = layer;
-        adjustLayer(layer);
-    }
-
-    private void adjustLayer(int layer) {
-        if (mDimSurface != null) {
-            mDimSurface.setLayer(layer);
-        }
-    }
-
-    int getLayer() {
-        return mLayer;
-    }
-
-    private void setAlpha(float alpha) {
-        if (mAlpha == alpha) {
-            return;
-        }
-        mAlpha = alpha;
-        adjustAlpha(alpha);
-    }
-
-    private void adjustAlpha(float alpha) {
-        if (DEBUG_DIM_LAYER) Slog.v(TAG, "setAlpha alpha=" + alpha);
-        try {
-            if (mDimSurface != null) {
-                mDimSurface.setAlpha(alpha);
-            }
-            if (alpha == 0 && mShowing) {
-                if (DEBUG_DIM_LAYER) Slog.v(TAG, "setAlpha hiding");
-                if (mDimSurface != null) {
-                    mDimSurface.hide();
-                    mShowing = false;
-                }
-            } else if (alpha > 0 && !mShowing) {
-                if (DEBUG_DIM_LAYER) Slog.v(TAG, "setAlpha showing");
-                if (mDimSurface != null) {
-                    mDimSurface.show();
-                    mShowing = true;
-                }
-            }
-        } catch (RuntimeException e) {
-            Slog.w(TAG, "Failure setting alpha immediately", e);
-        }
-    }
-
-    /**
-     * NOTE: Must be called with Surface transaction open.
-     */
-    private void adjustBounds() {
-        if (mUser.dimFullscreen()) {
-            getBoundsForFullscreen(mBounds);
-        }
-
-        if (mDimSurface != null) {
-            mDimSurface.setPosition(mBounds.left, mBounds.top);
-            mDimSurface.setSize(mBounds.width(), mBounds.height());
-            if (DEBUG_DIM_LAYER) Slog.v(TAG,
-                    "adjustBounds user=" + mUser.toShortString() + " mBounds=" + mBounds);
-        }
-
-        mLastBounds.set(mBounds);
-    }
-
-    private void getBoundsForFullscreen(Rect outBounds) {
-        final int dw, dh;
-        final float xPos, yPos;
-        // Set surface size to screen size.
-        final DisplayInfo info = mUser.getDisplayInfo();
-        // Multiply by 1.5 so that rotating a frozen surface that includes this does not expose
-        // a corner.
-        dw = (int) (info.logicalWidth * 1.5);
-        dh = (int) (info.logicalHeight * 1.5);
-        // back off position so 1/4 of Surface is before and 1/4 is after.
-        xPos = -1 * dw / 6;
-        yPos = -1 * dh / 6;
-        outBounds.set((int) xPos, (int) yPos, (int) xPos + dw, (int) yPos + dh);
-    }
-
-    void setBoundsForFullscreen() {
-        getBoundsForFullscreen(mBounds);
-        setBounds(mBounds);
-    }
-
-    /** @param bounds The new bounds to set */
-    void setBounds(Rect bounds) {
-        mBounds.set(bounds);
-        if (isDimming() && !mLastBounds.equals(bounds)) {
-            try {
-                mService.openSurfaceTransaction();
-                adjustBounds();
-            } catch (RuntimeException e) {
-                Slog.w(TAG, "Failure setting size", e);
-            } finally {
-                mService.closeSurfaceTransaction("DimLayer.setBounds");
-            }
-        }
-    }
-
-    /**
-     * @param duration The time to test.
-     * @return True if the duration would lead to an earlier end to the current animation.
-     */
-    private boolean durationEndsEarlier(long duration) {
-        return SystemClock.uptimeMillis() + duration < mStartTime + mDuration;
-    }
-
-    /** Jump to the end of the animation.
-     * NOTE: Must be called with Surface transaction open. */
-    void show() {
-        if (isAnimating()) {
-            if (DEBUG_DIM_LAYER) Slog.v(TAG, "show: immediate");
-            show(mLayer, mTargetAlpha, 0);
-        }
-    }
-
-    /**
-     * Begin an animation to a new dim value.
-     * NOTE: Must be called with Surface transaction open.
-     *
-     * @param layer The layer to set the surface to.
-     * @param alpha The dim value to end at.
-     * @param duration How long to take to get there in milliseconds.
-     */
-    void show(int layer, float alpha, long duration) {
-        if (DEBUG_DIM_LAYER) Slog.v(TAG, "show: layer=" + layer + " alpha=" + alpha
-                + " duration=" + duration + ", mDestroyed=" + mDestroyed);
-        if (mDestroyed) {
-            Slog.e(TAG, "show: no Surface");
-            // Make sure isAnimating() returns false.
-            mTargetAlpha = mAlpha = 0;
-            return;
-        }
-
-        if (mDimSurface == null) {
-            constructSurface(mService);
-        }
-
-        if (!mLastBounds.equals(mBounds)) {
-            adjustBounds();
-        }
-        setLayer(layer);
-
-        long curTime = SystemClock.uptimeMillis();
-        final boolean animating = isAnimating();
-        if ((animating && (mTargetAlpha != alpha || durationEndsEarlier(duration)))
-                || (!animating && mAlpha != alpha)) {
-            if (duration <= 0) {
-                // No animation required, just set values.
-                setAlpha(alpha);
-            } else {
-                // Start or continue animation with new parameters.
-                mStartAlpha = mAlpha;
-                mStartTime = curTime;
-                mDuration = duration;
-            }
-        }
-        mTargetAlpha = alpha;
-        if (DEBUG_DIM_LAYER) Slog.v(TAG, "show: mStartAlpha=" + mStartAlpha + " mStartTime="
-                + mStartTime + " mTargetAlpha=" + mTargetAlpha);
-    }
-
-    /** Immediate hide.
-     * NOTE: Must be called with Surface transaction open. */
-    void hide() {
-        if (mShowing) {
-            if (DEBUG_DIM_LAYER) Slog.v(TAG, "hide: immediate");
-            hide(0);
-        }
-    }
-
-    /**
-     * Gradually fade to transparent.
-     * NOTE: Must be called with Surface transaction open.
-     *
-     * @param duration Time to fade in milliseconds.
-     */
-    void hide(long duration) {
-        if (mShowing && (mTargetAlpha != 0 || durationEndsEarlier(duration))) {
-            if (DEBUG_DIM_LAYER) Slog.v(TAG, "hide: duration=" + duration);
-            show(mLayer, 0, duration);
-        }
-    }
-
-    /**
-     * Advance the dimming per the last #show(int, float, long) call.
-     * NOTE: Must be called with Surface transaction open.
-     *
-     * @return True if animation is still required after this step.
-     */
-    boolean stepAnimation() {
-        if (mDestroyed) {
-            Slog.e(TAG, "stepAnimation: surface destroyed");
-            // Ensure that isAnimating() returns false;
-            mTargetAlpha = mAlpha = 0;
-            return false;
-        }
-        if (isAnimating()) {
-            final long curTime = SystemClock.uptimeMillis();
-            final float alphaDelta = mTargetAlpha - mStartAlpha;
-            float alpha = mStartAlpha + alphaDelta * (curTime - mStartTime) / mDuration;
-            if (alphaDelta > 0 && alpha > mTargetAlpha ||
-                    alphaDelta < 0 && alpha < mTargetAlpha) {
-                // Don't exceed limits.
-                alpha = mTargetAlpha;
-            }
-            if (DEBUG_DIM_LAYER) Slog.v(TAG, "stepAnimation: curTime=" + curTime + " alpha=" + alpha);
-            setAlpha(alpha);
-        }
-
-        return isAnimating();
-    }
-
-    /** Cleanup */
-    void destroySurface() {
-        if (DEBUG_DIM_LAYER) Slog.v(TAG, "destroySurface.");
-        if (mDimSurface != null) {
-            mDimSurface.destroy();
-            mDimSurface = null;
-        }
-        mDestroyed = true;
-    }
-
-    public void printTo(String prefix, PrintWriter pw) {
-        pw.print(prefix); pw.print("mDimSurface="); pw.print(mDimSurface);
-                pw.print(" mLayer="); pw.print(mLayer);
-                pw.print(" mAlpha="); pw.println(mAlpha);
-        pw.print(prefix); pw.print("mLastBounds="); pw.print(mLastBounds.toShortString());
-                pw.print(" mBounds="); pw.println(mBounds.toShortString());
-        pw.print(prefix); pw.print("Last animation: ");
-                pw.print(" mDuration="); pw.print(mDuration);
-                pw.print(" mStartTime="); pw.print(mStartTime);
-                pw.print(" curTime="); pw.println(SystemClock.uptimeMillis());
-        pw.print(prefix); pw.print(" mStartAlpha="); pw.print(mStartAlpha);
-                pw.print(" mTargetAlpha="); pw.println(mTargetAlpha);
-    }
-}
diff --git a/com/android/server/wm/DimLayerController.java b/com/android/server/wm/DimLayerController.java
deleted file mode 100644
index 6f9e45a..0000000
--- a/com/android/server/wm/DimLayerController.java
+++ /dev/null
@@ -1,403 +0,0 @@
-package com.android.server.wm;
-
-import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
-import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DIM_LAYER;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.WindowManagerService.LAYER_OFFSET_DIM;
-
-import android.graphics.Rect;
-import android.util.ArrayMap;
-import android.util.Slog;
-import android.util.TypedValue;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.wm.DimLayer.DimLayerUser;
-
-import java.io.PrintWriter;
-
-/**
- * Centralizes the control of dim layers used for
- * {@link android.view.WindowManager.LayoutParams#FLAG_DIM_BEHIND}
- * as well as other use cases (such as dimming above a dead window).
- */
-class DimLayerController {
-    private static final String TAG_LOCAL = "DimLayerController";
-    private static final String TAG = TAG_WITH_CLASS_NAME ? TAG_LOCAL : TAG_WM;
-
-    /** Amount of time in milliseconds to animate the dim surface from one value to another,
-     * when no window animation is driving it. */
-    private static final int DEFAULT_DIM_DURATION = 200;
-
-    /**
-     * The default amount of dim applied over a dead window
-     */
-    private static final float DEFAULT_DIM_AMOUNT_DEAD_WINDOW = 0.5f;
-
-    // Shared dim layer for fullscreen users. {@link DimLayerState#dimLayer} will point to this
-    // instead of creating a new object per fullscreen task on a display.
-    private DimLayer mSharedFullScreenDimLayer;
-
-    private ArrayMap<DimLayer.DimLayerUser, DimLayerState> mState = new ArrayMap<>();
-
-    private DisplayContent mDisplayContent;
-
-    private Rect mTmpBounds = new Rect();
-
-    DimLayerController(DisplayContent displayContent) {
-        mDisplayContent = displayContent;
-    }
-
-    /** Updates the dim layer bounds, recreating it if needed. */
-    void updateDimLayer(DimLayer.DimLayerUser dimLayerUser) {
-        final DimLayerState state = getOrCreateDimLayerState(dimLayerUser);
-        final boolean previousFullscreen = state.dimLayer != null
-                && state.dimLayer == mSharedFullScreenDimLayer;
-        DimLayer newDimLayer;
-        final int displayId = mDisplayContent.getDisplayId();
-        if (dimLayerUser.dimFullscreen()) {
-            if (previousFullscreen && mSharedFullScreenDimLayer != null) {
-                // Update the bounds for fullscreen in case of rotation.
-                mSharedFullScreenDimLayer.setBoundsForFullscreen();
-                return;
-            }
-            // Use shared fullscreen dim layer
-            newDimLayer = mSharedFullScreenDimLayer;
-            if (newDimLayer == null) {
-                if (state.dimLayer != null) {
-                    // Re-purpose the previous dim layer.
-                    newDimLayer = state.dimLayer;
-                } else {
-                    // Create new full screen dim layer.
-                    newDimLayer = new DimLayer(mDisplayContent.mService, dimLayerUser, displayId,
-                            getDimLayerTag(dimLayerUser));
-                }
-                dimLayerUser.getDimBounds(mTmpBounds);
-                newDimLayer.setBounds(mTmpBounds);
-                mSharedFullScreenDimLayer = newDimLayer;
-            } else if (state.dimLayer != null) {
-                state.dimLayer.destroySurface();
-            }
-        } else {
-            newDimLayer = (state.dimLayer == null || previousFullscreen)
-                    ? new DimLayer(mDisplayContent.mService, dimLayerUser, displayId,
-                            getDimLayerTag(dimLayerUser))
-                    : state.dimLayer;
-            dimLayerUser.getDimBounds(mTmpBounds);
-            newDimLayer.setBounds(mTmpBounds);
-        }
-        state.dimLayer = newDimLayer;
-    }
-
-    private static String getDimLayerTag(DimLayerUser dimLayerUser) {
-        return TAG_LOCAL + "/" + dimLayerUser.toShortString();
-    }
-
-    private DimLayerState getOrCreateDimLayerState(DimLayer.DimLayerUser dimLayerUser) {
-        if (DEBUG_DIM_LAYER) Slog.v(TAG, "getOrCreateDimLayerState, dimLayerUser="
-                + dimLayerUser.toShortString());
-        DimLayerState state = mState.get(dimLayerUser);
-        if (state == null) {
-            state = new DimLayerState();
-            mState.put(dimLayerUser, state);
-        }
-        return state;
-    }
-
-    private void setContinueDimming(DimLayer.DimLayerUser dimLayerUser) {
-        DimLayerState state = mState.get(dimLayerUser);
-        if (state == null) {
-            if (DEBUG_DIM_LAYER) Slog.w(TAG, "setContinueDimming, no state for: "
-                    + dimLayerUser.toShortString());
-            return;
-        }
-        state.continueDimming = true;
-    }
-
-    boolean isDimming() {
-        for (int i = mState.size() - 1; i >= 0; i--) {
-            DimLayerState state = mState.valueAt(i);
-            if (state.dimLayer != null && state.dimLayer.isDimming()) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    void resetDimming() {
-        for (int i = mState.size() - 1; i >= 0; i--) {
-            mState.valueAt(i).continueDimming = false;
-        }
-    }
-
-    private boolean getContinueDimming(DimLayer.DimLayerUser dimLayerUser) {
-        DimLayerState state = mState.get(dimLayerUser);
-        return state != null && state.continueDimming;
-    }
-
-    void startDimmingIfNeeded(DimLayer.DimLayerUser dimLayerUser,
-            WindowStateAnimator newWinAnimator, boolean aboveApp) {
-        // Only set dim params on the highest dimmed layer.
-        // Don't turn on for an unshown surface, or for any layer but the highest dimmed layer.
-        DimLayerState state = getOrCreateDimLayerState(dimLayerUser);
-        state.dimAbove = aboveApp;
-        if (DEBUG_DIM_LAYER) Slog.v(TAG, "startDimmingIfNeeded,"
-                + " dimLayerUser=" + dimLayerUser.toShortString()
-                + " newWinAnimator=" + newWinAnimator
-                + " state.animator=" + state.animator);
-        if (newWinAnimator.getShown() && (state.animator == null
-                || !state.animator.getShown()
-                || state.animator.mAnimLayer <= newWinAnimator.mAnimLayer)) {
-            state.animator = newWinAnimator;
-            if (state.animator.mWin.mAppToken == null && !dimLayerUser.dimFullscreen()) {
-                // Dim should cover the entire screen for system windows.
-                mDisplayContent.getLogicalDisplayRect(mTmpBounds);
-            } else {
-                dimLayerUser.getDimBounds(mTmpBounds);
-            }
-            state.dimLayer.setBounds(mTmpBounds);
-        }
-    }
-
-    void stopDimmingIfNeeded() {
-        if (DEBUG_DIM_LAYER) Slog.v(TAG, "stopDimmingIfNeeded, mState.size()=" + mState.size());
-        for (int i = mState.size() - 1; i >= 0; i--) {
-            DimLayer.DimLayerUser dimLayerUser = mState.keyAt(i);
-            stopDimmingIfNeeded(dimLayerUser);
-        }
-    }
-
-    private void stopDimmingIfNeeded(DimLayer.DimLayerUser dimLayerUser) {
-        // No need to check if state is null, we know the key has a value.
-        DimLayerState state = mState.get(dimLayerUser);
-        if (DEBUG_DIM_LAYER) Slog.v(TAG, "stopDimmingIfNeeded,"
-                + " dimLayerUser=" + dimLayerUser.toShortString()
-                + " state.continueDimming=" + state.continueDimming
-                + " state.dimLayer.isDimming=" + state.dimLayer.isDimming());
-        if (state.animator != null && state.animator.mWin.mWillReplaceWindow) {
-            return;
-        }
-
-        if (!state.continueDimming && state.dimLayer.isDimming()) {
-            state.animator = null;
-            dimLayerUser.getDimBounds(mTmpBounds);
-            state.dimLayer.setBounds(mTmpBounds);
-        }
-    }
-
-    boolean animateDimLayers() {
-        int fullScreen = -1;
-        int fullScreenAndDimming = -1;
-        int topFullScreenUserLayer = 0;
-        boolean result = false;
-
-        for (int i = mState.size() - 1; i >= 0; i--) {
-            final DimLayer.DimLayerUser user = mState.keyAt(i);
-            final DimLayerState state = mState.valueAt(i);
-
-            if (!user.isAttachedToDisplay()) {
-                // Leaked dim user that is no longer attached to the display. Go ahead and clean it
-                // clean-up and log what happened.
-                // TODO: This is a work around for b/34395537 as the dim user should have cleaned-up
-                // it self when it was detached from the display. Need to investigate how the dim
-                // user is leaking...
-                //Slog.wtfStack(TAG_WM, "Leaked dim user=" + user.toShortString()
-                //        + " state=" + state);
-                Slog.w(TAG_WM, "Leaked dim user=" + user.toShortString() + " state=" + state);
-                removeDimLayerUser(user);
-                continue;
-            }
-
-            // We have to check that we are actually the shared fullscreen layer
-            // for this path. If we began as non fullscreen and became fullscreen
-            // (e.g. Docked stack closing), then we may not be the shared layer
-            // and we have to make sure we always animate the layer.
-            if (user.dimFullscreen() && state.dimLayer == mSharedFullScreenDimLayer) {
-                fullScreen = i;
-                if (!state.continueDimming) {
-                    continue;
-                }
-
-                // When choosing which user to assign the shared fullscreen layer to
-                // we need to look at Z-order.
-                if (topFullScreenUserLayer == 0 ||
-                        (state.animator != null && state.animator.mAnimLayer > topFullScreenUserLayer)) {
-                    fullScreenAndDimming = i;
-                    if (state.animator != null) {
-                        topFullScreenUserLayer = state.animator.mAnimLayer;
-                    }
-                }
-            } else {
-                // We always want to animate the non fullscreen windows, they don't share their
-                // dim layers.
-                result |= animateDimLayers(user);
-            }
-        }
-        // For the shared, full screen dim layer, we prefer the animation that is causing it to
-        // appear.
-        if (fullScreenAndDimming != -1) {
-            result |= animateDimLayers(mState.keyAt(fullScreenAndDimming));
-        } else if (fullScreen != -1) {
-            // If there is no animation for the full screen dim layer to appear, we can use any of
-            // the animators that will cause it to disappear.
-            result |= animateDimLayers(mState.keyAt(fullScreen));
-        }
-        return result;
-    }
-
-    private boolean animateDimLayers(DimLayer.DimLayerUser dimLayerUser) {
-        DimLayerState state = mState.get(dimLayerUser);
-        if (DEBUG_DIM_LAYER) Slog.v(TAG, "animateDimLayers,"
-                + " dimLayerUser=" + dimLayerUser.toShortString()
-                + " state.animator=" + state.animator
-                + " state.continueDimming=" + state.continueDimming);
-        final int dimLayer;
-        final float dimAmount;
-        if (state.animator == null) {
-            dimLayer = state.dimLayer.getLayer();
-            dimAmount = 0;
-        } else {
-            if (state.dimAbove) {
-                dimLayer = state.animator.mAnimLayer + LAYER_OFFSET_DIM;
-                dimAmount = DEFAULT_DIM_AMOUNT_DEAD_WINDOW;
-            } else {
-                dimLayer = dimLayerUser.getLayerForDim(state.animator, LAYER_OFFSET_DIM,
-                        state.animator.mAnimLayer - LAYER_OFFSET_DIM);
-                dimAmount = state.animator.mWin.mAttrs.dimAmount;
-            }
-        }
-        final float targetAlpha = state.dimLayer.getTargetAlpha();
-        if (targetAlpha != dimAmount) {
-            if (state.animator == null) {
-                state.dimLayer.hide(DEFAULT_DIM_DURATION);
-            } else {
-                long duration = (state.animator.mAnimating && state.animator.mAnimation != null)
-                        ? state.animator.mAnimation.computeDurationHint()
-                        : DEFAULT_DIM_DURATION;
-                if (targetAlpha > dimAmount) {
-                    duration = getDimLayerFadeDuration(duration);
-                }
-                state.dimLayer.show(dimLayer, dimAmount, duration);
-
-                // If we showed a dim layer, make sure to redo the layout because some things depend
-                // on whether a dim layer is showing or not.
-                if (targetAlpha == 0) {
-                    mDisplayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_LAYOUT;
-                    mDisplayContent.setLayoutNeeded();
-                }
-            }
-        } else if (state.dimLayer.getLayer() != dimLayer) {
-            state.dimLayer.setLayer(dimLayer);
-        }
-        if (state.dimLayer.isAnimating()) {
-            if (!mDisplayContent.okToAnimate()) {
-                // Jump to the end of the animation.
-                state.dimLayer.show();
-            } else {
-                return state.dimLayer.stepAnimation();
-            }
-        }
-        return false;
-    }
-
-    boolean isDimming(DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator winAnimator) {
-        DimLayerState state = mState.get(dimLayerUser);
-        return state != null && state.animator == winAnimator && state.dimLayer.isDimming();
-    }
-
-    private long getDimLayerFadeDuration(long duration) {
-        TypedValue tv = new TypedValue();
-        mDisplayContent.mService.mContext.getResources().getValue(
-                com.android.internal.R.fraction.config_dimBehindFadeDuration, tv, true);
-        if (tv.type == TypedValue.TYPE_FRACTION) {
-            duration = (long) tv.getFraction(duration, duration);
-        } else if (tv.type >= TypedValue.TYPE_FIRST_INT && tv.type <= TypedValue.TYPE_LAST_INT) {
-            duration = tv.data;
-        }
-        return duration;
-    }
-
-    void close() {
-        for (int i = mState.size() - 1; i >= 0; i--) {
-            DimLayerState state = mState.valueAt(i);
-            state.dimLayer.destroySurface();
-        }
-        mState.clear();
-        mSharedFullScreenDimLayer = null;
-    }
-
-    void removeDimLayerUser(DimLayer.DimLayerUser dimLayerUser) {
-        DimLayerState state = mState.get(dimLayerUser);
-        if (state != null) {
-            // Destroy the surface, unless it's the shared fullscreen dim.
-            if (state.dimLayer != mSharedFullScreenDimLayer) {
-                state.dimLayer.destroySurface();
-            }
-            mState.remove(dimLayerUser);
-        }
-        if (mState.isEmpty()) {
-            mSharedFullScreenDimLayer = null;
-        }
-    }
-
-    @VisibleForTesting
-    boolean hasDimLayerUser(DimLayer.DimLayerUser dimLayerUser) {
-        return mState.containsKey(dimLayerUser);
-    }
-
-    @VisibleForTesting
-    boolean hasSharedFullScreenDimLayer() {
-        return mSharedFullScreenDimLayer != null;
-    }
-
-    void applyDimBehind(DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator animator) {
-        applyDim(dimLayerUser, animator, false /* aboveApp */);
-    }
-
-    void applyDimAbove(DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator animator) {
-        applyDim(dimLayerUser, animator, true /* aboveApp */);
-    }
-
-    void applyDim(
-            DimLayer.DimLayerUser dimLayerUser, WindowStateAnimator animator, boolean aboveApp) {
-        if (dimLayerUser == null) {
-            Slog.e(TAG, "Trying to apply dim layer for: " + this
-                    + ", but no dim layer user found.");
-            return;
-        }
-        if (!getContinueDimming(dimLayerUser)) {
-            setContinueDimming(dimLayerUser);
-            if (!isDimming(dimLayerUser, animator)) {
-                if (DEBUG_DIM_LAYER) Slog.v(TAG, "Win " + this + " start dimming.");
-                startDimmingIfNeeded(dimLayerUser, animator, aboveApp);
-            }
-        }
-    }
-
-    private static class DimLayerState {
-        // The particular window requesting a dim layer. If null, hide dimLayer.
-        WindowStateAnimator animator;
-        // Set to false at the start of performLayoutAndPlaceSurfaces. If it is still false by the
-        // end then stop any dimming.
-        boolean continueDimming;
-        DimLayer dimLayer;
-        boolean dimAbove;
-    }
-
-    void dump(String prefix, PrintWriter pw) {
-        pw.println(prefix + "DimLayerController");
-        final String doubleSpace = "  ";
-        final String prefixPlusDoubleSpace = prefix + doubleSpace;
-
-        for (int i = 0, n = mState.size(); i < n; i++) {
-            pw.println(prefixPlusDoubleSpace + mState.keyAt(i).toShortString());
-            DimLayerState state = mState.valueAt(i);
-            pw.println(prefixPlusDoubleSpace + doubleSpace + "dimLayer="
-                    + (state.dimLayer == mSharedFullScreenDimLayer ? "shared" : state.dimLayer)
-                    + ", animator=" + state.animator + ", continueDimming=" + state.continueDimming);
-            if (state.dimLayer != null) {
-                state.dimLayer.printTo(prefixPlusDoubleSpace + doubleSpace, pw);
-            }
-        }
-    }
-}
diff --git a/com/android/server/wm/Dimmer.java b/com/android/server/wm/Dimmer.java
new file mode 100644
index 0000000..9fe16ae
--- /dev/null
+++ b/com/android/server/wm/Dimmer.java
@@ -0,0 +1,195 @@
+/*
+ * 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.wm;
+
+import android.util.ArrayMap;
+import android.util.Slog;
+import android.view.SurfaceControl;
+import android.graphics.Rect;
+
+/**
+ * Utility class for use by a WindowContainer implementation to add "DimLayer" support, that is
+ * black layers of varying opacity at various Z-levels which create the effect of a Dim.
+ */
+class Dimmer {
+    private static final String TAG = "WindowManager";
+
+    private class DimState {
+        SurfaceControl mSurfaceControl;
+        boolean mDimming;
+
+        /**
+         * Used for Dims not assosciated with a WindowContainer. See {@link Dimmer#dimAbove} for
+         * details on Dim lifecycle.
+         */
+        boolean mDontReset;
+
+        DimState(SurfaceControl ctl) {
+            mSurfaceControl = ctl;
+            mDimming = true;
+        }
+    };
+
+    private ArrayMap<WindowContainer, DimState> mDimLayerUsers = new ArrayMap<>();
+
+    /**
+     * The {@link WindowContainer} that our Dim's are bounded to. We may be dimming on behalf of the
+     * host, some controller of it, or one of the hosts children.
+     */
+    private WindowContainer mHost;
+
+    Dimmer(WindowContainer host) {
+        mHost = host;
+    }
+
+    SurfaceControl makeDimLayer() {
+        final SurfaceControl control = mHost.makeChildSurface(null)
+                .setParent(mHost.getSurfaceControl())
+                .setColorLayer(true)
+                .setName("Dim Layer for - " + mHost.getName())
+                .build();
+        return control;
+    }
+
+    /**
+     * Retreive the DimState for a given child of the host.
+     */
+    DimState getDimState(WindowContainer container) {
+        DimState state = mDimLayerUsers.get(container);
+        if (state == null) {
+            final SurfaceControl ctl = makeDimLayer();
+            state = new DimState(ctl);
+            /**
+             * See documentation on {@link #dimAbove} to understand lifecycle management of Dim's
+             * via state resetting for Dim's with containers.
+             */
+            if (container == null) {
+                state.mDontReset = true;
+            }
+            mDimLayerUsers.put(container, state);
+        }
+        return state;
+    }
+
+    private void dim(SurfaceControl.Transaction t, WindowContainer container, int relativeLayer,
+            float alpha) {
+        final DimState d = getDimState(container);
+        t.show(d.mSurfaceControl);
+        if (container != null) {
+            t.setRelativeLayer(d.mSurfaceControl,
+                    container.getSurfaceControl(), relativeLayer);
+        } else {
+            t.setLayer(d.mSurfaceControl, Integer.MAX_VALUE);
+        }
+        t.setAlpha(d.mSurfaceControl, alpha);
+
+        d.mDimming = true;
+    }
+
+    /**
+     * Finish a dim started by dimAbove in the case there was no call to dimAbove.
+     *
+     * @param t A Transaction in which to finish the dim.
+     */
+    void stopDim(SurfaceControl.Transaction t) {
+        DimState d = getDimState(null);
+        t.hide(d.mSurfaceControl);
+        d.mDontReset = false;
+    }
+    /**
+     * Place a Dim above the entire host container. The caller is responsible for calling stopDim to
+     * remove this effect. If the Dim can be assosciated with a particular child of the host
+     * consider using the other variant of dimAbove which ties the Dim lifetime to the child
+     * lifetime more explicitly.
+     *
+     * @param t A transaction in which to apply the Dim.
+     * @param alpha The alpha at which to Dim.
+     */
+    void dimAbove(SurfaceControl.Transaction t, float alpha) {
+        dim(t, null, 1, alpha);
+    }
+
+    /**
+     * Place a dim above the given container, which should be a child of the host container.
+     * for each call to {@link WindowContainer#prepareSurfaces} the Dim state will be reset
+     * and the child should call dimAbove again to request the Dim to continue.
+     *
+     * @param t A transaction in which to apply the Dim.
+     * @param container The container which to dim above. Should be a child of our host.
+     * @param alpha The alpha at which to Dim.
+     */
+    void dimAbove(SurfaceControl.Transaction t, WindowContainer container, float alpha) {
+        dim(t, container, 1, alpha);
+    }
+
+    /**
+     * Like {@link #dimAbove} but places the dim below the given container.
+     *
+     * @param t A transaction in which to apply the Dim.
+     * @param container The container which to dim below. Should be a child of our host.
+     * @param alpha The alpha at which to Dim.
+     */
+
+    void dimBelow(SurfaceControl.Transaction t, WindowContainer container, float alpha) {
+        dim(t, container, -1, alpha);
+    }
+
+    /**
+     * Mark all dims as pending completion on the next call to {@link #updateDims}
+     *
+     * This is intended for us by the host container, to be called at the beginning of
+     * {@link WindowContainer#prepareSurfaces}. After calling this, the container should
+     * chain {@link WindowContainer#prepareSurfaces} down to it's children to give them
+     * a chance to request dims to continue.
+     */
+    void resetDimStates() {
+        for (int i = mDimLayerUsers.size() - 1; i >= 0; i--) {
+            final DimState state = mDimLayerUsers.valueAt(i);
+            if (state.mDontReset == false) {
+                state.mDimming = false;
+            }
+        }
+    }
+
+    /**
+     * Call after invoking {@link WindowContainer#prepareSurfaces} on children as
+     * described in {@link #resetDimStates}.
+     *
+     * @param t A transaction in which to update the dims.
+     * @param bounds The bounds at which to dim.
+     * @return true if any Dims were updated.
+     */
+    boolean updateDims(SurfaceControl.Transaction t, Rect bounds) {
+        boolean didSomething = false;
+        for (int i = mDimLayerUsers.size() - 1; i >= 0; i--) {
+            DimState state = mDimLayerUsers.valueAt(i);
+            // TODO: We want to animate the addition and removal of Dim's instead of immediately
+            // acting. When we do this we need to take care to account for the "Replacing Windows"
+            // case (and seamless dim transfer).
+            if (state.mDimming == false) {
+                mDimLayerUsers.removeAt(i);
+                state.mSurfaceControl.destroy();
+            } else {
+                didSomething = true;
+                // TODO: Once we use geometry from hierarchy this falls away.
+                t.setSize(state.mSurfaceControl, bounds.width(), bounds.height());
+                t.setPosition(state.mSurfaceControl, bounds.left, bounds.top);
+            }
+        }
+        return didSomething;
+    }
+}
diff --git a/com/android/server/wm/DisplayContent.java b/com/android/server/wm/DisplayContent.java
index 4d839d0..41348ba 100644
--- a/com/android/server/wm/DisplayContent.java
+++ b/com/android/server/wm/DisplayContent.java
@@ -58,10 +58,10 @@
 import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;
 import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
-import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
-import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG;
-import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
-import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
+import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
+import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG;
+import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
+import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
 import static com.android.server.wm.AppTransition.TRANSIT_KEYGUARD_UNOCCLUDE;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_BOOT;
@@ -142,14 +142,15 @@
 import android.view.Display;
 import android.view.DisplayInfo;
 import android.view.InputDevice;
+import android.view.MagnificationSpec;
 import android.view.Surface;
 import android.view.SurfaceControl;
-import android.view.WindowManagerPolicy;
+import android.view.SurfaceSession;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ToBooleanFunction;
 import com.android.internal.view.IInputMethodClient;
-import android.view.DisplayFrames;
+import com.android.server.policy.WindowManagerPolicy;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -180,8 +181,8 @@
     private final TaskStackContainers mTaskStackContainers = new TaskStackContainers();
     // Contains all non-app window containers that should be displayed above the app containers
     // (e.g. Status bar)
-    private final NonAppWindowContainers mAboveAppWindowsContainers =
-            new NonAppWindowContainers("mAboveAppWindowsContainers");
+    private final AboveAppWindowContainers mAboveAppWindowsContainers =
+            new AboveAppWindowContainers("mAboveAppWindowsContainers");
     // Contains all non-app window containers that should be displayed below the app containers
     // (e.g. Wallpaper).
     private final NonAppWindowContainers mBelowAppWindowsContainers =
@@ -313,6 +314,9 @@
     private final Matrix mTmpMatrix = new Matrix();
     private final Region mTmpRegion = new Region();
 
+    /** Used for handing back size of display */
+    private final Rect mTmpBounds = new Rect();
+
     WindowManagerService mService;
 
     /** Remove this display when animation on it has completed. */
@@ -321,8 +325,6 @@
     final DockedStackDividerController mDividerControllerLocked;
     final PinnedStackController mPinnedStackControllerLocked;
 
-    DimLayerController mDimLayerController;
-
     final ArrayList<WindowState> mTapExcludedWindows = new ArrayList<>();
 
     private boolean mHaveBootMsg = false;
@@ -346,10 +348,38 @@
     // {@code false} if this display is in the processing of being created.
     private boolean mDisplayReady = false;
 
-    private final WindowLayersController mLayersController;
     WallpaperController mWallpaperController;
     int mInputMethodAnimLayerAdjustment;
 
+    private final SurfaceSession mSession = new SurfaceSession();
+
+    /**
+     * We organize all top-level Surfaces in to the following layers.
+     * mOverlayLayer contains a few Surfaces which are always on top of others
+     * and omitted from Screen-Magnification, for example the strict mode flash or
+     * the magnification overlay itself.
+     * {@link #mWindowingLayer} contains everything else.
+     */
+    private SurfaceControl mOverlayLayer;
+
+    /**
+     * See {@link #mOverlayLayer}
+     */
+    private SurfaceControl mWindowingLayer;
+
+    /**
+     * Specifies the size of the surfaces in {@link #mOverlayLayer} and {@link #mWindowingLayer}.
+     * <p>
+     * For these surfaces currently we use a surface based on the larger of width or height so we
+     * don't have to resize when rotating the display.
+     */
+    private int mSurfaceSize;
+
+    /**
+     * A list of surfaces to be destroyed after {@link #mPendingTransaction} is applied.
+     */
+    private final ArrayList<SurfaceControl> mPendingDestroyingSurfaces = new ArrayList<>();
+
     private final Consumer<WindowState> mUpdateWindowsForAnimator = w -> {
         WindowStateAnimator winAnimator = w.mWinAnimator;
         if (winAnimator.hasSurface()) {
@@ -503,9 +533,6 @@
         return true;
     };
 
-    private final Consumer<WindowState> mPrepareWindowSurfaces =
-            w -> w.mWinAnimator.prepareSurfaceLocked(true);
-
     private final Consumer<WindowState> mPerformLayout = w -> {
         // Don't do layout of a window if it is not visible, or soon won't be visible, to avoid
         // wasting time and funky changes while a window is animating away.
@@ -558,12 +585,6 @@
                     w.updateLastInsetValues();
                 }
 
-                // Window frames may have changed. Update dim layer with the new bounds.
-                final Task task = w.getTask();
-                if (task != null) {
-                    mDimLayerController.updateDimLayer(task);
-                }
-
                 if (DEBUG_LAYOUT) Slog.v(TAG, "  LAYOUT: mFrame=" + w.mFrame
                         + " mContainingFrame=" + w.mContainingFrame
                         + " mDisplayFrame=" + w.mDisplayFrame);
@@ -657,8 +678,6 @@
             }
         }
 
-        w.applyDimLayerIfNeeded();
-
         if (isDefaultDisplay && obscuredChanged && w.isVisibleLw()
                 && mWallpaperController.isWallpaperTarget(w)) {
             // This is the wallpaper target and its obscured state changed... make sure the
@@ -741,13 +760,11 @@
      * initialize direct children.
      * @param display May not be null.
      * @param service You know.
-     * @param layersController window layer controller used to assign layer to the windows on this
-     *                         display.
      * @param wallpaperController wallpaper windows controller used to adjust the positioning of the
      *                            wallpaper windows in the window list.
      */
     DisplayContent(Display display, WindowManagerService service,
-            WindowLayersController layersController, WallpaperController wallpaperController) {
+            WallpaperController wallpaperController) {
         if (service.mRoot.getDisplayContent(display.getDisplayId()) != null) {
             throw new IllegalArgumentException("Display with ID=" + display.getDisplayId()
                     + " already exists=" + service.mRoot.getDisplayContent(display.getDisplayId())
@@ -756,7 +773,6 @@
 
         mDisplay = display;
         mDisplayId = display.getDisplayId();
-        mLayersController = layersController;
         mWallpaperController = wallpaperController;
         display.getDisplayInfo(mDisplayInfo);
         display.getMetrics(mDisplayMetrics);
@@ -766,7 +782,22 @@
         initializeDisplayBaseInfo();
         mDividerControllerLocked = new DockedStackDividerController(service, this);
         mPinnedStackControllerLocked = new PinnedStackController(service, this);
-        mDimLayerController = new DimLayerController(this);
+
+        mSurfaceSize = Math.max(mBaseDisplayHeight, mBaseDisplayWidth);
+
+        final SurfaceControl.Builder b = mService.makeSurfaceBuilder(mSession)
+                .setSize(mSurfaceSize, mSurfaceSize)
+                .setOpaque(true);
+        mWindowingLayer = b.setName("Display Root").build();
+        mOverlayLayer = b.setName("Display Overlays").build();
+
+        getPendingTransaction().setLayer(mWindowingLayer, 0)
+                .setLayerStack(mWindowingLayer, mDisplayId)
+                .show(mWindowingLayer)
+                .setLayer(mOverlayLayer, 1)
+                .setLayerStack(mOverlayLayer, mDisplayId)
+                .show(mOverlayLayer);
+        getPendingTransaction().apply();
 
         // These are the only direct children we should ever have and they are permanent.
         super.addChild(mBelowAppWindowsContainers, null);
@@ -1030,11 +1061,7 @@
 
         setLayoutNeeded();
         final int[] anim = new int[2];
-        if (isDimming()) {
-            anim[0] = anim[1] = 0;
-        } else {
-            mService.mPolicy.selectRotationAnimationLw(anim);
-        }
+        mService.mPolicy.selectRotationAnimationLw(anim);
 
         if (!rotateSeamlessly) {
             mService.startFreezingDisplayLocked(inTransaction, anim[0], anim[1], this);
@@ -1071,8 +1098,7 @@
             //       it doesn't support hardware OpenGL emulation yet.
             if (CUSTOM_SCREEN_ROTATION && screenRotationAnimation != null
                     && screenRotationAnimation.hasScreenshot()) {
-                if (screenRotationAnimation.setRotationInTransaction(
-                        rotation, mService.mFxSession,
+                if (screenRotationAnimation.setRotationInTransaction(rotation,
                         MAX_ANIMATION_DURATION, mService.getTransitionAnimationScaleLocked(),
                         mDisplayInfo.logicalWidth, mDisplayInfo.logicalHeight)) {
                     mService.scheduleAnimationLocked();
@@ -1201,6 +1227,8 @@
             mCompatibleScreenScale = CompatibilityInfo.computeCompatibleScaling(mDisplayMetrics,
                     mCompatDisplayMetrics);
         }
+
+        updateBounds();
         return mDisplayInfo;
     }
 
@@ -1519,8 +1547,17 @@
         // See {@link PhoneWindowManager#setInitialDisplaySize}...sigh...
         mService.reconfigureDisplayLocked(this);
 
-        getDockedDividerController().onConfigurationChanged();
-        getPinnedStackController().onConfigurationChanged();
+        final DockedStackDividerController dividerController = getDockedDividerController();
+
+        if (dividerController != null) {
+            getDockedDividerController().onConfigurationChanged();
+        }
+
+        final PinnedStackController pinnedStackController = getPinnedStackController();
+
+        if (pinnedStackController != null) {
+            getPinnedStackController().onConfigurationChanged();
+        }
     }
 
     /**
@@ -1659,33 +1696,6 @@
         mInitialDisplayDensity = mDisplayInfo.logicalDensityDpi;
     }
 
-    void getLogicalDisplayRect(Rect out) {
-        // Uses same calculation as in LogicalDisplay#configureDisplayInTransactionLocked.
-        final int orientation = mDisplayInfo.rotation;
-        boolean rotated = (orientation == ROTATION_90 || orientation == ROTATION_270);
-        final int physWidth = rotated ? mBaseDisplayHeight : mBaseDisplayWidth;
-        final int physHeight = rotated ? mBaseDisplayWidth : mBaseDisplayHeight;
-        int width = mDisplayInfo.logicalWidth;
-        int left = (physWidth - width) / 2;
-        int height = mDisplayInfo.logicalHeight;
-        int top = (physHeight - height) / 2;
-        out.set(left, top, left + width, top + height);
-    }
-
-    private void getLogicalDisplayRect(Rect out, int orientation) {
-        getLogicalDisplayRect(out);
-
-        // Rotate the Rect if needed.
-        final int currentRotation = mDisplayInfo.rotation;
-        final int rotationDelta = deltaRotation(currentRotation, orientation);
-        if (rotationDelta == ROTATION_90 || rotationDelta == ROTATION_270) {
-            createRotationMatrix(rotationDelta, mBaseDisplayWidth, mBaseDisplayHeight, mTmpMatrix);
-            mTmpRectF.set(out);
-            mTmpMatrix.mapRect(mTmpRectF);
-            mTmpRectF.round(out);
-        }
-    }
-
     /**
      * If display metrics changed, overrides are not set and it's not just a rotation - update base
      * values.
@@ -1753,6 +1763,8 @@
         }
 
         mBaseDisplayRect.set(0, 0, mBaseDisplayWidth, mBaseDisplayHeight);
+
+        updateBounds();
     }
 
     void getContentRect(Rect out) {
@@ -1907,22 +1919,6 @@
         }
     }
 
-    boolean animateDimLayers() {
-        return mDimLayerController.animateDimLayers();
-    }
-
-    private void resetDimming() {
-        mDimLayerController.resetDimming();
-    }
-
-    boolean isDimming() {
-        return mDimLayerController.isDimming();
-    }
-
-    private void stopDimmingIfNeeded() {
-        mDimLayerController.stopDimmingIfNeeded();
-    }
-
     @Override
     void removeIfPossible() {
         if (isAnimating()) {
@@ -1938,7 +1934,6 @@
         try {
             super.removeImmediately();
             if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Removing display=" + this);
-            mDimLayerController.close();
             if (mService.canDispatchPointerEvents()) {
                 if (mTapDetector != null) {
                     mService.unregisterPointerEventListener(mTapDetector);
@@ -1947,6 +1942,9 @@
                     mService.unregisterPointerEventListener(mService.mMousePositionTracker);
                 }
             }
+            // The pending transaction won't be applied so we should
+            // just clean up any surfaces pending destruction.
+            onPendingTransactionApplied();
         } finally {
             mRemovingDisplay = false;
         }
@@ -2096,7 +2094,7 @@
     }
 
     void rotateBounds(int oldRotation, int newRotation, Rect bounds) {
-        getLogicalDisplayRect(mTmpRect, newRotation);
+        getBounds(mTmpRect, newRotation);
 
         // Compute a transform matrix to undo the coordinate space transformation,
         // and present the window at the same physical position it previously occupied.
@@ -2228,8 +2226,7 @@
                 token.dump(pw, "    ");
             }
         }
-        pw.println();
-        mDimLayerController.dump(prefix, pw);
+
         pw.println();
 
         // Dump stack references
@@ -2342,10 +2339,16 @@
 
     /** Updates the layer assignment of windows on this display. */
     void assignWindowLayers(boolean setLayoutNeeded) {
-        mLayersController.assignWindowLayers(this);
+        assignChildLayers(getPendingTransaction());
         if (setLayoutNeeded) {
             setLayoutNeeded();
         }
+
+        // We accumlate the layer changes in-to "getPendingTransaction()" but we defer
+        // the application of this transaction until the animation pass triggers
+        // prepareSurfaces. This allows us to synchronize Z-ordering changes with
+        // the hiding and showing of surfaces.
+        scheduleAnimation();
     }
 
     // TODO: This should probably be called any time a visual change is made to the hierarchy like
@@ -2701,10 +2704,6 @@
         }
     }
 
-    void prepareWindowSurfaces() {
-        forAllWindows(mPrepareWindowSurfaces, false /* traverseTopToBottom */);
-    }
-
     boolean inputMethodClientHasFocus(IInputMethodClient client) {
         final WindowState imFocus = computeImeTarget(false /* updateImeTarget */);
         if (imFocus == null) {
@@ -2846,7 +2845,6 @@
         } while (pendingLayoutChanges != 0);
 
         mTmpApplySurfaceChangesTransactionState.reset();
-        resetDimming();
 
         mTmpRecoveringMemory = recoveringMemory;
         forAllWindows(mApplySurfaceChangesTransaction, true /* traverseTopToBottom */);
@@ -2857,8 +2855,6 @@
                 mTmpApplySurfaceChangesTransactionState.preferredModeId,
                 true /* inTraversal, must call performTraversalInTrans... below */);
 
-        stopDimmingIfNeeded();
-
         final boolean wallpaperVisible = mWallpaperController.isWallpaperVisible();
         if (wallpaperVisible != mLastWallpaperVisible) {
             mLastWallpaperVisible = wallpaperVisible;
@@ -2875,6 +2871,44 @@
         return mTmpApplySurfaceChangesTransactionState.focusDisplayed;
     }
 
+    private void updateBounds() {
+        calculateBounds(mTmpBounds);
+        setBounds(mTmpBounds);
+    }
+
+    // Determines the current display bounds based on the current state
+    private void calculateBounds(Rect out) {
+        // Uses same calculation as in LogicalDisplay#configureDisplayInTransactionLocked.
+        final int orientation = mDisplayInfo.rotation;
+        boolean rotated = (orientation == ROTATION_90 || orientation == ROTATION_270);
+        final int physWidth = rotated ? mBaseDisplayHeight : mBaseDisplayWidth;
+        final int physHeight = rotated ? mBaseDisplayWidth : mBaseDisplayHeight;
+        int width = mDisplayInfo.logicalWidth;
+        int left = (physWidth - width) / 2;
+        int height = mDisplayInfo.logicalHeight;
+        int top = (physHeight - height) / 2;
+        out.set(left, top, left + width, top + height);
+    }
+
+    @Override
+    public void getBounds(Rect out) {
+        calculateBounds(out);
+    }
+
+    private void getBounds(Rect out, int orientation) {
+        getBounds(out);
+
+        // Rotate the Rect if needed.
+        final int currentRotation = mDisplayInfo.rotation;
+        final int rotationDelta = deltaRotation(currentRotation, orientation);
+        if (rotationDelta == ROTATION_90 || rotationDelta == ROTATION_270) {
+            createRotationMatrix(rotationDelta, mBaseDisplayWidth, mBaseDisplayHeight, mTmpMatrix);
+            mTmpRectF.set(out);
+            mTmpMatrix.mapRect(mTmpRectF);
+            mTmpRectF.round(out);
+        }
+    }
+
     void performLayout(boolean initial, boolean updateInputWindows) {
         if (!isLayoutNeeded()) {
             return;
@@ -2935,241 +2969,30 @@
      * Takes a snapshot of the display.  In landscape mode this grabs the whole screen.
      * In portrait mode, it grabs the full screenshot.
      *
-     * @param width the width of the target bitmap
-     * @param height the height of the target bitmap
-     * @param includeFullDisplay true if the screen should not be cropped before capture
-     * @param frameScale the scale to apply to the frame, only used when width = -1 and height = -1
      * @param config of the output bitmap
      * @param wallpaperOnly true if only the wallpaper layer should be included in the screenshot
-     * @param includeDecor whether to include window decors, like the status or navigation bar
-     *                     background of the window
      */
-    Bitmap screenshotApplications(IBinder appToken, int width, int height,
-            boolean includeFullDisplay, float frameScale, Bitmap.Config config,
-            boolean wallpaperOnly, boolean includeDecor) {
-        Bitmap bitmap = screenshotApplications(appToken, width, height, includeFullDisplay,
-                frameScale, wallpaperOnly, includeDecor, SurfaceControl::screenshot);
-        if (bitmap == null) {
-            return null;
-        }
-
-        if (DEBUG_SCREENSHOT) {
-            // TEST IF IT's ALL BLACK
-            int[] buffer = new int[bitmap.getWidth() * bitmap.getHeight()];
-            bitmap.getPixels(buffer, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(),
-                    bitmap.getHeight());
-            boolean allBlack = true;
-            final int firstColor = buffer[0];
-            for (int i = 0; i < buffer.length; i++) {
-                if (buffer[i] != firstColor) {
-                    allBlack = false;
-                    break;
-                }
-            }
-            if (allBlack) {
-                final WindowState appWin = mScreenshotApplicationState.appWin;
-                final int maxLayer = mScreenshotApplicationState.maxLayer;
-                final int minLayer = mScreenshotApplicationState.minLayer;
-                Slog.i(TAG_WM, "Screenshot " + appWin + " was monochrome(" +
-                        Integer.toHexString(firstColor) + ")! mSurfaceLayer=" +
-                        (appWin != null ?
-                                appWin.mWinAnimator.mSurfaceController.getLayer() : "null") +
-                        " minLayer=" + minLayer + " maxLayer=" + maxLayer);
-            }
-        }
-
-        // Create a copy of the screenshot that is immutable and backed in ashmem.
-        // This greatly reduces the overhead of passing the bitmap between processes.
-        Bitmap ret = bitmap.createAshmemBitmap(config);
-        bitmap.recycle();
-        return ret;
-    }
-
-    GraphicBuffer screenshotApplicationsToBuffer(IBinder appToken, int width, int height,
-            boolean includeFullDisplay, float frameScale, boolean wallpaperOnly,
-            boolean includeDecor) {
-        return screenshotApplications(appToken, width, height, includeFullDisplay, frameScale,
-                wallpaperOnly, includeDecor, SurfaceControl::screenshotToBuffer);
-    }
-
-    private <E> E screenshotApplications(IBinder appToken, int width, int height,
-            boolean includeFullDisplay, float frameScale, boolean wallpaperOnly,
-            boolean includeDecor, Screenshoter<E> screenshoter) {
-        int dw = mDisplayInfo.logicalWidth;
-        int dh = mDisplayInfo.logicalHeight;
-        if (dw == 0 || dh == 0) {
-            if (DEBUG_SCREENSHOT) Slog.i(TAG_WM, "Screenshot of " + appToken
-                    + ": returning null. logical widthxheight=" + dw + "x" + dh);
-            return null;
-        }
-
-        E bitmap;
-
-        mScreenshotApplicationState.reset(appToken == null && !wallpaperOnly);
-        final Rect frame = new Rect();
-        final Rect stackBounds = new Rect();
-
-        final int aboveAppLayer = (mService.mPolicy.getWindowLayerFromTypeLw(TYPE_APPLICATION) + 1)
-                * TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;
-        final MutableBoolean mutableIncludeFullDisplay = new MutableBoolean(includeFullDisplay);
-        synchronized(mService.mWindowMap) {
+    Bitmap screenshotDisplay(Bitmap.Config config, boolean wallpaperOnly) {
+        synchronized (mService.mWindowMap) {
             if (!mService.mPolicy.isScreenOn()) {
-                if (DEBUG_SCREENSHOT) Slog.i(TAG_WM, "Attempted to take screenshot while display"
-                        + " was off.");
-                return null;
-            }
-            // Figure out the part of the screen that is actually the app.
-            mScreenshotApplicationState.appWin = null;
-            forAllWindows(w -> {
-                if (!w.mHasSurface) {
-                    return false;
+                if (DEBUG_SCREENSHOT) {
+                    Slog.i(TAG_WM, "Attempted to take screenshot while display was off.");
                 }
-                if (w.mLayer >= aboveAppLayer) {
-                    return false;
-                }
-                if (wallpaperOnly && !w.mIsWallpaper) {
-                    return false;
-                }
-                if (w.mIsImWindow) {
-                    return false;
-                } else if (w.mIsWallpaper) {
-                    // If this is the wallpaper layer and we're only looking for the wallpaper layer
-                    // then the target window state is this one.
-                    if (wallpaperOnly) {
-                        mScreenshotApplicationState.appWin = w;
-                    }
-
-                    if (mScreenshotApplicationState.appWin == null) {
-                        // We have not ran across the target window yet, so it is probably behind
-                        // the wallpaper. This can happen when the keyguard is up and all windows
-                        // are moved behind the wallpaper. We don't want to include the wallpaper
-                        // layer in the screenshot as it will cover-up the layer of the target
-                        // window.
-                        return false;
-                    }
-                    // Fall through. The target window is in front of the wallpaper. For this
-                    // case we want to include the wallpaper layer in the screenshot because
-                    // the target window might have some transparent areas.
-                } else if (appToken != null) {
-                    if (w.mAppToken == null || w.mAppToken.token != appToken) {
-                        // This app window is of no interest if it is not associated with the
-                        // screenshot app.
-                        return false;
-                    }
-                    mScreenshotApplicationState.appWin = w;
-                }
-
-                // Include this window.
-
-                final WindowStateAnimator winAnim = w.mWinAnimator;
-                int layer = winAnim.mSurfaceController.getLayer();
-                if (mScreenshotApplicationState.maxLayer < layer) {
-                    mScreenshotApplicationState.maxLayer = layer;
-                }
-                if (mScreenshotApplicationState.minLayer > layer) {
-                    mScreenshotApplicationState.minLayer = layer;
-                }
-
-                // Don't include wallpaper in bounds calculation
-                if (!w.mIsWallpaper && !mutableIncludeFullDisplay.value) {
-                    if (includeDecor) {
-                        final Task task = w.getTask();
-                        if (task != null) {
-                            task.getBounds(frame);
-                        } else {
-
-                            // No task bounds? Too bad! Ain't no screenshot then.
-                            return true;
-                        }
-                    } else {
-                        final Rect wf = w.mFrame;
-                        final Rect cr = w.mContentInsets;
-                        int left = wf.left + cr.left;
-                        int top = wf.top + cr.top;
-                        int right = wf.right - cr.right;
-                        int bottom = wf.bottom - cr.bottom;
-                        frame.union(left, top, right, bottom);
-                        w.getVisibleBounds(stackBounds);
-                        if (!Rect.intersects(frame, stackBounds)) {
-                            // Set frame empty if there's no intersection.
-                            frame.setEmpty();
-                        }
-                    }
-                }
-
-                final boolean foundTargetWs =
-                        (w.mAppToken != null && w.mAppToken.token == appToken)
-                                || (mScreenshotApplicationState.appWin != null && wallpaperOnly);
-                if (foundTargetWs && winAnim.getShown() && winAnim.mLastAlpha > 0f) {
-                    mScreenshotApplicationState.screenshotReady = true;
-                }
-
-                if (w.isObscuringDisplay()){
-                    return true;
-                }
-                return false;
-            }, true /* traverseTopToBottom */);
-
-            final WindowState appWin = mScreenshotApplicationState.appWin;
-            final boolean screenshotReady = mScreenshotApplicationState.screenshotReady;
-            final int maxLayer = mScreenshotApplicationState.maxLayer;
-            final int minLayer = mScreenshotApplicationState.minLayer;
-
-            if (appToken != null && appWin == null) {
-                // Can't find a window to snapshot.
-                if (DEBUG_SCREENSHOT) Slog.i(TAG_WM,
-                        "Screenshot: Couldn't find a surface matching " + appToken);
                 return null;
             }
 
-            if (!screenshotReady) {
-                Slog.i(TAG_WM, "Failed to capture screenshot of " + appToken +
-                        " appWin=" + (appWin == null ? "null" : (appWin + " drawState=" +
-                        appWin.mWinAnimator.mDrawState)));
+            if (wallpaperOnly && !shouldScreenshotWallpaper()) {
                 return null;
             }
 
-            // Screenshot is ready to be taken. Everything from here below will continue
-            // through the bottom of the loop and return a value. We only stay in the loop
-            // because we don't want to release the mWindowMap lock until the screenshot is
-            // taken.
+            int dw = mDisplayInfo.logicalWidth;
+            int dh = mDisplayInfo.logicalHeight;
 
-            if (maxLayer == 0) {
-                if (DEBUG_SCREENSHOT) Slog.i(TAG_WM, "Screenshot of " + appToken
-                        + ": returning null maxLayer=" + maxLayer);
+            if (dw <= 0 || dh <= 0) {
                 return null;
             }
 
-            if (!mutableIncludeFullDisplay.value) {
-                // Constrain frame to the screen size.
-                if (!frame.intersect(0, 0, dw, dh)) {
-                    frame.setEmpty();
-                }
-            } else {
-                // Caller just wants entire display.
-                frame.set(0, 0, dw, dh);
-            }
-            if (frame.isEmpty()) {
-                return null;
-            }
-
-            if (width < 0) {
-                width = (int) (frame.width() * frameScale);
-            }
-            if (height < 0) {
-                height = (int) (frame.height() * frameScale);
-            }
-
-            // Tell surface flinger what part of the image to crop. Take the top
-            // right part of the application, and crop the larger dimension to fit.
-            Rect crop = new Rect(frame);
-            if (width / (float) frame.width() < height / (float) frame.height()) {
-                int cropWidth = (int)((float)width / (float)height * frame.height());
-                crop.right = crop.left + cropWidth;
-            } else {
-                int cropHeight = (int)((float)height / (float)width * frame.width());
-                crop.bottom = crop.top + cropHeight;
-            }
+            final Rect frame = new Rect(0, 0, dw, dh);
 
             // The screenshot API does not apply the current screen rotation.
             int rot = mDisplay.getRotation();
@@ -3178,43 +3001,52 @@
                 rot = (rot == ROTATION_90) ? ROTATION_270 : ROTATION_90;
             }
 
-            // Surfaceflinger is not aware of orientation, so convert our logical
-            // crop to surfaceflinger's portrait orientation.
-            convertCropForSurfaceFlinger(crop, rot, dw, dh);
-
-            if (DEBUG_SCREENSHOT) {
-                Slog.i(TAG_WM, "Screenshot: " + dw + "x" + dh + " from " + minLayer + " to "
-                        + maxLayer + " appToken=" + appToken);
-                forAllWindows(w -> {
-                    final WindowSurfaceController controller = w.mWinAnimator.mSurfaceController;
-                    Slog.i(TAG_WM, w + ": " + w.mLayer
-                            + " animLayer=" + w.mWinAnimator.mAnimLayer
-                            + " surfaceLayer=" + ((controller == null)
-                            ? "null" : controller.getLayer()));
-                }, false /* traverseTopToBottom */);
-            }
+            // SurfaceFlinger is not aware of orientation, so convert our logical
+            // crop to SurfaceFlinger's portrait orientation.
+            convertCropForSurfaceFlinger(frame, rot, dw, dh);
 
             final ScreenRotationAnimation screenRotationAnimation =
                     mService.mAnimator.getScreenRotationAnimationLocked(DEFAULT_DISPLAY);
             final boolean inRotation = screenRotationAnimation != null &&
                     screenRotationAnimation.isAnimating();
-            if (DEBUG_SCREENSHOT && inRotation) Slog.v(TAG_WM,
-                    "Taking screenshot while rotating");
+            if (DEBUG_SCREENSHOT && inRotation) Slog.v(TAG_WM, "Taking screenshot while rotating");
 
-            // We force pending transactions to flush before taking
-            // the screenshot by pushing an empty synchronous transaction.
-            SurfaceControl.openTransaction();
-            SurfaceControl.closeTransactionSync();
-
-            bitmap = screenshoter.screenshot(crop, width, height, minLayer, maxLayer,
-                    inRotation, rot);
+            // TODO(b/68392460): We should screenshot Task controls directly
+            // but it's difficult at the moment as the Task doesn't have the
+            // correct size set.
+            final Bitmap bitmap = SurfaceControl.screenshot(frame, dw, dh, 0, 1, inRotation, rot);
             if (bitmap == null) {
-                Slog.w(TAG_WM, "Screenshot failure taking screenshot for (" + dw + "x" + dh
-                        + ") to layer " + maxLayer);
+                Slog.w(TAG_WM, "Failed to take screenshot");
                 return null;
             }
+
+            // Create a copy of the screenshot that is immutable and backed in ashmem.
+            // This greatly reduces the overhead of passing the bitmap between processes.
+            final Bitmap ret = bitmap.createAshmemBitmap(config);
+            bitmap.recycle();
+            return ret;
         }
-        return bitmap;
+    }
+
+    private boolean shouldScreenshotWallpaper() {
+        MutableBoolean screenshotReady = new MutableBoolean(false);
+
+        forAllWindows(w -> {
+            if (!w.mIsWallpaper) {
+                return false;
+            }
+
+            // Found the wallpaper window
+            final WindowStateAnimator winAnim = w.mWinAnimator;
+
+            if (winAnim.getShown() && winAnim.mLastAlpha > 0f) {
+                screenshotReady.value = true;
+            }
+
+            return true;
+        }, true /* traverseTopToBottom */);
+
+        return screenshotReady.value;
     }
 
     // TODO: Can this use createRotationMatrix()?
@@ -3366,6 +3198,10 @@
      * I.e Activities.
      */
     private final class TaskStackContainers extends DisplayChildWindowContainer<TaskStack> {
+        /**
+         * A control placed at the appropriate level for transitions to occur.
+         */
+        SurfaceControl mAnimationLayer = null;
 
         // Cached reference to some special stacks we tend to get a lot so we don't need to loop
         // through the list to find them.
@@ -3677,13 +3513,85 @@
             // to prevent freezing/unfreezing the display too early.
             return mLastOrientation;
         }
+
+        @Override
+        void assignChildLayers(SurfaceControl.Transaction t) {
+            final int NORMAL_STACK_STATE = 0;
+            final int BOOSTED_STATE = 1;
+            final int ALWAYS_ON_TOP_STATE = 2;
+
+            // We allow stacks to change visual order from the AM specified order due to
+            // Z-boosting during animations. However we must take care to ensure TaskStacks
+            // which are marked as alwaysOnTop remain that way.
+            int layer = 0;
+            for (int state = 0; state <= ALWAYS_ON_TOP_STATE; state++) {
+                for (int i = 0; i < mChildren.size(); i++) {
+                    final TaskStack s = mChildren.get(i);
+                    layer++;
+                    if (state == NORMAL_STACK_STATE) {
+                        s.assignLayer(t, layer);
+                    } else if (state == BOOSTED_STATE && s.needsZBoost()) {
+                        s.assignLayer(t, layer);
+                    } else if (state == ALWAYS_ON_TOP_STATE &&
+                            s.isAlwaysOnTop()) {
+                        s.assignLayer(t, layer);
+                    }
+                    s.assignChildLayers(t);
+                }
+                // The appropriate place for App-Transitions to occur is right
+                // above all other animations but still below things in the Picture-and-Picture
+                // windowing mode.
+                if (state == BOOSTED_STATE && mAnimationLayer != null) {
+                    t.setLayer(mAnimationLayer, layer + 1);
+                }
+            }
+        }
+
+        @Override
+        void onParentSet() {
+            super.onParentSet();
+            if (getParent() != null) {
+                mAnimationLayer = makeSurface().build();
+            } else {
+                mAnimationLayer.destroy();
+                mAnimationLayer = null;
+            }
+        }
+    }
+
+    private final class AboveAppWindowContainers extends NonAppWindowContainers {
+        AboveAppWindowContainers(String name) {
+            super(name);
+        }
+
+        void assignChildLayers(SurfaceControl.Transaction t, WindowContainer imeContainer) {
+            boolean needAssignIme = imeContainer != null
+                    && imeContainer.getSurfaceControl() != null;
+            for (int j = 0; j < mChildren.size(); ++j) {
+                final WindowToken wt = mChildren.get(j);
+                wt.assignLayer(t, j);
+                wt.assignChildLayers(t);
+
+                int layer = mService.mPolicy.getWindowLayerFromTypeLw(
+                        wt.windowType, wt.mOwnerCanManageAppTokens);
+                if (needAssignIme && layer >= TYPE_INPUT_METHOD_DIALOG) {
+                    t.setRelativeLayer(imeContainer.getSurfaceControl(),
+                            wt.getSurfaceControl(), -1);
+                    needAssignIme = false;
+                }
+            }
+            if (needAssignIme) {
+                t.setRelativeLayer(imeContainer.getSurfaceControl(),
+                        getSurfaceControl(), Integer.MIN_VALUE);
+            }
+        }
     }
 
     /**
      * Window container class that contains all containers on this display that are not related to
      * Apps. E.g. status bar.
      */
-    private final class NonAppWindowContainers extends DisplayChildWindowContainer<WindowToken> {
+    private class NonAppWindowContainers extends DisplayChildWindowContainer<WindowToken> {
         /**
          * Compares two child window tokens returns -1 if the first is lesser than the second in
          * terms of z-order and 1 otherwise.
@@ -3752,12 +3660,124 @@
         }
     }
 
+    SurfaceControl.Builder makeSurface(SurfaceSession s) {
+        return mService.makeSurfaceBuilder(s)
+                .setParent(mWindowingLayer);
+    }
+
+    @Override
+    SurfaceSession getSession() {
+        return mSession;
+    }
+
+    @Override
+    SurfaceControl.Builder makeChildSurface(WindowContainer child) {
+        SurfaceSession s = child != null ? child.getSession() : getSession();
+        final SurfaceControl.Builder b = mService.makeSurfaceBuilder(s);
+        b.setSize(mSurfaceSize, mSurfaceSize);
+
+        if (child == null) {
+            return b;
+        }
+
+        return b.setName(child.getName())
+                .setParent(mWindowingLayer);
+    }
+
     /**
-     * Interface to screenshot into various types, i.e. {@link Bitmap} and {@link GraphicBuffer}.
+     * The makeSurface variants are for use by the window-container
+     * hierarchy. makeOverlay here is a function for various non windowing
+     * overlays like the ScreenRotation screenshot, the Strict Mode Flash
+     * and other potpourii.
      */
-    @FunctionalInterface
-    private interface Screenshoter<E> {
-        E screenshot(Rect sourceCrop, int width, int height, int minLayer, int maxLayer,
-                boolean useIdentityTransform, int rotation);
+    SurfaceControl.Builder makeOverlay() {
+        return mService.makeSurfaceBuilder(mSession)
+            .setParent(mOverlayLayer);
+    }
+
+    void applyMagnificationSpec(MagnificationSpec spec) {
+        applyMagnificationSpec(getPendingTransaction(), spec);
+        getPendingTransaction().apply();
+    }
+
+    @Override
+    void onParentSet() {
+        // Since we are the top of the SurfaceControl hierarchy here
+        // we create the root surfaces explicitly rather than chaining
+        // up as the default implementation in onParentSet does. So we
+        // explicitly do NOT call super here.
+    }
+
+    @Override
+    void assignChildLayers(SurfaceControl.Transaction t) {
+        t.setLayer(mOverlayLayer, 1)
+                .setLayer(mWindowingLayer, 0);
+
+        // These are layers as children of "mWindowingLayer"
+        mBelowAppWindowsContainers.assignLayer(t, 0);
+        mTaskStackContainers.assignLayer(t, 1);
+        mAboveAppWindowsContainers.assignLayer(t, 2);
+
+        WindowState imeTarget = mService.mInputMethodTarget;
+        boolean needAssignIme = true;
+
+        // In the case where we have an IME target that is not in split-screen
+        // mode IME assignment is easy. We just need the IME to go directly above
+        // the target. This way children of the target will naturally go above the IME
+        // and everyone is happy.
+        //
+        // In the case of split-screen windowing mode, we need to elevate the IME above the
+        // docked divider while keeping the app itself below the docked divider, so instead
+        // we use relative layering of the IME targets child windows, and place the
+        // IME in the non-app layer (see {@link AboveAppWindowContainers#assignChildLayers}).
+        //
+        // In the case where we have no IME target we assign it where it's base layer would
+        // place it in the AboveAppWindowContainers.
+        if (imeTarget != null && !imeTarget.inSplitScreenWindowingMode()
+                && (imeTarget.getSurfaceControl() != null)) {
+            t.setRelativeLayer(mImeWindowsContainers.getSurfaceControl(),
+                    imeTarget.getSurfaceControl(),
+                    // TODO: We need to use an extra level on the app surface to ensure
+                    // this is always above SurfaceView but always below attached window.
+                    1);
+            needAssignIme = false;
+        }
+
+        // Above we have assigned layers to our children, now we ask them to assign
+        // layers to their children.
+        mBelowAppWindowsContainers.assignChildLayers(t);
+        mTaskStackContainers.assignChildLayers(t);
+        mAboveAppWindowsContainers.assignChildLayers(t,
+                needAssignIme == true ? mImeWindowsContainers : null);
+        mImeWindowsContainers.assignChildLayers(t);
+    }
+
+    /**
+     * Here we satisfy an unfortunate special case of the IME in split-screen mode. Imagine
+     * that the IME target is one of the docked applications. We'd like the docked divider to be
+     * above both of the applications, and we'd like the IME to be above the docked divider.
+     * However we need child windows of the applications to be above the IME (Text drag handles).
+     * This is a non-strictly hierarcical layering and we need to break out of the Z ordering
+     * somehow. We do this by relatively ordering children of the target to the IME in cooperation
+     * with {@link #WindowState#assignLayer}
+     */
+    void assignRelativeLayerForImeTargetChild(SurfaceControl.Transaction t, WindowContainer child) {
+        t.setRelativeLayer(child.getSurfaceControl(), mImeWindowsContainers.getSurfaceControl(), 1);
+    }
+
+    @Override
+    void destroyAfterPendingTransaction(SurfaceControl surface) {
+        mPendingDestroyingSurfaces.add(surface);
+    }
+
+    /**
+     * Destroys any surfaces that have been put into the pending list with
+     * {@link #destroyAfterTransaction}.
+     */
+    void onPendingTransactionApplied() {
+        for (int i = mPendingDestroyingSurfaces.size() - 1; i >= 0; i--) {
+            mPendingDestroyingSurfaces.get(i).destroy();
+        }
+        mPendingDestroyingSurfaces.clear();
     }
 }
diff --git a/com/android/server/wm/DisplayFrames.java b/com/android/server/wm/DisplayFrames.java
new file mode 100644
index 0000000..0249713
--- /dev/null
+++ b/com/android/server/wm/DisplayFrames.java
@@ -0,0 +1,190 @@
+/*
+ * 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.wm;
+
+import static android.view.Surface.ROTATION_180;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
+import static com.android.server.wm.proto.DisplayFramesProto.STABLE_BOUNDS;
+
+import android.graphics.Rect;
+import android.util.proto.ProtoOutputStream;
+import android.view.DisplayInfo;
+
+import java.io.PrintWriter;
+
+/**
+ * Container class for all the display frames that affect how we do window layout on a display.
+ * @hide
+ */
+public class DisplayFrames {
+    public final int mDisplayId;
+
+    /**
+     * The current size of the screen; really; extends into the overscan area of the screen and
+     * doesn't account for any system elements like the status bar.
+     */
+    public final Rect mOverscan = new Rect();
+
+    /**
+     * The current visible size of the screen; really; (ir)regardless of whether the status bar can
+     * be hidden but not extending into the overscan area.
+     */
+    public final Rect mUnrestricted = new Rect();
+
+    /** Like mOverscan*, but allowed to move into the overscan region where appropriate. */
+    public final Rect mRestrictedOverscan = new Rect();
+
+    /**
+     * The current size of the screen; these may be different than (0,0)-(dw,dh) if the status bar
+     * can't be hidden; in that case it effectively carves out that area of the display from all
+     * other windows.
+     */
+    public final Rect mRestricted = new Rect();
+
+    /**
+     * During layout, the current screen borders accounting for any currently visible system UI
+     * elements.
+     */
+    public final Rect mSystem = new Rect();
+
+    /** For applications requesting stable content insets, these are them. */
+    public final Rect mStable = new Rect();
+
+    /**
+     * For applications requesting stable content insets but have also set the fullscreen window
+     * flag, these are the stable dimensions without the status bar.
+     */
+    public final Rect mStableFullscreen = new Rect();
+
+    /**
+     * During layout, the current screen borders with all outer decoration (status bar, input method
+     * dock) accounted for.
+     */
+    public final Rect mCurrent = new Rect();
+
+    /**
+     * During layout, the frame in which content should be displayed to the user, accounting for all
+     * screen decoration except for any space they deem as available for other content. This is
+     * usually the same as mCurrent*, but may be larger if the screen decor has supplied content
+     * insets.
+     */
+    public final Rect mContent = new Rect();
+
+    /**
+     * During layout, the frame in which voice content should be displayed to the user, accounting
+     * for all screen decoration except for any space they deem as available for other content.
+     */
+    public final Rect mVoiceContent = new Rect();
+
+    /** During layout, the current screen borders along which input method windows are placed. */
+    public final Rect mDock = new Rect();
+
+    private final Rect mDisplayInfoOverscan = new Rect();
+    private final Rect mRotatedDisplayInfoOverscan = new Rect();
+    public int mDisplayWidth;
+    public int mDisplayHeight;
+
+    public int mRotation;
+
+    public DisplayFrames(int displayId, DisplayInfo info) {
+        mDisplayId = displayId;
+        onDisplayInfoUpdated(info);
+    }
+
+    public void onDisplayInfoUpdated(DisplayInfo info) {
+        mDisplayWidth = info.logicalWidth;
+        mDisplayHeight = info.logicalHeight;
+        mRotation = info.rotation;
+        mDisplayInfoOverscan.set(
+                info.overscanLeft, info.overscanTop, info.overscanRight, info.overscanBottom);
+    }
+
+    public void onBeginLayout() {
+        switch (mRotation) {
+            case ROTATION_90:
+                mRotatedDisplayInfoOverscan.left = mDisplayInfoOverscan.top;
+                mRotatedDisplayInfoOverscan.top = mDisplayInfoOverscan.right;
+                mRotatedDisplayInfoOverscan.right = mDisplayInfoOverscan.bottom;
+                mRotatedDisplayInfoOverscan.bottom = mDisplayInfoOverscan.left;
+                break;
+            case ROTATION_180:
+                mRotatedDisplayInfoOverscan.left = mDisplayInfoOverscan.right;
+                mRotatedDisplayInfoOverscan.top = mDisplayInfoOverscan.bottom;
+                mRotatedDisplayInfoOverscan.right = mDisplayInfoOverscan.left;
+                mRotatedDisplayInfoOverscan.bottom = mDisplayInfoOverscan.top;
+                break;
+            case ROTATION_270:
+                mRotatedDisplayInfoOverscan.left = mDisplayInfoOverscan.bottom;
+                mRotatedDisplayInfoOverscan.top = mDisplayInfoOverscan.left;
+                mRotatedDisplayInfoOverscan.right = mDisplayInfoOverscan.top;
+                mRotatedDisplayInfoOverscan.bottom = mDisplayInfoOverscan.right;
+                break;
+            default:
+                mRotatedDisplayInfoOverscan.set(mDisplayInfoOverscan);
+                break;
+        }
+
+        mRestrictedOverscan.set(0, 0, mDisplayWidth, mDisplayHeight);
+        mOverscan.set(mRestrictedOverscan);
+        mSystem.set(mRestrictedOverscan);
+        mUnrestricted.set(mRotatedDisplayInfoOverscan);
+        mUnrestricted.right = mDisplayWidth - mUnrestricted.right;
+        mUnrestricted.bottom = mDisplayHeight - mUnrestricted.bottom;
+        mRestricted.set(mUnrestricted);
+        mDock.set(mUnrestricted);
+        mContent.set(mUnrestricted);
+        mVoiceContent.set(mUnrestricted);
+        mStable.set(mUnrestricted);
+        mStableFullscreen.set(mUnrestricted);
+        mCurrent.set(mUnrestricted);
+
+    }
+
+    public int getInputMethodWindowVisibleHeight() {
+        return mDock.bottom - mCurrent.bottom;
+    }
+
+    public void writeToProto(ProtoOutputStream proto, long fieldId) {
+        final long token = proto.start(fieldId);
+        mStable.writeToProto(proto, STABLE_BOUNDS);
+        proto.end(token);
+    }
+
+    public void dump(String prefix, PrintWriter pw) {
+        pw.println(prefix + "DisplayFrames w=" + mDisplayWidth + " h=" + mDisplayHeight
+                + " r=" + mRotation);
+        final String myPrefix = prefix + "  ";
+        dumpFrame(mStable, "mStable", myPrefix, pw);
+        dumpFrame(mStableFullscreen, "mStableFullscreen", myPrefix, pw);
+        dumpFrame(mDock, "mDock", myPrefix, pw);
+        dumpFrame(mCurrent, "mCurrent", myPrefix, pw);
+        dumpFrame(mSystem, "mSystem", myPrefix, pw);
+        dumpFrame(mContent, "mContent", myPrefix, pw);
+        dumpFrame(mVoiceContent, "mVoiceContent", myPrefix, pw);
+        dumpFrame(mOverscan, "mOverscan", myPrefix, pw);
+        dumpFrame(mRestrictedOverscan, "mRestrictedOverscan", myPrefix, pw);
+        dumpFrame(mRestricted, "mRestricted", myPrefix, pw);
+        dumpFrame(mUnrestricted, "mUnrestricted", myPrefix, pw);
+        dumpFrame(mDisplayInfoOverscan, "mDisplayInfoOverscan", myPrefix, pw);
+        dumpFrame(mRotatedDisplayInfoOverscan, "mRotatedDisplayInfoOverscan", myPrefix, pw);
+    }
+
+    private void dumpFrame(Rect frame, String name, String prefix, PrintWriter pw) {
+        pw.print(prefix + name + "="); frame.printShortString(pw); pw.println();
+    }
+}
diff --git a/com/android/server/wm/DockedStackDividerController.java b/com/android/server/wm/DockedStackDividerController.java
index d79ba89..a37598e 100644
--- a/com/android/server/wm/DockedStackDividerController.java
+++ b/com/android/server/wm/DockedStackDividerController.java
@@ -54,7 +54,6 @@
 import com.android.internal.policy.DividerSnapAlgorithm;
 import com.android.internal.policy.DockedDividerUtils;
 import com.android.server.LocalServices;
-import com.android.server.wm.DimLayer.DimLayerUser;
 import com.android.server.wm.WindowManagerService.H;
 
 import java.io.PrintWriter;
@@ -62,7 +61,7 @@
 /**
  * Keeps information about the docked stack divider.
  */
-public class DockedStackDividerController implements DimLayerUser {
+public class DockedStackDividerController {
 
     private static final String TAG = TAG_WITH_CLASS_NAME ? "DockedStackDividerController" : TAG_WM;
 
@@ -114,7 +113,6 @@
     private boolean mLastVisibility = false;
     private final RemoteCallbackList<IDockedStackListener> mDockedStackListeners
             = new RemoteCallbackList<>();
-    private final DimLayer mDimLayer;
 
     private boolean mMinimizedDock;
     private int mOriginalDockedSide = DOCKED_INVALID;
@@ -141,13 +139,12 @@
     private boolean mImeHideRequested;
     private final Rect mLastDimLayerRect = new Rect();
     private float mLastDimLayerAlpha;
+    private TaskStack mDimmedStack;
 
     DockedStackDividerController(WindowManagerService service, DisplayContent displayContent) {
         mService = service;
         mDisplayContent = displayContent;
         final Context context = service.mContext;
-        mDimLayer = new DimLayer(displayContent.mService, this, displayContent.getDisplayId(),
-                "DockedStackDim");
         mMinimizedDockInterpolator = AnimationUtils.loadInterpolator(
                 context, android.R.interpolator.fast_out_slow_in);
         loadDimens();
@@ -463,6 +460,11 @@
         }
         mOriginalDockedSide = DOCKED_INVALID;
         setMinimizedDockedStack(false /* minimizedDock */, false /* animate */);
+
+        if (mDimmedStack != null) {
+            mDimmedStack.stopDimming();
+            mDimmedStack = null;
+        }
     }
 
     /**
@@ -564,34 +566,12 @@
         final TaskStack dockedStack = mDisplayContent.getSplitScreenPrimaryStack();
         boolean visibleAndValid = visible && stack != null && dockedStack != null;
         if (visibleAndValid) {
-            stack.getDimBounds(mTmpRect);
-            if (mTmpRect.height() > 0 && mTmpRect.width() > 0) {
-                if (!mLastDimLayerRect.equals(mTmpRect) || mLastDimLayerAlpha != alpha) {
-                    try {
-                        // TODO: This should use the regular animation transaction - here and below
-                        mService.openSurfaceTransaction();
-                        mDimLayer.setBounds(mTmpRect);
-                        mDimLayer.show(getResizeDimLayer(), alpha, 0 /* duration */);
-                    } finally {
-                        mService.closeSurfaceTransaction("setResizeDimLayer");
-                    }
-                }
-                mLastDimLayerRect.set(mTmpRect);
-                mLastDimLayerAlpha = alpha;
-            } else {
-                visibleAndValid = false;
-            }
+            mDimmedStack = stack;
+            stack.dim(alpha);
         }
-        if (!visibleAndValid) {
-            if (mLastDimLayerAlpha != 0f) {
-                try {
-                    mService.openSurfaceTransaction();
-                    mDimLayer.hide();
-                } finally {
-                    mService.closeSurfaceTransaction("setResizeDimLayer");
-                }
-            }
-            mLastDimLayerAlpha = 0f;
+        if (!visibleAndValid && stack != null) {
+            mDimmedStack = null;
+            stack.stopDimming();
         }
     }
 
@@ -675,8 +655,8 @@
     }
 
     private boolean isWithinDisplay(Task task) {
-        task.mStack.getBounds(mTmpRect);
-        mDisplayContent.getLogicalDisplayRect(mTmpRect2);
+        task.getBounds(mTmpRect);
+        mDisplayContent.getBounds(mTmpRect2);
         return mTmpRect.intersect(mTmpRect2);
     }
 
@@ -829,12 +809,8 @@
             return animateForMinimizedDockedStack(now);
         } else if (mAnimatingForIme) {
             return animateForIme(now);
-        } else {
-            if (mDimLayer != null && mDimLayer.isDimming()) {
-                mDimLayer.setLayer(getResizeDimLayer());
-            }
-            return false;
         }
+        return false;
     }
 
     private boolean animateForIme(long now) {
@@ -942,27 +918,6 @@
                 + (1 - t) * (CLIP_REVEAL_MEET_LAST - CLIP_REVEAL_MEET_EARLIEST);
     }
 
-    @Override
-    public boolean dimFullscreen() {
-        return false;
-    }
-
-    @Override
-    public DisplayInfo getDisplayInfo() {
-        return mDisplayContent.getDisplayInfo();
-    }
-
-    @Override
-    public boolean isAttachedToDisplay() {
-        return mDisplayContent != null;
-    }
-
-    @Override
-    public void getDimBounds(Rect outBounds) {
-        // This dim layer user doesn't need this.
-    }
-
-    @Override
     public String toShortString() {
         return TAG;
     }
@@ -977,10 +932,6 @@
         pw.println(prefix + "  mMinimizedDock=" + mMinimizedDock);
         pw.println(prefix + "  mAdjustedForIme=" + mAdjustedForIme);
         pw.println(prefix + "  mAdjustedForDivider=" + mAdjustedForDivider);
-        if (mDimLayer.isDimming()) {
-            pw.println(prefix + "  Dim layer is dimming: ");
-            mDimLayer.printTo(prefix + "    ", pw);
-        }
     }
 
     void writeToProto(ProtoOutputStream proto, long fieldId) {
diff --git a/com/android/server/wm/DragDropController.java b/com/android/server/wm/DragDropController.java
index 4567e10..65951dc 100644
--- a/com/android/server/wm/DragDropController.java
+++ b/com/android/server/wm/DragDropController.java
@@ -36,9 +36,10 @@
 import android.view.SurfaceControl;
 import android.view.SurfaceSession;
 import android.view.View;
-import android.view.WindowManagerInternal.IDragDropCallback;
+
 import com.android.internal.util.Preconditions;
 import com.android.server.input.InputWindowHandle;
+import com.android.server.wm.WindowManagerInternal.IDragDropCallback;
 
 /**
  * Managing drag and drop operations initiated by View#startDragAndDrop.
diff --git a/com/android/server/wm/DragState.java b/com/android/server/wm/DragState.java
index e81d366..112e62f 100644
--- a/com/android/server/wm/DragState.java
+++ b/com/android/server/wm/DragState.java
@@ -645,15 +645,15 @@
             try (final SurfaceControl.Transaction transaction = new SurfaceControl.Transaction()) {
                 transaction.setPosition(
                         mSurfaceControl,
-                        (float) mAnimator.getAnimatedValue(ANIMATED_PROPERTY_X),
-                        (float) mAnimator.getAnimatedValue(ANIMATED_PROPERTY_Y));
+                        (float) animation.getAnimatedValue(ANIMATED_PROPERTY_X),
+                        (float) animation.getAnimatedValue(ANIMATED_PROPERTY_Y));
                 transaction.setAlpha(
                         mSurfaceControl,
-                        (float) mAnimator.getAnimatedValue(ANIMATED_PROPERTY_ALPHA));
+                        (float) animation.getAnimatedValue(ANIMATED_PROPERTY_ALPHA));
                 transaction.setMatrix(
                         mSurfaceControl,
-                        (float) mAnimator.getAnimatedValue(ANIMATED_PROPERTY_SCALE), 0,
-                        0, (float) mAnimator.getAnimatedValue(ANIMATED_PROPERTY_SCALE));
+                        (float) animation.getAnimatedValue(ANIMATED_PROPERTY_SCALE), 0,
+                        0, (float) animation.getAnimatedValue(ANIMATED_PROPERTY_SCALE));
                 transaction.apply();
             }
         }
diff --git a/com/android/server/wm/EmulatorDisplayOverlay.java b/com/android/server/wm/EmulatorDisplayOverlay.java
index 8bec8d7..fddf6ca 100644
--- a/com/android/server/wm/EmulatorDisplayOverlay.java
+++ b/com/android/server/wm/EmulatorDisplayOverlay.java
@@ -49,19 +49,19 @@
     private int mRotation;
     private boolean mVisible;
 
-    public EmulatorDisplayOverlay(Context context, Display display, SurfaceSession session,
+    public EmulatorDisplayOverlay(Context context, DisplayContent dc,
             int zOrder) {
+        final Display display = dc.getDisplay();
         mScreenSize = new Point();
         display.getSize(mScreenSize);
 
         SurfaceControl ctrl = null;
         try {
-            ctrl = new SurfaceControl.Builder(session)
+            ctrl = dc.makeOverlay()
                     .setName("EmulatorDisplayOverlay")
                     .setSize(mScreenSize.x, mScreenSize.y)
                     .setFormat(PixelFormat.TRANSLUCENT)
                     .build();
-            ctrl.setLayerStack(display.getLayerStack());
             ctrl.setLayer(zOrder);
             ctrl.setPosition(0, 0);
             ctrl.show();
diff --git a/com/android/server/wm/InputMonitor.java b/com/android/server/wm/InputMonitor.java
index a766097..7e29a3a 100644
--- a/com/android/server/wm/InputMonitor.java
+++ b/com/android/server/wm/InputMonitor.java
@@ -47,11 +47,11 @@
 import android.view.InputEventReceiver;
 import android.view.KeyEvent;
 import android.view.WindowManager;
-import android.view.WindowManagerPolicy;
 
 import com.android.server.input.InputApplicationHandle;
 import com.android.server.input.InputManagerService;
 import com.android.server.input.InputWindowHandle;
+import com.android.server.policy.WindowManagerPolicy;
 
 import java.io.PrintWriter;
 import java.util.Arrays;
@@ -661,8 +661,8 @@
             if (w.inPinnedWindowingMode()) {
                 if (mAddPipInputConsumerHandle
                         && (inputWindowHandle.layer <= pipInputConsumer.mWindowHandle.layer)) {
-                    // Update the bounds of the Pip input consumer to match the Pinned stack
-                    w.getStack().getBounds(pipTouchableBounds);
+                    // Update the bounds of the Pip input consumer to match the window bounds.
+                    w.getBounds(pipTouchableBounds);
                     pipInputConsumer.mWindowHandle.touchableRegion.set(pipTouchableBounds);
                     addInputWindowHandle(pipInputConsumer.mWindowHandle);
                     mAddPipInputConsumerHandle = false;
diff --git a/com/android/server/wm/KeyguardDisableHandler.java b/com/android/server/wm/KeyguardDisableHandler.java
index 2eb186b..4a20f1a 100644
--- a/com/android/server/wm/KeyguardDisableHandler.java
+++ b/com/android/server/wm/KeyguardDisableHandler.java
@@ -29,7 +29,8 @@
 import android.os.TokenWatcher;
 import android.util.Log;
 import android.util.Pair;
-import android.view.WindowManagerPolicy;
+
+import com.android.server.policy.WindowManagerPolicy;
 
 public class KeyguardDisableHandler extends Handler {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "KeyguardDisableHandler" : TAG_WM;
diff --git a/com/android/server/wm/PinnedStackWindowController.java b/com/android/server/wm/PinnedStackWindowController.java
index 41f076d..b021a72 100644
--- a/com/android/server/wm/PinnedStackWindowController.java
+++ b/com/android/server/wm/PinnedStackWindowController.java
@@ -106,7 +106,7 @@
                 } else {
                     // Otherwise, use the display bounds
                     toBounds = new Rect();
-                    mContainer.getDisplayContent().getLogicalDisplayRect(toBounds);
+                    mContainer.getDisplayContent().getBounds(toBounds);
                 }
             } else if (fromFullscreen) {
                 schedulePipModeChangedState = SCHEDULE_PIP_MODE_CHANGED_ON_END;
diff --git a/com/android/server/wm/PointerEventDispatcher.java b/com/android/server/wm/PointerEventDispatcher.java
index 484987e..ab8b8d4 100644
--- a/com/android/server/wm/PointerEventDispatcher.java
+++ b/com/android/server/wm/PointerEventDispatcher.java
@@ -21,7 +21,7 @@
 import android.view.InputEvent;
 import android.view.InputEventReceiver;
 import android.view.MotionEvent;
-import android.view.WindowManagerPolicy.PointerEventListener;
+import android.view.WindowManagerPolicyConstants.PointerEventListener;
 
 import com.android.server.UiThread;
 
diff --git a/com/android/server/wm/RootWindowContainer.java b/com/android/server/wm/RootWindowContainer.java
index f541926..4008811 100644
--- a/com/android/server/wm/RootWindowContainer.java
+++ b/com/android/server/wm/RootWindowContainer.java
@@ -59,10 +59,10 @@
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE;
 import static android.view.WindowManager.LayoutParams.TYPE_DREAM;
 import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG;
-import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
-import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
-import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
 
+import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM;
+import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
+import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DISPLAY;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_KEEP_SCREEN_ON;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT_REPEATS;
@@ -138,7 +138,6 @@
     ParcelFileDescriptor mSurfaceTraceFd;
     RemoteEventTrace mRemoteEventTrace;
 
-    private final WindowLayersController mLayersController;
     final WallpaperController mWallpaperController;
 
     private final Handler mHandler;
@@ -163,7 +162,6 @@
     RootWindowContainer(WindowManagerService service) {
         mService = service;
         mHandler = new MyHandler(service.mH.getLooper());
-        mLayersController = new WindowLayersController(mService);
         mWallpaperController = new WallpaperController(mService);
     }
 
@@ -231,7 +229,7 @@
     }
 
     private DisplayContent createDisplayContent(final Display display) {
-        final DisplayContent dc = new DisplayContent(display, mService, mLayersController,
+        final DisplayContent dc = new DisplayContent(display, mService,
                 mWallpaperController);
         final int displayId = display.getDisplayId();
 
@@ -1103,4 +1101,9 @@
     String getName() {
         return "ROOT";
     }
+
+    @Override
+    void scheduleAnimation() {
+        mService.scheduleAnimationLocked();
+    }
 }
diff --git a/com/android/server/wm/ScreenRotationAnimation.java b/com/android/server/wm/ScreenRotationAnimation.java
index 3350fea..5a39de5 100644
--- a/com/android/server/wm/ScreenRotationAnimation.java
+++ b/com/android/server/wm/ScreenRotationAnimation.java
@@ -28,7 +28,6 @@
 
 import android.content.Context;
 import android.graphics.Matrix;
-import android.graphics.PixelFormat;
 import android.graphics.Rect;
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
@@ -225,12 +224,12 @@
     }
 
     public ScreenRotationAnimation(Context context, DisplayContent displayContent,
-            SurfaceSession session, boolean inTransaction, boolean forceDefaultOrientation,
+            boolean inTransaction, boolean forceDefaultOrientation,
             boolean isSecure, WindowManagerService service) {
         mService = service;
         mContext = context;
         mDisplayContent = displayContent;
-        displayContent.getLogicalDisplayRect(mOriginalDisplayRect);
+        displayContent.getBounds(mOriginalDisplayRect);
 
         // Screenshot does NOT include rotation!
         final Display display = displayContent.getDisplay();
@@ -269,7 +268,7 @@
 
         try {
             try {
-                mSurfaceControl = new SurfaceControl.Builder(session)
+                mSurfaceControl = displayContent.makeOverlay()
                         .setName("ScreenshotSurface")
                         .setSize(mWidth, mHeight)
                         .setSecure(isSecure)
@@ -281,7 +280,6 @@
                 // TODO(multidisplay): we should use the proper display
                 SurfaceControl.screenshot(SurfaceControl.getBuiltInDisplay(
                         SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN), sur);
-                mSurfaceControl.setLayerStack(display.getLayerStack());
                 mSurfaceControl.setLayer(SCREEN_FREEZE_LAYER_SCREENSHOT);
                 mSurfaceControl.setAlpha(0);
                 mSurfaceControl.show();
@@ -313,7 +311,7 @@
             float x = mTmpFloats[Matrix.MTRANS_X];
             float y = mTmpFloats[Matrix.MTRANS_Y];
             if (mForceDefaultOrientation) {
-                mDisplayContent.getLogicalDisplayRect(mCurrentDisplayRect);
+                mDisplayContent.getBounds(mCurrentDisplayRect);
                 x -= mCurrentDisplayRect.left;
                 y -= mCurrentDisplayRect.top;
             }
@@ -370,11 +368,11 @@
     }
 
     // Must be called while in a transaction.
-    public boolean setRotationInTransaction(int rotation, SurfaceSession session,
+    public boolean setRotationInTransaction(int rotation,
             long maxAnimationDuration, float animationScale, int finalWidth, int finalHeight) {
         setRotationInTransaction(rotation);
         if (TWO_PHASE_ANIMATION) {
-            return startAnimation(session, maxAnimationDuration, animationScale,
+            return startAnimation(maxAnimationDuration, animationScale,
                     finalWidth, finalHeight, false, 0, 0);
         }
 
@@ -385,7 +383,7 @@
     /**
      * Returns true if animating.
      */
-    private boolean startAnimation(SurfaceSession session, long maxAnimationDuration,
+    private boolean startAnimation(long maxAnimationDuration,
             float animationScale, int finalWidth, int finalHeight, boolean dismissing,
             int exitAnim, int enterAnim) {
         if (mSurfaceControl == null) {
@@ -561,8 +559,8 @@
                 Rect outer = new Rect(-mOriginalWidth*1, -mOriginalHeight*1,
                         mOriginalWidth*2, mOriginalHeight*2);
                 Rect inner = new Rect(0, 0, mOriginalWidth, mOriginalHeight);
-                mCustomBlackFrame = new BlackFrame(session, outer, inner,
-                        SCREEN_FREEZE_LAYER_CUSTOM, layerStack, false);
+                mCustomBlackFrame = new BlackFrame(outer, inner,
+                        SCREEN_FREEZE_LAYER_CUSTOM, mDisplayContent, false);
                 mCustomBlackFrame.setMatrix(mFrameInitialMatrix);
             } catch (OutOfResourcesException e) {
                 Slog.w(TAG, "Unable to allocate black surface", e);
@@ -601,8 +599,8 @@
                             mOriginalWidth*2, mOriginalHeight*2);
                     inner = new Rect(0, 0, mOriginalWidth, mOriginalHeight);
                 }
-                mExitingBlackFrame = new BlackFrame(session, outer, inner,
-                        SCREEN_FREEZE_LAYER_EXIT, layerStack, mForceDefaultOrientation);
+                mExitingBlackFrame = new BlackFrame(outer, inner,
+                        SCREEN_FREEZE_LAYER_EXIT, mDisplayContent, mForceDefaultOrientation);
                 mExitingBlackFrame.setMatrix(mFrameInitialMatrix);
             } catch (OutOfResourcesException e) {
                 Slog.w(TAG, "Unable to allocate black surface", e);
@@ -624,8 +622,8 @@
                 Rect outer = new Rect(-finalWidth*1, -finalHeight*1,
                         finalWidth*2, finalHeight*2);
                 Rect inner = new Rect(0, 0, finalWidth, finalHeight);
-                mEnteringBlackFrame = new BlackFrame(session, outer, inner,
-                        SCREEN_FREEZE_LAYER_ENTER, layerStack, false);
+                mEnteringBlackFrame = new BlackFrame(outer, inner,
+                        SCREEN_FREEZE_LAYER_ENTER, mDisplayContent, false);
             } catch (OutOfResourcesException e) {
                 Slog.w(TAG, "Unable to allocate black surface", e);
             } finally {
@@ -642,7 +640,7 @@
     /**
      * Returns true if animating.
      */
-    public boolean dismiss(SurfaceSession session, long maxAnimationDuration,
+    public boolean dismiss(long maxAnimationDuration,
             float animationScale, int finalWidth, int finalHeight, int exitAnim, int enterAnim) {
         if (DEBUG_STATE) Slog.v(TAG, "Dismiss!");
         if (mSurfaceControl == null) {
@@ -650,7 +648,7 @@
             return false;
         }
         if (!mStarted) {
-            startAnimation(session, maxAnimationDuration, animationScale, finalWidth, finalHeight,
+            startAnimation(maxAnimationDuration, animationScale, finalWidth, finalHeight,
                     true, exitAnim, enterAnim);
         }
         if (!mStarted) {
diff --git a/com/android/server/wm/SnapshotStartingData.java b/com/android/server/wm/SnapshotStartingData.java
index 35f35db..c9e43c5 100644
--- a/com/android/server/wm/SnapshotStartingData.java
+++ b/com/android/server/wm/SnapshotStartingData.java
@@ -17,8 +17,8 @@
 package com.android.server.wm;
 
 import android.app.ActivityManager.TaskSnapshot;
-import android.graphics.GraphicBuffer;
-import android.view.WindowManagerPolicy.StartingSurface;
+
+import com.android.server.policy.WindowManagerPolicy.StartingSurface;
 
 /**
  * Represents starting data for snapshot starting windows.
diff --git a/com/android/server/wm/SplashScreenStartingData.java b/com/android/server/wm/SplashScreenStartingData.java
index 4b14f86..f52ce38 100644
--- a/com/android/server/wm/SplashScreenStartingData.java
+++ b/com/android/server/wm/SplashScreenStartingData.java
@@ -18,7 +18,8 @@
 
 import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
-import android.view.WindowManagerPolicy.StartingSurface;
+
+import com.android.server.policy.WindowManagerPolicy.StartingSurface;
 
 /**
  * Represents starting data for splash screens, i.e. "traditional" starting windows.
diff --git a/com/android/server/wm/StackWindowController.java b/com/android/server/wm/StackWindowController.java
index 95c1d53..e7547bf 100644
--- a/com/android/server/wm/StackWindowController.java
+++ b/com/android/server/wm/StackWindowController.java
@@ -87,12 +87,6 @@
         }
     }
 
-    public boolean isVisible() {
-        synchronized (mWindowMap) {
-            return mContainer != null && mContainer.isVisible();
-        }
-    }
-
     public void reparent(int displayId, Rect outStackBounds, boolean onTop) {
         synchronized (mWindowMap) {
             if (mContainer == null) {
@@ -111,8 +105,7 @@
         }
     }
 
-    public void positionChildAt(TaskWindowContainerController child, int position, Rect bounds,
-            Configuration overrideConfig) {
+    public void positionChildAt(TaskWindowContainerController child, int position) {
         synchronized (mWindowMap) {
             if (DEBUG_STACK) Slog.i(TAG_WM, "positionChildAt: positioning task=" + child
                     + " at " + position);
@@ -126,7 +119,7 @@
                         "positionChildAt: could not find stack for task=" + mContainer);
                 return;
             }
-            child.mContainer.positionAt(position, bounds, overrideConfig);
+            child.mContainer.positionAt(position);
             mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
         }
     }
@@ -178,24 +171,22 @@
      * Re-sizes a stack and its containing tasks.
      *
      * @param bounds New stack bounds. Passing in null sets the bounds to fullscreen.
-     * @param configs Configurations for tasks in the resized stack, keyed by task id.
      * @param taskBounds Bounds for tasks in the resized stack, keyed by task id.
-     * @return True if the stack is now fullscreen.
+     * @param taskTempInsetBounds Inset bounds for individual tasks, keyed by task id.
      */
-    public boolean resize(Rect bounds, SparseArray<Configuration> configs,
-            SparseArray<Rect> taskBounds, SparseArray<Rect> taskTempInsetBounds) {
+    public void resize(Rect bounds, SparseArray<Rect> taskBounds,
+            SparseArray<Rect> taskTempInsetBounds) {
         synchronized (mWindowMap) {
             if (mContainer == null) {
                 throw new IllegalArgumentException("resizeStack: stack " + this + " not found.");
             }
             // We might trigger a configuration change. Save the current task bounds for freezing.
             mContainer.prepareFreezingTaskBounds();
-            if (mContainer.setBounds(bounds, configs, taskBounds, taskTempInsetBounds)
+            if (mContainer.setBounds(bounds, taskBounds, taskTempInsetBounds)
                     && mContainer.isVisible()) {
                 mContainer.getDisplayContent().setLayoutNeeded();
                 mService.mWindowPlacerLocked.performSurfacePlacement();
             }
-            return mContainer.getRawFullscreen();
         }
     }
 
@@ -227,7 +218,7 @@
 
     public void getRawBounds(Rect outBounds) {
         synchronized (mWindowMap) {
-            if (mContainer.getRawFullscreen()) {
+            if (mContainer.matchParentBounds()) {
                 outBounds.setEmpty();
             } else {
                 mContainer.getRawBounds(outBounds);
@@ -275,6 +266,7 @@
 
             final Rect parentAppBounds = parentConfig.windowConfiguration.getAppBounds();
 
+            config.windowConfiguration.setBounds(bounds);
             config.windowConfiguration.setAppBounds(!bounds.isEmpty() ? bounds : null);
             boolean intersectParentBounds = false;
 
diff --git a/com/android/server/wm/StartingData.java b/com/android/server/wm/StartingData.java
index 8c564bb..eb5011f 100644
--- a/com/android/server/wm/StartingData.java
+++ b/com/android/server/wm/StartingData.java
@@ -16,7 +16,7 @@
 
 package com.android.server.wm;
 
-import android.view.WindowManagerPolicy.StartingSurface;
+import com.android.server.policy.WindowManagerPolicy.StartingSurface;
 
 /**
  * Represents the model about how a starting window should be constructed.
diff --git a/com/android/server/wm/StrictModeFlash.java b/com/android/server/wm/StrictModeFlash.java
index eb8ee69..f51a6a9 100644
--- a/com/android/server/wm/StrictModeFlash.java
+++ b/com/android/server/wm/StrictModeFlash.java
@@ -41,15 +41,14 @@
     private boolean mDrawNeeded;
     private final int mThickness = 20;
 
-    public StrictModeFlash(Display display, SurfaceSession session) {
+    public StrictModeFlash(DisplayContent dc) {
         SurfaceControl ctrl = null;
         try {
-            ctrl = new SurfaceControl.Builder(session)
+            ctrl = dc.makeOverlay()
                     .setName("StrictModeFlash")
                     .setSize(1, 1)
                     .setFormat(PixelFormat.TRANSLUCENT)
                     .build();
-            ctrl.setLayerStack(display.getLayerStack());
             ctrl.setLayer(WindowManagerService.TYPE_LAYER_MULTIPLIER * 101);  // one more than Watermark? arbitrary.
             ctrl.setPosition(0, 0);
             ctrl.show();
diff --git a/com/android/server/wm/SurfaceBuilderFactory.java b/com/android/server/wm/SurfaceBuilderFactory.java
new file mode 100644
index 0000000..5390e5a
--- /dev/null
+++ b/com/android/server/wm/SurfaceBuilderFactory.java
@@ -0,0 +1,25 @@
+/*
+ * 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.wm;
+
+import android.view.SurfaceSession;
+import android.view.SurfaceControl;
+
+interface SurfaceBuilderFactory {
+    SurfaceControl.Builder make(SurfaceSession s);
+};
+
diff --git a/com/android/server/wm/SurfaceControlWithBackground.java b/com/android/server/wm/SurfaceControlWithBackground.java
index a5080d5..7c5bd43 100644
--- a/com/android/server/wm/SurfaceControlWithBackground.java
+++ b/com/android/server/wm/SurfaceControlWithBackground.java
@@ -16,7 +16,13 @@
 
 package com.android.server.wm;
 
-import android.graphics.PixelFormat;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
+import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+
+import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_BOTTOM;
+import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_LEFT;
+import static com.android.server.policy.WindowManagerPolicy.NAV_BAR_RIGHT;
+
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.os.IBinder;
@@ -24,13 +30,6 @@
 import android.view.Surface;
 import android.view.Surface.OutOfResourcesException;
 import android.view.SurfaceControl;
-import android.view.SurfaceSession;
-
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
-import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
-import static android.view.WindowManagerPolicy.NAV_BAR_BOTTOM;
-import static android.view.WindowManagerPolicy.NAV_BAR_LEFT;
-import static android.view.WindowManagerPolicy.NAV_BAR_RIGHT;
 
 /**
  * SurfaceControl extension that has black background behind navigation bar area for fullscreen
diff --git a/com/android/server/wm/Task.java b/com/android/server/wm/Task.java
index 13435d7..8aa129a 100644
--- a/com/android/server/wm/Task.java
+++ b/com/android/server/wm/Task.java
@@ -51,14 +51,8 @@
 import java.io.PrintWriter;
 import java.util.function.Consumer;
 
-class Task extends WindowContainer<AppWindowToken> implements DimLayer.DimLayerUser {
+class Task extends WindowContainer<AppWindowToken> {
     static final String TAG = TAG_WITH_CLASS_NAME ? "Task" : TAG_WM;
-    // Return value from {@link setBounds} indicating no change was made to the Task bounds.
-    private static final int BOUNDS_CHANGE_NONE = 0;
-    // Return value from {@link setBounds} indicating the position of the Task bounds changed.
-    private static final int BOUNDS_CHANGE_POSITION = 1;
-    // Return value from {@link setBounds} indicating the size of the Task bounds changed.
-    private static final int BOUNDS_CHANGE_SIZE = 1 << 1;
 
     // TODO: Track parent marks like this in WindowContainer.
     TaskStack mStack;
@@ -67,8 +61,6 @@
     private boolean mDeferRemoval = false;
     final WindowManagerService mService;
 
-    // Content limits relative to the DisplayContent this sits in.
-    private Rect mBounds = new Rect();
     final Rect mPreparedFrozenBounds = new Rect();
     final Configuration mPreparedFrozenMergedConfig = new Configuration();
 
@@ -78,13 +70,12 @@
     // Device rotation as of the last time {@link #mBounds} was set.
     private int mRotation;
 
-    // Whether mBounds is fullscreen
-    private boolean mFillsParent = true;
-
     // For comparison with DisplayContent bounds.
     private Rect mTmpRect = new Rect();
     // For handling display rotations.
     private Rect mTmpRect2 = new Rect();
+    // For retrieving dim bounds
+    private Rect mTmpRect3 = new Rect();
 
     // Resize mode of the task. See {@link ActivityInfo#resizeMode}
     private int mResizeMode;
@@ -105,8 +96,11 @@
     // stack moves and we in fact do so when moving from full screen to pinned.
     private boolean mPreserveNonFloatingState = false;
 
-    Task(int taskId, TaskStack stack, int userId, WindowManagerService service, Rect bounds,
-            int resizeMode, boolean supportsPictureInPicture, TaskDescription taskDescription,
+    private Dimmer mDimmer = new Dimmer(this);
+    private final Rect mTmpDimBoundsRect = new Rect();
+
+    Task(int taskId, TaskStack stack, int userId, WindowManagerService service, int resizeMode,
+            boolean supportsPictureInPicture, TaskDescription taskDescription,
             TaskWindowContainerController controller) {
         mTaskId = taskId;
         mStack = stack;
@@ -115,7 +109,7 @@
         mResizeMode = resizeMode;
         mSupportsPictureInPicture = supportsPictureInPicture;
         setController(controller);
-        setBounds(bounds, getOverrideConfiguration());
+        setBounds(getOverrideBounds());
         mTaskDescription = taskDescription;
 
         // Tasks have no set orientation value (including SCREEN_ORIENTATION_UNSPECIFIED).
@@ -188,12 +182,6 @@
         EventLog.writeEvent(WM_TASK_REMOVED, mTaskId, "removeTask");
         mDeferRemoval = false;
 
-        // Make sure to remove dim layer user first before removing task its from parent.
-        DisplayContent content = getDisplayContent();
-        if (content != null) {
-            content.mDimLayerController.removeDimLayerUser(this);
-        }
-
         super.removeImmediately();
     }
 
@@ -230,13 +218,14 @@
     }
 
     /** @see com.android.server.am.ActivityManagerService#positionTaskInStack(int, int, int). */
-    void positionAt(int position, Rect bounds, Configuration overrideConfig) {
+    void positionAt(int position) {
         mStack.positionChildAt(position, this, false /* includingParents */);
-        resizeLocked(bounds, overrideConfig, false /* force */);
     }
 
     @Override
     void onParentSet() {
+        super.onParentSet();
+
         // Update task bounds if needed.
         updateDisplayInfo(getDisplayContent());
 
@@ -272,50 +261,37 @@
         }
     }
 
-    /** Set the task bounds. Passing in null sets the bounds to fullscreen. */
-    // TODO: There is probably not a need to pass in overrideConfig anymore since any change to it
-    // will be automatically propagated from the AM. Also, mBound is going to be in
-    // WindowConfiguration long term.
-    private int setBounds(Rect bounds, Configuration overrideConfig) {
-        if (overrideConfig == null) {
-            overrideConfig = EMPTY;
+    public int setBounds(Rect bounds, boolean forceResize) {
+        final int boundsChanged = setBounds(bounds);
+
+        if (forceResize && (boundsChanged & BOUNDS_CHANGE_SIZE) != BOUNDS_CHANGE_SIZE) {
+            onResize();
+            return BOUNDS_CHANGE_SIZE | boundsChanged;
         }
 
-        boolean oldFullscreen = mFillsParent;
+        return boundsChanged;
+    }
+
+    /** Set the task bounds. Passing in null sets the bounds to fullscreen. */
+    @Override
+    public int setBounds(Rect bounds) {
         int rotation = Surface.ROTATION_0;
         final DisplayContent displayContent = mStack.getDisplayContent();
         if (displayContent != null) {
-            displayContent.getLogicalDisplayRect(mTmpRect);
             rotation = displayContent.getDisplayInfo().rotation;
-            mFillsParent = bounds == null;
-            if (mFillsParent) {
-                bounds = mTmpRect;
-            }
-        }
-
-        if (bounds == null) {
+        } else if (bounds == null) {
             // Can't set to fullscreen if we don't have a display to get bounds from...
             return BOUNDS_CHANGE_NONE;
         }
-        if (mBounds.equals(bounds) && oldFullscreen == mFillsParent && mRotation == rotation) {
+
+        if (equivalentOverrideBounds(bounds)) {
             return BOUNDS_CHANGE_NONE;
         }
 
-        int boundsChange = BOUNDS_CHANGE_NONE;
-        if (mBounds.left != bounds.left || mBounds.top != bounds.top) {
-            boundsChange |= BOUNDS_CHANGE_POSITION;
-        }
-        if (mBounds.width() != bounds.width() || mBounds.height() != bounds.height()) {
-            boundsChange |= BOUNDS_CHANGE_SIZE;
-        }
-
-        mBounds.set(bounds);
+        final int boundsChange = super.setBounds(bounds);
 
         mRotation = rotation;
-        if (displayContent != null) {
-            displayContent.mDimLayerController.updateDimLayer(this);
-        }
-        onOverrideConfigurationChanged(overrideConfig);
+
         return boundsChange;
     }
 
@@ -363,28 +339,12 @@
         return isResizeable();
     }
 
-    boolean resizeLocked(Rect bounds, Configuration overrideConfig, boolean forced) {
-        int boundsChanged = setBounds(bounds, overrideConfig);
-        if (forced) {
-            boundsChanged |= BOUNDS_CHANGE_SIZE;
-        }
-        if (boundsChanged == BOUNDS_CHANGE_NONE) {
-            return false;
-        }
-        if ((boundsChanged & BOUNDS_CHANGE_SIZE) == BOUNDS_CHANGE_SIZE) {
-            onResize();
-        } else {
-            onMovedByResize();
-        }
-        return true;
-    }
-
     /**
      * Prepares the task bounds to be frozen with the current size. See
      * {@link AppWindowToken#freezeBounds}.
      */
     void prepareFreezingBounds() {
-        mPreparedFrozenBounds.set(mBounds);
+        mPreparedFrozenBounds.set(getBounds());
         mPreparedFrozenMergedConfig.setTo(getConfiguration());
     }
 
@@ -410,30 +370,30 @@
             mTmpRect2.offsetTo(adjustedBounds.left, adjustedBounds.top);
         }
         setTempInsetBounds(tempInsetBounds);
-        resizeLocked(mTmpRect2, getOverrideConfiguration(), false /* forced */);
+        setBounds(mTmpRect2, false /* forced */);
     }
 
     /** Return true if the current bound can get outputted to the rest of the system as-is. */
     private boolean useCurrentBounds() {
         final DisplayContent displayContent = getDisplayContent();
-        return mFillsParent
+        return matchParentBounds()
                 || !inSplitScreenSecondaryWindowingMode()
                 || displayContent == null
                 || displayContent.getSplitScreenPrimaryStackIgnoringVisibility() != null;
     }
 
-    /** Original bounds of the task if applicable, otherwise fullscreen rect. */
-    void getBounds(Rect out) {
+    @Override
+    public void getBounds(Rect out) {
         if (useCurrentBounds()) {
             // No need to adjust the output bounds if fullscreen or the docked stack is visible
             // since it is already what we want to represent to the rest of the system.
-            out.set(mBounds);
+            super.getBounds(out);
             return;
         }
 
         // The bounds has been adjusted to accommodate for a docked stack, but the docked stack is
         // not currently visible. Go ahead a represent it as fullscreen to the rest of the system.
-        mStack.getDisplayContent().getLogicalDisplayRect(out);
+        mStack.getDisplayContent().getBounds(out);
     }
 
     /**
@@ -482,7 +442,6 @@
     }
 
     /** Bounds of the task to be used for dimming, as well as touch related tests. */
-    @Override
     public void getDimBounds(Rect out) {
         final DisplayContent displayContent = mStack.getDisplayContent();
         // It doesn't matter if we in particular are part of the resize, since we couldn't have
@@ -494,7 +453,7 @@
                 return;
             }
 
-            if (!mFillsParent) {
+            if (!matchParentBounds()) {
                 // When minimizing the docked stack when going home, we don't adjust the task bounds
                 // so we need to intersect the task bounds with the stack bounds here.
                 //
@@ -505,11 +464,11 @@
                     mStack.getBounds(out);
                 } else {
                     mStack.getBounds(mTmpRect);
-                    mTmpRect.intersect(mBounds);
+                    mTmpRect.intersect(getBounds());
                 }
                 out.set(mTmpRect);
             } else {
-                out.set(mBounds);
+                out.set(getBounds());
             }
             return;
         }
@@ -517,7 +476,7 @@
         // The bounds has been adjusted to accommodate for a docked stack, but the docked stack is
         // not currently visible. Go ahead a represent it as fullscreen to the rest of the system.
         if (displayContent != null) {
-            displayContent.getLogicalDisplayRect(out);
+            displayContent.getBounds(out);
         }
     }
 
@@ -545,10 +504,10 @@
         if (displayContent == null) {
             return;
         }
-        if (mFillsParent) {
+        if (matchParentBounds()) {
             // TODO: Yeah...not sure if this works with WindowConfiguration, but shouldn't be a
             // problem once we move mBounds into WindowConfiguration.
-            setBounds(null, getOverrideConfiguration());
+            setBounds(null);
             return;
         }
         final int newRotation = displayContent.getDisplayInfo().rotation;
@@ -561,18 +520,18 @@
         //   task bounds so it stays in the same place.
         // - Rotate the bounds and notify activity manager if the task can be resized independently
         //   from its stack. The stack will take care of task rotation for the other case.
-        mTmpRect2.set(mBounds);
+        mTmpRect2.set(getBounds());
 
         if (!getWindowConfiguration().canResizeTask()) {
-            setBounds(mTmpRect2, getOverrideConfiguration());
+            setBounds(mTmpRect2);
             return;
         }
 
         displayContent.rotateBounds(mRotation, newRotation, mTmpRect2);
-        if (setBounds(mTmpRect2, getOverrideConfiguration()) != BOUNDS_CHANGE_NONE) {
+        if (setBounds(mTmpRect2) != BOUNDS_CHANGE_NONE) {
             final TaskWindowContainerController controller = getController();
             if (controller != null) {
-                controller.requestResize(mBounds, RESIZE_MODE_SYSTEM_SCREEN_ROTATION);
+                controller.requestResize(getBounds(), RESIZE_MODE_SYSTEM_SCREEN_ROTATION);
             }
         }
     }
@@ -634,26 +593,9 @@
         return null;
     }
 
-    @Override
-    public boolean dimFullscreen() {
-        return isFullscreen();
-    }
-
-    @Override
-    public int getLayerForDim(WindowStateAnimator animator, int layerOffset, int defaultLayer) {
-        // If the dim layer is for a starting window, move the dim layer back in the z-order behind
-        // the lowest activity window to ensure it does not occlude the main window if it is
-        // translucent
-        final AppWindowToken appToken = animator.mWin.mAppToken;
-        if (animator.mAttrType == TYPE_APPLICATION_STARTING && hasChild(appToken) ) {
-            return Math.min(defaultLayer, appToken.getLowestAnimLayer() - layerOffset);
-        }
-        return defaultLayer;
-    }
-
     boolean isFullscreen() {
         if (useCurrentBounds()) {
-            return mFillsParent;
+            return matchParentBounds();
         }
         // The bounds has been adjusted to accommodate for a docked stack, but the docked stack
         // is not currently visible. Go ahead a represent it as fullscreen to the rest of the
@@ -661,16 +603,6 @@
         return true;
     }
 
-    @Override
-    public DisplayInfo getDisplayInfo() {
-        return getDisplayContent().getDisplayInfo();
-    }
-
-    @Override
-    public boolean isAttachedToDisplay() {
-        return getDisplayContent() != null;
-    }
-
     void forceWindowsScaleable(boolean force) {
         mService.openSurfaceTransaction();
         try {
@@ -692,7 +624,7 @@
 
     @Override
     boolean fillsParent() {
-        return mFillsParent || !getWindowConfiguration().canResizeTask();
+        return matchParentBounds() || !getWindowConfiguration().canResizeTask();
     }
 
     @Override
@@ -718,9 +650,18 @@
         mPreserveNonFloatingState = false;
     }
 
+    Dimmer getDimmer() {
+        return mDimmer;
+    }
+
     @Override
-    public String toShortString() {
-        return "Task=" + mTaskId;
+    void prepareSurfaces() {
+        mDimmer.resetDimStates();
+        super.prepareSurfaces();
+        getDimBounds(mTmpDimBoundsRect);
+        if (mDimmer.updateDims(getPendingTransaction(), mTmpDimBoundsRect)) {
+            scheduleAnimation();
+        }
     }
 
     @CallSuper
@@ -733,8 +674,8 @@
             final AppWindowToken appWindowToken = mChildren.get(i);
             appWindowToken.writeToProto(proto, APP_WINDOW_TOKENS, trim);
         }
-        proto.write(FILLS_PARENT, mFillsParent);
-        mBounds.writeToProto(proto, BOUNDS);
+        proto.write(FILLS_PARENT, matchParentBounds());
+        getBounds().writeToProto(proto, BOUNDS);
         mTempInsetBounds.writeToProto(proto, TEMP_INSET_BOUNDS);
         proto.end(token);
     }
@@ -743,8 +684,7 @@
         final String doublePrefix = prefix + "  ";
 
         pw.println(prefix + "taskId=" + mTaskId);
-        pw.println(doublePrefix + "mFillsParent=" + mFillsParent);
-        pw.println(doublePrefix + "mBounds=" + mBounds.toShortString());
+        pw.println(doublePrefix + "mBounds=" + getBounds().toShortString());
         pw.println(doublePrefix + "mdr=" + mDeferRemoval);
         pw.println(doublePrefix + "appTokens=" + mChildren);
         pw.println(doublePrefix + "mTempInsetBounds=" + mTempInsetBounds.toShortString());
@@ -757,4 +697,8 @@
             wtoken.dump(pw, triplePrefix);
         }
     }
+
+    String toShortString() {
+        return "Task=" + mTaskId;
+    }
 }
diff --git a/com/android/server/wm/TaskPositioner.java b/com/android/server/wm/TaskPositioner.java
index 12f6b5a..87d0a40 100644
--- a/com/android/server/wm/TaskPositioner.java
+++ b/com/android/server/wm/TaskPositioner.java
@@ -59,7 +59,7 @@
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 
-class TaskPositioner implements DimLayer.DimLayerUser {
+class TaskPositioner {
     private static final boolean DEBUG_ORIENTATION_VIOLATIONS = false;
     private static final String TAG_LOCAL = "TaskPositioner";
     private static final String TAG = TAG_WITH_CLASS_NAME ? TAG_LOCAL : TAG_WM;
@@ -99,9 +99,6 @@
     private WindowPositionerEventReceiver mInputEventReceiver;
     private Display mDisplay;
     private final DisplayMetrics mDisplayMetrics = new DisplayMetrics();
-    private DimLayer mDimLayer;
-    @CtrlType
-    private int mCurrentDimSide;
     private Rect mTmpRect = new Rect();
     private int mSideMargin;
     private int mMinVisibleWidth;
@@ -207,15 +204,6 @@
                             mService.mActivityManager.resizeTask(
                                     mTask.mTaskId, mWindowDragBounds, RESIZE_MODE_USER_FORCED);
                         }
-
-                        if (mCurrentDimSide != CTRL_NONE) {
-                            final int createMode = mCurrentDimSide == CTRL_LEFT
-                                    ? SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT
-                                    : SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT;
-                            mService.mActivityManager.setTaskWindowingModeSplitScreenPrimary(
-                                    mTask.mTaskId, createMode, true /*toTop*/, true /* animate */,
-                                    null /* initialBounds */);
-                        }
                     } catch(RemoteException e) {}
 
                     // Post back to WM to handle clean-ups. We still need the input
@@ -243,7 +231,9 @@
     /**
      * @param display The Display that the window being dragged is on.
      */
-    void register(Display display) {
+    void register(DisplayContent displayContent) {
+        final Display display = displayContent.getDisplay();
+
         if (DEBUG_TASK_POSITIONING) {
             Slog.d(TAG, "Registering task positioner");
         }
@@ -305,7 +295,6 @@
         }
         mService.pauseRotationLocked();
 
-        mDimLayer = new DimLayer(mService, this, mDisplay.getDisplayId(), TAG_LOCAL);
         mSideMargin = dipToPixel(SIDE_MARGIN_DIP, mDisplayMetrics);
         mMinVisibleWidth = dipToPixel(MINIMUM_VISIBLE_WIDTH_IN_DP, mDisplayMetrics);
         mMinVisibleHeight = dipToPixel(MINIMUM_VISIBLE_HEIGHT_IN_DP, mDisplayMetrics);
@@ -336,12 +325,6 @@
         mDragWindowHandle = null;
         mDragApplicationHandle = null;
         mDisplay = null;
-
-        if (mDimLayer != null) {
-            mDimLayer.destroySurface();
-            mDimLayer = null;
-        }
-        mCurrentDimSide = CTRL_NONE;
         mDragEnded = true;
 
         // Resume rotations after a drag.
@@ -399,6 +382,27 @@
         mStartOrientationWasLandscape = startBounds.width() >= startBounds.height();
         mWindowOriginalBounds.set(startBounds);
 
+        // Notify the app that resizing has started, even though we haven't received any new
+        // bounds yet. This will guarantee that the app starts the backdrop renderer before
+        // configuration changes which could cause an activity restart.
+        if (mResizing) {
+            synchronized (mService.mWindowMap) {
+                notifyMoveLocked(startX, startY);
+            }
+
+            // Perform the resize on the WMS handler thread when we don't have the WMS lock held
+            // to ensure that we don't deadlock WMS and AMS. Note that WindowPositionerEventReceiver
+            // callbacks are delivered on the same handler so this initial resize is always
+            // guaranteed to happen before subsequent drag resizes.
+            mService.mH.post(() -> {
+                try {
+                    mService.mActivityManager.resizeTask(
+                            mTask.mTaskId, startBounds, RESIZE_MODE_USER_FORCED);
+                } catch (RemoteException e) {
+                }
+            });
+        }
+
         // Make sure we always have valid drag bounds even if the drag ends before any move events
         // have been handled.
         mWindowDragBounds.set(startBounds);
@@ -434,7 +438,6 @@
         }
 
         updateWindowDragBounds(nX, nY, mTmpRect);
-        updateDimLayerVisibility(nX);
         return false;
     }
 
@@ -621,88 +624,6 @@
                 "updateWindowDragBounds: " + mWindowDragBounds);
     }
 
-    private void updateDimLayerVisibility(int x) {
-        @CtrlType
-        int dimSide = getDimSide(x);
-        if (dimSide == mCurrentDimSide) {
-            return;
-        }
-
-        mCurrentDimSide = dimSide;
-
-        if (SHOW_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION updateDimLayerVisibility");
-        mService.openSurfaceTransaction();
-        if (mCurrentDimSide == CTRL_NONE) {
-            mDimLayer.hide();
-        } else {
-            showDimLayer();
-        }
-        mService.closeSurfaceTransaction("updateDimLayerVisibility");
-    }
-
-    /**
-     * Returns the side of the screen the dim layer should be shown.
-     * @param x horizontal coordinate used to determine if the dim layer should be shown
-     * @return Returns {@link #CTRL_LEFT} if the dim layer should be shown on the left half of the
-     * screen, {@link #CTRL_RIGHT} if on the right side, or {@link #CTRL_NONE} if the dim layer
-     * shouldn't be shown.
-     */
-    private int getDimSide(int x) {
-        if (!mTask.mStack.inFreeformWindowingMode()
-                || !mTask.mStack.fillsParent()
-                || mTask.mStack.getConfiguration().orientation != ORIENTATION_LANDSCAPE) {
-            return CTRL_NONE;
-        }
-
-        mTask.mStack.getDimBounds(mTmpRect);
-        if (x - mSideMargin <= mTmpRect.left) {
-            return CTRL_LEFT;
-        }
-        if (x + mSideMargin >= mTmpRect.right) {
-            return CTRL_RIGHT;
-        }
-
-        return CTRL_NONE;
-    }
-
-    private void showDimLayer() {
-        mTask.mStack.getDimBounds(mTmpRect);
-        if (mCurrentDimSide == CTRL_LEFT) {
-            mTmpRect.right = mTmpRect.centerX();
-        } else if (mCurrentDimSide == CTRL_RIGHT) {
-            mTmpRect.left = mTmpRect.centerX();
-        }
-
-        mDimLayer.setBounds(mTmpRect);
-        mDimLayer.show(mService.getDragLayerLocked(), RESIZING_HINT_ALPHA,
-                RESIZING_HINT_DURATION_MS);
-    }
-
-    @Override /** {@link DimLayer.DimLayerUser} */
-    public boolean dimFullscreen() {
-        return isFullscreen();
-    }
-
-    boolean isFullscreen() {
-        return false;
-    }
-
-    @Override /** {@link DimLayer.DimLayerUser} */
-    public DisplayInfo getDisplayInfo() {
-        return mTask.mStack.getDisplayInfo();
-    }
-
-    @Override
-    public boolean isAttachedToDisplay() {
-        return mTask != null && mTask.getDisplayContent() != null;
-    }
-
-    @Override
-    public void getDimBounds(Rect out) {
-        // This dim layer user doesn't need this.
-    }
-
-    @Override
     public String toShortString() {
         return TAG;
     }
diff --git a/com/android/server/wm/TaskSnapshotController.java b/com/android/server/wm/TaskSnapshotController.java
index 54ef065..84e475a 100644
--- a/com/android/server/wm/TaskSnapshotController.java
+++ b/com/android/server/wm/TaskSnapshotController.java
@@ -18,12 +18,12 @@
 
 import static com.android.server.wm.TaskSnapshotPersister.DISABLE_FULL_SIZED_BITMAPS;
 import static com.android.server.wm.TaskSnapshotPersister.REDUCED_SCALE;
+import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_SCREENSHOT;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
 import android.annotation.Nullable;
 import android.app.ActivityManager;
-import android.app.ActivityManager.StackId;
 import android.app.ActivityManager.TaskSnapshot;
 import android.content.pm.PackageManager;
 import android.graphics.Bitmap;
@@ -35,16 +35,17 @@
 import android.util.Slog;
 import android.view.DisplayListCanvas;
 import android.view.RenderNode;
+import android.view.SurfaceControl;
 import android.view.ThreadedRenderer;
 import android.view.WindowManager.LayoutParams;
-import android.view.WindowManagerPolicy.ScreenOffListener;
-import android.view.WindowManagerPolicy.StartingSurface;
-
-import com.google.android.collect.Sets;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.policy.WindowManagerPolicy.ScreenOffListener;
+import com.android.server.policy.WindowManagerPolicy.StartingSurface;
 import com.android.server.wm.TaskSnapshotSurface.SystemBarBackgroundPainter;
 
+import com.google.android.collect.Sets;
+
 import java.io.PrintWriter;
 
 /**
@@ -210,11 +211,28 @@
         if (mainWindow == null) {
             return null;
         }
+        if (!mService.mPolicy.isScreenOn()) {
+            if (DEBUG_SCREENSHOT) {
+                Slog.i(TAG_WM, "Attempted to take screenshot while display was off.");
+            }
+            return null;
+        }
+        if (task.getSurfaceControl() == null) {
+            return null;
+        }
+
         final boolean isLowRamDevice = ActivityManager.isLowRamDeviceStatic();
         final float scaleFraction = isLowRamDevice ? REDUCED_SCALE : 1f;
-        final GraphicBuffer buffer = top.mDisplayContent.screenshotApplicationsToBuffer(top.token,
-                -1, -1, false, scaleFraction, false, true);
+        final Rect taskFrame = new Rect();
+        task.getBounds(taskFrame);
+
+        final GraphicBuffer buffer = SurfaceControl.captureLayersToBuffer(
+                task.getSurfaceControl().getHandle(), taskFrame, scaleFraction);
+
         if (buffer == null || buffer.getWidth() <= 1 || buffer.getHeight() <= 1) {
+            if (DEBUG_SCREENSHOT) {
+                Slog.w(TAG_WM, "Failed to take screenshot");
+            }
             return null;
         }
         return new TaskSnapshot(buffer, top.getConfiguration().orientation,
diff --git a/com/android/server/wm/TaskSnapshotSurface.java b/com/android/server/wm/TaskSnapshotSurface.java
index 3ce090a..41915a3 100644
--- a/com/android/server/wm/TaskSnapshotSurface.java
+++ b/com/android/server/wm/TaskSnapshotSurface.java
@@ -18,9 +18,7 @@
 
 import static android.graphics.Color.WHITE;
 import static android.graphics.Color.alpha;
-import static android.view.SurfaceControl.HIDDEN;
 import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
-import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND;
 import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
 import static android.view.WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
 import static android.view.WindowManager.LayoutParams.FLAG_LOCAL_FOCUS_MODE;
@@ -67,12 +65,12 @@
 import android.view.ViewGroup.LayoutParams;
 import android.view.WindowManager;
 import android.view.WindowManagerGlobal;
-import android.view.WindowManagerPolicy.StartingSurface;
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.policy.DecorView;
 import com.android.internal.view.BaseIWindow;
+import com.android.server.policy.WindowManagerPolicy.StartingSurface;
 
 /**
  * This class represents a starting window that shows a snapshot.
diff --git a/com/android/server/wm/TaskStack.java b/com/android/server/wm/TaskStack.java
index 053fb47..4a3a3fc 100644
--- a/com/android/server/wm/TaskStack.java
+++ b/com/android/server/wm/TaskStack.java
@@ -31,12 +31,11 @@
 import static android.view.WindowManager.DOCKED_LEFT;
 import static android.view.WindowManager.DOCKED_RIGHT;
 import static android.view.WindowManager.DOCKED_TOP;
-import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
+import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
 import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_TASK_MOVEMENT;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.WindowManagerService.LAYER_OFFSET_DIM;
 import static com.android.server.wm.proto.StackProto.ANIMATION_BACKGROUND_SURFACE_IS_DIMMING;
 import static com.android.server.wm.proto.StackProto.BOUNDS;
 import static com.android.server.wm.proto.StackProto.FILLS_PARENT;
@@ -55,6 +54,7 @@
 import android.util.proto.ProtoOutputStream;
 import android.view.DisplayInfo;
 import android.view.Surface;
+import android.view.SurfaceControl;
 
 import com.android.internal.policy.DividerSnapAlgorithm;
 import com.android.internal.policy.DividerSnapAlgorithm.SnapTarget;
@@ -63,7 +63,7 @@
 
 import java.io.PrintWriter;
 
-public class TaskStack extends WindowContainer<Task> implements DimLayer.DimLayerUser,
+public class TaskStack extends WindowContainer<Task> implements
         BoundsAnimationTarget {
     /** Minimum size of an adjusted stack bounds relative to original stack bounds. Used to
      * restrict IME adjustment so that a min portion of top stack remains visible.*/
@@ -87,9 +87,6 @@
     private Rect mTmpRect2 = new Rect();
     private Rect mTmpRect3 = new Rect();
 
-    /** Content limits relative to the DisplayContent this sits in. */
-    private Rect mBounds = new Rect();
-
     /** Stack bounds adjusted to screen content area (taking into account IM windows, etc.) */
     private final Rect mAdjustedBounds = new Rect();
 
@@ -99,17 +96,14 @@
      */
     private final Rect mFullyAdjustedImeBounds = new Rect();
 
-    /** Whether mBounds is fullscreen */
-    private boolean mFillsParent = true;
-
     // Device rotation as of the last time {@link #mBounds} was set.
     private int mRotation;
 
     /** Density as of last time {@link #mBounds} was set. */
     private int mDensity;
 
-    /** Support for non-zero {@link android.view.animation.Animation#getBackgroundColor()} */
-    private DimLayer mAnimationBackgroundSurface;
+    private SurfaceControl mAnimationBackgroundSurface;
+    private boolean mAnimationBackgroundSurfaceIsShown = false;
 
     /** The particular window with an Animation with non-zero background color. */
     private WindowStateAnimator mAnimationBackgroundAnimator;
@@ -149,6 +143,13 @@
 
     Rect mPreAnimationBounds = new Rect();
 
+    private Dimmer mDimmer = new Dimmer(this);
+
+    /**
+     * For {@link #prepareSurfaces}.
+     */
+    final Rect mTmpDimBoundsRect = new Rect();
+
     TaskStack(WindowManagerService service, int stackId, StackWindowController controller) {
         mService = service;
         mStackId = stackId;
@@ -172,27 +173,20 @@
     /**
      * Set the bounds of the stack and its containing tasks.
      * @param stackBounds New stack bounds. Passing in null sets the bounds to fullscreen.
-     * @param configs Configuration for individual tasks, keyed by task id.
      * @param taskBounds Bounds for individual tasks, keyed by task id.
+     * @param taskTempInsetBounds Inset bounds for individual tasks, keyed by task id.
      * @return True if the stack bounds was changed.
      * */
     boolean setBounds(
-            Rect stackBounds, SparseArray<Configuration> configs, SparseArray<Rect> taskBounds,
-            SparseArray<Rect> taskTempInsetBounds) {
+            Rect stackBounds, SparseArray<Rect> taskBounds, SparseArray<Rect> taskTempInsetBounds) {
         setBounds(stackBounds);
 
         // Update bounds of containing tasks.
         for (int taskNdx = mChildren.size() - 1; taskNdx >= 0; --taskNdx) {
             final Task task = mChildren.get(taskNdx);
-            Configuration config = configs.get(task.mTaskId);
-            if (config != null) {
-                Rect bounds = taskBounds.get(task.mTaskId);
-                task.resizeLocked(bounds, config, false /* forced */);
-                task.setTempInsetBounds(taskTempInsetBounds != null ?
-                        taskTempInsetBounds.get(task.mTaskId) : null);
-            } else {
-                Slog.wtf(TAG_WM, "No config for task: " + task + ", is there a mismatch with AM?");
-            }
+            task.setBounds(taskBounds.get(task.mTaskId), false /* forced */);
+            task.setTempInsetBounds(taskTempInsetBounds != null ?
+                    taskTempInsetBounds.get(task.mTaskId) : null);
         }
         return true;
     }
@@ -219,20 +213,20 @@
         final boolean adjusted = !mAdjustedBounds.isEmpty();
         Rect insetBounds = null;
         if (adjusted && isAdjustedForMinimizedDockedStack()) {
-            insetBounds = mBounds;
+            insetBounds = getRawBounds();
         } else if (adjusted && mAdjustedForIme) {
             if (mImeGoingAway) {
-                insetBounds = mBounds;
+                insetBounds = getRawBounds();
             } else {
                 insetBounds = mFullyAdjustedImeBounds;
             }
         }
-        alignTasksToAdjustedBounds(adjusted ? mAdjustedBounds : mBounds, insetBounds);
+        alignTasksToAdjustedBounds(adjusted ? mAdjustedBounds : getRawBounds(), insetBounds);
         mDisplayContent.setLayoutNeeded();
     }
 
     private void alignTasksToAdjustedBounds(Rect adjustedBounds, Rect tempInsetBounds) {
-        if (mFillsParent) {
+        if (matchParentBounds()) {
             return;
         }
 
@@ -245,53 +239,85 @@
         }
     }
 
-    private boolean setBounds(Rect bounds) {
-        boolean oldFullscreen = mFillsParent;
+    private void updateAnimationBackgroundBounds() {
+        if (mAnimationBackgroundSurface == null) {
+            return;
+        }
+        getRawBounds(mTmpRect);
+        // TODO: Should be in relative coordinates.
+        getPendingTransaction().setSize(mAnimationBackgroundSurface, mTmpRect.width(),
+                mTmpRect.height()).setPosition(mAnimationBackgroundSurface, mTmpRect.left,
+                mTmpRect.top);
+        scheduleAnimation();
+    }
+
+    private void hideAnimationSurface() {
+        if (mAnimationBackgroundSurface == null) {
+            return;
+        }
+        getPendingTransaction().hide(mAnimationBackgroundSurface);
+        mAnimationBackgroundSurfaceIsShown = false;
+        scheduleAnimation();
+    }
+
+    private void showAnimationSurface(float alpha) {
+        if (mAnimationBackgroundSurface == null) {
+            return;
+        }
+        getPendingTransaction().setLayer(mAnimationBackgroundSurface, Integer.MIN_VALUE)
+                .setAlpha(mAnimationBackgroundSurface, alpha)
+                .show(mAnimationBackgroundSurface);
+        mAnimationBackgroundSurfaceIsShown = true;
+        scheduleAnimation();
+    }
+
+    @Override
+    public int setBounds(Rect bounds) {
+        return setBounds(getOverrideBounds(), bounds);
+    }
+
+    private int setBounds(Rect existing, Rect bounds) {
         int rotation = Surface.ROTATION_0;
         int density = DENSITY_DPI_UNDEFINED;
         if (mDisplayContent != null) {
-            mDisplayContent.getLogicalDisplayRect(mTmpRect);
+            mDisplayContent.getBounds(mTmpRect);
             rotation = mDisplayContent.getDisplayInfo().rotation;
             density = mDisplayContent.getDisplayInfo().logicalDensityDpi;
-            mFillsParent = bounds == null;
-            if (mFillsParent) {
-                bounds = mTmpRect;
-            }
         }
 
-        if (bounds == null) {
-            // Can't set to fullscreen if we don't have a display to get bounds from...
-            return false;
+        if (equivalentBounds(existing, bounds) && mRotation == rotation) {
+            return BOUNDS_CHANGE_NONE;
         }
-        if (mBounds.equals(bounds) && oldFullscreen == mFillsParent && mRotation == rotation) {
-            return false;
-        }
+
+        final int result = super.setBounds(bounds);
 
         if (mDisplayContent != null) {
-            mDisplayContent.mDimLayerController.updateDimLayer(this);
-            mAnimationBackgroundSurface.setBounds(bounds);
+            updateAnimationBackgroundBounds();
         }
 
-        mBounds.set(bounds);
         mRotation = rotation;
         mDensity = density;
 
         updateAdjustedBounds();
 
-        return true;
+        return result;
     }
 
     /** Bounds of the stack without adjusting for other factors in the system like visibility
      * of docked stack.
-     * Most callers should be using {@link #getBounds} as it take into consideration other system
-     * factors. */
+     * Most callers should be using {@link ConfigurationContainer#getOverrideBounds} as it take into
+     * consideration other system factors. */
     void getRawBounds(Rect out) {
-        out.set(mBounds);
+        out.set(getRawBounds());
+    }
+
+    Rect getRawBounds() {
+        return super.getBounds();
     }
 
     /** Return true if the current bound can get outputted to the rest of the system as-is. */
     private boolean useCurrentBounds() {
-        if (mFillsParent
+        if (matchParentBounds()
                 || !inSplitScreenSecondaryWindowingMode()
                 || mDisplayContent == null
                 || mDisplayContent.getSplitScreenPrimaryStack() != null) {
@@ -300,24 +326,29 @@
         return false;
     }
 
-    public void getBounds(Rect out) {
+    @Override
+    public void getBounds(Rect bounds) {
+        bounds.set(getBounds());
+    }
+
+    @Override
+    public Rect getBounds() {
         if (useCurrentBounds()) {
             // If we're currently adjusting for IME or minimized docked stack, we use the adjusted
             // bounds; otherwise, no need to adjust the output bounds if fullscreen or the docked
             // stack is visible since it is already what we want to represent to the rest of the
             // system.
             if (!mAdjustedBounds.isEmpty()) {
-                out.set(mAdjustedBounds);
+                return mAdjustedBounds;
             } else {
-                out.set(mBounds);
+                return super.getBounds();
             }
-            return;
         }
 
         // The bounds has been adjusted to accommodate for a docked stack, but the docked stack
         // is not currently visible. Go ahead a represent it as fullscreen to the rest of the
         // system.
-        mDisplayContent.getLogicalDisplayRect(out);
+        return mDisplayContent.getBounds();
     }
 
     /**
@@ -338,7 +369,7 @@
             mBoundsAnimationSourceHintBounds.setEmpty();
         }
 
-        mPreAnimationBounds.set(mBounds);
+        mPreAnimationBounds.set(getRawBounds());
     }
 
     /**
@@ -368,7 +399,6 @@
     }
 
     /** Bounds of the stack with other system factors taken into consideration. */
-    @Override
     public void getDimBounds(Rect out) {
         getBounds(out);
     }
@@ -384,12 +414,12 @@
         if (bounds != null) {
             setBounds(bounds);
             return;
-        } else if (mFillsParent) {
+        } else if (matchParentBounds()) {
             setBounds(null);
             return;
         }
 
-        mTmpRect2.set(mBounds);
+        mTmpRect2.set(getRawBounds());
         final int newRotation = mDisplayContent.getDisplayInfo().rotation;
         final int newDensity = mDisplayContent.getDisplayInfo().logicalDensityDpi;
         if (mRotation == newRotation && mDensity == newDensity) {
@@ -432,14 +462,14 @@
             return false;
         }
 
-        if (mFillsParent) {
+        if (matchParentBounds()) {
             // Update stack bounds again since rotation changed since updateDisplayInfo().
             setBounds(null);
             // Return false since we don't need the client to resize.
             return false;
         }
 
-        mTmpRect2.set(mBounds);
+        mTmpRect2.set(getRawBounds());
         mDisplayContent.rotateBounds(mRotation, newRotation, mTmpRect2);
         if (inSplitScreenPrimaryWindowingMode()) {
             repositionPrimarySplitScreenStackAfterRotation(mTmpRect2);
@@ -476,7 +506,7 @@
         if (mDisplayContent.getDockedDividerController().canPrimaryStackDockTo(dockSide)) {
             return;
         }
-        mDisplayContent.getLogicalDisplayRect(mTmpRect);
+        mDisplayContent.getBounds(mTmpRect);
         dockSide = DockedDividerUtils.invertDockSide(dockSide);
         switch (dockSide) {
             case DOCKED_LEFT:
@@ -700,9 +730,12 @@
         }
 
         mDisplayContent = dc;
-        mAnimationBackgroundSurface = new DimLayer(mService, this, mDisplayContent.getDisplayId(),
-                "animation background stackId=" + mStackId);
+
         updateBoundsForWindowModeChange();
+        mAnimationBackgroundSurface = makeChildSurface(null).setColorLayer(true)
+            .setName("animation background stackId=" + mStackId)
+            .build();
+
         super.onDisplayChanged(dc);
     }
 
@@ -718,7 +751,7 @@
             // not fullscreen. If it's fullscreen, it means that we are in the transition of
             // dismissing it, so we must not resize this stack.
             bounds = new Rect();
-            mDisplayContent.getLogicalDisplayRect(mTmpRect);
+            mDisplayContent.getBounds(mTmpRect);
             mTmpRect2.setEmpty();
             if (splitScreenStack != null) {
                 splitScreenStack.getRawBounds(mTmpRect2);
@@ -781,7 +814,7 @@
         }
 
         if (!inSplitScreenWindowingMode() || mDisplayContent == null) {
-            outStackBounds.set(mBounds);
+            outStackBounds.set(getRawBounds());
             return;
         }
 
@@ -796,7 +829,7 @@
             // The docked stack is being dismissed, but we caught before it finished being
             // dismissed. In that case we want to treat it as if it is not occupying any space and
             // let others occupy the whole display.
-            mDisplayContent.getLogicalDisplayRect(outStackBounds);
+            mDisplayContent.getBounds(outStackBounds);
             return;
         }
 
@@ -804,11 +837,11 @@
         if (dockedSide == DOCKED_INVALID) {
             // Not sure how you got here...Only thing we can do is return current bounds.
             Slog.e(TAG_WM, "Failed to get valid docked side for docked stack=" + dockedStack);
-            outStackBounds.set(mBounds);
+            outStackBounds.set(getRawBounds());
             return;
         }
 
-        mDisplayContent.getLogicalDisplayRect(mTmpRect);
+        mDisplayContent.getBounds(mTmpRect);
         dockedStack.getRawBounds(mTmpRect2);
         final boolean dockedOnTopOrLeft = dockedSide == DOCKED_TOP || dockedSide == DOCKED_LEFT;
         getStackDockedModeBounds(mTmpRect, outStackBounds, mTmpRect2,
@@ -914,16 +947,16 @@
 
     @Override
     void onParentSet() {
+        super.onParentSet();
+
         if (getParent() != null || mDisplayContent == null) {
             return;
         }
 
-        // Looks like the stack was removed from the display. Go ahead and clean things up.
-        mDisplayContent.mDimLayerController.removeDimLayerUser(this);
         EventLog.writeEvent(EventLogTags.WM_STACK_REMOVED, mStackId);
 
         if (mAnimationBackgroundSurface != null) {
-            mAnimationBackgroundSurface.destroySurface();
+            mAnimationBackgroundSurface.destroy();
             mAnimationBackgroundSurface = null;
         }
 
@@ -933,9 +966,7 @@
 
     void resetAnimationBackgroundAnimator() {
         mAnimationBackgroundAnimator = null;
-        if (mAnimationBackgroundSurface != null) {
-            mAnimationBackgroundSurface.hide();
-        }
+        hideAnimationSurface();
     }
 
     void setAnimationBackground(WindowStateAnimator winAnimator, int color) {
@@ -944,8 +975,7 @@
                 || animLayer < mAnimationBackgroundAnimator.mAnimLayer) {
             mAnimationBackgroundAnimator = winAnimator;
             animLayer = mDisplayContent.getLayerForAnimationBackground(winAnimator);
-            mAnimationBackgroundSurface.show(animLayer - LAYER_OFFSET_DIM,
-                    ((color >> 24) & 0xff) / 255f, 0);
+            showAnimationSurface(((color >> 24) & 0xff) / 255f);
         }
     }
 
@@ -1112,14 +1142,14 @@
             // occluded by IME. We shift its bottom up by the height of the IME, but
             // leaves at least 30% of the top stack visible.
             final int minTopStackBottom =
-                    getMinTopStackBottom(displayContentRect, mBounds.bottom);
+                    getMinTopStackBottom(displayContentRect, getRawBounds().bottom);
             final int bottom = Math.max(
-                    mBounds.bottom - yOffset + dividerWidth - dividerWidthInactive,
+                    getRawBounds().bottom - yOffset + dividerWidth - dividerWidthInactive,
                     minTopStackBottom);
-            mTmpAdjustedBounds.set(mBounds);
-            mTmpAdjustedBounds.bottom =
-                    (int) (mAdjustImeAmount * bottom + (1 - mAdjustImeAmount) * mBounds.bottom);
-            mFullyAdjustedImeBounds.set(mBounds);
+            mTmpAdjustedBounds.set(getRawBounds());
+            mTmpAdjustedBounds.bottom = (int) (mAdjustImeAmount * bottom + (1 - mAdjustImeAmount)
+                    * getRawBounds().bottom);
+            mFullyAdjustedImeBounds.set(getRawBounds());
         } else {
             // When the stack is on bottom and has no focus, it's only adjusted for divider width.
             final int dividerWidthDelta = dividerWidthInactive - dividerWidth;
@@ -1129,22 +1159,24 @@
             // We try to move it up by the height of the IME window, but only to the extent
             // that leaves at least 30% of the top stack visible.
             // 'top' is where the top of bottom stack will move to in this case.
-            final int topBeforeImeAdjust = mBounds.top - dividerWidth + dividerWidthInactive;
+            final int topBeforeImeAdjust =
+                    getRawBounds().top - dividerWidth + dividerWidthInactive;
             final int minTopStackBottom =
-                    getMinTopStackBottom(displayContentRect, mBounds.top - dividerWidth);
+                    getMinTopStackBottom(displayContentRect,
+                            getRawBounds().top - dividerWidth);
             final int top = Math.max(
-                    mBounds.top - yOffset, minTopStackBottom + dividerWidthInactive);
+                    getRawBounds().top - yOffset, minTopStackBottom + dividerWidthInactive);
 
-            mTmpAdjustedBounds.set(mBounds);
+            mTmpAdjustedBounds.set(getRawBounds());
             // Account for the adjustment for IME and divider width separately.
             // (top - topBeforeImeAdjust) is the amount of movement due to IME only,
             // and dividerWidthDelta is due to divider width change only.
-            mTmpAdjustedBounds.top = mBounds.top +
+            mTmpAdjustedBounds.top = getRawBounds().top +
                     (int) (mAdjustImeAmount * (top - topBeforeImeAdjust) +
                             mAdjustDividerAmount * dividerWidthDelta);
-            mFullyAdjustedImeBounds.set(mBounds);
+            mFullyAdjustedImeBounds.set(getRawBounds());
             mFullyAdjustedImeBounds.top = top;
-            mFullyAdjustedImeBounds.bottom = top + mBounds.height();
+            mFullyAdjustedImeBounds.bottom = top + getRawBounds().height();
         }
         return true;
     }
@@ -1158,21 +1190,21 @@
         if (dockSide == DOCKED_TOP) {
             mService.getStableInsetsLocked(DEFAULT_DISPLAY, mTmpRect);
             int topInset = mTmpRect.top;
-            mTmpAdjustedBounds.set(mBounds);
-            mTmpAdjustedBounds.bottom =
-                    (int) (minimizeAmount * topInset + (1 - minimizeAmount) * mBounds.bottom);
+            mTmpAdjustedBounds.set(getRawBounds());
+            mTmpAdjustedBounds.bottom = (int) (minimizeAmount * topInset + (1 - minimizeAmount)
+                    * getRawBounds().bottom);
         } else if (dockSide == DOCKED_LEFT) {
-            mTmpAdjustedBounds.set(mBounds);
-            final int width = mBounds.width();
+            mTmpAdjustedBounds.set(getRawBounds());
+            final int width = getRawBounds().width();
             mTmpAdjustedBounds.right =
                     (int) (minimizeAmount * mDockedStackMinimizeThickness
-                            + (1 - minimizeAmount) * mBounds.right);
+                            + (1 - minimizeAmount) * getRawBounds().right);
             mTmpAdjustedBounds.left = mTmpAdjustedBounds.right - width;
         } else if (dockSide == DOCKED_RIGHT) {
-            mTmpAdjustedBounds.set(mBounds);
-            mTmpAdjustedBounds.left =
-                    (int) (minimizeAmount * (mBounds.right - mDockedStackMinimizeThickness)
-                            + (1 - minimizeAmount) * mBounds.left);
+            mTmpAdjustedBounds.set(getRawBounds());
+            mTmpAdjustedBounds.left = (int) (minimizeAmount *
+                    (getRawBounds().right - mDockedStackMinimizeThickness)
+                            + (1 - minimizeAmount) * getRawBounds().left);
         }
         return true;
     }
@@ -1194,9 +1226,9 @@
         if (dockSide == DOCKED_TOP) {
             mService.getStableInsetsLocked(DEFAULT_DISPLAY, mTmpRect);
             int topInset = mTmpRect.top;
-            return mBounds.bottom - topInset;
+            return getRawBounds().bottom - topInset;
         } else if (dockSide == DOCKED_LEFT || dockSide == DOCKED_RIGHT) {
-            return mBounds.width() - mDockedStackMinimizeThickness;
+            return getRawBounds().width() - mDockedStackMinimizeThickness;
         } else {
             return 0;
         }
@@ -1230,11 +1262,12 @@
             return;
         }
 
-        final Rect insetBounds = mImeGoingAway ? mBounds : mFullyAdjustedImeBounds;
+        final Rect insetBounds = mImeGoingAway ? getRawBounds() : mFullyAdjustedImeBounds;
         task.alignToAdjustedBounds(mAdjustedBounds, insetBounds, getDockSide() == DOCKED_TOP);
         mDisplayContent.setLayoutNeeded();
     }
 
+
     boolean isAdjustedForMinimizedDockedStack() {
         return mMinimizeAmount != 0f;
     }
@@ -1248,17 +1281,16 @@
         for (int taskNdx = mChildren.size() - 1; taskNdx >= 0; taskNdx--) {
             mChildren.get(taskNdx).writeToProto(proto, TASKS, trim);
         }
-        proto.write(FILLS_PARENT, mFillsParent);
-        mBounds.writeToProto(proto, BOUNDS);
-        proto.write(ANIMATION_BACKGROUND_SURFACE_IS_DIMMING, mAnimationBackgroundSurface.isDimming());
+        proto.write(FILLS_PARENT, matchParentBounds());
+        getRawBounds().writeToProto(proto, BOUNDS);
+        proto.write(ANIMATION_BACKGROUND_SURFACE_IS_DIMMING, mAnimationBackgroundSurfaceIsShown);
         proto.end(token);
     }
 
     public void dump(String prefix, PrintWriter pw) {
         pw.println(prefix + "mStackId=" + mStackId);
         pw.println(prefix + "mDeferRemoval=" + mDeferRemoval);
-        pw.println(prefix + "mFillsParent=" + mFillsParent);
-        pw.println(prefix + "mBounds=" + mBounds.toShortString());
+        pw.println(prefix + "mBounds=" + getRawBounds().toShortString());
         if (mMinimizeAmount != 0f) {
             pw.println(prefix + "mMinimizeAmount=" + mMinimizeAmount);
         }
@@ -1273,9 +1305,8 @@
         for (int taskNdx = mChildren.size() - 1; taskNdx >= 0; taskNdx--) {
             mChildren.get(taskNdx).dump(prefix + "  ", pw);
         }
-        if (mAnimationBackgroundSurface.isDimming()) {
-            pw.println(prefix + "mWindowAnimationBackgroundSurface:");
-            mAnimationBackgroundSurface.printTo(prefix + "  ", pw);
+        if (mAnimationBackgroundSurfaceIsShown) {
+            pw.println(prefix + "mWindowAnimationBackgroundSurface is shown");
         }
         if (!mExitingAppTokens.isEmpty()) {
             pw.println();
@@ -1290,23 +1321,10 @@
         }
     }
 
-    /** Fullscreen status of the stack without adjusting for other factors in the system like
-     * visibility of docked stack.
-     * Most callers should be using {@link #fillsParent} as it take into consideration other
-     * system factors. */
-    boolean getRawFullscreen() {
-        return mFillsParent;
-    }
-
-    @Override
-    public boolean dimFullscreen() {
-        return !isActivityTypeStandard() || fillsParent();
-    }
-
     @Override
     boolean fillsParent() {
         if (useCurrentBounds()) {
-            return mFillsParent;
+            return matchParentBounds();
         }
         // The bounds has been adjusted to accommodate for a docked stack, but the docked stack
         // is not currently visible. Go ahead a represent it as fullscreen to the rest of the
@@ -1315,16 +1333,6 @@
     }
 
     @Override
-    public DisplayInfo getDisplayInfo() {
-        return mDisplayContent.getDisplayInfo();
-    }
-
-    @Override
-    public boolean isAttachedToDisplay() {
-        return mDisplayContent != null;
-    }
-
-    @Override
     public String toString() {
         return "{stackId=" + mStackId + " tasks=" + mChildren + "}";
     }
@@ -1333,7 +1341,6 @@
         return toShortString();
     }
 
-    @Override
     public String toShortString() {
         return "Stack=" + mStackId;
     }
@@ -1343,7 +1350,7 @@
      * information which side of the screen was the dock anchored.
      */
     int getDockSide() {
-        return getDockSide(mBounds);
+        return getDockSide(getRawBounds());
     }
 
     private int getDockSide(Rect bounds) {
@@ -1353,7 +1360,7 @@
         if (mDisplayContent == null) {
             return DOCKED_INVALID;
         }
-        mDisplayContent.getLogicalDisplayRect(mTmpRect);
+        mDisplayContent.getBounds(mTmpRect);
         final int orientation = mDisplayContent.getConfiguration().orientation;
         return getDockSideUnchecked(bounds, mTmpRect, orientation);
     }
@@ -1473,7 +1480,7 @@
              */
 
             if (task.isActivityTypeHome() && isMinimizedDockAndHomeStackResizable()) {
-                mDisplayContent.getLogicalDisplayRect(mTmpRect);
+                mDisplayContent.getBounds(mTmpRect);
             } else {
                 task.getDimBounds(mTmpRect);
             }
@@ -1691,4 +1698,32 @@
                 || activityType == ACTIVITY_TYPE_RECENTS
                 || activityType == ACTIVITY_TYPE_ASSISTANT;
     }
+
+    Dimmer getDimmer() {
+        return mDimmer;
+    }
+
+    @Override
+    void prepareSurfaces() {
+        mDimmer.resetDimStates();
+        super.prepareSurfaces();
+        getDimBounds(mTmpDimBoundsRect);
+        if (mDimmer.updateDims(getPendingTransaction(), mTmpDimBoundsRect)) {
+            scheduleAnimation();
+        }
+    }
+
+    public DisplayInfo getDisplayInfo() {
+        return mDisplayContent.getDisplayInfo();
+    }
+
+    void dim(float alpha) {
+        mDimmer.dimAbove(getPendingTransaction(), alpha);
+        scheduleAnimation();
+    }
+
+    void stopDimming() {
+        mDimmer.stopDim(getPendingTransaction());
+        scheduleAnimation();
+    }
 }
diff --git a/com/android/server/wm/TaskTapPointerEventListener.java b/com/android/server/wm/TaskTapPointerEventListener.java
index 42a2d9d..84ad576 100644
--- a/com/android/server/wm/TaskTapPointerEventListener.java
+++ b/com/android/server/wm/TaskTapPointerEventListener.java
@@ -20,7 +20,7 @@
 import android.graphics.Region;
 import android.hardware.input.InputManager;
 import android.view.MotionEvent;
-import android.view.WindowManagerPolicy.PointerEventListener;
+import android.view.WindowManagerPolicyConstants.PointerEventListener;
 
 import com.android.server.wm.WindowManagerService.H;
 
diff --git a/com/android/server/wm/TaskWindowContainerController.java b/com/android/server/wm/TaskWindowContainerController.java
index b3bb0b7..5caae32 100644
--- a/com/android/server/wm/TaskWindowContainerController.java
+++ b/com/android/server/wm/TaskWindowContainerController.java
@@ -18,7 +18,6 @@
 
 import android.app.ActivityManager.TaskDescription;
 import android.app.ActivityManager.TaskSnapshot;
-import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.Handler;
 import android.os.Looper;
@@ -30,6 +29,7 @@
 import java.lang.ref.WeakReference;
 
 import static com.android.server.EventLogTags.WM_TASK_CREATED;
+import static com.android.server.wm.ConfigurationContainer.BOUNDS_CHANGE_NONE;
 import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER;
 import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
@@ -75,7 +75,7 @@
                         + stackController);
             }
             EventLog.writeEvent(WM_TASK_CREATED, taskId, stack.mStackId);
-            final Task task = createTask(taskId, stack, userId, bounds, resizeMode,
+            final Task task = createTask(taskId, stack, userId, resizeMode,
                     supportsPictureInPicture, taskDescription);
             final int position = toTop ? POSITION_TOP : POSITION_BOTTOM;
             // We only want to move the parents to the parents if we are creating this task at the
@@ -85,10 +85,10 @@
     }
 
     @VisibleForTesting
-    Task createTask(int taskId, TaskStack stack, int userId, Rect bounds, int resizeMode,
+    Task createTask(int taskId, TaskStack stack, int userId, int resizeMode,
             boolean supportsPictureInPicture, TaskDescription taskDescription) {
-        return new Task(taskId, stack, userId, mService, bounds, resizeMode,
-                supportsPictureInPicture, taskDescription, this);
+        return new Task(taskId, stack, userId, mService, resizeMode, supportsPictureInPicture,
+                taskDescription, this);
     }
 
     @Override
@@ -151,14 +151,14 @@
         }
     }
 
-    public void resize(Rect bounds, Configuration overrideConfig, boolean relayout,
-            boolean forced) {
+    public void resize(boolean relayout, boolean forced) {
         synchronized (mWindowMap) {
             if (mContainer == null) {
                 throw new IllegalArgumentException("resizeTask: taskId " + mTaskId + " not found.");
             }
 
-            if (mContainer.resizeLocked(bounds, overrideConfig, forced) && relayout) {
+            if (mContainer.setBounds(mContainer.getOverrideBounds(), forced) != BOUNDS_CHANGE_NONE
+                    && relayout) {
                 mContainer.getDisplayContent().layoutAndAssignWindowLayersIfNeeded();
             }
         }
diff --git a/com/android/server/wm/WallpaperController.java b/com/android/server/wm/WallpaperController.java
index 629cc86..3ae4549 100644
--- a/com/android/server/wm/WallpaperController.java
+++ b/com/android/server/wm/WallpaperController.java
@@ -23,8 +23,8 @@
 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
-import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
 
+import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
 import static com.android.server.wm.AppTransition.TRANSIT_FLAG_KEYGUARD_GOING_AWAY_WITH_WALLPAPER;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_APP_TRANSITIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_WALLPAPER;
diff --git a/com/android/server/wm/Watermark.java b/com/android/server/wm/Watermark.java
index d97aaac..9216b66 100644
--- a/com/android/server/wm/Watermark.java
+++ b/com/android/server/wm/Watermark.java
@@ -53,7 +53,7 @@
     private int mLastDH;
     private boolean mDrawNeeded;
 
-    Watermark(Display display, DisplayMetrics dm, SurfaceSession session, String[] tokens) {
+    Watermark(DisplayContent dc, DisplayMetrics dm, String[] tokens) {
         if (false) {
             Log.i(TAG_WM, "*********************** WATERMARK");
             for (int i=0; i<tokens.length; i++) {
@@ -61,7 +61,7 @@
             }
         }
 
-        mDisplay = display;
+        mDisplay = dc.getDisplay();
         mTokens = tokens;
 
         StringBuilder builder = new StringBuilder(32);
@@ -114,7 +114,7 @@
 
         SurfaceControl ctrl = null;
         try {
-            ctrl = new SurfaceControl.Builder(session)
+            ctrl = dc.makeOverlay()
                     .setName("WatermarkSurface")
                     .setSize(1, 1)
                     .setFormat(PixelFormat.TRANSLUCENT)
diff --git a/com/android/server/wm/WindowAnimator.java b/com/android/server/wm/WindowAnimator.java
index 1912095..7c56f00 100644
--- a/com/android/server/wm/WindowAnimator.java
+++ b/com/android/server/wm/WindowAnimator.java
@@ -30,10 +30,9 @@
 import android.util.SparseArray;
 import android.util.TimeUtils;
 import android.view.Choreographer;
-import android.view.SurfaceControl;
-import android.view.WindowManagerPolicy;
 
 import com.android.server.AnimationThread;
+import com.android.server.policy.WindowManagerPolicy;
 
 import java.io.PrintWriter;
 
@@ -200,7 +199,7 @@
                     ++mAnimTransactionSequence;
                     dc.updateWindowsForAnimator(this);
                     dc.updateWallpaperForAnimator(this);
-                    dc.prepareWindowSurfaces();
+                    dc.prepareSurfaces();
                 }
 
                 for (int i = 0; i < numDisplays; i++) {
@@ -214,8 +213,6 @@
                     if (screenRotationAnimation != null) {
                         screenRotationAnimation.updateSurfacesInTransaction();
                     }
-
-                    orAnimating(dc.animateDimLayers());
                     orAnimating(dc.getDockedDividerController().animate(mCurrentTime));
                     //TODO (multidisplay): Magnification is supported only for the default display.
                     if (accessibilityController != null && dc.isDefaultDisplay) {
@@ -237,6 +234,13 @@
                 if (SHOW_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION animate");
             }
 
+            final int numDisplays = mDisplayContentsAnimators.size();
+            for (int i = 0; i < numDisplays; i++) {
+                final int displayId = mDisplayContentsAnimators.keyAt(i);
+                final DisplayContent dc = mService.mRoot.getDisplayContentOrCreate(displayId);
+                dc.onPendingTransactionApplied();
+            }
+
             boolean hasPendingLayoutChanges = mService.mRoot.hasPendingLayoutChanges(this);
             boolean doRequest = false;
             if (mBulkUpdateParams != 0) {
@@ -271,6 +275,7 @@
             mService.destroyPreservedSurfaceLocked();
             mService.mWindowPlacerLocked.destroyPendingSurfaces();
 
+
             if (DEBUG_WINDOW_TRACE) {
                 Slog.i(TAG, "!!! animate: exit mAnimating=" + mAnimating
                         + " mBulkUpdateParams=" + Integer.toHexString(mBulkUpdateParams)
diff --git a/com/android/server/wm/WindowContainer.java b/com/android/server/wm/WindowContainer.java
index 8f4b897..6467582 100644
--- a/com/android/server/wm/WindowContainer.java
+++ b/com/android/server/wm/WindowContainer.java
@@ -21,9 +21,13 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 import static com.android.server.wm.proto.WindowContainerProto.CONFIGURATION_CONTAINER;
 import static com.android.server.wm.proto.WindowContainerProto.ORIENTATION;
+import static android.view.SurfaceControl.Transaction;
 
 import android.annotation.CallSuper;
 import android.content.res.Configuration;
+import android.view.MagnificationSpec;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
 import android.util.Pools;
 
 import android.util.proto.ProtoOutputStream;
@@ -64,7 +68,14 @@
             new Pools.SynchronizedPool<>(3);
 
     // The owner/creator for this container. No controller if null.
-    private WindowContainerController mController;
+     WindowContainerController mController;
+
+    protected SurfaceControl mSurfaceControl;
+
+    /**
+     * Applied as part of the animation pass in "prepareSurfaces".
+     */
+    private Transaction mPendingTransaction = new Transaction();
 
     @Override
     final protected WindowContainer getParent() {
@@ -101,7 +112,22 @@
      * Supposed to be overridden and contain actions that should be executed after parent was set.
      */
     void onParentSet() {
-        // Do nothing by default.
+        if (mParent == null) {
+            return;
+        }
+        if (mSurfaceControl == null) {
+            // If we don't yet have a surface, but we now have a parent, we should
+            // build a surface.
+            mSurfaceControl = makeSurface().build();
+            getPendingTransaction().show(mSurfaceControl);
+        } else {
+            // If we have a surface but a new parent, we just need to perform a reparent.
+            getPendingTransaction().reparent(mSurfaceControl, mParent.mSurfaceControl.getHandle());
+        }
+
+        // Either way we need to ask the parent to assign us a Z-order.
+        mParent.assignChildLayers();
+        scheduleAnimation();
     }
 
     // Temp. holders for a chain of containers we are currently processing.
@@ -188,6 +214,11 @@
             mChildren.remove(child);
         }
 
+        if (mSurfaceControl != null) {
+            destroyAfterPendingTransaction(mSurfaceControl);
+            mSurfaceControl = null;
+        }
+
         if (mParent != null) {
             mParent.removeChild(this);
         }
@@ -195,6 +226,7 @@
         if (mController != null) {
             setController(null);
         }
+
     }
 
     /**
@@ -286,11 +318,24 @@
      * @see #mFullConfiguration
      */
     @Override
-    final public void onOverrideConfigurationChanged(Configuration overrideConfiguration) {
+    public void onOverrideConfigurationChanged(Configuration overrideConfiguration) {
+        // We must diff before the configuration is applied so that we can capture the change
+        // against the existing bounds.
+        final int diff = diffOverrideBounds(overrideConfiguration.windowConfiguration.getBounds());
         super.onOverrideConfigurationChanged(overrideConfiguration);
         if (mParent != null) {
             mParent.onDescendantOverrideConfigurationChanged();
         }
+
+        if (diff == BOUNDS_CHANGE_NONE) {
+            return;
+        }
+
+        if ((diff & BOUNDS_CHANGE_SIZE) == BOUNDS_CHANGE_SIZE) {
+            onResize();
+        } else {
+            onMovedByResize();
+        }
     }
 
     /**
@@ -407,7 +452,7 @@
     }
 
     /**
-a     * Returns whether this child is on top of the window hierarchy.
+     * @return Whether this child is on top of the window hierarchy.
      */
     boolean isOnTop() {
         return getParent().getTopChild() == this && getParent().isOnTop();
@@ -673,6 +718,82 @@
         mController = controller;
     }
 
+    SurfaceControl.Builder makeSurface() {
+        final WindowContainer p = getParent();
+        return p.makeChildSurface(this);
+    }
+
+    /**
+     * @param child The WindowContainer this child surface is for, or null if the Surface
+     *              is not assosciated with a WindowContainer (e.g. a surface used for Dimming).
+     */
+    SurfaceControl.Builder makeChildSurface(WindowContainer child) {
+        final WindowContainer p = getParent();
+        // Give the parent a chance to set properties. In hierarchy v1 we rely
+        // on this to set full-screen dimensions on all our Surface-less Layers.
+        return p.makeChildSurface(child)
+                .setParent(mSurfaceControl);
+    }
+
+    /**
+     * @return Whether this WindowContainer should be magnified by the accessibility magnifier.
+     */
+    boolean shouldMagnify() {
+        for (int i = 0; i < mChildren.size(); i++) {
+            if (!mChildren.get(i).shouldMagnify()) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    SurfaceSession getSession() {
+        if (getParent() != null) {
+            return getParent().getSession();
+        }
+        return null;
+    }
+
+    void assignLayer(Transaction t, int layer) {
+        if (mSurfaceControl != null) {
+            t.setLayer(mSurfaceControl, layer);
+        }
+    }
+
+    void assignChildLayers(Transaction t) {
+        int layer = 0;
+        boolean boosting = false;
+
+        // We use two passes as a way to promote children which
+        // need Z-boosting to the end of the list.
+        for (int i = 0; i < 2; i++ ) {
+            for (int j = 0; j < mChildren.size(); ++j) {
+                final WindowContainer wc = mChildren.get(j);
+                if (wc.needsZBoost() && !boosting) {
+                    continue;
+                }
+                wc.assignLayer(t, layer);
+                wc.assignChildLayers(t);
+
+                layer++;
+            }
+            boosting = true;
+        }
+    }
+
+    void assignChildLayers() {
+        assignChildLayers(getPendingTransaction());
+    }
+
+    boolean needsZBoost() {
+        for (int i = 0; i < mChildren.size(); i++) {
+            if (mChildren.get(i).needsZBoost()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     /**
      * Write to a protocol buffer output stream. Protocol buffer message definition is at
      * {@link com.android.server.wm.proto.WindowContainerProto}.
@@ -719,4 +840,59 @@
             mConsumerWrapperPool.release(this);
         }
     }
+
+    // TODO(b/68336570): Should this really be on WindowContainer since it
+    // can only be used on the top-level nodes that aren't animated?
+    // (otherwise we would be fighting other callers of setMatrix).
+    void applyMagnificationSpec(Transaction t, MagnificationSpec spec) {
+        if (shouldMagnify()) {
+            t.setMatrix(mSurfaceControl, spec.scale, 0, 0, spec.scale)
+                    .setPosition(mSurfaceControl, spec.offsetX, spec.offsetY);
+        } else {
+            for (int i = 0; i < mChildren.size(); i++) {
+                mChildren.get(i).applyMagnificationSpec(t, spec);
+            }
+        }
+    }
+
+    /**
+     * TODO: Once we totally eliminate global transaction we will pass transaction in here
+     * rather than merging to global.
+     */
+    void prepareSurfaces() {
+        SurfaceControl.mergeToGlobalTransaction(getPendingTransaction());
+        for (int i = 0; i < mChildren.size(); i++) {
+            mChildren.get(i).prepareSurfaces();
+        }
+    }
+
+    /**
+     * Trigger a call to prepareSurfaces from the animation thread, such that
+     * mPendingTransaction will be applied.
+     */
+    void scheduleAnimation() {
+        if (mParent != null) {
+            mParent.scheduleAnimation();
+        }
+    }
+
+    SurfaceControl getSurfaceControl() {
+        return mSurfaceControl;
+    }
+
+    /**
+     * Destroy a given surface after executing mPendingTransaction. This is
+     * largely a workaround for destroy not being part of transactions
+     * rather than an intentional design, so please take care when
+     * expanding use.
+     */
+    void destroyAfterPendingTransaction(SurfaceControl surface) {
+        if (mParent != null) {
+            mParent.destroyAfterPendingTransaction(surface);
+        }
+    }
+    
+    Transaction getPendingTransaction() {
+        return mPendingTransaction;
+    }
 }
diff --git a/com/android/server/wm/WindowLayersController.java b/com/android/server/wm/WindowLayersController.java
deleted file mode 100644
index 7caf2fe..0000000
--- a/com/android/server/wm/WindowLayersController.java
+++ /dev/null
@@ -1,273 +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.wm;
-
-import android.util.Slog;
-
-import java.util.ArrayDeque;
-import java.util.function.Consumer;
-
-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;
-import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
-import static com.android.server.wm.WindowManagerService.TYPE_LAYER_OFFSET;
-import static com.android.server.wm.WindowManagerService.WINDOW_LAYER_MULTIPLIER;
-
-/**
- * Controller for assigning layers to windows on the display.
- *
- * This class encapsulates general algorithm for assigning layers and special rules that we need to
- * apply on top. The general algorithm goes through windows from bottom to the top and the higher
- * the window is, the higher layer is assigned. The final layer is equal to base layer +
- * adjustment from the order. This means that the window list is assumed to be ordered roughly by
- * the base layer (there are exceptions, e.g. due to keyguard and wallpaper and they need to be
- * handled with care, because they break the algorithm).
- *
- * On top of the general algorithm we add special rules, that govern such amazing things as:
- * <li>IME (which has higher base layer, but will be positioned above application windows)</li>
- * <li>docked/pinned windows (that need to be lifted above other application windows, including
- * animations)
- * <li>dock divider (which needs to live above applications, but below IME)</li>
- * <li>replaced windows, which need to live above their normal level, because they anticipate
- * an animation</li>.
- */
-class WindowLayersController {
-    private final WindowManagerService mService;
-
-    WindowLayersController(WindowManagerService service) {
-        mService = service;
-    }
-
-    private ArrayDeque<WindowState> mPinnedWindows = new ArrayDeque<>();
-    private ArrayDeque<WindowState> mDockedWindows = new ArrayDeque<>();
-    private ArrayDeque<WindowState> mAssistantWindows = new ArrayDeque<>();
-    private ArrayDeque<WindowState> mInputMethodWindows = new ArrayDeque<>();
-    private WindowState mDockDivider = null;
-    private ArrayDeque<WindowState> mReplacingWindows = new ArrayDeque<>();
-    private int mCurBaseLayer;
-    private int mCurLayer;
-    private boolean mAnyLayerChanged;
-    private int mHighestApplicationLayer;
-    private int mHighestDockedAffectedLayer;
-    private int mHighestLayerInImeTargetBaseLayer;
-    private WindowState mImeTarget;
-    private boolean mAboveImeTarget;
-    private ArrayDeque<WindowState> mAboveImeTargetAppWindows = new ArrayDeque();
-
-    private final Consumer<WindowState> mAssignWindowLayersConsumer = w -> {
-        boolean layerChanged = false;
-
-        int oldLayer = w.mLayer;
-        if (w.mBaseLayer == mCurBaseLayer) {
-            mCurLayer += WINDOW_LAYER_MULTIPLIER;
-        } else {
-            mCurBaseLayer = mCurLayer = w.mBaseLayer;
-        }
-        assignAnimLayer(w, mCurLayer);
-
-        // TODO: Preserved old behavior of code here but not sure comparing oldLayer to
-        // mAnimLayer and mLayer makes sense...though the worst case would be unintentional
-        // layer reassignment.
-        if (w.mLayer != oldLayer || w.mWinAnimator.mAnimLayer != oldLayer) {
-            layerChanged = true;
-            mAnyLayerChanged = true;
-        }
-
-        if (w.mAppToken != null) {
-            mHighestApplicationLayer = Math.max(mHighestApplicationLayer,
-                    w.mWinAnimator.mAnimLayer);
-        }
-        if (mImeTarget != null && w.mBaseLayer == mImeTarget.mBaseLayer) {
-            mHighestLayerInImeTargetBaseLayer = Math.max(mHighestLayerInImeTargetBaseLayer,
-                    w.mWinAnimator.mAnimLayer);
-        }
-        if (w.getAppToken() != null && w.inSplitScreenSecondaryWindowingMode()) {
-            mHighestDockedAffectedLayer = Math.max(mHighestDockedAffectedLayer,
-                    w.mWinAnimator.mAnimLayer);
-        }
-
-        collectSpecialWindows(w);
-
-        if (layerChanged) {
-            w.scheduleAnimationIfDimming();
-        }
-    };
-
-    final void assignWindowLayers(DisplayContent dc) {
-        if (DEBUG_LAYERS) Slog.v(TAG_WM, "Assigning layers based",
-                new RuntimeException("here").fillInStackTrace());
-
-        reset();
-        dc.forAllWindows(mAssignWindowLayersConsumer, false /* traverseTopToBottom */);
-
-        adjustSpecialWindows();
-
-        //TODO (multidisplay): Magnification is supported only for the default display.
-        if (mService.mAccessibilityController != null && mAnyLayerChanged
-                && dc.getDisplayId() == DEFAULT_DISPLAY) {
-            mService.mAccessibilityController.onWindowLayersChangedLocked();
-        }
-
-        if (DEBUG_LAYERS) logDebugLayers(dc);
-    }
-
-    private void logDebugLayers(DisplayContent dc) {
-        dc.forAllWindows((w) -> {
-            final WindowStateAnimator winAnimator = w.mWinAnimator;
-            Slog.v(TAG_WM, "Assign layer " + w + ": " + "mBase=" + w.mBaseLayer
-                    + " mLayer=" + w.mLayer + (w.mAppToken == null
-                    ? "" : " mAppLayer=" + w.mAppToken.getAnimLayerAdjustment())
-                    + " =mAnimLayer=" + winAnimator.mAnimLayer);
-        }, false /* traverseTopToBottom */);
-    }
-
-    private void reset() {
-        mPinnedWindows.clear();
-        mInputMethodWindows.clear();
-        mDockedWindows.clear();
-        mAssistantWindows.clear();
-        mReplacingWindows.clear();
-        mDockDivider = null;
-
-        mCurBaseLayer = 0;
-        mCurLayer = 0;
-        mAnyLayerChanged = false;
-
-        mHighestApplicationLayer = 0;
-        mHighestDockedAffectedLayer = 0;
-        mHighestLayerInImeTargetBaseLayer = (mImeTarget != null) ? mImeTarget.mBaseLayer : 0;
-        mImeTarget = mService.mInputMethodTarget;
-        mAboveImeTarget = false;
-        mAboveImeTargetAppWindows.clear();
-    }
-
-    private void collectSpecialWindows(WindowState w) {
-        if (w.mAttrs.type == TYPE_DOCK_DIVIDER) {
-            mDockDivider = w;
-            return;
-        }
-        if (w.mWillReplaceWindow) {
-            mReplacingWindows.add(w);
-        }
-        if (w.mIsImWindow) {
-            mInputMethodWindows.add(w);
-            return;
-        }
-        if (mImeTarget != null) {
-            if (w.getParentWindow() == mImeTarget && w.mSubLayer > 0) {
-                // Child windows of the ime target with a positive sub-layer should be placed above
-                // the IME.
-                mAboveImeTargetAppWindows.add(w);
-            } else if (mAboveImeTarget && w.mAppToken != null) {
-                // windows of apps above the IME target should be placed above the IME.
-                mAboveImeTargetAppWindows.add(w);
-            }
-            if (w == mImeTarget) {
-                mAboveImeTarget = true;
-            }
-        }
-
-        final int windowingMode = w.getWindowingMode();
-        if (windowingMode == WINDOWING_MODE_PINNED) {
-            mPinnedWindows.add(w);
-        } else if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
-            mDockedWindows.add(w);
-        }
-        if (w.isActivityTypeAssistant()) {
-            mAssistantWindows.add(w);
-        }
-    }
-
-    private void adjustSpecialWindows() {
-        // The following adjustments are beyond the highest docked-affected layer
-        int layer = mHighestDockedAffectedLayer +  TYPE_LAYER_OFFSET;
-
-        // Adjust the docked stack windows and dock divider above only the windows that are affected
-        // by the docked stack. When this happens, also boost the assistant window layers, otherwise
-        // the docked stack windows & divider would be promoted above the assistant.
-        if (!mDockedWindows.isEmpty() && mHighestDockedAffectedLayer > 0) {
-            while (!mDockedWindows.isEmpty()) {
-                final WindowState window = mDockedWindows.remove();
-                layer = assignAndIncreaseLayerIfNeeded(window, layer);
-            }
-
-            layer = assignAndIncreaseLayerIfNeeded(mDockDivider, layer);
-
-            while (!mAssistantWindows.isEmpty()) {
-                final WindowState window = mAssistantWindows.remove();
-                if (window.mLayer > mHighestDockedAffectedLayer) {
-                    layer = assignAndIncreaseLayerIfNeeded(window, layer);
-                }
-            }
-        }
-
-        // The following adjustments are beyond the highest app layer or boosted layer
-        layer = Math.max(layer, mHighestApplicationLayer + WINDOW_LAYER_MULTIPLIER);
-
-        // We know that we will be animating a relaunching window in the near future, which will
-        // receive a z-order increase. We want the replaced window to immediately receive the same
-        // treatment, e.g. to be above the dock divider.
-        while (!mReplacingWindows.isEmpty()) {
-            layer = assignAndIncreaseLayerIfNeeded(mReplacingWindows.remove(), layer);
-        }
-
-        while (!mPinnedWindows.isEmpty()) {
-            layer = assignAndIncreaseLayerIfNeeded(mPinnedWindows.remove(), layer);
-        }
-
-        // Make sure IME is the highest window in the base layer of it's target.
-        if (mImeTarget != null) {
-            if (mImeTarget.mAppToken == null) {
-                // For non-app ime targets adjust the layer we start from to match what we found
-                // when assigning layers. Otherwise, just use the highest app layer we have some far.
-                layer = mHighestLayerInImeTargetBaseLayer + WINDOW_LAYER_MULTIPLIER;
-            }
-
-            while (!mInputMethodWindows.isEmpty()) {
-                layer = assignAndIncreaseLayerIfNeeded(mInputMethodWindows.remove(), layer);
-            }
-
-            // Adjust app windows the should be displayed above the IME since they are above the IME
-            // target.
-            while (!mAboveImeTargetAppWindows.isEmpty()) {
-                layer = assignAndIncreaseLayerIfNeeded(mAboveImeTargetAppWindows.remove(), layer);
-            }
-        }
-
-    }
-
-    private int assignAndIncreaseLayerIfNeeded(WindowState win, int layer) {
-        if (win != null) {
-            assignAnimLayer(win, layer);
-            // Make sure we leave space in-between normal windows for dims and such.
-            layer += WINDOW_LAYER_MULTIPLIER;
-        }
-        return layer;
-    }
-
-    private void assignAnimLayer(WindowState w, int layer) {
-        w.mLayer = layer;
-        w.mWinAnimator.mAnimLayer = w.getAnimLayerAdjustment()
-                + w.getSpecialWindowAnimLayerAdjustment();
-        if (w.mAppToken != null) {
-            w.mAppToken.mAppAnimator.updateThumbnailLayer();
-        }
-    }
-}
diff --git a/com/android/server/wm/WindowManagerInternal.java b/com/android/server/wm/WindowManagerInternal.java
new file mode 100644
index 0000000..036f7b0
--- /dev/null
+++ b/com/android/server/wm/WindowManagerInternal.java
@@ -0,0 +1,395 @@
+/*
+ * 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.wm;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ClipData;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.hardware.display.DisplayManagerInternal;
+import android.os.IBinder;
+import android.view.IInputFilter;
+import android.view.IWindow;
+import android.view.MagnificationSpec;
+import android.view.WindowInfo;
+import android.view.animation.Animation;
+
+import com.android.server.policy.WindowManagerPolicy;
+
+import java.util.List;
+
+/**
+ * Window manager local system service interface.
+ *
+ * @hide Only for use within the system server.
+ */
+public abstract class WindowManagerInternal {
+
+    /**
+     * Interface to receive a callback when the windows reported for
+     * accessibility changed.
+     */
+    public interface WindowsForAccessibilityCallback {
+
+        /**
+         * Called when the windows for accessibility changed.
+         *
+         * @param windows The windows for accessibility.
+         */
+        public void onWindowsForAccessibilityChanged(List<WindowInfo> windows);
+    }
+
+    /**
+     * Callbacks for contextual changes that affect the screen magnification
+     * feature.
+     */
+    public interface MagnificationCallbacks {
+
+        /**
+         * Called when the region where magnification operates changes. Note that this isn't the
+         * entire screen. For example, IMEs are not magnified.
+         *
+         * @param magnificationRegion the current magnification region
+         */
+        public void onMagnificationRegionChanged(Region magnificationRegion);
+
+        /**
+         * Called when an application requests a rectangle on the screen to allow
+         * the client to apply the appropriate pan and scale.
+         *
+         * @param left The rectangle left.
+         * @param top The rectangle top.
+         * @param right The rectangle right.
+         * @param bottom The rectangle bottom.
+         */
+        public void onRectangleOnScreenRequested(int left, int top, int right, int bottom);
+
+        /**
+         * Notifies that the rotation changed.
+         *
+         * @param rotation The current rotation.
+         */
+        public void onRotationChanged(int rotation);
+
+        /**
+         * Notifies that the context of the user changed. For example, an application
+         * was started.
+         */
+        public void onUserContextChanged();
+    }
+
+    /**
+     * Abstract class to be notified about {@link com.android.server.wm.AppTransition} events. Held
+     * as an abstract class so a listener only needs to implement the methods of its interest.
+     */
+    public static abstract class AppTransitionListener {
+
+        /**
+         * Called when an app transition is being setup and about to be executed.
+         */
+        public void onAppTransitionPendingLocked() {}
+
+        /**
+         * Called when a pending app transition gets cancelled.
+         *
+         * @param transit transition type indicating what kind of transition got cancelled
+         */
+        public void onAppTransitionCancelledLocked(int transit) {}
+
+        /**
+         * Called when an app transition gets started
+         *
+         * @param transit transition type indicating what kind of transition gets run, must be one
+         *                of AppTransition.TRANSIT_* values
+         * @param openToken the token for the opening app
+         * @param closeToken the token for the closing app
+         * @param openAnimation the animation for the opening app
+         * @param closeAnimation the animation for the closing app
+         *
+         * @return Return any bit set of {@link WindowManagerPolicy#FINISH_LAYOUT_REDO_LAYOUT},
+         * {@link WindowManagerPolicy#FINISH_LAYOUT_REDO_CONFIG},
+         * {@link WindowManagerPolicy#FINISH_LAYOUT_REDO_WALLPAPER},
+         * or {@link WindowManagerPolicy#FINISH_LAYOUT_REDO_ANIM}.
+         */
+        public int onAppTransitionStartingLocked(int transit, IBinder openToken, IBinder closeToken,
+                Animation openAnimation, Animation closeAnimation) {
+            return 0;
+        }
+
+        /**
+         * Called when an app transition is finished running.
+         *
+         * @param token the token for app whose transition has finished
+         */
+        public void onAppTransitionFinishedLocked(IBinder token) {}
+    }
+
+    /**
+      * An interface to be notified about hardware keyboard status.
+      */
+    public interface OnHardKeyboardStatusChangeListener {
+        public void onHardKeyboardStatusChange(boolean available);
+    }
+
+    /**
+     * An interface to customize drag and drop behaviors.
+     */
+    public interface IDragDropCallback {
+        /**
+         * Called when drag operation is started.
+         */
+        default boolean performDrag(IWindow window, IBinder dragToken,
+                int touchSource, float touchX, float touchY, float thumbCenterX, float thumbCenterY,
+                ClipData data) {
+            return true;
+        }
+
+        /**
+         * Called when drop result is reported.
+         */
+        default void reportDropResult(IWindow window, boolean consumed) {}
+
+        /**
+         * Called when drag operation is cancelled.
+         */
+        default void cancelDragAndDrop(IBinder dragToken) {}
+    }
+
+    /**
+     * Request that the window manager call
+     * {@link DisplayManagerInternal#performTraversalInTransactionFromWindowManager}
+     * within a surface transaction at a later time.
+     */
+    public abstract void requestTraversalFromDisplayManager();
+
+    /**
+     * Set by the accessibility layer to observe changes in the magnified region,
+     * rotation, and other window transformations related to display magnification
+     * as the window manager is responsible for doing the actual magnification
+     * and has access to the raw window data while the accessibility layer serves
+     * as a controller.
+     *
+     * @param callbacks The callbacks to invoke.
+     */
+    public abstract void setMagnificationCallbacks(@Nullable MagnificationCallbacks callbacks);
+
+    /**
+     * Set by the accessibility layer to specify the magnification and panning to
+     * be applied to all windows that should be magnified.
+     *
+     * @param spec The MagnficationSpec to set.
+     *
+     * @see #setMagnificationCallbacks(MagnificationCallbacks)
+     */
+    public abstract void setMagnificationSpec(MagnificationSpec spec);
+
+    /**
+     * Set by the accessibility framework to indicate whether the magnifiable regions of the display
+     * should be shown.
+     *
+     * @param show {@code true} to show magnifiable region bounds, {@code false} to hide
+     */
+    public abstract void setForceShowMagnifiableBounds(boolean show);
+
+    /**
+     * Obtains the magnification regions.
+     *
+     * @param magnificationRegion the current magnification region
+     */
+    public abstract void getMagnificationRegion(@NonNull Region magnificationRegion);
+
+    /**
+     * Gets the magnification and translation applied to a window given its token.
+     * Not all windows are magnified and the window manager policy determines which
+     * windows are magnified. The returned result also takes into account the compat
+     * scale if necessary.
+     *
+     * @param windowToken The window's token.
+     *
+     * @return The magnification spec for the window.
+     *
+     * @see #setMagnificationCallbacks(MagnificationCallbacks)
+     */
+    public abstract MagnificationSpec getCompatibleMagnificationSpecForWindow(
+            IBinder windowToken);
+
+    /**
+     * Sets a callback for observing which windows are touchable for the purposes
+     * of accessibility.
+     *
+     * @param callback The callback.
+     */
+    public abstract void setWindowsForAccessibilityCallback(
+            WindowsForAccessibilityCallback callback);
+
+    /**
+     * Sets a filter for manipulating the input event stream.
+     *
+     * @param filter The filter implementation.
+     */
+    public abstract void setInputFilter(IInputFilter filter);
+
+    /**
+     * Gets the token of the window that has input focus.
+     *
+     * @return The token.
+     */
+    public abstract IBinder getFocusedWindowToken();
+
+    /**
+     * @return Whether the keyguard is engaged.
+     */
+    public abstract boolean isKeyguardLocked();
+
+    /**
+    * @return Whether the keyguard is showing and not occluded.
+    */
+    public abstract boolean isKeyguardShowingAndNotOccluded();
+
+    /**
+     * Gets the frame of a window given its token.
+     *
+     * @param token The token.
+     * @param outBounds The frame to populate.
+     */
+    public abstract void getWindowFrame(IBinder token, Rect outBounds);
+
+    /**
+     * Opens the global actions dialog.
+     */
+    public abstract void showGlobalActions();
+
+    /**
+     * Invalidate all visible windows. Then report back on the callback once all windows have
+     * redrawn.
+     */
+    public abstract void waitForAllWindowsDrawn(Runnable callback, long timeout);
+
+    /**
+     * Adds a window token for a given window type.
+     *
+     * @param token The token to add.
+     * @param type The window type.
+     * @param displayId The display to add the token to.
+     */
+    public abstract void addWindowToken(android.os.IBinder token, int type, int displayId);
+
+    /**
+     * Removes a window token.
+     *
+     * @param token The toke to remove.
+     * @param removeWindows Whether to also remove the windows associated with the token.
+     * @param displayId The display to remove the token from.
+     */
+    public abstract void removeWindowToken(android.os.IBinder token, boolean removeWindows,
+            int displayId);
+
+    /**
+     * Registers a listener to be notified about app transition events.
+     *
+     * @param listener The listener to register.
+     */
+    public abstract void registerAppTransitionListener(AppTransitionListener listener);
+
+    /**
+     * Retrieves a height of input method window.
+     */
+    public abstract int getInputMethodWindowVisibleHeight();
+
+    /**
+      * Saves last input method window for transition.
+      *
+      * Note that it is assumed that this method is called only by InputMethodManagerService.
+      */
+    public abstract void saveLastInputMethodWindowForTransition();
+
+    /**
+     * Clears last input method window for transition.
+     *
+     * Note that it is assumed that this method is called only by InputMethodManagerService.
+     */
+    public abstract void clearLastInputMethodWindowForTransition();
+
+    /**
+     * Notifies WindowManagerService that the current IME window status is being changed.
+     *
+     * <p>Only {@link com.android.server.InputMethodManagerService} is the expected and tested
+     * caller of this method.</p>
+     *
+     * @param imeToken token to track the active input method. Corresponding IME windows can be
+     *                 identified by checking {@link android.view.WindowManager.LayoutParams#token}.
+     *                 Note that there is no guarantee that the corresponding window is already
+     *                 created
+     * @param imeWindowVisible whether the active IME thinks that its window should be visible or
+     *                         hidden, no matter how WindowManagerService will react / has reacted
+     *                         to corresponding API calls.  Note that this state is not guaranteed
+     *                         to be synchronized with state in WindowManagerService.
+     * @param dismissImeOnBackKeyPressed {@code true} if the software keyboard is shown and the back
+     *                                   key is expected to dismiss the software keyboard.
+     * @param targetWindowToken token to identify the target window that the IME is associated with.
+     *                          {@code null} when application, system, or the IME itself decided to
+     *                          change its window visibility before being associated with any target
+     *                          window.
+     */
+    public abstract void updateInputMethodWindowStatus(@NonNull IBinder imeToken,
+            boolean imeWindowVisible, boolean dismissImeOnBackKeyPressed,
+            @Nullable IBinder targetWindowToken);
+
+    /**
+      * Returns true when the hardware keyboard is available.
+      */
+    public abstract boolean isHardKeyboardAvailable();
+
+    /**
+      * Sets the callback listener for hardware keyboard status changes.
+      *
+      * @param listener The listener to set.
+      */
+    public abstract void setOnHardKeyboardStatusChangeListener(
+        OnHardKeyboardStatusChangeListener listener);
+
+    /** Returns true if a stack in the windowing mode is currently visible. */
+    public abstract boolean isStackVisible(int windowingMode);
+
+    /**
+     * @return True if and only if the docked divider is currently in resize mode.
+     */
+    public abstract boolean isDockedDividerResizing();
+
+    /**
+     * Requests the window manager to recompute the windows for accessibility.
+     */
+    public abstract void computeWindowsForAccessibility();
+
+    /**
+     * Called after virtual display Id is updated by
+     * {@link com.android.server.vr.Vr2dDisplay} with a specific
+     * {@param vr2dDisplayId}.
+     */
+    public abstract void setVr2dDisplayId(int vr2dDisplayId);
+
+    /**
+     * Sets callback to DragDropController.
+     */
+    public abstract void registerDragDropControllerCallback(IDragDropCallback callback);
+
+    /**
+     * @see android.view.IWindowManager#lockNow
+     */
+    public abstract void lockNow();
+}
diff --git a/com/android/server/wm/WindowManagerService.java b/com/android/server/wm/WindowManagerService.java
index ce34306..82f842c 100644
--- a/com/android/server/wm/WindowManagerService.java
+++ b/com/android/server/wm/WindowManagerService.java
@@ -65,10 +65,10 @@
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
 import static android.view.WindowManagerGlobal.RELAYOUT_DEFER_SURFACE_DESTROY;
 import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED;
-import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
-import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
 import static com.android.server.LockGuard.INDEX_WINDOW;
 import static com.android.server.LockGuard.installLock;
+import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
+import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
 import static com.android.server.wm.AppTransition.TRANSIT_UNSET;
 import static com.android.server.wm.AppWindowAnimator.PROLONG_ANIMATION_AT_END;
 import static com.android.server.wm.AppWindowAnimator.PROLONG_ANIMATION_AT_START;
@@ -126,7 +126,6 @@
 import android.app.IActivityManager;
 import android.app.IAssistDataReceiver;
 import android.content.BroadcastReceiver;
-import android.content.ClipData;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
@@ -219,10 +218,7 @@
 import android.view.WindowManager;
 import android.view.WindowManager.LayoutParams;
 import android.view.WindowManagerGlobal;
-import android.view.WindowManagerInternal;
-import android.view.WindowManagerPolicy;
-import android.view.WindowManagerPolicy.PointerEventListener;
-import android.view.WindowManagerPolicy.ScreenOffListener;
+import android.view.WindowManagerPolicyConstants.PointerEventListener;
 import android.view.animation.Animation;
 import android.view.inputmethod.InputMethodManagerInternal;
 
@@ -245,7 +241,8 @@
 import com.android.server.UiThread;
 import com.android.server.Watchdog;
 import com.android.server.input.InputManagerService;
-import android.view.DisplayFrames;
+import com.android.server.policy.WindowManagerPolicy;
+import com.android.server.policy.WindowManagerPolicy.ScreenOffListener;
 import com.android.server.power.ShutdownThread;
 import com.android.server.utils.PriorityDump;
 
@@ -529,7 +526,6 @@
 
     AccessibilityController mAccessibilityController;
 
-    final SurfaceSession mFxSession;
     Watermark mWatermark;
     StrictModeFlash mStrictModeFlash;
     CircularDisplayMask mCircularDisplayMask;
@@ -804,6 +800,13 @@
     static WindowManagerThreadPriorityBooster sThreadPriorityBooster =
             new WindowManagerThreadPriorityBooster();
 
+    class DefaultSurfaceBuilderFactory implements SurfaceBuilderFactory {
+        public SurfaceControl.Builder make(SurfaceSession s) {
+            return new SurfaceControl.Builder(s);
+        }
+    };
+    SurfaceBuilderFactory mSurfaceBuilderFactory = new DefaultSurfaceBuilderFactory();
+
     static void boostPriorityForLockedSection() {
         sThreadPriorityBooster.boost();
     }
@@ -992,7 +995,6 @@
             mPointerEventDispatcher = null;
         }
 
-        mFxSession = new SurfaceSession();
         mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
         mDisplays = mDisplayManager.getDisplays();
         for (Display display : mDisplays) {
@@ -2478,11 +2480,8 @@
                 mWaitingForConfig = true;
                 displayContent.setLayoutNeeded();
                 int anim[] = new int[2];
-                if (displayContent.isDimming()) {
-                    anim[0] = anim[1] = 0;
-                } else {
-                    mPolicy.selectRotationAnimationLw(anim);
-                }
+                mPolicy.selectRotationAnimationLw(anim);
+
                 startFreezingDisplayLocked(false, anim[0], anim[1], displayContent);
                 config = new Configuration(mTempConfiguration);
             }
@@ -3592,8 +3591,7 @@
                                 com.android.internal.R.dimen.circular_display_mask_thickness);
 
                         mCircularDisplayMask = new CircularDisplayMask(
-                                getDefaultDisplayContentLocked().getDisplay(),
-                                mFxSession,
+                                getDefaultDisplayContentLocked(),
                                 mPolicy.getWindowLayerFromTypeLw(
                                         WindowManager.LayoutParams.TYPE_POINTER)
                                         * TYPE_LAYER_MULTIPLIER + 10, screenOffset, maskThickness);
@@ -3621,8 +3619,7 @@
                 if (mEmulatorDisplayOverlay == null) {
                     mEmulatorDisplayOverlay = new EmulatorDisplayOverlay(
                             mContext,
-                            getDefaultDisplayContentLocked().getDisplay(),
-                            mFxSession,
+                            getDefaultDisplayContentLocked(),
                             mPolicy.getWindowLayerFromTypeLw(
                                     WindowManager.LayoutParams.TYPE_POINTER)
                                     * TYPE_LAYER_MULTIPLIER + 10);
@@ -3670,7 +3667,7 @@
                 // TODO(multi-display): support multiple displays
                 if (mStrictModeFlash == null) {
                     mStrictModeFlash = new StrictModeFlash(
-                            getDefaultDisplayContentLocked().getDisplay(), mFxSession);
+                            getDefaultDisplayContentLocked());
                 }
                 mStrictModeFlash.setVisibility(on);
             } finally {
@@ -3694,9 +3691,8 @@
         }
         try {
             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "screenshotWallpaper");
-            return screenshotApplications(null /* appToken */, DEFAULT_DISPLAY, -1 /* width */,
-                    -1 /* height */, true /* includeFullDisplay */, 1f /* frameScale */,
-                    Bitmap.Config.ARGB_8888, true /* wallpaperOnly */, false /* includeDecor */);
+            return screenshotApplications(DEFAULT_DISPLAY, Bitmap.Config.ARGB_8888,
+                    true /* wallpaperOnly */);
         } finally {
             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         }
@@ -3715,10 +3711,8 @@
         }
 
         FgThread.getHandler().post(() -> {
-            Bitmap bm = screenshotApplications(null /* appToken */, DEFAULT_DISPLAY,
-                    -1 /* width */, -1 /* height */, true /* includeFullDisplay */,
-                    1f /* frameScale */, Bitmap.Config.ARGB_8888, false /* wallpaperOnly */,
-                    false /* includeDecor */);
+            Bitmap bm = screenshotApplications(DEFAULT_DISPLAY, Bitmap.Config.ARGB_8888,
+                    false /* wallpaperOnly */);
             try {
                 receiver.onHandleAssistScreenshot(bm);
             } catch (RemoteException e) {
@@ -3752,29 +3746,21 @@
      * In portrait mode, it grabs the full screenshot.
      *
      * @param displayId the Display to take a screenshot of.
-     * @param width the width of the target bitmap
-     * @param height the height of the target bitmap
-     * @param includeFullDisplay true if the screen should not be cropped before capture
-     * @param frameScale the scale to apply to the frame, only used when width = -1 and height = -1
      * @param config of the output bitmap
      * @param wallpaperOnly true if only the wallpaper layer should be included in the screenshot
-     * @param includeDecor whether to include window decors, like the status or navigation bar
-     *                     background of the window
      */
-    private Bitmap screenshotApplications(IBinder appToken, int displayId, int width,
-            int height, boolean includeFullDisplay, float frameScale, Bitmap.Config config,
-            boolean wallpaperOnly, boolean includeDecor) {
+    private Bitmap screenshotApplications(int displayId, Bitmap.Config config,
+            boolean wallpaperOnly) {
         final DisplayContent displayContent;
         synchronized(mWindowMap) {
             displayContent = mRoot.getDisplayContentOrCreate(displayId);
             if (displayContent == null) {
-                if (DEBUG_SCREENSHOT) Slog.i(TAG_WM, "Screenshot of " + appToken
-                        + ": returning null. No Display for displayId=" + displayId);
+                if (DEBUG_SCREENSHOT) Slog.i(TAG_WM, "Screenshot returning null. No Display for "
+                        + "displayId=" + displayId);
                 return null;
             }
         }
-        return displayContent.screenshotApplications(appToken, width, height,
-                includeFullDisplay, frameScale, config, wallpaperOnly, includeDecor);
+        return displayContent.screenshotDisplay(config, wallpaperOnly);
     }
 
     /**
@@ -4522,7 +4508,7 @@
 
         Display display = displayContent.getDisplay();
         mTaskPositioner = new TaskPositioner(this);
-        mTaskPositioner.register(display);
+        mTaskPositioner.register(displayContent);
         mInputMonitor.updateInputWindowsLw(true /*force*/);
 
         // We need to grab the touch focus so that the touch events during the
@@ -5519,7 +5505,7 @@
     }
 
     void reconfigureDisplayLocked(@NonNull DisplayContent displayContent) {
-        if (!mDisplayReady) {
+        if (!displayContent.isReady()) {
             return;
         }
         displayContent.configureDisplayPolicy();
@@ -5892,7 +5878,7 @@
 
             displayContent.updateDisplayInfo();
             screenRotationAnimation = new ScreenRotationAnimation(mContext, displayContent,
-                    mFxSession, inTransaction, mPolicy.isDefaultOrientationForced(), isSecure,
+                    inTransaction, mPolicy.isDefaultOrientationForced(), isSecure,
                     this);
             mAnimator.setScreenRotationAnimationLocked(mFrozenDisplayId,
                     screenRotationAnimation);
@@ -5952,11 +5938,10 @@
             // TODO(multidisplay): rotation on main screen only.
             DisplayInfo displayInfo = displayContent.getDisplayInfo();
             // Get rotation animation again, with new top window
-            boolean isDimming = displayContent.isDimming();
-            if (!mPolicy.validateRotationAnimationLw(mExitAnimId, mEnterAnimId, isDimming)) {
+            if (!mPolicy.validateRotationAnimationLw(mExitAnimId, mEnterAnimId, false)) {
                 mExitAnimId = mEnterAnimId = 0;
             }
-            if (screenRotationAnimation.dismiss(mFxSession, MAX_ANIMATION_DURATION,
+            if (screenRotationAnimation.dismiss(MAX_ANIMATION_DURATION,
                     getTransitionAnimationScaleLocked(), displayInfo.logicalWidth,
                         displayInfo.logicalHeight, mExitAnimId, mEnterAnimId)) {
                 scheduleAnimationLocked();
@@ -6040,8 +6025,8 @@
                 if (toks != null && toks.length > 0) {
                     // TODO(multi-display): Show watermarks on secondary displays.
                     final DisplayContent displayContent = getDefaultDisplayContentLocked();
-                    mWatermark = new Watermark(displayContent.getDisplay(),
-                            displayContent.mRealDisplayMetrics, mFxSession, toks);
+                    mWatermark = new Watermark(displayContent, displayContent.mRealDisplayMetrics,
+                            toks);
                 }
             }
         } catch (FileNotFoundException e) {
@@ -7456,6 +7441,11 @@
         public void registerDragDropControllerCallback(IDragDropCallback callback) {
             mDragDropController.registerCallback(callback);
         }
+
+        @Override
+        public void lockNow() {
+            WindowManagerService.this.lockNow(null);
+        }
     }
 
     void registerAppFreezeListener(AppFreezeListener listener) {
@@ -7557,4 +7547,13 @@
             w.setForceHideNonSystemOverlayWindowIfNeeded(hideSystemAlertWindows);
         }, false /* traverseTopToBottom */);
     }
+
+    public void applyMagnificationSpec(MagnificationSpec spec) {
+        getDefaultDisplayContentLocked().applyMagnificationSpec(spec);
+    }
+
+    SurfaceControl.Builder makeSurfaceBuilder(SurfaceSession s) {
+        return mSurfaceBuilderFactory.make(s);
+    }
 }
+
diff --git a/com/android/server/wm/WindowManagerShellCommand.java b/com/android/server/wm/WindowManagerShellCommand.java
index 4b98d9d..2358695 100644
--- a/com/android/server/wm/WindowManagerShellCommand.java
+++ b/com/android/server/wm/WindowManagerShellCommand.java
@@ -16,6 +16,8 @@
 
 package com.android.server.wm;
 
+import static android.os.Build.IS_USER;
+
 import android.os.ShellCommand;
 
 import java.io.PrintWriter;
@@ -50,8 +52,10 @@
         pw.println("  help");
         pw.println("    Print this help text.");
         pw.println();
-        pw.println("  tracing (start | stop)");
-        pw.println("    start or stop window tracing");
-        pw.println();
+        if (!IS_USER){
+            pw.println("  tracing (start | stop)");
+            pw.println("    start or stop window tracing");
+            pw.println();
+        }
     }
 }
diff --git a/com/android/server/wm/WindowState.java b/com/android/server/wm/WindowState.java
index 6b1932d..c2ac905 100644
--- a/com/android/server/wm/WindowState.java
+++ b/com/android/server/wm/WindowState.java
@@ -23,6 +23,7 @@
 import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME;
 import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
 import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_VISIBLE;
+import static android.view.SurfaceControl.Transaction;
 import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW;
 import static android.view.WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW;
 import static android.view.WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON;
@@ -41,6 +42,7 @@
 import static android.view.WindowManager.LayoutParams.MATCH_PARENT;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_WILL_NOT_REPLACE_ON_RELAUNCH;
@@ -54,6 +56,10 @@
 import static android.view.WindowManager.LayoutParams.TYPE_DRAWN_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
+import static android.view.WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
+import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
+import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
 import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
 import static android.view.WindowManager.LayoutParams.isSystemAlertWindowType;
@@ -61,10 +67,10 @@
 import static android.view.WindowManagerGlobal.RELAYOUT_RES_DRAG_RESIZING_FREEFORM;
 import static android.view.WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME;
 import static android.view.WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED;
-import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
-import static android.view.WindowManagerPolicy.TRANSIT_ENTER;
-import static android.view.WindowManagerPolicy.TRANSIT_EXIT;
-import static android.view.WindowManagerPolicy.TRANSIT_PREVIEW_DONE;
+import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
+import static com.android.server.policy.WindowManagerPolicy.TRANSIT_ENTER;
+import static com.android.server.policy.WindowManagerPolicy.TRANSIT_EXIT;
+import static com.android.server.policy.WindowManagerPolicy.TRANSIT_PREVIEW_DONE;
 import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER;
 import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_FREEFORM;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE;
@@ -147,14 +153,16 @@
 import android.view.InputChannel;
 import android.view.InputEvent;
 import android.view.InputEventReceiver;
+import android.view.SurfaceControl;
+import android.view.SurfaceSession;
 import android.view.View;
 import android.view.ViewTreeObserver;
 import android.view.WindowInfo;
 import android.view.WindowManager;
-import android.view.WindowManagerPolicy;
 
 import com.android.internal.util.ToBooleanFunction;
 import com.android.server.input.InputWindowHandle;
+import com.android.server.policy.WindowManagerPolicy;
 
 import java.io.PrintWriter;
 import java.lang.ref.WeakReference;
@@ -602,6 +610,14 @@
                 };
             };
 
+    /**
+     * Indicates whether we have requested a Dim (in the sense of {@link Dimmer}) from our host
+     * container.
+     */
+    private boolean mIsDimming = false;
+
+    private static final float DEFAULT_DIM_AMOUNT_DEAD_WINDOW = 0.5f;
+
     WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
            WindowState parentWindow, int appOp, int seq, WindowManager.LayoutParams a,
            int viewVisibility, int ownerId, boolean ownerCanAddInternalSystemWindow) {
@@ -799,7 +815,7 @@
             layoutXDiff = 0;
             layoutYDiff = 0;
         } else {
-            getContainerBounds(mContainingFrame);
+            getBounds(mContainingFrame);
             if (mAppToken != null && !mAppToken.mFrozenBounds.isEmpty()) {
 
                 // If the bounds are frozen, we still want to translate the window freely and only
@@ -957,7 +973,7 @@
             mContentInsets.setEmpty();
             mVisibleInsets.setEmpty();
         } else {
-            getDisplayContent().getLogicalDisplayRect(mTmpRect);
+            getDisplayContent().getBounds(mTmpRect);
             // Override right and/or bottom insets in case if the frame doesn't fit the screen in
             // non-fullscreen mode.
             boolean overrideRightInset = !windowsAreFloating && !inFullscreenContainer
@@ -1029,6 +1045,18 @@
                 + " of=" + mOutsets.toShortString());
     }
 
+    // TODO: Look into whether this override is still necessary.
+    @Override
+    public Rect getBounds() {
+        if (isInMultiWindowMode()) {
+            return getTask().getBounds();
+        } else if (mAppToken != null){
+            return mAppToken.getBounds();
+        } else {
+            return super.getBounds();
+        }
+    }
+
     @Override
     public Rect getFrameLw() {
         return mFrame;
@@ -2034,23 +2062,6 @@
         return isVisibleOrAdding();
     }
 
-    void scheduleAnimationIfDimming() {
-        final DisplayContent dc = getDisplayContent();
-        if (dc == null) {
-            return;
-        }
-
-        // If layout is currently deferred, we want to hold of with updating the layers.
-        if (mService.mWindowPlacerLocked.isLayoutDeferred()) {
-            return;
-        }
-        final DimLayer.DimLayerUser dimLayerUser = getDimLayerUser();
-        if (dimLayerUser != null && dc.mDimLayerController.isDimming(dimLayerUser, mWinAnimator)) {
-            // Force an animation pass just to update the mDimLayer layer.
-            mService.scheduleAnimationLocked();
-        }
-    }
-
     private final class DeadWindowEventReceiver extends InputEventReceiver {
         DeadWindowEventReceiver(InputChannel inputChannel) {
             super(inputChannel, mService.mH.getLooper());
@@ -2106,31 +2117,16 @@
         mInputWindowHandle.inputChannel = null;
     }
 
-    void applyDimLayerIfNeeded() {
-        // When the app is terminated (eg. from Recents), the task might have already been
-        // removed with the window pending removal. Don't apply dim in such cases, as there
-        // will be no more updateDimLayer() calls, which leaves the dimlayer invalid.
-        final AppWindowToken token = mAppToken;
-        if (token != null && token.removed) {
-            return;
-        }
-
-        final DisplayContent dc = getDisplayContent();
-        if (!mAnimatingExit && mAppDied) {
-            // If app died visible, apply a dim over the window to indicate that it's inactive
-            dc.mDimLayerController.applyDimAbove(getDimLayerUser(), mWinAnimator);
-        } else if ((mAttrs.flags & FLAG_DIM_BEHIND) != 0
-                && dc != null && !mAnimatingExit && isVisible()) {
-            dc.mDimLayerController.applyDimBehind(getDimLayerUser(), mWinAnimator);
-        }
-    }
-
-    private DimLayer.DimLayerUser getDimLayerUser() {
+    private Dimmer getDimmer() {
         Task task = getTask();
         if (task != null) {
-            return task;
+            return task.getDimmer();
         }
-        return getStack();
+        TaskStack taskStack = getStack();
+        if (taskStack != null) {
+            return taskStack.getDimmer();
+        }
+        return null;
     }
 
     /** Returns true if the replacement window was removed. */
@@ -2152,9 +2148,6 @@
 
     private void removeReplacedWindow() {
         if (DEBUG_ADD_REMOVE) Slog.d(TAG, "Removing replaced window: " + this);
-        if (isDimming()) {
-            transferDimToReplacement();
-        }
         mWillReplaceWindow = false;
         mAnimateReplacingWindow = false;
         mReplacingRemoveRequested = false;
@@ -2217,11 +2210,11 @@
             // need to intercept touches outside of that window. The dim layer user
             // associated with the window (task or stack) will give us the good bounds, as
             // they would be used to display the dim layer.
-            final DimLayer.DimLayerUser dimLayerUser = getDimLayerUser();
-            if (dimLayerUser != null) {
-                dimLayerUser.getDimBounds(mTmpRect);
+            final Task task = getTask();
+            if (task != null) {
+                task.getDimBounds(mTmpRect);
             } else {
-                getVisibleBounds(mTmpRect);
+                getStack().getDimBounds(mTmpRect);
             }
             if (inFreeformWindowingMode()) {
                 // For freeform windows we the touch region to include the whole surface for the
@@ -2465,7 +2458,7 @@
         mPolicyVisibility = true;
         mPolicyVisibilityAfterAnim = true;
         if (doAnimation) {
-            mWinAnimator.applyAnimationLocked(WindowManagerPolicy.TRANSIT_ENTER, true);
+            mWinAnimator.applyAnimationLocked(TRANSIT_ENTER, true);
         }
         if (requestAnim) {
             mService.scheduleAnimationLocked();
@@ -2493,7 +2486,7 @@
             return false;
         }
         if (doAnimation) {
-            mWinAnimator.applyAnimationLocked(WindowManagerPolicy.TRANSIT_EXIT, false);
+            mWinAnimator.applyAnimationLocked(TRANSIT_EXIT, false);
             if (mWinAnimator.mAnimation == null) {
                 doAnimation = false;
             }
@@ -2729,14 +2722,6 @@
         return displayContent.isDefaultDisplay;
     }
 
-    @Override
-    public boolean isDimming() {
-        final DimLayer.DimLayerUser dimLayerUser = getDimLayerUser();
-        final DisplayContent dc = getDisplayContent();
-        return dimLayerUser != null && dc != null
-                && dc.mDimLayerController.isDimming(dimLayerUser, mWinAnimator);
-    }
-
     void setShowToOwnerOnlyLocked(boolean showToOwnerOnly) {
         mShowToOwnerOnly = showToOwnerOnly;
     }
@@ -2979,33 +2964,12 @@
 
     /** Is this window in a container that takes up the entire screen space? */
     private boolean inFullscreenContainer() {
-        if (mAppToken == null) {
-            return true;
-        }
-        if (mAppToken.hasBounds()) {
-            return false;
-        }
-        return !isInMultiWindowMode();
+        return mAppToken == null || (mAppToken.matchParentBounds() && !isInMultiWindowMode());
     }
 
     /** @return true when the window is in fullscreen task, but has non-fullscreen bounds set. */
     boolean isLetterboxedAppWindow() {
-        final Task task = getTask();
-        final boolean taskIsFullscreen = task != null && task.isFullscreen();
-        final boolean appWindowIsFullscreen = mAppToken != null && !mAppToken.hasBounds();
-
-        return taskIsFullscreen && !appWindowIsFullscreen;
-    }
-
-    /** Returns the appropriate bounds to use for computing frames. */
-    private void getContainerBounds(Rect outBounds) {
-        if (isInMultiWindowMode()) {
-            getTask().getBounds(outBounds);
-        } else if (mAppToken != null){
-            mAppToken.getBounds(outBounds);
-        } else {
-            outBounds.setEmpty();
-        }
+        return !isInMultiWindowMode() && mAppToken != null && !mAppToken.matchParentBounds();
     }
 
     boolean isDragResizeChanged() {
@@ -3071,7 +3035,7 @@
         if (task == null) {
             return false;
         }
-        if (!inSplitScreenWindowingMode()) {
+        if (!inSplitScreenWindowingMode() && !inFreeformWindowingMode()) {
             return false;
         }
         if (mAttrs.width != MATCH_PARENT || mAttrs.height != MATCH_PARENT) {
@@ -3591,15 +3555,6 @@
         return winY;
     }
 
-    private void transferDimToReplacement() {
-        final DimLayer.DimLayerUser dimLayerUser = getDimLayerUser();
-        final DisplayContent dc = getDisplayContent();
-        if (dimLayerUser != null && dc != null) {
-            dc.mDimLayerController.applyDim(dimLayerUser,
-                    mReplacementWindow.mWinAnimator, (mAttrs.flags & FLAG_DIM_BEHIND) != 0);
-        }
-    }
-
     // During activity relaunch due to resize, we sometimes use window replacement
     // for only child windows (as the main window is handled by window preservation)
     // and the big surface.
@@ -4156,9 +4111,6 @@
             policyCrop.intersect(-mCompatFrame.left, -mCompatFrame.top,
                     displayInfo.logicalWidth - mCompatFrame.left,
                     displayInfo.logicalHeight - mCompatFrame.top);
-        } else if (mLayer >= mService.mSystemDecorLayer) {
-            // Above the decor layer is easy, just use the entire window
-            policyCrop.set(0, 0, mCompatFrame.width(), mCompatFrame.height());
         } else if (mDecorFrame.isEmpty()) {
             // Windows without policy decor aren't cropped.
             policyCrop.set(0, 0, mCompatFrame.width(), mCompatFrame.height());
@@ -4388,4 +4340,78 @@
             return false;
         }
     }
+
+    @Override
+    boolean shouldMagnify() {
+        if (mAttrs.type == TYPE_INPUT_METHOD ||
+                mAttrs.type == TYPE_INPUT_METHOD_DIALOG ||
+                mAttrs.type == TYPE_MAGNIFICATION_OVERLAY ||
+                mAttrs.type == TYPE_NAVIGATION_BAR ||
+                // It's tempting to wonder: Have we forgotten the rounded corners overlay?
+                // worry not: it's a fake TYPE_NAVIGATION_BAR_PANEL
+                mAttrs.type == TYPE_NAVIGATION_BAR_PANEL ||
+                mAttrs.type == TYPE_STATUS_BAR) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    SurfaceSession getSession() {
+        if (mSession.mSurfaceSession != null) {
+            return mSession.mSurfaceSession;
+        } else {
+            return getParent().getSession();
+        }
+    }
+
+    @Override
+    boolean needsZBoost() {
+        return getAnimLayerAdjustment() > 0 || mWillReplaceWindow;
+    }
+
+    @Override
+    SurfaceControl.Builder makeSurface() {
+        return getParent().makeChildSurface(this);
+    }
+
+    private void applyDims(Dimmer dimmer) {
+        if (!mAnimatingExit && mAppDied) {
+            mIsDimming = true;
+            getDimmer().dimAbove(getPendingTransaction(), this, DEFAULT_DIM_AMOUNT_DEAD_WINDOW);
+        } else if ((mAttrs.flags & FLAG_DIM_BEHIND) != 0
+                && !mAnimatingExit && isVisible()) {
+            mIsDimming = true;
+            getDimmer().dimBelow(getPendingTransaction(), this, mAttrs.dimAmount);
+        }
+    }
+
+    @Override
+    void prepareSurfaces() {
+        final Dimmer dimmer = getDimmer();
+        mIsDimming = false;
+        if (dimmer != null) {
+            applyDims(dimmer);
+        }
+
+        mWinAnimator.prepareSurfaceLocked(true);
+        super.prepareSurfaces();
+    }
+
+    @Override
+    void assignLayer(Transaction t, int layer) {
+        // See comment in assignRelativeLayerForImeTargetChild
+        if (!isChildWindow()
+                || (mService.mInputMethodTarget != getParentWindow())
+                || !inSplitScreenWindowingMode()) {
+            super.assignLayer(t, layer);
+            return;
+        }
+        getDisplayContent().assignRelativeLayerForImeTargetChild(t, this);
+    }
+
+    @Override
+    public boolean isDimming() {
+        return mIsDimming;
+    }
 }
diff --git a/com/android/server/wm/WindowStateAnimator.java b/com/android/server/wm/WindowStateAnimator.java
index 86397ae..4b5661f 100644
--- a/com/android/server/wm/WindowStateAnimator.java
+++ b/com/android/server/wm/WindowStateAnimator.java
@@ -60,17 +60,17 @@
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 import android.view.DisplayInfo;
-import android.view.MagnificationSpec;
 import android.view.Surface.OutOfResourcesException;
 import android.view.SurfaceControl;
 import android.view.WindowManager;
 import android.view.WindowManager.LayoutParams;
-import android.view.WindowManagerPolicy;
 import android.view.animation.Animation;
 import android.view.animation.AnimationSet;
 import android.view.animation.AnimationUtils;
 import android.view.animation.Transformation;
 
+import com.android.server.policy.WindowManagerPolicy;
+
 import java.io.PrintWriter;
 import java.io.FileDescriptor;
 
@@ -559,7 +559,10 @@
         }
         if (SHOW_TRANSACTIONS) WindowManagerService.logSurface(mWin, "SET FREEZE LAYER", false);
         if (mSurfaceController != null) {
-            mSurfaceController.setLayer(mAnimLayer + 1);
+            // Our SurfaceControl is always at layer 0 within the parent Surface managed by
+            // window-state. We want this old Surface to stay on top of the new one
+            // until we do the swap, so we place it at layer 1.
+            mSurfaceController.mSurfaceControl.setLayer(1);
         }
         mDestroyPreservedSurfaceUponRedraw = true;
         mSurfaceDestroyDeferred = true;
@@ -730,7 +733,6 @@
         try {
             mSurfaceController.setPositionInTransaction(mTmpSize.left, mTmpSize.top, false);
             mSurfaceController.setLayerStackInTransaction(getLayerStack());
-            mSurfaceController.setLayer(mAnimLayer);
         } finally {
             mService.closeSurfaceTransaction("createSurfaceLocked");
         }
@@ -867,29 +869,11 @@
         mPendingDestroySurface = null;
     }
 
-    void applyMagnificationSpec(MagnificationSpec spec, Matrix transform) {
-        final int surfaceInsetLeft = mWin.mAttrs.surfaceInsets.left;
-        final int surfaceInsetTop = mWin.mAttrs.surfaceInsets.top;
-
-        if (spec != null && !spec.isNop()) {
-            float scale = spec.scale;
-            transform.postScale(scale, scale);
-            transform.postTranslate(spec.offsetX, spec.offsetY);
-
-            // As we are scaling the whole surface, to keep the content
-            // in the same position we will also have to scale the surfaceInsets.
-            transform.postTranslate(-(surfaceInsetLeft*scale - surfaceInsetLeft),
-                    -(surfaceInsetTop*scale - surfaceInsetTop));
-        }
-    }
-
     void computeShownFrameLocked() {
         final boolean selfTransformation = mHasLocalTransformation;
-        Transformation attachedTransformation =
-                (mParentWinAnimator != null && mParentWinAnimator.mHasLocalTransformation)
-                ? mParentWinAnimator.mTransformation : null;
         Transformation appTransformation = (mAppAnimator != null && mAppAnimator.hasTransformation)
                 ? mAppAnimator.transformation : null;
+        Transformation wallpaperTargetTransformation = null;
 
         // Wallpapers are animated based on the "real" window they
         // are currently targeting.
@@ -899,9 +883,9 @@
             if (wallpaperAnimator.mHasLocalTransformation &&
                     wallpaperAnimator.mAnimation != null &&
                     !wallpaperAnimator.mAnimation.getDetachWallpaper()) {
-                attachedTransformation = wallpaperAnimator.mTransformation;
-                if (DEBUG_WALLPAPER && attachedTransformation != null) {
-                    Slog.v(TAG, "WP target attached xform: " + attachedTransformation);
+                wallpaperTargetTransformation = wallpaperAnimator.mTransformation;
+                if (DEBUG_WALLPAPER && wallpaperTargetTransformation != null) {
+                    Slog.v(TAG, "WP target attached xform: " + wallpaperTargetTransformation);
                 }
             }
             final AppWindowAnimator wpAppAnimator = wallpaperTarget.mAppToken == null ?
@@ -923,7 +907,7 @@
                 screenRotationAnimation != null && screenRotationAnimation.isAnimating();
 
         mHasClipRect = false;
-        if (selfTransformation || attachedTransformation != null
+        if (selfTransformation || wallpaperTargetTransformation != null
                 || appTransformation != null || screenAnimation) {
             // cache often used attributes locally
             final Rect frame = mWin.mFrame;
@@ -953,27 +937,31 @@
             if (selfTransformation) {
                 tmpMatrix.postConcat(mTransformation.getMatrix());
             }
-            if (attachedTransformation != null) {
-                tmpMatrix.postConcat(attachedTransformation.getMatrix());
+
+            if (wallpaperTargetTransformation != null) {
+                tmpMatrix.postConcat(wallpaperTargetTransformation.getMatrix());
             }
             if (appTransformation != null) {
                 tmpMatrix.postConcat(appTransformation.getMatrix());
             }
 
+            int left = frame.left;
+            int top = frame.top;
+            if (mWin.isChildWindow()) {
+                WindowState parent = mWin.getParentWindow();
+                left -= parent.mFrame.left;
+                top  -= parent.mFrame.top;
+            }
+
             // The translation that applies the position of the window needs to be applied at the
             // end in case that other translations include scaling. Otherwise the scaling will
             // affect this translation. But it needs to be set before the screen rotation animation
             // so the pivot point is at the center of the screen for all windows.
-            tmpMatrix.postTranslate(frame.left + mWin.mXOffset, frame.top + mWin.mYOffset);
+            tmpMatrix.postTranslate(left + mWin.mXOffset, top + mWin.mYOffset);
             if (screenAnimation) {
                 tmpMatrix.postConcat(screenRotationAnimation.getEnterTransformation().getMatrix());
             }
 
-            MagnificationSpec spec = getMagnificationSpec();
-            if (spec != null) {
-                applyMagnificationSpec(spec, tmpMatrix);
-            }
-
             // "convert" it into SurfaceFlinger's format
             // (a 2x2 matrix + an offset)
             // Here we must not transform the position of the surface
@@ -1004,8 +992,8 @@
                 if (selfTransformation) {
                     mShownAlpha *= mTransformation.getAlpha();
                 }
-                if (attachedTransformation != null) {
-                    mShownAlpha *= attachedTransformation.getAlpha();
+                if (wallpaperTargetTransformation != null) {
+                    mShownAlpha *= wallpaperTargetTransformation.getAlpha();
                 }
                 if (appTransformation != null) {
                     mShownAlpha *= appTransformation.getAlpha();
@@ -1036,8 +1024,8 @@
                     && (mShownAlpha == 1.0 || mShownAlpha == 0.0)) Slog.v(
                     TAG, "computeShownFrameLocked: Animating " + this + " mAlpha=" + mAlpha
                     + " self=" + (selfTransformation ? mTransformation.getAlpha() : "null")
-                    + " attached=" + (attachedTransformation == null ?
-                            "null" : attachedTransformation.getAlpha())
+                    + " attached=" + (wallpaperTargetTransformation == null ?
+                            "null" : wallpaperTargetTransformation.getAlpha())
                     + " app=" + (appTransformation == null ? "null" : appTransformation.getAlpha())
                     + " screen=" + (screenAnimation ?
                             screenRotationAnimation.getEnterTransformation().getAlpha() : "null"));
@@ -1057,49 +1045,16 @@
                 TAG, "computeShownFrameLocked: " + this +
                 " not attached, mAlpha=" + mAlpha);
 
-        MagnificationSpec spec = getMagnificationSpec();
-        if (spec != null) {
-            final Rect frame = mWin.mFrame;
-            final float tmpFloats[] = mService.mTmpFloats;
-            final Matrix tmpMatrix = mWin.mTmpMatrix;
-
-            tmpMatrix.setScale(mWin.mGlobalScale, mWin.mGlobalScale);
-            tmpMatrix.postTranslate(frame.left + mWin.mXOffset, frame.top + mWin.mYOffset);
-
-            applyMagnificationSpec(spec, tmpMatrix);
-
-            tmpMatrix.getValues(tmpFloats);
-
-            mHaveMatrix = true;
-            mDsDx = tmpFloats[Matrix.MSCALE_X];
-            mDtDx = tmpFloats[Matrix.MSKEW_Y];
-            mDtDy = tmpFloats[Matrix.MSKEW_X];
-            mDsDy = tmpFloats[Matrix.MSCALE_Y];
-            float x = tmpFloats[Matrix.MTRANS_X];
-            float y = tmpFloats[Matrix.MTRANS_Y];
-            mWin.mShownPosition.set(Math.round(x), Math.round(y));
-
-            mShownAlpha = mAlpha;
-        } else {
-            mWin.mShownPosition.set(mWin.mFrame.left, mWin.mFrame.top);
-            if (mWin.mXOffset != 0 || mWin.mYOffset != 0) {
-                mWin.mShownPosition.offset(mWin.mXOffset, mWin.mYOffset);
-            }
-            mShownAlpha = mAlpha;
-            mHaveMatrix = false;
-            mDsDx = mWin.mGlobalScale;
-            mDtDx = 0;
-            mDtDy = 0;
-            mDsDy = mWin.mGlobalScale;
+        mWin.mShownPosition.set(mWin.mFrame.left, mWin.mFrame.top);
+        if (mWin.mXOffset != 0 || mWin.mYOffset != 0) {
+            mWin.mShownPosition.offset(mWin.mXOffset, mWin.mYOffset);
         }
-    }
-
-    private MagnificationSpec getMagnificationSpec() {
-        //TODO (multidisplay): Magnification is supported only for the default display.
-        if (mService.mAccessibilityController != null && mWin.getDisplayId() == DEFAULT_DISPLAY) {
-            return mService.mAccessibilityController.getMagnificationSpecForWindowLocked(mWin);
-        }
-        return null;
+        mShownAlpha = mAlpha;
+        mHaveMatrix = false;
+        mDsDx = mWin.mGlobalScale;
+        mDtDx = 0;
+        mDtDy = 0;
+        mDsDy = mWin.mGlobalScale;
     }
 
     /**
@@ -1140,26 +1095,6 @@
             w.expandForSurfaceInsets(finalClipRect);
         }
 
-        // We may be applying a magnification spec to all windows,
-        // simulating a transformation in screen space, in which case
-        // we need to transform all other screen space values...including
-        // the final crop. This is kind of messed up and we should look
-        // in to actually transforming screen-space via a parent-layer.
-        // b/38322835
-        MagnificationSpec spec = getMagnificationSpec();
-        if (spec != null && !spec.isNop()) {
-            Matrix transform = mWin.mTmpMatrix;
-            RectF finalCrop = mService.mTmpRectF;
-            transform.reset();
-            transform.postScale(spec.scale, spec.scale);
-            transform.postTranslate(-spec.offsetX, -spec.offsetY);
-            transform.mapRect(finalCrop);
-            finalClipRect.top = (int) finalCrop.top;
-            finalClipRect.left = (int) finalCrop.left;
-            finalClipRect.right = (int) finalCrop.right;
-            finalClipRect.bottom = (int) finalCrop.bottom;
-        }
-
         return true;
     }
 
@@ -1517,7 +1452,6 @@
             mReportSurfaceResized = true;
             mAnimator.setPendingLayoutChanges(w.getDisplayId(),
                     WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER);
-            w.applyDimLayerIfNeeded();
         }
     }
 
@@ -1615,7 +1549,6 @@
                         mDtDy * w.mHScale * mExtraHScale,
                         mDsDy * w.mVScale * mExtraVScale,
                         recoveringMemory);
-            mSurfaceController.setLayer(mAnimLayer);
 
             if (prepared && mDrawState == HAS_DRAWN) {
                 if (mLastHidden) {
@@ -2045,7 +1978,7 @@
         final float width = w.mFrame.width();
         final float height = w.mFrame.height();
 
-        mService.getDefaultDisplayContentLocked().getLogicalDisplayRect(displayRect);
+        mService.getDefaultDisplayContentLocked().getBounds(displayRect);
         final float displayWidth = displayRect.width();
         final float displayHeight = displayRect.height();
 
diff --git a/com/android/server/wm/WindowSurfaceController.java b/com/android/server/wm/WindowSurfaceController.java
index a214523..6746754 100644
--- a/com/android/server/wm/WindowSurfaceController.java
+++ b/com/android/server/wm/WindowSurfaceController.java
@@ -53,7 +53,7 @@
 
     final WindowStateAnimator mAnimator;
 
-    private SurfaceControlWithBackground mSurfaceControl;
+    SurfaceControlWithBackground mSurfaceControl;
 
     // Should only be set from within setShown().
     private boolean mSurfaceShown = false;
@@ -101,7 +101,8 @@
         mWindowSession = win.mSession;
 
         Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "new SurfaceControl");
-        final SurfaceControl.Builder b = new SurfaceControl.Builder(s)
+        final SurfaceControl.Builder b = win.makeSurface()
+                .setParent(win.getSurfaceControl())
                 .setName(name)
                 .setSize(w, h)
                 .setFormat(format)
@@ -245,25 +246,6 @@
         }
     }
 
-    void setLayer(int layer) {
-        if (mSurfaceControl != null) {
-            mService.openSurfaceTransaction();
-            try {
-                if (mAnimator.mWin.usesRelativeZOrdering()) {
-                    mSurfaceControl.setRelativeLayer(
-                            mAnimator.mWin.getParentWindow()
-                            .mWinAnimator.mSurfaceController.mSurfaceControl,
-                            -1);
-                } else {
-                    mSurfaceLayer = layer;
-                    mSurfaceControl.setLayer(layer);
-                }
-            } finally {
-                mService.closeSurfaceTransaction("setLayer");
-            }
-        }
-    }
-
     void setLayerStackInTransaction(int layerStack) {
         if (mSurfaceControl != null) {
             mSurfaceControl.setLayerStack(layerStack);
diff --git a/com/android/server/wm/WindowSurfacePlacer.java b/com/android/server/wm/WindowSurfacePlacer.java
index cd5e475..169d0a3 100644
--- a/com/android/server/wm/WindowSurfacePlacer.java
+++ b/com/android/server/wm/WindowSurfacePlacer.java
@@ -20,8 +20,8 @@
 import static android.app.ActivityManagerInternal.APP_TRANSITION_SNAPSHOT;
 import static android.app.ActivityManagerInternal.APP_TRANSITION_SPLASH_SCREEN;
 import static android.app.ActivityManagerInternal.APP_TRANSITION_WINDOWS_DRAWN;
-import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG;
-import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
+import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_CONFIG;
+import static com.android.server.policy.WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT;
 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_FLAG_KEYGUARD_GOING_AWAY_NO_ANIMATION;
@@ -704,7 +704,7 @@
 
             // Create a new surface for the thumbnail
             WindowState window = appToken.findMainWindow();
-            final SurfaceControl surfaceControl = new SurfaceControl.Builder(mService.mFxSession)
+            final SurfaceControl surfaceControl = appToken.makeSurface()
                     .setName("thumbnail anim")
                     .setSize(dirty.width(), dirty.height())
                     .setFormat(PixelFormat.TRANSLUCENT)
@@ -712,7 +712,6 @@
                             window != null ? window.mOwnerUid : Binder.getCallingUid())
                     .build();
 
-            surfaceControl.setLayerStack(display.getLayerStack());
             if (SHOW_TRANSACTIONS) {
                 Slog.i(TAG, "  THUMBNAIL " + surfaceControl + ": CREATE");
             }
@@ -750,10 +749,13 @@
             anim.restrictDuration(MAX_ANIMATION_DURATION);
             anim.scaleCurrentDuration(mService.getTransitionAnimationScaleLocked());
 
-            openingAppAnimator.updateThumbnailLayer();
             openingAppAnimator.thumbnail = surfaceControl;
             openingAppAnimator.thumbnailAnimation = anim;
             mService.mAppTransition.getNextAppTransitionStartRect(taskId, mTmpStartRect);
+
+            // We parent the thumbnail to the app token, and just place it
+            // on top of anything else in the app token.
+            surfaceControl.setLayer(Integer.MAX_VALUE);
         } catch (Surface.OutOfResourcesException e) {
             Slog.e(TAG, "Can't allocate thumbnail/Canvas surface w="
                     + dirty.width() + " h=" + dirty.height(), e);
diff --git a/com/android/server/wm/WindowTracing.java b/com/android/server/wm/WindowTracing.java
index 5657f6c..c858b19 100644
--- a/com/android/server/wm/WindowTracing.java
+++ b/com/android/server/wm/WindowTracing.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static android.os.Build.IS_USER;
 import static com.android.server.wm.proto.WindowManagerTraceFileProto.ENTRY;
 import static com.android.server.wm.proto.WindowManagerTraceFileProto.MAGIC_NUMBER;
 import static com.android.server.wm.proto.WindowManagerTraceFileProto.MAGIC_NUMBER_H;
@@ -62,11 +63,16 @@
     }
 
     void startTrace(PrintWriter pw) throws IOException {
+        if (IS_USER){
+            logAndPrintln(pw, "Error: Tracing is not supported on user builds.");
+            return;
+        }
         synchronized (mLock) {
             logAndPrintln(pw, "Start tracing to " + mTraceFile + ".");
             mWriteQueue.clear();
             mTraceFile.delete();
             try (OutputStream os = new FileOutputStream(mTraceFile)) {
+                mTraceFile.setReadable(true, false);
                 ProtoOutputStream proto = new ProtoOutputStream(os);
                 proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE);
                 proto.flush();
@@ -82,6 +88,10 @@
     }
 
     void stopTrace(PrintWriter pw) {
+        if (IS_USER){
+            logAndPrintln(pw, "Error: Tracing is not supported on user builds.");
+            return;
+        }
         synchronized (mLock) {
             logAndPrintln(pw, "Stop tracing to " + mTraceFile + ". Waiting for traces to flush.");
             mEnabled = mEnabledLockFree = false;
@@ -147,9 +157,11 @@
     }
 
     static WindowTracing createDefaultAndStartLooper(Context context) {
-        File file = new File("/data/system/window_trace.proto");
+        File file = new File("/data/misc/wmtrace/wm_trace.pb");
         WindowTracing windowTracing = new WindowTracing(file);
-        new Thread(windowTracing::loop, "window_tracing").start();
+        if (!IS_USER){
+            new Thread(windowTracing::loop, "window_tracing").start();
+        }
         return windowTracing;
     }
 
diff --git a/com/android/settingslib/Utils.java b/com/android/settingslib/Utils.java
index 2186169..eb33842 100644
--- a/com/android/settingslib/Utils.java
+++ b/com/android/settingslib/Utils.java
@@ -105,7 +105,8 @@
             }
         }
         return new UserIconDrawable(iconSize).setIconDrawable(
-                UserIcons.getDefaultUserIcon(user.id, /* light= */ false)).bake();
+                UserIcons.getDefaultUserIcon(context.getResources(), user.id, /* light= */ false))
+                .bake();
     }
 
     /** Formats a double from 0.0..100.0 with an option to round **/
diff --git a/com/android/settingslib/drawer/CategoryManager.java b/com/android/settingslib/drawer/CategoryManager.java
index ee7885d..0703330 100644
--- a/com/android/settingslib/drawer/CategoryManager.java
+++ b/com/android/settingslib/drawer/CategoryManager.java
@@ -18,7 +18,6 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.support.annotation.VisibleForTesting;
-import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.Log;
@@ -27,7 +26,6 @@
 import com.android.settingslib.applications.InterestingConfigChanges;
 
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -104,10 +102,10 @@
         }
         for (int i = 0; i < mCategories.size(); i++) {
             DashboardCategory category = mCategories.get(i);
-            for (int j = 0; j < category.tiles.size(); j++) {
-                Tile tile = category.tiles.get(j);
+            for (int j = 0; j < category.getTilesCount(); j++) {
+                Tile tile = category.getTile(j);
                 if (tileBlacklist.contains(tile.intent.getComponent())) {
-                    category.tiles.remove(j--);
+                    category.removeTile(j--);
                 }
             }
         }
@@ -181,7 +179,7 @@
                         newCategory = new DashboardCategory();
                         categoryByKeyMap.put(newCategoryKey, newCategory);
                     }
-                    newCategory.tiles.add(tile);
+                    newCategory.addTile(tile);
                 }
             }
         }
@@ -198,7 +196,7 @@
     synchronized void sortCategories(Context context,
             Map<String, DashboardCategory> categoryByKeyMap) {
         for (Entry<String, DashboardCategory> categoryEntry : categoryByKeyMap.entrySet()) {
-            sortCategoriesForExternalTiles(context, categoryEntry.getValue());
+            categoryEntry.getValue().sortTiles(context.getPackageName());
         }
     }
 
@@ -210,16 +208,16 @@
     synchronized void filterDuplicateTiles(Map<String, DashboardCategory> categoryByKeyMap) {
         for (Entry<String, DashboardCategory> categoryEntry : categoryByKeyMap.entrySet()) {
             final DashboardCategory category = categoryEntry.getValue();
-            final int count = category.tiles.size();
+            final int count = category.getTilesCount();
             final Set<ComponentName> components = new ArraySet<>();
             for (int i = count - 1; i >= 0; i--) {
-                final Tile tile = category.tiles.get(i);
+                final Tile tile = category.getTile(i);
                 if (tile.intent == null) {
                     continue;
                 }
                 final ComponentName tileComponent = tile.intent.getComponent();
                 if (components.contains(tileComponent)) {
-                    category.tiles.remove(i);
+                    category.removeTile(i);
                 } else {
                     components.add(tileComponent);
                 }
@@ -234,28 +232,7 @@
      */
     private synchronized void sortCategoriesForExternalTiles(Context context,
             DashboardCategory dashboardCategory) {
-        final String skipPackageName = context.getPackageName();
+        dashboardCategory.sortTiles(context.getPackageName());
 
-        // Sort tiles based on [priority, package within priority]
-        Collections.sort(dashboardCategory.tiles, (tile1, tile2) -> {
-            final String package1 = tile1.intent.getComponent().getPackageName();
-            final String package2 = tile2.intent.getComponent().getPackageName();
-            final int packageCompare = CASE_INSENSITIVE_ORDER.compare(package1, package2);
-            // First sort by priority
-            final int priorityCompare = tile2.priority - tile1.priority;
-            if (priorityCompare != 0) {
-                return priorityCompare;
-            }
-            // Then sort by package name, skip package take precedence
-            if (packageCompare != 0) {
-                if (TextUtils.equals(package1, skipPackageName)) {
-                    return -1;
-                }
-                if (TextUtils.equals(package2, skipPackageName)) {
-                    return 1;
-                }
-            }
-            return packageCompare;
-        });
     }
 }
diff --git a/com/android/settingslib/drawer/DashboardCategory.java b/com/android/settingslib/drawer/DashboardCategory.java
index f6f8168..a966e82 100644
--- a/com/android/settingslib/drawer/DashboardCategory.java
+++ b/com/android/settingslib/drawer/DashboardCategory.java
@@ -16,6 +16,8 @@
 
 package com.android.settingslib.drawer;
 
+import static java.lang.String.CASE_INSENSITIVE_ORDER;
+
 import android.content.ComponentName;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -23,6 +25,8 @@
 import android.util.Log;
 
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
 import java.util.List;
 
 public class DashboardCategory implements Parcelable {
@@ -48,39 +52,59 @@
     /**
      * List of the category's children
      */
-    public List<Tile> tiles = new ArrayList<>();
+    private List<Tile> mTiles = new ArrayList<>();
 
+    DashboardCategory(DashboardCategory in) {
+        if (in != null) {
+            title = in.title;
+            key = in.key;
+            priority = in.priority;
+            for (Tile tile : in.mTiles) {
+                mTiles.add(tile);
+            }
+        }
+    }
 
     public DashboardCategory() {
         // Empty
     }
 
+    /**
+     * Get a copy of the list of the category's children.
+     *
+     * Note: the returned list serves as a read-only list. If tiles needs to be added or removed
+     * from the actual tiles list, it should be done through {@link #addTile}, {@link #removeTile}.
+     */
+    public List<Tile> getTiles() {
+        return Collections.unmodifiableList(mTiles);
+    }
+
     public void addTile(Tile tile) {
-        tiles.add(tile);
+        mTiles.add(tile);
     }
 
     public void addTile(int n, Tile tile) {
-        tiles.add(n, tile);
+        mTiles.add(n, tile);
     }
 
     public void removeTile(Tile tile) {
-        tiles.remove(tile);
+        mTiles.remove(tile);
     }
 
     public void removeTile(int n) {
-        tiles.remove(n);
+        mTiles.remove(n);
     }
 
     public int getTilesCount() {
-        return tiles.size();
+        return mTiles.size();
     }
 
     public Tile getTile(int n) {
-        return tiles.get(n);
+        return mTiles.get(n);
     }
 
     public boolean containsComponent(ComponentName component) {
-        for (Tile tile : tiles) {
+        for (Tile tile : mTiles) {
             if (TextUtils.equals(tile.intent.getComponent().getClassName(),
                     component.getClassName())) {
                 if (DEBUG) {
@@ -95,6 +119,40 @@
         return false;
     }
 
+    /**
+     * Sort priority value for tiles in this category.
+     */
+    public void sortTiles() {
+        Collections.sort(mTiles, TILE_COMPARATOR);
+    }
+
+    /**
+     * Sort priority value and package name for tiles in this category.
+     */
+    public void sortTiles(String skipPackageName) {
+        // Sort mTiles based on [priority, package within priority]
+        Collections.sort(mTiles, (tile1, tile2) -> {
+            final String package1 = tile1.intent.getComponent().getPackageName();
+            final String package2 = tile2.intent.getComponent().getPackageName();
+            final int packageCompare = CASE_INSENSITIVE_ORDER.compare(package1, package2);
+            // First sort by priority
+            final int priorityCompare = tile2.priority - tile1.priority;
+            if (priorityCompare != 0) {
+                return priorityCompare;
+            }
+            // Then sort by package name, skip package take precedence
+            if (packageCompare != 0) {
+                if (TextUtils.equals(package1, skipPackageName)) {
+                    return -1;
+                }
+                if (TextUtils.equals(package2, skipPackageName)) {
+                    return 1;
+                }
+            }
+            return packageCompare;
+        });
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -106,11 +164,11 @@
         dest.writeString(key);
         dest.writeInt(priority);
 
-        final int count = tiles.size();
+        final int count = mTiles.size();
         dest.writeInt(count);
 
         for (int n = 0; n < count; n++) {
-            Tile tile = tiles.get(n);
+            Tile tile = mTiles.get(n);
             tile.writeToParcel(dest, flags);
         }
     }
@@ -124,7 +182,7 @@
 
         for (int n = 0; n < count; n++) {
             Tile tile = Tile.CREATOR.createFromParcel(in);
-            tiles.add(tile);
+            mTiles.add(tile);
         }
     }
 
@@ -141,4 +199,13 @@
             return new DashboardCategory[size];
         }
     };
+
+    public static final Comparator<Tile> TILE_COMPARATOR =
+            new Comparator<Tile>() {
+                @Override
+                public int compare(Tile lhs, Tile rhs) {
+                    return rhs.priority - lhs.priority;
+                }
+            };
+
 }
diff --git a/com/android/settingslib/drawer/TileUtils.java b/com/android/settingslib/drawer/TileUtils.java
index 038dcf8..e986e0f 100644
--- a/com/android/settingslib/drawer/TileUtils.java
+++ b/com/android/settingslib/drawer/TileUtils.java
@@ -253,7 +253,7 @@
         }
         ArrayList<DashboardCategory> categories = new ArrayList<>(categoryMap.values());
         for (DashboardCategory category : categories) {
-            Collections.sort(category.tiles, TILE_COMPARATOR);
+            category.sortTiles();
         }
         Collections.sort(categories, CATEGORY_COMPARATOR);
         if (DEBUG_TIMING) Log.d(LOG_TAG, "getCategories took "
@@ -595,14 +595,6 @@
         return pathSegments.get(0);
     }
 
-    public static final Comparator<Tile> TILE_COMPARATOR =
-            new Comparator<Tile>() {
-        @Override
-        public int compare(Tile lhs, Tile rhs) {
-            return rhs.priority - lhs.priority;
-        }
-    };
-
     private static final Comparator<DashboardCategory> CATEGORY_COMPARATOR =
             new Comparator<DashboardCategory>() {
         @Override
diff --git a/com/android/settingslib/drawer/UserAdapter.java b/com/android/settingslib/drawer/UserAdapter.java
index b27d823..8a09df2 100644
--- a/com/android/settingslib/drawer/UserAdapter.java
+++ b/com/android/settingslib/drawer/UserAdapter.java
@@ -64,7 +64,8 @@
                 if (um.getUserIcon(userId) != null) {
                     icon = new BitmapDrawable(context.getResources(), um.getUserIcon(userId));
                 } else {
-                    icon = UserIcons.getDefaultUserIcon(userId, /* light= */ false);
+                    icon = UserIcons.getDefaultUserIcon(
+                            context.getResources(), userId, /* light= */ false);
                 }
             }
             this.mIcon = encircle(context, icon);
diff --git a/com/android/setupwizardlib/GlifPatternDrawable.java b/com/android/setupwizardlib/GlifPatternDrawable.java
index 51c1a49..c1d968a 100644
--- a/com/android/setupwizardlib/GlifPatternDrawable.java
+++ b/com/android/setupwizardlib/GlifPatternDrawable.java
@@ -23,7 +23,6 @@
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.ColorFilter;
-import android.graphics.ColorMatrixColorFilter;
 import android.graphics.Paint;
 import android.graphics.Path;
 import android.graphics.PixelFormat;
@@ -96,7 +95,6 @@
 
     private int mColor;
     private Paint mTempPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
-    private ColorFilter mColorFilter;
 
     public GlifPatternDrawable(int color) {
         setColor(color);
@@ -140,17 +138,10 @@
         canvas.clipRect(bounds);
 
         scaleCanvasToBounds(canvas, bitmap, bounds);
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB
-                && canvas.isHardwareAccelerated()) {
-            mTempPaint.setColorFilter(mColorFilter);
-            canvas.drawBitmap(bitmap, 0, 0, mTempPaint);
-        } else {
-            // Software renderer doesn't work properly with ColorMatrix filter on ALPHA_8 bitmaps.
-            canvas.drawColor(Color.BLACK);
-            mTempPaint.setColor(Color.WHITE);
-            canvas.drawBitmap(bitmap, 0, 0, mTempPaint);
-            canvas.drawColor(mColor);
-        }
+        canvas.drawColor(Color.BLACK);
+        mTempPaint.setColor(Color.WHITE);
+        canvas.drawBitmap(bitmap, 0, 0, mTempPaint);
+        canvas.drawColor(mColor);
 
         canvas.restore();
     }
@@ -299,12 +290,6 @@
         final int g = Color.green(color);
         final int b = Color.blue(color);
         mColor = Color.argb(COLOR_ALPHA_INT, r, g, b);
-        mColorFilter = new ColorMatrixColorFilter(new float[] {
-                0, 0, 0, 1 - COLOR_ALPHA, r * COLOR_ALPHA,
-                0, 0, 0, 1 - COLOR_ALPHA, g * COLOR_ALPHA,
-                0, 0, 0, 1 - COLOR_ALPHA, b * COLOR_ALPHA,
-                0, 0, 0,               0,             255
-        });
         invalidateSelf();
     }
 
diff --git a/com/android/systemui/OverviewProxyService.java b/com/android/systemui/OverviewProxyService.java
index 2e4a5a4..22922e7 100644
--- a/com/android/systemui/OverviewProxyService.java
+++ b/com/android/systemui/OverviewProxyService.java
@@ -22,11 +22,8 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.ServiceConnection;
-import android.graphics.Bitmap;
 import android.graphics.Rect;
-import android.net.Uri;
 import android.os.Binder;
-import android.os.Build;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
@@ -36,12 +33,14 @@
 import android.util.Log;
 import android.view.SurfaceControl;
 
+import com.android.systemui.OverviewProxyService.OverviewProxyListener;
 import com.android.systemui.shared.recents.IOverviewProxy;
 import com.android.systemui.shared.recents.ISystemUiProxy;
-import com.android.systemui.OverviewProxyService.OverviewProxyListener;
+import com.android.systemui.shared.system.GraphicBufferCompat;
 import com.android.systemui.statusbar.policy.CallbackController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController.DeviceProvisionedListener;
+
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -67,12 +66,12 @@
     private int mConnectionBackoffAttempts;
 
     private ISystemUiProxy mSysUiProxy = new ISystemUiProxy.Stub() {
-        public Bitmap screenshot(Rect sourceCrop, int width, int height, int minLayer, int maxLayer,
-                boolean useIdentityTransform, int rotation) {
+        public GraphicBufferCompat screenshot(Rect sourceCrop, int width, int height, int minLayer,
+                int maxLayer, boolean useIdentityTransform, int rotation) {
             long token = Binder.clearCallingIdentity();
             try {
-                return SurfaceControl.screenshot(sourceCrop, width, height, minLayer, maxLayer,
-                        useIdentityTransform, rotation);
+                return new GraphicBufferCompat(SurfaceControl.screenshotToBuffer(sourceCrop, width,
+                        height, minLayer, maxLayer, useIdentityTransform, rotation));
             } finally {
                 Binder.restoreCallingIdentity(token);
             }
diff --git a/com/android/systemui/SystemUIFactory.java b/com/android/systemui/SystemUIFactory.java
index 0c067ff..526a8f4 100644
--- a/com/android/systemui/SystemUIFactory.java
+++ b/com/android/systemui/SystemUIFactory.java
@@ -28,6 +28,7 @@
 import com.android.systemui.keyguard.DismissCallbackRegistry;
 import com.android.systemui.statusbar.KeyguardIndicationController;
 import com.android.systemui.statusbar.ScrimView;
+import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.KeyguardBouncer;
 import com.android.systemui.statusbar.phone.LightBarController;
 import com.android.systemui.statusbar.phone.LockIcon;
@@ -86,10 +87,10 @@
 
     public ScrimController createScrimController(LightBarController lightBarController,
             ScrimView scrimBehind, ScrimView scrimInFront, View headsUpScrim,
-            LockscreenWallpaper lockscreenWallpaper,
-            Consumer<Boolean> scrimVisibleListener) {
+            LockscreenWallpaper lockscreenWallpaper, Consumer<Boolean> scrimVisibleListener,
+            DozeParameters dozeParameters) {
         return new ScrimController(lightBarController, scrimBehind, scrimInFront, headsUpScrim,
-                scrimVisibleListener);
+                scrimVisibleListener, dozeParameters);
     }
 
     public NotificationIconAreaController createNotificationIconAreaController(Context context,
diff --git a/com/android/systemui/doze/DozeHost.java b/com/android/systemui/doze/DozeHost.java
index 7db118d..2f607ee 100644
--- a/com/android/systemui/doze/DozeHost.java
+++ b/com/android/systemui/doze/DozeHost.java
@@ -35,7 +35,6 @@
     boolean isBlockingDoze();
 
     void startPendingIntentDismissingKeyguard(PendingIntent intent);
-    void abortPulsing();
     void extendPulse();
 
     void setAnimateWakeup(boolean animateWakeup);
diff --git a/com/android/systemui/keyguard/KeyguardSliceProvider.java b/com/android/systemui/keyguard/KeyguardSliceProvider.java
index 03018f7..6ddc76b 100644
--- a/com/android/systemui/keyguard/KeyguardSliceProvider.java
+++ b/com/android/systemui/keyguard/KeyguardSliceProvider.java
@@ -84,7 +84,7 @@
 
     @Override
     public Slice onBindSlice(Uri sliceUri) {
-        return new Slice.Builder(sliceUri).addText(mLastText, Slice.HINT_TITLE).build();
+        return new Slice.Builder(sliceUri).addText(mLastText, null, Slice.HINT_TITLE).build();
     }
 
     @Override
diff --git a/com/android/systemui/keyguard/KeyguardViewMediator.java b/com/android/systemui/keyguard/KeyguardViewMediator.java
index a35ba9f..c92acd0 100644
--- a/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -60,7 +60,7 @@
 import android.util.Log;
 import android.util.Slog;
 import android.view.ViewGroup;
-import android.view.WindowManagerPolicy;
+import android.view.WindowManagerPolicyConstants;
 import android.view.animation.Animation;
 import android.view.animation.AnimationUtils;
 
@@ -129,7 +129,7 @@
  * false, this will override all other conditions for turning on the keyguard.
  *
  * Threading and synchronization:
- * This class is created by the initialization routine of the {@link android.view.WindowManagerPolicy},
+ * This class is created by the initialization routine of the {@link WindowManagerPolicyConstants},
  * and runs on its thread.  The keyguard UI is created from that thread in the
  * constructor of this class.  The apis may be called from other threads, including the
  * {@link com.android.server.input.InputManagerService}'s and {@link android.view.WindowManager}'s.
@@ -766,8 +766,8 @@
 
     /**
      * Called to let us know the screen was turned off.
-     * @param why either {@link android.view.WindowManagerPolicy#OFF_BECAUSE_OF_USER} or
-     *   {@link android.view.WindowManagerPolicy#OFF_BECAUSE_OF_TIMEOUT}.
+     * @param why either {@link WindowManagerPolicyConstants#OFF_BECAUSE_OF_USER} or
+     *   {@link WindowManagerPolicyConstants#OFF_BECAUSE_OF_TIMEOUT}.
      */
     public void onStartedGoingToSleep(int why) {
         if (DEBUG) Log.d(TAG, "onStartedGoingToSleep(" + why + ")");
@@ -797,8 +797,8 @@
                 }
             } else if (mShowing) {
                 mPendingReset = true;
-            } else if ((why == WindowManagerPolicy.OFF_BECAUSE_OF_TIMEOUT && timeout > 0)
-                    || (why == WindowManagerPolicy.OFF_BECAUSE_OF_USER && !lockImmediately)) {
+            } else if ((why == WindowManagerPolicyConstants.OFF_BECAUSE_OF_TIMEOUT && timeout > 0)
+                    || (why == WindowManagerPolicyConstants.OFF_BECAUSE_OF_USER && !lockImmediately)) {
                 doKeyguardLaterLocked(timeout);
                 mLockLater = true;
             } else if (!mLockPatternUtils.isLockScreenDisabled(currentUser)) {
@@ -1031,7 +1031,7 @@
     }
 
     /**
-     * Same semantics as {@link android.view.WindowManagerPolicy#enableKeyguard}; provide
+     * Same semantics as {@link WindowManagerPolicyConstants#enableKeyguard}; provide
      * a way for external stuff to override normal keyguard behavior.  For instance
      * the phone app disables the keyguard when it receives incoming calls.
      */
@@ -1780,13 +1780,13 @@
                 int flags = 0;
                 if (mStatusBarKeyguardViewManager.shouldDisableWindowAnimationsForUnlock()
                         || mWakeAndUnlocking) {
-                    flags |= WindowManagerPolicy.KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS;
+                    flags |= WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_NO_WINDOW_ANIMATIONS;
                 }
                 if (mStatusBarKeyguardViewManager.isGoingToNotificationShade()) {
-                    flags |= WindowManagerPolicy.KEYGUARD_GOING_AWAY_FLAG_TO_SHADE;
+                    flags |= WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_SHADE;
                 }
                 if (mStatusBarKeyguardViewManager.isUnlockWithWallpaper()) {
-                    flags |= WindowManagerPolicy.KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER;
+                    flags |= WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_WITH_WALLPAPER;
                 }
 
                 mUpdateMonitor.setKeyguardGoingAway(true /* goingAway */);
@@ -2028,12 +2028,9 @@
     }
 
     public StatusBarKeyguardViewManager registerStatusBar(StatusBar statusBar,
-            ViewGroup container,
-            ScrimController scrimController,
-            FingerprintUnlockController fingerprintUnlockController) {
+            ViewGroup container, FingerprintUnlockController fingerprintUnlockController) {
         mStatusBarKeyguardViewManager.registerStatusBar(statusBar, container,
-                scrimController, fingerprintUnlockController,
-                mDismissCallbackRegistry);
+                fingerprintUnlockController, mDismissCallbackRegistry);
         return mStatusBarKeyguardViewManager;
     }
 
diff --git a/com/android/systemui/pip/phone/PipTouchHandler.java b/com/android/systemui/pip/phone/PipTouchHandler.java
index 2b48e0f..51175d1 100644
--- a/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -387,7 +387,9 @@
             }
             case MotionEvent.ACTION_HOVER_ENTER:
             case MotionEvent.ACTION_HOVER_MOVE: {
-                if (mAccessibilityManager.isEnabled() && !mSendingHoverAccessibilityEvents) {
+                if (mAccessibilityManager.isObservedEventType(
+                                AccessibilityEvent.TYPE_VIEW_HOVER_ENTER)
+                        && !mSendingHoverAccessibilityEvents) {
                     AccessibilityEvent event = AccessibilityEvent.obtain(
                             AccessibilityEvent.TYPE_VIEW_HOVER_ENTER);
                     event.setImportantForAccessibility(true);
@@ -400,7 +402,9 @@
                 break;
             }
             case MotionEvent.ACTION_HOVER_EXIT: {
-                if (mAccessibilityManager.isEnabled() && mSendingHoverAccessibilityEvents) {
+                if (mAccessibilityManager.isObservedEventType(
+                                AccessibilityEvent.TYPE_VIEW_HOVER_EXIT)
+                        && mSendingHoverAccessibilityEvents) {
                     AccessibilityEvent event = AccessibilityEvent.obtain(
                             AccessibilityEvent.TYPE_VIEW_HOVER_EXIT);
                     event.setImportantForAccessibility(true);
diff --git a/com/android/systemui/pip/tv/PipManager.java b/com/android/systemui/pip/tv/PipManager.java
index eef43d2..a984680 100644
--- a/com/android/systemui/pip/tv/PipManager.java
+++ b/com/android/systemui/pip/tv/PipManager.java
@@ -625,9 +625,7 @@
         @Override
         public void onTaskStackChanged() {
             if (DEBUG) Log.d(TAG, "onTaskStackChanged()");
-            if (!checkCurrentUserId(mContext, DEBUG)) {
-                return;
-            }
+
             if (getState() != STATE_NO_PIP) {
                 boolean hasPip = false;
 
@@ -662,9 +660,7 @@
         @Override
         public void onActivityPinned(String packageName, int userId, int taskId, int stackId) {
             if (DEBUG) Log.d(TAG, "onActivityPinned()");
-            if (!checkCurrentUserId(mContext, DEBUG)) {
-                return;
-            }
+
             StackInfo stackInfo = getPinnedStackInfo();
             if (stackInfo == null) {
                 Log.w(TAG, "Cannot find pinned stack");
@@ -690,9 +686,7 @@
         @Override
         public void onPinnedActivityRestartAttempt(boolean clearedTask) {
             if (DEBUG) Log.d(TAG, "onPinnedActivityRestartAttempt()");
-            if (!checkCurrentUserId(mContext, DEBUG)) {
-                return;
-            }
+
             // If PIPed activity is launched again by Launcher or intent, make it fullscreen.
             movePipToFullscreen();
         }
@@ -700,9 +694,7 @@
         @Override
         public void onPinnedStackAnimationEnded() {
             if (DEBUG) Log.d(TAG, "onPinnedStackAnimationEnded()");
-            if (!checkCurrentUserId(mContext, DEBUG)) {
-                return;
-            }
+
             switch (getState()) {
                 case STATE_PIP_MENU:
                     showPipMenu();
diff --git a/com/android/systemui/recents/RecentsImpl.java b/com/android/systemui/recents/RecentsImpl.java
index 3b1b2f9..663f206 100644
--- a/com/android/systemui/recents/RecentsImpl.java
+++ b/com/android/systemui/recents/RecentsImpl.java
@@ -23,14 +23,12 @@
 import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_RECENT_APPS;
 
 import android.app.ActivityManager;
-import android.app.ActivityManager.TaskSnapshot;
 import android.app.ActivityOptions;
 import android.content.ActivityNotFoundException;
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
-import android.graphics.GraphicBuffer;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.drawable.Drawable;
@@ -89,7 +87,6 @@
 import com.android.systemui.shared.recents.view.RecentsTransition;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.stackdivider.DividerView;
-import com.android.systemui.statusbar.phone.NavigationBarGestureHelper;
 import com.android.systemui.statusbar.phone.StatusBar;
 
 import java.util.ArrayList;
@@ -156,7 +153,8 @@
                     // Launched from app is always the worst case (in terms of how many
                     // thumbnails/tasks visible)
                     launchState.launchedFromApp = true;
-                    mBackgroundLayoutAlgorithm.update(plan.getTaskStack(), EMPTY_SET, launchState);
+                    mBackgroundLayoutAlgorithm.update(plan.getTaskStack(), EMPTY_SET, launchState,
+                            -1 /* lastScrollPPresent */);
                     VisibilityReport visibilityReport =
                             mBackgroundLayoutAlgorithm.computeStackVisibilityReport(
                                     stack.getTasks());
@@ -656,13 +654,6 @@
         // the resize mode already.
         if (ssp.setTaskWindowingModeSplitScreenPrimary(taskId, stackCreateMode, initialBounds)) {
             EventBus.getDefault().send(new DockedTopTaskEvent(dragMode, initialBounds));
-            showRecents(
-                    false /* triggeredFromAltTab */,
-                    dragMode == NavigationBarGestureHelper.DRAG_MODE_RECENTS,
-                    false /* animate */,
-                    true /* launchedWhileDockingTask*/,
-                    false /* fromHome */,
-                    DividerView.INVALID_RECENTS_GROW_TARGET);
         }
     }
 
diff --git a/com/android/systemui/recents/misc/SystemServicesProxy.java b/com/android/systemui/recents/misc/SystemServicesProxy.java
index d89bab7..2d3080b 100644
--- a/com/android/systemui/recents/misc/SystemServicesProxy.java
+++ b/com/android/systemui/recents/misc/SystemServicesProxy.java
@@ -21,12 +21,10 @@
 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 android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityManager.StackInfo;
 import android.app.ActivityOptions;
@@ -49,9 +47,6 @@
 import android.graphics.PorterDuffXfermode;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
-import android.os.Handler;
-import android.os.IRemoteCallback;
-import android.os.Looper;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemProperties;
@@ -63,7 +58,6 @@
 import android.util.Log;
 import android.util.MutableBoolean;
 import android.view.Display;
-import android.view.IAppTransitionAnimationSpecsFuture;
 import android.view.IDockedStackListener;
 import android.view.IWindowManager;
 import android.view.WindowManager;
@@ -74,16 +68,12 @@
 import com.android.internal.app.AssistUtils;
 import com.android.internal.os.BackgroundThread;
 import com.android.systemui.Dependency;
-import com.android.systemui.R;
 import com.android.systemui.UiOffloadThread;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.RecentsImpl;
-import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.shared.system.TaskStackChangeListeners;
 import com.android.systemui.statusbar.policy.UserInfoController;
 
 import java.util.List;
-import java.util.function.Consumer;
 
 /**
  * Acts as a shim around the real system services that we need to access data from, and provides
@@ -268,22 +258,6 @@
         return mIsSafeMode;
     }
 
-    /** Docks a task to the side of the screen and starts it. */
-    public boolean startTaskInDockedMode(int taskId, int createMode) {
-        if (mIam == null) return false;
-
-        try {
-            final ActivityOptions options = ActivityOptions.makeBasic();
-            options.setSplitScreenCreateMode(createMode);
-            options.setLaunchWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
-            mIam.startActivityFromRecents(taskId, options.toBundle());
-            return true;
-        } catch (Exception e) {
-            Log.e(TAG, "Failed to dock task: " + taskId + " with createMode: " + createMode, e);
-        }
-        return false;
-    }
-
     /** Moves an already resumed task to the side of the screen to initiate split screen. */
     public boolean setTaskWindowingModeSplitScreenPrimary(int taskId, int createMode,
             Rect initialBounds) {
@@ -397,7 +371,7 @@
         if (mIam == null) return false;
 
         try {
-            return mIam.isInLockTaskMode();
+            return mIam.getLockTaskModeState() == ActivityManager.LOCK_TASK_MODE_PINNED;
         } catch (RemoteException e) {
             return false;
         }
@@ -540,16 +514,6 @@
         }
     }
 
-    public void overridePendingAppTransitionMultiThumbFuture(
-            IAppTransitionAnimationSpecsFuture future, IRemoteCallback animStartedListener,
-            boolean scaleUp) {
-        try {
-            mIwm.overridePendingAppTransitionMultiThumbFuture(future, animStartedListener, scaleUp);
-        } catch (RemoteException e) {
-            Log.w(TAG, "Failed to override transition: " + e);
-        }
-    }
-
     /**
      * Updates the visibility of recents.
      */
diff --git a/com/android/systemui/recents/views/RecentsView.java b/com/android/systemui/recents/views/RecentsView.java
index 1440fc1..e3ed1aa 100644
--- a/com/android/systemui/recents/views/RecentsView.java
+++ b/com/android/systemui/recents/views/RecentsView.java
@@ -16,13 +16,14 @@
 
 package com.android.systemui.recents.views;
 
+import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
+
 import static com.android.systemui.statusbar.phone.StatusBar.SYSTEM_DIALOG_REASON_RECENT_APPS;
 
 import android.animation.ValueAnimator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
 import android.annotation.Nullable;
 import android.app.ActivityOptions;
-import android.app.ActivityOptions.OnAnimationStartedListener;
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.graphics.Canvas;
@@ -33,11 +34,11 @@
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.Handler;
+import android.os.IRemoteCallback;
 import android.util.ArraySet;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.MathUtils;
-import android.view.AppTransitionAnimationSpec;
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
@@ -86,13 +87,15 @@
 import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
 import com.android.systemui.recents.misc.ReferenceCountedTrigger;
 import com.android.systemui.recents.misc.SystemServicesProxy;
-import com.android.systemui.shared.recents.utilities.Utilities;
 import com.android.systemui.shared.recents.model.Task;
 import com.android.systemui.shared.recents.model.TaskStack;
+import com.android.systemui.shared.recents.utilities.Utilities;
 import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecCompat;
 import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture;
 import com.android.systemui.shared.recents.view.RecentsTransition;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
+import com.android.systemui.shared.system.ActivityOptionsCompat;
+import com.android.systemui.shared.system.WindowManagerWrapper;
 import com.android.systemui.stackdivider.WindowManagerProxy;
 import com.android.systemui.statusbar.FlingAnimationUtils;
 import com.android.systemui.statusbar.phone.ScrimController;
@@ -608,16 +611,17 @@
             // rect to its final layout-space rect
             Utilities.setViewFrameFromTranslation(event.taskView);
 
-            // Dock the task and launch it
-            SystemServicesProxy ssp = Recents.getSystemServices();
-            if (ssp.startTaskInDockedMode(event.task.key.id, dockState.createMode)) {
+            final ActivityOptions options = ActivityOptionsCompat.makeSplitScreenOptions(
+                    dockState.createMode == SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT);
+            if (ActivityManagerWrapper.getInstance().startActivityFromRecents(event.task.key.id,
+                    options)) {
                 final Runnable animStartedListener = () -> {
                     EventBus.getDefault().send(new DockedFirstAnimationFrameEvent());
-                    // Remove the task and don't bother relaying out, as all the tasks will be
-                    // relaid out when the stack changes on the multiwindow change event
+                    // Remove the task and don't bother relaying out, as all the tasks
+                    // will be relaid out when the stack changes on the multiwindow
+                    // change event
                     getStack().removeTask(event.task, null, true /* fromDockGesture */);
                 };
-
                 final Rect taskRect = getTaskRect(event.taskView);
                 AppTransitionAnimationSpecsFuture future = new AppTransitionAnimationSpecsFuture(
                         getHandler()) {
@@ -626,10 +630,8 @@
                         return mTransitionHelper.composeDockAnimationSpec(event.taskView, taskRect);
                     }
                 };
-                ssp.overridePendingAppTransitionMultiThumbFuture(future.getFuture(),
-                        RecentsTransition.wrapStartedListener(getHandler(), animStartedListener),
-                        true /* scaleUp */);
-
+                WindowManagerWrapper.getInstance().overridePendingAppTransitionMultiThumbFuture(
+                        future, animStartedListener, getHandler(), true /* scaleUp */);
                 MetricsLogger.action(mContext, MetricsEvent.ACTION_WINDOW_DOCK_DRAG_DROP,
                         event.task.getTopComponent().flattenToShortString());
             } else {
@@ -1032,11 +1034,9 @@
                 if (taskIndex > -1) {
                     taskIndexFromFront = stack.getTaskCount() - taskIndex - 1;
                 }
-                EventBus.getDefault().send(new LaunchTaskSucceededEvent(
-                        taskIndexFromFront));
+                EventBus.getDefault().send(new LaunchTaskSucceededEvent(taskIndexFromFront));
             } else {
-                Log.e(TAG, mContext.getString(R.string.recents_launch_error_message,
-                        task.title));
+                Log.e(TAG, mContext.getString(R.string.recents_launch_error_message, task.title));
 
                 // Dismiss the task if we fail to launch it
                 if (taskView != null) {
diff --git a/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java b/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
index 600da04..d9f79bb 100644
--- a/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
+++ b/com/android/systemui/recents/views/TaskStackLayoutAlgorithm.java
@@ -431,7 +431,7 @@
      * in the stack.
      */
     public void update(TaskStack stack, ArraySet<Task.TaskKey> ignoreTasksSet,
-            RecentsActivityLaunchState launchState) {
+            RecentsActivityLaunchState launchState, float lastScrollPPercent) {
         SystemServicesProxy ssp = Recents.getSystemServices();
 
         // Clear the progress map
@@ -506,6 +506,8 @@
 
             if (launchState.launchedWithAltTab) {
                 mInitialScrollP = Utilities.clamp(launchTaskIndex, mMinScrollP, mMaxScrollP);
+            } else if (0 <= lastScrollPPercent && lastScrollPPercent <= 1) {
+                mInitialScrollP = Utilities.mapRange(lastScrollPPercent, mMinScrollP, mMaxScrollP);
             } else if (Recents.getConfiguration().isLowRamDevice) {
                 mInitialScrollP = mTaskStackLowRamLayoutAlgorithm.getInitialScrollP(mNumStackTasks,
                         scrollToFront);
diff --git a/com/android/systemui/recents/views/TaskStackView.java b/com/android/systemui/recents/views/TaskStackView.java
index 1197501..36c9095 100644
--- a/com/android/systemui/recents/views/TaskStackView.java
+++ b/com/android/systemui/recents/views/TaskStackView.java
@@ -209,6 +209,9 @@
     private int mLastHeight;
     private boolean mStackActionButtonVisible;
 
+    // Percentage of last ScrollP from the min to max scrollP that lives after configuration changes
+    private float mLastScrollPPercent;
+
     // We keep track of the task view focused by user interaction and draw a frame around it in the
     // grid layout.
     private TaskViewFocusFrame mTaskViewFocusFrame;
@@ -327,6 +330,7 @@
             mStackScroller.reset();
             mStableLayoutAlgorithm.reset();
             mLayoutAlgorithm.reset();
+            mLastScrollPPercent = -1;
         }
 
         // Since we always animate to the same place in (the initial state), always reset the stack
@@ -822,7 +826,7 @@
    public void updateLayoutAlgorithm(boolean boundScrollToNewMinMax,
            RecentsActivityLaunchState launchState) {
         // Compute the min and max scroll values
-        mLayoutAlgorithm.update(mStack, mIgnoreTasks, launchState);
+        mLayoutAlgorithm.update(mStack, mIgnoreTasks, launchState, mLastScrollPPercent);
 
         if (boundScrollToNewMinMax) {
             mStackScroller.boundScroll();
@@ -1150,6 +1154,8 @@
         if (mTaskViewsClipDirty) {
             clipTaskViews();
         }
+        mLastScrollPPercent = Utilities.clamp(Utilities.unmapRange(mStackScroller.getStackScroll(),
+            mLayoutAlgorithm.mMinScrollP, mLayoutAlgorithm.mMaxScrollP), 0, 1);
     }
 
     /**
diff --git a/com/android/systemui/shared/recents/model/RecentsTaskLoadPlan.java b/com/android/systemui/shared/recents/model/RecentsTaskLoadPlan.java
index 8e2a25c..4834bb1 100644
--- a/com/android/systemui/shared/recents/model/RecentsTaskLoadPlan.java
+++ b/com/android/systemui/shared/recents/model/RecentsTaskLoadPlan.java
@@ -45,6 +45,11 @@
  */
 public class RecentsTaskLoadPlan {
 
+    /** The set of conditions to preload tasks. */
+    public static class PreloadOptions {
+        public boolean loadTitles = true;
+    }
+
     /** The set of conditions to load tasks. */
     public static class Options {
         public int runningTaskId = -1;
@@ -80,7 +85,8 @@
      * Note: Do not lock, since this can be calling back to the loader, which separately also drives
      * this call (callers should synchronize on the loader before making this call).
      */
-    public void preloadPlan(RecentsTaskLoader loader, int runningTaskId, int currentUserId) {
+    public void preloadPlan(PreloadOptions opts, RecentsTaskLoader loader, int runningTaskId,
+            int currentUserId) {
         Resources res = mContext.getResources();
         ArrayList<Task> allTasks = new ArrayList<>();
         if (mRawTasks == null) {
@@ -110,9 +116,12 @@
             }
 
             // Load the title, icon, and color
-            String title = loader.getAndUpdateActivityTitle(taskKey, t.taskDescription);
-            String titleDescription = loader.getAndUpdateContentDescription(taskKey,
-                    t.taskDescription);
+            String title = opts.loadTitles
+                    ? loader.getAndUpdateActivityTitle(taskKey, t.taskDescription)
+                    : "";
+            String titleDescription = opts.loadTitles
+                    ? loader.getAndUpdateContentDescription(taskKey, t.taskDescription)
+                    : "";
             Drawable icon = isStackTask
                     ? loader.getAndUpdateActivityIcon(taskKey, t.taskDescription, res, false)
                     : null;
diff --git a/com/android/systemui/shared/recents/model/RecentsTaskLoader.java b/com/android/systemui/shared/recents/model/RecentsTaskLoader.java
index 9a991cf..0f68026 100644
--- a/com/android/systemui/shared/recents/model/RecentsTaskLoader.java
+++ b/com/android/systemui/shared/recents/model/RecentsTaskLoader.java
@@ -32,6 +32,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan.Options;
+import com.android.systemui.shared.recents.model.RecentsTaskLoadPlan.PreloadOptions;
 import com.android.systemui.shared.recents.model.Task.TaskKey;
 import com.android.systemui.shared.recents.model.TaskKeyLruCache.EvictionCallback;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -155,7 +156,7 @@
             int currentUserId) {
         try {
             Trace.beginSection("preloadPlan");
-            plan.preloadPlan(this, runningTaskId, currentUserId);
+            plan.preloadPlan(new PreloadOptions(), this, runningTaskId, currentUserId);
         } finally {
             Trace.endSection();
         }
diff --git a/com/android/systemui/shared/recents/utilities/AppTrace.java b/com/android/systemui/shared/recents/utilities/AppTrace.java
new file mode 100644
index 0000000..0241c59
--- /dev/null
+++ b/com/android/systemui/shared/recents/utilities/AppTrace.java
@@ -0,0 +1,73 @@
+/*
+ * 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.shared.recents.utilities;
+
+import static android.os.Trace.TRACE_TAG_APP;
+
+/**
+ * Helper class for internal trace functions.
+ */
+public class AppTrace {
+
+    /**
+     * Begins a new async trace section with the given {@param key} and {@param cookie}.
+     */
+    public static void start(String key, int cookie) {
+        android.os.Trace.asyncTraceBegin(TRACE_TAG_APP, key, cookie);
+    }
+
+    /**
+     * Begins a new async trace section with the given {@param key}.
+     */
+    public static void start(String key) {
+        android.os.Trace.asyncTraceBegin(TRACE_TAG_APP, key, 0);
+    }
+
+    /**
+     * Ends an existing async trace section with the given {@param key}.
+     */
+    public static void end(String key) {
+        android.os.Trace.asyncTraceEnd(TRACE_TAG_APP, key, 0);
+    }
+
+    /**
+     * Ends an existing async trace section with the given {@param key} and {@param cookie}.
+     */
+    public static void end(String key, int cookie) {
+        android.os.Trace.asyncTraceEnd(TRACE_TAG_APP, key, cookie);
+    }
+
+    /**
+     * Begins a new trace section with the given {@param key}. Can be nested.
+     */
+    public static void beginSection(String key) {
+        android.os.Trace.beginSection(key);
+    }
+
+    /**
+     * Ends an existing trace section started in the last {@link #beginSection(String)}.
+     */
+    public static void endSection() {
+        android.os.Trace.endSection();
+    }
+
+    /**
+     * Traces a counter value.
+     */
+    public static void count(String name, int count) {
+        android.os.Trace.traceCounter(TRACE_TAG_APP, name, count);
+    }
+}
diff --git a/com/android/systemui/shared/system/ActivityManagerWrapper.java b/com/android/systemui/shared/system/ActivityManagerWrapper.java
index f6fab86..eb2d12e 100644
--- a/com/android/systemui/shared/system/ActivityManagerWrapper.java
+++ b/com/android/systemui/shared/system/ActivityManagerWrapper.java
@@ -319,32 +319,39 @@
         mBackgroundExecutor.submit(new Runnable() {
             @Override
             public void run() {
+                boolean result = false;
                 try {
-                    ActivityManager.getService().startActivityFromRecents(taskKey.id,
-                            finalOptions == null ? null : finalOptions.toBundle());
-                    if (resultCallback != null) {
-                        resultCallbackHandler.post(new Runnable() {
-                            @Override
-                            public void run() {
-                                resultCallback.accept(true);
-                            }
-                        });
-                    }
+                    result = startActivityFromRecents(taskKey.id, finalOptions);
                 } catch (Exception e) {
-                    if (resultCallback != null) {
-                        resultCallbackHandler.post(new Runnable() {
-                            @Override
-                            public void run() {
-                                resultCallback.accept(false);
-                            }
-                        });
-                    }
+                    // Fall through
+                }
+                final boolean finalResult = result;
+                if (resultCallback != null) {
+                    resultCallbackHandler.post(new Runnable() {
+                        @Override
+                        public void run() {
+                            resultCallback.accept(finalResult);
+                        }
+                    });
                 }
             }
         });
     }
 
     /**
+     * Starts a task from Recents synchronously.
+     */
+    public boolean startActivityFromRecents(int taskId, ActivityOptions options) {
+        try {
+            Bundle optsBundle = options == null ? null : options.toBundle();
+            ActivityManager.getService().startActivityFromRecents(taskId, optsBundle);
+            return true;
+        } catch (Exception e) {
+            return false;
+        }
+    }
+
+    /**
      * Registers a task stack listener with the system.
      * This should be called on the main thread.
      */
diff --git a/com/android/systemui/shared/system/ActivityOptionsCompat.java b/com/android/systemui/shared/system/ActivityOptionsCompat.java
new file mode 100644
index 0000000..705a215
--- /dev/null
+++ b/com/android/systemui/shared/system/ActivityOptionsCompat.java
@@ -0,0 +1,41 @@
+/*
+ * 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.shared.system;
+
+import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT;
+import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+
+import android.app.ActivityOptions;
+
+/**
+ * Wrapper around internal ActivityOptions creation.
+ */
+public abstract class ActivityOptionsCompat {
+
+    /**
+     * @return ActivityOptions for starting a task in split screen.
+     */
+    public static ActivityOptions makeSplitScreenOptions(boolean dockTopLeft) {
+        final ActivityOptions options = ActivityOptions.makeBasic();
+        options.setLaunchWindowingMode(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+        options.setSplitScreenCreateMode(dockTopLeft
+                ? SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT
+                : SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT);
+        return options;
+    }
+}
diff --git a/com/android/systemui/shared/system/BackgroundExecutor.java b/com/android/systemui/shared/system/BackgroundExecutor.java
index cfd1f9a..0bd89a7 100644
--- a/com/android/systemui/shared/system/BackgroundExecutor.java
+++ b/com/android/systemui/shared/system/BackgroundExecutor.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.shared.system;
 
+import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
@@ -37,6 +38,13 @@
     }
 
     /**
+     * Runs the given {@param callable} on one of the background executor threads.
+     */
+    public <T> Future<T> submit(Callable<T> callable) {
+        return mExecutorService.submit(callable);
+    }
+
+    /**
      * Runs the given {@param runnable} on one of the background executor threads.
      */
     public Future<?> submit(Runnable runnable) {
diff --git a/com/android/systemui/shared/system/ChoreographerCompat.java b/com/android/systemui/shared/system/ChoreographerCompat.java
new file mode 100644
index 0000000..4d422bb
--- /dev/null
+++ b/com/android/systemui/shared/system/ChoreographerCompat.java
@@ -0,0 +1,33 @@
+/*
+ * 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.shared.system;
+
+import static android.view.Choreographer.CALLBACK_INPUT;
+
+import android.view.Choreographer;
+
+/**
+ * Wraps the internal choreographer.
+ */
+public class ChoreographerCompat {
+
+    /**
+     * Posts an input callback to the choreographer.
+     */
+    public static void postInputFrame(Choreographer choreographer, Runnable runnable) {
+        choreographer.postCallback(CALLBACK_INPUT, runnable, null);
+    }
+}
diff --git a/com/android/systemui/shared/system/GraphicBufferCompat.java b/com/android/systemui/shared/system/GraphicBufferCompat.java
new file mode 100644
index 0000000..66b8fed
--- /dev/null
+++ b/com/android/systemui/shared/system/GraphicBufferCompat.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.systemui.shared.system;
+
+import android.graphics.Bitmap;
+import android.graphics.GraphicBuffer;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Wraps the internal graphic buffer.
+ */
+public class GraphicBufferCompat implements Parcelable {
+
+    private GraphicBuffer mBuffer;
+
+    public GraphicBufferCompat(GraphicBuffer buffer) {
+        mBuffer = buffer;
+    }
+
+    public GraphicBufferCompat(Parcel in) {
+        mBuffer = GraphicBuffer.CREATOR.createFromParcel(in);
+    }
+
+    public Bitmap toBitmap() {
+        return mBuffer != null
+                ? Bitmap.createHardwareBitmap(mBuffer)
+                : null;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        mBuffer.writeToParcel(dest, flags);
+    }
+
+    public static final Parcelable.Creator<GraphicBufferCompat> CREATOR
+            = new Parcelable.Creator<GraphicBufferCompat>() {
+        public GraphicBufferCompat createFromParcel(Parcel in) {
+            return new GraphicBufferCompat(in);
+        }
+
+        public GraphicBufferCompat[] newArray(int size) {
+            return new GraphicBufferCompat[size];
+        }
+    };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+}
diff --git a/com/android/systemui/shared/system/WindowManagerWrapper.java b/com/android/systemui/shared/system/WindowManagerWrapper.java
index 1477558..225dbb4 100644
--- a/com/android/systemui/shared/system/WindowManagerWrapper.java
+++ b/com/android/systemui/shared/system/WindowManagerWrapper.java
@@ -19,8 +19,15 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 
 import android.graphics.Rect;
+import android.os.Handler;
+import android.os.IRemoteCallback;
+import android.os.RemoteException;
+import android.util.Log;
 import android.view.WindowManagerGlobal;
 
+import com.android.systemui.shared.recents.view.AppTransitionAnimationSpecsFuture;
+import com.android.systemui.shared.recents.view.RecentsTransition;
+
 public class WindowManagerWrapper {
 
     private static final String TAG = "WindowManagerWrapper";
@@ -38,8 +45,24 @@
         try {
             WindowManagerGlobal.getWindowManagerService().getStableInsets(DEFAULT_DISPLAY,
                     outStableInsets);
-        } catch (Exception e) {
-            e.printStackTrace();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to get stable insets", e);
+        }
+    }
+
+    /**
+     * Overrides a pending app transition.
+     */
+    public void overridePendingAppTransitionMultiThumbFuture(
+            AppTransitionAnimationSpecsFuture animationSpecFuture,
+            Runnable animStartedCallback, Handler animStartedCallbackHandler, boolean scaleUp) {
+        try {
+            WindowManagerGlobal.getWindowManagerService()
+                    .overridePendingAppTransitionMultiThumbFuture(animationSpecFuture.getFuture(),
+                            RecentsTransition.wrapStartedListener(animStartedCallbackHandler,
+                                    animStartedCallback), scaleUp);
+        } catch (RemoteException e) {
+            Log.w(TAG, "Failed to override pending app transition (multi-thumbnail future): ", e);
         }
     }
 }
diff --git a/com/android/systemui/shortcut/ShortcutKeyDispatcher.java b/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
index 1cda301..da79884 100644
--- a/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
+++ b/com/android/systemui/shortcut/ShortcutKeyDispatcher.java
@@ -20,6 +20,8 @@
 import static android.app.ActivityManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
 import static android.os.UserHandle.USER_CURRENT;
 
+import static com.android.systemui.statusbar.phone.NavigationBarGestureHelper.DRAG_MODE_NONE;
+
 import android.app.ActivityManager;
 import android.content.res.Configuration;
 import android.os.RemoteException;
@@ -36,6 +38,7 @@
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.stackdivider.Divider;
 import com.android.systemui.stackdivider.DividerView;
+import com.android.systemui.statusbar.phone.NavigationBarGestureHelper;
 
 import java.util.List;
 
@@ -89,20 +92,11 @@
         try {
             int dockSide = mWindowManagerService.getDockedStackSide();
             if (dockSide == WindowManager.DOCKED_INVALID) {
-                // If there is no window docked, we dock the top-most window.
+                // Split the screen
                 Recents recents = getComponent(Recents.class);
-                int dockMode = (shortcutCode == SC_DOCK_LEFT)
+                recents.splitPrimaryTask(DRAG_MODE_NONE, (shortcutCode == SC_DOCK_LEFT)
                         ? SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT
-                        : SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT;
-                List<ActivityManager.RecentTaskInfo> taskList =
-                        ActivityManagerWrapper.getInstance().getRecentTasks(1, USER_CURRENT);
-                recents.showRecentApps(
-                        false /* triggeredFromAltTab */,
-                        false /* fromHome */);
-                if (!taskList.isEmpty()) {
-                    SystemServicesProxy.getInstance(mContext).startTaskInDockedMode(
-                            taskList.get(0).id, dockMode);
-                }
+                        : SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT, null, -1);
             } else {
                 // If there is already a docked window, we respond by resizing the docking pane.
                 DividerView dividerView = getComponent(Divider.class).getView();
diff --git a/com/android/systemui/statusbar/ActivatableNotificationView.java b/com/android/systemui/statusbar/ActivatableNotificationView.java
index 84b7015..ff0357a 100644
--- a/com/android/systemui/statusbar/ActivatableNotificationView.java
+++ b/com/android/systemui/statusbar/ActivatableNotificationView.java
@@ -695,6 +695,11 @@
                 mBackgroundNormal.getVisibility() == View.VISIBLE ? 1.0f : 0.0f);
     }
 
+    protected void updateBackgroundClipping() {
+        mBackgroundNormal.setBottomAmountClips(!isChildInGroup());
+        mBackgroundDimmed.setBottomAmountClips(!isChildInGroup());
+    }
+
     protected boolean shouldHideBackground() {
         return mDark;
     }
@@ -901,12 +906,45 @@
         contentView.setAlpha(contentAlpha);
     }
 
+    @Override
+    protected void applyRoundness() {
+        super.applyRoundness();
+        applyBackgroundRoundness(getCurrentBackgroundRadiusTop(),
+                getCurrentBackgroundRadiusBottom());
+    }
+
+    protected void applyBackgroundRoundness(float topRadius, float bottomRadius) {
+        mBackgroundDimmed.setRoundness(topRadius, bottomRadius);
+        mBackgroundNormal.setRoundness(topRadius, bottomRadius);
+    }
+
+    @Override
+    protected void setBackgroundTop(int backgroundTop) {
+        mBackgroundDimmed.setBackgroundTop(backgroundTop);
+        mBackgroundNormal.setBackgroundTop(backgroundTop);
+    }
+
     protected abstract View getContentView();
 
     public int calculateBgColor() {
         return calculateBgColor(true /* withTint */, true /* withOverRide */);
     }
 
+    @Override
+    public void setCurrentSidePaddings(float currentSidePaddings) {
+        super.setCurrentSidePaddings(currentSidePaddings);
+        mBackgroundNormal.setCurrentSidePaddings(currentSidePaddings);
+        mBackgroundDimmed.setCurrentSidePaddings(currentSidePaddings);
+    }
+
+    @Override
+    protected boolean childNeedsClipping(View child) {
+        if (child instanceof NotificationBackgroundView && isClippingNeeded()) {
+            return true;
+        }
+        return super.childNeedsClipping(child);
+    }
+
     /**
      * @param withTint should a possible tint be factored in?
      * @param withOverRide should the value be interpolated with {@link #mOverrideTint}
diff --git a/com/android/systemui/statusbar/CommandQueue.java b/com/android/systemui/statusbar/CommandQueue.java
index 6349275..8e1b104 100644
--- a/com/android/systemui/statusbar/CommandQueue.java
+++ b/com/android/systemui/statusbar/CommandQueue.java
@@ -82,6 +82,7 @@
     private static final int MSG_TOGGLE_PANEL                  = 35 << MSG_SHIFT;
     private static final int MSG_SHOW_SHUTDOWN_UI              = 36 << MSG_SHIFT;
     private static final int MSG_SET_TOP_APP_HIDES_STATUS_BAR  = 37 << MSG_SHIFT;
+    private static final int MSG_ROTATION_PROPOSAL             = 38 << MSG_SHIFT;
 
     public static final int FLAG_EXCLUDE_NONE = 0;
     public static final int FLAG_EXCLUDE_SEARCH_PANEL = 1 << 0;
@@ -142,6 +143,8 @@
         default void handleSystemKey(int arg1) { }
         default void handleShowGlobalActionsMenu() { }
         default void handleShowShutdownUi(boolean isReboot, String reason) { }
+
+        default void onRotationProposal(int rotation) { }
     }
 
     @VisibleForTesting
@@ -458,6 +461,15 @@
         }
     }
 
+    @Override
+    public void onProposedRotationChanged(int rotation) {
+        synchronized (mLock) {
+            mHandler.removeMessages(MSG_ROTATION_PROPOSAL);
+            mHandler.obtainMessage(MSG_ROTATION_PROPOSAL, rotation, 0,
+                    null).sendToTarget();
+        }
+    }
+
     private final class H extends Handler {
         private H(Looper l) {
             super(l);
@@ -654,6 +666,11 @@
                         mCallbacks.get(i).setTopAppHidesStatusBar(msg.arg1 != 0);
                     }
                     break;
+                case MSG_ROTATION_PROPOSAL:
+                    for (int i = 0; i < mCallbacks.size(); i++) {
+                        mCallbacks.get(i).onRotationProposal(msg.arg1);
+                    }
+                    break;
             }
         }
     }
diff --git a/com/android/systemui/statusbar/ExpandableNotificationRow.java b/com/android/systemui/statusbar/ExpandableNotificationRow.java
index 8ff950e..23d9cae 100644
--- a/com/android/systemui/statusbar/ExpandableNotificationRow.java
+++ b/com/android/systemui/statusbar/ExpandableNotificationRow.java
@@ -26,6 +26,7 @@
 import android.content.Context;
 import android.content.res.Resources;
 import android.content.res.Configuration;
+import android.graphics.Path;
 import android.graphics.drawable.AnimatedVectorDrawable;
 import android.graphics.drawable.AnimationDrawable;
 import android.graphics.drawable.ColorDrawable;
@@ -65,7 +66,6 @@
 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;
@@ -102,7 +102,9 @@
     private int mIconTransformContentShift;
     private int mIconTransformContentShiftNoIcon;
     private int mNotificationMinHeightLegacy;
+    private int mNotificationMinHeightBeforeP;
     private int mMaxHeadsUpHeightLegacy;
+    private int mMaxHeadsUpHeightBeforeP;
     private int mMaxHeadsUpHeight;
     private int mMaxHeadsUpHeightIncreased;
     private int mNotificationMinHeight;
@@ -435,9 +437,10 @@
         boolean customView = layout.getContractedChild().getId()
                 != com.android.internal.R.id.status_bar_latest_event_content;
         boolean beforeN = mEntry.targetSdk < Build.VERSION_CODES.N;
+        boolean beforeP = mEntry.targetSdk < Build.VERSION_CODES.P;
         int minHeight;
-        if (customView && beforeN && !mIsSummaryWithChildren) {
-            minHeight = mNotificationMinHeightLegacy;
+        if (customView && beforeP && !mIsSummaryWithChildren) {
+            minHeight = beforeN ? mNotificationMinHeightLegacy : mNotificationMinHeightBeforeP;
         } else if (mUseIncreasedCollapsedHeight && layout == mPrivateLayout) {
             minHeight = mNotificationMinHeightLarge;
         } else {
@@ -447,8 +450,8 @@
                 layout.getHeadsUpChild().getId()
                         != com.android.internal.R.id.status_bar_latest_event_content;
         int headsUpheight;
-        if (headsUpCustom && beforeN) {
-            headsUpheight = mMaxHeadsUpHeightLegacy;
+        if (headsUpCustom && beforeP) {
+            headsUpheight = beforeN ? mMaxHeadsUpHeightLegacy : mMaxHeadsUpHeightBeforeP;
         } else if (mUseIncreasedHeadsUpHeight && layout == mPrivateLayout) {
             headsUpheight = mMaxHeadsUpHeightIncreased;
         } else {
@@ -535,6 +538,7 @@
         }
         onChildrenCountChanged();
         row.setIsChildInGroup(false, null);
+        row.setBottomRoundness(0.0f, false /* animate */);
     }
 
     @Override
@@ -563,6 +567,7 @@
             mNotificationParent.updateBackgroundForGroupState();
         }
         updateIconVisibilities();
+        updateBackgroundClipping();
     }
 
     @Override
@@ -916,6 +921,7 @@
             addView(mMenuRow.getMenuView(), menuIndex);
         }
         for (NotificationContentView l : mLayouts) {
+            l.initView();
             l.reInflateViews();
         }
         mNotificationInflater.onDensityOrFontScaleChanged();
@@ -1025,6 +1031,7 @@
         mKeepInParent = keepInParent;
     }
 
+    @Override
     public boolean isRemoved() {
         return mRemoved;
     }
@@ -1264,6 +1271,8 @@
     private void initDimens() {
         mNotificationMinHeightLegacy = NotificationUtils.getFontScaledHeight(mContext,
                 R.dimen.notification_min_height_legacy);
+        mNotificationMinHeightBeforeP = NotificationUtils.getFontScaledHeight(mContext,
+                R.dimen.notification_min_height_before_p);
         mNotificationMinHeight = NotificationUtils.getFontScaledHeight(mContext,
                 R.dimen.notification_min_height);
         mNotificationMinHeightLarge = NotificationUtils.getFontScaledHeight(mContext,
@@ -1274,6 +1283,8 @@
                 R.dimen.notification_ambient_height);
         mMaxHeadsUpHeightLegacy = NotificationUtils.getFontScaledHeight(mContext,
                 R.dimen.notification_max_heads_up_height_legacy);
+        mMaxHeadsUpHeightBeforeP = NotificationUtils.getFontScaledHeight(mContext,
+                R.dimen.notification_max_heads_up_height_before_p);
         mMaxHeadsUpHeight = NotificationUtils.getFontScaledHeight(mContext,
                 R.dimen.notification_max_heads_up_height);
         mMaxHeadsUpHeightIncreased = NotificationUtils.getFontScaledHeight(mContext,
@@ -1752,6 +1763,7 @@
         mPrivateLayout.updateExpandButtons(isExpandable());
         updateChildrenHeaderAppearance();
         updateChildrenVisibility();
+        applyChildrenRoundness();
     }
 
     public void updateChildrenHeaderAppearance() {
@@ -2332,6 +2344,56 @@
         }
     }
 
+    @Override
+    protected boolean childNeedsClipping(View child) {
+        if (child instanceof NotificationContentView) {
+            NotificationContentView contentView = (NotificationContentView) child;
+            if (isClippingNeeded()) {
+                return true;
+            } else if (!hasNoRoundingAndNoPadding() && contentView.shouldClipToSidePaddings()) {
+                return true;
+            }
+        } else if (child == mChildrenContainer) {
+            if (isClippingNeeded() || ((isGroupExpanded() || isGroupExpansionChanging())
+                    && getClipBottomAmount() != 0.0f && getCurrentBottomRoundness() != 0.0f)) {
+                return true;
+            }
+        } else if (child instanceof NotificationGuts) {
+            return !hasNoRoundingAndNoPadding();
+        }
+        return super.childNeedsClipping(child);
+    }
+
+    @Override
+    protected void applyRoundness() {
+        super.applyRoundness();
+        applyChildrenRoundness();
+    }
+
+    private void applyChildrenRoundness() {
+        if (mIsSummaryWithChildren) {
+            mChildrenContainer.setCurrentBottomRoundness(getCurrentBottomRoundness());
+        }
+    }
+
+    @Override
+    public Path getCustomClipPath(View child) {
+        if (child instanceof NotificationGuts) {
+            return getClipPath(true, /* ignoreTranslation */
+                    false /* clipRoundedToBottom */);
+        }
+        if (child instanceof NotificationChildrenContainer) {
+            return getClipPath(false, /* ignoreTranslation */
+                    true /* clipRoundedToBottom */);
+        }
+        return super.getCustomClipPath(child);
+    }
+
+    private boolean hasNoRoundingAndNoPadding() {
+        return mCurrentSidePaddings == 0 && getCurrentBottomRoundness() == 0.0f
+                && getCurrentTopRoundness() == 0.0f;
+    }
+
     public boolean isShowingAmbient() {
         return mShowAmbient;
     }
@@ -2344,6 +2406,20 @@
         }
     }
 
+    @Override
+    public void setCurrentSidePaddings(float currentSidePaddings) {
+        if (mIsSummaryWithChildren) {
+            List<ExpandableNotificationRow> notificationChildren =
+                    mChildrenContainer.getNotificationChildren();
+            int size = notificationChildren.size();
+            for (int i = 0; i < size; i++) {
+                ExpandableNotificationRow row = notificationChildren.get(i);
+                row.setCurrentSidePaddings(currentSidePaddings);
+            }
+        }
+        super.setCurrentSidePaddings(currentSidePaddings);
+    }
+
     public static class NotificationViewState extends ExpandableViewState {
 
         private final StackScrollState mOverallState;
diff --git a/com/android/systemui/statusbar/ExpandableOutlineView.java b/com/android/systemui/statusbar/ExpandableOutlineView.java
index 2556890..b3d6e32 100644
--- a/com/android/systemui/statusbar/ExpandableOutlineView.java
+++ b/com/android/systemui/statusbar/ExpandableOutlineView.java
@@ -18,23 +18,58 @@
 
 import android.content.Context;
 import android.content.res.Resources;
+import android.graphics.Canvas;
 import android.graphics.Outline;
+import android.graphics.Path;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.util.AttributeSet;
 import android.view.View;
 import android.view.ViewOutlineProvider;
+
+import com.android.settingslib.Utils;
 import com.android.systemui.R;
+import com.android.systemui.statusbar.notification.AnimatableProperty;
+import com.android.systemui.statusbar.notification.PropertyAnimator;
+import com.android.systemui.statusbar.stack.AnimationProperties;
+import com.android.systemui.statusbar.stack.StackStateAnimator;
 
 /**
  * Like {@link ExpandableView}, but setting an outline for the height and clipping.
  */
 public abstract class ExpandableOutlineView extends ExpandableView {
 
+    private static final AnimatableProperty TOP_ROUNDNESS = AnimatableProperty.from(
+            "topRoundness",
+            ExpandableOutlineView::setTopRoundnessInternal,
+            ExpandableOutlineView::getCurrentTopRoundness,
+            R.id.top_roundess_animator_tag,
+            R.id.top_roundess_animator_end_tag,
+            R.id.top_roundess_animator_start_tag);
+    private static final AnimatableProperty BOTTOM_ROUNDNESS = AnimatableProperty.from(
+            "bottomRoundness",
+            ExpandableOutlineView::setBottomRoundnessInternal,
+            ExpandableOutlineView::getCurrentBottomRoundness,
+            R.id.bottom_roundess_animator_tag,
+            R.id.bottom_roundess_animator_end_tag,
+            R.id.bottom_roundess_animator_start_tag);
+    private static final AnimationProperties ROUNDNESS_PROPERTIES =
+            new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
+    private static final Path EMPTY_PATH = new Path();
+
     private final Rect mOutlineRect = new Rect();
     private boolean mCustomOutline;
     private float mOutlineAlpha = -1f;
     private float mOutlineRadius;
+    private boolean mAlwaysRoundBothCorners;
+    private Path mTmpPath = new Path();
+    private Path mTmpPath2 = new Path();
+    private float mCurrentBottomRoundness;
+    private float mCurrentTopRoundness;
+    private float mBottomRoundness;
+    private float mTopRoundness;
+    private int mBackgroundTop;
+    protected int mCurrentSidePaddings;
 
     /**
      * {@code true} if the children views of the {@link ExpandableOutlineView} are translated when
@@ -45,61 +80,248 @@
     private final ViewOutlineProvider mProvider = new ViewOutlineProvider() {
         @Override
         public void getOutline(View view, Outline outline) {
-            int translation = mShouldTranslateContents ? (int) getTranslation() : 0;
-            if (!mCustomOutline) {
-                outline.setRoundRect(translation,
-                        mClipTopAmount,
-                        getWidth() + translation,
-                        Math.max(getActualHeight() - mClipBottomAmount, mClipTopAmount),
-                        mOutlineRadius);
-            } else {
-                outline.setRoundRect(mOutlineRect, mOutlineRadius);
+            Path clipPath = getClipPath();
+            if (clipPath != null && clipPath.isConvex()) {
+                // The path might not be convex in border cases where the view is small and clipped
+                outline.setConvexPath(clipPath);
             }
             outline.setAlpha(mOutlineAlpha);
         }
     };
 
+    private Path getClipPath() {
+        return getClipPath(false, /* ignoreTranslation */
+                false /* clipRoundedToBottom */);
+    }
+
+    protected Path getClipPath(boolean ignoreTranslation, boolean clipRoundedToBottom) {
+        int left;
+        int top;
+        int right;
+        int bottom;
+        int height;
+        Path intersectPath = null;
+        if (!mCustomOutline) {
+            int translation = mShouldTranslateContents && !ignoreTranslation
+                    ? (int) getTranslation() : 0;
+            left = Math.max(translation + mCurrentSidePaddings, mCurrentSidePaddings);
+            top = mClipTopAmount + mBackgroundTop;
+            right = getWidth() - mCurrentSidePaddings + Math.min(translation, 0);
+            bottom = Math.max(getActualHeight(), top);
+            int intersectBottom = Math.max(getActualHeight() - mClipBottomAmount, top);
+            if (bottom != intersectBottom) {
+                if (clipRoundedToBottom) {
+                    bottom = intersectBottom;
+                } else {
+                    getRoundedRectPath(left, top, right,
+                            intersectBottom, 0.0f,
+                            0.0f, mTmpPath2);
+                    intersectPath = mTmpPath2;
+                }
+            }
+        } else {
+            left = mOutlineRect.left;
+            top = mOutlineRect.top;
+            right = mOutlineRect.right;
+            bottom = mOutlineRect.bottom;
+            left = Math.max(mCurrentSidePaddings, left);
+            right = Math.min(getWidth() - mCurrentSidePaddings, right);
+        }
+        height = bottom - top;
+        if (height == 0) {
+            return EMPTY_PATH;
+        }
+        float topRoundness = mAlwaysRoundBothCorners
+                ? mOutlineRadius : mCurrentTopRoundness * mOutlineRadius;
+        float bottomRoundness = mAlwaysRoundBothCorners
+                ? mOutlineRadius : mCurrentBottomRoundness * mOutlineRadius;
+        if (topRoundness + bottomRoundness > height) {
+            float overShoot = topRoundness + bottomRoundness - height;
+            topRoundness -= overShoot * mCurrentTopRoundness
+                    / (mCurrentTopRoundness + mCurrentBottomRoundness);
+            bottomRoundness -= overShoot * mCurrentBottomRoundness
+                    / (mCurrentTopRoundness + mCurrentBottomRoundness);
+        }
+        getRoundedRectPath(left, top, right, bottom, topRoundness,
+                bottomRoundness, mTmpPath);
+        Path roundedRectPath = mTmpPath;
+        if (intersectPath != null) {
+            roundedRectPath.op(intersectPath, Path.Op.INTERSECT);
+        }
+        return roundedRectPath;
+    }
+
+    protected Path getRoundedRectPath(int left, int top, int right, int bottom, float topRoundness,
+            float bottomRoundness) {
+        getRoundedRectPath(left, top, right, bottom, topRoundness, bottomRoundness,
+                mTmpPath);
+        return mTmpPath;
+    }
+
+    private void getRoundedRectPath(int left, int top, int right, int bottom, float topRoundness,
+            float bottomRoundness, Path outPath) {
+        outPath.reset();
+        int width = right - left;
+        float topRoundnessX = topRoundness;
+        float bottomRoundnessX = bottomRoundness;
+        topRoundnessX = Math.min(width / 2, topRoundnessX);
+        bottomRoundnessX = Math.min(width / 2, bottomRoundnessX);
+        if (topRoundness > 0.0f) {
+            outPath.moveTo(left, top + topRoundness);
+            outPath.quadTo(left, top, left + topRoundnessX, top);
+            outPath.lineTo(right - topRoundnessX, top);
+            outPath.quadTo(right, top, right, top + topRoundness);
+        } else {
+            outPath.moveTo(left, top);
+            outPath.lineTo(right, top);
+        }
+        if (bottomRoundness > 0.0f) {
+            outPath.lineTo(right, bottom - bottomRoundness);
+            outPath.quadTo(right, bottom, right - bottomRoundnessX, bottom);
+            outPath.lineTo(left + bottomRoundnessX, bottom);
+            outPath.quadTo(left, bottom, left, bottom - bottomRoundness);
+        } else {
+            outPath.lineTo(right, bottom);
+            outPath.lineTo(left, bottom);
+        }
+        outPath.close();
+    }
+
     public ExpandableOutlineView(Context context, AttributeSet attrs) {
         super(context, attrs);
         setOutlineProvider(mProvider);
         initDimens();
     }
 
+    @Override
+    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
+        canvas.save();
+        if (childNeedsClipping(child)) {
+            Path clipPath = getCustomClipPath(child);
+            if (clipPath == null) {
+                clipPath = getClipPath();
+            }
+            if (clipPath != null) {
+                canvas.clipPath(clipPath);
+            }
+        }
+        boolean result = super.drawChild(canvas, child, drawingTime);
+        canvas.restore();
+        return result;
+    }
+
+    protected boolean childNeedsClipping(View child) {
+        return false;
+    }
+
+    protected boolean isClippingNeeded() {
+        return mAlwaysRoundBothCorners || mCustomOutline || getTranslation() != 0 ;
+
+    }
+
     private void initDimens() {
         Resources res = getResources();
         mShouldTranslateContents =
                 res.getBoolean(R.bool.config_translateNotificationContentsOnSwipe);
         mOutlineRadius = res.getDimension(R.dimen.notification_shadow_radius);
-        setClipToOutline(res.getBoolean(R.bool.config_clipNotificationsToOutline));
+        mAlwaysRoundBothCorners = res.getBoolean(R.bool.config_clipNotificationsToOutline);
+        if (!mAlwaysRoundBothCorners) {
+            mOutlineRadius = res.getDimensionPixelSize(
+                    Utils.getThemeAttr(mContext, android.R.attr.dialogCornerRadius));
+        }
+        setClipToOutline(mAlwaysRoundBothCorners);
+    }
+
+    public void setTopRoundness(float topRoundness, boolean animate) {
+        if (mTopRoundness != topRoundness) {
+            mTopRoundness = topRoundness;
+            PropertyAnimator.setProperty(this, TOP_ROUNDNESS, topRoundness,
+                    ROUNDNESS_PROPERTIES, animate);
+        }
+    }
+
+    protected void applyRoundness() {
+        invalidateOutline();
+        invalidate();
+    }
+
+    public float getCurrentBackgroundRadiusTop() {
+        return mCurrentTopRoundness * mOutlineRadius;
+    }
+
+    public float getCurrentTopRoundness() {
+        return mCurrentTopRoundness;
+    }
+
+    public float getCurrentBottomRoundness() {
+        return mCurrentBottomRoundness;
+    }
+
+    protected float getCurrentBackgroundRadiusBottom() {
+        return mCurrentBottomRoundness * mOutlineRadius;
+    }
+
+    public void setBottomRoundness(float bottomRoundness, boolean animate) {
+        if (mBottomRoundness != bottomRoundness) {
+            mBottomRoundness = bottomRoundness;
+            PropertyAnimator.setProperty(this, BOTTOM_ROUNDNESS, bottomRoundness,
+                    ROUNDNESS_PROPERTIES, animate);
+        }
+    }
+
+    protected void setBackgroundTop(int backgroundTop) {
+        if (mBackgroundTop != backgroundTop) {
+            mBackgroundTop = backgroundTop;
+            invalidateOutline();
+        }
+    }
+
+    private void setTopRoundnessInternal(float topRoundness) {
+        mCurrentTopRoundness = topRoundness;
+        applyRoundness();
+    }
+
+    private void setBottomRoundnessInternal(float bottomRoundness) {
+        mCurrentBottomRoundness = bottomRoundness;
+        applyRoundness();
     }
 
     public void onDensityOrFontScaleChanged() {
         initDimens();
-        invalidateOutline();
+        applyRoundness();
     }
 
     @Override
     public void setActualHeight(int actualHeight, boolean notifyListeners) {
+        int previousHeight = getActualHeight();
         super.setActualHeight(actualHeight, notifyListeners);
-        invalidateOutline();
+        if (previousHeight != actualHeight) {
+            applyRoundness();
+        }
     }
 
     @Override
     public void setClipTopAmount(int clipTopAmount) {
+        int previousAmount = getClipTopAmount();
         super.setClipTopAmount(clipTopAmount);
-        invalidateOutline();
+        if (previousAmount != clipTopAmount) {
+            applyRoundness();
+        }
     }
 
     @Override
     public void setClipBottomAmount(int clipBottomAmount) {
+        int previousAmount = getClipBottomAmount();
         super.setClipBottomAmount(clipBottomAmount);
-        invalidateOutline();
+        if (previousAmount != clipBottomAmount) {
+            applyRoundness();
+        }
     }
 
     protected void setOutlineAlpha(float alpha) {
         if (alpha != mOutlineAlpha) {
             mOutlineAlpha = alpha;
-            invalidateOutline();
+            applyRoundness();
         }
     }
 
@@ -113,8 +335,7 @@
             setOutlineRect(rect.left, rect.top, rect.right, rect.bottom);
         } else {
             mCustomOutline = false;
-            setClipToOutline(false);
-            invalidateOutline();
+            applyRoundness();
         }
     }
 
@@ -151,15 +372,22 @@
 
     protected void setOutlineRect(float left, float top, float right, float bottom) {
         mCustomOutline = true;
-        setClipToOutline(true);
 
         mOutlineRect.set((int) left, (int) top, (int) right, (int) bottom);
 
         // Outlines need to be at least 1 dp
         mOutlineRect.bottom = (int) Math.max(top, mOutlineRect.bottom);
         mOutlineRect.right = (int) Math.max(left, mOutlineRect.right);
-
-        invalidateOutline();
+        applyRoundness();
     }
 
+    public Path getCustomClipPath(View child) {
+        return null;
+    }
+
+    public void setCurrentSidePaddings(float currentSidePaddings) {
+        mCurrentSidePaddings = (int) currentSidePaddings;
+        invalidateOutline();
+        invalidate();
+    }
 }
diff --git a/com/android/systemui/statusbar/ExpandableView.java b/com/android/systemui/statusbar/ExpandableView.java
index aac9af8..18b9860 100644
--- a/com/android/systemui/statusbar/ExpandableView.java
+++ b/com/android/systemui/statusbar/ExpandableView.java
@@ -202,6 +202,10 @@
         return mDark;
     }
 
+    public boolean isRemoved() {
+        return false;
+    }
+
     /**
      * See {@link #setHideSensitive}. This is a variant which notifies this view in advance about
      * the upcoming state of hiding sensitive notifications. It gets called at the very beginning
diff --git a/com/android/systemui/statusbar/NotificationBackgroundView.java b/com/android/systemui/statusbar/NotificationBackgroundView.java
index 81a99bc..68cf51c 100644
--- a/com/android/systemui/statusbar/NotificationBackgroundView.java
+++ b/com/android/systemui/statusbar/NotificationBackgroundView.java
@@ -19,37 +19,57 @@
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.graphics.Canvas;
-import android.graphics.ColorFilter;
 import android.graphics.PorterDuff;
 import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.LayerDrawable;
 import android.graphics.drawable.RippleDrawable;
 import android.util.AttributeSet;
 import android.view.View;
 
+import com.android.systemui.R;
+
 /**
  * A view that can be used for both the dimmed and normal background of an notification.
  */
 public class NotificationBackgroundView extends View {
 
+    private final boolean mDontModifyCorners;
     private Drawable mBackground;
     private int mClipTopAmount;
     private int mActualHeight;
     private int mClipBottomAmount;
     private int mTintColor;
+    private float[] mCornerRadii = new float[8];
+    private int mCurrentSidePaddings;
+    private boolean mBottomIsRounded;
+    private int mBackgroundTop;
+    private boolean mBottomAmountClips = true;
 
     public NotificationBackgroundView(Context context, AttributeSet attrs) {
         super(context, attrs);
+        mDontModifyCorners = getResources().getBoolean(
+                R.bool.config_clipNotificationsToOutline);
     }
 
     @Override
     protected void onDraw(Canvas canvas) {
-        draw(canvas, mBackground);
+        if (mClipTopAmount + mClipBottomAmount < mActualHeight - mBackgroundTop) {
+            canvas.save();
+            canvas.clipRect(0, mClipTopAmount, getWidth(), mActualHeight - mClipBottomAmount);
+            draw(canvas, mBackground);
+            canvas.restore();
+        }
     }
 
     private void draw(Canvas canvas, Drawable drawable) {
-        int bottom = mActualHeight - mClipBottomAmount;
-        if (drawable != null && bottom > mClipTopAmount) {
-            drawable.setBounds(0, mClipTopAmount, getWidth(), bottom);
+        if (drawable != null) {
+            int bottom = mActualHeight;
+            if (mBottomIsRounded && mBottomAmountClips) {
+                bottom -= mClipBottomAmount;
+            }
+            drawable.setBounds(mCurrentSidePaddings, mBackgroundTop,
+                    getWidth() - mCurrentSidePaddings, bottom);
             drawable.draw(canvas);
         }
     }
@@ -87,6 +107,7 @@
             unscheduleDrawable(mBackground);
         }
         mBackground = background;
+        mBackground.mutate();
         if (mBackground != null) {
             mBackground.setCallback(this);
             setTint(mTintColor);
@@ -94,6 +115,7 @@
         if (mBackground instanceof RippleDrawable) {
             ((RippleDrawable) mBackground).setForceSoftware(true);
         }
+        updateBackgroundRadii();
         invalidate();
     }
 
@@ -152,4 +174,45 @@
     public void setDrawableAlpha(int drawableAlpha) {
         mBackground.setAlpha(drawableAlpha);
     }
+
+    public void setRoundness(float topRoundness, float bottomRoundNess) {
+        mBottomIsRounded = bottomRoundNess != 0.0f;
+        mCornerRadii[0] = topRoundness;
+        mCornerRadii[1] = topRoundness;
+        mCornerRadii[2] = topRoundness;
+        mCornerRadii[3] = topRoundness;
+        mCornerRadii[4] = bottomRoundNess;
+        mCornerRadii[5] = bottomRoundNess;
+        mCornerRadii[6] = bottomRoundNess;
+        mCornerRadii[7] = bottomRoundNess;
+        updateBackgroundRadii();
+    }
+
+    public void setBottomAmountClips(boolean clips) {
+        if (clips != mBottomAmountClips) {
+            mBottomAmountClips = clips;
+            invalidate();
+        }
+    }
+
+    private void updateBackgroundRadii() {
+        if (mDontModifyCorners) {
+            return;
+        }
+        if (mBackground instanceof LayerDrawable) {
+            GradientDrawable gradientDrawable =
+                    (GradientDrawable) ((LayerDrawable) mBackground).getDrawable(0);
+            gradientDrawable.setCornerRadii(mCornerRadii);
+        }
+    }
+
+    public void setCurrentSidePaddings(float currentSidePaddings) {
+        mCurrentSidePaddings = (int) currentSidePaddings;
+        invalidate();
+    }
+
+    public void setBackgroundTop(int backgroundTop) {
+        mBackgroundTop = backgroundTop;
+        invalidate();
+    }
 }
diff --git a/com/android/systemui/statusbar/NotificationContentView.java b/com/android/systemui/statusbar/NotificationContentView.java
index 9e059c8..39c2131 100644
--- a/com/android/systemui/statusbar/NotificationContentView.java
+++ b/com/android/systemui/statusbar/NotificationContentView.java
@@ -24,6 +24,7 @@
 import android.os.Build;
 import android.service.notification.StatusBarNotification;
 import android.util.AttributeSet;
+import android.util.Log;
 import android.view.NotificationHeaderView;
 import android.view.View;
 import android.view.ViewGroup;
@@ -34,8 +35,8 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.NotificationColorUtil;
 import com.android.systemui.R;
-import com.android.systemui.statusbar.notification.HybridNotificationView;
 import com.android.systemui.statusbar.notification.HybridGroupManager;
+import com.android.systemui.statusbar.notification.HybridNotificationView;
 import com.android.systemui.statusbar.notification.NotificationCustomViewWrapper;
 import com.android.systemui.statusbar.notification.NotificationUtils;
 import com.android.systemui.statusbar.notification.NotificationViewWrapper;
@@ -49,6 +50,7 @@
  */
 public class NotificationContentView extends FrameLayout {
 
+    private static final String TAG = "NotificationContentView";
     public static final int VISIBLE_TYPE_CONTRACTED = 0;
     public static final int VISIBLE_TYPE_EXPANDED = 1;
     public static final int VISIBLE_TYPE_HEADSUP = 2;
@@ -58,9 +60,9 @@
     public static final int UNDEFINED = -1;
 
     private final Rect mClipBounds = new Rect();
-    private final int mMinContractedHeight;
-    private final int mNotificationContentMarginEnd;
 
+    private int mMinContractedHeight;
+    private int mNotificationContentMarginEnd;
     private View mContractedChild;
     private View mExpandedChild;
     private View mHeadsUpChild;
@@ -134,15 +136,22 @@
     private int mClipBottomAmount;
     private boolean mIsLowPriority;
     private boolean mIsContentExpandable;
+    private int mCustomViewSidePaddings;
 
 
     public NotificationContentView(Context context, AttributeSet attrs) {
         super(context, attrs);
         mHybridGroupManager = new HybridGroupManager(getContext(), this);
+        initView();
+    }
+
+    public void initView() {
         mMinContractedHeight = getResources().getDimensionPixelSize(
                 R.dimen.min_notification_layout_height);
         mNotificationContentMarginEnd = getResources().getDimensionPixelSize(
                 com.android.internal.R.dimen.notification_content_margin_end);
+        mCustomViewSidePaddings = getResources().getDimensionPixelSize(
+                R.dimen.notification_content_custom_view_side_padding);
     }
 
     public void setHeights(int smallHeight, int headsUpMaxHeight, int maxHeight,
@@ -178,7 +187,7 @@
                     : MeasureSpec.makeMeasureSpec(size, useExactly
                             ? MeasureSpec.EXACTLY
                             : MeasureSpec.AT_MOST);
-            mExpandedChild.measure(widthMeasureSpec, spec);
+            measureChildWithMargins(mExpandedChild, widthMeasureSpec, 0, spec, 0);
             maxChildHeight = Math.max(maxChildHeight, mExpandedChild.getMeasuredHeight());
         }
         if (mContractedChild != null) {
@@ -196,22 +205,22 @@
             } else {
                 heightSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST);
             }
-            mContractedChild.measure(widthMeasureSpec, heightSpec);
+            measureChildWithMargins(mContractedChild, widthMeasureSpec, 0, heightSpec, 0);
             int measuredHeight = mContractedChild.getMeasuredHeight();
             if (measuredHeight < mMinContractedHeight) {
                 heightSpec = MeasureSpec.makeMeasureSpec(mMinContractedHeight, MeasureSpec.EXACTLY);
-                mContractedChild.measure(widthMeasureSpec, heightSpec);
+                measureChildWithMargins(mContractedChild, widthMeasureSpec, 0, heightSpec, 0);
             }
             maxChildHeight = Math.max(maxChildHeight, measuredHeight);
             if (updateContractedHeaderWidth()) {
-                mContractedChild.measure(widthMeasureSpec, heightSpec);
+                measureChildWithMargins(mContractedChild, widthMeasureSpec, 0, heightSpec, 0);
             }
             if (mExpandedChild != null
                     && mContractedChild.getMeasuredHeight() > mExpandedChild.getMeasuredHeight()) {
                 // the Expanded child is smaller then the collapsed. Let's remeasure it.
                 heightSpec = MeasureSpec.makeMeasureSpec(mContractedChild.getMeasuredHeight(),
                         MeasureSpec.EXACTLY);
-                mExpandedChild.measure(widthMeasureSpec, heightSpec);
+                measureChildWithMargins(mExpandedChild, widthMeasureSpec, 0, heightSpec, 0);
             }
         }
         if (mHeadsUpChild != null) {
@@ -223,9 +232,9 @@
                 size = Math.min(size, layoutParams.height);
                 useExactly = true;
             }
-            mHeadsUpChild.measure(widthMeasureSpec,
+            measureChildWithMargins(mHeadsUpChild, widthMeasureSpec, 0,
                     MeasureSpec.makeMeasureSpec(size, useExactly ? MeasureSpec.EXACTLY
-                            : MeasureSpec.AT_MOST));
+                            : MeasureSpec.AT_MOST), 0);
             maxChildHeight = Math.max(maxChildHeight, mHeadsUpChild.getMeasuredHeight());
         }
         if (mSingleLineView != null) {
@@ -382,6 +391,38 @@
         mContractedWrapper = NotificationViewWrapper.wrap(getContext(), child,
                 mContainingNotification);
         mContractedWrapper.setDark(mDark, false /* animate */, 0 /* delay */);
+        updateMargins(child);
+    }
+
+    private void updateMargins(View child) {
+        if (child == null) {
+            return;
+        }
+        NotificationViewWrapper wrapper = getWrapperForView(child);
+        boolean isCustomView = wrapper instanceof NotificationCustomViewWrapper;
+        boolean needsMargins = isCustomView &&
+                child.getContext().getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.P;
+        int padding = needsMargins ? mCustomViewSidePaddings : 0;
+        MarginLayoutParams layoutParams = (MarginLayoutParams) child.getLayoutParams();
+        layoutParams.setMarginStart(padding);
+        layoutParams.setMarginEnd(padding);
+        child.setLayoutParams(layoutParams);
+    }
+
+    private NotificationViewWrapper getWrapperForView(View child) {
+        if (child == mContractedChild) {
+            return mContractedWrapper;
+        }
+        if (child == mExpandedChild) {
+            return mExpandedWrapper;
+        }
+        if (child == mHeadsUpChild) {
+            return mHeadsUpWrapper;
+        }
+        if (child == mAmbientChild) {
+            return mAmbientWrapper;
+        }
+        return null;
     }
 
     public void setExpandedChild(View child) {
@@ -415,6 +456,7 @@
         mExpandedChild = child;
         mExpandedWrapper = NotificationViewWrapper.wrap(getContext(), child,
                 mContainingNotification);
+        updateMargins(child);
     }
 
     public void setHeadsUpChild(View child) {
@@ -448,6 +490,7 @@
         mHeadsUpChild = child;
         mHeadsUpWrapper = NotificationViewWrapper.wrap(getContext(), child,
                 mContainingNotification);
+        updateMargins(child);
     }
 
     public void setAmbientChild(View child) {
@@ -643,6 +686,13 @@
         int endHeight = getViewForVisibleType(mVisibleType).getHeight();
         int progress = Math.abs(mContentHeight - startHeight);
         int totalDistance = Math.abs(endHeight - startHeight);
+        if (totalDistance == 0) {
+            Log.wtf(TAG, "the total transformation distance is 0"
+                    + "\n StartType: " + mTransformationStartVisibleType + " height: " + startHeight
+                    + "\n VisibleType: " + mVisibleType + " height: " + endHeight
+                    + "\n mContentHeight: " + mContentHeight);
+            return 1.0f;
+        }
         float amount = (float) progress / (float) totalDistance;
         return Math.min(1.0f, amount);
     }
@@ -1459,4 +1509,20 @@
         }
         return false;
     }
+
+    public boolean shouldClipToSidePaddings() {
+        boolean needsPaddings = shouldClipToSidePaddings(getVisibleType());
+        if (mUserExpanding) {
+             needsPaddings |= shouldClipToSidePaddings(mTransformationStartVisibleType);
+        }
+        return needsPaddings;
+    }
+
+    private boolean shouldClipToSidePaddings(int visibleType) {
+        NotificationViewWrapper visibleWrapper = getVisibleWrapper(visibleType);
+        if (visibleWrapper == null) {
+            return false;
+        }
+        return visibleWrapper.shouldClipToSidePaddings();
+    }
 }
diff --git a/com/android/systemui/statusbar/NotificationGutsManager.java b/com/android/systemui/statusbar/NotificationGutsManager.java
index b585bdf..f451fda 100644
--- a/com/android/systemui/statusbar/NotificationGutsManager.java
+++ b/com/android/systemui/statusbar/NotificationGutsManager.java
@@ -37,6 +37,7 @@
 
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto;
+import com.android.internal.statusbar.IStatusBarService;
 import com.android.systemui.Dependency;
 import com.android.systemui.Dumpable;
 import com.android.systemui.Interpolators;
@@ -75,17 +76,20 @@
     private NotificationGuts mNotificationGutsExposed;
     private NotificationMenuRowPlugin.MenuItem mGutsMenuItem;
     private final NotificationInfo.CheckSaveListener mCheckSaveListener;
+    private final OnSettingsClickListener mOnSettingsClickListener;
     private String mKeyToRemoveOnGutsClosed;
 
     public NotificationGutsManager(
             NotificationPresenter presenter,
             NotificationStackScrollLayout stackScroller,
             NotificationInfo.CheckSaveListener checkSaveListener,
-            Context context) {
+            Context context,
+            OnSettingsClickListener onSettingsClickListener) {
         mPresenter = presenter;
         mStackScroller = stackScroller;
         mCheckSaveListener = checkSaveListener;
         mContext = context;
+        mOnSettingsClickListener = onSettingsClickListener;
         Resources res = context.getResources();
 
         mNonBlockablePkgs = new HashSet<>();
@@ -189,6 +193,7 @@
                 onSettingsClick = (View v, NotificationChannel channel, int appUid) -> {
                     mMetricsLogger.action(MetricsProto.MetricsEvent.ACTION_NOTE_INFO);
                     guts.resetFalsingCheck();
+                    mOnSettingsClickListener.onClick(sbn.getKey());
                     startAppNotificationSettingsActivity(pkg, appUid, channel);
                 };
             }
@@ -352,4 +357,8 @@
         pw.print("mKeyToRemoveOnGutsClosed: ");
         pw.println(mKeyToRemoveOnGutsClosed);
     }
+
+    public interface OnSettingsClickListener {
+        void onClick(String key);
+    }
 }
diff --git a/com/android/systemui/statusbar/NotificationMenuRow.java b/com/android/systemui/statusbar/NotificationMenuRow.java
index 99b4b07..b2604fe 100644
--- a/com/android/systemui/statusbar/NotificationMenuRow.java
+++ b/com/android/systemui/statusbar/NotificationMenuRow.java
@@ -88,6 +88,7 @@
     private float mHorizSpaceForIcon = -1;
     private int mVertSpaceForIcons = -1;
     private int mIconPadding = -1;
+    private int mSidePadding;
 
     private float mAlpha = 0f;
     private float mPrevX;
@@ -175,6 +176,7 @@
         final Resources res = mContext.getResources();
         mHorizSpaceForIcon = res.getDimensionPixelSize(R.dimen.notification_menu_icon_size);
         mVertSpaceForIcons = res.getDimensionPixelSize(R.dimen.notification_min_height);
+        mSidePadding = res.getDimensionPixelSize(R.dimen.notification_lockscreen_side_paddings);
         mIconPadding = res.getDimensionPixelSize(R.dimen.notification_menu_icon_padding);
         mMenuItems.clear();
         // Construct the menu items based on the notification
@@ -496,8 +498,8 @@
         final int count = mMenuContainer.getChildCount();
         for (int i = 0; i < count; i++) {
             final View v = mMenuContainer.getChildAt(i);
-            final float left = i * mHorizSpaceForIcon;
-            final float right = mParent.getWidth() - (mHorizSpaceForIcon * (i + 1));
+            final float left = mSidePadding + i * mHorizSpaceForIcon;
+            final float right = mParent.getWidth() - (mHorizSpaceForIcon * (i + 1)) - mSidePadding;
             v.setX(showOnLeft ? left : right);
         }
         mOnLeft = showOnLeft;
diff --git a/com/android/systemui/statusbar/NotificationShelf.java b/com/android/systemui/statusbar/NotificationShelf.java
index 5557dde..b7a00eb 100644
--- a/com/android/systemui/statusbar/NotificationShelf.java
+++ b/com/android/systemui/statusbar/NotificationShelf.java
@@ -85,6 +85,7 @@
     private boolean mVibrationOnAnimation;
     private boolean mUserTouchingScreen;
     private boolean mTouchActive;
+    private float mFirstElementRoundness;
 
     public NotificationShelf(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -107,6 +108,7 @@
         mViewInvertHelper = new ViewInvertHelper(mShelfIcons,
                 NotificationPanelView.DOZE_ANIMATION_DURATION);
         mShelfState = new ShelfState();
+        setBottomRoundness(1.0f, false /* animate */);
         initDimens();
     }
 
@@ -252,6 +254,8 @@
         boolean expandingAnimated = mAmbientState.isExpansionChanging()
                 && !mAmbientState.isPanelTracking();
         int baseZHeight = mAmbientState.getBaseZHeight();
+        int backgroundTop = 0;
+        float firstElementRoundness = 0.0f;
         while (notificationIndex < mHostLayout.getChildCount()) {
             ExpandableView child = (ExpandableView) mHostLayout.getChildAt(notificationIndex);
             notificationIndex++;
@@ -302,9 +306,20 @@
             if (notGoneIndex != 0 || !aboveShelf) {
                 row.setAboveShelf(false);
             }
+            if (notGoneIndex == 0) {
+                StatusBarIconView icon = row.getEntry().expandedIcon;
+                NotificationIconContainer.IconState iconState = getIconState(icon);
+                if (iconState.clampedAppearAmount == 1.0f) {
+                    // only if the first icon is fully in the shelf we want to clip to it!
+                    backgroundTop = (int) (row.getTranslationY() - getTranslationY());
+                    firstElementRoundness = row.getCurrentTopRoundness();
+                }
+            }
             notGoneIndex++;
             previousColor = ownColorUntinted;
         }
+        setBackgroundTop(backgroundTop);
+        setFirstElementRoundness(firstElementRoundness);
         mShelfIcons.setSpeedBumpIndex(mAmbientState.getSpeedBumpIndex());
         mShelfIcons.calculateIconTranslations();
         mShelfIcons.applyIconStates();
@@ -325,6 +340,13 @@
         }
     }
 
+    private void setFirstElementRoundness(float firstElementRoundness) {
+        if (mFirstElementRoundness != firstElementRoundness) {
+            mFirstElementRoundness = firstElementRoundness;
+            setTopRoundness(firstElementRoundness, false /* animate */);
+        }
+    }
+
     private void updateIconClipAmount(ExpandableNotificationRow row) {
         float maxTop = row.getTranslationY();
         StatusBarIconView icon = row.getEntry().expandedIcon;
diff --git a/com/android/systemui/statusbar/RemoteInputController.java b/com/android/systemui/statusbar/RemoteInputController.java
index 7f28c4c..ff6c775 100644
--- a/com/android/systemui/statusbar/RemoteInputController.java
+++ b/com/android/systemui/statusbar/RemoteInputController.java
@@ -19,7 +19,6 @@
 import com.android.internal.util.Preconditions;
 import com.android.systemui.Dependency;
 import com.android.systemui.statusbar.phone.StatusBarWindowManager;
-import com.android.systemui.statusbar.policy.HeadsUpManager;
 import com.android.systemui.statusbar.policy.RemoteInputView;
 
 import android.util.ArrayMap;
@@ -38,11 +37,11 @@
             = new ArrayList<>();
     private final ArrayMap<String, Object> mSpinning = new ArrayMap<>();
     private final ArrayList<Callback> mCallbacks = new ArrayList<>(3);
-    private final HeadsUpManager mHeadsUpManager;
+    private final Delegate mDelegate;
 
-    public RemoteInputController(HeadsUpManager headsUpManager) {
+    public RemoteInputController(Delegate delegate) {
         addCallback(Dependency.get(StatusBarWindowManager.class));
-        mHeadsUpManager = headsUpManager;
+        mDelegate = delegate;
     }
 
     /**
@@ -114,7 +113,7 @@
     }
 
     private void apply(NotificationData.Entry entry) {
-        mHeadsUpManager.setRemoteInputActive(entry, isRemoteInputActive(entry));
+        mDelegate.setRemoteInputActive(entry, isRemoteInputActive(entry));
         boolean remoteInputActive = isRemoteInputActive();
         int N = mCallbacks.size();
         for (int i = 0; i < N; i++) {
@@ -204,9 +203,35 @@
         }
     }
 
+    public void requestDisallowLongPressAndDismiss() {
+        mDelegate.requestDisallowLongPressAndDismiss();
+    }
+
+    public void lockScrollTo(NotificationData.Entry entry) {
+        mDelegate.lockScrollTo(entry);
+    }
+
     public interface Callback {
         default void onRemoteInputActive(boolean active) {}
 
         default void onRemoteInputSent(NotificationData.Entry entry) {}
     }
+
+    public interface Delegate {
+        /**
+         * Activate remote input if necessary.
+         */
+        void setRemoteInputActive(NotificationData.Entry entry, boolean remoteInputActive);
+
+       /**
+        * Request that the view does not dismiss nor perform long press for the current touch.
+        */
+       void requestDisallowLongPressAndDismiss();
+
+      /**
+       * Request that the view is made visible by scrolling to it, and keep the scroll locked until
+       * the user scrolls, or {@param v} loses focus or is detached.
+       */
+       void lockScrollTo(NotificationData.Entry entry);
+    }
 }
diff --git a/com/android/systemui/statusbar/ScrimView.java b/com/android/systemui/statusbar/ScrimView.java
index a53e348..8830352 100644
--- a/com/android/systemui/statusbar/ScrimView.java
+++ b/com/android/systemui/statusbar/ScrimView.java
@@ -41,6 +41,7 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.colorextraction.ColorExtractor;
 import com.android.internal.colorextraction.drawable.GradientDrawable;
+import com.android.settingslib.Utils;
 import com.android.systemui.Dependency;
 import com.android.systemui.statusbar.policy.ConfigurationController;
 
@@ -50,6 +51,7 @@
 public class ScrimView extends View implements ConfigurationController.ConfigurationListener {
     private static final String TAG = "ScrimView";
     private final ColorExtractor.GradientColors mColors;
+    private int mDensity;
     private boolean mDrawAsSrc;
     private float mViewAlpha = 1.0f;
     private ValueAnimator mAlphaAnimator;
@@ -72,6 +74,7 @@
         }
     };
     private Runnable mChangeRunnable;
+    private int mCornerRadius;
 
     public ScrimView(Context context) {
         this(context, null);
@@ -93,6 +96,24 @@
         mColors = new ColorExtractor.GradientColors();
         updateScreenSize();
         updateColorWithTint(false);
+        initView();
+        final Configuration currentConfig = mContext.getResources().getConfiguration();
+        mDensity = currentConfig.densityDpi;
+    }
+
+    private void initView() {
+        mCornerRadius = getResources().getDimensionPixelSize(
+                Utils.getThemeAttr(mContext, android.R.attr.dialogCornerRadius));
+    }
+
+    @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        int densityDpi = newConfig.densityDpi;
+        if (mDensity != densityDpi) {
+            mDensity = densityDpi;
+            initView();
+        }
     }
 
     @Override
@@ -145,6 +166,28 @@
                     mDrawable.draw(canvas);
                     canvas.restore();
                 }
+                // We also need to draw the rounded corners of the background
+                canvas.save();
+                canvas.clipRect(mExcludedRect.left, mExcludedRect.top,
+                        mExcludedRect.left + mCornerRadius, mExcludedRect.top + mCornerRadius);
+                mDrawable.draw(canvas);
+                canvas.restore();
+                canvas.save();
+                canvas.clipRect(mExcludedRect.right - mCornerRadius, mExcludedRect.top,
+                        mExcludedRect.right, mExcludedRect.top + mCornerRadius);
+                mDrawable.draw(canvas);
+                canvas.restore();
+                canvas.save();
+                canvas.clipRect(mExcludedRect.left, mExcludedRect.bottom - mCornerRadius,
+                        mExcludedRect.left + mCornerRadius, mExcludedRect.bottom);
+                mDrawable.draw(canvas);
+                canvas.restore();
+                canvas.save();
+                canvas.clipRect(mExcludedRect.right - mCornerRadius,
+                        mExcludedRect.bottom - mCornerRadius,
+                        mExcludedRect.right, mExcludedRect.bottom);
+                mDrawable.draw(canvas);
+                canvas.restore();
             }
         }
     }
@@ -252,6 +295,13 @@
         return false;
     }
 
+    /**
+     * It might look counterintuitive to have another method to set the alpha instead of
+     * only using {@link #setAlpha(float)}. In this case we're in a hardware layer
+     * optimizing blend modes, so it makes sense.
+     *
+     * @param alpha Gradient alpha from 0 to 1.
+     */
     public void setViewAlpha(float alpha) {
         if (alpha != mViewAlpha) {
             mViewAlpha = alpha;
diff --git a/com/android/systemui/statusbar/car/CarNavigationBarController.java b/com/android/systemui/statusbar/car/CarNavigationBarController.java
index f5c77f2..64c52ed 100644
--- a/com/android/systemui/statusbar/car/CarNavigationBarController.java
+++ b/com/android/systemui/statusbar/car/CarNavigationBarController.java
@@ -369,7 +369,7 @@
     private void onFacetClicked(Intent intent, int index) {
         String packageName = intent.getPackage();
 
-        if (packageName == null) {
+        if (packageName == null && !intent.getCategories().contains(Intent.CATEGORY_HOME)) {
             return;
         }
 
diff --git a/com/android/systemui/statusbar/notification/AnimatableProperty.java b/com/android/systemui/statusbar/notification/AnimatableProperty.java
new file mode 100644
index 0000000..d7b211f
--- /dev/null
+++ b/com/android/systemui/statusbar/notification/AnimatableProperty.java
@@ -0,0 +1,77 @@
+/*
+ * 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.statusbar.notification;
+
+import android.util.FloatProperty;
+import android.util.Property;
+import android.view.View;
+
+import com.android.systemui.statusbar.stack.AnimationProperties;
+
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+/**
+ * An animatable property of a view. Used with {@link PropertyAnimator}
+ */
+public interface AnimatableProperty {
+    int getAnimationStartTag();
+
+    int getAnimationEndTag();
+
+    int getAnimatorTag();
+
+    Property getProperty();
+
+    static <T extends View> AnimatableProperty from(String name, BiConsumer<T, Float> setter,
+            Function<T, Float> getter, int animatorTag, int startValueTag, int endValueTag) {
+        Property<T, Float> property = new FloatProperty<T>(name) {
+
+            @Override
+            public Float get(T object) {
+                return getter.apply(object);
+            }
+
+            @Override
+            public void setValue(T object, float value) {
+                setter.accept(object, value);
+            }
+        };
+        return new AnimatableProperty() {
+            @Override
+            public int getAnimationStartTag() {
+                return startValueTag;
+            }
+
+            @Override
+            public int getAnimationEndTag() {
+                return endValueTag;
+            }
+
+            @Override
+            public int getAnimatorTag() {
+                return animatorTag;
+            }
+
+            @Override
+            public Property getProperty() {
+                return property;
+            }
+        };
+    }
+}
diff --git a/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java b/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java
index fc420eb..27defca 100644
--- a/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java
+++ b/com/android/systemui/statusbar/notification/MessagingLayoutTransformState.java
@@ -89,6 +89,7 @@
 
     private void transformViewInternal(MessagingLayoutTransformState mlt,
             float transformationAmount, boolean to) {
+        ensureVisible();
         ArrayList<MessagingGroup> ownGroups = filterHiddenGroups(
                 mMessagingLayout.getMessagingGroups());
         ArrayList<MessagingGroup> otherGroups = filterHiddenGroups(
@@ -332,6 +333,7 @@
 
     @Override
     public void setVisible(boolean visible, boolean force) {
+        super.setVisible(visible, force);
         resetTransformedView();
         ArrayList<MessagingGroup> ownGroups = mMessagingLayout.getMessagingGroups();
         for (int i = 0; i < ownGroups.size(); i++) {
diff --git a/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java b/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java
index bca4b43..66682e4 100644
--- a/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java
+++ b/com/android/systemui/statusbar/notification/NotificationCustomViewWrapper.java
@@ -21,6 +21,7 @@
 import android.content.Context;
 import android.graphics.ColorMatrixColorFilter;
 import android.graphics.Paint;
+import android.os.Build;
 import android.view.View;
 
 import com.android.systemui.R;
@@ -37,6 +38,7 @@
     private final Paint mGreyPaint = new Paint();
     private boolean mIsLegacy;
     private int mLegacyColor;
+    private boolean mBeforeP;
 
     protected NotificationCustomViewWrapper(Context ctx, View view, ExpandableNotificationRow row) {
         super(ctx, view, row);
@@ -115,4 +117,17 @@
         super.setLegacy(legacy);
         mIsLegacy = legacy;
     }
+
+    @Override
+    public boolean shouldClipToSidePaddings() {
+        // Before P we ensure that they are now drawing inside out content bounds since we inset
+        // the view. If they target P, then we don't have that guarantee and we need to be safe.
+        return !mBeforeP;
+    }
+
+    @Override
+    public void onContentUpdated(ExpandableNotificationRow row) {
+        super.onContentUpdated(row);
+        mBeforeP = row.getEntry().targetSdk < Build.VERSION_CODES.P;
+    }
 }
diff --git a/com/android/systemui/statusbar/notification/NotificationMediaTemplateViewWrapper.java b/com/android/systemui/statusbar/notification/NotificationMediaTemplateViewWrapper.java
index eb211a1..060e6d6 100644
--- a/com/android/systemui/statusbar/notification/NotificationMediaTemplateViewWrapper.java
+++ b/com/android/systemui/statusbar/notification/NotificationMediaTemplateViewWrapper.java
@@ -58,6 +58,11 @@
 
     @Override
     public boolean isDimmable() {
-        return false;
+        return getCustomBackgroundColor() == 0;
+    }
+
+    @Override
+    public boolean shouldClipToSidePaddings() {
+        return true;
     }
 }
diff --git a/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java b/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
index fd085d9..e07112f 100644
--- a/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
+++ b/com/android/systemui/statusbar/notification/NotificationTemplateViewWrapper.java
@@ -265,6 +265,11 @@
         updateActionOffset();
     }
 
+    @Override
+    public boolean shouldClipToSidePaddings() {
+        return mActionsContainer != null && mActionsContainer.getVisibility() != View.GONE;
+    }
+
     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 1cd5f15..8a767bb 100644
--- a/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
+++ b/com/android/systemui/statusbar/notification/NotificationViewWrapper.java
@@ -194,4 +194,8 @@
     public int getMinLayoutHeight() {
         return 0;
     }
+
+    public boolean shouldClipToSidePaddings() {
+        return false;
+    }
 }
diff --git a/com/android/systemui/statusbar/notification/PropertyAnimator.java b/com/android/systemui/statusbar/notification/PropertyAnimator.java
index 80ba943..92dcc9e 100644
--- a/com/android/systemui/statusbar/notification/PropertyAnimator.java
+++ b/com/android/systemui/statusbar/notification/PropertyAnimator.java
@@ -34,6 +34,19 @@
  */
 public class PropertyAnimator {
 
+    public static <T extends View> void setProperty(final T view,
+            AnimatableProperty animatableProperty, float newEndValue,
+            AnimationProperties properties, boolean animated) {
+        int animatorTag = animatableProperty.getAnimatorTag();
+        ValueAnimator previousAnimator = ViewState.getChildTag(view, animatorTag);
+        if (previousAnimator != null || animated) {
+            startAnimation(view, animatableProperty, newEndValue, properties);
+        } else {
+            // no new animation needed, let's just apply the value
+            animatableProperty.getProperty().set(view, newEndValue);
+        }
+    }
+
     public static <T extends View> void startAnimation(final T view,
             AnimatableProperty animatableProperty, float newEndValue,
             AnimationProperties properties) {
@@ -102,10 +115,4 @@
         view.setTag(animationEndTag, newEndValue);
     }
 
-    public interface AnimatableProperty {
-        int getAnimationStartTag();
-        int getAnimationEndTag();
-        int getAnimatorTag();
-        Property getProperty();
-    }
 }
diff --git a/com/android/systemui/statusbar/notification/TransformState.java b/com/android/systemui/statusbar/notification/TransformState.java
index ad07af0..dec5303 100644
--- a/com/android/systemui/statusbar/notification/TransformState.java
+++ b/com/android/systemui/statusbar/notification/TransformState.java
@@ -95,18 +95,22 @@
     public void transformViewFrom(TransformState otherState, float transformationAmount) {
         mTransformedView.animate().cancel();
         if (sameAs(otherState)) {
-            if (mTransformedView.getVisibility() == View.INVISIBLE
-                    || mTransformedView.getAlpha() != 1.0f) {
-                // We have the same content, lets show ourselves
-                mTransformedView.setAlpha(1.0f);
-                mTransformedView.setVisibility(View.VISIBLE);
-            }
+            ensureVisible();
         } else {
             CrossFadeHelper.fadeIn(mTransformedView, transformationAmount);
         }
         transformViewFullyFrom(otherState, transformationAmount);
     }
 
+    protected void ensureVisible() {
+        if (mTransformedView.getVisibility() == View.INVISIBLE
+                || mTransformedView.getAlpha() != 1.0f) {
+            // We have the same content, lets show ourselves
+            mTransformedView.setAlpha(1.0f);
+            mTransformedView.setVisibility(View.VISIBLE);
+        }
+    }
+
     public void transformViewFullyFrom(TransformState otherState, float transformationAmount) {
         transformViewFrom(otherState, TRANSFORM_ALL, null, transformationAmount);
     }
diff --git a/com/android/systemui/statusbar/phone/DozeParameters.java b/com/android/systemui/statusbar/phone/DozeParameters.java
index 6b7397b..3f57c2f 100644
--- a/com/android/systemui/statusbar/phone/DozeParameters.java
+++ b/com/android/systemui/statusbar/phone/DozeParameters.java
@@ -46,10 +46,8 @@
     public void dump(PrintWriter pw) {
         pw.println("  DozeParameters:");
         pw.print("    getDisplayStateSupported(): "); pw.println(getDisplayStateSupported());
-        pw.print("    getPulseDuration(pickup=false): "); pw.println(getPulseDuration(false));
-        pw.print("    getPulseDuration(pickup=true): "); pw.println(getPulseDuration(true));
-        pw.print("    getPulseInDuration(pickup=false): "); pw.println(getPulseInDuration(false));
-        pw.print("    getPulseInDuration(pickup=true): "); pw.println(getPulseInDuration(true));
+        pw.print("    getPulseDuration(): "); pw.println(getPulseDuration());
+        pw.print("    getPulseInDuration(): "); pw.println(getPulseInDuration());
         pw.print("    getPulseInVisibleDuration(): "); pw.println(getPulseVisibleDuration());
         pw.print("    getPulseOutDuration(): "); pw.println(getPulseOutDuration());
         pw.print("    getPulseOnSigMotion(): "); pw.println(getPulseOnSigMotion());
@@ -81,14 +79,12 @@
         return mContext.getResources().getBoolean(R.bool.doze_suspend_display_state_supported);
     }
 
-    public int getPulseDuration(boolean pickup) {
-        return getPulseInDuration(pickup) + getPulseVisibleDuration() + getPulseOutDuration();
+    public int getPulseDuration() {
+        return getPulseInDuration() + getPulseVisibleDuration() + getPulseOutDuration();
     }
 
-    public int getPulseInDuration(boolean pickupOrDoubleTap) {
-        return pickupOrDoubleTap
-                ? getInt("doze.pulse.duration.in.pickup", R.integer.doze_pulse_duration_in_pickup)
-                : getInt("doze.pulse.duration.in", R.integer.doze_pulse_duration_in);
+    public int getPulseInDuration() {
+        return getInt("doze.pulse.duration.in", R.integer.doze_pulse_duration_in);
     }
 
     public int getPulseVisibleDuration() {
diff --git a/com/android/systemui/statusbar/phone/DozeScrimController.java b/com/android/systemui/statusbar/phone/DozeScrimController.java
index 8afb849..1011383 100644
--- a/com/android/systemui/statusbar/phone/DozeScrimController.java
+++ b/com/android/systemui/statusbar/phone/DozeScrimController.java
@@ -16,16 +16,11 @@
 
 package com.android.systemui.statusbar.phone;
 
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.ValueAnimator;
 import android.annotation.NonNull;
 import android.content.Context;
 import android.os.Handler;
 import android.util.Log;
-import android.view.animation.Interpolator;
 
-import com.android.systemui.Interpolators;
 import com.android.systemui.doze.DozeHost;
 import com.android.systemui.doze.DozeLog;
 
@@ -40,74 +35,59 @@
     private final Handler mHandler = new Handler();
     private final ScrimController mScrimController;
 
-    private final Context mContext;
-
     private boolean mDozing;
     private DozeHost.PulseCallback mPulseCallback;
     private int mPulseReason;
-    private Animator mInFrontAnimator;
-    private Animator mBehindAnimator;
-    private float mInFrontTarget;
-    private float mBehindTarget;
-    private boolean mDozingAborted;
-    private boolean mWakeAndUnlocking;
     private boolean mFullyPulsing;
 
-    private float mAodFrontScrimOpacity = 0;
-    private Runnable mSetDozeInFrontAlphaDelayed;
+    private final ScrimController.Callback mScrimCallback = new ScrimController.Callback() {
+        @Override
+        public void onDisplayBlanked() {
+            if (DEBUG) {
+                Log.d(TAG, "Pulse in, mDozing=" + mDozing + " mPulseReason="
+                        + DozeLog.pulseReasonToString(mPulseReason));
+            }
+            if (!mDozing) {
+                return;
+            }
+
+            // Signal that the pulse is ready to turn the screen on and draw.
+            pulseStarted();
+        }
+
+        @Override
+        public void onFinished() {
+            if (DEBUG) {
+                Log.d(TAG, "Pulse in finished, mDozing=" + mDozing);
+            }
+            if (!mDozing) {
+                return;
+            }
+            mHandler.postDelayed(mPulseOut, mDozeParameters.getPulseVisibleDuration());
+            mHandler.postDelayed(mPulseOutExtended,
+                    mDozeParameters.getPulseVisibleDurationExtended());
+            mFullyPulsing = true;
+        }
+
+        /**
+         * Transition was aborted before it was over.
+         */
+        @Override
+        public void onCancelled() {
+            pulseFinished();
+        }
+    };
 
     public DozeScrimController(ScrimController scrimController, Context context) {
-        mContext = context;
         mScrimController = scrimController;
         mDozeParameters = new DozeParameters(context);
     }
 
-    public void setDozing(boolean dozing, boolean animate) {
+    public void setDozing(boolean dozing) {
         if (mDozing == dozing) return;
         mDozing = dozing;
-        mWakeAndUnlocking = false;
-        if (mDozing) {
-            mDozingAborted = false;
-            abortAnimations();
-            mScrimController.setDozeBehindAlpha(1f);
-            setDozeInFrontAlpha(mDozeParameters.getAlwaysOn() ? mAodFrontScrimOpacity : 1f);
-        } else {
+        if (!mDozing) {
             cancelPulsing();
-            if (animate) {
-                startScrimAnimation(false /* inFront */, 0f /* target */,
-                        NotificationPanelView.DOZE_ANIMATION_DURATION,
-                        Interpolators.LINEAR_OUT_SLOW_IN);
-                startScrimAnimation(true /* inFront */, 0f /* target */,
-                        NotificationPanelView.DOZE_ANIMATION_DURATION,
-                        Interpolators.LINEAR_OUT_SLOW_IN);
-            } else {
-                abortAnimations();
-                mScrimController.setDozeBehindAlpha(0f);
-                setDozeInFrontAlpha(0f);
-            }
-        }
-    }
-
-    /**
-     * Set the opacity of the front scrim when showing AOD1
-     *
-     * Used to emulate lower brightness values than the hardware supports natively.
-     */
-    public void setAodDimmingScrim(float scrimOpacity) {
-        mAodFrontScrimOpacity = scrimOpacity;
-        if (mDozing && !isPulsing() && !mDozingAborted && !mWakeAndUnlocking
-                && mDozeParameters.getAlwaysOn()) {
-            setDozeInFrontAlpha(mAodFrontScrimOpacity);
-        }
-    }
-
-    public void setWakeAndUnlocking() {
-        // Immediately abort the doze scrims in case of wake-and-unlock
-        // for pulsing so the Keyguard fade-out animation scrim can take over.
-        if (!mWakeAndUnlocking) {
-            mWakeAndUnlocking = true;
-            mScrimController.setDozeBehindAlpha(0f);
-            setDozeInFrontAlpha(0f);
         }
     }
 
@@ -118,37 +98,21 @@
         }
 
         if (!mDozing || mPulseCallback != null) {
+            if (DEBUG) {
+                Log.d(TAG, "Pulse supressed. Dozing: " + mDozeParameters + " had callback? "
+                        + (mPulseCallback != null));
+            }
             // Pulse suppressed.
             callback.onPulseFinished();
             return;
         }
 
-        // Begin pulse.  Note that it's very important that the pulse finished callback
+        // Begin pulse. Note that it's very important that the pulse finished callback
         // be invoked when we're done so that the caller can drop the pulse wakelock.
         mPulseCallback = callback;
         mPulseReason = reason;
-        setDozeInFrontAlpha(1f);
-        mHandler.post(mPulseIn);
-    }
 
-    /**
-     * Aborts pulsing immediately.
-     */
-    public void abortPulsing() {
-        cancelPulsing();
-        if (mDozing && !mWakeAndUnlocking) {
-            mScrimController.setDozeBehindAlpha(1f);
-            setDozeInFrontAlpha(mDozeParameters.getAlwaysOn() && !mDozingAborted
-                    ? mAodFrontScrimOpacity : 1f);
-        }
-    }
-
-    /**
-     * Aborts dozing immediately.
-     */
-    public void abortDoze() {
-        mDozingAborted = true;
-        abortPulsing();
+        mScrimController.transitionTo(ScrimState.PULSING, mScrimCallback);
     }
 
     public void pulseOutNow() {
@@ -157,17 +121,6 @@
         }
     }
 
-    public void onScreenTurnedOn() {
-        if (isPulsing()) {
-            final boolean pickupOrDoubleTap = mPulseReason == DozeLog.PULSE_REASON_SENSOR_PICKUP
-                    || mPulseReason == DozeLog.PULSE_REASON_SENSOR_DOUBLE_TAP;
-            startScrimAnimation(true /* inFront */, 0f,
-                    mDozeParameters.getPulseInDuration(pickupOrDoubleTap),
-                    pickupOrDoubleTap ? Interpolators.LINEAR_OUT_SLOW_IN : Interpolators.ALPHA_OUT,
-                    mPulseInFinished);
-        }
-    }
-
     public boolean isPulsing() {
         return mPulseCallback != null;
     }
@@ -181,11 +134,9 @@
     }
 
     private void cancelPulsing() {
-        if (DEBUG) Log.d(TAG, "Cancel pulsing");
-
         if (mPulseCallback != null) {
+            if (DEBUG) Log.d(TAG, "Cancel pulsing");
             mFullyPulsing = false;
-            mHandler.removeCallbacks(mPulseIn);
             mHandler.removeCallbacks(mPulseOut);
             mHandler.removeCallbacks(mPulseOutExtended);
             pulseFinished();
@@ -193,151 +144,20 @@
     }
 
     private void pulseStarted() {
+        DozeLog.tracePulseStart(mPulseReason);
         if (mPulseCallback != null) {
             mPulseCallback.onPulseStarted();
         }
     }
 
     private void pulseFinished() {
+        DozeLog.tracePulseFinish();
         if (mPulseCallback != null) {
             mPulseCallback.onPulseFinished();
             mPulseCallback = null;
         }
     }
 
-    private void abortAnimations() {
-        if (mInFrontAnimator != null) {
-            mInFrontAnimator.cancel();
-        }
-        if (mBehindAnimator != null) {
-            mBehindAnimator.cancel();
-        }
-    }
-
-    private void startScrimAnimation(final boolean inFront, float target, long duration,
-            Interpolator interpolator) {
-        startScrimAnimation(inFront, target, duration, interpolator, null /* endRunnable */);
-    }
-
-    private void startScrimAnimation(final boolean inFront, float target, long duration,
-            Interpolator interpolator, final Runnable endRunnable) {
-        Animator current = getCurrentAnimator(inFront);
-        if (current != null) {
-            float currentTarget = getCurrentTarget(inFront);
-            if (currentTarget == target) {
-                return;
-            }
-            current.cancel();
-        }
-        ValueAnimator anim = ValueAnimator.ofFloat(getDozeAlpha(inFront), target);
-        anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-            @Override
-            public void onAnimationUpdate(ValueAnimator animation) {
-                float value = (float) animation.getAnimatedValue();
-                setDozeAlpha(inFront, value);
-            }
-        });
-        anim.setInterpolator(interpolator);
-        anim.setDuration(duration);
-        anim.addListener(new AnimatorListenerAdapter() {
-            @Override
-            public void onAnimationEnd(Animator animation) {
-                setCurrentAnimator(inFront, null);
-                if (endRunnable != null) {
-                    endRunnable.run();
-                }
-            }
-        });
-        anim.start();
-        setCurrentAnimator(inFront, anim);
-        setCurrentTarget(inFront, target);
-    }
-
-    private float getCurrentTarget(boolean inFront) {
-        return inFront ? mInFrontTarget : mBehindTarget;
-    }
-
-    private void setCurrentTarget(boolean inFront, float target) {
-        if (inFront) {
-            mInFrontTarget = target;
-        } else {
-            mBehindTarget = target;
-        }
-    }
-
-    private Animator getCurrentAnimator(boolean inFront) {
-        return inFront ? mInFrontAnimator : mBehindAnimator;
-    }
-
-    private void setCurrentAnimator(boolean inFront, Animator animator) {
-        if (inFront) {
-            mInFrontAnimator = animator;
-        } else {
-            mBehindAnimator = animator;
-        }
-    }
-
-    private void setDozeAlpha(boolean inFront, float alpha) {
-        if (mWakeAndUnlocking) {
-            return;
-        }
-        if (inFront) {
-            mScrimController.setDozeInFrontAlpha(alpha);
-        } else {
-            mScrimController.setDozeBehindAlpha(alpha);
-        }
-    }
-
-    private float getDozeAlpha(boolean inFront) {
-        return inFront
-                ? mScrimController.getDozeInFrontAlpha()
-                : mScrimController.getDozeBehindAlpha();
-    }
-
-    private void setDozeInFrontAlpha(float opacity) {
-        setDozeInFrontAlphaDelayed(opacity, 0 /* delay */);
-
-    }
-
-    private void setDozeInFrontAlphaDelayed(float opacity, long delayMs) {
-        if (mSetDozeInFrontAlphaDelayed != null) {
-            mHandler.removeCallbacks(mSetDozeInFrontAlphaDelayed);
-            mSetDozeInFrontAlphaDelayed = null;
-        }
-        if (delayMs <= 0) {
-            mScrimController.setDozeInFrontAlpha(opacity);
-        } else {
-            mHandler.postDelayed(mSetDozeInFrontAlphaDelayed = () -> {
-                setDozeInFrontAlpha(opacity);
-            }, delayMs);
-        }
-    }
-
-    private final Runnable mPulseIn = new Runnable() {
-        @Override
-        public void run() {
-            if (DEBUG) Log.d(TAG, "Pulse in, mDozing=" + mDozing + " mPulseReason="
-                    + DozeLog.pulseReasonToString(mPulseReason));
-            if (!mDozing) return;
-            DozeLog.tracePulseStart(mPulseReason);
-
-            // Signal that the pulse is ready to turn the screen on and draw.
-            pulseStarted();
-        }
-    };
-
-    private final Runnable mPulseInFinished = new Runnable() {
-        @Override
-        public void run() {
-            if (DEBUG) Log.d(TAG, "Pulse in finished, mDozing=" + mDozing);
-            if (!mDozing) return;
-            mHandler.postDelayed(mPulseOut, mDozeParameters.getPulseVisibleDuration());
-            mHandler.postDelayed(mPulseOutExtended,
-                    mDozeParameters.getPulseVisibleDurationExtended());
-            mFullyPulsing = true;
-        }
-    };
-
     private final Runnable mPulseOutExtended = new Runnable() {
         @Override
         public void run() {
@@ -354,38 +174,13 @@
             mHandler.removeCallbacks(mPulseOutExtended);
             if (DEBUG) Log.d(TAG, "Pulse out, mDozing=" + mDozing);
             if (!mDozing) return;
-            startScrimAnimation(true /* inFront */, 1,
-                    mDozeParameters.getPulseOutDuration(),
-                    Interpolators.ALPHA_IN, mPulseOutFinishing);
+            mScrimController.transitionTo(ScrimState.AOD,
+                    new ScrimController.Callback() {
+                        @Override
+                        public void onDisplayBlanked() {
+                            pulseFinished();
+                        }
+                    });
         }
     };
-
-    private final Runnable mPulseOutFinishing = new Runnable() {
-        @Override
-        public void run() {
-            if (DEBUG) Log.d(TAG, "Pulse out finished");
-            DozeLog.tracePulseFinish();
-            if (mDozeParameters.getAlwaysOn() && mDozing) {
-                // Setting power states can block rendering. For AOD, delay finishing the pulse and
-                // setting the power state until the fully black scrim had time to hit the
-                // framebuffer.
-                mHandler.postDelayed(mPulseOutFinished, 30);
-            } else {
-                mPulseOutFinished.run();
-            }
-        }
-    };
-
-    private final Runnable mPulseOutFinished = new Runnable() {
-        @Override
-        public void run() {
-            // Signal that the pulse is all finished so we can turn the screen off now.
-            DozeScrimController.this.pulseFinished();
-            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, 100);
-            }
-        }
-    };
-}
+}
\ No newline at end of file
diff --git a/com/android/systemui/statusbar/phone/FingerprintUnlockController.java b/com/android/systemui/statusbar/phone/FingerprintUnlockController.java
index 91369db..80d4061 100644
--- a/com/android/systemui/statusbar/phone/FingerprintUnlockController.java
+++ b/com/android/systemui/statusbar/phone/FingerprintUnlockController.java
@@ -181,9 +181,9 @@
     }
 
     private boolean pulsingOrAod() {
-        boolean pulsing = mDozeScrimController.isPulsing();
-        boolean dozingWithScreenOn = mStatusBar.isDozing() && !mStatusBar.isScreenFullyOff();
-        return pulsing || dozingWithScreenOn;
+        final ScrimState scrimState = mScrimController.getState();
+        return scrimState == ScrimState.AOD
+                || scrimState == ScrimState.PULSING;
     }
 
     @Override
@@ -246,15 +246,12 @@
                             true /* allowEnterAnimation */);
                 } else if (mMode == MODE_WAKE_AND_UNLOCK){
                     Trace.beginSection("MODE_WAKE_AND_UNLOCK");
-                    mDozeScrimController.abortDoze();
                 } else {
                     Trace.beginSection("MODE_WAKE_AND_UNLOCK_FROM_DREAM");
                     mUpdateMonitor.awakenFromDream();
                 }
                 mStatusBarWindowManager.setStatusBarFocusable(false);
                 mKeyguardViewMediator.onWakeAndUnlocking();
-                mScrimController.setWakeAndUnlocking();
-                mDozeScrimController.setWakeAndUnlocking();
                 if (mStatusBar.getNavigationBarView() != null) {
                     mStatusBar.getNavigationBarView().setWakeAndUnlocking(true);
                 }
@@ -269,6 +266,7 @@
     }
 
     private void showBouncer() {
+        mScrimController.transitionTo(ScrimState.BOUNCER);
         mStatusBarKeyguardViewManager.animateCollapsePanels(
                 FINGERPRINT_COLLAPSE_SPEEDUP_FACTOR);
         mPendingShowBouncer = false;
diff --git a/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java b/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
index a6691b1..da809c1 100644
--- a/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
+++ b/com/android/systemui/statusbar/phone/KeyguardStatusBarView.java
@@ -333,7 +333,7 @@
         return false;
     }
 
-    public void onOverlayChanged() {
+    public void onThemeChanged() {
         @ColorInt int textColor = Utils.getColorAttr(mContext, R.attr.wallpaperTextColor);
         @ColorInt int iconColor = Utils.getDefaultColor(mContext, Color.luminance(textColor) < 0.5 ?
                 R.color.dark_mode_icon_color_single_tone :
diff --git a/com/android/systemui/statusbar/phone/LockIcon.java b/com/android/systemui/statusbar/phone/LockIcon.java
index 5c9446c..34486db 100644
--- a/com/android/systemui/statusbar/phone/LockIcon.java
+++ b/com/android/systemui/statusbar/phone/LockIcon.java
@@ -250,7 +250,7 @@
                 }
                 break;
             case STATE_FACE_UNLOCK:
-                iconRes = com.android.internal.R.drawable.ic_account_circle;
+                iconRes = R.drawable.ic_account_circle;
                 break;
             case STATE_FINGERPRINT:
                 // If screen is off and device asleep, use the draw on animation so the first frame
diff --git a/com/android/systemui/statusbar/phone/NavigationBarTransitions.java b/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
index c950036..b81a3b0 100644
--- a/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
+++ b/com/android/systemui/statusbar/phone/NavigationBarTransitions.java
@@ -80,7 +80,8 @@
 
     @Override
     protected boolean isLightsOut(int mode) {
-        return super.isLightsOut(mode) || (mAutoDim && !mWallpaperVisible);
+        return super.isLightsOut(mode) || (mAutoDim && !mWallpaperVisible
+                && mode != MODE_WARNING);
     }
 
     public LightBarTransitionsController getLightTransitionsController() {
@@ -108,7 +109,9 @@
         // ok, everyone, stop it right there
         navButtons.animate().cancel();
 
-        final float navButtonsAlpha = lightsOut ? 0.6f : 1f;
+        // Bump percentage by 10% if dark.
+        float darkBump = mLightTransitionsController.getCurrentDarkIntensity() / 10;
+        final float navButtonsAlpha = lightsOut ? 0.6f + darkBump : 1f;
 
         if (!animate) {
             navButtons.setAlpha(navButtonsAlpha);
@@ -130,6 +133,9 @@
         for (int i = buttonDispatchers.size() - 1; i >= 0; i--) {
             buttonDispatchers.valueAt(i).setDarkIntensity(darkIntensity);
         }
+        if (mAutoDim) {
+            applyLightsOut(false, true);
+        }
     }
 
     private final View.OnTouchListener mLightsOutListener = new View.OnTouchListener() {
diff --git a/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index 0f246c6..836efff 100644
--- a/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -209,7 +209,7 @@
                 mAddAnimationStartIndex = Math.min(mAddAnimationStartIndex, childIndex);
             }
         }
-        if (mDark && child instanceof StatusBarIconView) {
+        if (child instanceof StatusBarIconView) {
             ((StatusBarIconView) child).setDark(mDark, false, 0);
         }
     }
diff --git a/com/android/systemui/statusbar/phone/NotificationPanelView.java b/com/android/systemui/statusbar/phone/NotificationPanelView.java
index 86a8f41..17e3599 100644
--- a/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -239,6 +239,7 @@
     private ValueAnimator mDarkAnimator;
     private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     private boolean mUserSetupComplete;
+    private int mQsNotificationTopPadding;
 
     public NotificationPanelView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -307,6 +308,8 @@
                 R.dimen.max_notification_fadeout_height);
         mIndicationBottomPadding = getResources().getDimensionPixelSize(
                 R.dimen.keyguard_indication_bottom_padding);
+        mQsNotificationTopPadding = getResources().getDimensionPixelSize(
+                R.dimen.qs_notification_keyguard_padding);
     }
 
     public void updateResources() {
@@ -330,7 +333,7 @@
         }
     }
 
-    public void onOverlayChanged() {
+    public void onThemeChanged() {
         // Re-inflate the status view group.
         int index = indexOfChild(mKeyguardStatusView);
         removeView(mKeyguardStatusView);
@@ -818,7 +821,7 @@
 
     private float getQsExpansionFraction() {
         return Math.min(1f, (mQsExpansionHeight - mQsMinExpansionHeight)
-                / (getTempQsMaxExpansion() - mQsMinExpansionHeight));
+                / (mQsMaxExpansionHeight - mQsMinExpansionHeight));
     }
 
     @Override
@@ -1361,7 +1364,7 @@
             // take the maximum and linearly interpolate with the panel expansion for a nice motion.
             int maxNotifications = mClockPositionResult.stackScrollerPadding
                     - mClockPositionResult.stackScrollerPaddingAdjustment;
-            int maxQs = getTempQsMaxExpansion();
+            int maxQs = mQsMaxExpansionHeight + mQsNotificationTopPadding;
             int max = mStatusBarState == StatusBarState.KEYGUARD
                     ? Math.max(maxNotifications, maxQs)
                     : maxQs;
@@ -1375,7 +1378,7 @@
             // from a scrolled quick settings.
             return interpolate(getQsExpansionFraction(),
                     mNotificationStackScroller.getIntrinsicPadding(),
-                    mQsMaxExpansionHeight);
+                    mQsMaxExpansionHeight + mQsNotificationTopPadding);
         } else {
             return mQsExpansionHeight;
         }
@@ -1544,7 +1547,7 @@
                         / (panelHeightQsExpanded - panelHeightQsCollapsed);
             }
             setQsExpansion(mQsMinExpansionHeight
-                    + t * (getTempQsMaxExpansion() - mQsMinExpansionHeight));
+                    + t * (mQsMaxExpansionHeight - mQsMinExpansionHeight));
         }
         updateExpandedHeight(expandedHeight);
         updateHeader();
@@ -1566,14 +1569,6 @@
         }
     }
 
-    /**
-     * @return a temporary override of {@link #mQsMaxExpansionHeight}, which is needed when
-     *         collapsing QS / the panel when QS was scrolled
-     */
-    private int getTempQsMaxExpansion() {
-        return mQsMaxExpansionHeight;
-    }
-
     private int calculatePanelHeightShade() {
         int emptyBottomMargin = mNotificationStackScroller.getEmptyBottomMargin();
         int maxHeight = mNotificationStackScroller.getHeight() - emptyBottomMargin
@@ -1596,6 +1591,10 @@
         }
         int maxQsHeight = mQsMaxExpansionHeight;
 
+        if (mKeyguardShowing) {
+            maxQsHeight += mQsNotificationTopPadding;
+        }
+
         // If an animation is changing the size of the QS panel, take the animated value.
         if (mQsSizeChangeAnimator != null) {
             maxQsHeight = (int) mQsSizeChangeAnimator.getAnimatedValue();
diff --git a/com/android/systemui/statusbar/phone/ScrimController.java b/com/android/systemui/statusbar/phone/ScrimController.java
index 702afa3..3a36776 100644
--- a/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/com/android/systemui/statusbar/phone/ScrimController.java
@@ -25,7 +25,9 @@
 import android.graphics.Color;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
+import android.os.Handler;
 import android.os.Trace;
+import android.util.Log;
 import android.util.MathUtils;
 import android.view.View;
 import android.view.ViewGroup;
@@ -34,12 +36,14 @@
 import android.view.animation.Interpolator;
 import android.view.animation.PathInterpolator;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.colorextraction.ColorExtractor;
 import com.android.internal.colorextraction.ColorExtractor.GradientColors;
 import com.android.internal.colorextraction.ColorExtractor.OnColorsChangedListener;
 import com.android.internal.graphics.ColorUtils;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.Dependency;
+import com.android.systemui.Dumpable;
 import com.android.systemui.R;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.statusbar.ExpandableNotificationRow;
@@ -47,7 +51,10 @@
 import com.android.systemui.statusbar.ScrimView;
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
 import com.android.systemui.statusbar.stack.ViewState;
+import com.android.systemui.util.wakelock.DelayedWakeLock;
+import com.android.systemui.util.wakelock.WakeLock;
 
+import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.function.Consumer;
 
@@ -56,33 +63,54 @@
  * security method gets shown).
  */
 public class ScrimController implements ViewTreeObserver.OnPreDrawListener,
-        OnHeadsUpChangedListener, OnColorsChangedListener {
+        OnHeadsUpChangedListener, OnColorsChangedListener, Dumpable {
+
+    private static final String TAG = "ScrimController";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
     public static final long ANIMATION_DURATION = 220;
     public static final Interpolator KEYGUARD_FADE_OUT_INTERPOLATOR
             = new PathInterpolator(0f, 0, 0.7f, 1f);
     public static final Interpolator KEYGUARD_FADE_OUT_INTERPOLATOR_LOCKED
             = new PathInterpolator(0.3f, 0f, 0.8f, 1f);
-    // Default alpha value for most scrims, if unsure use this constant
+    /**
+     * Default alpha value for most scrims.
+     */
     public static final float GRADIENT_SCRIM_ALPHA = 0.45f;
-    // A scrim varies its opacity based on a busyness factor, for example
-    // how many notifications are currently visible.
+    /**
+     * A scrim varies its opacity based on a busyness factor, for example
+     * how many notifications are currently visible.
+     */
     public static final float GRADIENT_SCRIM_ALPHA_BUSY = 0.70f;
+    /**
+     * The most common scrim, the one under the keyguard.
+     */
     protected static final float SCRIM_BEHIND_ALPHA_KEYGUARD = GRADIENT_SCRIM_ALPHA;
+    /**
+     * We fade out the bottom scrim when the bouncer is visible.
+     */
     protected static final float SCRIM_BEHIND_ALPHA_UNLOCKING = 0.2f;
-    private static final float SCRIM_IN_FRONT_ALPHA = GRADIENT_SCRIM_ALPHA_BUSY;
-    private static final float SCRIM_IN_FRONT_ALPHA_LOCKED = GRADIENT_SCRIM_ALPHA_BUSY;
-    private static final int TAG_KEY_ANIM = R.id.scrim;
+    /**
+     * Opacity of the scrim behind the bouncer (the one doing actual background protection.)
+     */
+    protected static final float SCRIM_IN_FRONT_ALPHA_LOCKED = GRADIENT_SCRIM_ALPHA_BUSY;
+
+    static final int TAG_KEY_ANIM = R.id.scrim;
+    static final int TAG_KEY_ANIM_BLANK = R.id.scrim_blanking;
     private static final int TAG_KEY_ANIM_TARGET = R.id.scrim_target;
     private static final int TAG_START_ALPHA = R.id.scrim_alpha_start;
     private static final int TAG_END_ALPHA = R.id.scrim_alpha_end;
     private static final float NOT_INITIALIZED = -1;
 
-    private final LightBarController mLightBarController;
+    private ScrimState mState = ScrimState.UNINITIALIZED;
+    private final Context mContext;
     protected final ScrimView mScrimBehind;
     protected final ScrimView mScrimInFront;
-    private final UnlockMethodCache mUnlockMethodCache;
     private final View mHeadsUpScrim;
+    private final LightBarController mLightBarController;
+    private final UnlockMethodCache mUnlockMethodCache;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    private final DozeParameters mDozeParameters;
 
     private final SysuiColorExtractor mColorExtractor;
     private GradientColors mLockColors;
@@ -94,61 +122,53 @@
     protected float mScrimBehindAlphaKeyguard = SCRIM_BEHIND_ALPHA_KEYGUARD;
     protected float mScrimBehindAlphaUnlocking = SCRIM_BEHIND_ALPHA_UNLOCKING;
 
-    protected boolean mKeyguardShowing;
     private float mFraction;
 
     private boolean mDarkenWhileDragging;
-    protected boolean mBouncerShowing;
-    protected boolean mBouncerIsKeyguard = false;
-    private boolean mWakeAndUnlocking;
     protected boolean mAnimateChange;
     private boolean mUpdatePending;
     private boolean mTracking;
     private boolean mAnimateKeyguardFadingOut;
-    protected long mDurationOverride = -1;
+    protected long mAnimationDuration = -1;
     private long mAnimationDelay;
     private Runnable mOnAnimationFinished;
     private boolean mDeferFinishedListener;
     private final Interpolator mInterpolator = new DecelerateInterpolator();
-    private boolean mDozing;
-    private float mDozeInFrontAlpha;
-    private float mDozeBehindAlpha;
     private float mCurrentInFrontAlpha  = NOT_INITIALIZED;
     private float mCurrentBehindAlpha = NOT_INITIALIZED;
-    private float mCurrentHeadsUpAlpha = NOT_INITIALIZED;
+    private int mCurrentInFrontTint;
+    private int mCurrentBehindTint;
     private int mPinnedHeadsUpCount;
     private float mTopHeadsUpDragAmount;
     private View mDraggedHeadsUpView;
-    private boolean mForceHideScrims;
-    private boolean mSkipFirstFrame;
-    private boolean mDontAnimateBouncerChanges;
     private boolean mKeyguardFadingOutInProgress;
-    private boolean mAnimatingDozeUnlock;
     private ValueAnimator mKeyguardFadeoutAnimation;
-    /** Wake up from AOD transition is starting; need fully opaque front scrim */
-    private boolean mWakingUpFromAodStarting;
-    /** Wake up from AOD transition is in progress; need black tint */
-    private boolean mWakingUpFromAodInProgress;
-    /** Wake up from AOD transition is animating; need to reset when animation finishes */
-    private boolean mWakingUpFromAodAnimationRunning;
-    private boolean mScrimsVisble;
+    private boolean mScrimsVisible;
     private final Consumer<Boolean> mScrimVisibleListener;
+    private boolean mBlankScreen;
+    private boolean mScreenBlankingCallbackCalled;
+    private Callback mCallback;
+
+    private final WakeLock mWakeLock;
+    private boolean mWakeLockHeld;
 
     public ScrimController(LightBarController lightBarController, ScrimView scrimBehind,
-            ScrimView scrimInFront, View headsUpScrim,
-            Consumer<Boolean> scrimVisibleListener) {
+            ScrimView scrimInFront, View headsUpScrim, Consumer<Boolean> scrimVisibleListener,
+            DozeParameters dozeParameters) {
         mScrimBehind = scrimBehind;
         mScrimInFront = scrimInFront;
         mHeadsUpScrim = headsUpScrim;
         mScrimVisibleListener = scrimVisibleListener;
-        final Context context = scrimBehind.getContext();
-        mUnlockMethodCache = UnlockMethodCache.getInstance(context);
-        mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(context);
+        mContext = scrimBehind.getContext();
+        mUnlockMethodCache = UnlockMethodCache.getInstance(mContext);
+        mKeyguardUpdateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
         mLightBarController = lightBarController;
-        mScrimBehindAlphaResValue = context.getResources().getFloat(R.dimen.scrim_behind_alpha);
+        mScrimBehindAlphaResValue = mContext.getResources().getFloat(R.dimen.scrim_behind_alpha);
+        mWakeLock = createWakeLock();
         // Scrim alpha is initially set to the value on the resource but might be changed
         // to make sure that text on top of it is legible.
         mScrimBehindAlpha = mScrimBehindAlphaResValue;
+        mDozeParameters = dozeParameters;
 
         mColorExtractor = Dependency.get(SysuiColorExtractor.class);
         mColorExtractor.addOnColorsChangedListener(this);
@@ -158,22 +178,90 @@
                 ColorExtractor.TYPE_DARK, true /* ignoreVisibility */);
         mNeedsDrawableColorUpdate = true;
 
+        final ScrimState[] states = ScrimState.values();
+        for (int i = 0; i < states.length; i++) {
+            states[i].init(mScrimInFront, mScrimBehind, mDozeParameters);
+            states[i].setScrimBehindAlphaKeyguard(mScrimBehindAlphaKeyguard);
+        }
+        mState = ScrimState.UNINITIALIZED;
+
         updateHeadsUpScrim(false);
         updateScrims();
     }
 
-    public void setKeyguardShowing(boolean showing) {
-        mKeyguardShowing = showing;
+    public void transitionTo(ScrimState state) {
+        transitionTo(state, null);
+    }
 
-        // Showing/hiding the keyguard means that scrim colors have to be switched
-        mNeedsDrawableColorUpdate = true;
-        scheduleUpdate();
+    public void transitionTo(ScrimState state, Callback callback) {
+        if (state == mState) {
+            return;
+        } else if (DEBUG) {
+            Log.d(TAG, "State changed to: " + state);
+        }
+
+        if (state == ScrimState.UNINITIALIZED) {
+            throw new IllegalArgumentException("Cannot change to UNINITIALIZED.");
+        }
+
+        if (mCallback != null) {
+            mCallback.onCancelled();
+        }
+        mCallback = callback;
+
+        state.prepare(mState);
+        mScreenBlankingCallbackCalled = false;
+        mAnimationDelay = 0;
+        mBlankScreen = state.getBlanksScreen();
+        mAnimateChange = state.getAnimateChange();
+        mAnimationDuration = state.getAnimationDuration();
+        mCurrentInFrontTint = state.getFrontTint();
+        mCurrentBehindTint = state.getBehindTint();
+        mCurrentInFrontAlpha = state.getFrontAlpha();
+        mCurrentBehindAlpha = state.getBehindAlpha();
+
+        // Showing/hiding the keyguard means that scrim colors have to be switched, not necessary
+        // to do the same when you're just showing the brightness mirror.
+        mNeedsDrawableColorUpdate = state != ScrimState.BRIGHTNESS_MIRROR;
+
+        if (mKeyguardFadeoutAnimation != null) {
+            mKeyguardFadeoutAnimation.cancel();
+        }
+
+        mState = state;
+
+        // Do not let the device sleep until we're done with all animations
+        if (!mWakeLockHeld) {
+            if (mWakeLock != null) {
+                mWakeLockHeld = true;
+                mWakeLock.acquire();
+            } else {
+                Log.w(TAG, "Cannot hold wake lock, it has not been set yet");
+            }
+        }
+
+        if (!mKeyguardUpdateMonitor.needsSlowUnlockTransition()) {
+            scheduleUpdate();
+        } else {
+            // In case the user isn't unlocked, make sure to delay a bit because the system is hosed
+            // with too many things at this case, in order to not skip the initial frames.
+            mScrimInFront.postOnAnimationDelayed(this::scheduleUpdate, 16);
+            mAnimationDelay = StatusBar.FADE_KEYGUARD_START_DELAY;
+        }
+    }
+
+    public ScrimState getState() {
+        return mState;
     }
 
     protected void setScrimBehindValues(float scrimBehindAlphaKeyguard,
             float scrimBehindAlphaUnlocking) {
         mScrimBehindAlphaKeyguard = scrimBehindAlphaKeyguard;
         mScrimBehindAlphaUnlocking = scrimBehindAlphaUnlocking;
+        ScrimState[] states = ScrimState.values();
+        for (int i = 0; i < states.length; i++) {
+            states[i].setScrimBehindAlphaKeyguard(scrimBehindAlphaKeyguard);
+        }
         scheduleUpdate();
     }
 
@@ -186,131 +274,59 @@
         mTracking = false;
     }
 
+    /**
+     * Current state of the shade expansion when pulling it from the top.
+     * This value is 1 when on top of the keyguard and goes to 0 as the user drags up.
+     *
+     * The expansion fraction is tied to the scrim opacity.
+     *
+     * @param fraction From 0 to 1 where 0 means collapse and 1 expanded.
+     */
     public void setPanelExpansion(float fraction) {
         if (mFraction != fraction) {
             mFraction = fraction;
-            scheduleUpdate();
+
+            if (mState == ScrimState.UNLOCKED) {
+                // Darken scrim as you pull down the shade when unlocked
+                float behindFraction = getInterpolatedFraction();
+                behindFraction = (float) Math.pow(behindFraction, 0.8f);
+                mCurrentBehindAlpha = behindFraction * mScrimBehindAlphaKeyguard;
+                mCurrentInFrontAlpha = 0;
+            } else if (mState == ScrimState.KEYGUARD) {
+                if (mUpdatePending) {
+                    return;
+                }
+
+                // Either darken of make the scrim transparent when you
+                // pull down the shade
+                float interpolatedFract = getInterpolatedFraction();
+                if (mDarkenWhileDragging) {
+                    mCurrentBehindAlpha = MathUtils.lerp(mScrimBehindAlphaUnlocking,
+                            mScrimBehindAlphaKeyguard, interpolatedFract);
+                    mCurrentInFrontAlpha = (1f - interpolatedFract) * SCRIM_IN_FRONT_ALPHA_LOCKED;
+                } else {
+                    mCurrentBehindAlpha = MathUtils.lerp(0 /* start */, mScrimBehindAlphaKeyguard,
+                            interpolatedFract);
+                    mCurrentInFrontAlpha = 0;
+                }
+            } else {
+                Log.w(TAG, "Invalid state, cannot set panel expansion when: " + mState);
+                return;
+            }
+
             if (mPinnedHeadsUpCount != 0) {
                 updateHeadsUpScrim(false);
             }
-            if (mKeyguardFadeoutAnimation != null && mTracking) {
-                mKeyguardFadeoutAnimation.cancel();
-            }
+
+            updateScrim(false /* animate */, mScrimInFront, mCurrentInFrontAlpha);
+            updateScrim(false /* animate */, mScrimBehind, mCurrentBehindAlpha);
         }
     }
 
-    public void setBouncerShowing(boolean showing) {
-        mBouncerShowing = showing;
-        mAnimateChange = !mTracking && !mDontAnimateBouncerChanges && !mKeyguardFadingOutInProgress;
-        scheduleUpdate();
-    }
-
-    /** Prepares the wakeUpFromAod animation (while turning on screen); Forces black scrims. */
-    public void prepareWakeUpFromAod() {
-        if (mWakingUpFromAodInProgress) {
-            return;
-        }
-        mWakingUpFromAodInProgress = true;
-        mWakingUpFromAodStarting = true;
-        mAnimateChange = false;
-        scheduleUpdate();
-        onPreDraw();
-    }
-
-    /** Starts the wakeUpFromAod animation (once screen is on); animate to transparent scrims. */
-    public void wakeUpFromAod() {
-        if (mWakeAndUnlocking || mAnimateKeyguardFadingOut) {
-            // Wake and unlocking has a separate transition that must not be interfered with.
-            mWakingUpFromAodStarting = false;
-            mWakingUpFromAodInProgress = false;
-            return;
-        }
-        if (mWakingUpFromAodStarting) {
-            mWakingUpFromAodInProgress = true;
-            mWakingUpFromAodStarting = false;
-            mAnimateChange = true;
-            scheduleUpdate();
-        }
-    }
-
-    public void setWakeAndUnlocking() {
-        mWakeAndUnlocking = true;
-        mAnimatingDozeUnlock = true;
-        mWakingUpFromAodStarting = false;
-        mWakingUpFromAodInProgress = false;
-        scheduleUpdate();
-    }
-
-    public void animateKeyguardFadingOut(long delay, long duration, Runnable onAnimationFinished,
-            boolean skipFirstFrame) {
-        mWakeAndUnlocking = false;
-        mAnimateKeyguardFadingOut = true;
-        mDurationOverride = duration;
-        mAnimationDelay = delay;
-        mAnimateChange = true;
-        mSkipFirstFrame = skipFirstFrame;
-        mOnAnimationFinished = onAnimationFinished;
-
-        if (!mKeyguardUpdateMonitor.needsSlowUnlockTransition()) {
-            scheduleUpdate();
-
-            // No need to wait for the next frame to be drawn for this case - onPreDraw will execute
-            // the changes we just scheduled.
-            onPreDraw();
-        } else {
-
-            // In case the user isn't unlocked, make sure to delay a bit because the system is hosed
-            // with too many things in this case, in order to not skip the initial frames.
-            mScrimInFront.postOnAnimationDelayed(this::scheduleUpdate, 16);
-        }
-    }
-
-    public void abortKeyguardFadingOut() {
-        if (mAnimateKeyguardFadingOut) {
-            endAnimateKeyguardFadingOut(true /* force */);
-        }
-    }
-
-    public void animateKeyguardUnoccluding(long duration) {
-        mAnimateChange = false;
-        setScrimBehindAlpha(0f);
-        mAnimateChange = true;
-        scheduleUpdate();
-        mDurationOverride = duration;
-    }
-
-    public void animateGoingToFullShade(long delay, long duration) {
-        mDurationOverride = duration;
-        mAnimationDelay = delay;
-        mAnimateChange = true;
-        scheduleUpdate();
-    }
-
-    public void setDozing(boolean dozing) {
-        if (mDozing != dozing) {
-            mDozing = dozing;
-            scheduleUpdate();
-        }
-    }
-
-    public void setDozeInFrontAlpha(float alpha) {
-        mDozeInFrontAlpha = alpha;
-        updateScrimColor(mScrimInFront);
-    }
-
-    public void setDozeBehindAlpha(float alpha) {
-        mDozeBehindAlpha = alpha;
-        updateScrimColor(mScrimBehind);
-    }
-
-    public float getDozeBehindAlpha() {
-        return mDozeBehindAlpha;
-    }
-
-    public float getDozeInFrontAlpha() {
-        return mDozeInFrontAlpha;
-    }
-
+    /**
+     * Keyguard and shade scrim opacity varies according to how many notifications are visible.
+     * @param notificationCount Number of visible notifications.
+     */
     public void setNotificationCount(int notificationCount) {
         final float maxNotificationDensity = 3;
         float notificationDensity = Math.min(notificationCount / maxNotificationDensity, 1f);
@@ -319,15 +335,11 @@
                 notificationDensity);
         if (mScrimBehindAlphaKeyguard != newAlpha) {
             mScrimBehindAlphaKeyguard = newAlpha;
-            mAnimateChange = true;
-            scheduleUpdate();
-        }
-    }
 
-    private float getScrimInFrontAlpha() {
-        return mKeyguardUpdateMonitor.needsSlowUnlockTransition()
-                ? SCRIM_IN_FRONT_ALPHA_LOCKED
-                : SCRIM_IN_FRONT_ALPHA;
+            if (mState == ScrimState.KEYGUARD || mState == ScrimState.BOUNCER) {
+                scheduleUpdate();
+            }
+        }
     }
 
     /**
@@ -352,7 +364,7 @@
         if (mNeedsDrawableColorUpdate) {
             mNeedsDrawableColorUpdate = false;
             final GradientColors currentScrimColors;
-            if (mKeyguardShowing) {
+            if (mState == ScrimState.KEYGUARD || mState == ScrimState.BOUNCER) {
                 // Always animate color changes if we're seeing the keyguard
                 mScrimInFront.setColors(mLockColors, true /* animated */);
                 mScrimBehind.setColors(mLockColors, true /* animated */);
@@ -375,77 +387,31 @@
             mLightBarController.setScrimColor(mScrimInFront.getColors());
         }
 
-        if (mAnimateKeyguardFadingOut || mForceHideScrims) {
-            setScrimInFrontAlpha(0f);
-            setScrimBehindAlpha(0f);
-        } else if (mWakeAndUnlocking) {
-            // During wake and unlock, we first hide everything behind a black scrim, which then
-            // gets faded out from animateKeyguardFadingOut. This must never be animated.
-            mAnimateChange = false;
-            if (mDozing) {
-                setScrimInFrontAlpha(0f);
-                setScrimBehindAlpha(1f);
-            } else {
-                setScrimInFrontAlpha(1f);
-                setScrimBehindAlpha(0f);
-            }
-        } else if (!mKeyguardShowing && !mBouncerShowing && !mWakingUpFromAodStarting) {
-            updateScrimNormal();
-            setScrimInFrontAlpha(0);
-        } else {
-            updateScrimKeyguard();
-        }
-        mAnimateChange = false;
+        setScrimInFrontAlpha(mCurrentInFrontAlpha);
+        setScrimBehindAlpha(mCurrentBehindAlpha);
+
         dispatchScrimsVisible();
     }
 
     private void dispatchScrimsVisible() {
         boolean scrimsVisible = mScrimBehind.getViewAlpha() > 0 || mScrimInFront.getViewAlpha() > 0;
 
-        if (mScrimsVisble != scrimsVisible) {
-            mScrimsVisble = scrimsVisible;
+        if (mScrimsVisible != scrimsVisible) {
+            mScrimsVisible = scrimsVisible;
 
             mScrimVisibleListener.accept(scrimsVisible);
         }
     }
 
-    private void updateScrimKeyguard() {
-        if (mTracking && mDarkenWhileDragging) {
-            float behindFraction = Math.max(0, Math.min(mFraction, 1));
-            float fraction = 1 - behindFraction;
-            fraction = (float) Math.pow(fraction, 0.8f);
-            behindFraction = (float) Math.pow(behindFraction, 0.8f);
-            setScrimInFrontAlpha(fraction * getScrimInFrontAlpha());
-            setScrimBehindAlpha(behindFraction * mScrimBehindAlphaKeyguard);
-        } else if (mBouncerShowing && !mBouncerIsKeyguard) {
-            setScrimInFrontAlpha(getScrimInFrontAlpha());
-            updateScrimNormal();
-        } else if (mBouncerShowing) {
-            setScrimInFrontAlpha(0f);
-            setScrimBehindAlpha(mScrimBehindAlpha);
-        } else {
-            float fraction = Math.max(0, Math.min(mFraction, 1));
-            if (mWakingUpFromAodStarting) {
-                setScrimInFrontAlpha(1f);
-            } else {
-                setScrimInFrontAlpha(0f);
-            }
-            setScrimBehindAlpha(fraction
-                    * (mScrimBehindAlphaKeyguard - mScrimBehindAlphaUnlocking)
-                    + mScrimBehindAlphaUnlocking);
-        }
-    }
-
-    private void updateScrimNormal() {
+    private float getInterpolatedFraction() {
         float frac = mFraction;
         // let's start this 20% of the way down the screen
         frac = frac * 1.2f - 0.2f;
         if (frac <= 0) {
-            setScrimBehindAlpha(0);
+            return 0;
         } else {
             // woo, special effects
-            final float k = (float)(1f-0.5f*(1f-Math.cos(3.14159f * Math.pow(1f-frac, 2f))));
-            setScrimBehindAlpha(k * mScrimBehindAlpha);
+            return (float)(1f-0.5f*(1f-Math.cos(3.14159f * Math.pow(1f-frac, 2f))));
         }
     }
 
@@ -455,102 +421,76 @@
 
     private void setScrimInFrontAlpha(float alpha) {
         setScrimAlpha(mScrimInFront, alpha);
-        if (alpha == 0f) {
-            mScrimInFront.setClickable(false);
-        } else {
-            // Eat touch events (unless dozing).
-            mScrimInFront.setClickable(!mDozing);
-        }
     }
 
     private void setScrimAlpha(View scrim, float alpha) {
-        updateScrim(mAnimateChange, scrim, alpha, getCurrentScrimAlpha(scrim));
-    }
-
-    protected float getDozeAlpha(View scrim) {
-        return scrim == mScrimBehind ? mDozeBehindAlpha : mDozeInFrontAlpha;
-    }
-
-    protected float getCurrentScrimAlpha(View scrim) {
-        return scrim == mScrimBehind ? mCurrentBehindAlpha
-                : scrim == mScrimInFront ? mCurrentInFrontAlpha
-                : mCurrentHeadsUpAlpha;
-    }
-
-    private void setCurrentScrimAlpha(View scrim, float alpha) {
-        if (scrim == mScrimBehind) {
-            mCurrentBehindAlpha = alpha;
-            mLightBarController.setScrimAlpha(mCurrentBehindAlpha);
-        } else if (scrim == mScrimInFront) {
-            mCurrentInFrontAlpha = alpha;
+        if (alpha == 0f) {
+            scrim.setClickable(false);
         } else {
-            alpha = Math.max(0.0f, Math.min(1.0f, alpha));
-            mCurrentHeadsUpAlpha = alpha;
+            // Eat touch events (unless dozing).
+            scrim.setClickable(!(mState == ScrimState.AOD));
         }
+        updateScrim(mAnimateChange, scrim, alpha);
     }
 
-    private void updateScrimColor(View scrim) {
-        float alpha1 = getCurrentScrimAlpha(scrim);
+    private void updateScrimColor(View scrim, float alpha, int tint) {
+        alpha = Math.max(0, Math.min(1.0f, alpha));
         if (scrim instanceof ScrimView) {
             ScrimView scrimView = (ScrimView) scrim;
-            float dozeAlpha = getDozeAlpha(scrim);
-            float alpha = 1 - (1 - alpha1) * (1 - dozeAlpha);
-            alpha = Math.max(0, Math.min(1.0f, alpha));
-            scrimView.setViewAlpha(alpha);
 
             Trace.traceCounter(Trace.TRACE_TAG_APP,
                     scrim == mScrimInFront ? "front_scrim_alpha" : "back_scrim_alpha",
                     (int) (alpha * 255));
 
-            int dozeTint = Color.TRANSPARENT;
-
-            boolean dozing = mAnimatingDozeUnlock || mDozing;
-            boolean frontScrimDozing = mWakingUpFromAodInProgress;
-            if (dozing || frontScrimDozing && scrim == mScrimInFront) {
-                dozeTint = Color.BLACK;
-            }
             Trace.traceCounter(Trace.TRACE_TAG_APP,
                     scrim == mScrimInFront ? "front_scrim_tint" : "back_scrim_tint",
-                    dozeTint == Color.BLACK ? 1 : 0);
+                    Color.alpha(tint));
 
-            scrimView.setTint(dozeTint);
+            scrimView.setTint(tint);
+            scrimView.setViewAlpha(alpha);
         } else {
-            scrim.setAlpha(alpha1);
+            scrim.setAlpha(alpha);
         }
         dispatchScrimsVisible();
     }
 
-    private void startScrimAnimation(final View scrim, float target) {
-        float current = getCurrentScrimAlpha(scrim);
-        ValueAnimator anim = ValueAnimator.ofFloat(current, target);
+    private int getCurrentScrimTint(View scrim) {
+        return scrim == mScrimInFront ? mCurrentInFrontTint : mCurrentBehindTint;
+    }
+
+    private void startScrimAnimation(final View scrim, float current, float target) {
+        ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
+        final int initialScrimTint = scrim instanceof ScrimView ? ((ScrimView) scrim).getTint() :
+                Color.TRANSPARENT;
         anim.addUpdateListener(animation -> {
-            float alpha = (float) animation.getAnimatedValue();
-            setCurrentScrimAlpha(scrim, alpha);
-            updateScrimColor(scrim);
+            final float animAmount = (float) animation.getAnimatedValue();
+            final int finalScrimTint = scrim == mScrimInFront ?
+                    mCurrentInFrontTint : mCurrentBehindTint;
+            float alpha = MathUtils.lerp(current, target, animAmount);
+            int tint = ColorUtils.blendARGB(initialScrimTint, finalScrimTint, animAmount);
+            updateScrimColor(scrim, alpha, tint);
             dispatchScrimsVisible();
         });
         anim.setInterpolator(getInterpolator());
         anim.setStartDelay(mAnimationDelay);
-        anim.setDuration(mDurationOverride != -1 ? mDurationOverride : ANIMATION_DURATION);
+        anim.setDuration(mAnimationDuration);
         anim.addListener(new AnimatorListenerAdapter() {
             @Override
             public void onAnimationEnd(Animator animation) {
+                if (mKeyguardFadingOutInProgress) {
+                    mKeyguardFadeoutAnimation = null;
+                    mKeyguardFadingOutInProgress = false;
+                }
+                onFinished();
+
+                scrim.setTag(TAG_KEY_ANIM, null);
+                scrim.setTag(TAG_KEY_ANIM_TARGET, null);
+                dispatchScrimsVisible();
+
                 if (!mDeferFinishedListener && mOnAnimationFinished != null) {
                     mOnAnimationFinished.run();
                     mOnAnimationFinished = null;
                 }
-                if (mKeyguardFadingOutInProgress) {
-                    mKeyguardFadeoutAnimation = null;
-                    mKeyguardFadingOutInProgress = false;
-                    mAnimatingDozeUnlock = false;
-                }
-                if (mWakingUpFromAodAnimationRunning && !mDeferFinishedListener) {
-                    mWakingUpFromAodAnimationRunning = false;
-                    mWakingUpFromAodInProgress = false;
-                }
-                scrim.setTag(TAG_KEY_ANIM, null);
-                scrim.setTag(TAG_KEY_ANIM_TARGET, null);
-                dispatchScrimsVisible();
             }
         });
         anim.start();
@@ -558,12 +498,6 @@
             mKeyguardFadingOutInProgress = true;
             mKeyguardFadeoutAnimation = anim;
         }
-        if (mWakingUpFromAodInProgress) {
-            mWakingUpFromAodAnimationRunning = true;
-        }
-        if (mSkipFirstFrame) {
-            anim.setCurrentPlayTime(16);
-        }
         scrim.setTag(TAG_KEY_ANIM, anim);
         scrim.setTag(TAG_KEY_ANIM_TARGET, target);
     }
@@ -582,19 +516,33 @@
     public boolean onPreDraw() {
         mScrimBehind.getViewTreeObserver().removeOnPreDrawListener(this);
         mUpdatePending = false;
-        if (mDontAnimateBouncerChanges) {
-            mDontAnimateBouncerChanges = false;
+        if (mCallback != null) {
+            mCallback.onStart();
         }
         updateScrims();
-        mDurationOverride = -1;
-        mAnimationDelay = 0;
-        mSkipFirstFrame = false;
 
         // Make sure that we always call the listener even if we didn't start an animation.
         endAnimateKeyguardFadingOut(false /* force */);
         return true;
     }
 
+    private void onFinished() {
+        if (mWakeLockHeld) {
+            mWakeLock.release();
+            mWakeLockHeld = false;
+        }
+        if (mCallback != null) {
+            mCallback.onFinished();
+            mCallback = null;
+        }
+        // When unlocking with fingerprint, we'll fade the scrims from black to transparent.
+        // At the end of the animation we need to remove the tint.
+        if (mState == ScrimState.UNLOCKED) {
+            mCurrentInFrontTint = Color.TRANSPARENT;
+            mCurrentBehindTint = Color.TRANSPARENT;
+        }
+    }
+
     private void endAnimateKeyguardFadingOut(boolean force) {
         mAnimateKeyguardFadingOut = false;
         if (force || (!isAnimating(mScrimInFront) && !isAnimating(mScrimBehind))) {
@@ -603,8 +551,6 @@
                 mOnAnimationFinished = null;
             }
             mKeyguardFadingOutInProgress = false;
-            if (!mWakeAndUnlocking || force)
-                mAnimatingDozeUnlock = false;
         }
     }
 
@@ -641,16 +587,19 @@
     }
 
     private void updateHeadsUpScrim(boolean animate) {
-        updateScrim(animate, mHeadsUpScrim, calculateHeadsUpAlpha(), mCurrentHeadsUpAlpha);
+        updateScrim(animate, mHeadsUpScrim, calculateHeadsUpAlpha());
     }
 
-    private void updateScrim(boolean animate, View scrim, float alpha, float currentAlpha) {
-        if (mKeyguardFadingOutInProgress && mKeyguardFadeoutAnimation.getCurrentPlayTime() != 0) {
-            return;
-        }
+    @VisibleForTesting
+    void setOnAnimationFinished(Runnable onAnimationFinished) {
+        mOnAnimationFinished = onAnimationFinished;
+    }
 
-        ValueAnimator previousAnimator = ViewState.getChildTag(scrim,
-                TAG_KEY_ANIM);
+    private void updateScrim(boolean animate, View scrim, float alpha) {
+        final float currentAlpha = scrim instanceof ScrimView ? ((ScrimView) scrim).getViewAlpha()
+            : scrim.getAlpha();
+
+        ValueAnimator previousAnimator = ViewState.getChildTag(scrim, TAG_KEY_ANIM);
         float animEndValue = -1;
         if (previousAnimator != null) {
             if (animate || alpha == currentAlpha) {
@@ -664,9 +613,37 @@
                 animEndValue = ViewState.getChildTag(scrim, TAG_END_ALPHA);
             }
         }
-        if (alpha != currentAlpha && alpha != animEndValue) {
+
+        final boolean blankingInProgress = mScrimInFront.getTag(TAG_KEY_ANIM_BLANK) != null;
+        if (mBlankScreen || blankingInProgress) {
+            if (!blankingInProgress) {
+                blankDisplay();
+            }
+            return;
+        } else if (!mScreenBlankingCallbackCalled) {
+            // Not blanking the screen. Letting the callback know that we're ready
+            // to replace what was on the screen before.
+            if (mCallback != null) {
+                mCallback.onDisplayBlanked();
+                mScreenBlankingCallbackCalled = true;
+            }
+        }
+
+        // TODO factor mLightBarController out of this class
+        if (scrim == mScrimBehind) {
+            mLightBarController.setScrimAlpha(alpha);
+        }
+
+        final ScrimView scrimView = scrim instanceof  ScrimView ? (ScrimView) scrim : null;
+        final boolean wantsAlphaUpdate = alpha != currentAlpha && alpha != animEndValue;
+        final boolean wantsTintUpdate = scrimView != null
+                && scrimView.getTint() != getCurrentScrimTint(scrimView);
+
+        if (wantsAlphaUpdate || wantsTintUpdate) {
             if (animate) {
-                startScrimAnimation(scrim, alpha);
+                final float fromAlpha = scrimView == null ? scrim.getAlpha()
+                        : scrimView.getViewAlpha();
+                startScrimAnimation(scrim, fromAlpha, alpha);
                 scrim.setTag(TAG_START_ALPHA, currentAlpha);
                 scrim.setTag(TAG_END_ALPHA, alpha);
             } else {
@@ -685,13 +662,62 @@
                     previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
                 } else {
                     // update the alpha directly
-                    setCurrentScrimAlpha(scrim, alpha);
-                    updateScrimColor(scrim);
+                    updateScrimColor(scrim, alpha, getCurrentScrimTint(scrim));
+                    onFinished();
                 }
             }
+        } else {
+            onFinished();
         }
     }
 
+    private void blankDisplay() {
+        final float initialAlpha = mScrimInFront.getViewAlpha();
+        final int initialTint = mScrimInFront.getTint();
+        ValueAnimator anim = ValueAnimator.ofFloat(0, 1);
+        anim.addUpdateListener(animation -> {
+            final float amount = (float) animation.getAnimatedValue();
+            float animAlpha = MathUtils.lerp(initialAlpha, 1, amount);
+            int animTint = ColorUtils.blendARGB(initialTint, Color.BLACK, amount);
+            updateScrimColor(mScrimInFront, animAlpha, animTint);
+            dispatchScrimsVisible();
+        });
+        anim.setInterpolator(getInterpolator());
+        anim.setDuration(mDozeParameters.getPulseInDuration());
+        anim.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                if (mCallback != null) {
+                    mCallback.onDisplayBlanked();
+                    mScreenBlankingCallbackCalled = true;
+                }
+                Runnable blankingCallback = () -> {
+                    mScrimInFront.setTag(TAG_KEY_ANIM_BLANK, null);
+                    mBlankScreen = false;
+                    // Try again.
+                    updateScrims();
+                };
+
+                // 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.
+                getHandler().postDelayed(blankingCallback, 100);
+
+            }
+        });
+        anim.start();
+        mScrimInFront.setTag(TAG_KEY_ANIM_BLANK, anim);
+
+        // Finish animation if we're already at its final state
+        if (initialAlpha == 1 && mScrimInFront.getTint() == Color.BLACK) {
+            anim.end();
+        }
+    }
+
+    @VisibleForTesting
+    protected Handler getHandler() {
+        return Handler.getMain();
+    }
+
     /**
      * Set the amount the current top heads up view is dragged. The range is from 0 to 1 and 0 means
      * the heads up is in its resting space and 1 means it's fully dragged out.
@@ -719,23 +745,13 @@
         return alpha * expandFactor;
     }
 
-    public void forceHideScrims(boolean hide, boolean animated) {
-        mForceHideScrims = hide;
-        mAnimateChange = animated;
-        scheduleUpdate();
-    }
-
-    public void dontAnimateBouncerChangesUntilNextFrame() {
-        mDontAnimateBouncerChanges = true;
-    }
-
     public void setExcludedBackgroundArea(Rect area) {
         mScrimBehind.setExcludedArea(area);
     }
 
     public int getBackgroundColor() {
         int color = mLockColors.getMainColor();
-        return Color.argb((int) (mScrimBehind.getAlpha() * Color.alpha(color)),
+        return Color.argb((int) (mScrimBehind.getViewAlpha() * Color.alpha(color)),
                 Color.red(color), Color.green(color), Color.blue(color));
     }
 
@@ -764,27 +780,41 @@
         }
         if ((which & WallpaperManager.FLAG_SYSTEM) != 0) {
             mSystemColors = mColorExtractor.getColors(WallpaperManager.FLAG_SYSTEM,
-                    ColorExtractor.TYPE_DARK, mKeyguardShowing);
+                    ColorExtractor.TYPE_DARK, mState != ScrimState.UNLOCKED);
             mNeedsDrawableColorUpdate = true;
             scheduleUpdate();
         }
     }
 
-    public void dump(PrintWriter pw) {
-        pw.println(" ScrimController:");
+    @VisibleForTesting
+    protected WakeLock createWakeLock() {
+         return new DelayedWakeLock(getHandler(),
+                WakeLock.createPartial(mContext, "Doze"));
+    }
 
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.println(" ScrimController:");
+        pw.print(" state:"); pw.println(mState);
         pw.print("   frontScrim:"); pw.print(" viewAlpha="); pw.print(mScrimInFront.getViewAlpha());
         pw.print(" alpha="); pw.print(mCurrentInFrontAlpha);
-        pw.print(" dozeAlpha="); pw.print(mDozeInFrontAlpha);
         pw.print(" tint=0x"); pw.println(Integer.toHexString(mScrimInFront.getTint()));
 
         pw.print("   backScrim:"); pw.print(" viewAlpha="); pw.print(mScrimBehind.getViewAlpha());
         pw.print(" alpha="); pw.print(mCurrentBehindAlpha);
-        pw.print(" dozeAlpha="); pw.print(mDozeBehindAlpha);
         pw.print(" tint=0x"); pw.println(Integer.toHexString(mScrimBehind.getTint()));
 
-        pw.print("   mBouncerShowing="); pw.println(mBouncerShowing);
         pw.print("   mTracking="); pw.println(mTracking);
-        pw.print("   mForceHideScrims="); pw.println(mForceHideScrims);
+    }
+
+    public interface Callback {
+        default void onStart() {
+        }
+        default void onDisplayBlanked() {
+        }
+        default void onFinished() {
+        }
+        default void onCancelled() {
+        }
     }
 }
diff --git a/com/android/systemui/statusbar/phone/ScrimState.java b/com/android/systemui/statusbar/phone/ScrimState.java
new file mode 100644
index 0000000..0db98f3
--- /dev/null
+++ b/com/android/systemui/statusbar/phone/ScrimState.java
@@ -0,0 +1,208 @@
+/*
+ * 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.statusbar.phone;
+
+import android.graphics.Color;
+import android.os.Trace;
+
+import com.android.systemui.statusbar.ScrimView;
+
+/**
+ * Possible states of the ScrimController state machine.
+ */
+public enum ScrimState {
+
+    /**
+     * Initial state.
+     */
+    UNINITIALIZED,
+
+    /**
+     * On the lock screen.
+     */
+    KEYGUARD {
+
+        @Override
+        public void prepare(ScrimState previousState) {
+            // DisplayPowerManager will blank the screen, we'll just
+            // set our scrim to black in this frame to avoid flickering and
+            // fade it out afterwards.
+            mBlankScreen = previousState == ScrimState.AOD;
+            if (previousState == ScrimState.AOD) {
+                updateScrimColor(mScrimInFront, 1, Color.BLACK);
+            }
+            mCurrentBehindAlpha = mScrimBehindAlphaKeyguard;
+            mCurrentInFrontAlpha = 0;
+        }
+    },
+
+    /**
+     * Showing password challenge.
+     */
+    BOUNCER {
+        @Override
+        public void prepare(ScrimState previousState) {
+            mCurrentBehindAlpha = ScrimController.SCRIM_BEHIND_ALPHA_UNLOCKING;
+            mCurrentInFrontAlpha = ScrimController.SCRIM_IN_FRONT_ALPHA_LOCKED;
+        }
+    },
+
+    /**
+     * Changing screen brightness from quick settings.
+     */
+    BRIGHTNESS_MIRROR {
+        @Override
+        public void prepare(ScrimState previousState) {
+            mCurrentBehindAlpha = 0;
+            mCurrentInFrontAlpha = 0;
+        }
+    },
+
+    /**
+     * Always on display or screen off.
+     */
+    AOD {
+        @Override
+        public void prepare(ScrimState previousState) {
+            if (previousState == ScrimState.PULSING) {
+                updateScrimColor(mScrimInFront, 1, Color.BLACK);
+            }
+            final boolean alwaysOnEnabled = mDozeParameters.getAlwaysOn();
+            mBlankScreen = previousState == ScrimState.PULSING;
+            mCurrentBehindAlpha = 1;
+            mCurrentInFrontAlpha = alwaysOnEnabled ? mAodFrontScrimAlpha : 1f;
+            mCurrentInFrontTint = Color.BLACK;
+            mCurrentBehindTint = Color.BLACK;
+            // DisplayPowerManager will blank the screen for us, we just need
+            // to set our state.
+            mAnimateChange = false;
+        }
+    },
+
+    /**
+     * When phone wakes up because you received a notification.
+     */
+    PULSING {
+        @Override
+        public void prepare(ScrimState previousState) {
+            mCurrentBehindAlpha = 1;
+            mCurrentInFrontAlpha = 0;
+            mCurrentInFrontTint = Color.BLACK;
+            mCurrentBehindTint = Color.BLACK;
+            mBlankScreen = true;
+            updateScrimColor(mScrimInFront, 1, Color.BLACK);
+        }
+    },
+
+    /**
+     * Unlocked on top of an app (launcher or any other activity.)
+     */
+    UNLOCKED {
+        @Override
+        public void prepare(ScrimState previousState) {
+            mCurrentBehindAlpha = 0;
+            mCurrentInFrontAlpha = 0;
+            mAnimationDuration = StatusBar.FADE_KEYGUARD_DURATION;
+
+            if (previousState == ScrimState.AOD) {
+                // Fade from black to transparent when coming directly from AOD
+                updateScrimColor(mScrimInFront, 1, Color.BLACK);
+                updateScrimColor(mScrimBehind, 1, Color.BLACK);
+                // Scrims should still be black at the end of the transition.
+                mCurrentInFrontTint = Color.BLACK;
+                mCurrentBehindTint = Color.BLACK;
+                mBlankScreen = true;
+            } else {
+                // Scrims should still be black at the end of the transition.
+                mCurrentInFrontTint = Color.TRANSPARENT;
+                mCurrentBehindTint = Color.TRANSPARENT;
+                mBlankScreen = false;
+            }
+        }
+    };
+
+    boolean mBlankScreen = false;
+    long mAnimationDuration = ScrimController.ANIMATION_DURATION;
+    int mCurrentInFrontTint = Color.TRANSPARENT;
+    int mCurrentBehindTint = Color.TRANSPARENT;
+    boolean mAnimateChange = true;
+    float mCurrentInFrontAlpha;
+    float mCurrentBehindAlpha;
+    float mAodFrontScrimAlpha;
+    float mScrimBehindAlphaKeyguard;
+    ScrimView mScrimInFront;
+    ScrimView mScrimBehind;
+    DozeParameters mDozeParameters;
+
+    public void init(ScrimView scrimInFront, ScrimView scrimBehind, DozeParameters dozeParameters) {
+        mScrimInFront = scrimInFront;
+        mScrimBehind = scrimBehind;
+        mDozeParameters = dozeParameters;
+    }
+
+    public void prepare(ScrimState previousState) {
+    }
+
+    public float getFrontAlpha() {
+        return mCurrentInFrontAlpha;
+    }
+
+    public float getBehindAlpha() {
+        return mCurrentBehindAlpha;
+    }
+
+    public int getFrontTint() {
+        return mCurrentInFrontTint;
+    }
+
+    public int getBehindTint() {
+        return mCurrentBehindTint;
+    }
+
+    public long getAnimationDuration() {
+        return mAnimationDuration;
+    }
+
+    public boolean getBlanksScreen() {
+        return mBlankScreen;
+    }
+
+    public void updateScrimColor(ScrimView scrim, float alpha, int tint) {
+        Trace.traceCounter(Trace.TRACE_TAG_APP,
+                scrim == mScrimInFront ? "front_scrim_alpha" : "back_scrim_alpha",
+                (int) (alpha * 255));
+
+        Trace.traceCounter(Trace.TRACE_TAG_APP,
+                scrim == mScrimInFront ? "front_scrim_tint" : "back_scrim_tint",
+                Color.alpha(tint));
+
+        scrim.setTint(tint);
+        scrim.setViewAlpha(alpha);
+    }
+
+    public boolean getAnimateChange() {
+        return mAnimateChange;
+    }
+
+    public void setAodFrontScrimAlpha(float aodFrontScrimAlpha) {
+        mAodFrontScrimAlpha = aodFrontScrimAlpha;
+    }
+
+    public void setScrimBehindAlphaKeyguard(float scrimBehindAlphaKeyguard) {
+        mScrimBehindAlphaKeyguard = scrimBehindAlphaKeyguard;
+    }
+}
\ No newline at end of file
diff --git a/com/android/systemui/statusbar/phone/StatusBar.java b/com/android/systemui/statusbar/phone/StatusBar.java
index 6775615..dc8100f 100644
--- a/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/com/android/systemui/statusbar/phone/StatusBar.java
@@ -79,7 +79,6 @@
 import android.graphics.drawable.Drawable;
 import android.media.AudioAttributes;
 import android.media.MediaMetadata;
-import android.media.session.MediaSessionManager;
 import android.metrics.LogMaker;
 import android.net.Uri;
 import android.os.AsyncTask;
@@ -239,6 +238,7 @@
         .OnChildLocationsChangedListener;
 import com.android.systemui.util.NotificationChannels;
 import com.android.systemui.util.leak.LeakDetector;
+import com.android.systemui.util.wakelock.WakeLock;
 import com.android.systemui.volume.VolumeComponent;
 
 import java.io.FileDescriptor;
@@ -387,6 +387,7 @@
 
     private VolumeComponent mVolumeComponent;
     private BrightnessMirrorController mBrightnessMirrorController;
+    private boolean mBrightnessMirrorVisible;
     protected FingerprintUnlockController mFingerprintUnlockController;
     private LightBarController mLightBarController;
     protected LockscreenWallpaper mLockscreenWallpaper;
@@ -647,6 +648,31 @@
         }
     };
 
+    // Notifies StatusBarKeyguardViewManager every time the keyguard transition is over,
+    // this animation is tied to the scrim for historic reasons.
+    // TODO: notify when keyguard has faded away instead of the scrim.
+    private final ScrimController.Callback mUnlockScrimCallback = new ScrimController
+            .Callback() {
+        @Override
+        public void onFinished() {
+            notifyKeyguardState();
+        }
+
+        @Override
+        public void onCancelled() {
+            notifyKeyguardState();
+        }
+
+        private void notifyKeyguardState() {
+            if (mStatusBarKeyguardViewManager == null) {
+                Log.w(TAG, "Tried to notify keyguard visibility when "
+                        + "mStatusBarKeyguardViewManager was null");
+                return;
+            }
+            mStatusBarKeyguardViewManager.onKeyguardFadedAway();
+        }
+    };
+
     private NotificationMessagingUtil mMessagingUtil;
     private KeyguardUserSwitcher mKeyguardUserSwitcher;
     private UserSwitcherController mUserSwitcherController;
@@ -919,7 +945,14 @@
         mNotificationPanel = mStatusBarWindow.findViewById(R.id.notification_panel);
         mStackScroller = mStatusBarWindow.findViewById(R.id.notification_stack_scroller);
         mGutsManager = new NotificationGutsManager(this, mStackScroller,
-                mCheckSaveListener, mContext);
+                mCheckSaveListener, mContext,
+                key -> {
+                    try {
+                        mBarService.onNotificationSettingsViewed(key);
+                    } catch (RemoteException e) {
+                        // if we're here we're dead
+                    }
+                });
         mNotificationPanel.setStatusBar(this);
         mNotificationPanel.setGroupManager(mGroupManager);
         mAboveShelfObserver = new AboveShelfObserver(mStackScroller);
@@ -1039,7 +1072,7 @@
                     if (mStatusBarWindowManager != null) {
                         mStatusBarWindowManager.setScrimsVisible(scrimsVisible);
                     }
-                });
+                }, new DozeParameters(mContext));
         if (mScrimSrcModeEnabled) {
             Runnable runnable = () -> {
                 boolean asSrc = mBackdrop.getVisibility() != View.VISIBLE;
@@ -1075,7 +1108,10 @@
             final QSTileHost qsh = SystemUIFactory.getInstance().createQSTileHost(mContext, this,
                     mIconController);
             mBrightnessMirrorController = new BrightnessMirrorController(mStatusBarWindow,
-                    mScrimController);
+                    (visible) -> {
+                        mBrightnessMirrorVisible = visible;
+                        updateScrimController();
+                    });
             fragmentHostManager.addTagListener(QS.TAG, (tag, f) -> {
                 QS qs = (QS) f;
                 if (qs instanceof QSFragment) {
@@ -1222,13 +1258,13 @@
         reevaluateStyles();
     }
 
-    private void reinflateViews() {
+    private void onThemeChanged() {
         reevaluateStyles();
 
         // Clock and bottom icons
-        mNotificationPanel.onOverlayChanged();
+        mNotificationPanel.onThemeChanged();
         // The status bar on the keyguard is a special layout.
-        if (mKeyguardStatusBar != null) mKeyguardStatusBar.onOverlayChanged();
+        if (mKeyguardStatusBar != null) mKeyguardStatusBar.onThemeChanged();
         // Recreate Indication controller because internal references changed
         mKeyguardIndicationController =
                 SystemUIFactory.getInstance().createKeyguardIndicationController(mContext,
@@ -1239,11 +1275,8 @@
                 .setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
         mKeyguardIndicationController.setVisible(mState == StatusBarState.KEYGUARD);
         mKeyguardIndicationController.setDozing(mDozing);
-        if (mBrightnessMirrorController != null) {
-            mBrightnessMirrorController.onOverlayChanged();
-        }
         if (mStatusBarKeyguardViewManager != null) {
-            mStatusBarKeyguardViewManager.onOverlayChanged();
+            mStatusBarKeyguardViewManager.onThemeChanged();
         }
         if (mAmbientIndicationContainer instanceof AutoReinflateContainer) {
             ((AutoReinflateContainer) mAmbientIndicationContainer).inflateLayout();
@@ -1258,6 +1291,13 @@
         updateEmptyShadeView();
     }
 
+    @Override
+    public void onOverlayChanged() {
+        if (mBrightnessMirrorController != null) {
+            mBrightnessMirrorController.onOverlayChanged();
+        }
+    }
+
     private void updateNotificationsOnDensityOrFontScaleChanged() {
         ArrayList<Entry> activeNotifications = mNotificationData.getActiveNotifications();
         for (int i = 0; i < activeNotifications.size(); i++) {
@@ -1447,8 +1487,7 @@
                 mDozeScrimController, keyguardViewMediator,
                 mScrimController, this, UnlockMethodCache.getInstance(mContext));
         mStatusBarKeyguardViewManager = keyguardViewMediator.registerStatusBar(this,
-                getBouncerContainer(), mScrimController,
-                mFingerprintUnlockController);
+                getBouncerContainer(), mFingerprintUnlockController);
         mKeyguardIndicationController
                 .setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
         mFingerprintUnlockController.setStatusBarKeyguardViewManager(mStatusBarKeyguardViewManager);
@@ -1470,6 +1509,11 @@
                         }
                     }, REMOTE_INPUT_KEPT_ENTRY_AUTO_CANCEL_DELAY);
                 }
+                try {
+                    mBarService.onNotificationDirectReplied(entry.key);
+                } catch (RemoteException e) {
+                    // system process is dead if we're here.
+                }
             }
         });
 
@@ -1785,9 +1829,14 @@
         final int id = n.getId();
         final int userId = n.getUserId();
         try {
-            // TODO: record actual dismissal surface
+            int dismissalSurface = NotificationStats.DISMISSAL_SHADE;
+            if (isHeadsUp(n.getKey())) {
+                dismissalSurface = NotificationStats.DISMISSAL_PEEK;
+            } else if (mStackScroller.hasPulsingNotifications()) {
+                dismissalSurface = NotificationStats.DISMISSAL_AOD;
+            }
             mBarService.onNotificationClear(pkg, tag, id, userId, n.getKey(),
-                    NotificationStats.DISMISSAL_OTHER);
+                    dismissalSurface);
             if (FORCE_REMOTE_INPUT_HISTORY
                     && mKeysKeptForRemoteInput.contains(n.getKey())) {
                 mKeysKeptForRemoteInput.remove(n.getKey());
@@ -2620,7 +2669,7 @@
     }
 
     public boolean isPulsing() {
-        return mDozeScrimController.isPulsing();
+        return mDozeScrimController != null && mDozeScrimController.isPulsing();
     }
 
     @Override
@@ -3328,7 +3377,7 @@
         }
 
         if (mScrimController != null) {
-            mScrimController.dump(pw);
+            mScrimController.dump(fd, pw, args);
         }
 
         if (DUMPTRUCK) {
@@ -3393,7 +3442,19 @@
     private void addStatusBarWindow() {
         makeStatusBarView();
         mStatusBarWindowManager = Dependency.get(StatusBarWindowManager.class);
-        mRemoteInputController = new RemoteInputController(mHeadsUpManager);
+        mRemoteInputController = new RemoteInputController(new RemoteInputController.Delegate() {
+          public void setRemoteInputActive(NotificationData.Entry entry,
+                  boolean remoteInputActive) {
+              mHeadsUpManager.setRemoteInputActive(entry, remoteInputActive);
+          }
+          public void lockScrollTo(NotificationData.Entry entry) {
+              mStackScroller.lockScrollTo(entry.row);
+          }
+          public void requestDisallowLongPressAndDismiss() {
+              mStackScroller.requestDisallowLongPress();
+              mStackScroller.requestDisallowDismiss();
+          }
+        });
         mStatusBarWindowManager.add(mStatusBarWindow, getStatusBarHeight());
     }
 
@@ -4061,7 +4122,6 @@
         releaseGestureWakeLock();
         runLaunchTransitionEndRunnable();
         mLaunchTransitionFadingAway = false;
-        mScrimController.forceHideScrims(false /* hide */, false /* animated */);
         updateMediaMetaData(true /* metaDataChanged */, true);
     }
 
@@ -4094,7 +4154,7 @@
             if (beforeFading != null) {
                 beforeFading.run();
             }
-            mScrimController.forceHideScrims(true /* hide */, false /* animated */);
+            updateScrimController();
             updateMediaMetaData(false, true);
             mNotificationPanel.setAlpha(1);
             mStackScroller.setParentNotFullyVisible(true);
@@ -4125,6 +4185,13 @@
                 .setStartDelay(0)
                 .setDuration(FADE_KEYGUARD_DURATION_PULSING)
                 .setInterpolator(ScrimController.KEYGUARD_FADE_OUT_INTERPOLATOR)
+                .setListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        hideKeyguard();
+                        mStatusBarKeyguardViewManager.onKeyguardFadedAway();
+                    }
+                })
                 .start();
     }
 
@@ -4132,7 +4199,6 @@
      * Plays the animation when an activity that was occluding Keyguard goes away.
      */
     public void animateKeyguardUnoccluding() {
-        mScrimController.animateKeyguardUnoccluding(500);
         mNotificationPanel.setExpandedFraction(0f);
         animateExpandNotificationsPanel();
     }
@@ -4320,11 +4386,6 @@
                 mAmbientIndicationContainer.setVisibility(View.INVISIBLE);
             }
         }
-        if (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED) {
-            mScrimController.setKeyguardShowing(true);
-        } else {
-            mScrimController.setKeyguardShowing(false);
-        }
         mNotificationPanel.setBarState(mState, mKeyguardFadingAway, goingToFullShade);
         updateTheme();
         updateDozingState();
@@ -4332,6 +4393,7 @@
         updateStackScrollerState(goingToFullShade, fromShadeLocked);
         updateNotifications();
         checkBarModes();
+        updateScrimController();
         updateMediaMetaData(false, mState != StatusBarState.KEYGUARD);
         mKeyguardMonitor.notifyKeyguardState(mStatusBarKeyguardViewManager.isShowing(),
                 mUnlockMethodCache.isMethodSecure(),
@@ -4369,7 +4431,7 @@
         if (mContext.getThemeResId() != themeResId) {
             mContext.setTheme(themeResId);
             if (inflated) {
-                reinflateViews();
+                onThemeChanged();
             }
         }
 
@@ -4395,11 +4457,10 @@
         boolean animate = !mDozing && mDozeServiceHost.shouldAnimateWakeup();
         mNotificationPanel.setDozing(mDozing, animate);
         mStackScroller.setDark(mDozing, animate, mWakeUpTouchLocation);
-        mScrimController.setDozing(mDozing);
+        mDozeScrimController.setDozing(mDozing);
         mKeyguardIndicationController.setDozing(mDozing);
         mNotificationPanel.setDark(mDozing, animate);
         updateQsExpansionEnabled();
-        mDozeScrimController.setDozing(mDozing, animate);
         updateRowStates();
         Trace.endSection();
     }
@@ -4894,6 +4955,7 @@
         if (mStatusBarView != null) mStatusBarView.setBouncerShowing(bouncerShowing);
         updateHideIconsForBouncer(true /* animate */);
         recomputeDisableFlags(true /* animate */);
+        updateScrimController();
     }
 
     public void cancelCurrentTouch() {
@@ -4945,12 +5007,10 @@
             mStackScroller.setAnimationsEnabled(true);
             mVisualStabilityManager.setScreenOn(true);
             mNotificationPanel.setTouchDisabled(false);
-
-            maybePrepareWakeUpFromAod();
-
             mDozeServiceHost.stopDozing();
             updateVisibleToUser();
             updateIsKeyguard();
+            updateScrimController();
         }
     };
 
@@ -4960,18 +5020,16 @@
             mFalsingManager.onScreenTurningOn();
             mNotificationPanel.onScreenTurningOn();
 
-            maybePrepareWakeUpFromAod();
-
             if (mLaunchCameraOnScreenTurningOn) {
                 mNotificationPanel.launchCamera(false, mLastCameraLaunchSource);
                 mLaunchCameraOnScreenTurningOn = false;
             }
+
+            updateScrimController();
         }
 
         @Override
         public void onScreenTurnedOn() {
-            mScrimController.wakeUpFromAod();
-            mDozeScrimController.onScreenTurnedOn();
         }
 
         @Override
@@ -4989,13 +5047,6 @@
         return mWakefulnessLifecycle.getWakefulness();
     }
 
-    private void maybePrepareWakeUpFromAod() {
-        int wakefulness = mWakefulnessLifecycle.getWakefulness();
-        if (mDozing && wakefulness == WAKEFULNESS_WAKING && !isPulsing()) {
-            mScrimController.prepareWakeUpFromAod();
-        }
-    }
-
     private void vibrateForCameraGesture() {
         // Make sure to pass -1 for repeat so VibratorService doesn't stop us when going to sleep.
         mVibrator.vibrate(mCameraLaunchGestureVibePattern, -1 /* repeat */);
@@ -5078,12 +5129,12 @@
             if (!mDeviceInteractive) {
                 // Avoid flickering of the scrim when we instant launch the camera and the bouncer
                 // comes on.
-                mScrimController.dontAnimateBouncerChangesUntilNextFrame();
                 mGestureWakeLock.acquire(LAUNCH_TRANSITION_TIMEOUT_MS + 1000L);
             }
             if (isScreenTurningOnOrOn()) {
                 if (DEBUG_CAMERA_LIFT) Slog.d(TAG, "Launching camera");
                 mNotificationPanel.launchCamera(mDeviceInteractive /* animate */, source);
+                updateScrimController();
             } else {
                 // We need to defer the camera launch until the screen comes on, since otherwise
                 // we will dismiss us too early since we are waiting on an activity to be drawn and
@@ -5125,15 +5176,16 @@
     private void updateDozing() {
         Trace.beginSection("StatusBar#updateDozing");
         // When in wake-and-unlock while pulsing, keep dozing state until fully unlocked.
-        mDozing = mDozingRequested && mState == StatusBarState.KEYGUARD
+        boolean dozing = mDozingRequested && mState == StatusBarState.KEYGUARD
                 || mFingerprintUnlockController.getMode()
                         == FingerprintUnlockController.MODE_WAKE_AND_UNLOCK_PULSING;
         // When in wake-and-unlock we may not have received a change to mState
         // but we still should not be dozing, manually set to false.
         if (mFingerprintUnlockController.getMode() ==
                 FingerprintUnlockController.MODE_WAKE_AND_UNLOCK) {
-            mDozing = false;
+            dozing = false;
         }
+        mDozing = dozing;
         mStatusBarWindowManager.setDozing(mDozing);
         mStatusBarKeyguardViewManager.setDozing(mDozing);
         if (mAmbientIndicationContainer instanceof DozeReceiver) {
@@ -5143,6 +5195,24 @@
         Trace.endSection();
     }
 
+    public void updateScrimController() {
+        if (mBouncerShowing) {
+            mScrimController.transitionTo(ScrimState.BOUNCER);
+        } else if (mLaunchCameraOnScreenTurningOn || isInLaunchTransition()) {
+            mScrimController.transitionTo(ScrimState.UNLOCKED, mUnlockScrimCallback);
+        } else if (mBrightnessMirrorVisible) {
+            mScrimController.transitionTo(ScrimState.BRIGHTNESS_MIRROR);
+        } else if (isPulsing()) {
+            // Handled in DozeScrimController#setPulsing
+        } else if (mDozing) {
+            mScrimController.transitionTo(ScrimState.AOD);
+        } else if (mIsKeyguard) {
+            mScrimController.transitionTo(ScrimState.KEYGUARD);
+        } else {
+            mScrimController.transitionTo(ScrimState.UNLOCKED, mUnlockScrimCallback);
+        }
+    }
+
     public boolean isKeyguardShowing() {
         if (mStatusBarKeyguardViewManager == null) {
             Slog.i(TAG, "isKeyguardShowing() called before startKeyguard(), returning true");
@@ -5202,7 +5272,6 @@
             }
 
             mDozeScrimController.pulse(new PulseCallback() {
-
                 @Override
                 public void onPulseStarted() {
                     callback.onPulseStarted();
@@ -5287,11 +5356,6 @@
         }
 
         @Override
-        public void abortPulsing() {
-            mDozeScrimController.abortPulsing();
-        }
-
-        @Override
         public void extendPulse() {
             mDozeScrimController.extendPulse();
         }
@@ -5327,7 +5391,7 @@
 
         @Override
         public void setAodDimmingScrim(float scrimOpacity) {
-            mDozeScrimController.setAodDimmingScrim(scrimOpacity);
+            ScrimState.AOD.setAodFrontScrimAlpha(scrimOpacity);
         }
 
         public void dispatchDoubleTap(float viewX, float viewY) {
diff --git a/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 09828dc..ef05bbb 100644
--- a/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -24,7 +24,6 @@
 import android.content.Context;
 import android.os.Bundle;
 import android.os.SystemClock;
-import android.os.Trace;
 import android.view.KeyEvent;
 import android.view.View;
 import android.view.ViewGroup;
@@ -71,17 +70,14 @@
 
     protected final Context mContext;
     private final StatusBarWindowManager mStatusBarWindowManager;
-    private final boolean mDisplayBlanksAfterDoze;
 
     protected LockPatternUtils mLockPatternUtils;
     protected ViewMediatorCallback mViewMediatorCallback;
     protected StatusBar mStatusBar;
-    private ScrimController mScrimController;
     private FingerprintUnlockController mFingerprintUnlockController;
 
     private ViewGroup mContainer;
 
-    private boolean mScreenTurnedOn;
     protected KeyguardBouncer mBouncer;
     protected boolean mShowing;
     protected boolean mOccluded;
@@ -95,12 +91,10 @@
     private boolean mLastBouncerDismissible;
     protected boolean mLastRemoteInputActive;
     private boolean mLastDozing;
-    private boolean mLastDeferScrimFadeOut;
     private int mLastFpMode;
 
     private OnDismissAction mAfterKeyguardGoneAction;
     private final ArrayList<Runnable> mAfterKeyguardGoneRunnables = new ArrayList<>();
-    private boolean mDeferScrimFadeOut;
 
     // Dismiss action to be launched when we stop dozing or the keyguard is gone.
     private DismissWithActionRequest mPendingWakeupAction;
@@ -125,18 +119,14 @@
         mLockPatternUtils = lockPatternUtils;
         mStatusBarWindowManager = Dependency.get(StatusBarWindowManager.class);
         KeyguardUpdateMonitor.getInstance(context).registerCallback(mUpdateMonitorCallback);
-        mDisplayBlanksAfterDoze = context.getResources().getBoolean(
-                com.android.internal.R.bool.config_displayBlanksAfterDoze);
     }
 
     public void registerStatusBar(StatusBar statusBar,
             ViewGroup container,
-            ScrimController scrimController,
             FingerprintUnlockController fingerprintUnlockController,
             DismissCallbackRegistry dismissCallbackRegistry) {
         mStatusBar = statusBar;
         mContainer = container;
-        mScrimController = scrimController;
         mFingerprintUnlockController = fingerprintUnlockController;
         mBouncer = SystemUIFactory.getInstance().createKeyguardBouncer(mContext,
                 mViewMediatorCallback, mLockPatternUtils, container, dismissCallbackRegistry);
@@ -149,7 +139,6 @@
     public void show(Bundle options) {
         mShowing = true;
         mStatusBarWindowManager.setKeyguardShowing(true);
-        mScrimController.abortKeyguardFadingOut();
         reset(true /* hideBouncerWhenShowing */);
     }
 
@@ -253,15 +242,7 @@
     }
 
     public void onScreenTurnedOn() {
-        Trace.beginSection("StatusBarKeyguardViewManager#onScreenTurnedOn");
-        mScreenTurnedOn = true;
-        if (mDeferScrimFadeOut) {
-            mDeferScrimFadeOut = false;
-            animateScrimControllerKeyguardFadingOut(0, WAKE_AND_UNLOCK_SCRIM_FADEOUT_DURATION_MS,
-                    true /* skipFirstFrame */);
-            updateStates();
-        }
-        Trace.endSection();
+        // TODO: remove
     }
 
     @Override
@@ -285,7 +266,7 @@
     }
 
     public void onScreenTurnedOff() {
-        mScreenTurnedOn = false;
+        // TODO: remove
     }
 
     public void notifyDeviceWakeUpRequested() {
@@ -374,10 +355,6 @@
                     mStatusBarWindowManager.setKeyguardFadingAway(true);
                     hideBouncer(true /* destroyView */);
                     updateStates();
-                    mScrimController.animateKeyguardFadingOut(
-                            StatusBar.FADE_KEYGUARD_START_DELAY,
-                            StatusBar.FADE_KEYGUARD_DURATION, null,
-                            false /* skipFirstFrame */);
                 }
             }, new Runnable() {
                 @Override
@@ -400,36 +377,16 @@
             mFingerprintUnlockController.startKeyguardFadingAway();
             hideBouncer(true /* destroyView */);
             if (wakeUnlockPulsing) {
-                mStatusBarWindowManager.setKeyguardFadingAway(true);
                 mStatusBar.fadeKeyguardWhilePulsing();
-                animateScrimControllerKeyguardFadingOut(delay, fadeoutDuration,
-                        mStatusBar::hideKeyguard, false /* skipFirstFrame */);
+                wakeAndUnlockDejank();
             } else {
                 mFingerprintUnlockController.startKeyguardFadingAway();
                 mStatusBar.setKeyguardFadingAway(startTime, delay, fadeoutDuration);
                 boolean staying = mStatusBar.hideKeyguard();
                 if (!staying) {
                     mStatusBarWindowManager.setKeyguardFadingAway(true);
-                    if (mFingerprintUnlockController.getMode() == MODE_WAKE_AND_UNLOCK) {
-                        boolean turnedOnSinceAuth =
-                                mFingerprintUnlockController.hasScreenTurnedOnSinceAuthenticating();
-                        if (!mScreenTurnedOn || mDisplayBlanksAfterDoze && !turnedOnSinceAuth) {
-                            // Not ready to animate yet; either because the screen is not on yet,
-                            // or it is on but will turn off before waking out of doze.
-                            mDeferScrimFadeOut = true;
-                        } else {
-
-                            // Screen is already on, don't defer with fading out.
-                            animateScrimControllerKeyguardFadingOut(0,
-                                    WAKE_AND_UNLOCK_SCRIM_FADEOUT_DURATION_MS,
-                                    true /* skipFirstFrame */);
-                        }
-                    } else {
-                        animateScrimControllerKeyguardFadingOut(delay, fadeoutDuration,
-                                false /* skipFirstFrame */);
-                    }
+                    wakeAndUnlockDejank();
                 } else {
-                    mScrimController.animateGoingToFullShade(delay, fadeoutDuration);
                     mStatusBar.finishKeyguardFadingAway();
                     mFingerprintUnlockController.finishKeyguardFadingAway();
                 }
@@ -444,35 +401,22 @@
         hideBouncer(true /* destroyView */);
     }
 
-    public void onOverlayChanged() {
+    public void onThemeChanged() {
         hideBouncer(true /* destroyView */);
         mBouncer.prepare();
     }
 
-    private void animateScrimControllerKeyguardFadingOut(long delay, long duration,
-            boolean skipFirstFrame) {
-        animateScrimControllerKeyguardFadingOut(delay, duration, null /* endRunnable */,
-                skipFirstFrame);
+    public void onKeyguardFadedAway() {
+        mContainer.postDelayed(() -> mStatusBarWindowManager.setKeyguardFadingAway(false),
+                100);
+        mStatusBar.finishKeyguardFadingAway();
+        mFingerprintUnlockController.finishKeyguardFadingAway();
+        WindowManagerGlobal.getInstance().trimMemory(
+                ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);
+
     }
 
-    private void animateScrimControllerKeyguardFadingOut(long delay, long duration,
-            final Runnable endRunnable, boolean skipFirstFrame) {
-        Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, "Fading out", 0);
-        mScrimController.animateKeyguardFadingOut(delay, duration, new Runnable() {
-            @Override
-            public void run() {
-                if (endRunnable != null) {
-                    endRunnable.run();
-                }
-                mContainer.postDelayed(() -> mStatusBarWindowManager.setKeyguardFadingAway(false),
-                        100);
-                mStatusBar.finishKeyguardFadingAway();
-                mFingerprintUnlockController.finishKeyguardFadingAway();
-                WindowManagerGlobal.getInstance().trimMemory(
-                        ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);
-                Trace.asyncTraceEnd(Trace.TRACE_TAG_VIEW, "Fading out", 0);
-            }
-        }, skipFirstFrame);
+    private void wakeAndUnlockDejank() {
         if (mFingerprintUnlockController.getMode() == MODE_WAKE_AND_UNLOCK
                 && LatencyTracker.isEnabled(mContext)) {
             DejankUtils.postAfterTraversal(() ->
@@ -593,7 +537,6 @@
         if (bouncerShowing != mLastBouncerShowing || mFirstUpdate) {
             mStatusBarWindowManager.setBouncerShowing(bouncerShowing);
             mStatusBar.setBouncerShowing(bouncerShowing);
-            mScrimController.setBouncerShowing(bouncerShowing);
         }
 
         KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
@@ -611,7 +554,6 @@
         mLastBouncerDismissible = bouncerDismissible;
         mLastRemoteInputActive = remoteInputActive;
         mLastDozing = mDozing;
-        mLastDeferScrimFadeOut = mDeferScrimFadeOut;
         mLastFpMode = mFingerprintUnlockController.getMode();
         mStatusBar.onKeyguardViewManagerStatesUpdated();
     }
@@ -624,7 +566,7 @@
         boolean keyguardShowing = mShowing && !mOccluded;
         boolean hideWhileDozing = mDozing && fpMode != MODE_WAKE_AND_UNLOCK_PULSING;
         return (!keyguardShowing && !hideWhileDozing || mBouncer.isShowing()
-                || mRemoteInputActive) && !mDeferScrimFadeOut;
+                || mRemoteInputActive);
     }
 
     /**
@@ -634,7 +576,7 @@
         boolean keyguardShowing = mLastShowing && !mLastOccluded;
         boolean hideWhileDozing = mLastDozing && mLastFpMode != MODE_WAKE_AND_UNLOCK_PULSING;
         return (!keyguardShowing && !hideWhileDozing || mLastBouncerShowing
-                || mLastRemoteInputActive) && !mLastDeferScrimFadeOut;
+                || mLastRemoteInputActive);
     }
 
     public boolean shouldDismissOnMenuPressed() {
diff --git a/com/android/systemui/statusbar/policy/BrightnessMirrorController.java b/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
index 42ce4c5..a011952 100644
--- a/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
+++ b/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
@@ -16,7 +16,9 @@
 
 package com.android.systemui.statusbar.policy;
 
+import android.annotation.NonNull;
 import android.util.ArraySet;
+import android.view.ContextThemeWrapper;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewPropertyAnimator;
@@ -25,55 +27,52 @@
 import com.android.internal.util.Preconditions;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
-import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.phone.StatusBarWindowView;
 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
 
+import java.util.function.Consumer;
+
 /**
  * Controls showing and hiding of the brightness mirror.
  */
 public class BrightnessMirrorController
         implements CallbackController<BrightnessMirrorController.BrightnessMirrorListener> {
 
-    private final NotificationStackScrollLayout mStackScroller;
-    public long TRANSITION_DURATION_OUT = 150;
-    public long TRANSITION_DURATION_IN = 200;
+    private final static long TRANSITION_DURATION_OUT = 150;
+    private final static long TRANSITION_DURATION_IN = 200;
 
     private final StatusBarWindowView mStatusBarWindow;
-    private final ScrimController mScrimController;
+    private final NotificationStackScrollLayout mStackScroller;
+    private final Consumer<Boolean> mVisibilityCallback;
     private final View mNotificationPanel;
     private final ArraySet<BrightnessMirrorListener> mBrightnessMirrorListeners = new ArraySet<>();
     private final int[] mInt2Cache = new int[2];
     private View mBrightnessMirror;
 
     public BrightnessMirrorController(StatusBarWindowView statusBarWindow,
-            ScrimController scrimController) {
+            @NonNull Consumer<Boolean> visibilityCallback) {
         mStatusBarWindow = statusBarWindow;
         mBrightnessMirror = statusBarWindow.findViewById(R.id.brightness_mirror);
         mNotificationPanel = statusBarWindow.findViewById(R.id.notification_panel);
-        mStackScroller = (NotificationStackScrollLayout) statusBarWindow.findViewById(
-                R.id.notification_stack_scroller);
-        mScrimController = scrimController;
+        mStackScroller = statusBarWindow.findViewById(R.id.notification_stack_scroller);
+        mVisibilityCallback = visibilityCallback;
     }
 
     public void showMirror() {
         mBrightnessMirror.setVisibility(View.VISIBLE);
         mStackScroller.setFadingOut(true);
-        mScrimController.forceHideScrims(true /* hide */, true /* animated */);
+        mVisibilityCallback.accept(true);
         outAnimation(mNotificationPanel.animate())
                 .withLayer();
     }
 
     public void hideMirror() {
-        mScrimController.forceHideScrims(false /* hide */, true /* animated */);
+        mVisibilityCallback.accept(false);
         inAnimation(mNotificationPanel.animate())
                 .withLayer()
-                .withEndAction(new Runnable() {
-                    @Override
-                    public void run() {
-                        mBrightnessMirror.setVisibility(View.INVISIBLE);
-                        mStackScroller.setFadingOut(false);
-                    }
+                .withEndAction(() -> {
+                    mBrightnessMirror.setVisibility(View.INVISIBLE);
+                    mStackScroller.setFadingOut(false);
                 });
     }
 
@@ -128,9 +127,11 @@
     }
 
     private void reinflate() {
+        ContextThemeWrapper qsThemeContext =
+                new ContextThemeWrapper(mBrightnessMirror.getContext(), R.style.qs_theme);
         int index = mStatusBarWindow.indexOfChild(mBrightnessMirror);
         mStatusBarWindow.removeView(mBrightnessMirror);
-        mBrightnessMirror = LayoutInflater.from(mBrightnessMirror.getContext()).inflate(
+        mBrightnessMirror = LayoutInflater.from(qsThemeContext).inflate(
                 R.layout.brightness_mirror, mStatusBarWindow, false);
         mStatusBarWindow.addView(mBrightnessMirror, index);
 
diff --git a/com/android/systemui/statusbar/policy/RemoteInputView.java b/com/android/systemui/statusbar/policy/RemoteInputView.java
index 37b0de4..4fc5044 100644
--- a/com/android/systemui/statusbar/policy/RemoteInputView.java
+++ b/com/android/systemui/statusbar/policy/RemoteInputView.java
@@ -53,11 +53,9 @@
 import com.android.internal.logging.nano.MetricsProto;
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
-import com.android.systemui.statusbar.ExpandableView;
 import com.android.systemui.statusbar.NotificationData;
 import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.notification.NotificationViewWrapper;
-import com.android.systemui.statusbar.stack.ScrollContainer;
 import com.android.systemui.statusbar.stack.StackStateAnimator;
 
 /**
@@ -82,8 +80,6 @@
 
     private NotificationData.Entry mEntry;
 
-    private ScrollContainer mScrollContainer;
-    private View mScrollContainerChild;
     private boolean mRemoved;
 
     private int mRevealCx;
@@ -347,41 +343,16 @@
     @Override
     public boolean onInterceptTouchEvent(MotionEvent ev) {
         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
-            findScrollContainer();
-            if (mScrollContainer != null) {
-                mScrollContainer.requestDisallowLongPress();
-                mScrollContainer.requestDisallowDismiss();
-            }
+            mController.requestDisallowLongPressAndDismiss();
         }
         return super.onInterceptTouchEvent(ev);
     }
 
     public boolean requestScrollTo() {
-        findScrollContainer();
-        mScrollContainer.lockScrollTo(mScrollContainerChild);
+        mController.lockScrollTo(mEntry);
         return true;
     }
 
-    private void findScrollContainer() {
-        if (mScrollContainer == null) {
-            mScrollContainerChild = null;
-            ViewParent p = this;
-            while (p != null) {
-                if (mScrollContainerChild == null && p instanceof ExpandableView) {
-                    mScrollContainerChild = (View) p;
-                }
-                if (p.getParent() instanceof ScrollContainer) {
-                    mScrollContainer = (ScrollContainer) p.getParent();
-                    if (mScrollContainerChild == null) {
-                        mScrollContainerChild = (View) p;
-                    }
-                    break;
-                }
-                p = p.getParent();
-            }
-        }
-    }
-
     public boolean isActive() {
         return mEditText.isFocused() && mEditText.isEnabled();
     }
diff --git a/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java b/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
index 527addf..f5ae88b 100644
--- a/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
+++ b/com/android/systemui/statusbar/policy/UserInfoControllerImpl.java
@@ -154,7 +154,9 @@
                     avatar = new UserIconDrawable(avatarSize)
                             .setIcon(rawAvatar).setBadgeIfManagedUser(mContext, userId).bake();
                 } else {
-                    avatar = UserIcons.getDefaultUserIcon(isGuest? UserHandle.USER_NULL : userId,
+                    avatar = UserIcons.getDefaultUserIcon(
+                            context.getResources(),
+                            isGuest? UserHandle.USER_NULL : userId,
                             lightIcon);
                 }
 
diff --git a/com/android/systemui/statusbar/policy/UserSwitcherController.java b/com/android/systemui/statusbar/policy/UserSwitcherController.java
index 700c01a..7006d38 100644
--- a/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -747,7 +747,8 @@
             if (item.isAddUser) {
                 return context.getDrawable(R.drawable.ic_add_circle_qs);
             }
-            Drawable icon = UserIcons.getDefaultUserIcon(item.resolveId(), /* light= */ false);
+            Drawable icon = UserIcons.getDefaultUserIcon(
+                    context.getResources(), item.resolveId(), /* light= */ false);
             if (item.isGuest) {
                 icon.setColorFilter(Utils.getColorAttr(context, android.R.attr.colorForeground),
                         Mode.SRC_IN);
@@ -910,6 +911,7 @@
                     context.getString(android.R.string.cancel), this);
             setButton(DialogInterface.BUTTON_POSITIVE,
                     context.getString(R.string.guest_exit_guest_dialog_remove), this);
+            SystemUIDialog.setWindowOnTop(this);
             setCanceledOnTouchOutside(false);
             mGuestId = guestId;
             mTargetId = targetId;
@@ -937,6 +939,7 @@
                     context.getString(android.R.string.cancel), this);
             setButton(DialogInterface.BUTTON_POSITIVE,
                     context.getString(android.R.string.ok), this);
+            SystemUIDialog.setWindowOnTop(this);
         }
 
         @Override
@@ -957,7 +960,7 @@
                 }
                 int id = user.id;
                 Bitmap icon = UserIcons.convertToBitmap(UserIcons.getDefaultUserIcon(
-                        id, /* light= */ false));
+                        mContext.getResources(), id, /* light= */ false));
                 mUserManager.setUserIcon(id, icon);
                 switchToUserId(id);
             }
diff --git a/com/android/systemui/statusbar/stack/AnimationProperties.java b/com/android/systemui/statusbar/stack/AnimationProperties.java
index ebb0a6d..2f6e658 100644
--- a/com/android/systemui/statusbar/stack/AnimationProperties.java
+++ b/com/android/systemui/statusbar/stack/AnimationProperties.java
@@ -36,7 +36,12 @@
      * @return an animation filter for this animation.
      */
     public AnimationFilter getAnimationFilter() {
-        return new AnimationFilter();
+        return new AnimationFilter() {
+            @Override
+            public boolean shouldAnimateProperty(Property property) {
+                return true;
+            }
+        };
     }
 
     /**
diff --git a/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java b/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
index fe53104..c0241e3 100644
--- a/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
+++ b/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
@@ -1260,4 +1260,17 @@
     public boolean isUserLocked() {
         return mUserLocked;
     }
+
+    public void setCurrentBottomRoundness(float currentBottomRoundness) {
+        boolean last = true;
+        for (int i = mChildren.size() - 1; i >= 0; i--) {
+            ExpandableNotificationRow child = mChildren.get(i);
+            if (child.getVisibility() == View.GONE) {
+                continue;
+            }
+            float bottomRoundness = last ? currentBottomRoundness : 0.0f;
+            child.setBottomRoundness(bottomRoundness, isShown() /* animate */);
+            last = false;
+        }
+    }
 }
diff --git a/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java b/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
index efe049a..ebebfac 100644
--- a/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
+++ b/com/android/systemui/statusbar/stack/NotificationStackScrollLayout.java
@@ -30,6 +30,7 @@
 import android.content.res.Resources;
 import android.graphics.Canvas;
 import android.graphics.Paint;
+import android.graphics.Path;
 import android.graphics.PointF;
 import android.graphics.PorterDuff;
 import android.graphics.PorterDuffXfermode;
@@ -75,6 +76,7 @@
 import com.android.systemui.statusbar.DismissView;
 import com.android.systemui.statusbar.EmptyShadeView;
 import com.android.systemui.statusbar.ExpandableNotificationRow;
+import com.android.systemui.statusbar.ExpandableOutlineView;
 import com.android.systemui.statusbar.ExpandableView;
 import com.android.systemui.statusbar.NotificationData;
 import com.android.systemui.statusbar.NotificationGuts;
@@ -82,8 +84,10 @@
 import com.android.systemui.statusbar.NotificationSnooze;
 import com.android.systemui.statusbar.StackScrollerDecorView;
 import com.android.systemui.statusbar.StatusBarState;
+import com.android.systemui.statusbar.notification.AnimatableProperty;
 import com.android.systemui.statusbar.notification.FakeShadowView;
 import com.android.systemui.statusbar.notification.NotificationUtils;
+import com.android.systemui.statusbar.notification.PropertyAnimator;
 import com.android.systemui.statusbar.notification.VisibilityLocationProvider;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.StatusBar;
@@ -108,8 +112,7 @@
 public class NotificationStackScrollLayout extends ViewGroup
         implements SwipeHelper.Callback, ExpandHelper.Callback, ScrollAdapter,
         ExpandableView.OnHeightChangedListener, NotificationGroupManager.OnGroupChangeListener,
-        NotificationMenuRowPlugin.OnMenuEventListener, ScrollContainer,
-        VisibilityLocationProvider {
+        NotificationMenuRowPlugin.OnMenuEventListener, VisibilityLocationProvider {
 
     public static final float BACKGROUND_ALPHA_DIMMED = 0.7f;
     private static final String TAG = "StackScroller";
@@ -121,12 +124,23 @@
      * Sentinel value for no current active pointer. Used by {@link #mActivePointerId}.
      */
     private static final int INVALID_POINTER = -1;
+    private static final AnimatableProperty SIDE_PADDINGS = AnimatableProperty.from(
+            "sidePaddings",
+            NotificationStackScrollLayout::setCurrentSidePadding,
+            NotificationStackScrollLayout::getCurrentSidePadding,
+            R.id.side_padding_animator_tag,
+            R.id.side_padding_animator_end_tag,
+            R.id.side_padding_animator_start_tag);
+    private static final AnimationProperties SIDE_PADDING_PROPERTIES =
+            new AnimationProperties().setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
 
     private ExpandHelper mExpandHelper;
     private NotificationSwipeHelper mSwipeHelper;
     private boolean mSwipingInProgress;
     private int mCurrentStackHeight = Integer.MAX_VALUE;
     private final Paint mBackgroundPaint = new Paint();
+    private final Path mBackgroundPath = new Path();
+    private final float[] mBackgroundRadii = new float[8];
     private final boolean mShouldDrawNotificationBackground;
 
     private float mExpandedHeight;
@@ -157,6 +171,7 @@
     private int mTopPadding;
     private int mBottomMargin;
     private int mBottomInset = 0;
+    private float mCurrentSidePadding;
 
     /**
      * The algorithm which calculates the properties for our children
@@ -383,6 +398,9 @@
     private int mCachedBackgroundColor;
     private boolean mHeadsUpGoingAwayAnimationsAllowed = true;
     private Runnable mAnimateScroll = this::animateScroll;
+    private int mCornerRadius;
+    private int mLockscreenSidePaddings;
+    private int mSidePaddings;
 
     public NotificationStackScrollLayout(Context context) {
         this(context, null);
@@ -419,6 +437,8 @@
                 res.getBoolean(R.bool.config_fadeNotificationsOnDismiss);
 
         updateWillNotDraw();
+        mBackgroundPaint.setAntiAlias(true);
+        mBackgroundPaint.setStyle(Paint.Style.FILL);
         if (DEBUG) {
             mDebugPaint = new Paint();
             mDebugPaint.setColor(0xffff0000);
@@ -466,8 +486,7 @@
     protected void onDraw(Canvas canvas) {
         if (mShouldDrawNotificationBackground && !mAmbientState.isDark()
                 && mCurrentBounds.top < mCurrentBounds.bottom) {
-            canvas.drawRect(0, mCurrentBounds.top, getWidth(), mCurrentBounds.bottom,
-                    mBackgroundPaint);
+            canvas.drawPath(mBackgroundPath, mBackgroundPaint);
         }
 
         if (DEBUG) {
@@ -520,8 +539,12 @@
                 R.dimen.min_top_overscroll_to_qs);
         mStatusBarHeight = res.getDimensionPixelOffset(R.dimen.status_bar_height);
         mBottomMargin = res.getDimensionPixelSize(R.dimen.notification_panel_margin_bottom);
+        mLockscreenSidePaddings = res.getDimensionPixelSize(
+                R.dimen.notification_lockscreen_side_paddings);
         mMinInteractionHeight = res.getDimensionPixelSize(
                 R.dimen.notification_min_interaction_height);
+        mCornerRadius = res.getDimensionPixelSize(
+                Utils.getThemeAttr(mContext, android.R.attr.dialogCornerRadius));
     }
 
     public void setDrawBackgroundAsSrc(boolean asSrc) {
@@ -1195,7 +1218,6 @@
         mScrollingEnabled = enable;
     }
 
-    @Override
     public void lockScrollTo(View v) {
         if (mForcedScroll == v) {
             return;
@@ -1204,7 +1226,6 @@
         scrollTo(v);
     }
 
-    @Override
     public boolean scrollTo(View v) {
         ExpandableView expandableView = (ExpandableView) v;
         int positionInLinearLayout = getPositionInLinearLayout(v);
@@ -2221,9 +2242,31 @@
         mScrimController.setExcludedBackgroundArea(
                 mFadingOut || mParentNotFullyVisible || mAmbientState.isDark() || mIsClipped ? null
                         : mCurrentBounds);
+        updateBackgroundPath();
         invalidate();
     }
 
+    private void updateBackgroundPath() {
+        mBackgroundPath.reset();
+        float topRoundness = 0;
+        if (mFirstVisibleBackgroundChild != null) {
+            topRoundness = mFirstVisibleBackgroundChild.getCurrentBackgroundRadiusTop();
+        }
+        topRoundness = onKeyguard() ? mCornerRadius : topRoundness;
+        float bottomRoundNess = mCornerRadius;
+        mBackgroundRadii[0] = topRoundness;
+        mBackgroundRadii[1] = topRoundness;
+        mBackgroundRadii[2] = topRoundness;
+        mBackgroundRadii[3] = topRoundness;
+        mBackgroundRadii[4] = bottomRoundNess;
+        mBackgroundRadii[5] = bottomRoundNess;
+        mBackgroundRadii[6] = bottomRoundNess;
+        mBackgroundRadii[7] = bottomRoundNess;
+        mBackgroundPath.addRoundRect(mCurrentSidePadding, mCurrentBounds.top,
+                getWidth() - mCurrentSidePadding, mCurrentBounds.bottom, mBackgroundRadii,
+                Path.Direction.CCW);
+    }
+
     /**
      * Update the background bounds to the new desired bounds
      */
@@ -2236,6 +2279,8 @@
             mBackgroundBounds.left = mTempInt2[0];
             mBackgroundBounds.right = mTempInt2[0] + getWidth();
         }
+        mBackgroundBounds.left += mCurrentSidePadding;
+        mBackgroundBounds.right -= mCurrentSidePadding;
         if (!mIsExpanded) {
             mBackgroundBounds.top = 0;
             mBackgroundBounds.bottom = 0;
@@ -2820,16 +2865,45 @@
     private void updateFirstAndLastBackgroundViews() {
         ActivatableNotificationView firstChild = getFirstChildWithBackground();
         ActivatableNotificationView lastChild = getLastChildWithBackground();
+        boolean firstChanged = firstChild != mFirstVisibleBackgroundChild;
+        boolean lastChanged = lastChild != mLastVisibleBackgroundChild;
         if (mAnimationsEnabled && mIsExpanded) {
-            mAnimateNextBackgroundTop = firstChild != mFirstVisibleBackgroundChild;
-            mAnimateNextBackgroundBottom = lastChild != mLastVisibleBackgroundChild;
+            mAnimateNextBackgroundTop = firstChanged;
+            mAnimateNextBackgroundBottom = lastChanged;
         } else {
             mAnimateNextBackgroundTop = false;
             mAnimateNextBackgroundBottom = false;
         }
+        if (firstChanged && mFirstVisibleBackgroundChild != null
+                && !mFirstVisibleBackgroundChild.isRemoved()) {
+            mFirstVisibleBackgroundChild.setTopRoundness(0.0f,
+                    mFirstVisibleBackgroundChild.isShown());
+        }
+        if (lastChanged && mLastVisibleBackgroundChild != null
+                && !mLastVisibleBackgroundChild.isRemoved()) {
+            mLastVisibleBackgroundChild.setBottomRoundness(0.0f,
+                    mLastVisibleBackgroundChild.isShown());
+        }
         mFirstVisibleBackgroundChild = firstChild;
         mLastVisibleBackgroundChild = lastChild;
         mAmbientState.setLastVisibleBackgroundChild(lastChild);
+        applyRoundedNess();
+    }
+
+    private void applyRoundedNess() {
+        if (mFirstVisibleBackgroundChild != null) {
+            mFirstVisibleBackgroundChild.setTopRoundness(
+                    mStatusBarState == StatusBarState.KEYGUARD ? 1.0f : 0.0f,
+                    mFirstVisibleBackgroundChild.isShown()
+                            && !mChildrenToAddAnimated.contains(mFirstVisibleBackgroundChild));
+        }
+        if (mLastVisibleBackgroundChild != null) {
+            mLastVisibleBackgroundChild.setBottomRoundness(1.0f,
+                    mLastVisibleBackgroundChild.isShown()
+                            && !mChildrenToAddAnimated.contains(mLastVisibleBackgroundChild));
+        }
+        updateBackgroundPath();
+        invalidate();
     }
 
     private void onViewAddedInternal(View child) {
@@ -2838,6 +2912,7 @@
         generateAddAnimation(child, false /* fromMoreCard */);
         updateAnimationState(child);
         updateChronometerForChild(child);
+        updateCurrentSidePaddings(child);
     }
 
     private void updateHideSensitiveForChild(View child) {
@@ -3321,12 +3396,10 @@
         }
     }
 
-    @Override
     public void requestDisallowLongPress() {
         cancelLongPress();
     }
 
-    @Override
     public void requestDisallowDismiss() {
         mDisallowDismissInThisMotion = true;
     }
@@ -4285,6 +4358,43 @@
     public void setStatusBarState(int statusBarState) {
         mStatusBarState = statusBarState;
         mAmbientState.setStatusBarState(statusBarState);
+        applyRoundedNess();
+        updateSidePaddings();
+    }
+
+    private void updateSidePaddings() {
+        int sidePaddings = mStatusBarState == StatusBarState.KEYGUARD ? mLockscreenSidePaddings : 0;
+        if (sidePaddings != mSidePaddings) {
+            boolean animate = isShown();
+            mSidePaddings = sidePaddings;
+            PropertyAnimator.setProperty(this, SIDE_PADDINGS, sidePaddings,
+                    SIDE_PADDING_PROPERTIES, animate);
+        }
+    }
+
+    protected void setCurrentSidePadding(float sidePadding) {
+        mCurrentSidePadding = sidePadding;
+        updateBackground();
+        applySidePaddingsToChildren();
+    }
+
+    private void applySidePaddingsToChildren() {
+        for (int i = 0; i < getChildCount(); i++) {
+            View view = getChildAt(i);
+            updateCurrentSidePaddings(view);
+        }
+    }
+
+    private void updateCurrentSidePaddings(View view) {
+        if (!(view instanceof ExpandableOutlineView)) {
+            return;
+        }
+        ExpandableOutlineView outlineView = (ExpandableOutlineView) view;
+        outlineView.setCurrentSidePaddings(mCurrentSidePadding);
+    }
+
+    protected float getCurrentSidePadding() {
+        return mCurrentSidePadding;
     }
 
     public void setExpandingVelocity(float expandingVelocity) {
diff --git a/com/android/systemui/statusbar/stack/ScrollContainer.java b/com/android/systemui/statusbar/stack/ScrollContainer.java
deleted file mode 100644
index b9d12ce..0000000
--- a/com/android/systemui/statusbar/stack/ScrollContainer.java
+++ /dev/null
@@ -1,48 +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.systemui.statusbar.stack;
-
-import android.view.View;
-
-/**
- * Interface for container layouts that scroll and listen for long presses. A child that
- * wants to handle long press can use this to cancel the parents long press logic or request
- * to be made visible by scrolling to it.
- */
-public interface ScrollContainer {
-    /**
-     * Request that the view does not perform long press for the current touch.
-     */
-    void requestDisallowLongPress();
-
-    /**
-     * Request that the view is made visible by scrolling to it.
-     * Return true if it scrolls.
-     */
-    boolean scrollTo(View v);
-
-    /**
-     * Like {@link #scrollTo(View)}, but keeps the scroll locked until the user
-     * scrolls, or {@param v} loses focus or is detached.
-     */
-    void lockScrollTo(View v);
-
-    /**
-     * Request that the view does not dismiss for the current touch.
-     */
-    void requestDisallowDismiss();
-}
diff --git a/com/android/systemui/statusbar/stack/ViewState.java b/com/android/systemui/statusbar/stack/ViewState.java
index 27b730c..682b849 100644
--- a/com/android/systemui/statusbar/stack/ViewState.java
+++ b/com/android/systemui/statusbar/stack/ViewState.java
@@ -21,7 +21,6 @@
 import android.animation.ObjectAnimator;
 import android.animation.PropertyValuesHolder;
 import android.animation.ValueAnimator;
-import android.app.Notification;
 import android.util.Property;
 import android.view.View;
 import android.view.animation.Interpolator;
@@ -29,7 +28,7 @@
 import com.android.systemui.Interpolators;
 import com.android.systemui.R;
 import com.android.systemui.statusbar.ExpandableView;
-import com.android.systemui.statusbar.NotificationShelf;
+import com.android.systemui.statusbar.notification.AnimatableProperty;
 import com.android.systemui.statusbar.notification.PropertyAnimator;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 
@@ -64,8 +63,8 @@
     private static final int TAG_START_TRANSLATION_Z = R.id.translation_z_animator_start_value_tag;
     private static final int TAG_START_ALPHA = R.id.alpha_animator_start_value_tag;
 
-    private static final PropertyAnimator.AnimatableProperty SCALE_X_PROPERTY
-            = new PropertyAnimator.AnimatableProperty() {
+    private static final AnimatableProperty SCALE_X_PROPERTY
+            = new AnimatableProperty() {
 
         @Override
         public int getAnimationStartTag() {
@@ -88,8 +87,8 @@
         }
     };
 
-    private static final PropertyAnimator.AnimatableProperty SCALE_Y_PROPERTY
-            = new PropertyAnimator.AnimatableProperty() {
+    private static final AnimatableProperty SCALE_Y_PROPERTY
+            = new AnimatableProperty() {
 
         @Override
         public int getAnimationStartTag() {
@@ -251,7 +250,7 @@
         return getChildTag(view, tag) != null;
     }
 
-    public static boolean isAnimating(View view, PropertyAnimator.AnimatableProperty property) {
+    public static boolean isAnimating(View view, AnimatableProperty property) {
         return getChildTag(view, property.getAnimatorTag()) != null;
     }
 
@@ -403,7 +402,7 @@
         startZTranslationAnimation(view, NO_NEW_ANIMATIONS);
     }
 
-    private void updateAnimation(View view, PropertyAnimator.AnimatableProperty property,
+    private void updateAnimation(View view, AnimatableProperty property,
             float endValue) {
         PropertyAnimator.startAnimation(view, property, endValue, NO_NEW_ANIMATIONS);
     }
diff --git a/com/android/systemui/usb/UsbPermissionActivity.java b/com/android/systemui/usb/UsbPermissionActivity.java
index 87d11b2..4606aee 100644
--- a/com/android/systemui/usb/UsbPermissionActivity.java
+++ b/com/android/systemui/usb/UsbPermissionActivity.java
@@ -235,7 +235,7 @@
                 intent.putExtra(UsbManager.EXTRA_DEVICE, mDevice);
                 if (mPermissionGranted) {
                     service.grantDevicePermission(mDevice, mUid);
-                    if (mAlwaysUse.isChecked()) {
+                    if (mAlwaysUse != null && mAlwaysUse.isChecked()) {
                         final int userId = UserHandle.getUserId(mUid);
                         service.setDevicePackage(mDevice, mPackageName, userId);
                     }
@@ -245,7 +245,7 @@
                 intent.putExtra(UsbManager.EXTRA_ACCESSORY, mAccessory);
                 if (mPermissionGranted) {
                     service.grantAccessoryPermission(mAccessory, mUid);
-                    if (mAlwaysUse.isChecked()) {
+                    if (mAlwaysUse != null && mAlwaysUse.isChecked()) {
                         final int userId = UserHandle.getUserId(mUid);
                         service.setAccessoryPackage(mAccessory, mPackageName, userId);
                     }
diff --git a/com/android/systemui/volume/VolumeDialogImpl.java b/com/android/systemui/volume/VolumeDialogImpl.java
index 4b8f581..383d327 100644
--- a/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/com/android/systemui/volume/VolumeDialogImpl.java
@@ -207,6 +207,7 @@
         } else {
             addExistingRows();
         }
+        updateRowsH(getActiveRow());
     }
 
     private ColorStateList loadColorStateList(int colorResId) {
@@ -440,7 +441,8 @@
                 .withEndAction(() -> mDialog.dismiss())
                 .setInterpolator(new SystemUIInterpolators.LogAccelerateInterpolator())
                 .start();
-        if (mAccessibilityMgr.isEnabled()) {
+        if (mAccessibilityMgr.isObservedEventType(
+                AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED)) {
             AccessibilityEvent event =
                     AccessibilityEvent.obtain(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
             event.setPackageName(mContext.getPackageName());
diff --git a/com/android/uiautomator/testrunner/UiAutomatorTestCase.java b/com/android/uiautomator/testrunner/UiAutomatorTestCase.java
index 3d5476d..7c9aede 100644
--- a/com/android/uiautomator/testrunner/UiAutomatorTestCase.java
+++ b/com/android/uiautomator/testrunner/UiAutomatorTestCase.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2012 The Android Open Source Project
+ * Copyright (C) 2013 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,55 +16,24 @@
 
 package com.android.uiautomator.testrunner;
 
-import android.content.Context;
+import android.app.Instrumentation;
 import android.os.Bundle;
-import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.os.SystemClock;
-import android.view.inputmethod.InputMethodInfo;
+import android.test.InstrumentationTestCase;
 
-import com.android.internal.view.IInputMethodManager;
+import com.android.uiautomator.core.InstrumentationUiAutomatorBridge;
 import com.android.uiautomator.core.UiDevice;
 
-import junit.framework.TestCase;
-
-import java.util.List;
-
 /**
- * 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
+ * UI Automator test case that is executed on the device.
  * @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 TestCase {
+public class UiAutomatorTestCase extends InstrumentationTestCase {
 
-    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
@@ -72,7 +41,7 @@
      * @since API Level 16
      */
     public UiDevice getUiDevice() {
-        return mUiDevice;
+        return UiDevice.getInstance();
     }
 
     /**
@@ -85,34 +54,43 @@
         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;
     }
 
     /**
-     * package private
-     * @param uiDevice
+     * Initializes this test case.
+     *
+     * @param params Instrumentation arguments.
      */
-    void setUiDevice(UiDevice uiDevice) {
-        mUiDevice = uiDevice;
-    }
-
-    /**
-     * package private
-     * @param params
-     */
-    void setParams(Bundle params) {
+    void initialize(Bundle params) {
         mParams = params;
-    }
 
-    void setAutomationSupport(IAutomationSupport automationSupport) {
-        mAutomationSupport = automationSupport;
+        // 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()));
     }
 
     /**
@@ -123,28 +101,4 @@
     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.
-    }
 }