Merge "Brightness mirror should use QS theme"
diff --git a/Android.mk b/Android.mk
index 08a8b64..f4a9ff9 100644
--- a/Android.mk
+++ b/Android.mk
@@ -521,10 +521,12 @@
 	telephony/java/com/android/ims/internal/IImsEcbmListener.aidl \
         telephony/java/com/android/ims/internal/IImsExternalCallStateListener.aidl \
         telephony/java/com/android/ims/internal/IImsFeatureStatusCallback.aidl \
+        telephony/java/com/android/ims/internal/IImsMMTelFeature.aidl \
         telephony/java/com/android/ims/internal/IImsMultiEndpoint.aidl \
+	telephony/java/com/android/ims/internal/IImsRcsFeature.aidl \
 	telephony/java/com/android/ims/internal/IImsService.aidl \
 	telephony/java/com/android/ims/internal/IImsServiceController.aidl \
-	telephony/java/com/android/ims/internal/IImsServiceFeatureListener.aidl \
+	telephony/java/com/android/ims/internal/IImsServiceFeatureCallback.aidl \
 	telephony/java/com/android/ims/internal/IImsStreamMediaSession.aidl \
 	telephony/java/com/android/ims/internal/IImsUt.aidl \
 	telephony/java/com/android/ims/internal/IImsUtListener.aidl \
diff --git a/api/current.txt b/api/current.txt
index dca7528..f59bbf7 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -2777,6 +2777,7 @@
     field public static final int GESTURE_SWIPE_UP_AND_RIGHT = 14; // 0xe
     field public static final int GLOBAL_ACTION_BACK = 1; // 0x1
     field public static final int GLOBAL_ACTION_HOME = 2; // 0x2
+    field public static final int GLOBAL_ACTION_LOCK_SCREEN = 8; // 0x8
     field public static final int GLOBAL_ACTION_NOTIFICATIONS = 4; // 0x4
     field public static final int GLOBAL_ACTION_POWER_DIALOG = 6; // 0x6
     field public static final int GLOBAL_ACTION_QUICK_SETTINGS = 5; // 0x5
@@ -5616,6 +5617,7 @@
     method public final int getCurrentInterruptionFilter();
     method public int getImportance();
     method public android.app.NotificationChannel getNotificationChannel(java.lang.String);
+    method public android.app.NotificationChannelGroup getNotificationChannelGroup(java.lang.String);
     method public java.util.List<android.app.NotificationChannelGroup> getNotificationChannelGroups();
     method public java.util.List<android.app.NotificationChannel> getNotificationChannels();
     method public android.app.NotificationManager.Policy getNotificationPolicy();
@@ -5628,8 +5630,12 @@
     method public void setNotificationPolicy(android.app.NotificationManager.Policy);
     method public boolean updateAutomaticZenRule(java.lang.String, android.app.AutomaticZenRule);
     field public static final java.lang.String ACTION_INTERRUPTION_FILTER_CHANGED = "android.app.action.INTERRUPTION_FILTER_CHANGED";
+    field public static final java.lang.String ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED = "android.app.action.NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED";
+    field public static final java.lang.String ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED = "android.app.action.NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED";
     field public static final java.lang.String ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED = "android.app.action.NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED";
     field public static final java.lang.String ACTION_NOTIFICATION_POLICY_CHANGED = "android.app.action.NOTIFICATION_POLICY_CHANGED";
+    field public static final java.lang.String EXTRA_BLOCKED_STATE = "android.app.extra.BLOCKED_STATE";
+    field public static final java.lang.String EXTRA_BLOCK_STATE_CHANGED_ID = "android.app.extra.BLOCK_STATE_CHANGED_ID";
     field public static final int IMPORTANCE_DEFAULT = 3; // 0x3
     field public static final int IMPORTANCE_HIGH = 4; // 0x4
     field public static final int IMPORTANCE_LOW = 2; // 0x2
@@ -6371,6 +6377,7 @@
     method public boolean installCaCert(android.content.ComponentName, byte[]);
     method public boolean installKeyPair(android.content.ComponentName, java.security.PrivateKey, java.security.cert.Certificate, java.lang.String);
     method public boolean installKeyPair(android.content.ComponentName, java.security.PrivateKey, java.security.cert.Certificate[], java.lang.String, boolean);
+    method public boolean installKeyPair(android.content.ComponentName, java.security.PrivateKey, java.security.cert.Certificate[], java.lang.String, boolean, boolean);
     method public boolean isActivePasswordSufficient();
     method public boolean isAdminActive(android.content.ComponentName);
     method public boolean isApplicationHidden(android.content.ComponentName, java.lang.String);
@@ -6884,6 +6891,7 @@
     method public android.app.job.JobInfo.Builder setClipData(android.content.ClipData, int);
     method public android.app.job.JobInfo.Builder setEstimatedNetworkBytes(long);
     method public android.app.job.JobInfo.Builder setExtras(android.os.PersistableBundle);
+    method public android.app.job.JobInfo.Builder setImportantWhileForeground(boolean);
     method public android.app.job.JobInfo.Builder setMinimumLatency(long);
     method public android.app.job.JobInfo.Builder setOverrideDeadline(long);
     method public android.app.job.JobInfo.Builder setPeriodic(long);
@@ -23045,6 +23053,7 @@
 
   public static final class MediaMuxer.OutputFormat {
     field public static final int MUXER_OUTPUT_3GPP = 2; // 0x2
+    field public static final int MUXER_OUTPUT_HEIF = 3; // 0x3
     field public static final int MUXER_OUTPUT_MPEG_4 = 0; // 0x0
     field public static final int MUXER_OUTPUT_WEBM = 1; // 0x1
   }
@@ -25854,7 +25863,6 @@
     method public void removeTransportModeTransform(java.io.FileDescriptor, android.net.IpSecTransform) throws java.io.IOException;
     method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(int, java.net.InetAddress) throws android.net.IpSecManager.ResourceUnavailableException;
     method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(int, java.net.InetAddress, int) throws android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
-    field public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; // 0x0
   }
 
   public static final class IpSecManager.ResourceUnavailableException extends android.util.AndroidException {
@@ -30830,6 +30838,7 @@
     field public static final int BATTERY_STATUS_FULL = 5; // 0x5
     field public static final int BATTERY_STATUS_NOT_CHARGING = 4; // 0x4
     field public static final int BATTERY_STATUS_UNKNOWN = 1; // 0x1
+    field public static final java.lang.String EXTRA_BATTERY_LOW = "battery_low";
     field public static final java.lang.String EXTRA_HEALTH = "health";
     field public static final java.lang.String EXTRA_ICON_SMALL = "icon-small";
     field public static final java.lang.String EXTRA_LEVEL = "level";
diff --git a/api/system-current.txt b/api/system-current.txt
index aa3a1e2..67e4586 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -2919,6 +2919,7 @@
     field public static final int GESTURE_SWIPE_UP_AND_RIGHT = 14; // 0xe
     field public static final int GLOBAL_ACTION_BACK = 1; // 0x1
     field public static final int GLOBAL_ACTION_HOME = 2; // 0x2
+    field public static final int GLOBAL_ACTION_LOCK_SCREEN = 8; // 0x8
     field public static final int GLOBAL_ACTION_NOTIFICATIONS = 4; // 0x4
     field public static final int GLOBAL_ACTION_POWER_DIALOG = 6; // 0x6
     field public static final int GLOBAL_ACTION_QUICK_SETTINGS = 5; // 0x5
@@ -5825,6 +5826,7 @@
     method public final int getCurrentInterruptionFilter();
     method public int getImportance();
     method public android.app.NotificationChannel getNotificationChannel(java.lang.String);
+    method public android.app.NotificationChannelGroup getNotificationChannelGroup(java.lang.String);
     method public java.util.List<android.app.NotificationChannelGroup> getNotificationChannelGroups();
     method public java.util.List<android.app.NotificationChannel> getNotificationChannels();
     method public android.app.NotificationManager.Policy getNotificationPolicy();
@@ -5837,8 +5839,12 @@
     method public void setNotificationPolicy(android.app.NotificationManager.Policy);
     method public boolean updateAutomaticZenRule(java.lang.String, android.app.AutomaticZenRule);
     field public static final java.lang.String ACTION_INTERRUPTION_FILTER_CHANGED = "android.app.action.INTERRUPTION_FILTER_CHANGED";
+    field public static final java.lang.String ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED = "android.app.action.NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED";
+    field public static final java.lang.String ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED = "android.app.action.NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED";
     field public static final java.lang.String ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED = "android.app.action.NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED";
     field public static final java.lang.String ACTION_NOTIFICATION_POLICY_CHANGED = "android.app.action.NOTIFICATION_POLICY_CHANGED";
+    field public static final java.lang.String EXTRA_BLOCKED_STATE = "android.app.extra.BLOCKED_STATE";
+    field public static final java.lang.String EXTRA_BLOCK_STATE_CHANGED_ID = "android.app.extra.BLOCK_STATE_CHANGED_ID";
     field public static final int IMPORTANCE_DEFAULT = 3; // 0x3
     field public static final int IMPORTANCE_HIGH = 4; // 0x4
     field public static final int IMPORTANCE_LOW = 2; // 0x2
@@ -6599,6 +6605,7 @@
     method public boolean installCaCert(android.content.ComponentName, byte[]);
     method public boolean installKeyPair(android.content.ComponentName, java.security.PrivateKey, java.security.cert.Certificate, java.lang.String);
     method public boolean installKeyPair(android.content.ComponentName, java.security.PrivateKey, java.security.cert.Certificate[], java.lang.String, boolean);
+    method public boolean installKeyPair(android.content.ComponentName, java.security.PrivateKey, java.security.cert.Certificate[], java.lang.String, boolean, boolean);
     method public boolean isActivePasswordSufficient();
     method public boolean isAdminActive(android.content.ComponentName);
     method public boolean isApplicationHidden(android.content.ComponentName, java.lang.String);
@@ -7327,6 +7334,7 @@
     method public android.app.job.JobInfo.Builder setClipData(android.content.ClipData, int);
     method public android.app.job.JobInfo.Builder setEstimatedNetworkBytes(long);
     method public android.app.job.JobInfo.Builder setExtras(android.os.PersistableBundle);
+    method public android.app.job.JobInfo.Builder setImportantWhileForeground(boolean);
     method public android.app.job.JobInfo.Builder setMinimumLatency(long);
     method public android.app.job.JobInfo.Builder setOverrideDeadline(long);
     method public android.app.job.JobInfo.Builder setPeriodic(long);
@@ -24939,6 +24947,7 @@
 
   public static final class MediaMuxer.OutputFormat {
     field public static final int MUXER_OUTPUT_3GPP = 2; // 0x2
+    field public static final int MUXER_OUTPUT_HEIF = 3; // 0x3
     field public static final int MUXER_OUTPUT_MPEG_4 = 0; // 0x0
     field public static final int MUXER_OUTPUT_WEBM = 1; // 0x1
   }
@@ -28100,7 +28109,6 @@
     method public void removeTransportModeTransform(java.io.FileDescriptor, android.net.IpSecTransform) throws java.io.IOException;
     method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(int, java.net.InetAddress) throws android.net.IpSecManager.ResourceUnavailableException;
     method public android.net.IpSecManager.SecurityParameterIndex reserveSecurityParameterIndex(int, java.net.InetAddress, int) throws android.net.IpSecManager.ResourceUnavailableException, android.net.IpSecManager.SpiUnavailableException;
-    field public static final int INVALID_SECURITY_PARAMETER_INDEX = 0; // 0x0
   }
 
   public static final class IpSecManager.ResourceUnavailableException extends android.util.AndroidException {
@@ -33562,6 +33570,7 @@
     field public static final int BATTERY_STATUS_FULL = 5; // 0x5
     field public static final int BATTERY_STATUS_NOT_CHARGING = 4; // 0x4
     field public static final int BATTERY_STATUS_UNKNOWN = 1; // 0x1
+    field public static final java.lang.String EXTRA_BATTERY_LOW = "battery_low";
     field public static final java.lang.String EXTRA_HEALTH = "health";
     field public static final java.lang.String EXTRA_ICON_SMALL = "icon-small";
     field public static final java.lang.String EXTRA_LEVEL = "level";
diff --git a/api/test-current.txt b/api/test-current.txt
index 57b5222..2eca72f 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -2777,6 +2777,7 @@
     field public static final int GESTURE_SWIPE_UP_AND_RIGHT = 14; // 0xe
     field public static final int GLOBAL_ACTION_BACK = 1; // 0x1
     field public static final int GLOBAL_ACTION_HOME = 2; // 0x2
+    field public static final int GLOBAL_ACTION_LOCK_SCREEN = 8; // 0x8
     field public static final int GLOBAL_ACTION_NOTIFICATIONS = 4; // 0x4
     field public static final int GLOBAL_ACTION_POWER_DIALOG = 6; // 0x6
     field public static final int GLOBAL_ACTION_QUICK_SETTINGS = 5; // 0x5
@@ -5647,6 +5648,7 @@
     method public android.content.ComponentName getEffectsSuppressor();
     method public int getImportance();
     method public android.app.NotificationChannel getNotificationChannel(java.lang.String);
+    method public android.app.NotificationChannelGroup getNotificationChannelGroup(java.lang.String);
     method public java.util.List<android.app.NotificationChannelGroup> getNotificationChannelGroups();
     method public java.util.List<android.app.NotificationChannel> getNotificationChannels();
     method public android.app.NotificationManager.Policy getNotificationPolicy();
@@ -5659,8 +5661,12 @@
     method public void setNotificationPolicy(android.app.NotificationManager.Policy);
     method public boolean updateAutomaticZenRule(java.lang.String, android.app.AutomaticZenRule);
     field public static final java.lang.String ACTION_INTERRUPTION_FILTER_CHANGED = "android.app.action.INTERRUPTION_FILTER_CHANGED";
+    field public static final java.lang.String ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED = "android.app.action.NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED";
+    field public static final java.lang.String ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED = "android.app.action.NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED";
     field public static final java.lang.String ACTION_NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED = "android.app.action.NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED";
     field public static final java.lang.String ACTION_NOTIFICATION_POLICY_CHANGED = "android.app.action.NOTIFICATION_POLICY_CHANGED";
+    field public static final java.lang.String EXTRA_BLOCKED_STATE = "android.app.extra.BLOCKED_STATE";
+    field public static final java.lang.String EXTRA_BLOCK_STATE_CHANGED_ID = "android.app.extra.BLOCK_STATE_CHANGED_ID";
     field public static final int IMPORTANCE_DEFAULT = 3; // 0x3
     field public static final int IMPORTANCE_HIGH = 4; // 0x4
     field public static final int IMPORTANCE_LOW = 2; // 0x2
@@ -6440,6 +6446,7 @@
     method public boolean installCaCert(android.content.ComponentName, byte[]);
     method public boolean installKeyPair(android.content.ComponentName, java.security.PrivateKey, java.security.cert.Certificate, java.lang.String);
     method public boolean installKeyPair(android.content.ComponentName, java.security.PrivateKey, java.security.cert.Certificate[], java.lang.String, boolean);
+    method public boolean installKeyPair(android.content.ComponentName, java.security.PrivateKey, java.security.cert.Certificate[], java.lang.String, boolean, boolean);
     method public boolean isActivePasswordSufficient();
     method public boolean isAdminActive(android.content.ComponentName);
     method public boolean isApplicationHidden(android.content.ComponentName, java.lang.String);
@@ -6958,6 +6965,7 @@
     method public android.app.job.JobInfo.Builder setClipData(android.content.ClipData, int);
     method public android.app.job.JobInfo.Builder setEstimatedNetworkBytes(long);
     method public android.app.job.JobInfo.Builder setExtras(android.os.PersistableBundle);
+    method public android.app.job.JobInfo.Builder setImportantWhileForeground(boolean);
     method public android.app.job.JobInfo.Builder setMinimumLatency(long);
     method public android.app.job.JobInfo.Builder setOverrideDeadline(long);
     method public android.app.job.JobInfo.Builder setPeriodic(long);
@@ -23248,6 +23256,7 @@
 
   public static final class MediaMuxer.OutputFormat {
     field public static final int MUXER_OUTPUT_3GPP = 2; // 0x2
+    field public static final int MUXER_OUTPUT_HEIF = 3; // 0x3
     field public static final int MUXER_OUTPUT_MPEG_4 = 0; // 0x0
     field public static final int MUXER_OUTPUT_WEBM = 1; // 0x1
   }
@@ -31033,6 +31042,7 @@
     field public static final int BATTERY_STATUS_FULL = 5; // 0x5
     field public static final int BATTERY_STATUS_NOT_CHARGING = 4; // 0x4
     field public static final int BATTERY_STATUS_UNKNOWN = 1; // 0x1
+    field public static final java.lang.String EXTRA_BATTERY_LOW = "battery_low";
     field public static final java.lang.String EXTRA_HEALTH = "health";
     field public static final java.lang.String EXTRA_ICON_SMALL = "icon-small";
     field public static final java.lang.String EXTRA_LEVEL = "level";
diff --git a/cmds/pm/Android.mk b/cmds/pm/Android.mk
index 6a03def..960c805 100644
--- a/cmds/pm/Android.mk
+++ b/cmds/pm/Android.mk
@@ -3,14 +3,8 @@
 LOCAL_PATH:= $(call my-dir)
 
 include $(CLEAR_VARS)
-LOCAL_SRC_FILES := $(call all-subdir-java-files)
-LOCAL_MODULE := pmlib
-LOCAL_MODULE_STEM := pm
-include $(BUILD_JAVA_LIBRARY)
-
-include $(CLEAR_VARS)
 LOCAL_MODULE := pm
-LOCAL_MODULE_CLASS := EXECUTABLES
 LOCAL_SRC_FILES := pm
-LOCAL_REQUIRED_MODULES := pmlib
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_MODULE_TAGS := optional
 include $(BUILD_PREBUILT)
diff --git a/cmds/pm/pm b/cmds/pm/pm
index 53f85b2..4d1f945 100755
--- a/cmds/pm/pm
+++ b/cmds/pm/pm
@@ -1,8 +1,2 @@
 #!/system/bin/sh
-# Script to start "pm" on the device, which has a very rudimentary
-# shell.
-#
-base=/system
-export CLASSPATH=$base/framework/pm.jar
-exec app_process $base/bin com.android.commands.pm.Pm "$@"
-
+cmd package "$@"
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
deleted file mode 100644
index 9490880..0000000
--- a/cmds/pm/src/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/cmds/statsd/Android.mk b/cmds/statsd/Android.mk
index 1b2f9da..fd9465d 100644
--- a/cmds/statsd/Android.mk
+++ b/cmds/statsd/Android.mk
@@ -55,7 +55,8 @@
     src/storage/DropboxWriter.cpp \
     src/StatsLogProcessor.cpp \
     src/StatsService.cpp \
-    src/stats_util.cpp
+    src/stats_util.cpp \
+    src/guardrail/MemoryLeakTrackUtil.cpp
 
 statsd_common_c_includes := \
     $(LOCAL_PATH)/src \
@@ -82,7 +83,8 @@
     libhidltransport \
     libhwbinder \
     android.hardware.power@1.0 \
-    android.hardware.power@1.1
+    android.hardware.power@1.1 \
+    libmemunreachable
 
 # =========
 # statsd
@@ -178,3 +180,7 @@
 statsd_common_c_includes:=
 
 include $(BUILD_NATIVE_TEST)
+
+##############################
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index 1a056df..65b3da1 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -17,10 +17,11 @@
 #define DEBUG true
 #include "Log.h"
 
-#include "android-base/stringprintf.h"
 #include "StatsService.h"
+#include "android-base/stringprintf.h"
 #include "config/ConfigKey.h"
 #include "config/ConfigManager.h"
+#include "guardrail/MemoryLeakTrackUtil.h"
 #include "storage/DropboxReader.h"
 
 #include <android-base/file.h>
@@ -226,6 +227,10 @@
         if (!args[0].compare(String8("clear-config"))) {
             return cmd_remove_config_files(out);
         }
+
+        if (!args[0].compare(String8("meminfo"))) {
+            return cmd_dump_memory_info(out);
+        }
     }
 
     print_cmd_help(out);
@@ -238,6 +243,15 @@
             "[timestamp_nsec_optional]\n");
     fprintf(out, "\n");
     fprintf(out, "\n");
+    fprintf(out, "usage: adb shell cmd stats meminfo\n");
+    fprintf(out, "\n");
+    fprintf(out, "  Prints the malloc debug information. You need to run the following first: \n");
+    fprintf(out, "   # adb shell stop\n");
+    fprintf(out, "   # adb shell setprop libc.debug.malloc.program statsd \n");
+    fprintf(out, "   # adb shell setprop libc.debug.malloc.options backtrace \n");
+    fprintf(out, "   # adb shell start\n");
+    fprintf(out, "\n");
+    fprintf(out, "\n");
     fprintf(out, "usage: adb shell cmd stats print-uid-map \n");
     fprintf(out, "\n");
     fprintf(out, "  Prints the UID, app name, version mapping.\n");
@@ -507,6 +521,13 @@
     return NO_ERROR;
 }
 
+status_t StatsService::cmd_dump_memory_info(FILE* out) {
+    std::string s = dumpMemInfo(100);
+    fprintf(out, "Memory Info\n");
+    fprintf(out, "%s", s.c_str());
+    return NO_ERROR;
+}
+
 Status StatsService::informAllUidData(const vector<int32_t>& uid, const vector<int32_t>& version,
                                       const vector<String16>& app) {
     if (DEBUG) ALOGD("StatsService::informAllUidData was called");
diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h
index 4d768f6..47f0bb6 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -160,6 +160,11 @@
      */
     status_t cmd_remove_config_files(FILE* out);
 
+    /*
+     * Dump memory usage by statsd.
+     */
+    status_t cmd_dump_memory_info(FILE* out);
+
     /**
      * Update a configuration.
      */
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 57a92b6..20e7c60 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -74,6 +74,7 @@
         ActivityForegroundStateChanged activity_foreground_state_changed = 42;
         IsolatedUidChanged isolated_uid_changed = 43;
         PacketWakeupOccurred packet_wakeup_occurred = 44;
+        DropboxErrorChanged dropbox_error_changed = 45;
         // TODO: Reorder the numbering so that the most frequent occur events occur in the first 15.
     }
 
@@ -716,6 +717,34 @@
 }
 
 /**
+ * Logs when an error is written to dropbox.
+ * Logged from:
+ *      frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
+ */
+message DropboxErrorChanged {
+    // The uid if available. -1 means not available.
+    optional int32 uid = 1;
+
+    // Tag used when recording this error to dropbox. Contains data_ or system_ prefix.
+    optional string tag = 2;
+
+    // The name of the process.
+    optional string process_name = 3;
+
+    // The pid if available. -1 means not available.
+    optional int32 pid = 4;
+
+    // 1 indicates is instant app. -1 indicates Not applicable.
+    optional int32 is_instant_app = 5;
+
+    // The activity name if available.
+    optional string activity_name = 6;
+
+    // 1 indicates in foreground. -1 indicates not available.
+    optional int32 is_foreground = 7;
+}
+
+/**
  * Pulls bytes transferred via wifi (Sum of foreground and background usage).
  *
  * Pulled from:
diff --git a/cmds/statsd/src/config/ConfigManager.cpp b/cmds/statsd/src/config/ConfigManager.cpp
index 2125609..0c9252e 100644
--- a/cmds/statsd/src/config/ConfigManager.cpp
+++ b/cmds/statsd/src/config/ConfigManager.cpp
@@ -232,7 +232,7 @@
 static StatsdConfig build_fake_config() {
     // HACK: Hard code a test metric for counting screen on events...
     StatsdConfig config;
-    config.set_name("12345");
+    config.set_name("CONFIG_12345");
 
     int WAKE_LOCK_TAG_ID = 1111;  // put a fake id here to make testing easier.
     int WAKE_LOCK_UID_KEY_ID = 1;
@@ -263,20 +263,21 @@
 
     // Count Screen ON events.
     CountMetric* metric = config.add_count_metric();
-    metric->set_name("1");
+    metric->set_name("METRIC_1");
     metric->set_what("SCREEN_TURNED_ON");
     metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
 
     // Anomaly threshold for screen-on count.
     Alert* alert = config.add_alert();
-    alert->set_name("1");
+    alert->set_name("ALERT_1");
+    alert->set_metric_name("METRIC_1");
     alert->set_number_of_buckets(6);
     alert->set_trigger_if_sum_gt(10);
     alert->set_refractory_period_secs(30);
 
     // Count process state changes, slice by uid.
     metric = config.add_count_metric();
-    metric->set_name("2");
+    metric->set_name("METRIC_2");
     metric->set_what("PROCESS_STATE_CHANGE");
     metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
     KeyMatcher* keyMatcher = metric->add_dimension();
@@ -284,14 +285,15 @@
 
     // Anomaly threshold for background count.
     alert = config.add_alert();
-    alert->set_name("2");
+    alert->set_name("ALERT_2");
+    alert->set_metric_name("METRIC_2");
     alert->set_number_of_buckets(4);
     alert->set_trigger_if_sum_gt(30);
     alert->set_refractory_period_secs(20);
 
     // Count process state changes, slice by uid, while SCREEN_IS_OFF
     metric = config.add_count_metric();
-    metric->set_name("3");
+    metric->set_name("METRIC_3");
     metric->set_what("PROCESS_STATE_CHANGE");
     metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
     keyMatcher = metric->add_dimension();
@@ -300,7 +302,7 @@
 
     // Count wake lock, slice by uid, while SCREEN_IS_ON and app in background
     metric = config.add_count_metric();
-    metric->set_name("4");
+    metric->set_name("METRIC_4");
     metric->set_what("APP_GET_WL");
     metric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
     keyMatcher = metric->add_dimension();
@@ -313,7 +315,7 @@
 
     // Duration of an app holding any wl, while screen on and app in background, slice by uid
     DurationMetric* durationMetric = config.add_duration_metric();
-    durationMetric->set_name("5");
+    durationMetric->set_name("METRIC_5");
     durationMetric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
     durationMetric->set_aggregation_type(DurationMetric_AggregationType_SUM);
     keyMatcher = durationMetric->add_dimension();
@@ -327,7 +329,7 @@
 
     // max Duration of an app holding any wl, while screen on and app in background, slice by uid
     durationMetric = config.add_duration_metric();
-    durationMetric->set_name("6");
+    durationMetric->set_name("METRIC_6");
     durationMetric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
     durationMetric->set_aggregation_type(DurationMetric_AggregationType_MAX_SPARSE);
     keyMatcher = durationMetric->add_dimension();
@@ -341,7 +343,7 @@
 
     // Duration of an app holding any wl, while screen on and app in background
     durationMetric = config.add_duration_metric();
-    durationMetric->set_name("7");
+    durationMetric->set_name("METRIC_7");
     durationMetric->mutable_bucket()->set_bucket_size_millis(30 * 1000L);
     durationMetric->set_aggregation_type(DurationMetric_AggregationType_MAX_SPARSE);
     durationMetric->set_what("WL_HELD_PER_APP_PER_NAME");
@@ -353,14 +355,14 @@
 
     // Duration of screen on time.
     durationMetric = config.add_duration_metric();
-    durationMetric->set_name("8");
+    durationMetric->set_name("METRIC_8");
     durationMetric->mutable_bucket()->set_bucket_size_millis(10 * 1000L);
     durationMetric->set_aggregation_type(DurationMetric_AggregationType_SUM);
     durationMetric->set_what("SCREEN_IS_ON");
 
     // Value metric to count KERNEL_WAKELOCK when screen turned on
     ValueMetric* valueMetric = config.add_value_metric();
-    valueMetric->set_name("6");
+    valueMetric->set_name("METRIC_6");
     valueMetric->set_what("KERNEL_WAKELOCK");
     valueMetric->set_value_field(1);
     valueMetric->set_condition("SCREEN_IS_ON");
@@ -371,12 +373,12 @@
 
     // Add an EventMetric to log process state change events.
     EventMetric* eventMetric = config.add_event_metric();
-    eventMetric->set_name("9");
+    eventMetric->set_name("METRIC_9");
     eventMetric->set_what("SCREEN_TURNED_ON");
 
     // Add an GaugeMetric.
     GaugeMetric* gaugeMetric = config.add_gauge_metric();
-    gaugeMetric->set_name("10");
+    gaugeMetric->set_name("METRIC_10");
     gaugeMetric->set_what("DEVICE_TEMPERATURE");
     gaugeMetric->set_gauge_field(DEVICE_TEMPERATURE_KEY);
     gaugeMetric->mutable_bucket()->set_bucket_size_millis(60 * 1000L);
diff --git a/cmds/statsd/src/guardrail/MemoryLeakTrackUtil.cpp b/cmds/statsd/src/guardrail/MemoryLeakTrackUtil.cpp
new file mode 100644
index 0000000..e1947c4
--- /dev/null
+++ b/cmds/statsd/src/guardrail/MemoryLeakTrackUtil.cpp
@@ -0,0 +1,99 @@
+/*
+ * 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.
+ */
+
+#define DEBUG true  // STOPSHIP if true
+#include "Log.h"
+
+#include <sstream>
+#include "MemoryLeakTrackUtil.h"
+
+/*
+ * The code here originally resided in MediaPlayerService.cpp
+ */
+
+// Figure out the abi based on defined macros.
+#if defined(__arm__)
+#define ABI_STRING "arm"
+#elif defined(__aarch64__)
+#define ABI_STRING "arm64"
+#elif defined(__mips__) && !defined(__LP64__)
+#define ABI_STRING "mips"
+#elif defined(__mips__) && defined(__LP64__)
+#define ABI_STRING "mips64"
+#elif defined(__i386__)
+#define ABI_STRING "x86"
+#elif defined(__x86_64__)
+#define ABI_STRING "x86_64"
+#else
+#error "Unsupported ABI"
+#endif
+
+extern std::string backtrace_string(const uintptr_t* frames, size_t frame_count);
+
+namespace android {
+namespace os {
+namespace statsd {
+
+extern "C" void get_malloc_leak_info(uint8_t** info, size_t* overallSize, size_t* infoSize,
+                                     size_t* totalMemory, size_t* backtraceSize);
+
+extern "C" void free_malloc_leak_info(uint8_t* info);
+
+std::string dumpMemInfo(size_t limit) {
+    uint8_t* info;
+    size_t overallSize;
+    size_t infoSize;
+    size_t totalMemory;
+    size_t backtraceSize;
+    get_malloc_leak_info(&info, &overallSize, &infoSize, &totalMemory, &backtraceSize);
+
+    size_t count;
+    if (info == nullptr || overallSize == 0 || infoSize == 0 ||
+        (count = overallSize / infoSize) == 0) {
+        ALOGD("no malloc info, libc.debug.malloc.program property should be set");
+        return std::string();
+    }
+
+    std::ostringstream oss;
+    oss << totalMemory << " bytes in " << count << " allocations\n";
+    oss << "  ABI: '" ABI_STRING "'"
+        << "\n\n";
+    if (count > limit) count = limit;
+
+    // The memory is sorted based on total size which is useful for finding
+    // worst memory offenders. For diffs, sometimes it is preferable to sort
+    // based on the backtrace.
+    for (size_t i = 0; i < count; i++) {
+        struct AllocEntry {
+            size_t size;  // bit 31 is set if this is zygote allocated memory
+            size_t allocations;
+            uintptr_t backtrace[];
+        };
+
+        const AllocEntry* const e = (AllocEntry*)(info + i * infoSize);
+
+        oss << (e->size * e->allocations) << " bytes ( " << e->size << " bytes * " << e->allocations
+            << " allocations )\n";
+        oss << backtrace_string(e->backtrace, backtraceSize) << "\n";
+    }
+    oss << "\n";
+    free_malloc_leak_info(info);
+    return oss.str();
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/src/guardrail/MemoryLeakTrackUtil.h b/cmds/statsd/src/guardrail/MemoryLeakTrackUtil.h
new file mode 100644
index 0000000..444ed92
--- /dev/null
+++ b/cmds/statsd/src/guardrail/MemoryLeakTrackUtil.h
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+#pragma once
+
+#include <iostream>
+
+namespace android {
+namespace os {
+namespace statsd {
+/*
+ * Dump the heap memory of the calling process, sorted by total size
+ * (allocation size * number of allocations).
+ *
+ *    limit is the number of unique allocations to return.
+ */
+extern std::string dumpMemInfo(size_t limit);
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.cpp b/cmds/statsd/src/metrics/CountMetricProducer.cpp
index d47bd4f..a5ce389 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/CountMetricProducer.cpp
@@ -133,7 +133,7 @@
                     mProto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION);
             mProto->write(FIELD_TYPE_INT32 | FIELD_ID_KEY, kv.key());
             if (kv.has_value_str()) {
-                mProto->write(FIELD_TYPE_INT32 | FIELD_ID_VALUE_STR, kv.value_str());
+                mProto->write(FIELD_TYPE_STRING | FIELD_ID_VALUE_STR, kv.value_str());
             } else if (kv.has_value_int()) {
                 mProto->write(FIELD_TYPE_INT64 | FIELD_ID_VALUE_INT, kv.value_int());
             } else if (kv.has_value_bool()) {
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
index b0a97b1..a32e0cb 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
@@ -163,6 +163,14 @@
             ALOGW("Dimension key %s not found?!?! skip...", hashableKey.c_str());
             continue;
         }
+
+        // If there is no duration bucket info for this key, don't include it in the report.
+        // For example, duration started, but condition is never turned to true.
+        // TODO: Only add the key to the map when we add duration buckets info for it.
+        if (pair.second.size() == 0) {
+            continue;
+        }
+
         long long wrapperToken =
                 mProto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DATA);
 
@@ -172,7 +180,7 @@
                     mProto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION);
             mProto->write(FIELD_TYPE_INT32 | FIELD_ID_KEY, kv.key());
             if (kv.has_value_str()) {
-                mProto->write(FIELD_TYPE_INT32 | FIELD_ID_VALUE_STR, kv.value_str());
+                mProto->write(FIELD_TYPE_STRING | FIELD_ID_VALUE_STR, kv.value_str());
             } else if (kv.has_value_int()) {
                 mProto->write(FIELD_TYPE_INT64 | FIELD_ID_VALUE_INT, kv.value_int());
             } else if (kv.has_value_bool()) {
@@ -203,7 +211,6 @@
     mProto->end(mProtoToken);
     mProto->write(FIELD_TYPE_INT64 | FIELD_ID_END_REPORT_NANOS,
                   (long long)mCurrentBucketStartTimeNs);
-
     std::unique_ptr<std::vector<uint8_t>> buffer = serializeProto();
     startNewProtoOutputStream(endTime);
     // TODO: Properly clear the old buckets.
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
index 42ac1a2..d7b7296 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.cpp
@@ -135,7 +135,7 @@
                     mProto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION);
             mProto->write(FIELD_TYPE_INT32 | FIELD_ID_KEY, kv.key());
             if (kv.has_value_str()) {
-                mProto->write(FIELD_TYPE_INT32 | FIELD_ID_VALUE_STR, kv.value_str());
+                mProto->write(FIELD_TYPE_STRING | FIELD_ID_VALUE_STR, kv.value_str());
             } else if (kv.has_value_int()) {
                 mProto->write(FIELD_TYPE_INT64 | FIELD_ID_VALUE_INT, kv.value_int());
             } else if (kv.has_value_bool()) {
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.cpp b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
index 9cbe6f6..0dbdd29 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.cpp
@@ -153,7 +153,7 @@
                     mProto->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED | FIELD_ID_DIMENSION);
             mProto->write(FIELD_TYPE_INT32 | FIELD_ID_KEY, kv.key());
             if (kv.has_value_str()) {
-                mProto->write(FIELD_TYPE_INT32 | FIELD_ID_VALUE_STR, kv.value_str());
+                mProto->write(FIELD_TYPE_STRING | FIELD_ID_VALUE_STR, kv.value_str());
             } else if (kv.has_value_int()) {
                 mProto->write(FIELD_TYPE_INT64 | FIELD_ID_VALUE_INT, kv.value_int());
             } else if (kv.has_value_bool()) {
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp
index 2344cb4..4660263 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.cpp
+++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp
@@ -43,12 +43,12 @@
                                  int& logTrackerIndex) {
     auto logTrackerIt = logTrackerMap.find(what);
     if (logTrackerIt == logTrackerMap.end()) {
-        ALOGW("cannot find the LogEntryMatcher %s in config", what.c_str());
+        ALOGW("cannot find the LogEntryMatcher \"%s\" in config", what.c_str());
         return false;
     }
     if (usedForDimension && allLogEntryMatchers[logTrackerIt->second]->getTagIds().size() > 1) {
-        ALOGE("LogEntryMatcher %s has more than one tag ids. When a metric has dimension, the "
-              "\"what\" can only about one atom type.",
+        ALOGE("LogEntryMatcher \"%s\" has more than one tag ids. When a metric has dimension, "
+              "the \"what\" can only about one atom type.",
               what.c_str());
         return false;
     }
@@ -67,14 +67,14 @@
         unordered_map<int, std::vector<int>>& conditionToMetricMap) {
     auto condition_it = conditionTrackerMap.find(condition);
     if (condition_it == conditionTrackerMap.end()) {
-        ALOGW("cannot find the Condition %s in the config", condition.c_str());
+        ALOGW("cannot find Condition \"%s\" in the config", condition.c_str());
         return false;
     }
 
     for (const auto& link : links) {
         auto it = conditionTrackerMap.find(link.condition());
         if (it == conditionTrackerMap.end()) {
-            ALOGW("cannot find the Condition %s in the config", link.condition().c_str());
+            ALOGW("cannot find Condition \"%s\" in the config", link.condition().c_str());
             return false;
         }
         allConditionTrackers[condition_it->second]->setSliced(true);
@@ -110,7 +110,7 @@
                         new CombinationLogMatchingTracker(logMatcher.name(), index));
                 break;
             default:
-                ALOGE("Matcher %s malformed", logMatcher.name().c_str());
+                ALOGE("Matcher \"%s\" malformed", logMatcher.name().c_str());
                 return false;
                 // continue;
         }
@@ -158,7 +158,7 @@
                 break;
             }
             default:
-                ALOGE("Condition %s malformed", condition.name().c_str());
+                ALOGE("Condition \"%s\" malformed", condition.name().c_str());
                 return false;
         }
         if (conditionTrackerMap.find(condition.name()) != conditionTrackerMap.end()) {
@@ -204,7 +204,7 @@
     for (int i = 0; i < config.count_metric_size(); i++) {
         const CountMetric& metric = config.count_metric(i);
         if (!metric.has_what()) {
-            ALOGW("cannot find what in CountMetric %s", metric.name().c_str());
+            ALOGW("cannot find \"what\" in CountMetric \"%s\"", metric.name().c_str());
             return false;
         }
 
@@ -341,7 +341,7 @@
     for (int i = 0; i < config.value_metric_size(); i++) {
         const ValueMetric& metric = config.value_metric(i);
         if (!metric.has_what()) {
-            ALOGW("cannot find what in ValueMetric %s", metric.name().c_str());
+            ALOGW("cannot find \"what\" in ValueMetric \"%s\"", metric.name().c_str());
             return false;
         }
 
@@ -387,7 +387,7 @@
     for (int i = 0; i < config.gauge_metric_size(); i++) {
         const GaugeMetric& metric = config.gauge_metric(i);
         if (!metric.has_what()) {
-            ALOGW("cannot find what in ValueMetric %s", metric.name().c_str());
+            ALOGW("cannot find \"what\" in ValueMetric \"%s\"", metric.name().c_str());
             return false;
         }
 
@@ -438,7 +438,7 @@
         const Alert& alert = config.alert(i);
         const auto& itr = metricProducerMap.find(alert.metric_name());
         if (itr == metricProducerMap.end()) {
-            ALOGW("alert has unknown metric name: %s %s", alert.name().c_str(),
+            ALOGW("alert \"%s\" has unknown metric name: \"%s\"", alert.name().c_str(),
                   alert.metric_name().c_str());
             return false;
         }
diff --git a/cmds/statsd/src/stats_log.proto b/cmds/statsd/src/stats_log.proto
index 4f5df55..3fbcfee 100644
--- a/cmds/statsd/src/stats_log.proto
+++ b/cmds/statsd/src/stats_log.proto
@@ -125,7 +125,7 @@
 }
 
 message StatsLogReport {
-  optional int32 metric_id = 1;
+  optional string metric_name = 1;
 
   optional int64 start_report_nanos = 2;
 
diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto
index e75a37f..c8fa155 100644
--- a/cmds/statsd/src/statsd_config.proto
+++ b/cmds/statsd/src/statsd_config.proto
@@ -88,7 +88,7 @@
     UNKNOWN = 0;
     FALSE = 1;
   }
-  optional InitialValue initial_value = 5 [default = UNKNOWN];
+  optional InitialValue initial_value = 5 [default = FALSE];
 
   repeated KeyMatcher dimension = 6;
 }
diff --git a/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp b/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp
index 80a0068..11fb011 100644
--- a/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp
+++ b/cmds/statsd/tests/condition/SimpleConditionTracker_test.cpp
@@ -70,6 +70,7 @@
     simpleCondition.set_start("SCREEN_TURNED_ON");
     simpleCondition.set_stop("SCREEN_TURNED_OFF");
     simpleCondition.set_count_nesting(false);
+    simpleCondition.set_initial_value(SimpleCondition_InitialValue_UNKNOWN);
 
     unordered_map<string, int> trackerNameIndexMap;
     trackerNameIndexMap["SCREEN_TURNED_ON"] = 0;
diff --git a/cmds/statsd/tools/Android.mk b/cmds/statsd/tools/Android.mk
new file mode 100644
index 0000000..faf2d2c
--- /dev/null
+++ b/cmds/statsd/tools/Android.mk
@@ -0,0 +1,20 @@
+# 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.
+#
+#
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+# Include the sub-makefiles
+include $(call all-makefiles-under,$(LOCAL_PATH))
\ No newline at end of file
diff --git a/cmds/statsd/tools/dogfood/Android.mk b/cmds/statsd/tools/dogfood/Android.mk
new file mode 100644
index 0000000..1bd5f30
--- /dev/null
+++ b/cmds/statsd/tools/dogfood/Android.mk
@@ -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.
+#
+#
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_SRC_FILES += ../../src/stats_log.proto \
+                   ../../src/atoms_copy.proto
+
+LOCAL_PROTOC_FLAGS := --proto_path=$(LOCAL_PATH)/../../src/
+
+LOCAL_PROTOC_OPTIMIZE_TYPE := lite-static
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+
+LOCAL_PACKAGE_NAME := StatsdDogfood
+LOCAL_CERTIFICATE := platform
+LOCAL_PRIVILEGED_MODULE := true
+LOCAL_DEX_PREOPT := false
+include $(BUILD_PACKAGE)
\ No newline at end of file
diff --git a/cmds/statsd/tools/dogfood/AndroidManifest.xml b/cmds/statsd/tools/dogfood/AndroidManifest.xml
new file mode 100644
index 0000000..cd76c9d
--- /dev/null
+++ b/cmds/statsd/tools/dogfood/AndroidManifest.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 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.
+*/
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.statsd.dogfood"
+    android:sharedUserId="android.uid.system"
+    android:versionCode="1"
+    android:versionName="1.0" >
+
+    <uses-permission android:name="android.permission.DUMP" />
+
+    <application
+        android:allowBackup="true"
+        android:icon="@drawable/ic_launcher"
+        android:label="@string/app_name" >
+        <activity
+            android:name=".MainActivity"
+            android:label="@string/app_name"
+            android:launchMode="singleTop" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/cmds/statsd/tools/dogfood/res/drawable-hdpi/ic_launcher.png b/cmds/statsd/tools/dogfood/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..55621cc
--- /dev/null
+++ b/cmds/statsd/tools/dogfood/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/cmds/statsd/tools/dogfood/res/drawable-mdpi/ic_launcher.png b/cmds/statsd/tools/dogfood/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..11ec206
--- /dev/null
+++ b/cmds/statsd/tools/dogfood/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/cmds/statsd/tools/dogfood/res/drawable-xhdpi/ic_launcher.png b/cmds/statsd/tools/dogfood/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..7c02b78
--- /dev/null
+++ b/cmds/statsd/tools/dogfood/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/cmds/statsd/tools/dogfood/res/drawable-xxhdpi/ic_launcher.png b/cmds/statsd/tools/dogfood/res/drawable-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..915d914
--- /dev/null
+++ b/cmds/statsd/tools/dogfood/res/drawable-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/cmds/statsd/tools/dogfood/res/layout/activity_main.xml b/cmds/statsd/tools/dogfood/res/layout/activity_main.xml
new file mode 100644
index 0000000..3997897
--- /dev/null
+++ b/cmds/statsd/tools/dogfood/res/layout/activity_main.xml
@@ -0,0 +1,133 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 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.
+*/
+-->
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" >
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:orientation="vertical">
+
+        <Button
+            android:id="@+id/push_config"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:background="@android:color/holo_green_light"
+            android:text="@string/push_config"/>
+
+        <LinearLayout android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal">
+            <Button android:id="@+id/app_a_wake_lock_acquire1"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/app_a_get_wl1"/>
+            <Button android:id="@+id/app_a_wake_lock_release1"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/app_a_release_wl1"/>
+        </LinearLayout>
+
+        <LinearLayout android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal">
+            <Button android:id="@+id/app_a_wake_lock_acquire2"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/app_a_get_wl2"/>
+            <Button android:id="@+id/app_a_wake_lock_release2"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/app_a_release_wl2"/>
+        </LinearLayout>
+
+        <LinearLayout android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal">
+            <Button android:id="@+id/app_b_wake_lock_acquire1"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/app_b_get_wl1"/>
+            <Button android:id="@+id/app_b_wake_lock_release1"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/app_b_release_wl1"/>
+        </LinearLayout>
+        <LinearLayout android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal">
+            <Button android:id="@+id/app_b_wake_lock_acquire2"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/app_b_get_wl2"/>
+            <Button android:id="@+id/app_b_wake_lock_release2"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/app_b_release_wl2"/>
+        </LinearLayout>
+
+    <LinearLayout android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:orientation="horizontal">
+        <Button android:id="@+id/plug"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/plug"/>
+
+        <Button android:id="@+id/unplug"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/unplug"/>
+    </LinearLayout>
+
+        <LinearLayout android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal">
+            <Button android:id="@+id/screen_on"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/screen_on"/>
+
+            <Button android:id="@+id/screen_off"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="@string/screen_off"/>
+        </LinearLayout>
+
+        <Button android:id="@+id/dump"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:background="@android:color/holo_purple"
+            android:text="@string/dump"/>
+
+        <TextView
+            android:id="@+id/header"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/report_header"/>
+
+        <TextView
+            android:id="@+id/report_text"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content" />
+
+    </LinearLayout>
+
+</ScrollView>
\ No newline at end of file
diff --git a/cmds/statsd/tools/dogfood/res/raw/statsd_baseline_config b/cmds/statsd/tools/dogfood/res/raw/statsd_baseline_config
new file mode 100644
index 0000000..d5b8fed
--- /dev/null
+++ b/cmds/statsd/tools/dogfood/res/raw/statsd_baseline_config
Binary files differ
diff --git a/cmds/statsd/tools/dogfood/res/values/strings.xml b/cmds/statsd/tools/dogfood/res/values/strings.xml
new file mode 100644
index 0000000..7690df6
--- /dev/null
+++ b/cmds/statsd/tools/dogfood/res/values/strings.xml
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 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.
+*/
+-->
+<resources>
+
+    <string name="app_name">Statsd Dogfood</string>
+
+    <string name="statsd_running">Statsd Running</string>
+    <string name="statsd_not_running">Statsd NOT Running</string>
+
+    <string name="push_config">Push baseline config</string>
+
+    <string name="app_a_foreground">App A foreground</string>
+    <string name="app_b_foreground">App B foreground</string>
+
+
+    <string name="app_a_get_wl1">App A get wl_1</string>
+    <string name="app_a_release_wl1">App A release wl_1</string>
+
+    <string name="app_a_get_wl2">App A get wl_2</string>
+    <string name="app_a_release_wl2">App A release wl_2</string>
+
+    <string name="app_b_get_wl1">App B get wl_1</string>
+    <string name="app_b_release_wl1">App B release wl_1</string>
+
+    <string name="app_b_get_wl2">App B get wl_2</string>
+    <string name="app_b_release_wl2">App B release wl_2</string>
+
+    <string name="plug">Plug</string>
+    <string name="unplug">Unplug</string>
+
+    <string name="screen_on">Screen On</string>
+    <string name="screen_off">Screen Off</string>
+
+    <string name="dump">DumpReport</string>
+    <string name="report_header">Report details</string>
+</resources>
diff --git a/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/DisplayProtoUtils.java b/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/DisplayProtoUtils.java
new file mode 100644
index 0000000..f6dea42
--- /dev/null
+++ b/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/DisplayProtoUtils.java
@@ -0,0 +1,245 @@
+/*
+ * 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.statsd.dogfood;
+
+import android.text.format.DateFormat;
+
+import com.android.os.StatsLog;
+
+import java.util.List;
+
+public class DisplayProtoUtils {
+    public static void displayLogReport(StringBuilder sb, StatsLog.ConfigMetricsReport report) {
+        sb.append("ConfigKey: ");
+        if (report.hasConfigKey()) {
+            com.android.os.StatsLog.ConfigMetricsReport.ConfigKey key = report.getConfigKey();
+            sb.append("\tuid: ").append(key.getUid()).append(" name: ").append(key.getName())
+                    .append("\n");
+        }
+
+        sb.append("StatsLogReport size: ").append(report.getMetricsCount()).append("\n");
+        for (StatsLog.StatsLogReport log : report.getMetricsList()) {
+            sb.append("\n\n");
+            sb.append("metric id: ").append(log.getMetricName()).append("\n");
+            sb.append("start time:").append(getDateStr(log.getStartReportNanos())).append("\n");
+            sb.append("end time:").append(getDateStr(log.getEndReportNanos())).append("\n");
+
+            switch (log.getDataCase()) {
+                case DURATION_METRICS:
+                    sb.append("Duration metric data\n");
+                    displayDurationMetricData(sb, log);
+                    break;
+                case EVENT_METRICS:
+                    sb.append("Event metric data\n");
+                    displayEventMetricData(sb, log);
+                    break;
+                case COUNT_METRICS:
+                    sb.append("Count metric data\n");
+                    displayCountMetricData(sb, log);
+                    break;
+                case GAUGE_METRICS:
+                    sb.append("Gauge metric data\n");
+                    displayGaugeMetricData(sb, log);
+                    break;
+                case VALUE_METRICS:
+                    sb.append("Value metric data\n");
+                    displayValueMetricData(sb, log);
+                    break;
+                case DATA_NOT_SET:
+                    sb.append("No metric data\n");
+                    break;
+            }
+        }
+    }
+
+    public static String getDateStr(long nanoSec) {
+        return DateFormat.format("dd/MM hh:mm:ss", nanoSec/1000000).toString();
+    }
+
+    private static void displayDimension(StringBuilder sb, List<StatsLog.KeyValuePair> pairs) {
+        for (com.android.os.StatsLog.KeyValuePair kv : pairs) {
+            sb.append(kv.getKey()).append(":");
+            if (kv.hasValueBool()) {
+                sb.append(kv.getValueBool());
+            } else if (kv.hasValueFloat()) {
+                sb.append(kv.getValueFloat());
+            } else if (kv.hasValueInt()) {
+                sb.append(kv.getValueInt());
+            } else if (kv.hasValueStr()) {
+                sb.append(kv.getValueStr());
+            }
+            sb.append(" ");
+        }
+    }
+
+    public static void displayDurationMetricData(StringBuilder sb, StatsLog.StatsLogReport log) {
+        StatsLog.StatsLogReport.DurationMetricDataWrapper durationMetricDataWrapper
+                = log.getDurationMetrics();
+        sb.append("Dimension size: ").append(durationMetricDataWrapper.getDataCount()).append("\n");
+        for (StatsLog.DurationMetricData duration : durationMetricDataWrapper.getDataList()) {
+            sb.append("dimension: ");
+            displayDimension(sb, duration.getDimensionList());
+            sb.append("\n");
+
+            for (StatsLog.DurationBucketInfo info : duration.getBucketInfoList())  {
+                sb.append("\t[").append(getDateStr(info.getStartBucketNanos())).append("-")
+                        .append(getDateStr(info.getEndBucketNanos())).append("] -> ")
+                        .append(info.getDurationNanos()).append(" ns\n");
+            }
+        }
+    }
+
+    public static void displayEventMetricData(StringBuilder sb, StatsLog.StatsLogReport log) {
+        sb.append("Contains ").append(log.getEventMetrics().getDataCount()).append(" events\n");
+        StatsLog.StatsLogReport.EventMetricDataWrapper eventMetricDataWrapper =
+                log.getEventMetrics();
+        for (StatsLog.EventMetricData event : eventMetricDataWrapper.getDataList()) {
+            sb.append(getDateStr(event.getTimestampNanos())).append(": ");
+            switch (event.getAtom().getPushedCase()) {
+                case SETTING_CHANGED:
+                    sb.append("SETTING_CHANGED\n");
+                    break;
+                case SYNC_STATE_CHANGED:
+                    sb.append("SYNC_STATE_CHANGED\n");
+                    break;
+                case AUDIO_STATE_CHANGED:
+                    sb.append("AUDIO_STATE_CHANGED\n");
+                    break;
+                case CAMERA_STATE_CHANGED:
+                    sb.append("CAMERA_STATE_CHANGED\n");
+                    break;
+                case ISOLATED_UID_CHANGED:
+                    sb.append("ISOLATED_UID_CHANGED\n");
+                    break;
+                case SCREEN_STATE_CHANGED:
+                    sb.append("SCREEN_STATE_CHANGED\n");
+                    break;
+                case SENSOR_STATE_CHANGED:
+                    sb.append("SENSOR_STATE_CHANGED\n");
+                    break;
+                case BATTERY_LEVEL_CHANGED:
+                    sb.append("BATTERY_LEVEL_CHANGED\n");
+                    break;
+                case PLUGGED_STATE_CHANGED:
+                    sb.append("PLUGGED_STATE_CHANGED\n");
+                    break;
+                case WAKEUP_ALARM_OCCURRED:
+                    sb.append("WAKEUP_ALARM_OCCURRED\n");
+                    break;
+                case BLE_SCAN_STATE_CHANGED:
+                    sb.append("BLE_SCAN_STATE_CHANGED\n");
+                    break;
+                case CHARGING_STATE_CHANGED:
+                    sb.append("CHARGING_STATE_CHANGED\n");
+                    break;
+                case GPS_SCAN_STATE_CHANGED:
+                    sb.append("GPS_SCAN_STATE_CHANGED\n");
+                    break;
+                case KERNEL_WAKEUP_REPORTED:
+                    sb.append("KERNEL_WAKEUP_REPORTED\n");
+                    break;
+                case WAKELOCK_STATE_CHANGED:
+                    sb.append("WAKELOCK_STATE_CHANGED\n");
+                    break;
+                case WIFI_LOCK_STATE_CHANGED:
+                    sb.append("WIFI_LOCK_STATE_CHANGED\n");
+                    break;
+                case WIFI_SCAN_STATE_CHANGED:
+                    sb.append("WIFI_SCAN_STATE_CHANGED\n");
+                    break;
+                case BLE_SCAN_RESULT_RECEIVED:
+                    sb.append("BLE_SCAN_RESULT_RECEIVED\n");
+                    break;
+                case DEVICE_ON_STATUS_CHANGED:
+                    sb.append("DEVICE_ON_STATUS_CHANGED\n");
+                    break;
+                case FLASHLIGHT_STATE_CHANGED:
+                    sb.append("FLASHLIGHT_STATE_CHANGED\n");
+                    break;
+                case SCREEN_BRIGHTNESS_CHANGED:
+                    sb.append("SCREEN_BRIGHTNESS_CHANGED\n");
+                    break;
+                case UID_PROCESS_STATE_CHANGED:
+                    sb.append("UID_PROCESS_STATE_CHANGED\n");
+                    break;
+                case UID_WAKELOCK_STATE_CHANGED:
+                    sb.append("UID_WAKELOCK_STATE_CHANGED\n");
+                    break;
+                case DEVICE_TEMPERATURE_REPORTED:
+                    sb.append("DEVICE_TEMPERATURE_REPORTED\n");
+                    break;
+                case SCHEDULED_JOB_STATE_CHANGED:
+                    sb.append("SCHEDULED_JOB_STATE_CHANGED\n");
+                    break;
+                case MEDIA_CODEC_ACTIVITY_CHANGED:
+                    sb.append("MEDIA_CODEC_ACTIVITY_CHANGED\n");
+                    break;
+                case WIFI_SIGNAL_STRENGTH_CHANGED:
+                    sb.append("WIFI_SIGNAL_STRENGTH_CHANGED\n");
+                    break;
+                case PHONE_SIGNAL_STRENGTH_CHANGED:
+                    sb.append("PHONE_SIGNAL_STRENGTH_CHANGED\n");
+                    break;
+                case DEVICE_IDLE_MODE_STATE_CHANGED:
+                    sb.append("DEVICE_IDLE_MODE_STATE_CHANGED\n");
+                    break;
+                case BATTERY_SAVER_MODE_STATE_CHANGED:
+                    sb.append("BATTERY_SAVER_MODE_STATE_CHANGED\n");
+                    break;
+                case PROCESS_LIFE_CYCLE_STATE_CHANGED:
+                    sb.append("PROCESS_LIFE_CYCLE_STATE_CHANGED\n");
+                    break;
+                case ACTIVITY_FOREGROUND_STATE_CHANGED:
+                    sb.append("ACTIVITY_FOREGROUND_STATE_CHANGED\n");
+                    break;
+                case BLE_UNOPTIMIZED_SCAN_STATE_CHANGED:
+                    sb.append("BLE_UNOPTIMIZED_SCAN_STATE_CHANGED\n");
+                    break;
+                case LONG_PARTIAL_WAKELOCK_STATE_CHANGED:
+                    sb.append("LONG_PARTIAL_WAKELOCK_STATE_CHANGED\n");
+                    break;
+                case PUSHED_NOT_SET:
+                    sb.append("PUSHED_NOT_SET\n");
+                    break;
+            }
+        }
+    }
+
+    public static void displayCountMetricData(StringBuilder sb, StatsLog.StatsLogReport log) {
+        StatsLog.StatsLogReport.CountMetricDataWrapper countMetricDataWrapper
+                = log.getCountMetrics();
+        sb.append("Dimension size: ").append(countMetricDataWrapper.getDataCount()).append("\n");
+        for (StatsLog.CountMetricData count : countMetricDataWrapper.getDataList()) {
+            sb.append("dimension: ");
+            displayDimension(sb, count.getDimensionList());
+            sb.append("\n");
+
+            for (StatsLog.CountBucketInfo info : count.getBucketInfoList())  {
+                sb.append("\t[").append(getDateStr(info.getStartBucketNanos())).append("-")
+                        .append(getDateStr(info.getEndBucketNanos())).append("] -> ")
+                        .append(info.getCount()).append("\n");
+            }
+        }
+    }
+
+    public static void displayGaugeMetricData(StringBuilder sb, StatsLog.StatsLogReport log) {
+        sb.append("Display me!");
+    }
+
+    public static void displayValueMetricData(StringBuilder sb, StatsLog.StatsLogReport log) {
+        sb.append("Display me!");
+    }
+}
diff --git a/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java b/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java
new file mode 100644
index 0000000..5e3160e
--- /dev/null
+++ b/cmds/statsd/tools/dogfood/src/com/android/statsd/dogfood/MainActivity.java
@@ -0,0 +1,259 @@
+/*
+ * 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.statsd.dogfood;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.os.Bundle;
+import android.util.Log;
+import android.util.StatsLog;
+import android.util.StatsManager;
+import android.view.View;
+import android.widget.TextView;
+import android.widget.Toast;
+import android.os.IStatsManager;
+import android.os.ServiceManager;
+
+import java.io.InputStream;
+
+import static com.android.statsd.dogfood.DisplayProtoUtils.displayLogReport;
+
+public class MainActivity extends Activity {
+    private final static String TAG = "StatsdDogfood";
+
+    final int[] mUids = {11111111, 2222222};
+    StatsManager mStatsManager;
+    TextView mReportText;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        setContentView(R.layout.activity_main);
+
+        findViewById(R.id.app_a_wake_lock_acquire1).setOnClickListener(
+                new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                onWakeLockAcquire(0, "wl_1");
+            }
+        });
+
+        findViewById(R.id.app_b_wake_lock_acquire1).setOnClickListener(
+                new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                onWakeLockAcquire(1, "wl_1");
+            }
+        });
+
+        findViewById(R.id.app_a_wake_lock_acquire2).setOnClickListener(
+                new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                onWakeLockAcquire(0, "wl_2");
+            }
+        });
+
+        findViewById(R.id.app_b_wake_lock_acquire2).setOnClickListener(
+                new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                onWakeLockAcquire(1, "wl_2");
+            }
+        });
+
+        findViewById(R.id.app_a_wake_lock_release1).setOnClickListener(
+                new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                onWakeLockRelease(0, "wl_1");
+            }
+        });
+
+
+        findViewById(R.id.app_b_wake_lock_release1).setOnClickListener(
+                new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                onWakeLockRelease(1, "wl_1");
+            }
+        });
+
+        findViewById(R.id.app_a_wake_lock_release2).setOnClickListener(
+                new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                onWakeLockRelease(0, "wl_2");
+            }
+        });
+
+
+        findViewById(R.id.app_b_wake_lock_release2).setOnClickListener(
+                new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                onWakeLockRelease(1, "wl_2");
+            }
+        });
+
+
+        findViewById(R.id.plug).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                StatsLog.write(StatsLog.PLUGGED_STATE_CHANGED, 1);
+            }
+        });
+
+        findViewById(R.id.unplug).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                StatsLog.write(StatsLog.PLUGGED_STATE_CHANGED, 0);
+            }
+        });
+
+        findViewById(R.id.screen_on).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                StatsLog.write(StatsLog.SCREEN_STATE_CHANGED, 2);
+            }
+        });
+
+        findViewById(R.id.screen_off).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                StatsLog.write(StatsLog.SCREEN_STATE_CHANGED, 1);
+            }
+        });
+
+        mReportText = (TextView) findViewById(R.id.report_text);
+
+        findViewById(R.id.dump).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                if (!statsdRunning()) {
+                    return;
+                }
+                if (mStatsManager != null) {
+                    byte[] data = mStatsManager.getData("fake");
+                    if (data != null) {
+                        displayData(data);
+                    } else {
+                        mReportText.setText("Failed!");
+                    }
+                }
+            }
+        });
+
+        findViewById(R.id.push_config).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                try {
+                    if (!statsdRunning()) {
+                        return;
+                    }
+                    Resources res = getResources();
+                    InputStream inputStream = res.openRawResource(R.raw.statsd_baseline_config);
+
+                    byte[] config = new byte[inputStream.available()];
+                    inputStream.read(config);
+                    if (mStatsManager != null) {
+                        if (mStatsManager.addConfiguration("fake",
+                                config, getPackageName(), MainActivity.this.getClass().getName())) {
+                            Toast.makeText(
+                                    MainActivity.this, "Config pushed", Toast.LENGTH_LONG).show();
+                        } else {
+                            Toast.makeText(MainActivity.this, "Config push FAILED!",
+                                    Toast.LENGTH_LONG).show();
+                        }
+                    }
+                } catch (Exception e) {
+                    Toast.makeText(MainActivity.this, "failed to read config", Toast.LENGTH_LONG);
+                }
+            }
+        });
+        mStatsManager = (StatsManager) getSystemService("stats");
+    }
+
+    private boolean statsdRunning() {
+        if (IStatsManager.Stub.asInterface(ServiceManager.getService("stats")) == null) {
+            Log.d(TAG, "Statsd not running");
+            Toast.makeText(MainActivity.this, "Statsd NOT running!", Toast.LENGTH_LONG).show();
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    public void onNewIntent(Intent intent) {
+        Log.d(TAG, "new intent: " + intent.getIntExtra("pkg", 0));
+        int pkg = intent.getIntExtra("pkg", 0);
+        String name = intent.getStringExtra("name");
+        if (intent.hasExtra("acquire")) {
+            onWakeLockAcquire(pkg, name);
+        } else if (intent.hasExtra("release")) {
+            onWakeLockRelease(pkg, name);
+        }
+    }
+
+    private void displayData(byte[] data) {
+        com.android.os.StatsLog.ConfigMetricsReport report = null;
+        boolean good = false;
+        if (data != null) {
+            try {
+                report = com.android.os.StatsLog.ConfigMetricsReport.parseFrom(data);
+                good = true;
+            } catch (com.google.protobuf.InvalidProtocolBufferException e) {
+                // display it in the text view.
+            }
+        }
+        int size = data == null ? 0 : data.length;
+        StringBuilder sb = new StringBuilder();
+        sb.append(good ? "Proto parsing OK!" : "Proto parsing Error!");
+        sb.append(" size:").append(size).append("\n");
+
+        if (good && report != null) {
+            displayLogReport(sb, report);
+            mReportText.setText(sb.toString());
+        }
+    }
+
+
+    private void onWakeLockAcquire(int id, String name) {
+        if (id > 1) {
+            Log.d(TAG, "invalid pkg id");
+            return;
+        }
+        StatsLog.write(StatsLog.WAKELOCK_STATE_CHANGED, mUids[id], 0, name, 1);
+        StringBuilder sb = new StringBuilder();
+        sb.append("StagsLog.write(10, ").append(mUids[id]).append(", ").append(0)
+                .append(", ").append(name).append(", 1);");
+        Toast.makeText(this, sb.toString(), Toast.LENGTH_LONG).show();
+    }
+
+    private void onWakeLockRelease(int id, String name) {
+        if (id > 1) {
+            Log.d(TAG, "invalid pkg id");
+            return;
+        }
+        StatsLog.write(10, mUids[id], 0, name, 0);
+        StringBuilder sb = new StringBuilder();
+        sb.append("StagsLog.write(10, ").append(mUids[id]).append(", ").append(0)
+                .append(", ").append(name).append(", 0);");
+        Toast.makeText(this, sb.toString(), Toast.LENGTH_LONG).show();
+    }
+}
diff --git a/config/compiled-classes-phone b/config/compiled-classes-phone
index fb201ef..8684d0a 100644
--- a/config/compiled-classes-phone
+++ b/config/compiled-classes-phone
@@ -5187,7 +5187,7 @@
 com.android.ims.internal.IImsService
 com.android.ims.internal.IImsService$Stub
 com.android.ims.internal.IImsServiceController
-com.android.ims.internal.IImsServiceFeatureListener
+com.android.ims.internal.IImsServiceFeatureCallback
 com.android.ims.internal.IImsUt
 com.android.ims.internal.IImsUt$Stub
 com.android.ims.internal.IImsUtListener
diff --git a/core/java/Android.bp b/core/java/Android.bp
index 1503445..d8c7929 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -3,7 +3,26 @@
     srcs: ["android/security/keymaster/IKeyAttestationApplicationIdProvider.aidl"],
 }
 
-filegroup {
-    name: "IKeystoreService.aidl",
+// only used by key_store_service
+cc_library_static {
+    name: "libkeystore_aidl",
     srcs: ["android/security/IKeystoreService.aidl"],
+    aidl: {
+        export_aidl_headers: true,
+        include_dirs: ["frameworks/base/core/java/"],
+    },
+    header_libs: [
+        "libkeystore_headers",
+    ],
+    shared_libs: [
+        "libbinder",
+        "libcutils",
+        "libhardware",
+        "libhidlbase",
+        "libhidltransport",
+        "libhwbinder",
+        "liblog",
+        "libselinux",
+        "libutils",
+    ],
 }
diff --git a/core/java/android/accessibilityservice/AccessibilityService.java b/core/java/android/accessibilityservice/AccessibilityService.java
index a558d68..8824643 100644
--- a/core/java/android/accessibilityservice/AccessibilityService.java
+++ b/core/java/android/accessibilityservice/AccessibilityService.java
@@ -358,6 +358,11 @@
      */
     public static final int GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN = 7;
 
+    /**
+     * Action to lock the screen
+     */
+    public static final int GLOBAL_ACTION_LOCK_SCREEN = 8;
+
     private static final String LOG_TAG = "AccessibilityService";
 
     /**
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 064e978..02b7f8c 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -519,11 +519,15 @@
      * process that contains activities. */
     public static final int PROCESS_STATE_CACHED_ACTIVITY_CLIENT = 16;
 
+    /** @hide Process is being cached for later use and has an activity that corresponds
+     * to an existing recent task. */
+    public static final int PROCESS_STATE_CACHED_RECENT = 17;
+
     /** @hide Process is being cached for later use and is empty. */
-    public static final int PROCESS_STATE_CACHED_EMPTY = 17;
+    public static final int PROCESS_STATE_CACHED_EMPTY = 18;
 
     /** @hide Process does not exist. */
-    public static final int PROCESS_STATE_NONEXISTENT = 18;
+    public static final int PROCESS_STATE_NONEXISTENT = 19;
 
     // NOTE: If PROCESS_STATEs are added or changed, then new fields must be added
     // to frameworks/base/core/proto/android/app/activitymanager.proto and the following method must
diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl
index 9e926bd..d1aacad 100644
--- a/core/java/android/app/INotificationManager.aidl
+++ b/core/java/android/app/INotificationManager.aidl
@@ -72,6 +72,7 @@
     int getNumNotificationChannelsForPackage(String pkg, int uid, boolean includeDeleted);
     int getDeletedChannelCount(String pkg, int uid);
     void deleteNotificationChannelGroup(String pkg, String channelGroupId);
+    NotificationChannelGroup getNotificationChannelGroup(String pkg, String channelGroupId);
     ParceledListSlice getNotificationChannelGroups(String pkg);
     boolean onlyHasDefaultChannel(String pkg, int uid);
 
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index f931589..659cf16 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -93,6 +93,58 @@
     private static boolean localLOGV = false;
 
     /**
+     * Intent that is broadcast when a {@link NotificationChannel} is blocked
+     * (when {@link NotificationChannel#getImportance()} is {@link #IMPORTANCE_NONE}) or unblocked
+     * (when {@link NotificationChannel#getImportance()} is anything other than
+     * {@link #IMPORTANCE_NONE}).
+     *
+     * This broadcast is only sent to the app that owns the channel that has changed.
+     *
+     * Input: nothing
+     * Output: {@link #EXTRA_BLOCK_STATE_CHANGED_ID}
+     */
+    @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED =
+            "android.app.action.NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED";
+
+    /**
+     * Extra for {@link #ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED} or
+     * {@link #ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED} containing the id of the
+     * object which has a new blocked state.
+     *
+     * The value will be the {@link NotificationChannel#getId()} of the channel for
+     * {@link #ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED} and
+     * the {@link NotificationChannelGroup#getId()} of the group for
+     * {@link #ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED}.
+     */
+    public static final String EXTRA_BLOCK_STATE_CHANGED_ID =
+            "android.app.extra.BLOCK_STATE_CHANGED_ID";
+
+    /**
+     * Extra for {@link #ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED} or
+     * {@link #ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED} containing the new blocked
+     * state as a boolean.
+     *
+     * The value will be {@code true} if this channel or group is now blocked and {@code false} if
+     * this channel or group is now unblocked.
+     */
+    public static final String EXTRA_BLOCKED_STATE = "android.app.extra.BLOCKED_STATE";
+
+
+    /**
+     * Intent that is broadcast when a {@link NotificationChannelGroup} is
+     * {@link NotificationChannelGroup#isBlocked() blocked} or unblocked.
+     *
+     * This broadcast is only sent to the app that owns the channel group that has changed.
+     *
+     * Input: nothing
+     * Output: {@link #EXTRA_BLOCK_STATE_CHANGED_ID}
+     */
+    @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED =
+            "android.app.action.NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED";
+
+    /**
      * Intent that is broadcast when the state of {@link #getEffectsSuppressor()} changes.
      * This broadcast is only sent to registered receivers.
      *
@@ -504,6 +556,20 @@
     }
 
     /**
+     * Returns the notification channel group settings for a given channel group id.
+     *
+     * The channel group must belong to your package, or null will be returned.
+     */
+    public NotificationChannelGroup getNotificationChannelGroup(String channelGroupId) {
+        INotificationManager service = getService();
+        try {
+            return service.getNotificationChannelGroup(mContext.getPackageName(), channelGroupId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Returns all notification channel groups belonging to the calling app.
      */
     public List<NotificationChannelGroup> getNotificationChannelGroups() {
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index f0226b7..0bca969 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -3858,6 +3858,47 @@
      */
     public boolean installKeyPair(@Nullable ComponentName admin, @NonNull PrivateKey privKey,
             @NonNull Certificate[] certs, @NonNull String alias, boolean requestAccess) {
+        return installKeyPair(admin, privKey, certs, alias, requestAccess, true);
+    }
+
+    /**
+     * Called by a device or profile owner, or delegated certificate installer, to install a
+     * certificate chain and corresponding private key for the leaf certificate. All apps within the
+     * profile will be able to access the certificate chain and use the private key, given direct
+     * user approval (if the user is allowed to select the private key).
+     *
+     * <p>The caller of this API may grant itself access to the certificate and private key
+     * immediately, without user approval. It is a best practice not to request this unless strictly
+     * necessary since it opens up additional security vulnerabilities.
+     *
+     * <p>Whether this key is offered to the user for approval at all or not depends on the
+     * {@code isUserSelectable} parameter.
+     *
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with, or
+     *        {@code null} if calling from a delegated certificate installer.
+     * @param privKey The private key to install.
+     * @param certs The certificate chain to install. The chain should start with the leaf
+     *        certificate and include the chain of trust in order. This will be returned by
+     *        {@link android.security.KeyChain#getCertificateChain}.
+     * @param alias The private key alias under which to install the certificate. If a certificate
+     *        with that alias already exists, it will be overwritten.
+     * @param requestAccess {@code true} to request that the calling app be granted access to the
+     *        credentials immediately. Otherwise, access to the credentials will be gated by user
+     *        approval.
+     * @param isUserSelectable {@code true} to indicate that a user can select this key via the
+     *        Certificate Selection prompt, false to indicate that this key can only be granted
+     *        access by implementing
+     *        {@link android.app.admin.DeviceAdminReceiver#onChoosePrivateKeyAlias}.
+     * @return {@code true} if the keys were installed, {@code false} otherwise.
+     * @throws SecurityException if {@code admin} is not {@code null} and not a device or profile
+     *         owner.
+     * @see android.security.KeyChain#getCertificateChain
+     * @see #setDelegatedScopes
+     * @see #DELEGATION_CERT_INSTALL
+     */
+    public boolean installKeyPair(@Nullable ComponentName admin, @NonNull PrivateKey privKey,
+            @NonNull Certificate[] certs, @NonNull String alias, boolean requestAccess,
+            boolean isUserSelectable) {
         throwIfParentInstance("installKeyPair");
         try {
             final byte[] pemCert = Credentials.convertToPem(certs[0]);
@@ -3868,7 +3909,7 @@
             final byte[] pkcs8Key = KeyFactory.getInstance(privKey.getAlgorithm())
                     .getKeySpec(privKey, PKCS8EncodedKeySpec.class).getEncoded();
             return mService.installKeyPair(admin, mContext.getPackageName(), pkcs8Key, pemCert,
-                    pemChain, alias, requestAccess);
+                    pemChain, alias, requestAccess, isUserSelectable);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index be0b920..b7740e9 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -162,7 +162,8 @@
     boolean isCaCertApproved(in String alias, int userHandle);
 
     boolean installKeyPair(in ComponentName who, in String callerPackage, in byte[] privKeyBuffer,
-            in byte[] certBuffer, in byte[] certChainBuffer, String alias, boolean requestAccess);
+            in byte[] certBuffer, in byte[] certChainBuffer, String alias, boolean requestAccess,
+            boolean isUserSelectable);
     boolean removeKeyPair(in ComponentName who, in String callerPackage, String alias);
     void choosePrivateKeyAlias(int uid, in Uri uri, in String alias, IBinder aliasCallback);
 
diff --git a/core/java/android/app/job/JobInfo.java b/core/java/android/app/job/JobInfo.java
index 530d84b..7c40b4e 100644
--- a/core/java/android/app/job/JobInfo.java
+++ b/core/java/android/app/job/JobInfo.java
@@ -244,6 +244,13 @@
     public static final int FLAG_WILL_BE_FOREGROUND = 1 << 0;
 
     /**
+     * Allows this job to run despite doze restrictions as long as the app is in the foreground
+     * or on the temporary whitelist
+     * @hide
+     */
+    public static final int FLAG_IMPORTANT_WHILE_FOREGROUND = 1 << 1;
+
+    /**
      * @hide
      */
     public static final int CONSTRAINT_FLAG_CHARGING = 1 << 0;
@@ -1333,6 +1340,30 @@
         }
 
         /**
+         * Setting this to true indicates that this job is important while the scheduling app
+         * is in the foreground or on the temporary whitelist for background restrictions.
+         * This means that the system will relax doze restrictions on this job during this time.
+         *
+         * Apps should use this flag only for short jobs that are essential for the app to function
+         * properly in the foreground.
+         *
+         * Note that once the scheduling app is no longer whitelisted from background restrictions
+         * and in the background, or the job failed due to unsatisfied constraints,
+         * this job should be expected to behave like other jobs without this flag.
+         *
+         * @param importantWhileForeground whether to relax doze restrictions for this job when the
+         *                                 app is in the foreground. False by default.
+         */
+        public Builder setImportantWhileForeground(boolean importantWhileForeground) {
+            if (importantWhileForeground) {
+                mFlags |= FLAG_IMPORTANT_WHILE_FOREGROUND;
+            } else {
+                mFlags &= (~FLAG_IMPORTANT_WHILE_FOREGROUND);
+            }
+            return this;
+        }
+
+        /**
          * Set whether or not to persist this job across device reboots.
          *
          * @param isPersisted True to indicate that the job will be written to
@@ -1395,6 +1426,10 @@
                             "persisted job");
                 }
             }
+            if ((mFlags & FLAG_IMPORTANT_WHILE_FOREGROUND) != 0 && mHasEarlyConstraint) {
+                throw new IllegalArgumentException("An important while foreground job cannot "
+                        + "have a time delay");
+            }
             if (mBackoffPolicySet && (mConstraintFlags & CONSTRAINT_FLAG_DEVICE_IDLE) != 0) {
                 throw new IllegalArgumentException("An idle mode job will not respect any" +
                         " back-off policy, so calling setBackoffCriteria with" +
diff --git a/core/java/android/content/CursorLoader.java b/core/java/android/content/CursorLoader.java
index 33386e5..7f24c51 100644
--- a/core/java/android/content/CursorLoader.java
+++ b/core/java/android/content/CursorLoader.java
@@ -145,7 +145,7 @@
     }
 
     /**
-     * Starts an asynchronous load of the contacts list data. When the result is ready the callbacks
+     * Starts an asynchronous load of the data. When the result is ready the callbacks
      * will be called on the UI thread. If a previous load has been completed and is still valid
      * the result may be passed to the callbacks immediately.
      *
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 2034280..edb27cd 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -19,6 +19,7 @@
 import static android.os.Build.VERSION_CODES.DONUT;
 
 import android.annotation.IntDef;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.content.Context;
@@ -890,6 +891,29 @@
     public int versionCode;
 
     /**
+     * The user-visible SDK version (ex. 26) of the framework against which the application claims
+     * to have been compiled, or {@code 0} if not specified.
+     * <p>
+     * This property is the compile-time equivalent of
+     * {@link android.os.Build.VERSION#CODENAME Build.VERSION.SDK_INT}.
+     *
+     * @hide For platform use only; we don't expect developers to need to read this value.
+     */
+    public int compileSdkVersion;
+
+    /**
+     * The development codename (ex. "O", "REL") of the framework against which the application
+     * claims to have been compiled, or {@code null} if not specified.
+     * <p>
+     * This property is the compile-time equivalent of
+     * {@link android.os.Build.VERSION#CODENAME Build.VERSION.CODENAME}.
+     *
+     * @hide For platform use only; we don't expect developers to need to read this value.
+     */
+    @Nullable
+    public String compileSdkVersionCodename;
+
+    /**
      * When false, indicates that all components within this application are
      * considered disabled, regardless of their individually set enabled status.
      */
@@ -1305,6 +1329,8 @@
         dest.writeInt(targetSandboxVersion);
         dest.writeString(classLoaderName);
         dest.writeStringArray(splitClassLoaderNames);
+        dest.writeInt(compileSdkVersion);
+        dest.writeString(compileSdkVersionCodename);
     }
 
     public static final Parcelable.Creator<ApplicationInfo> CREATOR
@@ -1372,6 +1398,8 @@
         targetSandboxVersion = source.readInt();
         classLoaderName = source.readString();
         splitClassLoaderNames = source.readStringArray();
+        compileSdkVersion = source.readInt();
+        compileSdkVersionCodename = source.readString();
     }
 
     /**
diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java
index ba488f6..f8889b6 100644
--- a/core/java/android/content/pm/PackageInfo.java
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -16,6 +16,7 @@
 
 package android.content.pm;
 
+import android.annotation.Nullable;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -289,6 +290,29 @@
     /** @hide */
     public boolean isStaticOverlay;
 
+    /**
+     * The user-visible SDK version (ex. 26) of the framework against which the application claims
+     * to have been compiled, or {@code 0} if not specified.
+     * <p>
+     * This property is the compile-time equivalent of
+     * {@link android.os.Build.VERSION#SDK_INT Build.VERSION.SDK_INT}.
+     *
+     * @hide For platform use only; we don't expect developers to need to read this value.
+     */
+    public int compileSdkVersion;
+
+    /**
+     * The development codename (ex. "O", "REL") of the framework against which the application
+     * claims to have been compiled, or {@code null} if not specified.
+     * <p>
+     * This property is the compile-time equivalent of
+     * {@link android.os.Build.VERSION#CODENAME Build.VERSION.CODENAME}.
+     *
+     * @hide For platform use only; we don't expect developers to need to read this value.
+     */
+    @Nullable
+    public String compileSdkVersionCodename;
+
     public PackageInfo() {
     }
 
@@ -344,6 +368,8 @@
         dest.writeString(overlayTarget);
         dest.writeInt(isStaticOverlay ? 1 : 0);
         dest.writeInt(overlayPriority);
+        dest.writeInt(compileSdkVersion);
+        dest.writeString(compileSdkVersionCodename);
     }
 
     public static final Parcelable.Creator<PackageInfo> CREATOR
@@ -396,6 +422,8 @@
         overlayTarget = source.readString();
         isStaticOverlay = source.readInt() != 0;
         overlayPriority = source.readInt();
+        compileSdkVersion = source.readInt();
+        compileSdkVersionCodename = source.readString();
 
         // The component lists were flattened with the redundant ApplicationInfo
         // instances omitted.  Distribute the canonical one here as appropriate.
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 1c5cf15..d9ac681 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -678,6 +678,8 @@
         pi.overlayTarget = p.mOverlayTarget;
         pi.overlayPriority = p.mOverlayPriority;
         pi.isStaticOverlay = p.mIsStaticOverlay;
+        pi.compileSdkVersion = p.mCompileSdkVersion;
+        pi.compileSdkVersionCodename = p.mCompileSdkVersionCodename;
         pi.firstInstallTime = firstInstallTime;
         pi.lastUpdateTime = lastUpdateTime;
         if ((flags&PackageManager.GET_GIDS) != 0) {
@@ -2076,6 +2078,16 @@
 
         pkg.coreApp = parser.getAttributeBooleanValue(null, "coreApp", false);
 
+        pkg.mCompileSdkVersion = sa.getInteger(
+                com.android.internal.R.styleable.AndroidManifest_compileSdkVersion, 0);
+        pkg.applicationInfo.compileSdkVersion = pkg.mCompileSdkVersion;
+        pkg.mCompileSdkVersionCodename = sa.getNonConfigurationString(
+                com.android.internal.R.styleable.AndroidManifest_compileSdkVersionCodename, 0);
+        if (pkg.mCompileSdkVersionCodename != null) {
+            pkg.mCompileSdkVersionCodename = pkg.mCompileSdkVersionCodename.intern();
+        }
+        pkg.applicationInfo.compileSdkVersionCodename = pkg.mCompileSdkVersionCodename;
+
         sa.recycle();
 
         return parseBaseApkCommon(pkg, null, res, parser, flags, outError);
@@ -5967,6 +5979,9 @@
         public boolean mIsStaticOverlay;
         public boolean mTrustedOverlay;
 
+        public int mCompileSdkVersion;
+        public String mCompileSdkVersionCodename;
+
         /**
          * Data used to feed the KeySetManagerService
          */
@@ -6458,6 +6473,8 @@
             mOverlayPriority = dest.readInt();
             mIsStaticOverlay = (dest.readInt() == 1);
             mTrustedOverlay = (dest.readInt() == 1);
+            mCompileSdkVersion = dest.readInt();
+            mCompileSdkVersionCodename = dest.readString();
             mSigningKeys = (ArraySet<PublicKey>) dest.readArraySet(boot);
             mUpgradeKeySets = (ArraySet<String>) dest.readArraySet(boot);
 
@@ -6581,6 +6598,8 @@
             dest.writeInt(mOverlayPriority);
             dest.writeInt(mIsStaticOverlay ? 1 : 0);
             dest.writeInt(mTrustedOverlay ? 1 : 0);
+            dest.writeInt(mCompileSdkVersion);
+            dest.writeString(mCompileSdkVersionCodename);
             dest.writeArraySet(mSigningKeys);
             dest.writeArraySet(mUpgradeKeySets);
             writeKeySetMapping(dest, mKeySetMapping);
diff --git a/core/java/android/database/sqlite/SQLiteConnectionPool.java b/core/java/android/database/sqlite/SQLiteConnectionPool.java
index 8b0fef4..5adb119 100644
--- a/core/java/android/database/sqlite/SQLiteConnectionPool.java
+++ b/core/java/android/database/sqlite/SQLiteConnectionPool.java
@@ -570,6 +570,16 @@
         mAvailableNonPrimaryConnections.clear();
     }
 
+    /**
+     * Close non-primary connections that are not currently in use. This method is safe to use
+     * in finalize block as it doesn't throw RuntimeExceptions.
+     */
+    void closeAvailableNonPrimaryConnectionsAndLogExceptions() {
+        synchronized (mLock) {
+            closeAvailableNonPrimaryConnectionsAndLogExceptionsLocked();
+        }
+    }
+
     // Can't throw.
     private void closeExcessConnectionsAndLogExceptionsLocked() {
         int availableCount = mAvailableNonPrimaryConnections.size();
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index 863fb19..09bb9c6 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -1740,7 +1740,8 @@
     private int executeSql(String sql, Object[] bindArgs) throws SQLException {
         acquireReference();
         try {
-            if (DatabaseUtils.getSqlStatementType(sql) == DatabaseUtils.STATEMENT_ATTACH) {
+            final int statementType = DatabaseUtils.getSqlStatementType(sql);
+            if (statementType == DatabaseUtils.STATEMENT_ATTACH) {
                 boolean disableWal = false;
                 synchronized (mLock) {
                     if (!mHasAttachedDbsLocked) {
@@ -1754,11 +1755,14 @@
                 }
             }
 
-            SQLiteStatement statement = new SQLiteStatement(this, sql, bindArgs);
-            try {
+            try (SQLiteStatement statement = new SQLiteStatement(this, sql, bindArgs)) {
                 return statement.executeUpdateDelete();
             } finally {
-                statement.close();
+                // If schema was updated, close non-primary connections, otherwise they might
+                // have outdated schema information
+                if (statementType == DatabaseUtils.STATEMENT_DDL) {
+                    mConnectionPoolLocked.closeAvailableNonPrimaryConnectionsAndLogExceptions();
+                }
             }
         } finally {
             releaseReference();
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index e845359..cd551bd 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -174,6 +174,11 @@
     public abstract boolean isUidPresentOnDisplay(int uid, int displayId);
 
     /**
+     * Persist brightness slider events.
+     */
+    public abstract void persistBrightnessSliderEvents();
+
+    /**
      * Describes the requested power state of the display.
      *
      * This object is intended to describe the general characteristics of the
diff --git a/core/java/android/hardware/usb/IUsbManager.aidl b/core/java/android/hardware/usb/IUsbManager.aidl
index 025d46d..151e62d 100644
--- a/core/java/android/hardware/usb/IUsbManager.aidl
+++ b/core/java/android/hardware/usb/IUsbManager.aidl
@@ -34,7 +34,7 @@
     /* Returns a file descriptor for communicating with the USB device.
      * The native fd can be passed to usb_device_new() in libusbhost.
      */
-    ParcelFileDescriptor openDevice(String deviceName);
+    ParcelFileDescriptor openDevice(String deviceName, String packageName);
 
     /* Returns the currently attached USB accessory */
     UsbAccessory getCurrentAccessory();
@@ -55,7 +55,7 @@
     void setAccessoryPackage(in UsbAccessory accessory, String packageName, int userId);
 
     /* Returns true if the caller has permission to access the device. */
-    boolean hasDevicePermission(in UsbDevice device);
+    boolean hasDevicePermission(in UsbDevice device, String packageName);
 
     /* Returns true if the caller has permission to access the accessory. */
     boolean hasAccessoryPermission(in UsbAccessory accessory);
diff --git a/core/java/android/hardware/usb/UsbManager.java b/core/java/android/hardware/usb/UsbManager.java
index 6ce9669..bdb90bc 100644
--- a/core/java/android/hardware/usb/UsbManager.java
+++ b/core/java/android/hardware/usb/UsbManager.java
@@ -344,7 +344,7 @@
     public UsbDeviceConnection openDevice(UsbDevice device) {
         try {
             String deviceName = device.getDeviceName();
-            ParcelFileDescriptor pfd = mService.openDevice(deviceName);
+            ParcelFileDescriptor pfd = mService.openDevice(deviceName, mContext.getPackageName());
             if (pfd != null) {
                 UsbDeviceConnection connection = new UsbDeviceConnection(device);
                 boolean result = connection.open(deviceName, pfd, mContext);
@@ -400,6 +400,9 @@
      * Permission might have been granted temporarily via
      * {@link #requestPermission(UsbDevice, PendingIntent)} or
      * by the user choosing the caller as the default application for the device.
+     * Permission for USB devices of class {@link UsbConstants#USB_CLASS_VIDEO} for clients that
+     * target SDK {@link android.os.Build.VERSION_CODES#P} and above can be granted only if they
+     * have additionally the {@link android.Manifest.permission#CAMERA} permission.
      *
      * @param device to check permissions for
      * @return true if caller has permission
@@ -409,7 +412,7 @@
             return false;
         }
         try {
-            return mService.hasDevicePermission(device);
+            return mService.hasDevicePermission(device, mContext.getPackageName());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -450,6 +453,10 @@
      * permission was granted by the user
      * </ul>
      *
+     * Permission for USB devices of class {@link UsbConstants#USB_CLASS_VIDEO} for clients that
+     * target SDK {@link android.os.Build.VERSION_CODES#P} and above can be granted only if they
+     * have additionally the {@link android.Manifest.permission#CAMERA} permission.
+     *
      * @param device to request permissions for
      * @param pi PendingIntent for returning result
      */
diff --git a/core/java/android/net/IpSecAlgorithm.java b/core/java/android/net/IpSecAlgorithm.java
index 64f8f39..d6e62cf 100644
--- a/core/java/android/net/IpSecAlgorithm.java
+++ b/core/java/android/net/IpSecAlgorithm.java
@@ -15,6 +15,7 @@
  */
 package android.net;
 
+import android.annotation.NonNull;
 import android.annotation.StringDef;
 import android.os.Build;
 import android.os.Parcel;
@@ -27,8 +28,10 @@
 import java.util.Arrays;
 
 /**
- * IpSecAlgorithm specifies a single algorithm that can be applied to an IpSec Transform. Refer to
- * RFC 4301.
+ * This class represents a single algorithm that can be used by an {@link IpSecTransform}.
+ *
+ * @see <a href="https://tools.ietf.org/html/rfc4301">RFC 4301, Security Architecture for the
+ * Internet Protocol</a>
  */
 public final class IpSecAlgorithm implements Parcelable {
     /**
@@ -39,16 +42,16 @@
     public static final String CRYPT_AES_CBC = "cbc(aes)";
 
     /**
-     * MD5 HMAC Authentication/Integrity Algorithm. This algorithm is not recommended for use in new
-     * applications and is provided for legacy compatibility with 3gpp infrastructure.
+     * MD5 HMAC Authentication/Integrity Algorithm. <b>This algorithm is not recommended for use in
+     * new applications and is provided for legacy compatibility with 3gpp infrastructure.</b>
      *
      * <p>Valid truncation lengths are multiples of 8 bits from 96 to (default) 128.
      */
     public static final String AUTH_HMAC_MD5 = "hmac(md5)";
 
     /**
-     * SHA1 HMAC Authentication/Integrity Algorithm. This algorithm is not recommended for use in
-     * new applications and is provided for legacy compatibility with 3gpp infrastructure.
+     * SHA1 HMAC Authentication/Integrity Algorithm. <b>This algorithm is not recommended for use in
+     * new applications and is provided for legacy compatibility with 3gpp infrastructure.</b>
      *
      * <p>Valid truncation lengths are multiples of 8 bits from 96 to (default) 160.
      */
@@ -69,7 +72,7 @@
     public static final String AUTH_HMAC_SHA384 = "hmac(sha384)";
 
     /**
-     * SHA512 HMAC Authentication/Integrity Algorithm
+     * SHA512 HMAC Authentication/Integrity Algorithm.
      *
      * <p>Valid truncation lengths are multiples of 8 bits from 256 to (default) 512.
      */
@@ -80,9 +83,9 @@
      *
      * <p>Valid lengths for keying material are {160, 224, 288}.
      *
-     * <p>As per RFC4106 (Section 8.1), keying material consists of a 128, 192, or 256 bit AES key
-     * followed by a 32-bit salt. RFC compliance requires that the salt must be unique per
-     * invocation with the same key.
+     * <p>As per <a href="https://tools.ietf.org/html/rfc4106#section-8.1">RFC4106 (Section
+     * 8.1)</a>, keying material consists of a 128, 192, or 256 bit AES key followed by a 32-bit
+     * salt. RFC compliance requires that the salt must be unique per invocation with the same key.
      *
      * <p>Valid ICV (truncation) lengths are {64, 96, 128}.
      */
@@ -105,48 +108,47 @@
     private final int mTruncLenBits;
 
     /**
-     * Specify a IpSecAlgorithm of one of the supported types including the truncation length of the
-     * algorithm
+     * Creates an IpSecAlgorithm of one of the supported types. Supported algorithm names are
+     * defined as constants in this class.
      *
-     * @param algorithm type for IpSec.
-     * @param key non-null Key padded to a multiple of 8 bits.
+     * @param algorithm name of the algorithm.
+     * @param key key padded to a multiple of 8 bits.
      */
-    public IpSecAlgorithm(String algorithm, byte[] key) {
+    public IpSecAlgorithm(@AlgorithmName String algorithm, @NonNull byte[] key) {
         this(algorithm, key, key.length * 8);
     }
 
     /**
-     * Specify a IpSecAlgorithm of one of the supported types including the truncation length of the
-     * algorithm
+     * Creates an IpSecAlgorithm of one of the supported types. Supported algorithm names are
+     * defined as constants in this class.
      *
-     * @param algoName precise name of the algorithm to be used.
-     * @param key non-null Key padded to a multiple of 8 bits.
-     * @param truncLenBits the number of bits of output hash to use; only meaningful for
-     *     Authentication or Authenticated Encryption (equivalent to ICV length).
+     * <p>This constructor only supports algorithms that use a truncation length. i.e.
+     * Authentication and Authenticated Encryption algorithms.
+     *
+     * @param algorithm name of the algorithm.
+     * @param key key padded to a multiple of 8 bits.
+     * @param truncLenBits number of bits of output hash to use.
      */
-    public IpSecAlgorithm(@AlgorithmName String algoName, byte[] key, int truncLenBits) {
-        if (!isTruncationLengthValid(algoName, truncLenBits)) {
+    public IpSecAlgorithm(@AlgorithmName String algorithm, @NonNull byte[] key, int truncLenBits) {
+        if (!isTruncationLengthValid(algorithm, truncLenBits)) {
             throw new IllegalArgumentException("Unknown algorithm or invalid length");
         }
-        mName = algoName;
+        mName = algorithm;
         mKey = key.clone();
         mTruncLenBits = Math.min(truncLenBits, key.length * 8);
     }
 
-    /** Retrieve the algorithm name */
+    /** Get the algorithm name */
     public String getName() {
         return mName;
     }
 
-    /** Retrieve the key for this algorithm */
+    /** Get the key for this algorithm */
     public byte[] getKey() {
         return mKey.clone();
     }
 
-    /**
-     * Retrieve the truncation length, in bits, for the key in this algo. By default this will be
-     * the length in bits of the key.
-     */
+    /** Get the truncation length of this algorithm, in bits */
     public int getTruncationLengthBits() {
         return mTruncLenBits;
     }
diff --git a/core/java/android/net/IpSecConfig.java b/core/java/android/net/IpSecConfig.java
index 61b13a9..e6cd3fc 100644
--- a/core/java/android/net/IpSecConfig.java
+++ b/core/java/android/net/IpSecConfig.java
@@ -20,7 +20,12 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 
-/** @hide */
+/**
+ * This class encapsulates all the configuration parameters needed to create IPsec transforms and
+ * policies.
+ *
+ * @hide
+ */
 public final class IpSecConfig implements Parcelable {
     private static final String TAG = "IpSecConfig";
 
@@ -38,6 +43,9 @@
     // for outbound packets. It may also be used to select packets.
     private Network mNetwork;
 
+    /**
+     * This class captures the parameters that specifically apply to inbound or outbound traffic.
+     */
     public static class Flow {
         // Minimum requirements for identifying a transform
         // SPI identifying the IPsec flow in packet processing
diff --git a/core/java/android/net/IpSecManager.java b/core/java/android/net/IpSecManager.java
index eccd5f4..a9e60ec 100644
--- a/core/java/android/net/IpSecManager.java
+++ b/core/java/android/net/IpSecManager.java
@@ -19,6 +19,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.SystemService;
+import android.annotation.TestApi;
 import android.content.Context;
 import android.os.Binder;
 import android.os.ParcelFileDescriptor;
@@ -37,22 +38,28 @@
 import java.net.Socket;
 
 /**
- * This class contains methods for managing IPsec sessions, which will perform kernel-space
- * encryption and decryption of socket or Network traffic.
+ * This class contains methods for managing IPsec sessions. Once configured, the kernel will apply
+ * confidentiality (encryption) and integrity (authentication) to IP traffic.
  *
- * <p>An IpSecManager may be obtained by calling {@link
- * android.content.Context#getSystemService(String) Context#getSystemService(String)} with {@link
- * android.content.Context#IPSEC_SERVICE Context#IPSEC_SERVICE}
+ * <p>Note that not all aspects of IPsec are permitted by this API. Applications may create
+ * transport mode security associations and apply them to individual sockets. Applications looking
+ * to create a VPN should use {@link VpnService}.
+ *
+ * @see <a href="https://tools.ietf.org/html/rfc4301">RFC 4301, Security Architecture for the
+ * Internet Protocol</a>
  */
 @SystemService(Context.IPSEC_SERVICE)
 public final class IpSecManager {
     private static final String TAG = "IpSecManager";
 
     /**
-     * The Security Parameter Index, SPI, 0 indicates an unknown or invalid index.
+     * The Security Parameter Index (SPI) 0 indicates an unknown or invalid index.
      *
      * <p>No IPsec packet may contain an SPI of 0.
+     *
+     * @hide
      */
+    @TestApi
     public static final int INVALID_SECURITY_PARAMETER_INDEX = 0;
 
     /** @hide */
@@ -66,10 +73,12 @@
     public static final int INVALID_RESOURCE_ID = 0;
 
     /**
-     * Indicates that the combination of remote InetAddress and SPI was non-unique for a given
-     * request. If encountered, selection of a new SPI is required before a transform may be
-     * created. Note, this should happen very rarely if the SPI is chosen to be sufficiently random
-     * or reserved using reserveSecurityParameterIndex.
+     * Thrown to indicate that a requested SPI is in use.
+     *
+     * <p>The combination of remote {@code InetAddress} and SPI must be unique across all apps on
+     * one device. If this error is encountered, a new SPI is required before a transform may be
+     * created. This error can be avoided by calling {@link
+     * IpSecManager#reserveSecurityParameterIndex}.
      */
     public static final class SpiUnavailableException extends AndroidException {
         private final int mSpi;
@@ -78,24 +87,26 @@
          * Construct an exception indicating that a transform with the given SPI is already in use
          * or otherwise unavailable.
          *
-         * @param msg Description indicating the colliding SPI
+         * @param msg description indicating the colliding SPI
          * @param spi the SPI that could not be used due to a collision
          */
         SpiUnavailableException(String msg, int spi) {
-            super(msg + "(spi: " + spi + ")");
+            super(msg + " (spi: " + spi + ")");
             mSpi = spi;
         }
 
-        /** Retrieve the SPI that caused a collision */
+        /** Get the SPI that caused a collision. */
         public int getSpi() {
             return mSpi;
         }
     }
 
     /**
-     * Indicates that the requested system resource for IPsec, such as a socket or other system
-     * resource is unavailable. If this exception is thrown, try releasing allocated objects of the
-     * type requested.
+     * Thrown to indicate that an IPsec resource is unavailable.
+     *
+     * <p>This could apply to resources such as sockets, {@link SecurityParameterIndex}, {@link
+     * IpSecTransform}, or other system resources. If this exception is thrown, users should release
+     * allocated objects of the type requested.
      */
     public static final class ResourceUnavailableException extends AndroidException {
 
@@ -106,6 +117,13 @@
 
     private final IIpSecService mService;
 
+    /**
+     * This class represents a reserved SPI.
+     *
+     * <p>Objects of this type are used to track reserved security parameter indices. They can be
+     * obtained by calling {@link IpSecManager#reserveSecurityParameterIndex} and must be released
+     * by calling {@link #close()} when they are no longer needed.
+     */
     public static final class SecurityParameterIndex implements AutoCloseable {
         private final IIpSecService mService;
         private final InetAddress mRemoteAddress;
@@ -113,7 +131,7 @@
         private int mSpi = INVALID_SECURITY_PARAMETER_INDEX;
         private int mResourceId;
 
-        /** Return the underlying SPI held by this object */
+        /** Get the underlying SPI held by this object. */
         public int getSpi() {
             return mSpi;
         }
@@ -135,6 +153,7 @@
             mCloseGuard.close();
         }
 
+        /** Check that the SPI was closed properly. */
         @Override
         protected void finalize() throws Throwable {
             if (mCloseGuard != null) {
@@ -197,13 +216,13 @@
     }
 
     /**
-     * Reserve an SPI for traffic bound towards the specified remote address.
+     * Reserve a random SPI for traffic bound to or from the specified remote address.
      *
      * <p>If successful, this SPI is guaranteed available until released by a call to {@link
      * SecurityParameterIndex#close()}.
      *
      * @param direction {@link IpSecTransform#DIRECTION_IN} or {@link IpSecTransform#DIRECTION_OUT}
-     * @param remoteAddress address of the remote. SPIs must be unique for each remoteAddress.
+     * @param remoteAddress address of the remote. SPIs must be unique for each remoteAddress
      * @return the reserved SecurityParameterIndex
      * @throws ResourceUnavailableException indicating that too many SPIs are currently allocated
      *     for this user
@@ -223,17 +242,18 @@
     }
 
     /**
-     * Reserve an SPI for traffic bound towards the specified remote address.
+     * Reserve the requested SPI for traffic bound to or from the specified remote address.
      *
      * <p>If successful, this SPI is guaranteed available until released by a call to {@link
      * SecurityParameterIndex#close()}.
      *
      * @param direction {@link IpSecTransform#DIRECTION_IN} or {@link IpSecTransform#DIRECTION_OUT}
-     * @param remoteAddress address of the remote. SPIs must be unique for each remoteAddress.
-     * @param requestedSpi the requested SPI, or '0' to allocate a random SPI.
+     * @param remoteAddress address of the remote. SPIs must be unique for each remoteAddress
+     * @param requestedSpi the requested SPI, or '0' to allocate a random SPI
      * @return the reserved SecurityParameterIndex
      * @throws ResourceUnavailableException indicating that too many SPIs are currently allocated
      *     for this user
+     * @throws SpiUnavailableException indicating that the requested SPI could not be reserved
      */
     public SecurityParameterIndex reserveSecurityParameterIndex(
             int direction, InetAddress remoteAddress, int requestedSpi)
@@ -245,16 +265,28 @@
     }
 
     /**
-     * Apply an active Transport Mode IPsec Transform to a stream socket to perform IPsec
-     * encapsulation of the traffic flowing between the socket and the remote InetAddress of that
-     * transform. For security reasons, attempts to send traffic to any IP address other than the
-     * address associated with that transform will throw an IOException. In addition, if the
-     * IpSecTransform is later deactivated, the socket will throw an IOException on any calls to
-     * send() or receive() until the transform is removed from the socket by calling {@link
-     * #removeTransportModeTransform(Socket, IpSecTransform)};
+     * Apply an IPsec transform to a stream socket.
+     *
+     * <p>This applies transport mode encapsulation to the given socket. Once applied, I/O on the
+     * socket will be encapsulated according to the parameters of the {@code IpSecTransform}. When
+     * the transform is removed from the socket by calling {@link #removeTransportModeTransform},
+     * unprotected traffic can resume on that socket.
+     *
+     * <p>For security reasons, the destination address of any traffic on the socket must match the
+     * remote {@code  InetAddress} of the {@code IpSecTransform}. Attempts to send traffic to any
+     * other IP address will result in an IOException. In addition, reads and writes on the socket
+     * will throw IOException if the user deactivates the transform (by calling {@link
+     * IpSecTransform#close()}) without calling {@link #removeTransportModeTransform}.
+     *
+     * <h4>Rekey Procedure</h4> <p>When applying a new tranform to a socket, the previous transform
+     * will be removed. However, inbound traffic on the old transform will continue to be decrypted
+     * until that transform is deallocated by calling {@link IpSecTransform#close()}. This overlap
+     * allows rekey procedures where both transforms are valid until both endpoints are using the
+     * new transform and all in-flight packets have been received.
      *
      * @param socket a stream socket
-     * @param transform an {@link IpSecTransform}, which must be an active Transport Mode transform.
+     * @param transform a transport mode {@code IpSecTransform}
+     * @throws IOException indicating that the transform could not be applied
      * @hide
      */
     public void applyTransportModeTransform(Socket socket, IpSecTransform transform)
@@ -265,16 +297,28 @@
     }
 
     /**
-     * Apply an active Transport Mode IPsec Transform to a datagram socket to perform IPsec
-     * encapsulation of the traffic flowing between the socket and the remote InetAddress of that
-     * transform. For security reasons, attempts to send traffic to any IP address other than the
-     * address associated with that transform will throw an IOException. In addition, if the
-     * IpSecTransform is later deactivated, the socket will throw an IOException on any calls to
-     * send() or receive() until the transform is removed from the socket by calling {@link
-     * #removeTransportModeTransform(DatagramSocket, IpSecTransform)};
+     * Apply an IPsec transform to a datagram socket.
+     *
+     * <p>This applies transport mode encapsulation to the given socket. Once applied, I/O on the
+     * socket will be encapsulated according to the parameters of the {@code IpSecTransform}. When
+     * the transform is removed from the socket by calling {@link #removeTransportModeTransform},
+     * unprotected traffic can resume on that socket.
+     *
+     * <p>For security reasons, the destination address of any traffic on the socket must match the
+     * remote {@code InetAddress} of the {@code IpSecTransform}. Attempts to send traffic to any
+     * other IP address will result in an IOException. In addition, reads and writes on the socket
+     * will throw IOException if the user deactivates the transform (by calling {@link
+     * IpSecTransform#close()}) without calling {@link #removeTransportModeTransform}.
+     *
+     * <h4>Rekey Procedure</h4> <p>When applying a new tranform to a socket, the previous transform
+     * will be removed. However, inbound traffic on the old transform will continue to be decrypted
+     * until that transform is deallocated by calling {@link IpSecTransform#close()}. This overlap
+     * allows rekey procedures where both transforms are valid until both endpoints are using the
+     * new transform and all in-flight packets have been received.
      *
      * @param socket a datagram socket
-     * @param transform an {@link IpSecTransform}, which must be an active Transport Mode transform.
+     * @param transform a transport mode {@code IpSecTransform}
+     * @throws IOException indicating that the transform could not be applied
      * @hide
      */
     public void applyTransportModeTransform(DatagramSocket socket, IpSecTransform transform)
@@ -285,16 +329,28 @@
     }
 
     /**
-     * Apply an active Transport Mode IPsec Transform to a stream socket to perform IPsec
-     * encapsulation of the traffic flowing between the socket and the remote InetAddress of that
-     * transform. For security reasons, attempts to send traffic to any IP address other than the
-     * address associated with that transform will throw an IOException. In addition, if the
-     * IpSecTransform is later deactivated, the socket will throw an IOException on any calls to
-     * send() or receive() until the transform is removed from the socket by calling {@link
-     * #removeTransportModeTransform(FileDescriptor, IpSecTransform)};
+     * Apply an IPsec transform to a socket.
+     *
+     * <p>This applies transport mode encapsulation to the given socket. Once applied, I/O on the
+     * socket will be encapsulated according to the parameters of the {@code IpSecTransform}. When
+     * the transform is removed from the socket by calling {@link #removeTransportModeTransform},
+     * unprotected traffic can resume on that socket.
+     *
+     * <p>For security reasons, the destination address of any traffic on the socket must match the
+     * remote {@code InetAddress} of the {@code IpSecTransform}. Attempts to send traffic to any
+     * other IP address will result in an IOException. In addition, reads and writes on the socket
+     * will throw IOException if the user deactivates the transform (by calling {@link
+     * IpSecTransform#close()}) without calling {@link #removeTransportModeTransform}.
+     *
+     * <h4>Rekey Procedure</h4> <p>When applying a new tranform to a socket, the previous transform
+     * will be removed. However, inbound traffic on the old transform will continue to be decrypted
+     * until that transform is deallocated by calling {@link IpSecTransform#close()}. This overlap
+     * allows rekey procedures where both transforms are valid until both endpoints are using the
+     * new transform and all in-flight packets have been received.
      *
      * @param socket a socket file descriptor
-     * @param transform an {@link IpSecTransform}, which must be an active Transport Mode transform.
+     * @param transform a transport mode {@code IpSecTransform}
+     * @throws IOException indicating that the transform could not be applied
      */
     public void applyTransportModeTransform(FileDescriptor socket, IpSecTransform transform)
             throws IOException {
@@ -323,6 +379,7 @@
      * Applications should probably not use this API directly. Instead, they should use {@link
      * VpnService} to provide VPN capability in a more generic fashion.
      *
+     * TODO: Update javadoc for tunnel mode APIs at the same time the APIs are re-worked.
      * @param net a {@link Network} that will be tunneled via IP Sec.
      * @param transform an {@link IpSecTransform}, which must be an active Tunnel Mode transform.
      * @hide
@@ -330,14 +387,19 @@
     public void applyTunnelModeTransform(Network net, IpSecTransform transform) {}
 
     /**
-     * Remove a transform from a given stream socket. Once removed, traffic on the socket will not
-     * be encypted. This allows sockets that have been used for IPsec to be reclaimed for
-     * communication in the clear in the event socket reuse is desired. This operation will succeed
-     * regardless of the underlying state of a transform. If a transform is removed, communication
-     * on all sockets to which that transform was applied will fail until this method is called.
+     * Remove an IPsec transform from a stream socket.
      *
-     * @param socket a socket that previously had a transform applied to it.
+     * <p>Once removed, traffic on the socket will not be encrypted. This operation will succeed
+     * regardless of the state of the transform. Removing a transform from a socket allows the
+     * socket to be reused for communication in the clear.
+     *
+     * <p>If an {@code IpSecTransform} object applied to this socket was deallocated by calling
+     * {@link IpSecTransform#close()}, then communication on the socket will fail until this method
+     * is called.
+     *
+     * @param socket a socket that previously had a transform applied to it
      * @param transform the IPsec Transform that was previously applied to the given socket
+     * @throws IOException indicating that the transform could not be removed from the socket
      * @hide
      */
     public void removeTransportModeTransform(Socket socket, IpSecTransform transform)
@@ -348,14 +410,19 @@
     }
 
     /**
-     * Remove a transform from a given datagram socket. Once removed, traffic on the socket will not
-     * be encypted. This allows sockets that have been used for IPsec to be reclaimed for
-     * communication in the clear in the event socket reuse is desired. This operation will succeed
-     * regardless of the underlying state of a transform. If a transform is removed, communication
-     * on all sockets to which that transform was applied will fail until this method is called.
+     * Remove an IPsec transform from a datagram socket.
      *
-     * @param socket a socket that previously had a transform applied to it.
+     * <p>Once removed, traffic on the socket will not be encrypted. This operation will succeed
+     * regardless of the state of the transform. Removing a transform from a socket allows the
+     * socket to be reused for communication in the clear.
+     *
+     * <p>If an {@code IpSecTransform} object applied to this socket was deallocated by calling
+     * {@link IpSecTransform#close()}, then communication on the socket will fail until this method
+     * is called.
+     *
+     * @param socket a socket that previously had a transform applied to it
      * @param transform the IPsec Transform that was previously applied to the given socket
+     * @throws IOException indicating that the transform could not be removed from the socket
      * @hide
      */
     public void removeTransportModeTransform(DatagramSocket socket, IpSecTransform transform)
@@ -366,14 +433,19 @@
     }
 
     /**
-     * Remove a transform from a given stream socket. Once removed, traffic on the socket will not
-     * be encypted. This allows sockets that have been used for IPsec to be reclaimed for
-     * communication in the clear in the event socket reuse is desired. This operation will succeed
-     * regardless of the underlying state of a transform. If a transform is removed, communication
-     * on all sockets to which that transform was applied will fail until this method is called.
+     * Remove an IPsec transform from a socket.
      *
-     * @param socket a socket file descriptor that previously had a transform applied to it.
+     * <p>Once removed, traffic on the socket will not be encrypted. This operation will succeed
+     * regardless of the state of the transform. Removing a transform from a socket allows the
+     * socket to be reused for communication in the clear.
+     *
+     * <p>If an {@code IpSecTransform} object applied to this socket was deallocated by calling
+     * {@link IpSecTransform#close()}, then communication on the socket will fail until this method
+     * is called.
+     *
+     * @param socket a socket that previously had a transform applied to it
      * @param transform the IPsec Transform that was previously applied to the given socket
+     * @throws IOException indicating that the transform could not be removed from the socket
      */
     public void removeTransportModeTransform(FileDescriptor socket, IpSecTransform transform)
             throws IOException {
@@ -382,7 +454,7 @@
         }
     }
 
-    /* Call down to activate a transform */
+    /* Call down to remove a transform */
     private void removeTransportModeTransform(ParcelFileDescriptor pfd, IpSecTransform transform) {
         try {
             mService.removeTransportModeTransform(pfd, transform.getResourceId());
@@ -397,6 +469,7 @@
      * all traffic that cannot be routed to the Tunnel's outbound interface. If that interface is
      * lost, all traffic will drop.
      *
+     * TODO: Update javadoc for tunnel mode APIs at the same time the APIs are re-worked.
      * @param net a network that currently has transform applied to it.
      * @param transform a Tunnel Mode IPsec Transform that has been previously applied to the given
      *     network
@@ -405,11 +478,18 @@
     public void removeTunnelModeTransform(Network net, IpSecTransform transform) {}
 
     /**
-     * Class providing access to a system-provided UDP Encapsulation Socket, which may be used for
-     * IKE signalling as well as for inbound and outbound UDP encapsulated IPsec traffic.
+     * This class provides access to a UDP encapsulation Socket.
      *
-     * <p>The socket provided by this class cannot be re-bound or closed via the inner
-     * FileDescriptor. Instead, disposing of this socket requires a call to close().
+     * <p>{@code UdpEncapsulationSocket} wraps a system-provided datagram socket intended for IKEv2
+     * signalling and UDP encapsulated IPsec traffic. Instances can be obtained by calling {@link
+     * IpSecManager#openUdpEncapsulationSocket}. The provided socket cannot be re-bound by the
+     * caller. The caller should not close the {@code FileDescriptor} returned by {@link
+     * #getSocket}, but should use {@link #close} instead.
+     *
+     * <p>Allowing the user to close or unbind a UDP encapsulation socket could impact the traffic
+     * of the next user who binds to that port. To prevent this scenario, these sockets are held
+     * open by the system so that they may only be closed by calling {@link #close} or when the user
+     * process exits.
      */
     public static final class UdpEncapsulationSocket implements AutoCloseable {
         private final ParcelFileDescriptor mPfd;
@@ -443,7 +523,7 @@
             mCloseGuard.open("constructor");
         }
 
-        /** Access the inner UDP Encapsulation Socket */
+        /** Get the wrapped socket. */
         public FileDescriptor getSocket() {
             if (mPfd == null) {
                 return null;
@@ -451,22 +531,19 @@
             return mPfd.getFileDescriptor();
         }
 
-        /** Retrieve the port number of the inner encapsulation socket */
+        /** Get the bound port of the wrapped socket. */
         public int getPort() {
             return mPort;
         }
 
-        @Override
         /**
-         * Release the resources that have been reserved for this Socket.
+         * Close this socket.
          *
-         * <p>This method closes the underlying socket, reducing a user's allocated sockets in the
-         * system. This must be done as part of cleanup following use of a socket. Failure to do so
-         * will cause the socket to count against a total allocation limit for IpSec and eventually
-         * fail due to resource limits.
-         *
-         * @param fd a file descriptor previously returned as a UDP Encapsulation socket.
+         * <p>This closes the wrapped socket. Open encapsulation sockets count against a user's
+         * resource limits, and forgetting to close them eventually will result in {@link
+         * ResourceUnavailableException} being thrown.
          */
+        @Override
         public void close() throws IOException {
             try {
                 mService.closeUdpEncapsulationSocket(mResourceId);
@@ -483,6 +560,7 @@
             mCloseGuard.close();
         }
 
+        /** Check that the socket was closed properly. */
         @Override
         protected void finalize() throws Throwable {
             if (mCloseGuard != null) {
@@ -499,21 +577,14 @@
     };
 
     /**
-     * Open a socket that is bound to a free UDP port on the system.
+     * Open a socket for UDP encapsulation and bind to the given port.
      *
-     * <p>By binding in this manner and holding the FileDescriptor, the socket cannot be un-bound by
-     * the caller. This provides safe access to a socket on a port that can later be used as a UDP
-     * Encapsulation port.
+     * <p>See {@link UdpEncapsulationSocket} for the proper way to close the returned socket.
      *
-     * <p>This socket reservation works in conjunction with IpSecTransforms, which may re-use the
-     * socket port. Explicitly opening this port is only necessary if communication is desired on
-     * that port.
-     *
-     * @param port a local UDP port to be reserved for UDP Encapsulation. is provided, then this
-     *     method will bind to the specified port or fail. To retrieve the port number, call {@link
-     *     android.system.Os#getsockname(FileDescriptor)}.
-     * @return a {@link UdpEncapsulationSocket} that is bound to the requested port for the lifetime
-     *     of the object.
+     * @param port a local UDP port
+     * @return a socket that is bound to the given port
+     * @throws IOException indicating that the socket could not be opened or bound
+     * @throws ResourceUnavailableException indicating that too many encapsulation sockets are open
      */
     // Returning a socket in this fashion that has been created and bound by the system
     // is the only safe way to ensure that a socket is both accessible to the user and
@@ -533,17 +604,16 @@
     }
 
     /**
-     * Open a socket that is bound to a port selected by the system.
+     * Open a socket for UDP encapsulation.
      *
-     * <p>By binding in this manner and holding the FileDescriptor, the socket cannot be un-bound by
-     * the caller. This provides safe access to a socket on a port that can later be used as a UDP
-     * Encapsulation port.
+     * <p>See {@link UdpEncapsulationSocket} for the proper way to close the returned socket.
      *
-     * <p>This socket reservation works in conjunction with IpSecTransforms, which may re-use the
-     * socket port. Explicitly opening this port is only necessary if communication is desired on
-     * that port.
+     * <p>The local port of the returned socket can be obtained by calling {@link
+     * UdpEncapsulationSocket#getPort()}.
      *
-     * @return a {@link UdpEncapsulationSocket} that is bound to an arbitrarily selected port
+     * @return a socket that is bound to a local port
+     * @throws IOException indicating that the socket could not be opened or bound
+     * @throws ResourceUnavailableException indicating that too many encapsulation sockets are open
      */
     // Returning a socket in this fashion that has been created and bound by the system
     // is the only safe way to ensure that a socket is both accessible to the user and
@@ -556,7 +626,7 @@
     }
 
     /**
-     * Retrieve an instance of an IpSecManager within you application context
+     * Construct an instance of IpSecManager within an application context.
      *
      * @param context the application context for this manager
      * @hide
diff --git a/core/java/android/net/IpSecTransform.java b/core/java/android/net/IpSecTransform.java
index 48b5bd5..cda4ec7 100644
--- a/core/java/android/net/IpSecTransform.java
+++ b/core/java/android/net/IpSecTransform.java
@@ -38,27 +38,29 @@
 import java.net.InetAddress;
 
 /**
- * This class represents an IpSecTransform, which encapsulates both properties and state of IPsec.
+ * This class represents an IPsec transform, which comprises security associations in one or both
+ * directions.
  *
- * <p>IpSecTransforms must be built from an IpSecTransform.Builder, and they must persist throughout
- * the lifetime of the underlying transform. If a transform object leaves scope, the underlying
- * transform may be disabled automatically, with likely undesirable results.
+ * <p>Transforms are created using {@link IpSecTransform.Builder}. Each {@code IpSecTransform}
+ * object encapsulates the properties and state of an inbound and outbound IPsec security
+ * association. That includes, but is not limited to, algorithm choice, key material, and allocated
+ * system resources.
  *
- * <p>An IpSecTransform may either represent a tunnel mode transform that operates on a wide array
- * of traffic or may represent a transport mode transform operating on a Socket or Sockets.
+ * @see <a href="https://tools.ietf.org/html/rfc4301">RFC 4301, Security Architecture for the
+ * Internet Protocol</a>
  */
 public final class IpSecTransform implements AutoCloseable {
     private static final String TAG = "IpSecTransform";
 
     /**
-     * For direction-specific attributes of an IpSecTransform, indicates that an attribute applies
-     * to traffic towards the host.
+     * For direction-specific attributes of an {@link IpSecTransform}, indicates that an attribute
+     * applies to traffic towards the host.
      */
     public static final int DIRECTION_IN = 0;
 
     /**
-     * For direction-specific attributes of an IpSecTransform, indicates that an attribute applies
-     * to traffic from the host.
+     * For direction-specific attributes of an {@link IpSecTransform}, indicates that an attribute
+     * applies to traffic from the host.
      */
     public static final int DIRECTION_OUT = 1;
 
@@ -77,16 +79,16 @@
     public static final int ENCAP_NONE = 0;
 
     /**
-     * IpSec traffic will be encapsulated within a UDP header with an additional 8-byte header pad
-     * (of '0'-value bytes) that prevents traffic from being interpreted as IKE or as ESP over UDP.
+     * IPsec traffic will be encapsulated within UDP, but with 8 zero-value bytes between the UDP
+     * header and payload. This prevents traffic from being interpreted as ESP or IKEv2.
      *
      * @hide
      */
     public static final int ENCAP_ESPINUDP_NON_IKE = 1;
 
     /**
-     * IpSec traffic will be encapsulated within UDP as per <a
-     * href="https://tools.ietf.org/html/rfc3948">RFC3498</a>.
+     * IPsec traffic will be encapsulated within UDP as per
+     * <a href="https://tools.ietf.org/html/rfc3948">RFC 3498</a>.
      *
      * @hide
      */
@@ -165,13 +167,14 @@
     }
 
     /**
-     * Deactivate an IpSecTransform and free all resources for that transform that are managed by
-     * the system for this Transform.
+     * Deactivate this {@code IpSecTransform} and free allocated resources.
      *
-     * <p>Deactivating a transform while it is still applied to any Socket will result in sockets
-     * refusing to send or receive data. This method will silently succeed if the specified
-     * transform has already been removed; thus, it is always safe to attempt cleanup when a
-     * transform is no longer needed.
+     * <p>Deactivating a transform while it is still applied to a socket will result in errors on
+     * that socket. Make sure to remove transforms by calling {@link
+     * IpSecManager#removeTransportModeTransform}. Note, removing an {@code IpSecTransform} from a
+     * socket will not deactivate it (because one transform may be applied to multiple sockets).
+     *
+     * <p>It is safe to call this method on a transform that has already been deactivated.
      */
     public void close() {
         Log.d(TAG, "Removing Transform with Id " + mResourceId);
@@ -197,6 +200,7 @@
         }
     }
 
+    /** Check that the transform was closed properly. */
     @Override
     protected void finalize() throws Throwable {
         if (mCloseGuard != null) {
@@ -264,65 +268,63 @@
     }
 
     /**
-     * Builder object to facilitate the creation of IpSecTransform objects.
-     *
-     * <p>Apply additional properties to the transform and then call a build() method to return an
-     * IpSecTransform object.
-     *
-     * @see Builder#buildTransportModeTransform(InetAddress)
+     * This class is used to build {@link IpSecTransform} objects.
      */
     public static class Builder {
         private Context mContext;
         private IpSecConfig mConfig;
 
         /**
-         * Add an encryption algorithm to the transform for the given direction.
+         * Set the encryption algorithm for the given direction.
          *
-         * <p>If encryption is set for a given direction without also providing an SPI for that
-         * direction, creation of an IpSecTransform will fail upon calling a build() method.
+         * <p>If encryption is set for a direction without also providing an SPI for that direction,
+         * creation of an {@code IpSecTransform} will fail when attempting to build the transform.
          *
-         * <p>Authenticated encryption is mutually exclusive with encryption and authentication.
+         * <p>Encryption is mutually exclusive with authenticated encryption.
          *
-         * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT}
+         * @param direction either {@link #DIRECTION_IN} or {@link #DIRECTION_OUT}
          * @param algo {@link IpSecAlgorithm} specifying the encryption to be applied.
          */
         public IpSecTransform.Builder setEncryption(
                 @TransformDirection int direction, IpSecAlgorithm algo) {
+            // TODO: throw IllegalArgumentException if algo is not an encryption algorithm.
             mConfig.setEncryption(direction, algo);
             return this;
         }
 
         /**
-         * Add an authentication/integrity algorithm to the transform.
+         * Set the authentication (integrity) algorithm for the given direction.
          *
-         * <p>If authentication is set for a given direction without also providing an SPI for that
-         * direction, creation of an IpSecTransform will fail upon calling a build() method.
+         * <p>If authentication is set for a direction without also providing an SPI for that
+         * direction, creation of an {@code IpSecTransform} will fail when attempting to build the
+         * transform.
          *
-         * <p>Authenticated encryption is mutually exclusive with encryption and authentication.
+         * <p>Authentication is mutually exclusive with authenticated encryption.
          *
-         * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT}
+         * @param direction either {@link #DIRECTION_IN} or {@link #DIRECTION_OUT}
          * @param algo {@link IpSecAlgorithm} specifying the authentication to be applied.
          */
         public IpSecTransform.Builder setAuthentication(
                 @TransformDirection int direction, IpSecAlgorithm algo) {
+            // TODO: throw IllegalArgumentException if algo is not an authentication algorithm.
             mConfig.setAuthentication(direction, algo);
             return this;
         }
 
         /**
-         * Add an authenticated encryption algorithm to the transform for the given direction.
+         * Set the authenticated encryption algorithm for the given direction.
          *
          * <p>If an authenticated encryption algorithm is set for a given direction without also
-         * providing an SPI for that direction, creation of an IpSecTransform will fail upon calling
-         * a build() method.
+         * providing an SPI for that direction, creation of an {@code IpSecTransform} will fail when
+         * attempting to build the transform.
          *
          * <p>The Authenticated Encryption (AE) class of algorithms are also known as Authenticated
          * Encryption with Associated Data (AEAD) algorithms, or Combined mode algorithms (as
-         * referred to in RFC 4301)
+         * referred to in <a href="https://tools.ietf.org/html/rfc4301">RFC 4301</a>).
          *
          * <p>Authenticated encryption is mutually exclusive with encryption and authentication.
          *
-         * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT}
+         * @param direction either {@link #DIRECTION_IN} or {@link #DIRECTION_OUT}
          * @param algo {@link IpSecAlgorithm} specifying the authenticated encryption algorithm to
          *     be applied.
          */
@@ -333,19 +335,16 @@
         }
 
         /**
-         * Set the SPI, which uniquely identifies a particular IPsec session from others. Because
-         * IPsec operates at the IP layer, this 32-bit identifier uniquely identifies packets to a
-         * given destination address.
+         * Set the SPI for the given direction.
          *
-         * <p>Care should be chosen when selecting an SPI to ensure that is is as unique as
-         * possible. To reserve a value call {@link IpSecManager#reserveSecurityParameterIndex(int,
-         * InetAddress, int)}. Otherwise, SPI collisions would prevent a transform from being
-         * activated. IpSecManager#reserveSecurityParameterIndex(int, InetAddres$s, int)}.
+         * <p>Because IPsec operates at the IP layer, this 32-bit identifier uniquely identifies
+         * packets to a given destination address. To prevent SPI collisions, values should be
+         * reserved by calling {@link IpSecManager#reserveSecurityParameterIndex}.
          *
-         * <p>Unless an SPI is set for a given direction, traffic in that direction will be
-         * sent/received without any IPsec applied.
+         * <p>If the SPI and algorithms are omitted for one direction, traffic in that direction
+         * will not be encrypted or authenticated.
          *
-         * @param direction either {@link #DIRECTION_IN or #DIRECTION_OUT}
+         * @param direction either {@link #DIRECTION_IN} or {@link #DIRECTION_OUT}
          * @param spi a unique {@link IpSecManager.SecurityParameterIndex} to identify transformed
          *     traffic
          */
@@ -356,11 +355,10 @@
         }
 
         /**
-         * Specify the network on which this transform will emit its traffic; (otherwise it will
-         * emit on the default network).
+         * Set the {@link Network} which will carry tunneled traffic.
          *
-         * <p>Restricts the transformed traffic to a particular {@link Network}. This is required in
-         * tunnel mode.
+         * <p>Restricts the transformed traffic to a particular {@link Network}. This is required
+         * for tunnel mode, otherwise tunneled traffic would be sent on the default network.
          *
          * @hide
          */
@@ -371,15 +369,18 @@
         }
 
         /**
-         * Add UDP encapsulation to an IPv4 transform
+         * Add UDP encapsulation to an IPv4 transform.
          *
-         * <p>This option allows IPsec traffic to pass through NAT. Refer to RFC 3947 and 3948 for
-         * details on how UDP should be applied to IPsec.
+         * <p>This allows IPsec traffic to pass through a NAT.
          *
-         * @param localSocket a {@link IpSecManager.UdpEncapsulationSocket} for sending and
-         *     receiving encapsulating traffic.
-         * @param remotePort the UDP port number of the remote that will send and receive
-         *     encapsulated traffic. In the case of IKE, this is likely port 4500.
+         * @see <a href="https://tools.ietf.org/html/rfc3948">RFC 3948, UDP Encapsulation of IPsec
+         * ESP Packets</a>
+         * @see <a href="https://tools.ietf.org/html/rfc7296#section-2.23">RFC 7296 section 2.23,
+         * NAT Traversal of IKEv2</a>
+         *
+         * @param localSocket a socket for sending and receiving encapsulated traffic
+         * @param remotePort the UDP port number of the remote host that will send and receive
+         *     encapsulated traffic. In the case of IKEv2, this should be port 4500.
          */
         public IpSecTransform.Builder setIpv4Encapsulation(
                 IpSecManager.UdpEncapsulationSocket localSocket, int remotePort) {
@@ -393,12 +394,15 @@
         // TODO: Probably a better exception to throw for NATTKeepalive failure
         // TODO: Specify the needed NATT keepalive permission.
         /**
-         * Send a NATT Keepalive packet with a given maximum interval. This will create an offloaded
-         * request to do power-efficient NATT Keepalive. If NATT keepalive is requested but cannot
-         * be activated, then the transform will fail to activate and throw an IOException.
+         * Set NAT-T keepalives to be sent with a given interval.
          *
-         * @param intervalSeconds the maximum number of seconds between keepalive packets, no less
-         *     than 20s and no more than 3600s.
+         * <p>This will set power-efficient keepalive packets to be sent by the system. If NAT-T
+         * keepalive is requested but cannot be activated, then creation of an {@link
+         * IpSecTransform} will fail when calling the build method.
+         *
+         * @param intervalSeconds the maximum number of seconds between keepalive packets. Must be
+         *     between 20s and 3600s.
+         *
          * @hide
          */
         @SystemApi
@@ -408,36 +412,29 @@
         }
 
         /**
-         * Build and return an active {@link IpSecTransform} object as a Transport Mode Transform.
-         * Some parameters have interdependencies that are checked at build time. If a well-formed
-         * transform cannot be created from the supplied parameters, this method will throw an
-         * Exception.
+         * Build a transport mode {@link IpSecTransform}.
          *
-         * <p>Upon a successful return from this call, the provided IpSecTransform will be active
-         * and may be applied to sockets. If too many IpSecTransform objects are active for a given
-         * user this operation will fail and throw ResourceUnavailableException. To avoid these
-         * exceptions, unused Transform objects must be cleaned up by calling {@link
-         * IpSecTransform#close()} when they are no longer needed.
+         * <p>This builds and activates a transport mode transform. Note that an active transform
+         * will not affect any network traffic until it has been applied to one or more sockets.
          *
-         * @param remoteAddress the {@link InetAddress} that, when matched on traffic to/from this
-         *     socket will cause the transform to be applied.
-         *     <p>Note that an active transform will not impact any network traffic until it has
-         *     been applied to one or more Sockets. Calling this method is a necessary precondition
-         *     for applying it to a socket, but is not sufficient to actually apply IPsec.
+         * @see IpSecManager#applyTransportModeTransform
+         *
+         * @param remoteAddress the remote {@code InetAddress} of traffic on sockets that will use
+         *     this transform
          * @throws IllegalArgumentException indicating that a particular combination of transform
-         *     properties is invalid.
-         * @throws IpSecManager.ResourceUnavailableException in the event that no more Transforms
-         *     may be allocated
-         * @throws SpiUnavailableException if the SPI collides with an existing transform
-         *     (unlikely).
-         * @throws ResourceUnavailableException if the current user currently has exceeded the
-         *     number of allowed active transforms.
+         *     properties is invalid
+         * @throws IpSecManager.ResourceUnavailableException indicating that too many transforms are
+         *     active
+         * @throws IpSecManager.SpiUnavailableException indicating the rare case where an SPI
+         *     collides with an existing transform
+         * @throws IOException indicating other errors
          */
         public IpSecTransform buildTransportModeTransform(InetAddress remoteAddress)
                 throws IpSecManager.ResourceUnavailableException,
                         IpSecManager.SpiUnavailableException, IOException {
             mConfig.setMode(MODE_TRANSPORT);
             mConfig.setRemoteAddress(remoteAddress.getHostAddress());
+            // FIXME: modifying a builder after calling build can change the built transform.
             return new IpSecTransform(mContext, mConfig).activate();
         }
 
@@ -465,9 +462,9 @@
         }
 
         /**
-         * Create a new IpSecTransform.Builder to construct an IpSecTransform
+         * Create a new IpSecTransform.Builder.
          *
-         * @param context current Context
+         * @param context current context
          */
         public Builder(@NonNull Context context) {
             Preconditions.checkNotNull(context);
diff --git a/core/java/android/os/BatteryManager.java b/core/java/android/os/BatteryManager.java
index 6e0f70c1..843bdb5 100644
--- a/core/java/android/os/BatteryManager.java
+++ b/core/java/android/os/BatteryManager.java
@@ -18,6 +18,7 @@
 
 import android.annotation.SystemService;
 import android.content.Context;
+import android.content.Intent;
 import android.hardware.health.V1_0.Constants;
 
 import com.android.internal.app.IBatteryStats;
@@ -56,6 +57,14 @@
 
     /**
      * Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
+     * Boolean field indicating whether the battery is currently considered to be
+     * low, that is whether a {@link Intent#ACTION_BATTERY_LOW} broadcast
+     * has been sent.
+     */
+    public static final String EXTRA_BATTERY_LOW = "battery_low";
+
+    /**
+     * Extra for {@link android.content.Intent#ACTION_BATTERY_CHANGED}:
      * integer containing the maximum battery level.
      */
     public static final String EXTRA_SCALE = "scale";
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index a8bd940..af91e81 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -222,8 +222,10 @@
      *   - Resource power manager (rpm) states [but screenOffRpm is disabled from working properly]
      * New in version 27:
      *   - Always On Display (screen doze mode) time and power
+     * New in version 28:
+     *   - Light/Deep Doze power
      */
-    static final int CHECKIN_VERSION = 27;
+    static final int CHECKIN_VERSION = 28;
 
     /**
      * Old version, we hit 9 and ran out of room, need to remove.
@@ -2696,6 +2698,18 @@
     public abstract long getUahDischarge(int which);
 
     /**
+     * @return the amount of battery discharge while the device is in light idle mode, measured in
+     * micro-Ampere-hours.
+     */
+    public abstract long getUahDischargeLightDoze(int which);
+
+    /**
+     * @return the amount of battery discharge while the device is in deep idle mode, measured in
+     * micro-Ampere-hours.
+     */
+    public abstract long getUahDischargeDeepDoze(int which);
+
+    /**
      * Returns the estimated real battery capacity, which may be less than the capacity
      * declared by the PowerProfile.
      * @return The estimated battery capacity in mAh.
@@ -3327,6 +3341,8 @@
         final long dischargeCount = getUahDischarge(which);
         final long dischargeScreenOffCount = getUahDischargeScreenOff(which);
         final long dischargeScreenDozeCount = getUahDischargeScreenDoze(which);
+        final long dischargeLightDozeCount = getUahDischargeLightDoze(which);
+        final long dischargeDeepDozeCount = getUahDischargeDeepDoze(which);
 
         final StringBuilder sb = new StringBuilder(128);
 
@@ -3497,14 +3513,16 @@
                     getDischargeStartLevel()-getDischargeCurrentLevel(),
                     getDischargeAmountScreenOn(), getDischargeAmountScreenOff(),
                     dischargeCount / 1000, dischargeScreenOffCount / 1000,
-                    getDischargeAmountScreenDoze(), dischargeScreenDozeCount / 1000);
+                    getDischargeAmountScreenDoze(), dischargeScreenDozeCount / 1000,
+                    dischargeLightDozeCount / 1000, dischargeDeepDozeCount / 1000);
         } else {
             dumpLine(pw, 0 /* uid */, category, BATTERY_DISCHARGE_DATA,
                     getLowDischargeAmountSinceCharge(), getHighDischargeAmountSinceCharge(),
                     getDischargeAmountScreenOnSinceCharge(),
                     getDischargeAmountScreenOffSinceCharge(),
                     dischargeCount / 1000, dischargeScreenOffCount / 1000,
-                    getDischargeAmountScreenDozeSinceCharge(), dischargeScreenDozeCount / 1000);
+                    getDischargeAmountScreenDozeSinceCharge(), dischargeScreenDozeCount / 1000,
+                    dischargeLightDozeCount / 1000, dischargeDeepDozeCount / 1000);
         }
 
         if (reqUid < 0) {
@@ -4169,6 +4187,26 @@
             pw.println(sb.toString());
         }
 
+        final long dischargeLightDozeCount = getUahDischargeLightDoze(which);
+        if (dischargeLightDozeCount >= 0) {
+            sb.setLength(0);
+            sb.append(prefix);
+            sb.append("  Device light doze discharge: ");
+            sb.append(BatteryStatsHelper.makemAh(dischargeLightDozeCount / 1000.0));
+            sb.append(" mAh");
+            pw.println(sb.toString());
+        }
+
+        final long dischargeDeepDozeCount = getUahDischargeDeepDoze(which);
+        if (dischargeDeepDozeCount >= 0) {
+            sb.setLength(0);
+            sb.append(prefix);
+            sb.append("  Device deep doze discharge: ");
+            sb.append(BatteryStatsHelper.makemAh(dischargeDeepDozeCount / 1000.0));
+            sb.append(" mAh");
+            pw.println(sb.toString());
+        }
+
         pw.print("  Start clock time: ");
         pw.println(DateFormat.format("yyyy-MM-dd-HH-mm-ss", getStartClockTime()).toString());
 
@@ -7131,6 +7169,10 @@
                 getUahDischargeScreenOff(which) / 1000);
         proto.write(SystemProto.BatteryDischarge.TOTAL_MAH_SCREEN_DOZE,
                 getUahDischargeScreenDoze(which) / 1000);
+        proto.write(SystemProto.BatteryDischarge.TOTAL_MAH_LIGHT_DOZE,
+                getUahDischargeLightDoze(which) / 1000);
+        proto.write(SystemProto.BatteryDischarge.TOTAL_MAH_DEEP_DOZE,
+                getUahDischargeDeepDoze(which) / 1000);
         proto.end(bdToken);
 
         // Time remaining
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index d092018..36121d4 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -1780,7 +1780,7 @@
             final int truncatedSize = Math.min(stackTrace.length, 5);
             StringBuilder sb = new StringBuilder();
             for (int i = 0; i < truncatedSize; i++) {
-                sb.append("\tat ").append(stackTrace[i]);
+                sb.append("\tat ").append(stackTrace[i]).append('\n');
             }
             writeString(sb.toString());
             final int payloadPosition = dataPosition();
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index 0fce7a4..5dd8d05 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -387,6 +387,12 @@
     public static final int GO_TO_SLEEP_REASON_SLEEP_BUTTON = 6;
 
     /**
+     * Go to sleep reason code: Going to sleep by request of an accessibility service
+     * @hide
+     */
+    public static final int GO_TO_SLEEP_REASON_ACCESSIBILITY = 7;
+
+    /**
      * Go to sleep flag: Skip dozing state and directly go to full sleep.
      * @hide
      */
diff --git a/core/java/android/os/ShellCallback.java b/core/java/android/os/ShellCallback.java
index ad9fbfb..6a62424 100644
--- a/core/java/android/os/ShellCallback.java
+++ b/core/java/android/os/ShellCallback.java
@@ -105,6 +105,9 @@
     ShellCallback(Parcel in) {
         mLocal = false;
         mShellCallback = IShellCallback.Stub.asInterface(in.readStrongBinder());
+        if (mShellCallback != null) {
+            Binder.allowBlocking(mShellCallback.asBinder());
+        }
     }
 
     public static final Parcelable.Creator<ShellCallback> CREATOR
diff --git a/core/java/android/os/ShellCommand.java b/core/java/android/os/ShellCommand.java
index d75219f..fa05a5e 100644
--- a/core/java/android/os/ShellCommand.java
+++ b/core/java/android/os/ShellCommand.java
@@ -91,7 +91,13 @@
         mCmd = cmd;
         mResultReceiver = resultReceiver;
 
-        if (DEBUG) Slog.d(TAG, "Starting command " + mCmd + " on " + mTarget);
+        if (DEBUG) {
+            RuntimeException here = new RuntimeException("here");
+            here.fillInStackTrace();
+            Slog.d(TAG, "Starting command " + mCmd + " on " + mTarget, here);
+            Slog.d(TAG, "Calling uid=" + Binder.getCallingUid()
+                    + " pid=" + Binder.getCallingPid() + " ShellCallback=" + getShellCallback());
+        }
         int res = -1;
         try {
             res = onCommand(mCmd);
@@ -227,15 +233,19 @@
      * @hide
      */
     public ParcelFileDescriptor openFileForSystem(String path, String mode) {
+        if (DEBUG) Slog.d(TAG, "openFileForSystem: " + path + " mode=" + mode);
         try {
             ParcelFileDescriptor pfd = getShellCallback().openFile(path,
                     "u:r:system_server:s0", mode);
             if (pfd != null) {
+                if (DEBUG) Slog.d(TAG, "Got file: " + pfd);
                 return pfd;
             }
         } catch (RuntimeException e) {
+            if (DEBUG) Slog.d(TAG, "Failure opening file: " + e.getMessage());
             getErrPrintWriter().println("Failure opening file: " + e.getMessage());
         }
+        if (DEBUG) Slog.d(TAG, "Error: Unable to open file: " + path);
         getErrPrintWriter().println("Error: Unable to open file: " + path);
         getErrPrintWriter().println("Consider using a file under /data/local/tmp/");
         return null;
diff --git a/core/java/android/view/WindowManagerInternal.java b/core/java/android/view/WindowManagerInternal.java
index cd1b190..d07b2ac 100644
--- a/core/java/android/view/WindowManagerInternal.java
+++ b/core/java/android/view/WindowManagerInternal.java
@@ -381,4 +381,9 @@
      * Sets callback to DragDropController.
      */
     public abstract void registerDragDropControllerCallback(IDragDropCallback callback);
+
+    /**
+     * @see android.view.IWindowManager#lockNow
+     */
+    public abstract void lockNow();
 }
diff --git a/core/java/com/android/internal/app/procstats/ProcessState.java b/core/java/com/android/internal/app/procstats/ProcessState.java
index 7519fce..fbdf17d 100644
--- a/core/java/com/android/internal/app/procstats/ProcessState.java
+++ b/core/java/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/core/java/com/android/internal/content/NativeLibraryHelper.java b/core/java/com/android/internal/content/NativeLibraryHelper.java
index 83b7d2f..a1e6fd8 100644
--- a/core/java/com/android/internal/content/NativeLibraryHelper.java
+++ b/core/java/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/core/java/com/android/internal/content/PackageHelper.java b/core/java/com/android/internal/content/PackageHelper.java
index 59a7995..e765ab1 100644
--- a/core/java/com/android/internal/content/PackageHelper.java
+++ b/core/java/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/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index f2483c0..6ede72d 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/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;
     }
@@ -9085,6 +9097,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 +9678,8 @@
         mChargeStepTracker.init();
         mDischargeScreenOffCounter.reset(false);
         mDischargeScreenDozeCounter.reset(false);
+        mDischargeLightDozeCounter.reset(false);
+        mDischargeDeepDozeCounter.reset(false);
         mDischargeCounter.reset(false);
     }
 
@@ -11263,6 +11279,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 +11329,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 +12095,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 +12521,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 +13074,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 +13262,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/core/jni/com_android_internal_content_NativeLibraryHelper.cpp b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
index fce5dd5..17b98da 100644
--- a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
+++ b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
@@ -557,6 +557,23 @@
     return reinterpret_cast<jlong>(zipFile);
 }
 
+static jlong
+com_android_internal_content_NativeLibraryHelper_openApkFd(JNIEnv *env, jclass,
+        jobject fileDescriptor, jstring debugPathName)
+{
+    ScopedUtfChars debugFilePath(env, debugPathName);
+
+    int fd = jniGetFDFromFileDescriptor(env, fileDescriptor);
+    if (fd < 0) {
+        jniThrowException(env, "java/lang/IllegalArgumentException", "Bad FileDescriptor");
+        return 0;
+    }
+
+    ZipFileRO* zipFile = ZipFileRO::openFd(fd, debugFilePath.c_str());
+
+    return reinterpret_cast<jlong>(zipFile);
+}
+
 static void
 com_android_internal_content_NativeLibraryHelper_close(JNIEnv *env, jclass, jlong apkHandle)
 {
@@ -567,6 +584,9 @@
     {"nativeOpenApk",
             "(Ljava/lang/String;)J",
             (void *)com_android_internal_content_NativeLibraryHelper_openApk},
+    {"nativeOpenApkFd",
+            "(Ljava/io/FileDescriptor;Ljava/lang/String;)J",
+            (void *)com_android_internal_content_NativeLibraryHelper_openApkFd},
     {"nativeClose",
             "(J)V",
             (void *)com_android_internal_content_NativeLibraryHelper_close},
diff --git a/core/proto/android/os/batterystats.proto b/core/proto/android/os/batterystats.proto
index c33c0a0..0a3344f 100644
--- a/core/proto/android/os/batterystats.proto
+++ b/core/proto/android/os/batterystats.proto
@@ -120,6 +120,14 @@
     // via a coulomb counter. For historical reasons, total_mah_screen_doze is
     // a subset of total_mah_screen_off.
     optional int64 total_mah_screen_doze = 8;
+    // Total amount of battery discharged in mAh while the device was in light doze mode.
+    // This will only be non-zero for devices that report battery discharge
+    // via a coulomb counter.
+    optional int64 total_mah_light_doze = 9;
+    // Total amount of battery discharged in mAh while the device was in deep doze mode.
+    // This will only be non-zero for devices that report battery discharge
+    // via a coulomb counter.
+    optional int64 total_mah_deep_doze = 10;
   };
   optional BatteryDischarge battery_discharge = 2;
 
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 86103e4..d15c075 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -485,7 +485,6 @@
     <protected-broadcast android:name="android.content.jobscheduler.JOB_DEADLINE_EXPIRED" />
     <protected-broadcast android:name="android.intent.action.ACTION_UNSOL_RESPONSE_OEM_HOOK_RAW" />
     <protected-broadcast android:name="android.net.conn.CONNECTIVITY_CHANGE_SUPL" />
-    <protected-broadcast android:name="android.os.action.ACTION_EFFECTS_SUPPRESSOR_CHANGED" />
     <protected-broadcast android:name="android.os.action.LIGHT_DEVICE_IDLE_MODE_CHANGED" />
     <protected-broadcast android:name="android.os.storage.action.VOLUME_STATE_CHANGED" />
     <protected-broadcast android:name="android.os.storage.action.DISK_SCANNED" />
@@ -504,6 +503,8 @@
     <protected-broadcast android:name="android.app.action.NOTIFICATION_POLICY_CHANGED" />
     <protected-broadcast android:name="android.app.action.NOTIFICATION_POLICY_ACCESS_GRANTED_CHANGED" />
     <protected-broadcast android:name="android.os.action.ACTION_EFFECTS_SUPPRESSOR_CHANGED" />
+    <protected-broadcast android:name="android.app.action.NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED" />
+    <protected-broadcast android:name="android.app.action.NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED" />
 
     <protected-broadcast android:name="android.permission.GET_APP_GRANTED_URI_PERMISSIONS" />
     <protected-broadcast android:name="android.permission.CLEAR_APP_GRANTED_URI_PERMISSIONS" />
@@ -3958,6 +3959,10 @@
         <service android:name="com.android.server.net.watchlist.ReportWatchlistJobService"
                  android:permission="android.permission.BIND_JOB_SERVICE" >
         </service>
-    </application>
+
+        <service android:name="com.android.server.display.BrightnessIdleJob"
+                 android:permission="android.permission.BIND_JOB_SERVICE" >
+        </service>
+</application>
 
 </manifest>
diff --git a/core/res/res/layout/unsupported_compile_sdk_dialog_content.xml b/core/res/res/layout/unsupported_compile_sdk_dialog_content.xml
new file mode 100644
index 0000000..89e58aa
--- /dev/null
+++ b/core/res/res/layout/unsupported_compile_sdk_dialog_content.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * 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.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content"
+              android:paddingTop="?attr/dialogPreferredPadding"
+              android:paddingLeft="?attr/dialogPreferredPadding"
+              android:paddingRight="?attr/dialogPreferredPadding">
+
+    <CheckBox
+        android:id="@+id/ask_checkbox"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="start"
+        android:text="@string/unsupported_compile_sdk_show" />
+</FrameLayout>
diff --git a/core/res/res/values-watch/config.xml b/core/res/res/values-watch/config.xml
index f26d6ed..e12f04a 100644
--- a/core/res/res/values-watch/config.xml
+++ b/core/res/res/values-watch/config.xml
@@ -21,9 +21,10 @@
      for watch products.  Do not translate. -->
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
 
-    <!-- Only show settings item due to smaller real estate. -->
+    <!-- Show smaller list of items due to smaller real estate. -->
     <string-array translatable="false" name="config_globalActionsList">
-        <item>assist</item>
+        <item>power</item>
+        <item>restart</item>
     </string-array>
 
     <!-- Base "touch slop" value used by ViewConfiguration as a
diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml
index be0f6d9..fe8ca56 100644
--- a/core/res/res/values/attrs_manifest.xml
+++ b/core/res/res/values/attrs_manifest.xml
@@ -1340,6 +1340,23 @@
          <p>The default value of this attribute is <code>1</code>. -->
     <attr name="targetSandboxVersion" format="integer" />
 
+    <!-- The user-visible SDK version (ex. 26) of the framework against which the application was
+         compiled. This attribute is automatically specified by the Android build tools and should
+         NOT be manually specified.
+         <p>
+         This attribute is the compile-time equivalent of
+         {@link android.os.Build.VERSION#SDK_INT Build.VERSION.SDK_INT}. -->
+    <attr name="compileSdkVersion" format="integer" />
+
+    <!-- The development codename (ex. "O") of the framework against which the application was
+         compiled, or "REL" if the application was compiled against a release build. This attribute
+         is automatically specified by the Android build tools and should NOT be manually
+         specified.
+         <p>
+         This attribute is the compile-time equivalent of
+         {@link android.os.Build.VERSION#CODENAME Build.VERSION.CODENAME}. -->
+    <attr name="compileSdkVersionCodename" format="string" />
+
     <!-- The <code>manifest</code> tag is the root of an
          <code>AndroidManifest.xml</code> file,
          describing the contents of an Android package (.apk) file.  One
@@ -1369,6 +1386,8 @@
         <attr name="isolatedSplits" />
         <attr name="isFeatureSplit" />
         <attr name="targetSandboxVersion" />
+        <attr name="compileSdkVersion" />
+        <attr name="compileSdkVersionCodename" />
     </declare-styleable>
 
     <!-- The <code>application</code> tag describes application-level components
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index edd793d..c104616 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -940,9 +940,16 @@
             1 - Global actions menu
             2 - Power off (with confirmation)
             3 - Power off (without confirmation)
+            4 - Go to voice assist
     -->
     <integer name="config_longPressOnPowerBehavior">1</integer>
 
+    <!-- Control the behavior when the user long presses the power button for a long time.
+            0 - Nothing
+            1 - Global actions menu
+    -->
+    <integer name="config_veryLongPressOnPowerBehavior">0</integer>
+
     <!-- Control the behavior when the user long presses the back button.  Non-zero values are only
          valid for watches as part of CDD/CTS.
             0 - Nothing
@@ -986,6 +993,9 @@
     -->
     <integer name="config_shortPressOnSleepBehavior">0</integer>
 
+    <!-- Time to wait while a button is pressed before triggering a very long press. -->
+    <integer name="config_veryLongPressTimeout">6000</integer>
+
     <!-- Package name for default keyguard appwidget [DO NOT TRANSLATE] -->
     <string name="widget_default_package_name" translatable="false"></string>
 
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index fdd56c4..d3533fe 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2848,6 +2848,10 @@
       <public name="ttcIndex" />
       <public name="fontVariationSettings" />
       <public name="dialogCornerRadius" />
+      <!-- @hide For use by platform and tools only. Developers should not specify this value. -->
+      <public name="compileSdkVersion" />
+      <!-- @hide For use by platform and tools only. Developers should not specify this value. -->
+      <public name="compileSdkVersionCodename" />
     </public-group>
 
     <public-group type="style" first-id="0x010302e0">
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 7a3fa1a..999ba73 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -2852,6 +2852,13 @@
     <!-- [CHAR LIMIT=50] Unsupported display size dialog: check box label. -->
     <string name="unsupported_display_size_show">Always show</string>
 
+    <!-- [CHAR LIMIT=200] Unsupported compile SDK dialog: message. Shown when an app may not be compatible with the device's current version of Android. -->
+    <string name="unsupported_compile_sdk_message"><xliff:g id="app_name">%1$s</xliff:g> was built for preview version %2$s of the Android OS and may behave unexpectedly. An updated version of the app may be available.</string>
+    <!-- [CHAR LIMIT=50] Unsupported compile SDK dialog: check box label. -->
+    <string name="unsupported_compile_sdk_show">Always show</string>
+    <!-- [CHAR LIMIT=50] Unsupported compile SDK dialog: label for button to check for an app update. -->
+    <string name="unsupported_compile_sdk_check_update">Check for update</string>
+
     <!-- Text of the alert that is displayed when an application has violated StrictMode. -->
     <string name="smv_application">The app <xliff:g id="application">%1$s</xliff:g>
         (process <xliff:g id="process">%2$s</xliff:g>) has violated its self-enforced StrictMode policy.</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 5497085..a115816 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -419,6 +419,8 @@
   <java-symbol type="integer" name="config_extraFreeKbytesAbsolute" />
   <java-symbol type="integer" name="config_immersive_mode_confirmation_panic" />
   <java-symbol type="integer" name="config_longPressOnPowerBehavior" />
+  <java-symbol type="integer" name="config_veryLongPressOnPowerBehavior" />
+  <java-symbol type="integer" name="config_veryLongPressTimeout" />
   <java-symbol type="integer" name="config_longPressOnBackBehavior" />
   <java-symbol type="integer" name="config_backPanicBehavior" />
   <java-symbol type="integer" name="config_lowMemoryKillerMinFreeKbytesAdjust" />
@@ -3151,4 +3153,9 @@
   <!-- From media projection -->
   <java-symbol type="string" name="config_mediaProjectionPermissionDialogComponent" />
   <java-symbol type="string" name="config_batterySaverDeviceSpecificConfig" />
+
+  <!-- Compile SDK check -->
+  <java-symbol type="layout" name="unsupported_compile_sdk_dialog_content" />
+  <java-symbol type="string" name="unsupported_compile_sdk_message" />
+  <java-symbol type="string" name="unsupported_compile_sdk_check_update" />
 </resources>
diff --git a/data/keyboards/OWNERS b/data/keyboards/OWNERS
new file mode 100644
index 0000000..031a6c1
--- /dev/null
+++ b/data/keyboards/OWNERS
@@ -0,0 +1,4 @@
+set noparent
+
+michaelwr@google.com
+svv@google.com
diff --git a/libs/androidfw/AssetManager.cpp b/libs/androidfw/AssetManager.cpp
index 3c8736e..0485625 100644
--- a/libs/androidfw/AssetManager.cpp
+++ b/libs/androidfw/AssetManager.cpp
@@ -148,11 +148,15 @@
     int count = android_atomic_dec(&gCount);
     if (kIsDebug) {
         ALOGI("Destroying AssetManager in %p #%d\n", this, count);
+    } else {
+        ALOGV("Destroying AssetManager in %p #%d\n", this, count);
     }
 
     // Manually close any fd paths for which we have not yet opened their zip (which
     // will take ownership of the fd and close it when done).
     for (size_t i=0; i<mAssetPaths.size(); i++) {
+        ALOGV("Cleaning path #%d: fd=%d, zip=%p", (int)i, mAssetPaths[i].rawFd,
+                mAssetPaths[i].zip.get());
         if (mAssetPaths[i].rawFd >= 0 && mAssetPaths[i].zip == NULL) {
             close(mAssetPaths[i].rawFd);
         }
@@ -202,7 +206,7 @@
          ap.type == kFileTypeDirectory ? "dir" : "zip", ap.path.string());
 
     ap.isSystemAsset = isSystemAsset;
-    mAssetPaths.add(ap);
+    ssize_t apPos = mAssetPaths.add(ap);
 
     // new paths are always added at the end
     if (cookie) {
@@ -219,7 +223,7 @@
 #endif
 
     if (mResources != NULL) {
-        appendPathToResTable(ap, appAsLib);
+        appendPathToResTable(mAssetPaths.editItemAt(apPos), appAsLib);
     }
 
     return true;
@@ -304,7 +308,7 @@
 
     ALOGV("In %p Asset fd %d name: %s", this, fd, ap.path.string());
 
-    mAssetPaths.add(ap);
+    ssize_t apPos = mAssetPaths.add(ap);
 
     // new paths are always added at the end
     if (cookie) {
@@ -312,7 +316,7 @@
     }
 
     if (mResources != NULL) {
-        appendPathToResTable(ap, appAsLib);
+        appendPathToResTable(mAssetPaths.editItemAt(apPos), appAsLib);
     }
 
     return true;
@@ -442,7 +446,8 @@
         i--;
         ALOGV("Looking for asset '%s' in '%s'\n",
                 assetName.string(), mAssetPaths.itemAt(i).path.string());
-        Asset* pAsset = openNonAssetInPathLocked(assetName.string(), mode, mAssetPaths.itemAt(i));
+        Asset* pAsset = openNonAssetInPathLocked(assetName.string(), mode,
+                mAssetPaths.editItemAt(i));
         if (pAsset != NULL) {
             return pAsset != kExcludedAsset ? pAsset : NULL;
         }
@@ -471,7 +476,7 @@
         i--;
         ALOGV("Looking for non-asset '%s' in '%s'\n", fileName, mAssetPaths.itemAt(i).path.string());
         Asset* pAsset = openNonAssetInPathLocked(
-            fileName, mode, mAssetPaths.itemAt(i));
+            fileName, mode, mAssetPaths.editItemAt(i));
         if (pAsset != NULL) {
             if (outCookie != NULL) *outCookie = static_cast<int32_t>(i + 1);
             return pAsset != kExcludedAsset ? pAsset : NULL;
@@ -493,7 +498,7 @@
         ALOGV("Looking for non-asset '%s' in '%s'\n", fileName,
                 mAssetPaths.itemAt(which).path.string());
         Asset* pAsset = openNonAssetInPathLocked(
-            fileName, mode, mAssetPaths.itemAt(which));
+            fileName, mode, mAssetPaths.editItemAt(which));
         if (pAsset != NULL) {
             return pAsset != kExcludedAsset ? pAsset : NULL;
         }
@@ -527,7 +532,7 @@
     }
 }
 
-bool AssetManager::appendPathToResTable(const asset_path& ap, bool appAsLib) const {
+bool AssetManager::appendPathToResTable(asset_path& ap, bool appAsLib) const {
     // skip those ap's that correspond to system overlays
     if (ap.isSystemOverlay) {
         return true;
@@ -645,7 +650,8 @@
     bool onlyEmptyResources = true;
     const size_t N = mAssetPaths.size();
     for (size_t i=0; i<N; i++) {
-        bool empty = appendPathToResTable(mAssetPaths.itemAt(i));
+        bool empty = appendPathToResTable(
+                const_cast<AssetManager*>(this)->mAssetPaths.editItemAt(i));
         onlyEmptyResources = onlyEmptyResources && empty;
     }
 
@@ -770,7 +776,7 @@
  * be used.
  */
 Asset* AssetManager::openNonAssetInPathLocked(const char* fileName, AccessMode mode,
-    const asset_path& ap)
+    asset_path& ap)
 {
     Asset* pAsset = NULL;
 
@@ -851,17 +857,19 @@
  * Return a pointer to one of our open Zip archives.  Returns NULL if no
  * matching Zip file exists.
  */
-ZipFileRO* AssetManager::getZipFileLocked(const asset_path& ap)
+ZipFileRO* AssetManager::getZipFileLocked(asset_path& ap)
 {
-    ALOGV("getZipFileLocked() in %p\n", this);
+    ALOGV("getZipFileLocked() in %p: ap=%p zip=%p", this, &ap, ap.zip.get());
 
     if (ap.zip != NULL) {
         return ap.zip->getZip();
     }
 
     if (ap.rawFd < 0) {
+        ALOGV("getZipFileLocked: Creating new zip from path %s", ap.path.string());
         ap.zip = mZipSet.getSharedZip(ap.path);
     } else {
+        ALOGV("getZipFileLocked: Creating new zip from fd %d", ap.rawFd);
         ap.zip = SharedZip::create(ap.rawFd, ap.path);
 
     }
diff --git a/libs/androidfw/include/androidfw/AssetManager.h b/libs/androidfw/include/androidfw/AssetManager.h
index 4254614..ecc5dc1 100644
--- a/libs/androidfw/include/androidfw/AssetManager.h
+++ b/libs/androidfw/include/androidfw/AssetManager.h
@@ -222,16 +222,16 @@
         bool isSystemOverlay;
         bool isSystemAsset;
         bool assumeOwnership;
-        mutable sp<SharedZip> zip;
+        sp<SharedZip> zip;
     };
 
     Asset* openNonAssetInPathLocked(const char* fileName, AccessMode mode,
-        const asset_path& path);
+        asset_path& path);
     String8 createPathNameLocked(const asset_path& path, const char* rootDir);
     String8 createZipSourceNameLocked(const String8& zipFileName,
         const String8& dirName, const String8& fileName);
 
-    ZipFileRO* getZipFileLocked(const asset_path& path);
+    ZipFileRO* getZipFileLocked(asset_path& path);
     Asset* openAssetFromFileLocked(const String8& fileName, AccessMode mode);
     Asset* openAssetFromZipLocked(const ZipFileRO* pZipFile,
         const ZipEntryRO entry, AccessMode mode, const String8& entryName);
@@ -247,7 +247,7 @@
     const ResTable* getResTable(bool required = true) const;
     void setLocaleLocked(const char* locale);
     void updateResourceParamsLocked() const;
-    bool appendPathToResTable(const asset_path& ap, bool appAsLib=false) const;
+    bool appendPathToResTable(asset_path& ap, bool appAsLib=false) const;
 
     Asset* openIdmapLocked(const struct asset_path& ap) const;
 
diff --git a/media/java/android/media/MediaFormat.java b/media/java/android/media/MediaFormat.java
index c475e12..306ed83 100644
--- a/media/java/android/media/MediaFormat.java
+++ b/media/java/android/media/MediaFormat.java
@@ -721,14 +721,16 @@
     /**
      * A key for boolean DEFAULT behavior for the track. The track with DEFAULT=true is
      * selected in the absence of a specific user choice.
-     * This is currently only used for subtitle tracks, when the user selected
-     * 'Default' for the captioning locale.
+     * This is currently used in two scenarios:
+     * 1) for subtitle tracks, when the user selected 'Default' for the captioning locale.
+     * 2) for a {@link #MIMETYPE_IMAGE_ANDROID_HEIC} track, indicating the image is the
+     * primary item in the file.
+
      * The associated value is an integer, where non-0 means TRUE.  This is an optional
      * field; if not specified, DEFAULT is considered to be FALSE.
      */
     public static final String KEY_IS_DEFAULT = "is-default";
 
-
     /**
      * A key for the FORCED field for subtitle tracks. True if it is a
      * forced subtitle track.  Forced subtitle tracks are essential for the
diff --git a/media/java/android/media/MediaMuxer.java b/media/java/android/media/MediaMuxer.java
index 91e57ee..02c71b2 100644
--- a/media/java/android/media/MediaMuxer.java
+++ b/media/java/android/media/MediaMuxer.java
@@ -258,12 +258,18 @@
          * in include/media/stagefright/MediaMuxer.h!
          */
         private OutputFormat() {}
+        /** @hide */
+        public static final int MUXER_OUTPUT_FIRST   = 0;
         /** MPEG4 media file format*/
-        public static final int MUXER_OUTPUT_MPEG_4 = 0;
+        public static final int MUXER_OUTPUT_MPEG_4 = MUXER_OUTPUT_FIRST;
         /** WEBM media file format*/
-        public static final int MUXER_OUTPUT_WEBM   = 1;
+        public static final int MUXER_OUTPUT_WEBM   = MUXER_OUTPUT_FIRST + 1;
         /** 3GPP media file format*/
-        public static final int MUXER_OUTPUT_3GPP   = 2;
+        public static final int MUXER_OUTPUT_3GPP   = MUXER_OUTPUT_FIRST + 2;
+        /** HEIF media file format*/
+        public static final int MUXER_OUTPUT_HEIF   = MUXER_OUTPUT_FIRST + 3;
+        /** @hide */
+        public static final int MUXER_OUTPUT_LAST   = MUXER_OUTPUT_HEIF;
     };
 
     /** @hide */
@@ -271,6 +277,7 @@
         OutputFormat.MUXER_OUTPUT_MPEG_4,
         OutputFormat.MUXER_OUTPUT_WEBM,
         OutputFormat.MUXER_OUTPUT_3GPP,
+        OutputFormat.MUXER_OUTPUT_HEIF,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface Format {}
@@ -347,8 +354,7 @@
     }
 
     private void setUpMediaMuxer(@NonNull FileDescriptor fd, @Format int format) throws IOException {
-        if (format != OutputFormat.MUXER_OUTPUT_MPEG_4 && format != OutputFormat.MUXER_OUTPUT_WEBM
-                && format != OutputFormat.MUXER_OUTPUT_3GPP) {
+        if (format < OutputFormat.MUXER_OUTPUT_FIRST || format > OutputFormat.MUXER_OUTPUT_LAST) {
             throw new IllegalArgumentException("format: " + format + " is invalid");
         }
         mNativeObject = nativeSetup(fd, format);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
index 700c01a..0f498bc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java
@@ -910,6 +910,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 +938,7 @@
                     context.getString(android.R.string.cancel), this);
             setButton(DialogInterface.BUTTON_POSITIVE,
                     context.getString(android.R.string.ok), this);
+            SystemUIDialog.setWindowOnTop(this);
         }
 
         @Override
diff --git a/services/accessibility/java/com/android/server/accessibility/GlobalActionPerformer.java b/services/accessibility/java/com/android/server/accessibility/GlobalActionPerformer.java
index 5db6f7d..5867006d 100644
--- a/services/accessibility/java/com/android/server/accessibility/GlobalActionPerformer.java
+++ b/services/accessibility/java/com/android/server/accessibility/GlobalActionPerformer.java
@@ -21,7 +21,11 @@
 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;
@@ -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/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java b/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java
index a45a4f0..dd29a04 100644
--- a/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java
+++ b/services/backup/java/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;
     }
diff --git a/services/backup/java/com/android/server/backup/TransportManager.java b/services/backup/java/com/android/server/backup/TransportManager.java
index 7a0173f..a2b5cb8 100644
--- a/services/backup/java/com/android/server/backup/TransportManager.java
+++ b/services/backup/java/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/services/backup/java/com/android/server/backup/internal/BackupHandler.java b/services/backup/java/com/android/server/backup/internal/BackupHandler.java
index 8f82300..9011b95 100644
--- a/services/backup/java/com/android/server/backup/internal/BackupHandler.java
+++ b/services/backup/java/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/services/backup/java/com/android/server/backup/internal/OnTaskFinishedListener.java b/services/backup/java/com/android/server/backup/internal/OnTaskFinishedListener.java
new file mode 100644
index 0000000..e417f06
--- /dev/null
+++ b/services/backup/java/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/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java b/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java
index c0caa557..1fa215a 100644
--- a/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java
+++ b/services/backup/java/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;
@@ -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) {
@@ -577,17 +585,21 @@
 
         if (!mCancelAll && mStatus == BackupTransport.TRANSPORT_OK &&
                 mPendingFullBackups != null && !mPendingFullBackups.isEmpty()) {
+            // TODO(brufino): Move the onFinish() call to the full-backup task
+            mListener.onFinished(callerLogString);
             Slog.d(TAG, "Starting full backups for: " + mPendingFullBackups);
             // Acquiring wakelock for PerformFullTransportBackupTask before its start.
             backupManagerService.getWakelock().acquire();
             (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/services/backup/java/com/android/server/backup/params/BackupParams.java b/services/backup/java/com/android/server/backup/params/BackupParams.java
index 4fd7ddb..2ba8ec1 100644
--- a/services/backup/java/com/android/server/backup/params/BackupParams.java
+++ b/services/backup/java/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/services/backup/java/com/android/server/backup/transport/TransportClient.java b/services/backup/java/com/android/server/backup/transport/TransportClient.java
new file mode 100644
index 0000000..9c39729
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/transport/TransportClient.java
@@ -0,0 +1,468 @@
+/*
+ * 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;
+    }
+
+    @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/services/backup/java/com/android/server/backup/transport/TransportClientManager.java b/services/backup/java/com/android/server/backup/transport/TransportClientManager.java
new file mode 100644
index 0000000..1cbe7471
--- /dev/null
+++ b/services/backup/java/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/services/backup/java/com/android/server/backup/transport/TransportConnectionListener.java b/services/backup/java/com/android/server/backup/transport/TransportConnectionListener.java
new file mode 100644
index 0000000..1ccffd0
--- /dev/null
+++ b/services/backup/java/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/services/backup/java/com/android/server/backup/transport/TransportNotAvailableException.java b/services/backup/java/com/android/server/backup/transport/TransportNotAvailableException.java
new file mode 100644
index 0000000..a02f03c
--- /dev/null
+++ b/services/backup/java/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)
+ */
+class TransportNotAvailableException extends Exception {
+    TransportNotAvailableException() {
+        super("Transport not available");
+    }
+}
diff --git a/services/backup/java/com/android/server/backup/transport/TransportUtils.java b/services/backup/java/com/android/server/backup/transport/TransportUtils.java
new file mode 100644
index 0000000..514717f
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/transport/TransportUtils.java
@@ -0,0 +1,60 @@
+/*
+ * 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 {
+
+    /**
+     * 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) {
+            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/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index ea0ed27..924e736 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/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/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index 0fd59ea..bdfccd6e 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/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/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS
index e5fbdd21..296fb32 100644
--- a/services/core/java/com/android/server/OWNERS
+++ b/services/core/java/com/android/server/OWNERS
@@ -1,3 +1,4 @@
+# Connectivity / Networking
 per-file ConnectivityService.java=ek@google.com
 per-file ConnectivityService.java=hugobenichi@google.com
 per-file ConnectivityService.java=lorenzo@google.com
@@ -7,3 +8,9 @@
 per-file NsdService.java=ek@google.com
 per-file NsdService.java=hugobenichi@google.com
 per-file NsdService.java=lorenzo@google.com
+
+# Vibrator
+per-file VibratorService.java=michaelwr@google.com
+
+# Threads
+per-file DisplayThread.java=michaelwr@google.com
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index b3b831e..ae91b82 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -351,7 +351,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;
@@ -716,6 +715,8 @@
 
     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}
@@ -1726,7 +1727,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;
@@ -1745,7 +1745,6 @@
     static KillHandler sKillHandler = null;
 
     CompatModeDialog mCompatModeDialog;
-    UnsupportedDisplaySizeDialog mUnsupportedDisplaySizeDialog;
     long mLastMemUsageReportTime = 0;
 
     /**
@@ -1927,23 +1926,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();
@@ -2676,6 +2658,7 @@
         GL_ES_VERSION = 0;
         mActivityStarter = null;
         mAppErrors = null;
+        mAppWarnings = null;
         mAppOpsService = mInjector.getAppOpsService(null, null);
         mBatteryStatsService = null;
         mCompatModePackages = null;
@@ -2743,10 +2726,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();
@@ -2793,7 +2779,7 @@
         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);
@@ -3310,22 +3296,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;
         }
@@ -3389,7 +3378,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
@@ -3493,7 +3482,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).
@@ -5330,6 +5320,8 @@
         // Remove this application's activities from active lists.
         boolean hasVisibleActivities = mStackSupervisor.handleAppDiedLocked(app);
 
+        app.clearRecentTasks();
+
         app.activities.clear();
 
         if (app.instr != null) {
@@ -11715,6 +11707,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);
@@ -14566,6 +14567,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();
@@ -19350,13 +19363,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);
                                     }
@@ -19435,13 +19442,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;
                 }
@@ -20631,8 +20633,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);
@@ -21013,7 +21014,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;
                     }
@@ -21092,6 +21093,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) {
@@ -22652,6 +22658,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.
@@ -23376,7 +23383,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 "
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 9a16745..5927666 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -934,6 +934,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;
     }
@@ -2549,7 +2557,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/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index c086c52..6985d6e 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -2602,7 +2602,7 @@
                             next.shortComponentName);
 
                     next.sleeping = false;
-                    mService.showUnsupportedZoomDialogIfNeededLocked(next);
+                    mService.getAppWarningsLocked().onResumeActivity(next);
                     mService.showAskCompatModeDialogLocked(next);
                     next.app.pendingUiClean = true;
                     next.app.forceProcessStateUpTo(mService.mTopProcessState);
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 745e9fb..ddde4bc 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -1270,7 +1270,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 +1358,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;
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index 9b8cbc1..03162bb 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/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: "
diff --git a/services/core/java/com/android/server/am/AppWarnings.java b/services/core/java/com/android/server/am/AppWarnings.java
new file mode 100644
index 0000000..a3c0345
--- /dev/null
+++ b/services/core/java/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/services/core/java/com/android/server/am/CompatModePackages.java b/services/core/java/com/android/server/am/CompatModePackages.java
index bfc0456..82019fd 100644
--- a/services/core/java/com/android/server/am/CompatModePackages.java
+++ b/services/core/java/com/android/server/am/CompatModePackages.java
@@ -60,6 +60,8 @@
     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;
+    // Unsupported compile SDK state: don't warn the user about unsupported compile SDK.
+    public static final int UNSUPPORTED_COMPILE_SDK_FLAG_DONT_NOTIFY = 1<<3;
 
     private final HashMap<String, Integer> mPackages = new HashMap<String, Integer>();
 
@@ -237,6 +239,10 @@
         return (getPackageFlags(packageName)&UNSUPPORTED_ZOOM_FLAG_DONT_NOTIFY) == 0;
     }
 
+    public boolean getPackageNotifyUnsupportedCompileSdkLocked(String packageName) {
+        return (getPackageFlags(packageName)&UNSUPPORTED_COMPILE_SDK_FLAG_DONT_NOTIFY) == 0;
+    }
+
     public void setFrontActivityAskCompatModeLocked(boolean ask) {
         ActivityRecord r = mService.getFocusedStack().topRunningActivityLocked();
         if (r != null) {
@@ -245,22 +251,20 @@
     }
 
     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) {
+        setPackageFlagLocked(packageName, UNSUPPORTED_ZOOM_FLAG_DONT_NOTIFY, notify);
+    }
+
+    public void setPackageNotifyUnsupportedCompileSdkLocked(String packageName, boolean notify) {
+        setPackageFlagLocked(packageName, UNSUPPORTED_COMPILE_SDK_FLAG_DONT_NOTIFY, 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/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 7810c5e..1a19601 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -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/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index e847723..9d3c2ae 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/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/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index 949f51f..a9c6eee 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -257,6 +257,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;
@@ -962,6 +967,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);
     }
@@ -2114,6 +2121,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 +2221,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));
diff --git a/services/core/java/com/android/server/am/UnsupportedCompileSdkDialog.java b/services/core/java/com/android/server/am/UnsupportedCompileSdkDialog.java
new file mode 100644
index 0000000..600589a
--- /dev/null
+++ b/services/core/java/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, appInfo.compileSdkVersionCodename);
+
+        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/services/core/java/com/android/server/am/UnsupportedDisplaySizeDialog.java b/services/core/java/com/android/server/am/UnsupportedDisplaySizeDialog.java
index 501cd6b..8850663 100644
--- a/services/core/java/com/android/server/am/UnsupportedDisplaySizeDialog.java
+++ b/services/core/java/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/services/core/java/com/android/server/display/BrightnessIdleJob.java b/services/core/java/com/android/server/display/BrightnessIdleJob.java
new file mode 100644
index 0000000..876acf4
--- /dev/null
+++ b/services/core/java/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/services/core/java/com/android/server/display/BrightnessTracker.java b/services/core/java/com/android/server/display/BrightnessTracker.java
index 361d928..90888f0 100644
--- a/services/core/java/com/android/server/display/BrightnessTracker.java
+++ b/services/core/java/com/android/server/display/BrightnessTracker.java
@@ -61,12 +61,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 +75,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 +103,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;
 
@@ -170,6 +172,8 @@
         intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED);
         mBroadcastReceiver = new Receiver();
         mInjector.registerReceiver(mContext, mBroadcastReceiver, intentFilter);
+
+        mInjector.scheduleIdleJob(mContext);
     }
 
     /** Stop listening for events */
@@ -181,6 +185,7 @@
         mInjector.unregisterSensorListener(mContext, mSensorListener);
         mInjector.unregisterReceiver(mContext, mBroadcastReceiver);
         mInjector.unregisterBrightnessObserver(mContext, mSettingsObserver);
+        mInjector.cancelIdleJob(mContext);
     }
 
     /**
@@ -211,6 +216,10 @@
                 brightness, userId);
     }
 
+    public void persistEvents() {
+        scheduleWriteEvents();
+    }
+
     private void handleBrightnessChanged() {
         if (DEBUG) {
             Slog.d(TAG, "Brightness change");
@@ -278,6 +287,7 @@
             Slog.d(TAG, "Event " + event.brightness + " " + event.packageName);
         }
         synchronized (mEventsLock) {
+            mEventsDirty = true;
             mEvents.append(event);
         }
     }
@@ -291,8 +301,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 +315,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 +333,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 +362,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 +486,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;
@@ -635,5 +667,13 @@
         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);
+        }
     }
 }
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index f1e2011..7530f3e 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -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/services/core/java/com/android/server/display/OWNERS b/services/core/java/com/android/server/display/OWNERS
new file mode 100644
index 0000000..7e7335d
--- /dev/null
+++ b/services/core/java/com/android/server/display/OWNERS
@@ -0,0 +1 @@
+michaelwr@google.com
diff --git a/services/core/java/com/android/server/input/OWNERS b/services/core/java/com/android/server/input/OWNERS
new file mode 100644
index 0000000..0313a40
--- /dev/null
+++ b/services/core/java/com/android/server/input/OWNERS
@@ -0,0 +1,2 @@
+michaelwr@google.com
+svv@google.com
diff --git a/services/core/java/com/android/server/job/JobSchedulerService.java b/services/core/java/com/android/server/job/JobSchedulerService.java
index b9777ec..4a3becb 100644
--- a/services/core/java/com/android/server/job/JobSchedulerService.java
+++ b/services/core/java/com/android/server/job/JobSchedulerService.java
@@ -151,6 +151,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.
@@ -622,15 +623,24 @@
             if (disabled) {
                 cancelJobsForUid(uid, "uid gone");
             }
+            synchronized (mLock) {
+                mDeviceIdleJobsController.setUidActiveLocked(uid, false);
+            }
         }
 
         @Override public void onUidActive(int uid) throws RemoteException {
+            synchronized (mLock) {
+                mDeviceIdleJobsController.setUidActiveLocked(uid, true);
+            }
         }
 
         @Override public void onUidIdle(int uid, boolean disabled) {
             if (disabled) {
                 cancelJobsForUid(uid, "app uid idle");
             }
+            synchronized (mLock) {
+                mDeviceIdleJobsController.setUidActiveLocked(uid, false);
+            }
         }
 
         @Override public void onUidCachedChanged(int uid, boolean cached) {
@@ -939,11 +949,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.
diff --git a/services/core/java/com/android/server/job/JobStore.java b/services/core/java/com/android/server/job/JobStore.java
index 1af3b39..28b60e3 100644
--- a/services/core/java/com/android/server/job/JobStore.java
+++ b/services/core/java/com/android/server/job/JobStore.java
@@ -250,7 +250,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 +287,10 @@
         mJobSet.forEachJob(uid, functor);
     }
 
+    public void forEachJobForSourceUid(int sourceUid, JobStatusFunctor functor) {
+        mJobSet.forEachJobForSourceUid(sourceUid, functor);
+    }
+
     public interface JobStatusFunctor {
         public void process(JobStatus jobStatus);
     }
@@ -979,9 +983,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 +1002,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 +1016,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 +1112,7 @@
 
         public void clear() {
             mJobs.clear();
+            mJobsPerSourceUid.clear();
         }
 
         public int size() {
@@ -1112,8 +1148,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/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java b/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java
index 374ab43..b7eb9e0 100644
--- a/services/core/java/com/android/server/job/controllers/DeviceIdleJobsController.java
+++ b/services/core/java/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/services/core/java/com/android/server/lights/OWNERS b/services/core/java/com/android/server/lights/OWNERS
new file mode 100644
index 0000000..7e7335d
--- /dev/null
+++ b/services/core/java/com/android/server/lights/OWNERS
@@ -0,0 +1 @@
+michaelwr@google.com
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 6ba1d8d..08da568 100644
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/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;
@@ -174,9 +177,9 @@
 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 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;
@@ -1520,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 =
@@ -1533,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,
@@ -1546,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) {
@@ -1924,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
@@ -1998,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
diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java
index b9c0d90..b1b0bf2 100644
--- a/services/core/java/com/android/server/notification/RankingConfig.java
+++ b/services/core/java/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/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java
index d566a45..c0dccb5 100644
--- a/services/core/java/com/android/server/notification/RankingHelper.java
+++ b/services/core/java/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/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index bbe59eb..2e44e79 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -3585,7 +3585,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;
             }
         }
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index ee773a5..44f36d1 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/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)
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 7748ae4..384070c 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -304,6 +304,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 +573,7 @@
     boolean mLidControlsSleep;
     int mShortPressOnPowerBehavior;
     int mLongPressOnPowerBehavior;
+    int mVeryLongPressOnPowerBehavior;
     int mDoublePressOnPowerBehavior;
     int mTriplePressOnPowerBehavior;
     int mLongPressOnBackBehavior;
@@ -586,6 +591,7 @@
     boolean mHasSoftInput = false;
     boolean mTranslucentDecorEnabled = true;
     boolean mUseTvRouting;
+    int mVeryLongPressTimeout;
 
     private boolean mHandleVolumeKeysInWM;
 
@@ -796,6 +802,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 +862,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;
@@ -1299,6 +1309,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 +1324,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 +1392,9 @@
             mPowerKeyHandled = true;
             mHandler.removeMessages(MSG_POWER_LONG_PRESS);
         }
+        if (hasVeryLongPressOnPowerBehavior()) {
+            mHandler.removeMessages(MSG_POWER_VERY_LONG_PRESS);
+        }
     }
 
     private void cancelPendingBackKeyAction() {
@@ -1516,6 +1542,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 +1623,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 +2032,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;
 
@@ -8193,6 +8250,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);
@@ -8445,6 +8505,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/services/core/java/com/android/server/power/BatterySaverPolicy.java b/services/core/java/com/android/server/power/BatterySaverPolicy.java
index 3992f8a..87c9274 100644
--- a/services/core/java/com/android/server/power/BatterySaverPolicy.java
+++ b/services/core/java/com/android/server/power/BatterySaverPolicy.java
@@ -32,6 +32,7 @@
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.power.batterysaver.CpuFrequencies;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -40,15 +41,14 @@
 /**
  * Class to decide whether to turn on battery saver mode for specific service
  *
- * TODO: We should probably make {@link #mFilesForInteractive} and {@link #mFilesForNoninteractive}
- * less flexible and just take a list of "CPU number - frequency" pairs. Being able to write
- * anything under /sys/ and /proc/ is too loose.
- *
- * 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
@@ -64,18 +64,26 @@
     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 = "force_all_apps_standby";
     private static final String KEY_OPTIONAL_SENSORS_DISABLED = "optional_sensors_disabled";
 
-    private static final String KEY_FILE_FOR_INTERACTIVE_PREFIX = "file-on:";
-    private static final String KEY_FILE_FOR_NONINTERACTIVE_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.
@@ -83,6 +91,7 @@
      * @see Settings.Global#BATTERY_SAVER_CONSTANTS
      * @see #KEY_VIBRATION_DISABLED
      */
+    @GuardedBy("mLock")
     private boolean mVibrationDisabled;
 
     /**
@@ -91,6 +100,7 @@
      * @see Settings.Global#BATTERY_SAVER_CONSTANTS
      * @see #KEY_ANIMATION_DISABLED
      */
+    @GuardedBy("mLock")
     private boolean mAnimationDisabled;
 
     /**
@@ -100,6 +110,7 @@
      * @see Settings.Global#BATTERY_SAVER_CONSTANTS
      * @see #KEY_SOUNDTRIGGER_DISABLED
      */
+    @GuardedBy("mLock")
     private boolean mSoundTriggerDisabled;
 
     /**
@@ -108,6 +119,7 @@
      * @see Settings.Global#BATTERY_SAVER_CONSTANTS
      * @see #KEY_FULLBACKUP_DEFERRED
      */
+    @GuardedBy("mLock")
     private boolean mFullBackupDeferred;
 
     /**
@@ -116,6 +128,7 @@
      * @see Settings.Global#BATTERY_SAVER_CONSTANTS
      * @see #KEY_KEYVALUE_DEFERRED
      */
+    @GuardedBy("mLock")
     private boolean mKeyValueBackupDeferred;
 
     /**
@@ -124,6 +137,7 @@
      * @see Settings.Global#BATTERY_SAVER_CONSTANTS
      * @see #KEY_FIREWALL_DISABLED
      */
+    @GuardedBy("mLock")
     private boolean mFireWallDisabled;
 
     /**
@@ -132,6 +146,7 @@
      * @see Settings.Global#BATTERY_SAVER_CONSTANTS
      * @see #KEY_ADJUST_BRIGHTNESS_DISABLED
      */
+    @GuardedBy("mLock")
     private boolean mAdjustBrightnessDisabled;
 
     /**
@@ -140,14 +155,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;
 
     /**
@@ -157,20 +180,21 @@
      * @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.
      */
+    @GuardedBy("mLock")
     private boolean mForceAllAppsStandby;
 
     /**
      * 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;
 
@@ -227,7 +251,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
@@ -273,6 +301,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.
@@ -291,6 +324,7 @@
         mAdjustBrightnessDisabled = parser.getBoolean(KEY_ADJUST_BRIGHTNESS_DISABLED, false);
         mAdjustBrightnessFactor = parser.getFloat(KEY_ADJUST_BRIGHTNESS_FACTOR, 0.5f);
         mDataSaverDisabled = parser.getBoolean(KEY_DATASAVER_DISABLED, true);
+        mLaunchBoostDisabled = parser.getBoolean(KEY_LAUNCH_BOOST_DISABLED, true);
         mForceAllAppsStandby = parser.getBoolean(KEY_FORCE_ALL_APPS_STANDBY, true);
         mOptionalSensorsDisabled = parser.getBoolean(KEY_OPTIONAL_SENSORS_DISABLED, true);
 
@@ -307,29 +341,11 @@
                     + deviceSpecificSetting);
         }
 
-        mFilesForInteractive = collectParams(parser, KEY_FILE_FOR_INTERACTIVE_PREFIX);
-        mFilesForNoninteractive = collectParams(parser, KEY_FILE_FOR_NONINTERACTIVE_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();
     }
 
     /**
@@ -395,14 +411,20 @@
         }
     }
 
+    public boolean isLaunchBoostDisabled() {
+        synchronized (mLock) {
+            return mLaunchBoostDisabled;
+        }
+    }
+
     public void dump(PrintWriter pw) {
         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);
@@ -411,6 +433,7 @@
             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);
diff --git a/services/core/java/com/android/server/power/OWNERS b/services/core/java/com/android/server/power/OWNERS
new file mode 100644
index 0000000..b4300a9
--- /dev/null
+++ b/services/core/java/com/android/server/power/OWNERS
@@ -0,0 +1,3 @@
+michaelwr@google.com
+
+per-file BatterySaverPolicy.java=omakoto@google.com
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index 584761c..2c87a40 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -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;
     }
 
-    private 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/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java b/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java
index 3db6a25..ae01ea57 100644
--- a/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java
+++ b/services/core/java/com/android/server/power/batterysaver/BatterySaverController.java
@@ -49,7 +49,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;
@@ -174,6 +174,13 @@
     }
 
     /**
+     * @return true if launch boost should currently be disabled.
+     */
+    public boolean isLaunchBoostDisabled() {
+        return isEnabled() && mBatterySaverPolicy.isLaunchBoostDisabled();
+    }
+
+    /**
      * Dispatch power save events to the listeners.
      *
      * This method is always called on the handler thread.
diff --git a/services/core/java/com/android/server/power/batterysaver/CpuFrequencies.java b/services/core/java/com/android/server/power/batterysaver/CpuFrequencies.java
new file mode 100644
index 0000000..1629486
--- /dev/null
+++ b/services/core/java/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/services/core/java/com/android/server/power/batterysaver/FileUpdater.java b/services/core/java/com/android/server/power/batterysaver/FileUpdater.java
index cfe8fc4..cc1b540 100644
--- a/services/core/java/com/android/server/power/batterysaver/FileUpdater.java
+++ b/services/core/java/com/android/server/power/batterysaver/FileUpdater.java
@@ -16,40 +16,259 @@
 package com.android.server.power.batterysaver;
 
 import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
 import android.util.ArrayMap;
 import android.util.Slog;
 
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.IoThread;
+
+import libcore.io.IoUtils;
+
+import java.io.FileWriter;
+import java.io.IOException;
+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;
 
+    // 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);
     }
 
+    /**
+     * 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;
+    }
+
+    /**
+     * Write values to files. (Note the actual writes happen ASAP but asynchronously.)
+     */
     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) + "'");
+        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();
+        }
     }
 
-    public void restoreDefault() {
-        if (DEBUG) {
-            Slog.d(TAG, "Resetting file default values");
+    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);
+        }
+        return true;
+    }
+
+    @VisibleForTesting
+    String injectReadFromFileTrimmed(String file) throws IOException {
+        return IoUtils.readFileAsString(file).trim();
+    }
+
+    @VisibleForTesting
+    void injectWriteToFile(String file, String value) throws IOException {
+        if (DEBUG) {
+            Slog.d(TAG, "Writing: '" + value + "' to '" + file + "'");
+        }
+        try (FileWriter out = new FileWriter(file)) {
+            out.write(value);
+        } catch (IOException e) {
+            Slog.w(TAG, "Failed writing '" + value + "' to '" + file + "': " + e.getMessage());
+            throw e;
+        }
+    }
+
+    private void doWtf(String message) {
+        injectWtf(message, null);
+    }
+
+    @VisibleForTesting
+    void injectWtf(String message, Throwable e) {
+        Slog.wtf(TAG, message, e);
+    }
 }
diff --git a/services/core/java/com/android/server/power/batterysaver/OWNERS b/services/core/java/com/android/server/power/batterysaver/OWNERS
new file mode 100644
index 0000000..09136dc
--- /dev/null
+++ b/services/core/java/com/android/server/power/batterysaver/OWNERS
@@ -0,0 +1 @@
+omakoto@google.com
diff --git a/services/core/java/com/android/server/utils/AppInstallerUtil.java b/services/core/java/com/android/server/utils/AppInstallerUtil.java
new file mode 100644
index 0000000..af7ff41
--- /dev/null
+++ b/services/core/java/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/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 4656539..e123bef 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -7455,6 +7455,11 @@
         public void registerDragDropControllerCallback(IDragDropCallback callback) {
             mDragDropController.registerCallback(callback);
         }
+
+        @Override
+        public void lockNow() {
+            WindowManagerService.this.lockNow(null);
+        }
     }
 
     void registerAppFreezeListener(AppFreezeListener listener) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 52b7a25..730ec37 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -2109,7 +2109,11 @@
         if (task != null) {
             return task.getDimmer();
         }
-        return getStack().getDimmer();
+        TaskStack taskStack = getStack();
+        if (taskStack != null) {
+            return taskStack.getDimmer();
+        }
+        return null;
     }
 
     /** Returns true if the replacement window was removed. */
@@ -4390,10 +4394,7 @@
         return mToken.makeChildSurface(this);
     }
 
-
-    @Override
-    void prepareSurfaces() {
-        mIsDimming = false;
+    private void applyDims(Dimmer dimmer) {
         if (!mAnimatingExit && mAppDied) {
             mIsDimming = true;
             getDimmer().dimAbove(getPendingTransaction(), this, DEFAULT_DIM_AMOUNT_DEAD_WINDOW);
@@ -4402,6 +4403,15 @@
             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();
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 2d8a0ee..60c36d1 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/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/services/robotests/src/com/android/server/backup/transport/TransportClientTest.java b/services/robotests/src/com/android/server/backup/transport/TransportClientTest.java
new file mode 100644
index 0000000..54d233a
--- /dev/null
+++ b/services/robotests/src/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/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
index c145e82..55ec133 100644
--- a/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/notification/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -16,8 +16,10 @@
 
 package com.android.server.notification;
 
+import static android.app.NotificationManager.EXTRA_BLOCKED_STATE;
 import static android.app.NotificationManager.IMPORTANCE_HIGH;
 import static android.app.NotificationManager.IMPORTANCE_LOW;
+import static android.app.NotificationManager.IMPORTANCE_MAX;
 import static android.app.NotificationManager.IMPORTANCE_NONE;
 import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
 import static android.content.pm.PackageManager.FEATURE_WATCH;
@@ -940,6 +942,120 @@
     }
 
     @Test
+    public void testUpdateChannelNotifyCreatorBlock() throws Exception {
+        mService.setRankingHelper(mRankingHelper);
+        when(mRankingHelper.getNotificationChannel(eq(PKG), anyInt(),
+                eq(mTestNotificationChannel.getId()), anyBoolean()))
+                .thenReturn(mTestNotificationChannel);
+
+        NotificationChannel updatedChannel =
+                new NotificationChannel(mTestNotificationChannel.getId(),
+                        mTestNotificationChannel.getName(), IMPORTANCE_NONE);
+
+        mBinderService.updateNotificationChannelForPackage(PKG, 0, updatedChannel);
+        ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext, times(1)).sendBroadcastAsUser(captor.capture(), any(), eq(null));
+
+        assertEquals(NotificationManager.ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED,
+                captor.getValue().getAction());
+        assertEquals(PKG, captor.getValue().getPackage());
+        assertEquals(mTestNotificationChannel.getId(), captor.getValue().getStringExtra(
+                        NotificationManager.EXTRA_BLOCK_STATE_CHANGED_ID));
+        assertTrue(captor.getValue().getBooleanExtra(EXTRA_BLOCKED_STATE, false));
+    }
+
+    @Test
+    public void testUpdateChannelNotifyCreatorUnblock() throws Exception {
+        NotificationChannel existingChannel =
+                new NotificationChannel(mTestNotificationChannel.getId(),
+                        mTestNotificationChannel.getName(), IMPORTANCE_NONE);
+        mService.setRankingHelper(mRankingHelper);
+        when(mRankingHelper.getNotificationChannel(eq(PKG), anyInt(),
+                eq(mTestNotificationChannel.getId()), anyBoolean()))
+                .thenReturn(existingChannel);
+
+        mBinderService.updateNotificationChannelForPackage(PKG, 0, mTestNotificationChannel);
+        ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext, times(1)).sendBroadcastAsUser(captor.capture(), any(), eq(null));
+
+        assertEquals(NotificationManager.ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED,
+                captor.getValue().getAction());
+        assertEquals(PKG, captor.getValue().getPackage());
+        assertEquals(mTestNotificationChannel.getId(), captor.getValue().getStringExtra(
+                NotificationManager.EXTRA_BLOCK_STATE_CHANGED_ID));
+        assertFalse(captor.getValue().getBooleanExtra(EXTRA_BLOCKED_STATE, false));
+    }
+
+    @Test
+    public void testUpdateChannelNoNotifyCreatorOtherChanges() throws Exception {
+        NotificationChannel existingChannel =
+                new NotificationChannel(mTestNotificationChannel.getId(),
+                        mTestNotificationChannel.getName(), IMPORTANCE_MAX);
+        mService.setRankingHelper(mRankingHelper);
+        when(mRankingHelper.getNotificationChannel(eq(PKG), anyInt(),
+                eq(mTestNotificationChannel.getId()), anyBoolean()))
+                .thenReturn(existingChannel);
+
+        mBinderService.updateNotificationChannelForPackage(PKG, 0, mTestNotificationChannel);
+        verify(mContext, never()).sendBroadcastAsUser(any(), any(), eq(null));
+    }
+
+    @Test
+    public void testUpdateGroupNotifyCreatorBlock() throws Exception {
+        NotificationChannelGroup existing = new NotificationChannelGroup("id", "name");
+        mService.setRankingHelper(mRankingHelper);
+        when(mRankingHelper.getNotificationChannelGroup(eq(existing.getId()), eq(PKG), anyInt()))
+                .thenReturn(existing);
+
+        NotificationChannelGroup updated = new NotificationChannelGroup("id", "name");
+        updated.setBlocked(true);
+
+        mBinderService.updateNotificationChannelGroupForPackage(PKG, 0, updated);
+        ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext, times(1)).sendBroadcastAsUser(captor.capture(), any(), eq(null));
+
+        assertEquals(NotificationManager.ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED,
+                captor.getValue().getAction());
+        assertEquals(PKG, captor.getValue().getPackage());
+        assertEquals(existing.getId(), captor.getValue().getStringExtra(
+                NotificationManager.EXTRA_BLOCK_STATE_CHANGED_ID));
+        assertTrue(captor.getValue().getBooleanExtra(EXTRA_BLOCKED_STATE, false));
+    }
+
+    @Test
+    public void testUpdateGroupNotifyCreatorUnblock() throws Exception {
+        NotificationChannelGroup existing = new NotificationChannelGroup("id", "name");
+        existing.setBlocked(true);
+        mService.setRankingHelper(mRankingHelper);
+        when(mRankingHelper.getNotificationChannelGroup(eq(existing.getId()), eq(PKG), anyInt()))
+                .thenReturn(existing);
+
+        mBinderService.updateNotificationChannelGroupForPackage(
+                PKG, 0, new NotificationChannelGroup("id", "name"));
+        ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
+        verify(mContext, times(1)).sendBroadcastAsUser(captor.capture(), any(), eq(null));
+
+        assertEquals(NotificationManager.ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED,
+                captor.getValue().getAction());
+        assertEquals(PKG, captor.getValue().getPackage());
+        assertEquals(existing.getId(), captor.getValue().getStringExtra(
+                NotificationManager.EXTRA_BLOCK_STATE_CHANGED_ID));
+        assertFalse(captor.getValue().getBooleanExtra(EXTRA_BLOCKED_STATE, false));
+    }
+
+    @Test
+    public void testUpdateGroupNoNotifyCreatorOtherChanges() throws Exception {
+        NotificationChannelGroup existing = new NotificationChannelGroup("id", "name");
+        mService.setRankingHelper(mRankingHelper);
+        when(mRankingHelper.getNotificationChannelGroup(eq(existing.getId()), eq(PKG), anyInt()))
+                .thenReturn(existing);
+
+        mBinderService.updateNotificationChannelGroupForPackage(
+                PKG, 0, new NotificationChannelGroup("id", "new name"));
+        verify(mContext, never()).sendBroadcastAsUser(any(), any(), eq(null));
+    }
+
+    @Test
     public void testCreateChannelNotifyListener() throws Exception {
         List<String> associations = new ArrayList<>();
         associations.add("a");
@@ -1040,6 +1156,9 @@
         List<String> associations = new ArrayList<>();
         associations.add("a");
         when(mCompanionMgr.getAssociations(PKG, mUid)).thenReturn(associations);
+        when(mRankingHelper.getNotificationChannel(eq(PKG), anyInt(),
+                eq(mTestNotificationChannel.getId()), anyBoolean()))
+                .thenReturn(mTestNotificationChannel);
 
         mBinderService.updateNotificationChannelFromPrivilegedListener(
                 null, PKG, Process.myUserHandle(), mTestNotificationChannel);
diff --git a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
index 3c02e23..2d03f11 100644
--- a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
+++ b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java
@@ -372,7 +372,7 @@
                 mHelper.getNotificationChannel(PKG, UID, channel2.getId(), false));
 
         List<NotificationChannelGroup> actualGroups =
-                mHelper.getNotificationChannelGroups(PKG, UID, false).getList();
+                mHelper.getNotificationChannelGroups(PKG, UID, false, true).getList();
         boolean foundNcg = false;
         for (NotificationChannelGroup actual : actualGroups) {
             if (ncg.getId().equals(actual.getId())) {
@@ -442,7 +442,7 @@
                 mHelper.getNotificationChannel(PKG, UID, channel3.getId(), false));
 
         List<NotificationChannelGroup> actualGroups =
-                mHelper.getNotificationChannelGroups(PKG, UID, false).getList();
+                mHelper.getNotificationChannelGroups(PKG, UID, false, true).getList();
         boolean foundNcg = false;
         for (NotificationChannelGroup actual : actualGroups) {
             if (ncg.getId().equals(actual.getId())) {
@@ -1281,7 +1281,8 @@
 
         mHelper.onPackagesChanged(true, UserHandle.USER_SYSTEM, new String[]{PKG}, new int[]{UID});
 
-        assertEquals(0, mHelper.getNotificationChannelGroups(PKG, UID, true).getList().size());
+        assertEquals(0,
+                mHelper.getNotificationChannelGroups(PKG, UID, true, true).getList().size());
     }
 
     @Test
@@ -1370,7 +1371,7 @@
         mHelper.createNotificationChannel(PKG, UID, channel3, true);
 
         List<NotificationChannelGroup> actual =
-                mHelper.getNotificationChannelGroups(PKG, UID, true).getList();
+                mHelper.getNotificationChannelGroups(PKG, UID, true, true).getList();
         assertEquals(3, actual.size());
         for (NotificationChannelGroup group : actual) {
             if (group.getId() == null) {
@@ -1402,13 +1403,13 @@
                 new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH);
         channel1.setGroup(ncg.getId());
         mHelper.createNotificationChannel(PKG, UID, channel1, true);
-        mHelper.getNotificationChannelGroups(PKG, UID, true).getList();
+        mHelper.getNotificationChannelGroups(PKG, UID, true, true).getList();
 
         channel1.setImportance(IMPORTANCE_LOW);
         mHelper.updateNotificationChannel(PKG, UID, channel1, true);
 
         List<NotificationChannelGroup> actual =
-                mHelper.getNotificationChannelGroups(PKG, UID, true).getList();
+                mHelper.getNotificationChannelGroups(PKG, UID, true, true).getList();
 
         assertEquals(2, actual.size());
         for (NotificationChannelGroup group : actual) {
diff --git a/services/tests/servicestests/res/values/strings.xml b/services/tests/servicestests/res/values/strings.xml
index 3ac56bb..57da0af 100644
--- a/services/tests/servicestests/res/values/strings.xml
+++ b/services/tests/servicestests/res/values/strings.xml
@@ -30,6 +30,6 @@
     <string name="test_account_type2">com.android.server.accounts.account_manager_service_test.account.type2</string>
 
     <string name="config_batterySaverDeviceSpecificConfig_1"></string>
-    <string name="config_batterySaverDeviceSpecificConfig_2">file-off:/sys/a=1,file-off:/sys/b=2</string>
-    <string name="config_batterySaverDeviceSpecificConfig_3">file-off:/sys/a=3,file-on:/proc/c=4,/abc=3</string>
+    <string name="config_batterySaverDeviceSpecificConfig_2">cpufreq-n=1:123/2:456</string>
+    <string name="config_batterySaverDeviceSpecificConfig_3">cpufreq-n=2:222,cpufreq-i=3:333/4:444</string>
 </resources>
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java b/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java
index f9933fb6..bc503c4 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityStarterTests.java
@@ -95,8 +95,7 @@
     public void setUp() throws Exception {
         super.setUp();
         mService = createActivityManagerService();
-        mPackageManager = mock(IPackageManager.class);
-        mStarter = new ActivityStarter(mService, mPackageManager);
+        mStarter = new ActivityStarter(mService);
     }
 
     @Test
@@ -178,7 +177,7 @@
             int expectedResult) {
         final ActivityManagerService service = createActivityManagerService();
         final IPackageManager packageManager = mock(IPackageManager.class);
-        final ActivityStarter starter = new ActivityStarter(service, packageManager);
+        final ActivityStarter starter = new ActivityStarter(service);
 
         final IApplicationThread caller = mock(IApplicationThread.class);
 
diff --git a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java
index 9c949ad..9683e22 100644
--- a/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/am/ActivityTestsBase.java
@@ -36,6 +36,7 @@
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.hardware.display.DisplayManager;
@@ -90,6 +91,7 @@
 
     protected ActivityManagerService setupActivityManagerService(ActivityManagerService service) {
         service = spy(service);
+        doReturn(mock(IPackageManager.class)).when(service).getPackageManager();
         service.mWindowManager = prepareMockWindowManager();
         return service;
     }
diff --git a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
index d9fac87..6938e0f 100644
--- a/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/BrightnessTrackerTest.java
@@ -99,10 +99,12 @@
         assertNotNull(mInjector.mSensorListener);
         assertNotNull(mInjector.mSettingsObserver);
         assertNotNull(mInjector.mBroadcastReceiver);
+        assertTrue(mInjector.mIdleScheduled);
         mTracker.stop();
         assertNull(mInjector.mSensorListener);
         assertNull(mInjector.mSettingsObserver);
         assertNull(mInjector.mBroadcastReceiver);
+        assertFalse(mInjector.mIdleScheduled);
     }
 
     @Test
@@ -399,6 +401,52 @@
     }
 
     @Test
+    public void testWritePrunesOldEvents() throws Exception {
+        final int brightness = 20;
+
+        mInjector.mSystemIntSettings.put(Settings.System.SCREEN_BRIGHTNESS, brightness);
+        mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_ACTIVATED, 1);
+        mInjector.mSecureIntSettings.put(Settings.Secure.NIGHT_DISPLAY_COLOR_TEMPERATURE, 3339);
+
+        startTracker(mTracker);
+        mInjector.mBroadcastReceiver.onReceive(InstrumentationRegistry.getContext(),
+                batteryChangeEvent(30, 100));
+        mInjector.mSensorListener.onSensorChanged(createSensorEvent(1000.0f));
+        mInjector.incrementTime(TimeUnit.SECONDS.toMillis(1));
+        mInjector.mSensorListener.onSensorChanged(createSensorEvent(2000.0f));
+        final long sensorTime = mInjector.currentTimeMillis();
+        mInjector.mSettingsObserver.onChange(false, Settings.System.getUriFor(
+                Settings.System.SCREEN_BRIGHTNESS));
+
+        // 31 days later
+        mInjector.incrementTime(TimeUnit.DAYS.toMillis(31));
+        mInjector.mSensorListener.onSensorChanged(createSensorEvent(3000.0f));
+        mInjector.mSettingsObserver.onChange(false, Settings.System.getUriFor(
+                Settings.System.SCREEN_BRIGHTNESS));
+        final long eventTime = mInjector.currentTimeMillis();
+
+        List<BrightnessChangeEvent> events = mTracker.getEvents(0).getList();
+        assertEquals(2, events.size());
+
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        mTracker.writeEventsLocked(baos);
+        events = mTracker.getEvents(0).getList();
+        mTracker.stop();
+
+        assertEquals(1, events.size());
+        BrightnessChangeEvent event = events.get(0);
+        assertEquals(eventTime, event.timeStamp);
+
+        // We will keep one of the old sensor events because we keep 1 event outside the window.
+        assertArrayEquals(new float[] {2000.0f, 3000.0f}, event.luxValues, 0.01f);
+        assertArrayEquals(new long[] {sensorTime, eventTime}, event.luxTimestamps);
+        assertEquals(brightness, event.brightness);
+        assertEquals(0.3, event.batteryLevel, 0.01f);
+        assertTrue(event.nightMode);
+        assertEquals(3339, event.colorTemperature);
+    }
+
+    @Test
     public void testParcelUnParcel() {
         Parcel parcel = Parcel.obtain();
         BrightnessChangeEvent event = new BrightnessChangeEvent();
@@ -516,6 +564,7 @@
         long mCurrentTimeMillis = System.currentTimeMillis();
         long mElapsedRealtimeNanos = SystemClock.elapsedRealtimeNanos();
         Handler mHandler;
+        boolean mIdleScheduled;
 
         public TestInjector(Handler handler) {
             mHandler = handler;
@@ -636,5 +685,14 @@
             focusedStack.topActivity = new ComponentName("a.package", "a.class");
             return focusedStack;
         }
+
+        public void scheduleIdleJob(Context context) {
+            // Don't actually schedule jobs during unit tests.
+            mIdleScheduled = true;
+        }
+
+        public void cancelIdleJob(Context context) {
+            mIdleScheduled = false;
+        }
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
index fa8feb0..1f9a243 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
@@ -197,6 +197,8 @@
         assertEquals(a.coreApp, b.coreApp);
         assertEquals(a.mRequiredForAllUsers, b.mRequiredForAllUsers);
         assertEquals(a.mTrustedOverlay, b.mTrustedOverlay);
+        assertEquals(a.mCompileSdkVersion, b.mCompileSdkVersion);
+        assertEquals(a.mCompileSdkVersionCodename, b.mCompileSdkVersionCodename);
         assertEquals(a.use32bitAbi, b.use32bitAbi);
         assertEquals(a.packageName, b.packageName);
         assertTrue(Arrays.equals(a.splitNames, b.splitNames));
diff --git a/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java b/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java
index 0db19e4..20cf733 100644
--- a/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/BatterySaverPolicyTest.java
@@ -237,21 +237,27 @@
         mBatterySaverPolicy.onChange();
         assertThat(mBatterySaverPolicy.getFileValues(true).toString()).isEqualTo("{}");
         assertThat(mBatterySaverPolicy.getFileValues(false).toString())
-                .isEqualTo("{/sys/a=1, /sys/b=2}");
-
+                .isEqualTo("{/sys/devices/system/cpu/cpu1/cpufreq/scaling_max_freq=123, " +
+                "/sys/devices/system/cpu/cpu2/cpufreq/scaling_max_freq=456}");
 
         mDeviceSpecificConfigResId = R.string.config_batterySaverDeviceSpecificConfig_3;
 
         mBatterySaverPolicy.onChange();
-        assertThat(mBatterySaverPolicy.getFileValues(true).toString()).isEqualTo("{/proc/c=4}");
-        assertThat(mBatterySaverPolicy.getFileValues(false).toString()).isEqualTo("{/sys/a=3}");
+        assertThat(mBatterySaverPolicy.getFileValues(true).toString())
+                .isEqualTo("{/sys/devices/system/cpu/cpu3/cpufreq/scaling_max_freq=333, " +
+                        "/sys/devices/system/cpu/cpu4/cpufreq/scaling_max_freq=444}");
+        assertThat(mBatterySaverPolicy.getFileValues(false).toString())
+                .isEqualTo("{/sys/devices/system/cpu/cpu2/cpufreq/scaling_max_freq=222}");
 
 
         mMockGlobalSettings.put(Global.BATTERY_SAVER_DEVICE_SPECIFIC_CONSTANTS,
-                "file-on:/proc/z=4");
+                "cpufreq-i=3:1234567890/4:014/5:015");
 
         mBatterySaverPolicy.onChange();
-        assertThat(mBatterySaverPolicy.getFileValues(true).toString()).isEqualTo("{/proc/z=4}");
+        assertThat(mBatterySaverPolicy.getFileValues(true).toString())
+                .isEqualTo("{/sys/devices/system/cpu/cpu3/cpufreq/scaling_max_freq=1234567890, " +
+                        "/sys/devices/system/cpu/cpu4/cpufreq/scaling_max_freq=14, " +
+                        "/sys/devices/system/cpu/cpu5/cpufreq/scaling_max_freq=15}");
         assertThat(mBatterySaverPolicy.getFileValues(false).toString()).isEqualTo("{}");
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/power/batterysaver/CpuFrequenciesTest.java b/services/tests/servicestests/src/com/android/server/power/batterysaver/CpuFrequenciesTest.java
new file mode 100644
index 0000000..f72ec34
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/power/batterysaver/CpuFrequenciesTest.java
@@ -0,0 +1,69 @@
+/*
+ * 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 static org.junit.Assert.assertEquals;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.ArrayMap;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/power/batterysaver/CpuFrequenciesTest.java
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class CpuFrequenciesTest {
+    private void check(ArrayMap<String, String> expected, String config) {
+        assertEquals(expected, (new CpuFrequencies().parseString(config))
+                .toSysFileMap());
+    }
+
+    @Test
+    public void test() {
+        check(new ArrayMap<>(), "");
+
+        final ArrayMap<String, String> expected = new ArrayMap<>();
+
+        expected.clear();
+        expected.put("/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq", "0");
+        check(expected, "0:0");
+
+        expected.clear();
+        expected.put("/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq", "0");
+        expected.put("/sys/devices/system/cpu/cpu1/cpufreq/scaling_max_freq", "1");
+        check(expected, "0:0/1:1");
+
+        expected.clear();
+        expected.put("/sys/devices/system/cpu/cpu2/cpufreq/scaling_max_freq", "0");
+        expected.put("/sys/devices/system/cpu/cpu1/cpufreq/scaling_max_freq", "1234567890");
+        check(expected, "2:0/1:1234567890");
+
+        expected.clear();
+        expected.put("/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq", "1900800");
+        expected.put("/sys/devices/system/cpu/cpu4/cpufreq/scaling_max_freq", "1958400");
+        check(expected, "0:1900800/4:1958400");
+
+        check(expected, "0:1900800/4:1958400/"); // Shouldn't crash.
+        check(expected, "0:1900800/4:1958400/1"); // Shouldn't crash.
+        check(expected, "0:1900800/4:1958400/a:1"); // Shouldn't crash.
+        check(expected, "0:1900800/4:1958400/1:"); // Shouldn't crash.
+        check(expected, "0:1900800/4:1958400/1:b"); // Shouldn't crash.
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/power/batterysaver/FileUpdaterTest.java b/services/tests/servicestests/src/com/android/server/power/batterysaver/FileUpdaterTest.java
new file mode 100644
index 0000000..7e2a7d2
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/power/batterysaver/FileUpdaterTest.java
@@ -0,0 +1,337 @@
+/*
+ * 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 static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Looper;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.ArrayMap;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatchers;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.IOException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+
+/**
+ atest $ANDROID_BUILD_TOP/frameworks/base/services/tests/servicestests/src/com/android/server/power/batterysaver/FileUpdaterTest.java
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class FileUpdaterTest {
+
+    private class FileUpdaterTestable extends FileUpdater {
+        FileUpdaterTestable(Context context, Looper looper, int maxRetries, int retryIntervalMs) {
+            super(context, looper, maxRetries, retryIntervalMs);
+        }
+
+        @Override
+        String injectReadFromFileTrimmed(String file) throws IOException {
+            return mInjector.injectReadFromFileTrimmed(file);
+        }
+
+        @Override
+        void injectWriteToFile(String file, String value) throws IOException {
+            mInjector.injectWriteToFile(file, value);
+        }
+
+        @Override
+        void injectWtf(String message, Throwable e) {
+            mInjector.injectWtf(message, e);
+        }
+    }
+
+    private interface Injector {
+        String injectReadFromFileTrimmed(String file) throws IOException;
+        void injectWriteToFile(String file, String value) throws IOException;
+        void injectWtf(String message, Throwable e);
+    }
+
+    private Handler mMainHandler;
+
+    @Mock
+    private Injector mInjector;
+
+    private static final int MAX_RETRIES = 3;
+
+    private FileUpdaterTestable mInstance;
+
+    public static <T> T anyOrNull(Class<T> clazz) {
+        return ArgumentMatchers.argThat(value -> true);
+    }
+
+    public static String anyOrNullString() {
+        return ArgumentMatchers.argThat(value -> true);
+    }
+
+    @Before
+    public void setUp() {
+        mMainHandler = new Handler(Looper.getMainLooper());
+
+        MockitoAnnotations.initMocks(this);
+
+        mInstance = newInstance();
+    }
+
+    private FileUpdaterTestable newInstance() {
+        return new FileUpdaterTestable(
+                InstrumentationRegistry.getContext(),
+                Looper.getMainLooper(),
+                MAX_RETRIES,
+                0 /* retry with no delays*/);
+    }
+
+    private void waitUntilMainHandlerDrain() throws Exception {
+        final CountDownLatch l = new CountDownLatch(1);
+        mMainHandler.post(() -> l.countDown());
+        assertTrue(l.await(5, TimeUnit.SECONDS));
+    }
+
+    private void veriryWtf(int times) {
+        verify(mInjector, times(times)).injectWtf(anyOrNullString(), anyOrNull(Throwable.class));
+    }
+
+    @Test
+    public void testNoWrites() throws Exception {
+        doReturn("111").when(mInjector).injectReadFromFileTrimmed("file1");
+        doReturn("222").when(mInjector).injectReadFromFileTrimmed("file2");
+        doReturn("333").when(mInjector).injectReadFromFileTrimmed("file3");
+
+        // Write
+        final ArrayMap<String, String> values = new ArrayMap<>();
+
+        mInstance.writeFiles(values);
+        waitUntilMainHandlerDrain();
+
+        verify(mInjector, times(0)).injectWriteToFile(anyOrNullString(), anyOrNullString());
+
+        // Reset to default
+        mInstance.restoreDefault();
+        waitUntilMainHandlerDrain();
+
+        verify(mInjector, times(0)).injectWriteToFile(anyOrNullString(), anyOrNullString());
+
+        // No WTF should have happened.
+        veriryWtf(0);
+    }
+
+    @Test
+    public void testSimpleWrite() throws Exception {
+        doReturn("111").when(mInjector).injectReadFromFileTrimmed("file1");
+        doReturn("222").when(mInjector).injectReadFromFileTrimmed("file2");
+        doReturn("333").when(mInjector).injectReadFromFileTrimmed("file3");
+
+        // Write
+        final ArrayMap<String, String> values = new ArrayMap<>();
+        values.put("file1", "11");
+
+        mInstance.writeFiles(values);
+        waitUntilMainHandlerDrain();
+
+        verify(mInjector, times(1)).injectWriteToFile("file1", "11");
+
+        // Reset to default
+        mInstance.restoreDefault();
+        waitUntilMainHandlerDrain();
+
+        verify(mInjector, times(1)).injectWriteToFile("file1", "111");
+
+        // No WTF should have happened.
+        veriryWtf(0);
+    }
+
+    @Test
+    public void testMultiWrites() throws Exception {
+        doReturn("111").when(mInjector).injectReadFromFileTrimmed("file1");
+        doReturn("222").when(mInjector).injectReadFromFileTrimmed("file2");
+        doReturn("333").when(mInjector).injectReadFromFileTrimmed("file3");
+
+        // Write
+        final ArrayMap<String, String> values = new ArrayMap<>();
+        values.put("file1", "11");
+        values.put("file2", "22");
+        values.put("file3", "33");
+
+        mInstance.writeFiles(values);
+        waitUntilMainHandlerDrain();
+
+        verify(mInjector, times(1)).injectWriteToFile("file1", "11");
+        verify(mInjector, times(1)).injectWriteToFile("file2", "22");
+        verify(mInjector, times(1)).injectWriteToFile("file3", "33");
+
+        // Reset to default
+        mInstance.restoreDefault();
+        waitUntilMainHandlerDrain();
+
+        verify(mInjector, times(1)).injectWriteToFile("file1", "111");
+        verify(mInjector, times(1)).injectWriteToFile("file2", "222");
+        verify(mInjector, times(1)).injectWriteToFile("file3", "333");
+
+        // No WTF should have happened.
+        veriryWtf(0);
+    }
+
+    @Test
+    public void testCantReadDefault() throws Exception {
+        doThrow(new IOException("can't read")).when(mInjector).injectReadFromFileTrimmed("file1");
+        doReturn("222").when(mInjector).injectReadFromFileTrimmed("file2");
+
+        // Write
+        final ArrayMap<String, String> values = new ArrayMap<>();
+        values.put("file1", "11");
+        values.put("file2", "22");
+
+        mInstance.writeFiles(values);
+        waitUntilMainHandlerDrain();
+
+        verify(mInjector, times(0)).injectWriteToFile("file1", "11");
+        verify(mInjector, times(1)).injectWriteToFile("file2", "22");
+
+        veriryWtf(1);
+
+        // Reset to default
+        mInstance.restoreDefault();
+        waitUntilMainHandlerDrain();
+
+        verify(mInjector, times(0)).injectWriteToFile("file1", "111");
+        verify(mInjector, times(1)).injectWriteToFile("file2", "222");
+
+        veriryWtf(1);
+    }
+
+    @Test
+    public void testWriteGiveUp() throws Exception {
+        doReturn("111").when(mInjector).injectReadFromFileTrimmed("file1");
+        doReturn("222").when(mInjector).injectReadFromFileTrimmed("file2");
+        doReturn("333").when(mInjector).injectReadFromFileTrimmed("fail1");
+
+        doThrow(new IOException("can't write")).when(mInjector).injectWriteToFile(
+                eq("fail1"), eq("33"));
+
+        // Write
+        final ArrayMap<String, String> values = new ArrayMap<>();
+        values.put("file1", "11");
+        values.put("file2", "22");
+        values.put("fail1", "33");
+
+        mInstance.writeFiles(values);
+        waitUntilMainHandlerDrain();
+
+        verify(mInjector, times(1)).injectWriteToFile("file1", "11");
+        verify(mInjector, times(1)).injectWriteToFile("file2", "22");
+
+        verify(mInjector, times(MAX_RETRIES + 1)).injectWriteToFile("fail1", "33");
+
+        // 1 WTF.
+        veriryWtf(1);
+
+        // Reset to default
+        mInstance.restoreDefault();
+        waitUntilMainHandlerDrain();
+
+        verify(mInjector, times(1)).injectWriteToFile("file1", "111");
+        verify(mInjector, times(1)).injectWriteToFile("file2", "222");
+
+        verify(mInjector, times(1)).injectWriteToFile("fail1", "333");
+
+        // No further WTF.
+        veriryWtf(1);
+    }
+
+    @Test
+    public void testSuccessWithRetry() throws Exception {
+        doReturn("111").when(mInjector).injectReadFromFileTrimmed("file1");
+        doReturn("222").when(mInjector).injectReadFromFileTrimmed("file2");
+        doReturn("333").when(mInjector).injectReadFromFileTrimmed("fail1");
+
+        final AtomicInteger counter = new AtomicInteger();
+        doAnswer((inv) -> {
+            if (counter.getAndIncrement() <= 1) {
+                throw new IOException();
+            }
+            return null;
+            }).when(mInjector).injectWriteToFile(eq("fail1"), eq("33"));
+
+        // Write
+        final ArrayMap<String, String> values = new ArrayMap<>();
+        values.put("file1", "11");
+        values.put("file2", "22");
+        values.put("fail1", "33");
+
+        mInstance.writeFiles(values);
+        waitUntilMainHandlerDrain();
+
+        verify(mInjector, times(1)).injectWriteToFile("file1", "11");
+        verify(mInjector, times(1)).injectWriteToFile("file2", "22");
+
+        // Should succeed after 2 retries.
+        verify(mInjector, times(3)).injectWriteToFile("fail1", "33");
+
+        // No WTF.
+        veriryWtf(0);
+
+        // Reset to default
+        mInstance.restoreDefault();
+        waitUntilMainHandlerDrain();
+
+        verify(mInjector, times(1)).injectWriteToFile("file1", "111");
+        verify(mInjector, times(1)).injectWriteToFile("file2", "222");
+        verify(mInjector, times(1)).injectWriteToFile("fail1", "333");
+
+        // Still no WTF.
+        veriryWtf(0);
+    }
+
+    @Test
+    public void testAll() throws Exception {
+        // Run multiple tests on the single target instance.
+
+        reset(mInjector);
+        testSimpleWrite();
+
+        reset(mInjector);
+        testWriteGiveUp();
+
+        reset(mInjector);
+        testMultiWrites();
+
+        reset(mInjector);
+        testSuccessWithRetry();
+
+        reset(mInjector);
+        testMultiWrites();
+    }
+}
diff --git a/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobActivity.java b/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobActivity.java
index 011817e..884ba70 100644
--- a/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobActivity.java
+++ b/services/tests/servicestests/test-apps/JobTestApp/src/com/android/servicestests/apps/jobtestapp/TestJobActivity.java
@@ -27,12 +27,11 @@
 
 public class TestJobActivity extends Activity {
     private static final String TAG = TestJobActivity.class.getSimpleName();
-    public static final String EXTRA_JOB_ID_KEY =
-            "com.android.servicestests.apps.jobtestapp.extra.JOB_ID";
-    public static final String ACTION_START_JOB =
-            "com.android.servicestests.apps.jobtestapp.action.START_JOB";
-    public static final String ACTION_CANCEL_JOBS =
-            "com.android.servicestests.apps.jobtestapp.action.CANCEL_JOBS";
+    private static final String PACKAGE_NAME = "com.android.servicestests.apps.jobtestapp";
+
+    public static final String EXTRA_JOB_ID_KEY = PACKAGE_NAME + ".extra.JOB_ID";
+    public static final String ACTION_START_JOB = PACKAGE_NAME + ".action.START_JOB";
+    public static final String ACTION_CANCEL_JOBS = PACKAGE_NAME + ".action.CANCEL_JOBS";
     public static final int JOB_INITIAL_BACKOFF = 10_000;
     public static final int JOB_MINIMUM_LATENCY = 5_000;
 
@@ -59,6 +58,8 @@
                     Log.d(TAG, "Successfully scheduled job with id " + jobId);
                 }
                 break;
+            default:
+                Log.e(TAG, "Unknown action " + intent.getAction());
         }
         finish();
     }
diff --git a/services/usb/java/com/android/server/usb/UsbHostManager.java b/services/usb/java/com/android/server/usb/UsbHostManager.java
index 095fdc6..9bc9cd0 100644
--- a/services/usb/java/com/android/server/usb/UsbHostManager.java
+++ b/services/usb/java/com/android/server/usb/UsbHostManager.java
@@ -323,7 +323,8 @@
     }
 
     /* Opens the specified USB device */
-    public ParcelFileDescriptor openDevice(String deviceName, UsbUserSettingsManager settings) {
+    public ParcelFileDescriptor openDevice(String deviceName, UsbUserSettingsManager settings,
+            String packageName, int uid) {
         synchronized (mLock) {
             if (isBlackListed(deviceName)) {
                 throw new SecurityException("USB device is on a restricted bus");
@@ -334,7 +335,7 @@
                 throw new IllegalArgumentException(
                         "device " + deviceName + " does not exist or is restricted");
             }
-            settings.checkPermission(device);
+            settings.checkPermission(device, packageName, uid);
             return nativeOpenDevice(deviceName);
         }
     }
diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java
index e4fcea7..17de83f 100644
--- a/services/usb/java/com/android/server/usb/UsbService.java
+++ b/services/usb/java/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/services/usb/java/com/android/server/usb/UsbUserSettingsManager.java b/services/usb/java/com/android/server/usb/UsbUserSettingsManager.java
index 96c5211..11e43e3 100644
--- a/services/usb/java/com/android/server/usb/UsbUserSettingsManager.java
+++ b/services/usb/java/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/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 4ffb3c3..e561e01 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -53,8 +53,9 @@
 import android.telephony.ims.feature.ImsFeature;
 import android.util.Log;
 
-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.telecom.ITelecomService;
 import com.android.internal.telephony.CellNetworkScanResult;
@@ -4659,27 +4660,78 @@
     public @interface Feature {}
 
     /**
-     * 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 slotIndex The SIM slot that we are requesting the {@link IImsServiceController} for.
-     * @param feature The IMS Feature we are requesting, corresponding to {@link ImsFeature}.
+     * Returns the {@link IImsMMTelFeature} that corresponds to the given slot Id and MMTel
+     * feature or {@link null} if the service is not available. If an MMTelFeature is available, the
+     * {@link IImsServiceFeatureCallback} callback is registered as a listener for feature updates.
+     * @param slotIndex 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
+     * @return {@link IImsMMTelFeature} interface for the feature specified or {@code null} if
      * it is unavailable.
      * @hide
      */
-    public IImsServiceController getImsServiceControllerAndListen(int slotIndex, @Feature int feature,
-            IImsServiceFeatureListener callback) {
+    public @Nullable IImsMMTelFeature getImsMMTelFeatureAndListen(int slotIndex,
+            IImsServiceFeatureCallback callback) {
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null) {
-                return telephony.getImsServiceControllerAndListen(slotIndex, feature, callback);
+                return telephony.getMMTelFeatureAndListen(slotIndex, callback);
             }
         } catch (RemoteException e) {
-            Rlog.e(TAG, "getImsServiceControllerAndListen, RemoteException: " + e.getMessage());
+            Rlog.e(TAG, "getImsMMTelFeatureAndListen, RemoteException: "
+                    + e.getMessage());
+        }
+        return null;
+    }
+
+    /**
+     * Returns the {@link IImsMMTelFeature} that corresponds to the given slot Id and MMTel
+     * feature for emergency calling or {@link null} if the service is not available. If an
+     * MMTelFeature is available, the {@link IImsServiceFeatureCallback} callback is registered as a
+     * listener for feature updates.
+     * @param slotIndex 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 IImsMMTelFeature} interface for the feature specified or {@code null} if
+     * it is unavailable.
+     * @hide
+     */
+    public @Nullable IImsMMTelFeature getImsEmergencyMMTelFeatureAndListen(int slotIndex,
+            IImsServiceFeatureCallback callback) {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                return telephony.getEmergencyMMTelFeatureAndListen(slotIndex, callback);
+            }
+        } catch (RemoteException e) {
+            Rlog.e(TAG, "getImsEmergencyMMTelFeatureAndListen, RemoteException: "
+                    + e.getMessage());
+        }
+        return null;
+    }
+
+    /**
+     * Returns the {@link IImsRcsFeature} that corresponds to the given slot Id and RCS
+     * feature for emergency calling or {@link null} if the service is not available. If an
+     * RcsFeature is available, the {@link IImsServiceFeatureCallback} callback is registered as a
+     * listener for feature updates.
+     * @param slotIndex The SIM slot that we are requesting the {@link IImsRcsFeature} for.
+     * @param callback Listener that will send updates to ImsManager when there are updates to
+     * ImsServiceController.
+     * @return {@link IImsRcsFeature} interface for the feature specified or {@code null} if
+     * it is unavailable.
+     * @hide
+     */
+    public @Nullable IImsRcsFeature getImsRcsFeatureAndListen(int slotIndex,
+            IImsServiceFeatureCallback callback) {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                return telephony.getRcsFeatureAndListen(slotIndex, callback);
+            }
+        } catch (RemoteException e) {
+            Rlog.e(TAG, "getImsRcsFeatureAndListen, RemoteException: "
+                    + e.getMessage());
         }
         return null;
     }
diff --git a/telephony/java/android/telephony/ims/ImsService.java b/telephony/java/android/telephony/ims/ImsService.java
index 9d91cc3..8230eaf 100644
--- a/telephony/java/android/telephony/ims/ImsService.java
+++ b/telephony/java/android/telephony/ims/ImsService.java
@@ -16,13 +16,11 @@
 
 package android.telephony.ims;
 
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
-import android.app.PendingIntent;
 import android.app.Service;
 import android.content.Intent;
-import android.content.pm.PackageManager;
 import android.os.IBinder;
-import android.os.Message;
 import android.os.RemoteException;
 import android.telephony.CarrierConfigManager;
 import android.telephony.ims.feature.ImsFeature;
@@ -31,22 +29,13 @@
 import android.util.Log;
 import android.util.SparseArray;
 
-import com.android.ims.ImsCallProfile;
-import com.android.ims.internal.IImsCallSession;
-import com.android.ims.internal.IImsCallSessionListener;
-import com.android.ims.internal.IImsConfig;
-import com.android.ims.internal.IImsEcbm;
 import com.android.ims.internal.IImsFeatureStatusCallback;
-import com.android.ims.internal.IImsMultiEndpoint;
-import com.android.ims.internal.IImsRegistrationListener;
+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.IImsUt;
 import com.android.internal.annotations.VisibleForTesting;
 
 import static android.Manifest.permission.MODIFY_PHONE_STATE;
-import static android.Manifest.permission.READ_PHONE_STATE;
-import static android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE;
 
 /**
  * Main ImsService implementation, which binds via the Telephony ImsResolver. Services that extend
@@ -92,247 +81,38 @@
      */
     public static final String SERVICE_INTERFACE = "android.telephony.ims.ImsService";
 
-    // A map of slot Id -> Set of features corresponding to that slot.
-    private final SparseArray<SparseArray<ImsFeature>> mFeatures = new SparseArray<>();
+    // A map of slot Id -> map of features (indexed by ImsFeature feature id) corresponding to that
+    // slot.
+    // We keep track of this to facilitate cleanup of the IImsFeatureStatusCallback and
+    // call ImsFeature#onFeatureRemoved.
+    private final SparseArray<SparseArray<ImsFeature>> mFeaturesBySlot = new SparseArray<>();
 
     /**
      * @hide
      */
-    // Implements all supported features as a flat interface.
     protected final IBinder mImsServiceController = new IImsServiceController.Stub() {
 
         @Override
-        public void createImsFeature(int slotId, int feature, IImsFeatureStatusCallback c)
+        public IImsMMTelFeature createEmergencyMMTelFeature(int slotId,
+                IImsFeatureStatusCallback c) {
+            return createEmergencyMMTelFeatureInternal(slotId, c);
+        }
+
+        @Override
+        public IImsMMTelFeature createMMTelFeature(int slotId, IImsFeatureStatusCallback c) {
+            return createMMTelFeatureInternal(slotId, c);
+        }
+
+        @Override
+        public IImsRcsFeature createRcsFeature(int slotId, IImsFeatureStatusCallback c) {
+            return createRcsFeatureInternal(slotId, c);
+        }
+
+        @Override
+        public void removeImsFeature(int slotId, int featureType, IImsFeatureStatusCallback c)
                 throws RemoteException {
-            synchronized (mFeatures) {
-                enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "createImsFeature");
-                onCreateImsFeatureInternal(slotId, feature, c);
-            }
+            ImsService.this.removeImsFeature(slotId, featureType, c);
         }
-
-        @Override
-        public void removeImsFeature(int slotId, int feature,  IImsFeatureStatusCallback c)
-                throws RemoteException {
-            synchronized (mFeatures) {
-                enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "removeImsFeature");
-                onRemoveImsFeatureInternal(slotId, feature, c);
-            }
-        }
-
-        @Override
-        public int startSession(int slotId, int featureType, PendingIntent incomingCallIntent,
-                IImsRegistrationListener listener) throws RemoteException {
-            enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "startSession");
-            synchronized (mFeatures) {
-                MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
-                if (feature != null) {
-                    return feature.startSession(incomingCallIntent, listener);
-                }
-            }
-            return 0;
-        }
-
-        @Override
-        public void endSession(int slotId, int featureType, int sessionId) throws RemoteException {
-            synchronized (mFeatures) {
-                enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "endSession");
-                MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
-                if (feature != null) {
-                    feature.endSession(sessionId);
-                }
-            }
-        }
-
-        @Override
-        public boolean isConnected(int slotId, int featureType, int callSessionType, int callType)
-                throws RemoteException {
-            enforceReadPhoneStatePermission("isConnected");
-            synchronized (mFeatures) {
-                MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
-                if (feature != null) {
-                    return feature.isConnected(callSessionType, callType);
-                }
-            }
-            return false;
-        }
-
-        @Override
-        public boolean isOpened(int slotId, int featureType) throws RemoteException {
-            enforceReadPhoneStatePermission("isOpened");
-            synchronized (mFeatures) {
-                MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
-                if (feature != null) {
-                    return feature.isOpened();
-                }
-            }
-            return false;
-        }
-
-        @Override
-        public int getFeatureStatus(int slotId, int featureType) throws RemoteException {
-            enforceReadPhoneStatePermission("getFeatureStatus");
-            int status = ImsFeature.STATE_NOT_AVAILABLE;
-            synchronized (mFeatures) {
-                SparseArray<ImsFeature> featureMap = mFeatures.get(slotId);
-                if (featureMap != null) {
-                    ImsFeature feature = getImsFeatureFromType(featureMap, featureType);
-                    if (feature != null) {
-                        status = feature.getFeatureState();
-                    }
-                }
-            }
-            return status;
-        }
-
-        @Override
-        public void addRegistrationListener(int slotId, int featureType,
-                IImsRegistrationListener listener) throws RemoteException {
-            enforceReadPhoneStatePermission("addRegistrationListener");
-            synchronized (mFeatures) {
-                MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
-                if (feature != null) {
-                    feature.addRegistrationListener(listener);
-                }
-            }
-        }
-
-        @Override
-        public void removeRegistrationListener(int slotId, int featureType,
-                IImsRegistrationListener listener) throws RemoteException {
-            enforceReadPhoneStatePermission("removeRegistrationListener");
-            synchronized (mFeatures) {
-                MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
-                if (feature != null) {
-                    feature.removeRegistrationListener(listener);
-                }
-            }
-        }
-
-        @Override
-        public ImsCallProfile createCallProfile(int slotId, int featureType, int sessionId,
-                int callSessionType, int callType) throws RemoteException {
-            enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "createCallProfile");
-            synchronized (mFeatures) {
-                MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
-                if (feature != null) {
-                    return feature.createCallProfile(sessionId, callSessionType,  callType);
-                }
-            }
-            return null;
-        }
-
-        @Override
-        public IImsCallSession createCallSession(int slotId, int featureType, int sessionId,
-                ImsCallProfile profile, IImsCallSessionListener listener) throws RemoteException {
-            enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "createCallSession");
-            synchronized (mFeatures) {
-                MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
-                if (feature != null) {
-                    return feature.createCallSession(sessionId, profile, listener);
-                }
-            }
-            return null;
-        }
-
-        @Override
-        public IImsCallSession getPendingCallSession(int slotId, int featureType, int sessionId,
-                String callId) throws RemoteException {
-            enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "getPendingCallSession");
-            synchronized (mFeatures) {
-                MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
-                if (feature != null) {
-                    return feature.getPendingCallSession(sessionId, callId);
-                }
-            }
-            return null;
-        }
-
-        @Override
-        public IImsUt getUtInterface(int slotId, int featureType)
-                throws RemoteException {
-            enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "getUtInterface");
-            synchronized (mFeatures) {
-                MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
-                if (feature != null) {
-                    return feature.getUtInterface();
-                }
-            }
-            return null;
-        }
-
-        @Override
-        public IImsConfig getConfigInterface(int slotId, int featureType)
-                throws RemoteException {
-            enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "getConfigInterface");
-            synchronized (mFeatures) {
-                MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
-                if (feature != null) {
-                    return feature.getConfigInterface();
-                }
-            }
-            return null;
-        }
-
-        @Override
-        public void turnOnIms(int slotId, int featureType) throws RemoteException {
-            enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "turnOnIms");
-            synchronized (mFeatures) {
-                MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
-                if (feature != null) {
-                    feature.turnOnIms();
-                }
-            }
-        }
-
-        @Override
-        public void turnOffIms(int slotId, int featureType) throws RemoteException {
-            enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "turnOffIms");
-            synchronized (mFeatures) {
-                MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
-                if (feature != null) {
-                    feature.turnOffIms();
-                }
-            }
-        }
-
-        @Override
-        public IImsEcbm getEcbmInterface(int slotId, int featureType)
-                throws RemoteException {
-            enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "getEcbmInterface");
-            synchronized (mFeatures) {
-                MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
-                if (feature != null) {
-                    return feature.getEcbmInterface();
-                }
-            }
-            return null;
-        }
-
-        @Override
-        public void setUiTTYMode(int slotId, int featureType, int uiTtyMode, Message onComplete)
-                throws RemoteException {
-            enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "setUiTTYMode");
-            synchronized (mFeatures) {
-                MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
-                if (feature != null) {
-                    feature.setUiTTYMode(uiTtyMode, onComplete);
-                }
-            }
-        }
-
-        @Override
-        public IImsMultiEndpoint getMultiEndpointInterface(int slotId, int featureType)
-                throws RemoteException {
-            enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "getMultiEndpointInterface");
-            synchronized (mFeatures) {
-                MMTelFeature feature = resolveMMTelFeature(slotId, featureType);
-                if (feature != null) {
-                    return feature.getMultiEndpointInterface();
-                }
-            }
-            return null;
-        }
-
     };
 
     /**
@@ -341,127 +121,93 @@
     @Override
     public IBinder onBind(Intent intent) {
         if(SERVICE_INTERFACE.equals(intent.getAction())) {
+            Log.i(LOG_TAG, "ImsService Bound.");
             return mImsServiceController;
         }
         return null;
     }
 
     /**
-     * Called from the ImsResolver to create the requested ImsFeature, as defined by the slot and
-     * featureType
-     * @param slotId An integer representing which SIM slot the ImsFeature is assigned to.
-     * @param featureType An integer representing the type of ImsFeature being created. This is
-     * defined in {@link ImsFeature}.
+     * @hide
      */
-    // Be sure to lock on mFeatures before accessing this method
-    private void onCreateImsFeatureInternal(int slotId, int featureType,
+    @VisibleForTesting
+    public SparseArray<ImsFeature> getFeatures(int slotId) {
+        return mFeaturesBySlot.get(slotId);
+    }
+
+    private IImsMMTelFeature createEmergencyMMTelFeatureInternal(int slotId,
             IImsFeatureStatusCallback c) {
-        SparseArray<ImsFeature> featureMap = mFeatures.get(slotId);
-        if (featureMap == null) {
-            featureMap = new SparseArray<>();
-            mFeatures.put(slotId, featureMap);
-        }
-        ImsFeature f = makeImsFeature(slotId, featureType);
+        MMTelFeature f = onCreateEmergencyMMTelImsFeature(slotId);
         if (f != null) {
-            f.setContext(this);
-            f.setSlotId(slotId);
-            f.addImsFeatureStatusCallback(c);
-            featureMap.put(featureType, f);
-        }
-
-    }
-    /**
-     * Called from the ImsResolver to remove an existing ImsFeature, as defined by the slot and
-     * featureType.
-     * @param slotId An integer representing which SIM slot the ImsFeature is assigned to.
-     * @param featureType An integer representing the type of ImsFeature being removed. This is
-     * defined in {@link ImsFeature}.
-     */
-    // Be sure to lock on mFeatures before accessing this method
-    private void onRemoveImsFeatureInternal(int slotId, int featureType,
-            IImsFeatureStatusCallback c) {
-        SparseArray<ImsFeature> featureMap = mFeatures.get(slotId);
-        if (featureMap == null) {
-            return;
-        }
-
-        ImsFeature featureToRemove = getImsFeatureFromType(featureMap, featureType);
-        if (featureToRemove != null) {
-            featureMap.remove(featureType);
-            featureToRemove.notifyFeatureRemoved(slotId);
-            // Remove reference to Binder
-            featureToRemove.removeImsFeatureStatusCallback(c);
-        }
-    }
-
-    // Be sure to lock on mFeatures before accessing this method
-    private MMTelFeature resolveMMTelFeature(int slotId, int featureType) {
-        SparseArray<ImsFeature> features = getImsFeatureMap(slotId);
-        MMTelFeature feature = null;
-        if (features != null) {
-            feature = resolveImsFeature(features, featureType, MMTelFeature.class);
-        }
-        return feature;
-    }
-
-    // Be sure to lock on mFeatures before accessing this method
-    private <T extends ImsFeature> T resolveImsFeature(SparseArray<ImsFeature> set, int featureType,
-            Class<T> className) {
-        ImsFeature feature = getImsFeatureFromType(set, featureType);
-        if (feature == null) {
+            setupFeature(f, slotId, ImsFeature.EMERGENCY_MMTEL, c);
+            return f.getBinder();
+        } else {
             return null;
         }
-        try {
-            return className.cast(feature);
-        } catch (ClassCastException e)
-        {
-            Log.e(LOG_TAG, "Can not cast ImsFeature! Exception: " + e.getMessage());
+    }
+
+    private IImsMMTelFeature createMMTelFeatureInternal(int slotId,
+            IImsFeatureStatusCallback c) {
+        MMTelFeature f = onCreateMMTelImsFeature(slotId);
+        if (f != null) {
+            setupFeature(f, slotId, ImsFeature.MMTEL, c);
+            return f.getBinder();
+        } else {
+            return null;
         }
-        return null;
     }
 
-    /**
-     * @hide
-     */
-    @VisibleForTesting
-    // Be sure to lock on mFeatures before accessing this method
-    public SparseArray<ImsFeature> getImsFeatureMap(int slotId) {
-        return mFeatures.get(slotId);
-    }
-
-    /**
-     * @hide
-     */
-    @VisibleForTesting
-    // Be sure to lock on mFeatures before accessing this method
-    public ImsFeature getImsFeatureFromType(SparseArray<ImsFeature> set, int featureType) {
-        return set.get(featureType);
-    }
-
-    private ImsFeature makeImsFeature(int slotId, int feature) {
-        switch (feature) {
-            case ImsFeature.EMERGENCY_MMTEL: {
-                return onCreateEmergencyMMTelImsFeature(slotId);
-            }
-            case ImsFeature.MMTEL: {
-                return onCreateMMTelImsFeature(slotId);
-            }
-            case ImsFeature.RCS: {
-                return onCreateRcsFeature(slotId);
-            }
+    private IImsRcsFeature createRcsFeatureInternal(int slotId,
+            IImsFeatureStatusCallback c) {
+        RcsFeature f = onCreateRcsFeature(slotId);
+        if (f != null) {
+            setupFeature(f, slotId, ImsFeature.RCS, c);
+            return f.getBinder();
+        } else {
+            return null;
         }
-        // Tried to create feature that is not defined.
-        return null;
     }
 
-    /**
-     * Check for both READ_PHONE_STATE and READ_PRIVILEGED_PHONE_STATE. READ_PHONE_STATE is a
-     * public permission and READ_PRIVILEGED_PHONE_STATE is only granted to system apps.
-     */
-    private void enforceReadPhoneStatePermission(String fn) {
-        if (checkCallingOrSelfPermission(READ_PRIVILEGED_PHONE_STATE)
-                != PackageManager.PERMISSION_GRANTED) {
-            enforceCallingOrSelfPermission(READ_PHONE_STATE, fn);
+    private void setupFeature(ImsFeature f, int slotId, int featureType,
+            IImsFeatureStatusCallback c) {
+        f.setContext(this);
+        f.setSlotId(slotId);
+        f.addImsFeatureStatusCallback(c);
+        addImsFeature(slotId, featureType, f);
+    }
+
+    private void addImsFeature(int slotId, int featureType, ImsFeature f) {
+        synchronized (mFeaturesBySlot) {
+            // Get SparseArray for Features, by querying slot Id
+            SparseArray<ImsFeature> features = mFeaturesBySlot.get(slotId);
+            if (features == null) {
+                // Populate new SparseArray of features if it doesn't exist for this slot yet.
+                features = new SparseArray<>();
+                mFeaturesBySlot.put(slotId, features);
+            }
+            features.put(featureType, f);
+        }
+    }
+
+    private void removeImsFeature(int slotId, int featureType,
+            IImsFeatureStatusCallback c) {
+        synchronized (mFeaturesBySlot) {
+            // get ImsFeature associated with the slot/feature
+            SparseArray<ImsFeature> features = mFeaturesBySlot.get(slotId);
+            if (features == null) {
+                Log.w(LOG_TAG, "Can not remove ImsFeature. No ImsFeatures exist on slot "
+                        + slotId);
+                return;
+            }
+            ImsFeature f = features.get(featureType);
+            if (f == null) {
+                Log.w(LOG_TAG, "Can not remove ImsFeature. No feature with type "
+                        + featureType + " exists on slot " + slotId);
+                return;
+            }
+            f.removeImsFeatureStatusCallback(c);
+            f.onFeatureRemoved();
+            features.remove(featureType);
         }
     }
 
@@ -470,7 +216,7 @@
      * functionality. Must be able to handle emergency calls at any time as well.
      * @hide
      */
-    public MMTelFeature onCreateEmergencyMMTelImsFeature(int slotId) {
+    public @Nullable MMTelFeature onCreateEmergencyMMTelImsFeature(int slotId) {
         return null;
     }
 
@@ -479,7 +225,7 @@
      * functionality.
      * @hide
      */
-    public MMTelFeature onCreateMMTelImsFeature(int slotId) {
+    public @Nullable MMTelFeature onCreateMMTelImsFeature(int slotId) {
         return null;
     }
 
@@ -487,7 +233,7 @@
      * @return An implementation of RcsFeature that will be used by the system for RCS.
      * @hide
      */
-    public RcsFeature onCreateRcsFeature(int slotId) {
+    public @Nullable RcsFeature onCreateRcsFeature(int slotId) {
         return null;
     }
 }
diff --git a/telephony/java/android/telephony/ims/feature/ImsFeature.java b/telephony/java/android/telephony/ims/feature/ImsFeature.java
index 9d880b7..062858d 100644
--- a/telephony/java/android/telephony/ims/feature/ImsFeature.java
+++ b/telephony/java/android/telephony/ims/feature/ImsFeature.java
@@ -19,6 +19,7 @@
 import android.annotation.IntDef;
 import android.content.Context;
 import android.content.Intent;
+import android.os.IInterface;
 import android.os.RemoteException;
 import android.telephony.SubscriptionManager;
 import android.util.Log;
@@ -91,17 +92,12 @@
     public static final int STATE_INITIALIZING = 1;
     public static final int STATE_READY = 2;
 
-    private List<INotifyFeatureRemoved> mRemovedListeners = new ArrayList<>();
     private final Set<IImsFeatureStatusCallback> mStatusCallbacks = Collections.newSetFromMap(
             new WeakHashMap<IImsFeatureStatusCallback, Boolean>());
     private @ImsState int mState = STATE_NOT_AVAILABLE;
     private int mSlotId = SubscriptionManager.INVALID_SIM_SLOT_INDEX;
     private Context mContext;
 
-    public interface INotifyFeatureRemoved {
-        void onFeatureRemoved(int slotId);
-    }
-
     public void setContext(Context context) {
         mContext = context;
     }
@@ -110,26 +106,6 @@
         mSlotId = slotId;
     }
 
-    public void addFeatureRemovedListener(INotifyFeatureRemoved listener) {
-        synchronized (mRemovedListeners) {
-            mRemovedListeners.add(listener);
-        }
-    }
-
-    public void removeFeatureRemovedListener(INotifyFeatureRemoved listener) {
-        synchronized (mRemovedListeners) {
-            mRemovedListeners.remove(listener);
-        }
-    }
-
-    // Not final for testing.
-    public void notifyFeatureRemoved(int slotId) {
-        synchronized (mRemovedListeners) {
-            mRemovedListeners.forEach(l -> l.onFeatureRemoved(slotId));
-            onFeatureRemoved();
-        }
-    }
-
     public int getFeatureState() {
         return mState;
     }
@@ -215,4 +191,9 @@
      * Called when the feature is being removed and must be cleaned up.
      */
     public abstract void onFeatureRemoved();
+
+    /**
+     * @return Binder instance
+     */
+    public abstract IInterface getBinder();
 }
diff --git a/telephony/java/android/telephony/ims/feature/MMTelFeature.java b/telephony/java/android/telephony/ims/feature/MMTelFeature.java
index 758c379..e790d14 100644
--- a/telephony/java/android/telephony/ims/feature/MMTelFeature.java
+++ b/telephony/java/android/telephony/ims/feature/MMTelFeature.java
@@ -18,18 +18,18 @@
 
 import android.app.PendingIntent;
 import android.os.Message;
+import android.os.RemoteException;
 
 import com.android.ims.ImsCallProfile;
 import com.android.ims.internal.IImsCallSession;
 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.IImsUt;
-
-import java.util.ArrayList;
-import java.util.List;
+import com.android.ims.internal.ImsCallSession;
 
 /**
  * Base implementation for MMTel.
@@ -41,6 +41,146 @@
 
 public class MMTelFeature extends ImsFeature {
 
+    // Lock for feature synchronization
+    private final Object mLock = new Object();
+
+    private final IImsMMTelFeature mImsMMTelBinder = new IImsMMTelFeature.Stub() {
+
+        @Override
+        public int startSession(PendingIntent incomingCallIntent,
+                IImsRegistrationListener listener) throws RemoteException {
+            synchronized (mLock) {
+                return MMTelFeature.this.startSession(incomingCallIntent, listener);
+            }
+        }
+
+        @Override
+        public void endSession(int sessionId) throws RemoteException {
+            synchronized (mLock) {
+                MMTelFeature.this.endSession(sessionId);
+            }
+        }
+
+        @Override
+        public boolean isConnected(int callSessionType, int callType)
+                throws RemoteException {
+            synchronized (mLock) {
+                return MMTelFeature.this.isConnected(callSessionType, callType);
+            }
+        }
+
+        @Override
+        public boolean isOpened() throws RemoteException {
+            synchronized (mLock) {
+                return MMTelFeature.this.isOpened();
+            }
+        }
+
+        @Override
+        public int getFeatureStatus() throws RemoteException {
+            synchronized (mLock) {
+                return MMTelFeature.this.getFeatureState();
+            }
+        }
+
+        @Override
+        public void addRegistrationListener(IImsRegistrationListener listener)
+                throws RemoteException {
+            synchronized (mLock) {
+                MMTelFeature.this.addRegistrationListener(listener);
+            }
+        }
+
+        @Override
+        public void removeRegistrationListener(IImsRegistrationListener listener)
+                throws RemoteException {
+            synchronized (mLock) {
+                MMTelFeature.this.removeRegistrationListener(listener);
+            }
+        }
+
+        @Override
+        public ImsCallProfile createCallProfile(int sessionId, int callSessionType, int callType)
+                throws RemoteException {
+            synchronized (mLock) {
+                return MMTelFeature.this.createCallProfile(sessionId, callSessionType,  callType);
+            }
+        }
+
+        @Override
+        public IImsCallSession createCallSession(int sessionId, ImsCallProfile profile,
+                IImsCallSessionListener listener) throws RemoteException {
+            synchronized (mLock) {
+                return MMTelFeature.this.createCallSession(sessionId, profile, listener);
+            }
+        }
+
+        @Override
+        public IImsCallSession getPendingCallSession(int sessionId, String callId)
+                throws RemoteException {
+            synchronized (mLock) {
+                return MMTelFeature.this.getPendingCallSession(sessionId, callId);
+            }
+        }
+
+        @Override
+        public IImsUt getUtInterface() throws RemoteException {
+            synchronized (mLock) {
+                return MMTelFeature.this.getUtInterface();
+            }
+        }
+
+        @Override
+        public IImsConfig getConfigInterface() throws RemoteException {
+            synchronized (mLock) {
+                return MMTelFeature.this.getConfigInterface();
+            }
+        }
+
+        @Override
+        public void turnOnIms() throws RemoteException {
+            synchronized (mLock) {
+                MMTelFeature.this.turnOnIms();
+            }
+        }
+
+        @Override
+        public void turnOffIms() throws RemoteException {
+            synchronized (mLock) {
+                MMTelFeature.this.turnOffIms();
+            }
+        }
+
+        @Override
+        public IImsEcbm getEcbmInterface() throws RemoteException {
+            synchronized (mLock) {
+                return MMTelFeature.this.getEcbmInterface();
+            }
+        }
+
+        @Override
+        public void setUiTTYMode(int uiTtyMode, Message onComplete) throws RemoteException {
+            synchronized (mLock) {
+                MMTelFeature.this.setUiTTYMode(uiTtyMode, onComplete);
+            }
+        }
+
+        @Override
+        public IImsMultiEndpoint getMultiEndpointInterface() throws RemoteException {
+            synchronized (mLock) {
+                return MMTelFeature.this.getMultiEndpointInterface();
+            }
+        }
+    };
+
+    /**
+     * @hide
+     */
+    @Override
+    public final IImsMMTelFeature getBinder() {
+        return mImsMMTelBinder;
+    }
+
     /**
      * Notifies the MMTel feature that you would like to start a session. This should always be
      * done before making/receiving IMS calls. The IMS service will register the device to the
@@ -135,7 +275,7 @@
     }
 
     /**
-     * Creates a {@link ImsCallSession} with the specified call profile.
+     * Creates an {@link ImsCallSession} with the specified call profile.
      * Use other methods, if applicable, instead of interacting with
      * {@link ImsCallSession} directly.
      *
diff --git a/telephony/java/android/telephony/ims/feature/RcsFeature.java b/telephony/java/android/telephony/ims/feature/RcsFeature.java
index 332cca3..a82e608 100644
--- a/telephony/java/android/telephony/ims/feature/RcsFeature.java
+++ b/telephony/java/android/telephony/ims/feature/RcsFeature.java
@@ -16,6 +16,8 @@
 
 package android.telephony.ims.feature;
 
+import com.android.ims.internal.IImsRcsFeature;
+
 /**
  * Base implementation of the RcsFeature APIs. Any ImsService wishing to support RCS should extend
  * this class and provide implementations of the RcsFeature methods that they support.
@@ -24,6 +26,11 @@
 
 public class RcsFeature extends ImsFeature {
 
+    private final IImsRcsFeature mImsRcsBinder = new IImsRcsFeature.Stub() {
+        // Empty Default Implementation.
+    };
+
+
     public RcsFeature() {
         super();
     }
@@ -32,4 +39,9 @@
     public void onFeatureRemoved() {
 
     }
+
+    @Override
+    public final IImsRcsFeature getBinder() {
+        return mImsRcsBinder;
+    }
 }
diff --git a/telephony/java/com/android/ims/internal/IImsMMTelFeature.aidl b/telephony/java/com/android/ims/internal/IImsMMTelFeature.aidl
new file mode 100644
index 0000000..52b3853
--- /dev/null
+++ b/telephony/java/com/android/ims/internal/IImsMMTelFeature.aidl
@@ -0,0 +1,56 @@
+/*
+ * 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.ims.internal;
+
+import android.app.PendingIntent;
+
+import com.android.ims.ImsCallProfile;
+import com.android.ims.internal.IImsCallSession;
+import com.android.ims.internal.IImsCallSessionListener;
+import com.android.ims.internal.IImsConfig;
+import com.android.ims.internal.IImsEcbm;
+import com.android.ims.internal.IImsMultiEndpoint;
+import com.android.ims.internal.IImsRegistrationListener;
+import com.android.ims.internal.IImsUt;
+
+import android.os.Message;
+
+/**
+ * See MMTelFeature for more information.
+ * {@hide}
+ */
+interface IImsMMTelFeature {
+    int startSession(in PendingIntent incomingCallIntent,
+            in IImsRegistrationListener listener);
+    void endSession(int sessionId);
+    boolean isConnected(int callSessionType, int callType);
+    boolean isOpened();
+    int getFeatureStatus();
+    void addRegistrationListener(in IImsRegistrationListener listener);
+    void removeRegistrationListener(in IImsRegistrationListener listener);
+    ImsCallProfile createCallProfile(int sessionId, int callSessionType, int callType);
+    IImsCallSession createCallSession(int sessionId, in ImsCallProfile profile,
+            IImsCallSessionListener listener);
+    IImsCallSession getPendingCallSession(int sessionId, String callId);
+    IImsUt getUtInterface();
+    IImsConfig getConfigInterface();
+    void turnOnIms();
+    void turnOffIms();
+    IImsEcbm getEcbmInterface();
+    void setUiTTYMode(int uiTtyMode, in Message onComplete);
+    IImsMultiEndpoint getMultiEndpointInterface();
+}
diff --git a/telephony/java/com/android/ims/internal/IImsServiceFeatureListener.aidl b/telephony/java/com/android/ims/internal/IImsRcsFeature.aidl
similarity index 65%
copy from telephony/java/com/android/ims/internal/IImsServiceFeatureListener.aidl
copy to telephony/java/com/android/ims/internal/IImsRcsFeature.aidl
index df10700..b1cb23b 100644
--- a/telephony/java/com/android/ims/internal/IImsServiceFeatureListener.aidl
+++ b/telephony/java/com/android/ims/internal/IImsRcsFeature.aidl
@@ -17,12 +17,9 @@
 package com.android.ims.internal;
 
 /**
- *  Interface from ImsResolver to ImsServiceProxy in ImsManager.
- * Callback to ImsManager when a feature changes in the ImsServiceController.
+ * See RcsFeature for more information.
  * {@hide}
  */
-oneway interface IImsServiceFeatureListener {
-    void imsFeatureCreated(int slotId, int feature);
-    void imsFeatureRemoved(int slotId, int feature);
-    void imsStatusChanged(int slotId, int feature, int status);
+interface IImsRcsFeature {
+    //Empty Default Implementation
 }
\ No newline at end of file
diff --git a/telephony/java/com/android/ims/internal/IImsServiceController.aidl b/telephony/java/com/android/ims/internal/IImsServiceController.aidl
index f1e2262..857089f 100644
--- a/telephony/java/com/android/ims/internal/IImsServiceController.aidl
+++ b/telephony/java/com/android/ims/internal/IImsServiceController.aidl
@@ -16,49 +16,17 @@
 
 package com.android.ims.internal;
 
-import android.app.PendingIntent;
-
-import com.android.ims.ImsCallProfile;
-import com.android.ims.internal.IImsCallSession;
-import com.android.ims.internal.IImsCallSessionListener;
-import com.android.ims.internal.IImsConfig;
-import com.android.ims.internal.IImsEcbm;
 import com.android.ims.internal.IImsFeatureStatusCallback;
-import com.android.ims.internal.IImsMultiEndpoint;
-import com.android.ims.internal.IImsRegistrationListener;
-import com.android.ims.internal.IImsUt;
-
-import android.os.Message;
+import com.android.ims.internal.IImsMMTelFeature;
+import com.android.ims.internal.IImsRcsFeature;
 
 /**
  * See ImsService and MMTelFeature for more information.
  * {@hide}
  */
 interface IImsServiceController {
-    // ImsService Control
-    void createImsFeature(int slotId, int feature, IImsFeatureStatusCallback c);
-    void removeImsFeature(int slotId, int feature, IImsFeatureStatusCallback c);
-    // MMTel Feature
-    int startSession(int slotId, int featureType, in PendingIntent incomingCallIntent,
-            in IImsRegistrationListener listener);
-    void endSession(int slotId, int featureType, int sessionId);
-    boolean isConnected(int slotId, int featureType, int callSessionType, int callType);
-    boolean isOpened(int slotId, int featureType);
-    int getFeatureStatus(int slotId, int featureType);
-    void addRegistrationListener(int slotId, int featureType, in IImsRegistrationListener listener);
-    void removeRegistrationListener(int slotId, int featureType,
-            in IImsRegistrationListener listener);
-    ImsCallProfile createCallProfile(int slotId, int featureType, int sessionId,
-            int callSessionType, int callType);
-    IImsCallSession createCallSession(int slotId, int featureType, int sessionId,
-            in ImsCallProfile profile, IImsCallSessionListener listener);
-    IImsCallSession getPendingCallSession(int slotId, int featureType, int sessionId,
-            String callId);
-    IImsUt getUtInterface(int slotId, int featureType);
-    IImsConfig getConfigInterface(int slotId, int featureType);
-    void turnOnIms(int slotId, int featureType);
-    void turnOffIms(int slotId, int featureType);
-    IImsEcbm getEcbmInterface(int slotId, int featureType);
-    void setUiTTYMode(int slotId, int featureType, int uiTtyMode, in Message onComplete);
-    IImsMultiEndpoint getMultiEndpointInterface(int slotId, int featureType);
+    IImsMMTelFeature createEmergencyMMTelFeature(int slotId, in IImsFeatureStatusCallback c);
+    IImsMMTelFeature createMMTelFeature(int slotId, in IImsFeatureStatusCallback c);
+    IImsRcsFeature createRcsFeature(int slotId, in IImsFeatureStatusCallback c);
+    void removeImsFeature(int slotId, int featureType, in IImsFeatureStatusCallback c);
 }
diff --git a/telephony/java/com/android/ims/internal/IImsServiceFeatureListener.aidl b/telephony/java/com/android/ims/internal/IImsServiceFeatureCallback.aidl
similarity index 95%
rename from telephony/java/com/android/ims/internal/IImsServiceFeatureListener.aidl
rename to telephony/java/com/android/ims/internal/IImsServiceFeatureCallback.aidl
index df10700..9a9cf53 100644
--- a/telephony/java/com/android/ims/internal/IImsServiceFeatureListener.aidl
+++ b/telephony/java/com/android/ims/internal/IImsServiceFeatureCallback.aidl
@@ -21,7 +21,7 @@
  * Callback to ImsManager when a feature changes in the ImsServiceController.
  * {@hide}
  */
-oneway interface IImsServiceFeatureListener {
+oneway interface IImsServiceFeatureCallback {
     void imsFeatureCreated(int slotId, int feature);
     void imsFeatureRemoved(int slotId, int feature);
     void imsStatusChanged(int slotId, int feature, int status);
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 3cc9bde..fd6091a 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -38,8 +38,9 @@
 import android.telephony.SignalStrength;
 import android.telephony.TelephonyHistogram;
 import android.telephony.VisualVoicemailSmsFilterSettings;
-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.telephony.CellNetworkScanResult;
 import com.android.internal.telephony.OperatorInfo;
 
@@ -784,12 +785,27 @@
     int getTetherApnRequired();
 
     /**
-     *  Get ImsServiceController binder from ImsResolver that corresponds to the subId and feature
-     *  requested as well as registering the ImsServiceController for callbacks using the
-     *  IImsServiceFeatureListener interface.
+     *  Get IImsMMTelFeature binder from ImsResolver that corresponds to the subId and MMTel feature
+     *  as well as registering the MMTelFeature for callbacks using the IImsServiceFeatureCallback
+     *  interface.
      */
-    IImsServiceController getImsServiceControllerAndListen(int slotIndex, int feature,
-                IImsServiceFeatureListener callback);
+    IImsMMTelFeature getMMTelFeatureAndListen(int slotId, in IImsServiceFeatureCallback callback);
+
+    /**
+     *  Get IImsMMTelFeature binder from ImsResolver that corresponds to the subId and MMTel feature
+     *  as well as registering the MMTelFeature for callbacks using the IImsServiceFeatureCallback
+     *  interface.
+     *  Used for emergency calling only.
+     */
+    IImsMMTelFeature getEmergencyMMTelFeatureAndListen(int slotId,
+            in IImsServiceFeatureCallback callback);
+
+    /**
+     *  Get IImsRcsFeature binder from ImsResolver that corresponds to the subId and RCS feature
+     *  as well as registering the RcsFeature for callbacks using the IImsServiceFeatureCallback
+     *  interface.
+     */
+    IImsRcsFeature getRcsFeatureAndListen(int slotId, in IImsServiceFeatureCallback callback);
 
     /**
      * Set the network selection mode to automatic.
diff --git a/tools/aapt2/java/ProguardRules.cpp b/tools/aapt2/java/ProguardRules.cpp
index 132b234..ffcef89 100644
--- a/tools/aapt2/java/ProguardRules.cpp
+++ b/tools/aapt2/java/ProguardRules.cpp
@@ -346,22 +346,20 @@
       can_be_conditional &= CollectLocations(location, keep_set, &locations);
     }
 
-    for (const UsageLocation& location : entry.second) {
-      printer.Print("# Referenced at ").Println(location.source.to_string());
-    }
     if (keep_set.conditional_keep_rules_ && can_be_conditional) {
-      printer.Println("-if class **.R$layout {");
-      printer.Indent();
       for (const UsageLocation& location : locations) {
-        printer.Print("int ")
+        printer.Print("# Referenced at ").Println(location.source.to_string());
+        printer.Print("-if class **.R$layout { int ")
             .Print(JavaClassGenerator::TransformToFieldName(location.name.entry))
-            .Println(";");
+            .Println("; }");
+        printer.Print("-keep class ").Print(entry.first).Println(" { <init>(...); }");
       }
-      printer.Undent();
-      printer.Println("}");
-      printer.Println();
+    } else {
+      for (const UsageLocation& location : entry.second) {
+        printer.Print("# Referenced at ").Println(location.source.to_string());
+      }
+      printer.Print("-keep class ").Print(entry.first).Println(" { <init>(...); }");
     }
-    printer.Print("-keep class ").Print(entry.first).Println(" { <init>(...); }");
     printer.Println();
   }