Merge changes Ie1eb63dc,Ib7cc4d52

* changes:
  Renames setCallbackOnComplete method in ContextHubTransaction
  Rename ContextHubTransaction.Result IntDef
diff --git a/api/current.txt b/api/current.txt
index c8a85d4..6bb7584 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -5200,6 +5200,7 @@
     field public static final java.lang.String EXTRA_CONVERSATION_TITLE = "android.conversationTitle";
     field public static final java.lang.String EXTRA_HISTORIC_MESSAGES = "android.messages.historic";
     field public static final java.lang.String EXTRA_INFO_TEXT = "android.infoText";
+    field public static final java.lang.String EXTRA_IS_GROUP_CONVERSATION = "android.isGroupConversation";
     field public static final deprecated java.lang.String EXTRA_LARGE_ICON = "android.largeIcon";
     field public static final java.lang.String EXTRA_LARGE_ICON_BIG = "android.largeIcon.big";
     field public static final java.lang.String EXTRA_MEDIA_SESSION = "android.mediaSession";
@@ -5483,7 +5484,9 @@
     method public java.util.List<android.app.Notification.MessagingStyle.Message> getHistoricMessages();
     method public java.util.List<android.app.Notification.MessagingStyle.Message> getMessages();
     method public java.lang.CharSequence getUserDisplayName();
+    method public boolean isGroupConversation();
     method public android.app.Notification.MessagingStyle setConversationTitle(java.lang.CharSequence);
+    method public android.app.Notification.MessagingStyle setGroupConversation(boolean);
     field public static final int MAXIMUM_RETAINED_MESSAGES = 25; // 0x19
   }
 
@@ -7032,10 +7035,12 @@
     field public static final java.lang.String HINT_NO_TINT = "no_tint";
     field public static final java.lang.String HINT_PARTIAL = "partial";
     field public static final java.lang.String HINT_SELECTED = "selected";
+    field public static final java.lang.String HINT_SHORTCUT = "shortcut";
     field public static final java.lang.String HINT_SUMMARY = "summary";
     field public static final java.lang.String HINT_TITLE = "title";
     field public static final java.lang.String SUBTYPE_COLOR = "color";
     field public static final java.lang.String SUBTYPE_MESSAGE = "message";
+    field public static final java.lang.String SUBTYPE_PRIORITY = "priority";
     field public static final java.lang.String SUBTYPE_SLIDER = "slider";
     field public static final java.lang.String SUBTYPE_SOURCE = "source";
     field public static final java.lang.String SUBTYPE_TOGGLE = "toggle";
@@ -8223,7 +8228,7 @@
     method public void onAppStatusChanged(android.bluetooth.BluetoothDevice, boolean);
     method public void onConnectionStateChanged(android.bluetooth.BluetoothDevice, int);
     method public void onGetReport(android.bluetooth.BluetoothDevice, byte, byte, int);
-    method public void onIntrData(android.bluetooth.BluetoothDevice, byte, byte[]);
+    method public void onInterruptData(android.bluetooth.BluetoothDevice, byte, byte[]);
     method public void onSetProtocol(android.bluetooth.BluetoothDevice, byte);
     method public void onSetReport(android.bluetooth.BluetoothDevice, byte, byte, byte[]);
     method public void onVirtualCableUnplug(android.bluetooth.BluetoothDevice);
@@ -32259,6 +32264,7 @@
     method public deprecated void setUserRestrictions(android.os.Bundle);
     method public deprecated void setUserRestrictions(android.os.Bundle, android.os.UserHandle);
     method public static boolean supportsMultipleUsers();
+    method public boolean trySetQuietModeEnabled(boolean, android.os.UserHandle);
     field public static final java.lang.String ALLOW_PARENT_PROFILE_APP_LINKING = "allow_parent_profile_app_linking";
     field public static final java.lang.String DISALLOW_ADD_MANAGED_PROFILE = "no_add_managed_profile";
     field public static final java.lang.String DISALLOW_ADD_USER = "no_add_user";
@@ -44718,6 +44724,14 @@
     field public static final android.os.Parcelable.Creator<android.view.Display.Mode> CREATOR;
   }
 
+  public final class DisplayCutout {
+    method public android.graphics.Region getBounds();
+    method public int getSafeInsetBottom();
+    method public int getSafeInsetLeft();
+    method public int getSafeInsetRight();
+    method public int getSafeInsetTop();
+  }
+
   public final class DragAndDropPermissions implements android.os.Parcelable {
     method public int describeContents();
     method public void release();
@@ -47671,8 +47685,10 @@
 
   public final class WindowInsets {
     ctor public WindowInsets(android.view.WindowInsets);
+    method public android.view.WindowInsets consumeDisplayCutout();
     method public android.view.WindowInsets consumeStableInsets();
     method public android.view.WindowInsets consumeSystemWindowInsets();
+    method public android.view.DisplayCutout getDisplayCutout();
     method public int getStableInsetBottom();
     method public int getStableInsetLeft();
     method public int getStableInsetRight();
@@ -47732,6 +47748,7 @@
     field public static final int FIRST_APPLICATION_WINDOW = 1; // 0x1
     field public static final int FIRST_SUB_WINDOW = 1000; // 0x3e8
     field public static final int FIRST_SYSTEM_WINDOW = 2000; // 0x7d0
+    field public static final long FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA = 1L; // 0x1L
     field public static final int FLAGS_CHANGED = 4; // 0x4
     field public static final int FLAG_ALLOW_LOCK_WHILE_SCREEN_ON = 1; // 0x1
     field public static final int FLAG_ALT_FOCUSABLE_IM = 131072; // 0x20000
@@ -47826,6 +47843,7 @@
     field public float buttonBrightness;
     field public float dimAmount;
     field public int flags;
+    field public long flags2;
     field public int format;
     field public int gravity;
     field public float horizontalMargin;
@@ -48618,6 +48636,7 @@
     method public void cancel();
     method public void commit();
     method public void disableAutofillServices();
+    method public android.content.ComponentName getAutofillServiceComponentName();
     method public android.service.autofill.UserData getUserData();
     method public boolean hasEnabledAutofillServices();
     method public boolean isAutofillSupported();
diff --git a/api/removed.txt b/api/removed.txt
index be4d5be..1a4e796 100644
--- a/api/removed.txt
+++ b/api/removed.txt
@@ -506,14 +506,6 @@
 
 }
 
-package android.view.accessibility {
-
-  public final class AccessibilityWindowInfo implements android.os.Parcelable {
-    method public boolean inPictureInPicture();
-  }
-
-}
-
 package android.webkit {
 
   public class WebViewClient {
diff --git a/api/system-current.txt b/api/system-current.txt
index 30530a3..ec509ad 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -103,6 +103,7 @@
     field public static final deprecated java.lang.String MODIFY_NETWORK_ACCOUNTING = "android.permission.MODIFY_NETWORK_ACCOUNTING";
     field public static final java.lang.String MODIFY_PARENTAL_CONTROLS = "android.permission.MODIFY_PARENTAL_CONTROLS";
     field public static final java.lang.String MODIFY_PHONE_STATE = "android.permission.MODIFY_PHONE_STATE";
+    field public static final java.lang.String MODIFY_QUIET_MODE = "android.permission.MODIFY_QUIET_MODE";
     field public static final java.lang.String MOUNT_FORMAT_FILESYSTEMS = "android.permission.MOUNT_FORMAT_FILESYSTEMS";
     field public static final java.lang.String MOUNT_UNMOUNT_FILESYSTEMS = "android.permission.MOUNT_UNMOUNT_FILESYSTEMS";
     field public static final java.lang.String MOVE_PACKAGE = "android.permission.MOVE_PACKAGE";
diff --git a/cmds/statsd/src/packages/UidMap.cpp b/cmds/statsd/src/packages/UidMap.cpp
index 6e7a613..3018be1 100644
--- a/cmds/statsd/src/packages/UidMap.cpp
+++ b/cmds/statsd/src/packages/UidMap.cpp
@@ -31,10 +31,9 @@
 namespace os {
 namespace statsd {
 
-UidMap::UidMap() : mBytesUsed(0) {
-}
-UidMap::~UidMap() {
-}
+UidMap::UidMap() : mBytesUsed(0) {}
+
+UidMap::~UidMap() {}
 
 bool UidMap::hasApp(int uid, const string& packageName) const {
     lock_guard<mutex> lock(mMutex);
@@ -48,6 +47,27 @@
     return false;
 }
 
+string UidMap::normalizeAppName(const string& appName) const {
+    string normalizedName = appName;
+    std::transform(normalizedName.begin(), normalizedName.end(), normalizedName.begin(), ::tolower);
+    return normalizedName;
+}
+
+std::set<string> UidMap::getAppNamesFromUid(const int32_t& uid, bool returnNormalized) const {
+    lock_guard<mutex> lock(mMutex);
+    return getAppNamesFromUidLocked(uid,returnNormalized);
+}
+
+std::set<string> UidMap::getAppNamesFromUidLocked(const int32_t& uid, bool returnNormalized) const {
+    std::set<string> names;
+    auto range = mMap.equal_range(uid);
+    for (auto it = range.first; it != range.second; ++it) {
+        names.insert(returnNormalized ?
+            normalizeAppName(it->second.packageName) : it->second.packageName);
+    }
+    return names;
+}
+
 int64_t UidMap::getAppVersion(int uid, const string& packageName) const {
     lock_guard<mutex> lock(mMutex);
 
@@ -97,17 +117,17 @@
                        const int64_t& versionCode) {
     lock_guard<mutex> lock(mMutex);
 
-    string app = string(String8(app_16).string());
+    string appName = string(String8(app_16).string());
 
     // Notify any interested producers that this app has updated
     for (auto it : mSubscribers) {
-        it->notifyAppUpgrade(app, uid, versionCode);
+        it->notifyAppUpgrade(appName, uid, versionCode);
     }
 
     auto log = mOutput.add_changes();
     log->set_deletion(false);
     log->set_timestamp_nanos(timestamp);
-    log->set_app(app);
+    log->set_app(appName);
     log->set_uid(uid);
     log->set_version(versionCode);
     mBytesUsed += log->ByteSize();
@@ -117,16 +137,15 @@
 
     auto range = mMap.equal_range(int(uid));
     for (auto it = range.first; it != range.second; ++it) {
-        if (it->second.packageName == app) {
+        // If we find the exact same app name and uid, update the app version directly.
+        if (it->second.packageName == appName) {
             it->second.versionCode = versionCode;
             return;
         }
-        VLOG("updateApp failed to find the app %s with uid %i to update", app.c_str(), uid);
-        return;
     }
 
     // Otherwise, we need to add an app at this uid.
-    mMap.insert(make_pair(uid, AppData(app, versionCode)));
+    mMap.insert(make_pair(uid, AppData(appName, versionCode)));
 }
 
 void UidMap::ensureBytesUsedBelowLimit() {
@@ -154,6 +173,7 @@
 void UidMap::removeApp(const String16& app_16, const int32_t& uid) {
     removeApp(time(nullptr) * NS_PER_SEC, app_16, uid);
 }
+
 void UidMap::removeApp(const int64_t& timestamp, const String16& app_16, const int32_t& uid) {
     lock_guard<mutex> lock(mMutex);
 
diff --git a/cmds/statsd/src/packages/UidMap.h b/cmds/statsd/src/packages/UidMap.h
index 9e1ad69..487fdf9 100644
--- a/cmds/statsd/src/packages/UidMap.h
+++ b/cmds/statsd/src/packages/UidMap.h
@@ -14,8 +14,7 @@
  * limitations under the License.
  */
 
-#ifndef STATSD_UIDMAP_H
-#define STATSD_UIDMAP_H
+#pragma once
 
 #include "config/ConfigKey.h"
 #include "config/ConfigListener.h"
@@ -66,6 +65,9 @@
     // Returns true if the given uid contains the specified app (eg. com.google.android.gms).
     bool hasApp(int uid, const string& packageName) const;
 
+    // Returns the app names from uid.
+    std::set<string> getAppNamesFromUid(const int32_t& uid, bool returnNormalized) const;
+
     int64_t getAppVersion(int uid, const string& packageName) const;
 
     // Helper for debugging contents of this uid map. Can be triggered with:
@@ -103,6 +105,9 @@
     size_t getBytesUsed();
 
 private:
+    std::set<string> getAppNamesFromUidLocked(const int32_t& uid, bool returnNormalized) const;
+    string normalizeAppName(const string& appName) const;
+
     void updateMap(const int64_t& timestamp, const vector<int32_t>& uid,
                    const vector<int64_t>& versionCode, const vector<String16>& packageName);
 
@@ -160,4 +165,3 @@
 }  // namespace os
 }  // namespace android
 
-#endif  // STATSD_UIDMAP_H
diff --git a/cmds/statsd/tests/UidMap_test.cpp b/cmds/statsd/tests/UidMap_test.cpp
index 5b2cedd..3fa96d3 100644
--- a/cmds/statsd/tests/UidMap_test.cpp
+++ b/cmds/statsd/tests/UidMap_test.cpp
@@ -74,6 +74,14 @@
     EXPECT_TRUE(m.hasApp(1000, kApp1));
     EXPECT_TRUE(m.hasApp(1000, kApp2));
     EXPECT_FALSE(m.hasApp(1000, "not.app"));
+
+    std::set<string> name_set = m.getAppNamesFromUid(1000u, true /* returnNormalized */);
+    EXPECT_EQ(name_set.size(), 2u);
+    EXPECT_TRUE(name_set.find(kApp1) != name_set.end());
+    EXPECT_TRUE(name_set.find(kApp2) != name_set.end());
+
+    name_set = m.getAppNamesFromUid(12345, true /* returnNormalized */);
+    EXPECT_TRUE(name_set.empty());
 }
 
 TEST(UidMapTest, TestAddAndRemove) {
@@ -90,12 +98,59 @@
     versions.push_back(5);
     m.updateMap(uids, versions, apps);
 
+    std::set<string> name_set = m.getAppNamesFromUid(1000, true /* returnNormalized */);
+    EXPECT_EQ(name_set.size(), 2u);
+    EXPECT_TRUE(name_set.find(kApp1) != name_set.end());
+    EXPECT_TRUE(name_set.find(kApp2) != name_set.end());
+
+    // Update the app1 version.
     m.updateApp(String16(kApp1.c_str()), 1000, 40);
     EXPECT_EQ(40, m.getAppVersion(1000, kApp1));
 
+    name_set = m.getAppNamesFromUid(1000, true /* returnNormalized */);
+    EXPECT_EQ(name_set.size(), 2u);
+    EXPECT_TRUE(name_set.find(kApp1) != name_set.end());
+    EXPECT_TRUE(name_set.find(kApp2) != name_set.end());
+
     m.removeApp(String16(kApp1.c_str()), 1000);
     EXPECT_FALSE(m.hasApp(1000, kApp1));
     EXPECT_TRUE(m.hasApp(1000, kApp2));
+    name_set = m.getAppNamesFromUid(1000, true /* returnNormalized */);
+    EXPECT_EQ(name_set.size(), 1u);
+    EXPECT_TRUE(name_set.find(kApp1) == name_set.end());
+    EXPECT_TRUE(name_set.find(kApp2) != name_set.end());
+
+    // Remove app2.
+    m.removeApp(String16(kApp2.c_str()), 1000);
+    EXPECT_FALSE(m.hasApp(1000, kApp1));
+    EXPECT_FALSE(m.hasApp(1000, kApp2));
+    name_set = m.getAppNamesFromUid(1000, true /* returnNormalized */);
+    EXPECT_TRUE(name_set.empty());
+}
+
+TEST(UidMapTest, TestUpdateApp) {
+    UidMap m;
+    m.updateMap({1000, 1000}, {4, 5}, {String16(kApp1.c_str()), String16(kApp2.c_str())});
+    std::set<string> name_set = m.getAppNamesFromUid(1000, true /* returnNormalized */);
+    EXPECT_EQ(name_set.size(), 2u);
+    EXPECT_TRUE(name_set.find(kApp1) != name_set.end());
+    EXPECT_TRUE(name_set.find(kApp2) != name_set.end());
+
+    // Adds a new name for uid 1000.
+    m.updateApp(String16("NeW_aPP1_NAmE"), 1000, 40);
+    name_set = m.getAppNamesFromUid(1000, true /* returnNormalized */);
+    EXPECT_EQ(name_set.size(), 3u);
+    EXPECT_TRUE(name_set.find(kApp1) != name_set.end());
+    EXPECT_TRUE(name_set.find(kApp2) != name_set.end());
+    EXPECT_TRUE(name_set.find("NeW_aPP1_NAmE") == name_set.end());
+    EXPECT_TRUE(name_set.find("new_app1_name") != name_set.end());
+
+    // This name is also reused by another uid 2000.
+    m.updateApp(String16("NeW_aPP1_NAmE"), 2000, 1);
+    name_set = m.getAppNamesFromUid(2000, true /* returnNormalized */);
+    EXPECT_EQ(name_set.size(), 1u);
+    EXPECT_TRUE(name_set.find("NeW_aPP1_NAmE") == name_set.end());
+    EXPECT_TRUE(name_set.find("new_app1_name") != name_set.end());
 }
 
 TEST(UidMapTest, TestClearingOutput) {
diff --git a/core/java/android/app/DialogFragment.java b/core/java/android/app/DialogFragment.java
index a0fb6ee..3a355d9 100644
--- a/core/java/android/app/DialogFragment.java
+++ b/core/java/android/app/DialogFragment.java
@@ -137,7 +137,9 @@
  * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentDialogOrActivity.java
  *      embed}
  *
- * @deprecated Use {@link android.support.v4.app.DialogFragment}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ *      {@link android.support.v4.app.DialogFragment} for consistent behavior across all devices
+ *      and access to <a href="{@docRoot}topic/libraries/architecture/lifecycle.html">Lifecycle</a>.
  */
 @Deprecated
 public class DialogFragment extends Fragment
diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java
index a92684b..4ff07f2 100644
--- a/core/java/android/app/Fragment.java
+++ b/core/java/android/app/Fragment.java
@@ -257,7 +257,9 @@
  * pressing back will pop it to return the user to whatever previous state
  * the activity UI was in.
  *
- * @deprecated Use {@link android.support.v4.app.Fragment}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ *      {@link android.support.v4.app.Fragment} for consistent behavior across all devices
+ *      and access to <a href="{@docRoot}topic/libraries/architecture/lifecycle.html">Lifecycle</a>.
  */
 @Deprecated
 public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListener {
diff --git a/core/java/android/app/FragmentContainer.java b/core/java/android/app/FragmentContainer.java
index a1dd32f..536c866 100644
--- a/core/java/android/app/FragmentContainer.java
+++ b/core/java/android/app/FragmentContainer.java
@@ -25,7 +25,8 @@
 /**
  * Callbacks to a {@link Fragment}'s container.
  *
- * @deprecated Use {@link android.support.v4.app.FragmentContainer}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ *      {@link android.support.v4.app.FragmentContainer}.
  */
 @Deprecated
 public abstract class FragmentContainer {
diff --git a/core/java/android/app/FragmentController.java b/core/java/android/app/FragmentController.java
index cbb58d4..40bc248 100644
--- a/core/java/android/app/FragmentController.java
+++ b/core/java/android/app/FragmentController.java
@@ -38,7 +38,8 @@
  * It is the responsibility of the host to take care of the Fragment's lifecycle.
  * The methods provided by {@link FragmentController} are for that purpose.
  *
- * @deprecated Use {@link android.support.v4.app.FragmentController}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ *      {@link android.support.v4.app.FragmentController}
  */
 @Deprecated
 public class FragmentController {
diff --git a/core/java/android/app/FragmentHostCallback.java b/core/java/android/app/FragmentHostCallback.java
index 1edc68e..b48817b 100644
--- a/core/java/android/app/FragmentHostCallback.java
+++ b/core/java/android/app/FragmentHostCallback.java
@@ -38,7 +38,8 @@
  * host fragments, implement {@link FragmentHostCallback}, overriding the methods
  * applicable to the host.
  *
- * @deprecated Use {@link android.support.v4.app.FragmentHostCallback}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ *      {@link android.support.v4.app.FragmentHostCallback}
  */
 @Deprecated
 public abstract class FragmentHostCallback<E> extends FragmentContainer {
diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java
index 12e60b8..708450f 100644
--- a/core/java/android/app/FragmentManager.java
+++ b/core/java/android/app/FragmentManager.java
@@ -75,7 +75,9 @@
  * <a href="http://android-developers.blogspot.com/2011/03/fragments-for-all.html">
  * Fragments For All</a> for more details.
  *
- * @deprecated Use {@link android.support.v4.app.FragmentManager}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ *      {@link android.support.v4.app.FragmentManager} for consistent behavior across all devices
+ *      and access to <a href="{@docRoot}topic/libraries/architecture/lifecycle.html">Lifecycle</a>.
  */
 @Deprecated
 public abstract class FragmentManager {
@@ -90,7 +92,8 @@
      * the identifier as returned by {@link #getId} is the only thing that
      * will be persisted across activity instances.
      *
-     * @deprecated Use {@link android.support.v4.app.FragmentManager.BackStackEntry}
+     * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">
+     *      Support Library</a> {@link android.support.v4.app.FragmentManager.BackStackEntry}
      */
     @Deprecated
     public interface BackStackEntry {
@@ -136,7 +139,9 @@
     /**
      * Interface to watch for changes to the back stack.
      *
-     * @deprecated Use {@link android.support.v4.app.FragmentManager.OnBackStackChangedListener}
+     * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">
+     *      Support Library</a>
+     *      {@link android.support.v4.app.FragmentManager.OnBackStackChangedListener}
      */
     @Deprecated
     public interface OnBackStackChangedListener {
@@ -438,7 +443,9 @@
      * Callback interface for listening to fragment state changes that happen
      * within a given FragmentManager.
      *
-     * @deprecated Use {@link android.support.v4.app.FragmentManager.FragmentLifecycleCallbacks}
+     * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">
+     *      Support Library</a>
+     *      {@link android.support.v4.app.FragmentManager.FragmentLifecycleCallbacks}
      */
     @Deprecated
     public abstract static class FragmentLifecycleCallbacks {
diff --git a/core/java/android/app/FragmentManagerNonConfig.java b/core/java/android/app/FragmentManagerNonConfig.java
index beb1a15..326438a 100644
--- a/core/java/android/app/FragmentManagerNonConfig.java
+++ b/core/java/android/app/FragmentManagerNonConfig.java
@@ -28,7 +28,8 @@
  * {@link FragmentController#retainNonConfig()} and
  * {@link FragmentController#restoreAllState(Parcelable, FragmentManagerNonConfig)}.</p>
  *
- * @deprecated Use {@link android.support.v4.app.FragmentManagerNonConfig}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ *      {@link android.support.v4.app.FragmentManagerNonConfig}
  */
 @Deprecated
 public class FragmentManagerNonConfig {
diff --git a/core/java/android/app/FragmentTransaction.java b/core/java/android/app/FragmentTransaction.java
index 1103649..713a559 100644
--- a/core/java/android/app/FragmentTransaction.java
+++ b/core/java/android/app/FragmentTransaction.java
@@ -22,7 +22,8 @@
  * guide.</p>
  * </div>
  *
- * @deprecated Use {@link android.support.v4.app.FragmentTransaction}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ *      {@link android.support.v4.app.FragmentTransaction}
  */
 @Deprecated
 public abstract class FragmentTransaction {
diff --git a/core/java/android/app/ListFragment.java b/core/java/android/app/ListFragment.java
index 90b77b3..7790f70 100644
--- a/core/java/android/app/ListFragment.java
+++ b/core/java/android/app/ListFragment.java
@@ -145,7 +145,9 @@
  * @see #setListAdapter
  * @see android.widget.ListView
  *
- * @deprecated Use {@link android.support.v4.app.ListFragment}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ *      {@link android.support.v4.app.ListFragment} for consistent behavior across all devices
+ *      and access to <a href="{@docRoot}topic/libraries/architecture/lifecycle.html">Lifecycle</a>.
  */
 @Deprecated
 public class ListFragment extends Fragment {
diff --git a/core/java/android/app/LoaderManager.java b/core/java/android/app/LoaderManager.java
index 7969684..86d0fd6 100644
--- a/core/java/android/app/LoaderManager.java
+++ b/core/java/android/app/LoaderManager.java
@@ -55,14 +55,16 @@
  * <a href="{@docRoot}guide/topics/fundamentals/loaders.html">Loaders</a> developer guide.</p>
  * </div>
  *
- * @deprecated Use {@link android.support.v4.app.LoaderManager}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ *      {@link android.support.v4.app.LoaderManager}
  */
 @Deprecated
 public abstract class LoaderManager {
     /**
      * Callback interface for a client to interact with the manager.
      *
-     * @deprecated Use {@link android.support.v4.app.LoaderManager.LoaderCallbacks}
+     * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">
+     *      Support Library</a> {@link android.support.v4.app.LoaderManager.LoaderCallbacks}
      */
     @Deprecated
     public interface LoaderCallbacks<D> {
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 705f9a0..85c3be8 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1090,6 +1090,12 @@
     public static final String EXTRA_HISTORIC_MESSAGES = "android.messages.historic";
 
     /**
+     * {@link #extras} key: whether the {@link android.app.Notification.MessagingStyle} notification
+     * represents a group conversation.
+     */
+    public static final String EXTRA_IS_GROUP_CONVERSATION = "android.isGroupConversation";
+
+    /**
      * {@link #extras} key: whether the notification should be colorized as
      * supplied to {@link Builder#setColorized(boolean)}}.
      */
@@ -5960,9 +5966,10 @@
         public static final int MAXIMUM_RETAINED_MESSAGES = 25;
 
         CharSequence mUserDisplayName;
-        CharSequence mConversationTitle;
+        @Nullable CharSequence mConversationTitle;
         List<Message> mMessages = new ArrayList<>();
         List<Message> mHistoricMessages = new ArrayList<>();
+        boolean mIsGroupConversation;
 
         MessagingStyle() {
         }
@@ -5985,20 +5992,20 @@
         }
 
         /**
-         * Sets the title to be displayed on this conversation. This should only be used for
-         * group messaging and left unset for one-on-one conversations.
-         * @param conversationTitle
+         * Sets the title to be displayed on this conversation. May be set to {@code null}.
+         *
+         * @param conversationTitle A name for the conversation, or {@code null}
          * @return this object for method chaining.
          */
-        public MessagingStyle setConversationTitle(CharSequence conversationTitle) {
+        public MessagingStyle setConversationTitle(@Nullable CharSequence conversationTitle) {
             mConversationTitle = conversationTitle;
             return this;
         }
 
         /**
-         * Return the title to be displayed on this conversation. Can be <code>null</code> and
-         * should be for one-on-one conversations
+         * Return the title to be displayed on this conversation. May return {@code null}.
          */
+        @Nullable
         public CharSequence getConversationTitle() {
             return mConversationTitle;
         }
@@ -6075,6 +6082,24 @@
         }
 
         /**
+         * Sets whether this conversation notification represents a group.
+         * @param isGroupConversation {@code true} if the conversation represents a group,
+         * {@code false} otherwise.
+         * @return this object for method chaining
+         */
+        public MessagingStyle setGroupConversation(boolean isGroupConversation) {
+            mIsGroupConversation = isGroupConversation;
+            return this;
+        }
+
+        /**
+         * Returns {@code true} if this notification represents a group conversation.
+         */
+        public boolean isGroupConversation() {
+            return mIsGroupConversation;
+        }
+
+        /**
          * @hide
          */
         @Override
@@ -6094,6 +6119,7 @@
             }
 
             fixTitleAndTextExtras(extras);
+            extras.putBoolean(EXTRA_IS_GROUP_CONVERSATION, mIsGroupConversation);
         }
 
         private void fixTitleAndTextExtras(Bundle extras) {
@@ -6136,6 +6162,7 @@
             mMessages = Message.getMessagesFromBundleArray(messages);
             Parcelable[] histMessages = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES);
             mHistoricMessages = Message.getMessagesFromBundleArray(histMessages);
+            mIsGroupConversation = extras.getBoolean(EXTRA_IS_GROUP_CONVERSATION);
         }
 
         /**
diff --git a/core/java/android/app/slice/Slice.java b/core/java/android/app/slice/Slice.java
index 1aff7e9..5c7f674 100644
--- a/core/java/android/app/slice/Slice.java
+++ b/core/java/android/app/slice/Slice.java
@@ -63,7 +63,7 @@
             HINT_ACTIONS,
             HINT_SELECTED,
             HINT_NO_TINT,
-            HINT_HIDDEN,
+            HINT_SHORTCUT,
             HINT_TOGGLE,
             HINT_HORIZONTAL,
             HINT_PARTIAL,
@@ -118,12 +118,10 @@
      */
     public static final String HINT_NO_TINT     = "no_tint";
     /**
-     * Hint to indicate that this content should not be shown in larger renderings
-     * of Slices. This content may be used to populate the shortcut/icon
-     * format of the slice.
-     * @hide
+     * Hint to indicate that this content should only be displayed if the slice is presented
+     * as a shortcut.
      */
-    public static final String HINT_HIDDEN = "hidden";
+    public static final String HINT_SHORTCUT = "shortcut";
     /**
      * Hint indicating this content should be shown instead of the normal content when the slice
      * is in small format.
@@ -182,6 +180,10 @@
      * which can be retrieved to see the new state of the toggle.
      */
     public static final String SUBTYPE_TOGGLE = "toggle";
+    /**
+     * Subtype to tag an item representing priority.
+     */
+    public static final String SUBTYPE_PRIORITY = "priority";
 
     private final SliceItem[] mItems;
     private final @SliceHint String[] mHints;
diff --git a/core/java/android/bluetooth/BluetoothHidDevice.java b/core/java/android/bluetooth/BluetoothHidDevice.java
index f38e462..2fab305 100644
--- a/core/java/android/bluetooth/BluetoothHidDevice.java
+++ b/core/java/android/bluetooth/BluetoothHidDevice.java
@@ -85,7 +85,7 @@
      *
      * @see BluetoothHidDeviceCallback#onGetReport(BluetoothDevice, byte, byte, int)
      * @see BluetoothHidDeviceCallback#onSetReport(BluetoothDevice, byte, byte, byte[])
-     * @see BluetoothHidDeviceCallback#onIntrData(BluetoothDevice, byte, byte[])
+     * @see BluetoothHidDeviceCallback#onInterruptData(BluetoothDevice, byte, byte[])
      */
     public static final byte REPORT_TYPE_INPUT = (byte) 1;
     public static final byte REPORT_TYPE_OUTPUT = (byte) 2;
@@ -155,8 +155,8 @@
         }
 
         @Override
-        public void onIntrData(BluetoothDevice device, byte reportId, byte[] data) {
-            mCallback.onIntrData(device, reportId, data);
+        public void onInterruptData(BluetoothDevice device, byte reportId, byte[] data) {
+            mCallback.onInterruptData(device, reportId, data);
         }
 
         @Override
diff --git a/core/java/android/bluetooth/BluetoothHidDeviceCallback.java b/core/java/android/bluetooth/BluetoothHidDeviceCallback.java
index bd19955..e71b00f 100644
--- a/core/java/android/bluetooth/BluetoothHidDeviceCallback.java
+++ b/core/java/android/bluetooth/BluetoothHidDeviceCallback.java
@@ -106,8 +106,8 @@
      * @param reportId Report Id.
      * @param data Report data.
      */
-    public void onIntrData(BluetoothDevice device, byte reportId, byte[] data) {
-        Log.d(TAG, "onIntrData: device=" + device + " reportId=" + reportId);
+    public void onInterruptData(BluetoothDevice device, byte reportId, byte[] data) {
+        Log.d(TAG, "onInterruptData: device=" + device + " reportId=" + reportId);
     }
 
     /**
diff --git a/core/java/android/content/AsyncTaskLoader.java b/core/java/android/content/AsyncTaskLoader.java
index 6e9f09c..c44e356 100644
--- a/core/java/android/content/AsyncTaskLoader.java
+++ b/core/java/android/content/AsyncTaskLoader.java
@@ -50,7 +50,8 @@
  *
  * @param <D> the data type to be loaded.
  *
- * @deprecated Use {@link android.support.v4.content.AsyncTaskLoader}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ *      {@link android.support.v4.content.AsyncTaskLoader}
  */
 @Deprecated
 public abstract class AsyncTaskLoader<D> extends Loader<D> {
diff --git a/core/java/android/content/CursorLoader.java b/core/java/android/content/CursorLoader.java
index 7f24c51..5a08636 100644
--- a/core/java/android/content/CursorLoader.java
+++ b/core/java/android/content/CursorLoader.java
@@ -39,7 +39,8 @@
  * {@link #setSelectionArgs(String[])}, {@link #setSortOrder(String)},
  * and {@link #setProjection(String[])}.
  *
- * @deprecated Use {@link android.support.v4.content.CursorLoader}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ *      {@link android.support.v4.content.CursorLoader}
  */
 @Deprecated
 public class CursorLoader extends AsyncTaskLoader<Cursor> {
diff --git a/core/java/android/content/Loader.java b/core/java/android/content/Loader.java
index 80f9a14..b0555d4 100644
--- a/core/java/android/content/Loader.java
+++ b/core/java/android/content/Loader.java
@@ -49,7 +49,8 @@
  *
  * @param <D> The result returned when the load is complete
  *
- * @deprecated Use {@link android.support.v4.content.Loader}
+ * @deprecated Use the <a href="{@docRoot}tools/extras/support-library.html">Support Library</a>
+ *      {@link android.support.v4.content.Loader}
  */
 @Deprecated
 public class Loader<D> {
@@ -561,4 +562,4 @@
                     writer.print(" mReset="); writer.println(mReset);
         }
     }
-}
\ No newline at end of file
+}
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 9513b9b..430c28b 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -16,6 +16,7 @@
 
 package android.os;
 
+import android.app.ActivityManager;
 import android.app.job.JobParameters;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
@@ -33,6 +34,7 @@
 import android.util.proto.ProtoOutputStream;
 import android.view.Display;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.BatterySipper;
 import com.android.internal.os.BatteryStatsHelper;
 
@@ -327,7 +329,8 @@
      *
      * Other types might include times spent in foreground, background etc.
      */
-    private final String UID_TIMES_TYPE_ALL = "A";
+    @VisibleForTesting
+    public static final String UID_TIMES_TYPE_ALL = "A";
 
     /**
      * State for keeping track of counting information.
@@ -507,6 +510,31 @@
     }
 
     /**
+     * Maps the ActivityManager procstate into corresponding BatteryStats procstate.
+     */
+    public static int mapToInternalProcessState(int procState) {
+        if (procState == ActivityManager.PROCESS_STATE_NONEXISTENT) {
+            return ActivityManager.PROCESS_STATE_NONEXISTENT;
+        } else if (procState == ActivityManager.PROCESS_STATE_TOP) {
+            return Uid.PROCESS_STATE_TOP;
+        } else if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
+            // Persistent and other foreground states go here.
+            return Uid.PROCESS_STATE_FOREGROUND_SERVICE;
+        } else if (procState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
+            // Persistent and other foreground states go here.
+            return Uid.PROCESS_STATE_FOREGROUND;
+        } else if (procState <= ActivityManager.PROCESS_STATE_RECEIVER) {
+            return Uid.PROCESS_STATE_BACKGROUND;
+        } else if (procState <= ActivityManager.PROCESS_STATE_TOP_SLEEPING) {
+            return Uid.PROCESS_STATE_TOP_SLEEPING;
+        } else if (procState <= ActivityManager.PROCESS_STATE_HEAVY_WEIGHT) {
+            return Uid.PROCESS_STATE_HEAVY_WEIGHT;
+        } else {
+            return Uid.PROCESS_STATE_CACHED;
+        }
+    }
+
+    /**
      * The statistics associated with a particular uid.
      */
     public static abstract class Uid {
@@ -645,6 +673,15 @@
         public abstract long[] getCpuFreqTimes(int which);
         public abstract long[] getScreenOffCpuFreqTimes(int which);
 
+        /**
+         * Returns cpu times of an uid at a particular process state.
+         */
+        public abstract long[] getCpuFreqTimes(int which, int procState);
+        /**
+         * Returns cpu times of an uid while the screen if off at a particular process state.
+         */
+        public abstract long[] getScreenOffCpuFreqTimes(int which, int procState);
+
         // Note: the following times are disjoint.  They can be added together to find the
         // total time a uid has had any processes running at all.
 
@@ -689,11 +726,32 @@
          */
         public static final int NUM_PROCESS_STATE = 7;
 
+        // Used in dump
         static final String[] PROCESS_STATE_NAMES = {
                 "Top", "Fg Service", "Foreground", "Background", "Top Sleeping", "Heavy Weight",
                 "Cached"
         };
 
+        // Used in checkin dump
+        @VisibleForTesting
+        public static final String[] UID_PROCESS_TYPES = {
+                "T",  // TOP
+                "FS", // FOREGROUND_SERVICE
+                "F",  // FOREGROUND
+                "B",  // BACKGROUND
+                "TS", // TOP_SLEEPING
+                "HW",  // HEAVY_WEIGHT
+                "C"   // CACHED
+        };
+
+        /**
+         * When the process exits one of these states, we need to make sure cpu time in this state
+         * is not attributed to any non-critical process states.
+         */
+        public static final int[] CRITICAL_PROC_STATES = {
+            PROCESS_STATE_TOP, PROCESS_STATE_FOREGROUND_SERVICE, PROCESS_STATE_FOREGROUND
+        };
+
         public abstract long getProcessStateTime(int state, long elapsedRealtimeUs, int which);
         public abstract Timer getProcessStateTimer(int state);
 
@@ -4002,6 +4060,29 @@
                     dumpLine(pw, uid, category, CPU_TIMES_AT_FREQ_DATA, UID_TIMES_TYPE_ALL,
                             cpuFreqTimeMs.length, sb.toString());
                 }
+
+                for (int procState = 0; procState < Uid.NUM_PROCESS_STATE; ++procState) {
+                    final long[] timesMs = u.getCpuFreqTimes(which, procState);
+                    if (timesMs != null && timesMs.length == cpuFreqs.length) {
+                        sb.setLength(0);
+                        for (int i = 0; i < timesMs.length; ++i) {
+                            sb.append((i == 0 ? "" : ",") + timesMs[i]);
+                        }
+                        final long[] screenOffTimesMs = u.getScreenOffCpuFreqTimes(
+                                which, procState);
+                        if (screenOffTimesMs != null) {
+                            for (int i = 0; i < screenOffTimesMs.length; ++i) {
+                                sb.append("," + screenOffTimesMs[i]);
+                            }
+                        } else {
+                            for (int i = 0; i < timesMs.length; ++i) {
+                                sb.append(",0");
+                            }
+                        }
+                        dumpLine(pw, uid, category, CPU_TIMES_AT_FREQ_DATA,
+                                Uid.UID_PROCESS_TYPES[procState], timesMs.length, sb.toString());
+                    }
+                }
             }
 
             final ArrayMap<String, ? extends BatteryStats.Uid.Proc> processStats
@@ -5612,6 +5693,30 @@
                 pw.println(sb.toString());
             }
 
+            for (int procState = 0; procState < Uid.NUM_PROCESS_STATE; ++procState) {
+                final long[] cpuTimes = u.getCpuFreqTimes(which, procState);
+                if (cpuTimes != null) {
+                    sb.setLength(0);
+                    sb.append("    Cpu times per freq at state "
+                            + Uid.PROCESS_STATE_NAMES[procState] + ":");
+                    for (int i = 0; i < cpuTimes.length; ++i) {
+                        sb.append(" " + cpuTimes[i]);
+                    }
+                    pw.println(sb.toString());
+                }
+
+                final long[] screenOffCpuTimes = u.getScreenOffCpuFreqTimes(which, procState);
+                if (screenOffCpuTimes != null) {
+                    sb.setLength(0);
+                    sb.append("   Screen-off cpu times per freq at state "
+                            + Uid.PROCESS_STATE_NAMES[procState] + ":");
+                    for (int i = 0; i < screenOffCpuTimes.length; ++i) {
+                        sb.append(" " + screenOffCpuTimes[i]);
+                    }
+                    pw.println(sb.toString());
+                }
+            }
+
             final ArrayMap<String, ? extends BatteryStats.Uid.Proc> processStats
                     = u.getProcessStats();
             for (int ipr=processStats.size()-1; ipr>=0; ipr--) {
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index 9c90c38..f643c57 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -79,9 +79,7 @@
     void setDefaultGuestRestrictions(in Bundle restrictions);
     Bundle getDefaultGuestRestrictions();
     boolean markGuestForDeletion(int userHandle);
-    void setQuietModeEnabled(int userHandle, boolean enableQuietMode, in IntentSender target);
     boolean isQuietModeEnabled(int userHandle);
-    boolean trySetQuietModeDisabled(int userHandle, in IntentSender target);
     void setSeedAccountData(int userHandle, in String accountName,
             in String accountType, in PersistableBundle accountOptions, boolean persist);
     String getSeedAccountName();
@@ -99,4 +97,5 @@
     boolean isUserRunning(int userId);
     boolean isUserNameSet(int userHandle);
     boolean hasRestrictedProfiles();
+    boolean trySetQuietModeEnabled(String callingPackage, boolean enableQuietMode, int userHandle, in IntentSender target);
 }
diff --git a/core/java/android/os/RemoteCallbackList.java b/core/java/android/os/RemoteCallbackList.java
index b9b9a18..bbb8a7b 100644
--- a/core/java/android/os/RemoteCallbackList.java
+++ b/core/java/android/os/RemoteCallbackList.java
@@ -334,6 +334,23 @@
     }
 
     /**
+     * Performs {@code action} for each cookie associated with a callback, calling
+     * {@link #beginBroadcast()}/{@link #finishBroadcast()} before/after looping
+     *
+     * @hide
+     */
+    public <C> void broadcastForEachCookie(Consumer<C> action) {
+        int itemCount = beginBroadcast();
+        try {
+            for (int i = 0; i < itemCount; i++) {
+                action.accept((C) getBroadcastCookie(i));
+            }
+        } finally {
+            finishBroadcast();
+        }
+    }
+
+    /**
      * Returns the number of registered callbacks. Note that the number of registered
      * callbacks may differ from the value returned by {@link #beginBroadcast()} since
      * the former returns the number of callbacks registered at the time of the call
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 3504142..75cbd57 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -2130,15 +2130,46 @@
     }
 
     /**
-     * Set quiet mode of a managed profile.
+     * Enables or disables quiet mode for a managed profile. If quiet mode is enabled, apps in a
+     * managed profile don't run, generate notifications, or consume data or battery.
+     * <p>
+     * If a user's credential is needed to turn off quiet mode, a confirm credential screen will be
+     * shown to the user.
+     * <p>
+     * The change may not happen instantly, however apps can listen for
+     * {@link Intent#ACTION_MANAGED_PROFILE_AVAILABLE} and
+     * {@link Intent#ACTION_MANAGED_PROFILE_UNAVAILABLE} broadcasts in order to be notified of
+     * the change of the quiet mode. Apps can also check the current state of quiet mode by
+     * calling {@link #isQuietModeEnabled(UserHandle)}.
+     * <p>
+     * The caller must either be the foreground default launcher or have one of these permissions:
+     * {@code MANAGE_USERS} or {@code MODIFY_QUIET_MODE}.
      *
-     * @param userHandle The user handle of the profile.
-     * @param enableQuietMode Whether quiet mode should be enabled or disabled.
+     * @param enableQuietMode whether quiet mode should be enabled or disabled
+     * @param userHandle user handle of the profile
+     * @return {@code false} if user's credential is needed in order to turn off quiet mode,
+     *         {@code true} otherwise
+     * @throws SecurityException if the caller is invalid
+     * @throws IllegalArgumentException if {@code userHandle} is not a managed profile
+     *
+     * @see #isQuietModeEnabled(UserHandle)
+     */
+    public boolean trySetQuietModeEnabled(boolean enableQuietMode, @NonNull UserHandle userHandle) {
+        return trySetQuietModeEnabled(enableQuietMode, userHandle, null);
+    }
+
+    /**
+     * Similar to {@link #trySetQuietModeEnabled(boolean, UserHandle)}, except you can specify
+     * a target to start when user is unlocked.
+     *
+     * @see {@link #trySetQuietModeEnabled(boolean, UserHandle)}
      * @hide
      */
-    public void setQuietModeEnabled(@UserIdInt int userHandle, boolean enableQuietMode) {
+    public boolean trySetQuietModeEnabled(
+            boolean enableQuietMode, @NonNull UserHandle userHandle, IntentSender target) {
         try {
-            mService.setQuietModeEnabled(userHandle, enableQuietMode, null);
+            return mService.trySetQuietModeEnabled(
+                    mContext.getPackageName(), enableQuietMode, userHandle.getIdentifier(), target);
         } catch (RemoteException re) {
             throw re.rethrowFromSystemServer();
         }
@@ -2160,27 +2191,6 @@
     }
 
     /**
-     * Tries disabling quiet mode for a given user. If the user is still locked, we unlock the user
-     * first by showing the confirm credentials screen and disable quiet mode upon successful
-     * unlocking. If the user is already unlocked, we call through to {@link #setQuietModeEnabled}
-     * directly.
-     *
-     * @param userHandle The user that is going to disable quiet mode.
-     * @param target The target to launch when the user is unlocked.
-     * @return {@code true} if quiet mode is disabled without showing confirm credentials screen,
-     *         {@code false} otherwise.
-     * @hide
-     */
-    public boolean trySetQuietModeDisabled(
-            @UserIdInt int userHandle, @Nullable IntentSender target) {
-        try {
-            return mService.trySetQuietModeDisabled(userHandle, target);
-        } catch (RemoteException re) {
-            throw re.rethrowFromSystemServer();
-        }
-    }
-
-    /**
      * If the target user is a managed profile of the calling user or the caller
      * is itself a managed profile, then this returns a badged copy of the given
      * icon to be able to distinguish it from the original icon. For badging an
diff --git a/core/java/android/os/WorkSource.java b/core/java/android/os/WorkSource.java
index dd59142..8632aad 100644
--- a/core/java/android/os/WorkSource.java
+++ b/core/java/android/os/WorkSource.java
@@ -1,10 +1,13 @@
 package android.os;
 
+import android.annotation.Nullable;
 import android.os.WorkSourceProto;
 import android.util.Log;
 import android.util.proto.ProtoOutputStream;
 
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Objects;
 
 /**
  * Describes the source of some work that may be done by someone else.
@@ -19,6 +22,8 @@
     int[] mUids;
     String[] mNames;
 
+    private ArrayList<WorkChain> mChains;
+
     /**
      * Internal statics to avoid object allocations in some operations.
      * The WorkSource object itself is not thread safe, but we need to
@@ -39,6 +44,7 @@
      */
     public WorkSource() {
         mNum = 0;
+        mChains = null;
     }
 
     /**
@@ -48,6 +54,7 @@
     public WorkSource(WorkSource orig) {
         if (orig == null) {
             mNum = 0;
+            mChains = null;
             return;
         }
         mNum = orig.mNum;
@@ -58,6 +65,16 @@
             mUids = null;
             mNames = null;
         }
+
+        if (orig.mChains != null) {
+            // Make a copy of all WorkChains that exist on |orig| since they are mutable.
+            mChains = new ArrayList<>(orig.mChains.size());
+            for (WorkChain chain : orig.mChains) {
+                mChains.add(new WorkChain(chain));
+            }
+        } else {
+            mChains = null;
+        }
     }
 
     /** @hide */
@@ -65,6 +82,7 @@
         mNum = 1;
         mUids = new int[] { uid, 0 };
         mNames = null;
+        mChains = null;
     }
 
     /** @hide */
@@ -75,12 +93,21 @@
         mNum = 1;
         mUids = new int[] { uid, 0 };
         mNames = new String[] { name, null };
+        mChains = null;
     }
 
     WorkSource(Parcel in) {
         mNum = in.readInt();
         mUids = in.createIntArray();
         mNames = in.createStringArray();
+
+        int numChains = in.readInt();
+        if (numChains > 0) {
+            mChains = new ArrayList<>(numChains);
+            in.readParcelableList(mChains, WorkChain.class.getClassLoader());
+        } else {
+            mChains = null;
+        }
     }
 
     /** @hide */
@@ -99,7 +126,8 @@
     }
 
     /**
-     * Clear names from this WorkSource.  Uids are left intact.
+     * Clear names from this WorkSource. Uids are left intact. WorkChains if any, are left
+     * intact.
      *
      * <p>Useful when combining with another WorkSource that doesn't have names.
      * @hide
@@ -127,11 +155,16 @@
      */
     public void clear() {
         mNum = 0;
+        if (mChains != null) {
+            mChains.clear();
+        }
     }
 
     @Override
     public boolean equals(Object o) {
-        return o instanceof WorkSource && !diff((WorkSource)o);
+        return o instanceof WorkSource
+            && !diff((WorkSource) o)
+            && Objects.equals(mChains, ((WorkSource) o).mChains);
     }
 
     @Override
@@ -145,6 +178,11 @@
                 result = ((result << 4) | (result >>> 28)) ^ mNames[i].hashCode();
             }
         }
+
+        if (mChains != null) {
+            result = ((result << 4) | (result >>> 28)) ^ mChains.hashCode();
+        }
+
         return result;
     }
 
@@ -153,6 +191,8 @@
      * @param other The WorkSource to compare against.
      * @return If there is a difference, true is returned.
      */
+    // TODO: This is a public API so it cannot be renamed. Because it is used in several places,
+    // we keep its semantics the same and ignore any differences in WorkChains (if any).
     public boolean diff(WorkSource other) {
         int N = mNum;
         if (N != other.mNum) {
@@ -175,12 +215,15 @@
 
     /**
      * Replace the current contents of this work source with the given
-     * work source.  If <var>other</var> is null, the current work source
+     * work source.  If {@code other} is null, the current work source
      * will be made empty.
      */
     public void set(WorkSource other) {
         if (other == null) {
             mNum = 0;
+            if (mChains != null) {
+                mChains.clear();
+            }
             return;
         }
         mNum = other.mNum;
@@ -203,6 +246,18 @@
             mUids = null;
             mNames = null;
         }
+
+        if (other.mChains != null) {
+            if (mChains != null) {
+                mChains.clear();
+            } else {
+                mChains = new ArrayList<>(other.mChains.size());
+            }
+
+            for (WorkChain chain : other.mChains) {
+                mChains.add(new WorkChain(chain));
+            }
+        }
     }
 
     /** @hide */
@@ -211,6 +266,7 @@
         if (mUids == null) mUids = new int[2];
         mUids[0] = uid;
         mNames = null;
+        mChains.clear();
     }
 
     /** @hide */
@@ -225,9 +281,21 @@
         }
         mUids[0] = uid;
         mNames[0] = name;
+        mChains.clear();
     }
 
-    /** @hide */
+    /**
+     * Legacy API, DO NOT USE: Only deals with flat UIDs and tags. No chains are transferred, and no
+     * differences in chains are returned. This will be removed once its callers have been
+     * rewritten.
+     *
+     * NOTE: This is currently only used in GnssLocationProvider.
+     *
+     * @hide
+     * @deprecated for internal use only. WorkSources are opaque and no external callers should need
+     *     to be aware of internal differences.
+     */
+    @Deprecated
     public WorkSource[] setReturningDiffs(WorkSource other) {
         synchronized (sTmpWorkSource) {
             sNewbWork = null;
@@ -251,11 +319,34 @@
      */
     public boolean add(WorkSource other) {
         synchronized (sTmpWorkSource) {
-            return updateLocked(other, false, false);
+            boolean uidAdded = updateLocked(other, false, false);
+
+            boolean chainAdded = false;
+            if (other.mChains != null) {
+                // NOTE: This is quite an expensive operation, especially if the number of chains
+                // is large. We could look into optimizing it if it proves problematic.
+                if (mChains == null) {
+                    mChains = new ArrayList<>(other.mChains.size());
+                }
+
+                for (WorkChain wc : other.mChains) {
+                    if (!mChains.contains(wc)) {
+                        mChains.add(new WorkChain(wc));
+                    }
+                }
+            }
+
+            return uidAdded || chainAdded;
         }
     }
 
-    /** @hide */
+    /**
+     * Legacy API: DO NOT USE. Only in use from unit tests.
+     *
+     * @hide
+     * @deprecated meant for unit testing use only. Will be removed in a future API revision.
+     */
+    @Deprecated
     public WorkSource addReturningNewbs(WorkSource other) {
         synchronized (sTmpWorkSource) {
             sNewbWork = null;
@@ -311,22 +402,14 @@
         return true;
     }
 
-    /** @hide */
-    public WorkSource addReturningNewbs(int uid) {
-        synchronized (sTmpWorkSource) {
-            sNewbWork = null;
-            sTmpWorkSource.mUids[0] = uid;
-            updateLocked(sTmpWorkSource, false, true);
-            return sNewbWork;
-        }
-    }
-
     public boolean remove(WorkSource other) {
         if (mNum <= 0 || other.mNum <= 0) {
             return false;
         }
+
+        boolean uidRemoved = false;
         if (mNames == null && other.mNames == null) {
-            return removeUids(other);
+            uidRemoved = removeUids(other);
         } else {
             if (mNames == null) {
                 throw new IllegalArgumentException("Other " + other + " has names, but target "
@@ -336,8 +419,44 @@
                 throw new IllegalArgumentException("Target " + this + " has names, but other "
                         + other + " does not");
             }
-            return removeUidsAndNames(other);
+            uidRemoved = removeUidsAndNames(other);
         }
+
+        boolean chainRemoved = false;
+        if (other.mChains != null) {
+            if (mChains != null) {
+                chainRemoved = mChains.removeAll(other.mChains);
+            }
+        } else if (mChains != null) {
+            mChains.clear();
+            chainRemoved = true;
+        }
+
+        return uidRemoved || chainRemoved;
+    }
+
+    /**
+     * Create a new {@code WorkChain} associated with this WorkSource and return it.
+     *
+     * @hide
+     */
+    public WorkChain createWorkChain() {
+        if (mChains == null) {
+            mChains = new ArrayList<>(4);
+        }
+
+        final WorkChain wc = new WorkChain();
+        mChains.add(wc);
+
+        return wc;
+    }
+
+    /**
+     * @return the list of {@code WorkChains} associated with this {@code WorkSource}.
+     * @hide
+     */
+    public ArrayList<WorkChain> getWorkChains() {
+        return mChains;
     }
 
     private boolean removeUids(WorkSource other) {
@@ -648,6 +767,167 @@
         }
     }
 
+    /**
+     * Represents an attribution chain for an item of work being performed. An attribution chain is
+     * an indexed list of {code (uid, tag)} nodes. The node at {@code index == 0} is the initiator
+     * of the work, and the node at the highest index performs the work. Nodes at other indices
+     * are intermediaries that facilitate the work. Examples :
+     *
+     * (1) Work being performed by uid=2456 (no chaining):
+     * <pre>
+     * WorkChain {
+     *   mUids = { 2456 }
+     *   mTags = { null }
+     *   mSize = 1;
+     * }
+     * </pre>
+     *
+     * (2) Work being performed by uid=2456 (from component "c1") on behalf of uid=5678:
+     *
+     * <pre>
+     * WorkChain {
+     *   mUids = { 5678, 2456 }
+     *   mTags = { null, "c1" }
+     *   mSize = 1
+     * }
+     * </pre>
+     *
+     * Attribution chains are mutable, though the only operation that can be performed on them
+     * is the addition of a new node at the end of the attribution chain to represent
+     *
+     * @hide
+     */
+    public static class WorkChain implements Parcelable {
+        private int mSize;
+        private int[] mUids;
+        private String[] mTags;
+
+        // @VisibleForTesting
+        public WorkChain() {
+            mSize = 0;
+            mUids = new int[4];
+            mTags = new String[4];
+        }
+
+        // @VisibleForTesting
+        public WorkChain(WorkChain other) {
+            mSize = other.mSize;
+            mUids = other.mUids.clone();
+            mTags = other.mTags.clone();
+        }
+
+        private WorkChain(Parcel in) {
+            mSize = in.readInt();
+            mUids = in.createIntArray();
+            mTags = in.createStringArray();
+        }
+
+        /**
+         * Append a node whose uid is {@code uid} and whose optional tag is {@code tag} to this
+         * {@code WorkChain}.
+         */
+        public WorkChain addNode(int uid, @Nullable String tag) {
+            if (mSize == mUids.length) {
+                resizeArrays();
+            }
+
+            mUids[mSize] = uid;
+            mTags[mSize] = tag;
+            mSize++;
+
+            return this;
+        }
+
+        // TODO: The following three trivial getters are purely for testing and will be removed
+        // once we have higher level logic in place, e.g for serializing this WorkChain to a proto,
+        // diffing it etc.
+        //
+        // @VisibleForTesting
+        public int[] getUids() {
+            return mUids;
+        }
+        // @VisibleForTesting
+        public String[] getTags() {
+            return mTags;
+        }
+        // @VisibleForTesting
+        public int getSize() {
+            return mSize;
+        }
+
+        private void resizeArrays() {
+            final int newSize = mSize * 2;
+            int[] uids = new int[newSize];
+            String[] tags = new String[newSize];
+
+            System.arraycopy(mUids, 0, uids, 0, mSize);
+            System.arraycopy(mTags, 0, tags, 0, mSize);
+
+            mUids = uids;
+            mTags = tags;
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder result = new StringBuilder("WorkChain{");
+            for (int i = 0; i < mSize; ++i) {
+                if (i != 0) {
+                    result.append(", ");
+                }
+                result.append("(");
+                result.append(mUids[i]);
+                if (mTags[i] != null) {
+                    result.append(", ");
+                    result.append(mTags[i]);
+                }
+                result.append(")");
+            }
+
+            result.append("}");
+            return result.toString();
+        }
+
+        @Override
+        public int hashCode() {
+            return (mSize + 31 * Arrays.hashCode(mUids)) * 31 + Arrays.hashCode(mTags);
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (o instanceof WorkChain) {
+                WorkChain other = (WorkChain) o;
+
+                return mSize == other.mSize
+                    && Arrays.equals(mUids, other.mUids)
+                    && Arrays.equals(mTags, other.mTags);
+            }
+
+            return false;
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(mSize);
+            dest.writeIntArray(mUids);
+            dest.writeStringArray(mTags);
+        }
+
+        public static final Parcelable.Creator<WorkChain> CREATOR =
+                new Parcelable.Creator<WorkChain>() {
+                    public WorkChain createFromParcel(Parcel in) {
+                        return new WorkChain(in);
+                    }
+                    public WorkChain[] newArray(int size) {
+                        return new WorkChain[size];
+                    }
+                };
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -658,6 +938,13 @@
         dest.writeInt(mNum);
         dest.writeIntArray(mUids);
         dest.writeStringArray(mNames);
+
+        if (mChains == null) {
+            dest.writeInt(-1);
+        } else {
+            dest.writeInt(mChains.size());
+            dest.writeParcelableList(mChains, flags);
+        }
     }
 
     @Override
@@ -674,6 +961,17 @@
                 result.append(mNames[i]);
             }
         }
+
+        if (mChains != null) {
+            result.append(" chains=");
+            for (int i = 0; i < mChains.size(); ++i) {
+                if (i != 0) {
+                    result.append(", ");
+                }
+                result.append(mChains.get(i));
+            }
+        }
+
         result.append("}");
         return result.toString();
     }
diff --git a/core/java/android/security/recoverablekeystore/KeyDerivationParameters.java b/core/java/android/security/recoverablekeystore/KeyDerivationParameters.java
index 2205c41..978e60e 100644
--- a/core/java/android/security/recoverablekeystore/KeyDerivationParameters.java
+++ b/core/java/android/security/recoverablekeystore/KeyDerivationParameters.java
@@ -60,7 +60,7 @@
     /**
      * Creates instance of the class to to derive key using salted SHA256 hash.
      */
-    public KeyDerivationParameters createSHA256Parameters(@NonNull byte[] salt) {
+    public static KeyDerivationParameters createSHA256Parameters(@NonNull byte[] salt) {
         return new KeyDerivationParameters(ALGORITHM_SHA256, salt);
     }
 
diff --git a/core/java/android/security/recoverablekeystore/RecoverableKeyStoreLoader.java b/core/java/android/security/recoverablekeystore/RecoverableKeyStoreLoader.java
index f2f225d..f88768b 100644
--- a/core/java/android/security/recoverablekeystore/RecoverableKeyStoreLoader.java
+++ b/core/java/android/security/recoverablekeystore/RecoverableKeyStoreLoader.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.PendingIntent;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -29,6 +30,7 @@
 import com.android.internal.widget.ILockSettings;
 
 import java.util.List;
+import java.util.Map;
 
 /**
  * A wrapper around KeyStore which lets key be exported to trusted hardware on server side and
@@ -43,9 +45,23 @@
     public static final int NO_ERROR = KeyStore.NO_ERROR;
     public static final int SYSTEM_ERROR = KeyStore.SYSTEM_ERROR;
     public static final int UNINITIALIZED_RECOVERY_PUBLIC_KEY = 20;
-    // Too many updates to recovery public key or server parameters.
+    /**
+     * Rate limit is enforced to prevent using too many trusted remote devices, since each device
+     * can have its own number of user secret guesses allowed.
+     *
+     * @hide
+     */
     public static final int RATE_LIMIT_EXCEEDED = 21;
 
+    /** Key has been successfully synced. */
+    public static final int RECOVERY_STATUS_SYNCED = 0;
+    /** Waiting for recovery agent to sync the key. */
+    public static final int RECOVERY_STATUS_SYNC_IN_PROGRESS = 1;
+    /** Recovery account is not available. */
+    public static final int RECOVERY_STATUS_MISSING_ACCOUNT = 2;
+    /** Key cannot be synced. */
+    public static final int RECOVERY_STATUS_PERMANENT_FAILURE = 3;
+
     private final ILockSettings mBinder;
 
     private RecoverableKeyStoreLoader(ILockSettings binder) {
@@ -155,7 +171,7 @@
      * @return Data necessary to recover keystore.
      * @hide
      */
-    public KeyStoreRecoveryData getRecoveryData(@NonNull byte[] account)
+    public @NonNull KeyStoreRecoveryData getRecoveryData(@NonNull byte[] account)
             throws RecoverableKeyStoreLoaderException {
         try {
             KeyStoreRecoveryData recoveryData =
@@ -169,6 +185,50 @@
     }
 
     /**
+     * Sets a listener which notifies recovery agent that new recovery snapshot is available. {@link
+     * #getRecoveryData} can be used to get the snapshot. Note that every recovery agent can have at
+     * most one registered listener at any time.
+     *
+     * @param intent triggered when new snapshot is available. Unregisters listener if the value is
+     *     {@code null}.
+     * @hide
+     */
+    public void setSnapshotCreatedPendingIntent(@Nullable PendingIntent intent)
+            throws RecoverableKeyStoreLoaderException {
+        try {
+            mBinder.setSnapshotCreatedPendingIntent(intent, UserHandle.getCallingUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        } catch (ServiceSpecificException e) {
+            throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
+        }
+    }
+
+    /**
+     * Returns a map from recovery agent accounts to corresponding KeyStore recovery snapshot
+     * version. Version zero is used, if no snapshots were created for the account.
+     *
+     * @return Map from recovery agent accounts to snapshot versions.
+     * @see KeyStoreRecoveryData.getSnapshotVersion
+     * @hide
+     */
+    public @NonNull Map<byte[], Integer> getRecoverySnapshotVersions()
+            throws RecoverableKeyStoreLoaderException {
+        try {
+            // IPC doesn't support generic Maps.
+            @SuppressWarnings("unchecked")
+            Map<byte[], Integer> result =
+                    (Map<byte[], Integer>)
+                            mBinder.getRecoverySnapshotVersions(UserHandle.getCallingUserId());
+            return result;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        } catch (ServiceSpecificException e) {
+            throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
+        }
+    }
+
+    /**
      * Server parameters used to generate new recovery key blobs. This value will be included in
      * {@code KeyStoreRecoveryData.getEncryptedRecoveryKeyBlob()}. The same value must be included
      * in vaultParams {@link startRecoverySession}
@@ -191,8 +251,8 @@
 
     /**
      * Updates recovery status for given keys. It is used to notify keystore that key was
-     * successfully stored on the server or there were an error. Returned as a part of KeyInfo data
-     * structure.
+     * successfully stored on the server or there were an error. Application can check this value
+     * using {@code getRecoveyStatus}.
      *
      * @param packageName Application whose recoverable keys' statuses are to be updated.
      * @param aliases List of application-specific key aliases. If the array is empty, updates the
@@ -212,6 +272,39 @@
     }
 
     /**
+     * Returns a {@code Map} from Application's KeyStore key aliases to their recovery status.
+     * Negative status values are reserved for recovery agent specific codes. List of common codes:
+     *
+     * <ul>
+     *   <li>{@link #RECOVERY_STATUS_SYNCED}
+     *   <li>{@link #RECOVERY_STATUS_SYNC_IN_PROGRESS}
+     *   <li>{@link #RECOVERY_STATUS_MISSING_ACCOUNT}
+     *   <li>{@link #RECOVERY_STATUS_PERMANENT_FAILURE}
+     * </ul>
+     *
+     * @param packageName Application whose recoverable keys' statuses are to be retrieved. if
+     *     {@code null} caller's package will be used.
+     * @return {@code Map} from KeyStore alias to recovery status.
+     * @see #setRecoveryStatus
+     * @hide
+     */
+    public Map<String, Integer> getRecoveryStatus(@Nullable String packageName)
+            throws RecoverableKeyStoreLoaderException {
+        try {
+            // IPC doesn't support generic Maps.
+            @SuppressWarnings("unchecked")
+            Map<String, Integer> result =
+                    (Map<String, Integer>)
+                            mBinder.getRecoveryStatus(packageName, UserHandle.getCallingUserId());
+            return result;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        } catch (ServiceSpecificException e) {
+            throw RecoverableKeyStoreLoaderException.fromServiceSpecificException(e);
+        }
+    }
+
+    /**
      * Specifies a set of secret types used for end-to-end keystore encryption. Knowing all of them
      * is necessary to recover data.
      *
diff --git a/core/java/android/service/autofill/AutofillService.java b/core/java/android/service/autofill/AutofillService.java
index 2600f8a..917efa8 100644
--- a/core/java/android/service/autofill/AutofillService.java
+++ b/core/java/android/service/autofill/AutofillService.java
@@ -438,7 +438,7 @@
  *  AutofillValue password = passwordNode.getAutofillValue().getTextValue().toString();
  *
  *  save(username, password);
- * </pre>
+ *  </pre>
  *
  * <a name="Privacy"></a>
  * <h3>Privacy</h3>
diff --git a/core/java/android/view/DisplayCutout.java b/core/java/android/view/DisplayCutout.java
index 19cd42e..e448f14 100644
--- a/core/java/android/view/DisplayCutout.java
+++ b/core/java/android/view/DisplayCutout.java
@@ -21,40 +21,37 @@
 import static android.view.Surface.ROTATION_270;
 import static android.view.Surface.ROTATION_90;
 
-import android.annotation.NonNull;
+import android.graphics.Path;
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.Region;
 import android.os.Parcel;
 import android.os.Parcelable;
 
 import com.android.internal.annotations.VisibleForTesting;
 
-import java.util.ArrayList;
 import java.util.List;
 
 /**
  * Represents a part of the display that is not functional for displaying content.
  *
  * <p>{@code DisplayCutout} is immutable.
- *
- * @hide will become API
  */
 public final class DisplayCutout {
 
-    private static final Rect ZERO_RECT = new Rect(0, 0, 0, 0);
-    private static final ArrayList<Point> EMPTY_LIST = new ArrayList<>();
+    private static final Rect ZERO_RECT = new Rect();
+    private static final Region EMPTY_REGION = new Region();
 
     /**
-     * An instance where {@link #hasCutout()} returns {@code false}.
+     * An instance where {@link #isEmpty()} returns {@code true}.
      *
      * @hide
      */
-    public static final DisplayCutout NO_CUTOUT =
-            new DisplayCutout(ZERO_RECT, ZERO_RECT, EMPTY_LIST);
+    public static final DisplayCutout NO_CUTOUT = new DisplayCutout(ZERO_RECT, EMPTY_REGION);
 
     private final Rect mSafeInsets;
-    private final Rect mBoundingRect;
-    private final List<Point> mBoundingPolygon;
+    private final Region mBounds;
 
     /**
      * Creates a DisplayCutout instance.
@@ -64,22 +61,18 @@
      * @hide
      */
     @VisibleForTesting
-    public DisplayCutout(Rect safeInsets, Rect boundingRect, List<Point> boundingPolygon) {
+    public DisplayCutout(Rect safeInsets, Region bounds) {
         mSafeInsets = safeInsets != null ? safeInsets : ZERO_RECT;
-        mBoundingRect = boundingRect != null ? boundingRect : ZERO_RECT;
-        mBoundingPolygon = boundingPolygon != null ? boundingPolygon : EMPTY_LIST;
+        mBounds = bounds != null ? bounds : Region.obtain();
     }
 
     /**
-     * Returns whether there is a cutout.
+     * Returns true if there is no cutout or it is outside of the content view.
      *
-     * If false, the safe insets will all return zero, and the bounding box or polygon will be
-     * empty or outside the content view.
-     *
-     * @return {@code true} if there is a cutout, {@code false} otherwise
+     * @hide
      */
-    public boolean hasCutout() {
-        return !mSafeInsets.equals(ZERO_RECT);
+    public boolean isEmpty() {
+        return mSafeInsets.equals(ZERO_RECT);
     }
 
     /** Returns the inset from the top which avoids the display cutout. */
@@ -103,44 +96,41 @@
     }
 
     /**
-     * Obtains the safe insets in a rect.
+     * Returns the safe insets in a rect.
      *
-     * @param out a rect which is set to the safe insets.
+     * @return a rect which is set to the safe insets.
      * @hide
      */
-    public void getSafeInsets(@NonNull Rect out) {
-        out.set(mSafeInsets);
+    public Rect getSafeInsets() {
+        return new Rect(mSafeInsets);
     }
 
     /**
-     * Obtains the bounding rect of the cutout.
+     * Returns the bounding region of the cutout.
      *
-     * @param outRect is filled with the bounding rect of the cutout. Coordinates are relative
+     * @return the bounding region of the cutout. Coordinates are relative
      *         to the top-left corner of the content view.
      */
-    public void getBoundingRect(@NonNull Rect outRect) {
-        outRect.set(mBoundingRect);
+    public Region getBounds() {
+        return Region.obtain(mBounds);
     }
 
     /**
-     * Obtains the bounding polygon of the cutout.
+     * Returns the bounding rect of the cutout.
      *
-     * @param outPolygon is filled with a list of points representing the corners of a convex
-     *         polygon which covers the cutout. Coordinates are relative to the
-     *         top-left corner of the content view.
+     * @return the bounding rect of the cutout. Coordinates are relative
+     *         to the top-left corner of the content view.
+     * @hide
      */
-    public void getBoundingPolygon(List<Point> outPolygon) {
-        outPolygon.clear();
-        for (int i = 0; i < mBoundingPolygon.size(); i++) {
-            outPolygon.add(new Point(mBoundingPolygon.get(i)));
-        }
+    public Rect getBoundingRect() {
+        // TODO(roosa): Inline.
+        return mBounds.getBounds();
     }
 
     @Override
     public int hashCode() {
         int result = mSafeInsets.hashCode();
-        result = result * 31 + mBoundingRect.hashCode();
-        result = result * 31 + mBoundingPolygon.hashCode();
+        result = result * 31 + mBounds.getBounds().hashCode();
         return result;
     }
 
@@ -152,8 +142,7 @@
         if (o instanceof DisplayCutout) {
             DisplayCutout c = (DisplayCutout) o;
             return mSafeInsets.equals(c.mSafeInsets)
-                    && mBoundingRect.equals(c.mBoundingRect)
-                    && mBoundingPolygon.equals(c.mBoundingPolygon);
+                    && mBounds.equals(c.mBounds);
         }
         return false;
     }
@@ -161,7 +150,7 @@
     @Override
     public String toString() {
         return "DisplayCutout{insets=" + mSafeInsets
-                + " bounding=" + mBoundingRect
+                + " bounds=" + mBounds
                 + "}";
     }
 
@@ -172,15 +161,13 @@
      * @hide
      */
     public DisplayCutout inset(int insetLeft, int insetTop, int insetRight, int insetBottom) {
-        if (mBoundingRect.isEmpty()
+        if (mBounds.isEmpty()
                 || insetLeft == 0 && insetTop == 0 && insetRight == 0 && insetBottom == 0) {
             return this;
         }
 
         Rect safeInsets = new Rect(mSafeInsets);
-        Rect boundingRect = new Rect(mBoundingRect);
-        ArrayList<Point> boundingPolygon = new ArrayList<>();
-        getBoundingPolygon(boundingPolygon);
+        Region bounds = Region.obtain(mBounds);
 
         // Note: it's not really well defined what happens when the inset is negative, because we
         // don't know if the safe inset needs to expand in general.
@@ -197,10 +184,9 @@
             safeInsets.right = atLeastZero(safeInsets.right - insetRight);
         }
 
-        boundingRect.offset(-insetLeft, -insetTop);
-        offset(boundingPolygon, -insetLeft, -insetTop);
+        bounds.translate(-insetLeft, -insetTop);
 
-        return new DisplayCutout(safeInsets, boundingRect, boundingPolygon);
+        return new DisplayCutout(safeInsets, bounds);
     }
 
     /**
@@ -210,20 +196,17 @@
      * @hide
      */
     public DisplayCutout calculateRelativeTo(Rect frame) {
-        if (mBoundingRect.isEmpty() || !Rect.intersects(frame, mBoundingRect)) {
+        if (mBounds.isEmpty() || !Rect.intersects(frame, mBounds.getBounds())) {
             return NO_CUTOUT;
         }
 
-        Rect boundingRect = new Rect(mBoundingRect);
-        ArrayList<Point> boundingPolygon = new ArrayList<>();
-        getBoundingPolygon(boundingPolygon);
-
-        return DisplayCutout.calculateRelativeTo(frame, boundingRect, boundingPolygon);
+        return DisplayCutout.calculateRelativeTo(frame, Region.obtain(mBounds));
     }
 
-    private static DisplayCutout calculateRelativeTo(Rect frame, Rect boundingRect,
-            ArrayList<Point> boundingPolygon) {
+    private static DisplayCutout calculateRelativeTo(Rect frame, Region bounds) {
+        Rect boundingRect = bounds.getBounds();
         Rect safeRect = new Rect();
+
         int bestArea = 0;
         int bestVariant = 0;
         for (int variant = ROTATION_0; variant <= ROTATION_270; variant++) {
@@ -247,10 +230,9 @@
                     Math.max(0, frame.bottom - safeRect.bottom));
         }
 
-        boundingRect.offset(-frame.left, -frame.top);
-        offset(boundingPolygon, -frame.left, -frame.top);
+        bounds.translate(-frame.left, -frame.top);
 
-        return new DisplayCutout(safeRect, boundingRect, boundingPolygon);
+        return new DisplayCutout(safeRect, bounds);
     }
 
     private static int calculateInsetVariantArea(Rect frame, Rect boundingRect, int variant,
@@ -277,11 +259,6 @@
         return value < 0 ? 0 : value;
     }
 
-    private static void offset(ArrayList<Point> points, int dx, int dy) {
-        for (int i = 0; i < points.size(); i++) {
-            points.get(i).offset(dx, dy);
-        }
-    }
 
     /**
      * Creates an instance from a bounding polygon.
@@ -289,20 +266,28 @@
      * @hide
      */
     public static DisplayCutout fromBoundingPolygon(List<Point> points) {
-        Rect boundingRect = new Rect(Integer.MAX_VALUE, Integer.MAX_VALUE,
-                Integer.MIN_VALUE, Integer.MIN_VALUE);
-        ArrayList<Point> boundingPolygon = new ArrayList<>();
+        Region bounds = Region.obtain();
+        Path path = new Path();
 
+        path.reset();
         for (int i = 0; i < points.size(); i++) {
             Point point = points.get(i);
-            boundingRect.left = Math.min(boundingRect.left, point.x);
-            boundingRect.right = Math.max(boundingRect.right, point.x);
-            boundingRect.top = Math.min(boundingRect.top, point.y);
-            boundingRect.bottom = Math.max(boundingRect.bottom, point.y);
-            boundingPolygon.add(new Point(point));
+            if (i == 0) {
+                path.moveTo(point.x, point.y);
+            } else {
+                path.lineTo(point.x, point.y);
+            }
         }
+        path.close();
 
-        return new DisplayCutout(ZERO_RECT, boundingRect, boundingPolygon);
+        RectF clipRect = new RectF();
+        path.computeBounds(clipRect, false /* unused */);
+        Region clipRegion = Region.obtain();
+        clipRegion.set((int) clipRect.left, (int) clipRect.top,
+                (int) clipRect.right, (int) clipRect.bottom);
+
+        bounds.setPath(path, clipRegion);
+        return new DisplayCutout(ZERO_RECT, bounds);
     }
 
     /**
@@ -336,8 +321,7 @@
             } else {
                 out.writeInt(1);
                 out.writeTypedObject(mInner.mSafeInsets, flags);
-                out.writeTypedObject(mInner.mBoundingRect, flags);
-                out.writeTypedList(mInner.mBoundingPolygon, flags);
+                out.writeTypedObject(mInner.mBounds, flags);
             }
         }
 
@@ -368,13 +352,10 @@
                 return NO_CUTOUT;
             }
 
-            ArrayList<Point> boundingPolygon = new ArrayList<>();
-
             Rect safeInsets = in.readTypedObject(Rect.CREATOR);
-            Rect boundingRect = in.readTypedObject(Rect.CREATOR);
-            in.readTypedList(boundingPolygon, Point.CREATOR);
+            Region bounds = in.readTypedObject(Region.CREATOR);
 
-            return new DisplayCutout(safeInsets, boundingRect, boundingPolygon);
+            return new DisplayCutout(safeInsets, bounds);
         }
 
         public DisplayCutout get() {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 02beee0..cc63a62 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -26890,7 +26890,7 @@
         if (mAttachInfo == null || mTooltipInfo == null) {
             return false;
         }
-        if ((mViewFlags & ENABLED_MASK) != ENABLED) {
+        if (fromLongClick && (mViewFlags & ENABLED_MASK) != ENABLED) {
             return false;
         }
         if (TextUtils.isEmpty(mTooltipInfo.mTooltipText)) {
@@ -26938,7 +26938,7 @@
         }
         switch(event.getAction()) {
             case MotionEvent.ACTION_HOVER_MOVE:
-                if ((mViewFlags & TOOLTIP) != TOOLTIP || (mViewFlags & ENABLED_MASK) != ENABLED) {
+                if ((mViewFlags & TOOLTIP) != TOOLTIP) {
                     break;
                 }
                 if (!mTooltipInfo.mTooltipFromLongClick && mTooltipInfo.updateAnchorPos(event)) {
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 2c82ac4..6c5091c 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1602,7 +1602,7 @@
         if (!layoutInCutout) {
             // Window is either not laid out in cutout or the status bar inset takes care of
             // clearing the cutout, so we don't need to dispatch the cutout to the hierarchy.
-            insets = insets.consumeCutout();
+            insets = insets.consumeDisplayCutout();
         }
         host.dispatchApplyWindowInsets(insets);
     }
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index df124ac..e5cbe96 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -17,7 +17,7 @@
 
 package android.view;
 
-import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.graphics.Rect;
 
 /**
@@ -49,7 +49,7 @@
     private boolean mSystemWindowInsetsConsumed = false;
     private boolean mWindowDecorInsetsConsumed = false;
     private boolean mStableInsetsConsumed = false;
-    private boolean mCutoutConsumed = false;
+    private boolean mDisplayCutoutConsumed = false;
 
     private static final Rect EMPTY_RECT = new Rect(0, 0, 0, 0);
 
@@ -80,8 +80,9 @@
         mIsRound = isRound;
         mAlwaysConsumeNavBar = alwaysConsumeNavBar;
 
-        mCutoutConsumed = displayCutout == null;
-        mDisplayCutout = mCutoutConsumed ? DisplayCutout.NO_CUTOUT : displayCutout;
+        mDisplayCutoutConsumed = displayCutout == null;
+        mDisplayCutout = (mDisplayCutoutConsumed || displayCutout.isEmpty())
+                ? null : displayCutout;
     }
 
     /**
@@ -99,7 +100,7 @@
         mIsRound = src.mIsRound;
         mAlwaysConsumeNavBar = src.mAlwaysConsumeNavBar;
         mDisplayCutout = src.mDisplayCutout;
-        mCutoutConsumed = src.mCutoutConsumed;
+        mDisplayCutoutConsumed = src.mDisplayCutoutConsumed;
     }
 
     /** @hide */
@@ -269,15 +270,16 @@
      */
     public boolean hasInsets() {
         return hasSystemWindowInsets() || hasWindowDecorInsets() || hasStableInsets()
-                || mDisplayCutout.hasCutout();
+                || mDisplayCutout != null;
     }
 
     /**
-     * @return the display cutout
+     * Returns the display cutout if there is one.
+     *
+     * @return the display cutout or null if there is none
      * @see DisplayCutout
-     * @hide pending API
      */
-    @NonNull
+    @Nullable
     public DisplayCutout getDisplayCutout() {
         return mDisplayCutout;
     }
@@ -286,12 +288,11 @@
      * Returns a copy of this WindowInsets with the cutout fully consumed.
      *
      * @return A modified copy of this WindowInsets
-     * @hide pending API
      */
-    public WindowInsets consumeCutout() {
+    public WindowInsets consumeDisplayCutout() {
         final WindowInsets result = new WindowInsets(this);
-        result.mDisplayCutout = DisplayCutout.NO_CUTOUT;
-        result.mCutoutConsumed = true;
+        result.mDisplayCutout = null;
+        result.mDisplayCutoutConsumed = true;
         return result;
     }
 
@@ -311,7 +312,7 @@
      */
     public boolean isConsumed() {
         return mSystemWindowInsetsConsumed && mWindowDecorInsetsConsumed && mStableInsetsConsumed
-                && mCutoutConsumed;
+                && mDisplayCutoutConsumed;
     }
 
     /**
@@ -530,7 +531,7 @@
         return "WindowInsets{systemWindowInsets=" + mSystemWindowInsets
                 + " windowDecorInsets=" + mWindowDecorInsets
                 + " stableInsets=" + mStableInsets
-                + (mDisplayCutout.hasCutout() ? " cutout=" + mDisplayCutout : "")
+                + (mDisplayCutout != null ? " cutout=" + mDisplayCutout : "")
                 + (isRound() ? " round" : "")
                 + "}";
     }
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 012e864..cbe012a 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -1286,7 +1286,6 @@
          * The window must correctly position its contents to take the display cutout into account.
          *
          * @see DisplayCutout
-         * @hide for now
          */
         public static final long FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA = 0x00000001;
 
@@ -1294,7 +1293,6 @@
          * Various behavioral options/flags.  Default is none.
          *
          * @see #FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA
-         * @hide for now
          */
         @Flags2 public long flags2;
 
@@ -2249,6 +2247,7 @@
             out.writeInt(y);
             out.writeInt(type);
             out.writeInt(flags);
+            out.writeLong(flags2);
             out.writeInt(privateFlags);
             out.writeInt(softInputMode);
             out.writeInt(gravity);
@@ -2304,6 +2303,7 @@
             y = in.readInt();
             type = in.readInt();
             flags = in.readInt();
+            flags2 = in.readLong();
             privateFlags = in.readInt();
             softInputMode = in.readInt();
             gravity = in.readInt();
@@ -2436,6 +2436,10 @@
                 flags = o.flags;
                 changes |= FLAGS_CHANGED;
             }
+            if (flags2 != o.flags2) {
+                flags2 = o.flags2;
+                changes |= FLAGS_CHANGED;
+            }
             if (privateFlags != o.privateFlags) {
                 privateFlags = o.privateFlags;
                 changes |= PRIVATE_FLAGS_CHANGED;
@@ -2689,6 +2693,11 @@
             sb.append(System.lineSeparator());
             sb.append(prefix).append("  fl=").append(
                     ViewDebug.flagsToString(LayoutParams.class, "flags", flags));
+            if (flags2 != 0) {
+                sb.append(System.lineSeparator());
+                // TODO(roosa): add a long overload for ViewDebug.flagsToString.
+                sb.append(prefix).append("  fl2=0x").append(Long.toHexString(flags2));
+            }
             if (privateFlags != 0) {
                 sb.append(System.lineSeparator());
                 sb.append(prefix).append("  pfl=").append(ViewDebug.flagsToString(
diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
index 9c2f6bb..28ef697 100644
--- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java
@@ -2325,7 +2325,7 @@
     /**
      * Returns whether the node is explicitly marked as a focusable unit by a screen reader. Note
      * that {@code false} indicates that it is not explicitly marked, not that the node is not
-     * a focusable unit. Screen readers should generally used other signals, such as
+     * a focusable unit. Screen readers should generally use other signals, such as
      * {@link #isFocusable()}, or the presence of text in a node, to determine what should receive
      * focus.
      *
diff --git a/core/java/android/view/accessibility/AccessibilityWindowInfo.java b/core/java/android/view/accessibility/AccessibilityWindowInfo.java
index f11767d..ef1a3f3 100644
--- a/core/java/android/view/accessibility/AccessibilityWindowInfo.java
+++ b/core/java/android/view/accessibility/AccessibilityWindowInfo.java
@@ -87,6 +87,7 @@
     private static final int BOOLEAN_PROPERTY_ACTIVE = 1 << 0;
     private static final int BOOLEAN_PROPERTY_FOCUSED = 1 << 1;
     private static final int BOOLEAN_PROPERTY_ACCESSIBILITY_FOCUSED = 1 << 2;
+    private static final int BOOLEAN_PROPERTY_PICTURE_IN_PICTURE = 1 << 3;
 
     // Housekeeping.
     private static final int MAX_POOL_SIZE = 10;
@@ -103,8 +104,7 @@
     private final Rect mBoundsInScreen = new Rect();
     private LongArray mChildIds;
     private CharSequence mTitle;
-    private int mAnchorId = UNDEFINED_WINDOW_ID;
-    private boolean mInPictureInPicture;
+    private long mAnchorId = AccessibilityNodeInfo.UNDEFINED_NODE_ID;
 
     private int mConnectionId = UNDEFINED_WINDOW_ID;
 
@@ -202,7 +202,7 @@
      *
      * @hide
      */
-    public void setAnchorId(int anchorId) {
+    public void setAnchorId(long anchorId) {
         mAnchorId = anchorId;
     }
 
@@ -212,7 +212,8 @@
      * @return The anchor node, or {@code null} if none exists.
      */
     public AccessibilityNodeInfo getAnchor() {
-        if ((mConnectionId == UNDEFINED_WINDOW_ID) || (mAnchorId == UNDEFINED_WINDOW_ID)
+        if ((mConnectionId == UNDEFINED_WINDOW_ID)
+                || (mAnchorId == AccessibilityNodeInfo.UNDEFINED_NODE_ID)
                 || (mParentId == UNDEFINED_WINDOW_ID)) {
             return null;
         }
@@ -224,17 +225,7 @@
 
     /** @hide */
     public void setPictureInPicture(boolean pictureInPicture) {
-        mInPictureInPicture = pictureInPicture;
-    }
-
-    /**
-     * Check if the window is in picture-in-picture mode.
-     *
-     * @return {@code true} if the window is in picture-in-picture mode, {@code false} otherwise.
-     * @removed
-     */
-    public boolean inPictureInPicture() {
-        return isInPictureInPictureMode();
+        setBooleanProperty(BOOLEAN_PROPERTY_PICTURE_IN_PICTURE, pictureInPicture);
     }
 
     /**
@@ -243,7 +234,7 @@
      * @return {@code true} if the window is in picture-in-picture mode, {@code false} otherwise.
      */
     public boolean isInPictureInPictureMode() {
-        return mInPictureInPicture;
+        return getBooleanProperty(BOOLEAN_PROPERTY_PICTURE_IN_PICTURE);
     }
 
     /**
@@ -463,7 +454,6 @@
         infoClone.mBoundsInScreen.set(info.mBoundsInScreen);
         infoClone.mTitle = info.mTitle;
         infoClone.mAnchorId = info.mAnchorId;
-        infoClone.mInPictureInPicture = info.mInPictureInPicture;
 
         if (info.mChildIds != null && info.mChildIds.size() > 0) {
             if (infoClone.mChildIds == null) {
@@ -520,8 +510,7 @@
         parcel.writeInt(mParentId);
         mBoundsInScreen.writeToParcel(parcel, flags);
         parcel.writeCharSequence(mTitle);
-        parcel.writeInt(mAnchorId);
-        parcel.writeInt(mInPictureInPicture ? 1 : 0);
+        parcel.writeLong(mAnchorId);
 
         final LongArray childIds = mChildIds;
         if (childIds == null) {
@@ -545,8 +534,7 @@
         mParentId = parcel.readInt();
         mBoundsInScreen.readFromParcel(parcel);
         mTitle = parcel.readCharSequence();
-        mAnchorId = parcel.readInt();
-        mInPictureInPicture = parcel.readInt() == 1;
+        mAnchorId = parcel.readLong();
 
         final int childCount = parcel.readInt();
         if (childCount > 0) {
@@ -593,7 +581,7 @@
         builder.append(", bounds=").append(mBoundsInScreen);
         builder.append(", focused=").append(isFocused());
         builder.append(", active=").append(isActive());
-        builder.append(", pictureInPicture=").append(inPictureInPicture());
+        builder.append(", pictureInPicture=").append(isInPictureInPictureMode());
         if (DEBUG) {
             builder.append(", parent=").append(mParentId);
             builder.append(", children=[");
@@ -611,7 +599,8 @@
             builder.append(']');
         } else {
             builder.append(", hasParent=").append(mParentId != UNDEFINED_WINDOW_ID);
-            builder.append(", isAnchored=").append(mAnchorId != UNDEFINED_WINDOW_ID);
+            builder.append(", isAnchored=")
+                    .append(mAnchorId != AccessibilityNodeInfo.UNDEFINED_NODE_ID);
             builder.append(", hasChildren=").append(mChildIds != null
                     && mChildIds.size() > 0);
         }
@@ -633,8 +622,7 @@
             mChildIds.clear();
         }
         mConnectionId = UNDEFINED_WINDOW_ID;
-        mAnchorId = UNDEFINED_WINDOW_ID;
-        mInPictureInPicture = false;
+        mAnchorId = AccessibilityNodeInfo.UNDEFINED_NODE_ID;
         mTitle = null;
     }
 
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index d0dbff0e..2697454 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -530,10 +530,13 @@
      * @return whether autofill is enabled for the current user.
      */
     public boolean isEnabled() {
-        if (!hasAutofillFeature() || isDisabledByService()) {
+        if (!hasAutofillFeature()) {
             return false;
         }
         synchronized (mLock) {
+            if (isDisabledByServiceLocked()) {
+                return false;
+            }
             ensureServiceClientAddedIfNeededLocked();
             return mEnabled;
         }
@@ -605,19 +608,16 @@
     }
 
     private boolean shouldIgnoreViewEnteredLocked(@NonNull View view, int flags) {
-        if (isDisabledByService()) {
+        if (isDisabledByServiceLocked()) {
             if (sVerbose) {
                 Log.v(TAG, "ignoring notifyViewEntered(flags=" + flags + ", view=" + view
                         + ") on state " + getStateAsStringLocked());
             }
             return true;
         }
-        if (mState == STATE_FINISHED && (flags & FLAG_MANUAL_REQUEST) == 0) {
-            if (sVerbose) {
-                Log.v(TAG, "ignoring notifyViewEntered(flags=" + flags + ", view=" + view
-                        + ") on state " + getStateAsStringLocked());
-            }
-            return true;
+        if (sVerbose && isFinishedLocked()) {
+            Log.v(TAG, "not ignoring notifyViewEntered(flags=" + flags + ", view=" + view
+                    + ") on state " + getStateAsStringLocked());
         }
         return false;
     }
@@ -1009,6 +1009,21 @@
     }
 
     /**
+     * Returns the component name of the {@link AutofillService} that is enabled for the current
+     * user.
+     */
+    @Nullable
+    public ComponentName getAutofillServiceComponentName() {
+        if (mService == null) return null;
+
+        try {
+            return mService.getAutofillServiceComponentName();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Gets the user data used for
      * <a href="AutofillService.html#FieldClassification">field classification</a>.
      *
@@ -1139,10 +1154,10 @@
             Log.v(TAG, "startSessionLocked(): id=" + id + ", bounds=" + bounds + ", value=" + value
                     + ", flags=" + flags + ", state=" + getStateAsStringLocked());
         }
-        if (mState != STATE_UNKNOWN && (flags & FLAG_MANUAL_REQUEST) == 0) {
+        if (mState != STATE_UNKNOWN && !isFinishedLocked() && (flags & FLAG_MANUAL_REQUEST) == 0) {
             if (sVerbose) {
                 Log.v(TAG, "not automatically starting session for " + id
-                        + " on state " + getStateAsStringLocked());
+                        + " on state " + getStateAsStringLocked() + " and flags " + flags);
             }
             return;
         }
@@ -1744,10 +1759,14 @@
         return mState == STATE_ACTIVE;
     }
 
-    private boolean isDisabledByService() {
+    private boolean isDisabledByServiceLocked() {
         return mState == STATE_DISABLED_BY_SERVICE;
     }
 
+    private boolean isFinishedLocked() {
+        return mState == STATE_FINISHED;
+    }
+
     private void post(Runnable runnable) {
         final AutofillClient client = getClient();
         if (client == null) {
diff --git a/core/java/android/view/autofill/IAutoFillManager.aidl b/core/java/android/view/autofill/IAutoFillManager.aidl
index f49aa5b..38bb311 100644
--- a/core/java/android/view/autofill/IAutoFillManager.aidl
+++ b/core/java/android/view/autofill/IAutoFillManager.aidl
@@ -57,4 +57,5 @@
     UserData getUserData();
     void setUserData(in UserData userData);
     boolean isFieldClassificationEnabled();
+    ComponentName getAutofillServiceComponentName();
 }
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 9ac443b..1e17f34 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -4867,6 +4867,10 @@
      * Sets line spacing for this TextView.  Each line other than the last line will have its height
      * multiplied by {@code mult} and have {@code add} added to it.
      *
+     * @param add The value in pixels that should be added to each line other than the last line.
+     *            This will be applied after the multiplier
+     * @param mult The value by which each line height other than the last line will be multiplied
+     *             by
      *
      * @attr ref android.R.styleable#TextView_lineSpacingExtra
      * @attr ref android.R.styleable#TextView_lineSpacingMultiplier
diff --git a/core/java/com/android/internal/app/UnlaunchableAppActivity.java b/core/java/com/android/internal/app/UnlaunchableAppActivity.java
index 8016a65..2eadaf3 100644
--- a/core/java/com/android/internal/app/UnlaunchableAppActivity.java
+++ b/core/java/com/android/internal/app/UnlaunchableAppActivity.java
@@ -111,7 +111,7 @@
     @Override
     public void onClick(DialogInterface dialog, int which) {
         if (mReason == UNLAUNCHABLE_REASON_QUIET_MODE && which == DialogInterface.BUTTON_POSITIVE) {
-            UserManager.get(this).trySetQuietModeDisabled(mUserId, mTarget);
+            UserManager.get(this).trySetQuietModeEnabled(false, UserHandle.of(mUserId), mTarget);
         }
     }
 
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 6510a70..c1e5af8 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -51,6 +51,7 @@
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
 import android.util.ArrayMap;
+import android.util.IntArray;
 import android.util.Log;
 import android.util.LogWriter;
 import android.util.LongSparseArray;
@@ -120,7 +121,7 @@
     private static final int MAGIC = 0xBA757475; // 'BATSTATS'
 
     // Current on-disk Parcel version
-    private static final int VERSION = 171 + (USE_OLD_HISTORY ? 1000 : 0);
+    private static final int VERSION = 172 + (USE_OLD_HISTORY ? 1000 : 0);
 
     // Maximum number of items we will record in the history.
     private static final int MAX_HISTORY_ITEMS;
@@ -188,6 +189,8 @@
     @VisibleForTesting
     protected KernelUidCpuFreqTimeReader mKernelUidCpuFreqTimeReader =
             new KernelUidCpuFreqTimeReader();
+    @VisibleForTesting
+    protected KernelSingleUidTimeReader mKernelSingleUidTimeReader;
 
     private final KernelMemoryBandwidthStats mKernelMemoryBandwidthStats
             = new KernelMemoryBandwidthStats();
@@ -196,6 +199,18 @@
         return mKernelMemoryStats;
     }
 
+    @GuardedBy("this")
+    public boolean mPerProcStateCpuTimesAvailable = true;
+
+    /**
+     * Uids for which per-procstate cpu times need to be updated.
+     *
+     * Contains uid -> procState mappings.
+     */
+    @GuardedBy("this")
+    @VisibleForTesting
+    protected final SparseIntArray mPendingUids = new SparseIntArray();
+
     /** Container for Resource Power Manager stats. Updated by updateRpmStatsLocked. */
     private final RpmStats mTmpRpmStats = new RpmStats();
     /** The soonest the RPM stats can be updated after it was last updated. */
@@ -268,6 +283,138 @@
         }
     }
 
+    /**
+     * Update per-freq cpu times for all the uids in {@link #mPendingUids}.
+     */
+    public void updateProcStateCpuTimes() {
+        final SparseIntArray uidStates;
+        synchronized (BatteryStatsImpl.this) {
+            if(!initKernelSingleUidTimeReaderLocked()) {
+                return;
+            }
+
+            if (mPendingUids.size() == 0) {
+                return;
+            }
+            uidStates = mPendingUids.clone();
+            mPendingUids.clear();
+        }
+        for (int i = uidStates.size() - 1; i >= 0; --i) {
+            final int uid = uidStates.keyAt(i);
+            final int procState = uidStates.valueAt(i);
+            final int[] isolatedUids;
+            final Uid u;
+            final boolean onBattery;
+            synchronized (BatteryStatsImpl.this) {
+                // It's possible that uid no longer exists and any internal references have
+                // already been deleted, so using {@link #getAvailableUidStatsLocked} to avoid
+                // creating an UidStats object if it doesn't already exist.
+                u = getAvailableUidStatsLocked(uid);
+                if (u == null) {
+                    continue;
+                }
+                if (u.mChildUids == null) {
+                    isolatedUids = null;
+                } else {
+                    isolatedUids = u.mChildUids.toArray();
+                    for (int j = isolatedUids.length - 1; j >= 0; --j) {
+                        isolatedUids[j] = u.mChildUids.get(j);
+                    }
+                }
+                onBattery = mOnBatteryInternal;
+            }
+            long[] cpuTimesMs = mKernelSingleUidTimeReader.readDeltaMs(uid);
+            if (isolatedUids != null) {
+                for (int j = isolatedUids.length - 1; j >= 0; --j) {
+                    cpuTimesMs = addCpuTimes(cpuTimesMs,
+                            mKernelSingleUidTimeReader.readDeltaMs(isolatedUids[j]));
+                }
+            }
+            if (onBattery && cpuTimesMs != null) {
+                synchronized (BatteryStatsImpl.this) {
+                    u.addProcStateTimesMs(procState, cpuTimesMs);
+                    u.addProcStateScreenOffTimesMs(procState, cpuTimesMs);
+                }
+            }
+        }
+    }
+
+    /**
+     * When the battery/screen state changes, we don't attribute the cpu times to any process
+     * but we still need to snapshots of all uids to get correct deltas later on. Since we
+     * already read this data for updating per-freq cpu times, we can use the same data for
+     * per-procstate cpu times.
+     */
+    public void copyFromAllUidsCpuTimes() {
+        synchronized (BatteryStatsImpl.this) {
+            if(!initKernelSingleUidTimeReaderLocked()) {
+                return;
+            }
+
+            final SparseArray<long[]> allUidCpuFreqTimesMs =
+                    mKernelUidCpuFreqTimeReader.getAllUidCpuFreqTimeMs();
+            for (int i = allUidCpuFreqTimesMs.size() - 1; i >= 0; --i) {
+                final int uid = allUidCpuFreqTimesMs.keyAt(i);
+                final Uid u = getAvailableUidStatsLocked(mapUid(uid));
+                if (u == null) {
+                    continue;
+                }
+                final long[] cpuTimesMs = allUidCpuFreqTimesMs.valueAt(i);
+                if (cpuTimesMs == null) {
+                    continue;
+                }
+                final long[] deltaTimesMs = mKernelSingleUidTimeReader.computeDelta(
+                        uid, cpuTimesMs.clone());
+                if (mOnBatteryInternal && deltaTimesMs != null) {
+                    final int procState;
+                    final int idx = mPendingUids.indexOfKey(uid);
+                    if (idx >= 0) {
+                        procState = mPendingUids.valueAt(idx);
+                        mPendingUids.removeAt(idx);
+                    } else {
+                        procState = u.mProcessState;
+                    }
+                    if (procState >= 0 && procState < Uid.NUM_PROCESS_STATE) {
+                        u.addProcStateTimesMs(procState, deltaTimesMs);
+                        u.addProcStateScreenOffTimesMs(procState, deltaTimesMs);
+                    }
+                }
+            }
+        }
+    }
+
+    @VisibleForTesting
+    public long[] addCpuTimes(long[] timesA, long[] timesB) {
+        if (timesA != null && timesB != null) {
+            for (int i = timesA.length - 1; i >= 0; --i) {
+                timesA[i] += timesB[i];
+            }
+            return timesA;
+        }
+        return timesA == null ? (timesB == null ? null : timesB) : timesA;
+    }
+
+    @GuardedBy("this")
+    private boolean initKernelSingleUidTimeReaderLocked() {
+        if (mKernelSingleUidTimeReader == null) {
+            if (mPowerProfile == null) {
+                return false;
+            }
+            if (mCpuFreqs == null) {
+                mCpuFreqs = mKernelUidCpuFreqTimeReader.readFreqs(mPowerProfile);
+            }
+            if (mCpuFreqs != null) {
+                mKernelSingleUidTimeReader = new KernelSingleUidTimeReader(mCpuFreqs.length);
+            } else {
+                mPerProcStateCpuTimesAvailable = mKernelUidCpuFreqTimeReader.allUidTimesAvailable();
+                return false;
+            }
+        }
+        mPerProcStateCpuTimesAvailable = mKernelUidCpuFreqTimeReader.allUidTimesAvailable()
+                && mKernelSingleUidTimeReader.singleUidCpuTimesAvailable();
+        return true;
+    }
+
     public interface Clocks {
         public long elapsedRealtime();
         public long uptimeMillis();
@@ -293,9 +440,11 @@
 
         Future<?> scheduleSync(String reason, int flags);
         Future<?> scheduleCpuSyncDueToRemovedUid(int uid);
+        Future<?> scheduleReadProcStateCpuTimes();
+        Future<?> scheduleCopyFromAllUidsCpuTimes();
     }
 
-    public final MyHandler mHandler;
+    public Handler mHandler;
     private ExternalStatsSync mExternalSync = null;
     @VisibleForTesting
     protected UserInfoProvider mUserInfoProvider = null;
@@ -3623,6 +3772,7 @@
                         + " and battery is " + (unplugged ? "on" : "off"));
             }
             updateCpuTimeLocked();
+            mExternalSync.scheduleCopyFromAllUidsCpuTimes();
 
             mOnBatteryTimeBase.setRunning(unplugged, uptime, realtime);
             if (updateOnBatteryTimeBase) {
@@ -3652,6 +3802,8 @@
     public void addIsolatedUidLocked(int isolatedUid, int appUid) {
         mIsolatedUids.put(isolatedUid, appUid);
         StatsLog.write(StatsLog.ISOLATED_UID_CHANGED, appUid, isolatedUid, 1);
+        final Uid u = getUidStatsLocked(appUid);
+        u.addIsolatedUid(isolatedUid);
     }
 
     /**
@@ -3672,11 +3824,17 @@
      * @see #scheduleRemoveIsolatedUidLocked(int, int)
      */
     public void removeIsolatedUidLocked(int isolatedUid) {
-      StatsLog.write(
-          StatsLog.ISOLATED_UID_CHANGED, mIsolatedUids.get(isolatedUid, -1), isolatedUid, 0);
-      mIsolatedUids.delete(isolatedUid);
-      mKernelUidCpuTimeReader.removeUid(isolatedUid);
-      mKernelUidCpuFreqTimeReader.removeUid(isolatedUid);
+        StatsLog.write(
+            StatsLog.ISOLATED_UID_CHANGED, mIsolatedUids.get(isolatedUid, -1), isolatedUid, 0);
+        final int idx = mIsolatedUids.indexOfKey(isolatedUid);
+        if (idx >= 0) {
+            final int ownerUid = mIsolatedUids.valueAt(idx);
+            final Uid u = getUidStatsLocked(ownerUid);
+            u.removeIsolatedUid(isolatedUid);
+            mIsolatedUids.removeAt(idx);
+        }
+        mKernelUidCpuTimeReader.removeUid(isolatedUid);
+        mKernelUidCpuFreqTimeReader.removeUid(isolatedUid);
     }
 
     public int mapUid(int uid) {
@@ -5899,6 +6057,11 @@
         LongSamplingCounterArray mCpuFreqTimeMs;
         LongSamplingCounterArray mScreenOffCpuFreqTimeMs;
 
+        LongSamplingCounterArray[] mProcStateTimeMs;
+        LongSamplingCounterArray[] mProcStateScreenOffTimeMs;
+
+        IntArray mChildUids;
+
         /**
          * The statistics we have collected for this uid's wake locks.
          */
@@ -5984,40 +6147,107 @@
             mProcessStateTimer = new StopwatchTimer[NUM_PROCESS_STATE];
         }
 
+        @VisibleForTesting
+        public void setProcessStateForTest(int procState) {
+            mProcessState = procState;
+        }
+
         @Override
         public long[] getCpuFreqTimes(int which) {
-            if (mCpuFreqTimeMs == null) {
+            return nullIfAllZeros(mCpuFreqTimeMs, which);
+        }
+
+        @Override
+        public long[] getScreenOffCpuFreqTimes(int which) {
+            return nullIfAllZeros(mScreenOffCpuFreqTimeMs, which);
+        }
+
+        @Override
+        public long[] getCpuFreqTimes(int which, int procState) {
+            if (which < 0 || which >= NUM_PROCESS_STATE) {
                 return null;
             }
-            final long[] cpuFreqTimes = mCpuFreqTimeMs.getCountsLocked(which);
-            if (cpuFreqTimes == null) {
+            if (mProcStateTimeMs == null) {
                 return null;
             }
-            // Return cpuFreqTimes only if atleast one of the elements in non-zero.
-            for (int i = 0; i < cpuFreqTimes.length; ++i) {
-                if (cpuFreqTimes[i] != 0) {
-                    return cpuFreqTimes;
+            if (!mBsi.mPerProcStateCpuTimesAvailable) {
+                mProcStateTimeMs = null;
+                return null;
+            }
+            return nullIfAllZeros(mProcStateTimeMs[procState], which);
+        }
+
+        @Override
+        public long[] getScreenOffCpuFreqTimes(int which, int procState) {
+            if (which < 0 || which >= NUM_PROCESS_STATE) {
+                return null;
+            }
+            if (mProcStateScreenOffTimeMs == null) {
+                return null;
+            }
+            if (!mBsi.mPerProcStateCpuTimesAvailable) {
+                mProcStateScreenOffTimeMs = null;
+                return null;
+            }
+            return nullIfAllZeros(mProcStateScreenOffTimeMs[procState], which);
+        }
+
+        public void addIsolatedUid(int isolatedUid) {
+            if (mChildUids == null) {
+                mChildUids = new IntArray();
+            } else if (mChildUids.indexOf(isolatedUid) >= 0) {
+                return;
+            }
+            mChildUids.add(isolatedUid);
+        }
+
+        public void removeIsolatedUid(int isolatedUid) {
+            final int idx = mChildUids == null ? -1 : mChildUids.indexOf(isolatedUid);
+            if (idx < 0) {
+                return;
+            }
+            mChildUids.remove(idx);
+        }
+
+        private long[] nullIfAllZeros(LongSamplingCounterArray cpuTimesMs, int which) {
+            if (cpuTimesMs == null) {
+                return null;
+            }
+            final long[] counts = cpuTimesMs.getCountsLocked(which);
+            if (counts == null) {
+                return null;
+            }
+            // Return counts only if at least one of the elements is non-zero.
+            for (int i = counts.length - 1; i >= 0; --i) {
+                if (counts[i] != 0) {
+                    return counts;
                 }
             }
             return null;
         }
 
-        @Override
-        public long[] getScreenOffCpuFreqTimes(int which) {
-            if (mScreenOffCpuFreqTimeMs == null) {
-                return null;
+        private void addProcStateTimesMs(int procState, long[] cpuTimesMs) {
+            if (mProcStateTimeMs == null) {
+                mProcStateTimeMs = new LongSamplingCounterArray[NUM_PROCESS_STATE];
             }
-            final long[] cpuFreqTimes = mScreenOffCpuFreqTimeMs.getCountsLocked(which);
-            if (cpuFreqTimes == null) {
-                return null;
+            if (mProcStateTimeMs[procState] == null
+                    || mProcStateTimeMs[procState].getSize() != cpuTimesMs.length) {
+                mProcStateTimeMs[procState] = new LongSamplingCounterArray(
+                        mBsi.mOnBatteryTimeBase);
             }
-            // Return cpuFreqTimes only if atleast one of the elements in non-zero.
-            for (int i = 0; i < cpuFreqTimes.length; ++i) {
-                if (cpuFreqTimes[i] != 0) {
-                    return cpuFreqTimes;
-                }
+            mProcStateTimeMs[procState].addCountLocked(cpuTimesMs);
+        }
+
+        private void addProcStateScreenOffTimesMs(int procState, long[] cpuTimesMs) {
+            if (mProcStateScreenOffTimeMs == null) {
+                mProcStateScreenOffTimeMs = new LongSamplingCounterArray[NUM_PROCESS_STATE];
             }
-            return null;
+            if (mProcStateScreenOffTimeMs[procState] == null
+                    || mProcStateScreenOffTimeMs[procState].getSize() != cpuTimesMs.length) {
+                mProcStateScreenOffTimeMs[procState] = new LongSamplingCounterArray(
+                        mBsi.mOnBatteryScreenOffTimeBase);
+            }
+            mProcStateScreenOffTimeMs[procState].addCountLocked(cpuTimesMs);
         }
 
         @Override
@@ -6999,6 +7229,21 @@
                 mScreenOffCpuFreqTimeMs.reset(false);
             }
 
+            if (mProcStateTimeMs != null) {
+                for (LongSamplingCounterArray counters : mProcStateTimeMs) {
+                    if (counters != null) {
+                        counters.reset(false);
+                    }
+                }
+            }
+            if (mProcStateScreenOffTimeMs != null) {
+                for (LongSamplingCounterArray counters : mProcStateScreenOffTimeMs) {
+                    if (counters != null) {
+                        counters.reset(false);
+                    }
+                }
+            }
+
             resetLongCounterIfNotNull(mMobileRadioApWakeupCount, false);
             resetLongCounterIfNotNull(mWifiRadioApWakeupCount, false);
 
@@ -7189,6 +7434,20 @@
                     mScreenOffCpuFreqTimeMs.detach();
                 }
 
+                if (mProcStateTimeMs != null) {
+                    for (LongSamplingCounterArray counters : mProcStateTimeMs) {
+                        if (counters != null) {
+                            counters.detach();
+                        }
+                    }
+                }
+                if (mProcStateScreenOffTimeMs != null) {
+                    for (LongSamplingCounterArray counters : mProcStateScreenOffTimeMs) {
+                        if (counters != null) {
+                            counters.detach();
+                        }
+                    }
+                }
                 detachLongCounterIfNotNull(mMobileRadioApWakeupCount);
                 detachLongCounterIfNotNull(mWifiRadioApWakeupCount);
             }
@@ -7449,6 +7708,22 @@
 
             LongSamplingCounterArray.writeToParcel(out, mCpuFreqTimeMs);
             LongSamplingCounterArray.writeToParcel(out, mScreenOffCpuFreqTimeMs);
+            if (mProcStateTimeMs != null) {
+                out.writeInt(mProcStateTimeMs.length);
+                for (LongSamplingCounterArray counters : mProcStateTimeMs) {
+                    LongSamplingCounterArray.writeToParcel(out, counters);
+                }
+            } else {
+                out.writeInt(0);
+            }
+            if (mProcStateScreenOffTimeMs != null) {
+                out.writeInt(mProcStateScreenOffTimeMs.length);
+                for (LongSamplingCounterArray counters : mProcStateScreenOffTimeMs) {
+                    LongSamplingCounterArray.writeToParcel(out, counters);
+                }
+            } else {
+                out.writeInt(0);
+            }
 
             if (mMobileRadioApWakeupCount != null) {
                 out.writeInt(1);
@@ -7750,6 +8025,27 @@
             mScreenOffCpuFreqTimeMs = LongSamplingCounterArray.readFromParcel(
                     in, mBsi.mOnBatteryScreenOffTimeBase);
 
+            int length = in.readInt();
+            if (length == NUM_PROCESS_STATE) {
+                mProcStateTimeMs = new LongSamplingCounterArray[length];
+                for (int procState = 0; procState < length; ++procState) {
+                    mProcStateTimeMs[procState] = LongSamplingCounterArray.readFromParcel(
+                            in, mBsi.mOnBatteryTimeBase);
+                }
+            } else {
+                mProcStateTimeMs = null;
+            }
+            length = in.readInt();
+            if (length == NUM_PROCESS_STATE) {
+                mProcStateScreenOffTimeMs = new LongSamplingCounterArray[length];
+                for (int procState = 0; procState < length; ++procState) {
+                    mProcStateScreenOffTimeMs[procState] = LongSamplingCounterArray.readFromParcel(
+                            in, mBsi.mOnBatteryScreenOffTimeBase);
+                }
+            } else {
+                mProcStateScreenOffTimeMs = null;
+            }
+
             if (in.readInt() != 0) {
                 mMobileRadioApWakeupCount = new LongSamplingCounter(mBsi.mOnBatteryTimeBase, in);
             } else {
@@ -8671,25 +8967,7 @@
             // Make special note of Foreground Services
             final boolean userAwareService =
                     (procState == ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
-            if (procState == ActivityManager.PROCESS_STATE_NONEXISTENT) {
-                uidRunningState = ActivityManager.PROCESS_STATE_NONEXISTENT;
-            } else if (procState == ActivityManager.PROCESS_STATE_TOP) {
-                uidRunningState = PROCESS_STATE_TOP;
-            } else if (procState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
-                // Persistent and other foreground states go here.
-                uidRunningState = PROCESS_STATE_FOREGROUND_SERVICE;
-            } else if (procState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) {
-                // Persistent and other foreground states go here.
-                uidRunningState = PROCESS_STATE_FOREGROUND;
-            } else if (procState <= ActivityManager.PROCESS_STATE_RECEIVER) {
-                uidRunningState = PROCESS_STATE_BACKGROUND;
-            } else if (procState <= ActivityManager.PROCESS_STATE_TOP_SLEEPING) {
-                uidRunningState = PROCESS_STATE_TOP_SLEEPING;
-            } else if (procState <= ActivityManager.PROCESS_STATE_HEAVY_WEIGHT) {
-                uidRunningState = PROCESS_STATE_HEAVY_WEIGHT;
-            } else {
-                uidRunningState = PROCESS_STATE_CACHED;
-            }
+            uidRunningState = BatteryStats.mapToInternalProcessState(procState);
 
             if (mProcessState == uidRunningState && userAwareService == mInForegroundService) {
                 return;
@@ -8701,6 +8979,18 @@
 
                 if (mProcessState != ActivityManager.PROCESS_STATE_NONEXISTENT) {
                     mProcessStateTimer[mProcessState].stopRunningLocked(elapsedRealtimeMs);
+
+                    if (mBsi.mPerProcStateCpuTimesAvailable) {
+                        if (mBsi.mPendingUids.size() == 0) {
+                            mBsi.mExternalSync.scheduleReadProcStateCpuTimes();
+                        }
+                        if (mBsi.mPendingUids.indexOfKey(mUid) < 0
+                                || ArrayUtils.contains(CRITICAL_PROC_STATES, mProcessState)) {
+                            mBsi.mPendingUids.put(mUid, mProcessState);
+                        }
+                    } else {
+                        mBsi.mPendingUids.clear();
+                    }
                 }
                 mProcessState = uidRunningState;
                 if (uidRunningState != ActivityManager.PROCESS_STATE_NONEXISTENT) {
@@ -11769,11 +12059,23 @@
         return u;
     }
 
+    /**
+     * Retrieve the statistics object for a particular uid. Returns null if the object is not
+     * available.
+     */
+    public Uid getAvailableUidStatsLocked(int uid) {
+        Uid u = mUidStats.get(uid);
+        return u;
+    }
+
     public void onCleanupUserLocked(int userId) {
         final int firstUidForUser = UserHandle.getUid(userId, 0);
         final int lastUidForUser = UserHandle.getUid(userId, UserHandle.PER_USER_RANGE - 1);
         mKernelUidCpuFreqTimeReader.removeUidsInRange(firstUidForUser, lastUidForUser);
         mKernelUidCpuTimeReader.removeUidsInRange(firstUidForUser, lastUidForUser);
+        if (mKernelSingleUidTimeReader != null) {
+            mKernelSingleUidTimeReader.removeUidsInRange(firstUidForUser, lastUidForUser);
+        }
     }
 
     public void onUserRemovedLocked(int userId) {
@@ -11792,6 +12094,9 @@
     public void removeUidStatsLocked(int uid) {
         mKernelUidCpuTimeReader.removeUid(uid);
         mKernelUidCpuFreqTimeReader.removeUid(uid);
+        if (mKernelSingleUidTimeReader != null) {
+            mKernelSingleUidTimeReader.removeUid(uid);
+        }
         mUidStats.remove(uid);
     }
 
@@ -12393,6 +12698,28 @@
                     in, mOnBatteryTimeBase);
             u.mScreenOffCpuFreqTimeMs = LongSamplingCounterArray.readSummaryFromParcelLocked(
                     in, mOnBatteryScreenOffTimeBase);
+            int length = in.readInt();
+            if (length == Uid.NUM_PROCESS_STATE) {
+                u.mProcStateTimeMs = new LongSamplingCounterArray[length];
+                for (int procState = 0; procState < length; ++procState) {
+                    u.mProcStateTimeMs[procState]
+                            = LongSamplingCounterArray.readSummaryFromParcelLocked(
+                                    in, mOnBatteryTimeBase);
+                }
+            } else {
+                u.mProcStateTimeMs = null;
+            }
+            length = in.readInt();
+            if (length == Uid.NUM_PROCESS_STATE) {
+                u.mProcStateScreenOffTimeMs = new LongSamplingCounterArray[length];
+                for (int procState = 0; procState < length; ++procState) {
+                    u.mProcStateScreenOffTimeMs[procState]
+                            = LongSamplingCounterArray.readSummaryFromParcelLocked(
+                                    in, mOnBatteryScreenOffTimeBase);
+                }
+            } else {
+                u.mProcStateScreenOffTimeMs = null;
+            }
 
             if (in.readInt() != 0) {
                 u.mMobileRadioApWakeupCount = new LongSamplingCounter(mOnBatteryTimeBase);
@@ -12846,6 +13173,23 @@
             LongSamplingCounterArray.writeSummaryToParcelLocked(out, u.mCpuFreqTimeMs);
             LongSamplingCounterArray.writeSummaryToParcelLocked(out, u.mScreenOffCpuFreqTimeMs);
 
+            if (u.mProcStateTimeMs != null) {
+                out.writeInt(u.mProcStateTimeMs.length);
+                for (LongSamplingCounterArray counters : u.mProcStateTimeMs) {
+                    LongSamplingCounterArray.writeSummaryToParcelLocked(out, counters);
+                }
+            } else {
+                out.writeInt(0);
+            }
+            if (u.mProcStateScreenOffTimeMs != null) {
+                out.writeInt(u.mProcStateScreenOffTimeMs.length);
+                for (LongSamplingCounterArray counters : u.mProcStateScreenOffTimeMs) {
+                    LongSamplingCounterArray.writeSummaryToParcelLocked(out, counters);
+                }
+            } else {
+                out.writeInt(0);
+            }
+
             if (u.mMobileRadioApWakeupCount != null) {
                 out.writeInt(1);
                 u.mMobileRadioApWakeupCount.writeSummaryFromParcelLocked(out);
diff --git a/core/java/com/android/internal/os/KernelSingleUidTimeReader.java b/core/java/com/android/internal/os/KernelSingleUidTimeReader.java
new file mode 100644
index 0000000..ca635a4
--- /dev/null
+++ b/core/java/com/android/internal/os/KernelSingleUidTimeReader.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.os;
+
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
+
+import android.annotation.NonNull;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.Arrays;
+
+@VisibleForTesting(visibility = PACKAGE)
+public class KernelSingleUidTimeReader {
+    private final String TAG = KernelUidCpuFreqTimeReader.class.getName();
+    private final boolean DBG = false;
+
+    private final String PROC_FILE_DIR = "/proc/uid/";
+    private final String PROC_FILE_NAME = "/time_in_state";
+
+    @VisibleForTesting
+    public static final int TOTAL_READ_ERROR_COUNT = 5;
+
+    @GuardedBy("this")
+    private final int mCpuFreqsCount;
+
+    @GuardedBy("this")
+    private final SparseArray<long[]> mLastUidCpuTimeMs = new SparseArray<>();
+
+    @GuardedBy("this")
+    private int mReadErrorCounter;
+    @GuardedBy("this")
+    private boolean mSingleUidCpuTimesAvailable = true;
+
+    private final Injector mInjector;
+
+    KernelSingleUidTimeReader(int cpuFreqsCount) {
+        this(cpuFreqsCount, new Injector());
+    }
+
+    public KernelSingleUidTimeReader(int cpuFreqsCount, Injector injector) {
+        mInjector = injector;
+        mCpuFreqsCount = cpuFreqsCount;
+        if (mCpuFreqsCount == 0) {
+            mSingleUidCpuTimesAvailable = false;
+        }
+    }
+
+    public boolean singleUidCpuTimesAvailable() {
+        return mSingleUidCpuTimesAvailable;
+    }
+
+    public long[] readDeltaMs(int uid) {
+        synchronized (this) {
+            if (!mSingleUidCpuTimesAvailable) {
+                return null;
+            }
+            // Read total cpu times from the proc file.
+            final String procFile = new StringBuilder(PROC_FILE_DIR)
+                    .append(uid)
+                    .append(PROC_FILE_NAME).toString();
+            final long[] cpuTimesMs = new long[mCpuFreqsCount];
+            try {
+                final byte[] data = mInjector.readData(procFile);
+                final ByteBuffer buffer = ByteBuffer.wrap(data);
+                buffer.order(ByteOrder.nativeOrder());
+                for (int i = 0; i < mCpuFreqsCount; ++i) {
+                    // Times read will be in units of 10ms
+                    cpuTimesMs[i] = buffer.getLong() * 10;
+                }
+            } catch (Exception e) {
+                if (++mReadErrorCounter >= TOTAL_READ_ERROR_COUNT) {
+                    mSingleUidCpuTimesAvailable = false;
+                }
+                if (DBG) Slog.e(TAG, "Some error occured while reading " + procFile, e);
+                return null;
+            }
+
+            return computeDelta(uid, cpuTimesMs);
+        }
+    }
+
+    /**
+     * Compute and return cpu times delta of an uid using previously read cpu times and
+     * {@param latestCpuTimesMs}.
+     *
+     * @return delta of cpu times if at least one of the cpu time at a freq is +ve, otherwise null.
+     */
+    public long[] computeDelta(int uid, @NonNull long[] latestCpuTimesMs) {
+        synchronized (this) {
+            if (!mSingleUidCpuTimesAvailable) {
+                return null;
+            }
+            // Subtract the last read cpu times to get deltas.
+            final long[] lastCpuTimesMs = mLastUidCpuTimeMs.get(uid);
+            final long[] deltaTimesMs = getDeltaLocked(lastCpuTimesMs, latestCpuTimesMs);
+            if (deltaTimesMs == null) {
+                if (DBG) Slog.e(TAG, "Malformed data read for uid=" + uid
+                        + "; last=" + Arrays.toString(lastCpuTimesMs)
+                        + "; latest=" + Arrays.toString(latestCpuTimesMs));
+                return null;
+            }
+            // If all elements are zero, return null to avoid unnecessary work on the caller side.
+            boolean hasNonZero = false;
+            for (int i = deltaTimesMs.length - 1; i >= 0; --i) {
+                if (deltaTimesMs[i] > 0) {
+                    hasNonZero = true;
+                    break;
+                }
+            }
+            if (hasNonZero) {
+                mLastUidCpuTimeMs.put(uid, latestCpuTimesMs);
+                return deltaTimesMs;
+            } else {
+                return null;
+            }
+        }
+    }
+
+    /**
+     * Returns null if the latest cpu times are not valid**, otherwise delta of
+     * {@param latestCpuTimesMs} and {@param lastCpuTimesMs}.
+     *
+     * **latest cpu times are considered valid if all the cpu times are +ve and
+     * greater than or equal to previously read cpu times.
+     */
+    @GuardedBy("this")
+    @VisibleForTesting(visibility = PACKAGE)
+    public long[] getDeltaLocked(long[] lastCpuTimesMs, @NonNull long[] latestCpuTimesMs) {
+        for (int i = latestCpuTimesMs.length - 1; i >= 0; --i) {
+            if (latestCpuTimesMs[i] < 0) {
+                return null;
+            }
+        }
+        if (lastCpuTimesMs == null) {
+            return latestCpuTimesMs;
+        }
+        final long[] deltaTimesMs = new long[latestCpuTimesMs.length];
+        for (int i = latestCpuTimesMs.length - 1; i >= 0; --i) {
+            deltaTimesMs[i] = latestCpuTimesMs[i] - lastCpuTimesMs[i];
+            if (deltaTimesMs[i] < 0) {
+                return null;
+            }
+        }
+        return deltaTimesMs;
+    }
+
+    public void removeUid(int uid) {
+        synchronized (this) {
+            mLastUidCpuTimeMs.delete(uid);
+        }
+    }
+
+    public void removeUidsInRange(int startUid, int endUid) {
+        if (endUid < startUid) {
+            return;
+        }
+        synchronized (this) {
+            mLastUidCpuTimeMs.put(startUid, null);
+            mLastUidCpuTimeMs.put(endUid, null);
+            final int startIdx = mLastUidCpuTimeMs.indexOfKey(startUid);
+            final int endIdx = mLastUidCpuTimeMs.indexOfKey(endUid);
+            mLastUidCpuTimeMs.removeAtRange(startIdx, endIdx - startIdx + 1);
+        }
+    }
+
+    @VisibleForTesting
+    public static class Injector {
+        public byte[] readData(String procFile) throws IOException {
+            return Files.readAllBytes(Paths.get(procFile));
+        }
+    }
+
+    @VisibleForTesting
+    public SparseArray<long[]> getLastUidCpuTimeMs() {
+        return mLastUidCpuTimeMs;
+    }
+
+    @VisibleForTesting
+    public void setSingleUidCpuTimesAvailable(boolean singleUidCpuTimesAvailable) {
+        mSingleUidCpuTimesAvailable = singleUidCpuTimesAvailable;
+    }
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java b/core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java
index a39997d..b8982cc 100644
--- a/core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java
+++ b/core/java/com/android/internal/os/KernelUidCpuFreqTimeReader.java
@@ -66,13 +66,21 @@
     // start reading) and if it is not available, we simply ignore further read requests.
     private static final int TOTAL_READ_ERROR_COUNT = 5;
     private int mReadErrorCounter;
-    private boolean mProcFileAvailable;
     private boolean mPerClusterTimesAvailable;
+    private boolean mAllUidTimesAvailable = true;
 
     public boolean perClusterTimesAvailable() {
         return mPerClusterTimesAvailable;
     }
 
+    public boolean allUidTimesAvailable() {
+        return mAllUidTimesAvailable;
+    }
+
+    public SparseArray<long[]> getAllUidCpuFreqTimeMs() {
+        return mLastUidCpuFreqTimeMs;
+    }
+
     public long[] readFreqs(@NonNull PowerProfile powerProfile) {
         checkNotNull(powerProfile);
 
@@ -80,15 +88,16 @@
             // No need to read cpu freqs more than once.
             return mCpuFreqs;
         }
-        if (!mProcFileAvailable && mReadErrorCounter >= TOTAL_READ_ERROR_COUNT) {
+        if (!mAllUidTimesAvailable) {
             return null;
         }
         final int oldMask = StrictMode.allowThreadDiskReadsMask();
         try (BufferedReader reader = new BufferedReader(new FileReader(UID_TIMES_PROC_FILE))) {
-            mProcFileAvailable = true;
             return readFreqs(reader, powerProfile);
         } catch (IOException e) {
-            mReadErrorCounter++;
+            if (++mReadErrorCounter >= TOTAL_READ_ERROR_COUNT) {
+                mAllUidTimesAvailable = false;
+            }
             Slog.e(TAG, "Failed to read " + UID_TIMES_PROC_FILE + ": " + e);
             return null;
         } finally {
@@ -107,7 +116,7 @@
     }
 
     public void readDelta(@Nullable Callback callback) {
-        if (!mProcFileAvailable) {
+        if (mCpuFreqs == null) {
             return;
         }
         final int oldMask = StrictMode.allowThreadDiskReadsMask();
diff --git a/core/java/com/android/internal/util/CollectionUtils.java b/core/java/com/android/internal/util/CollectionUtils.java
index f983de1..7985e57 100644
--- a/core/java/com/android/internal/util/CollectionUtils.java
+++ b/core/java/com/android/internal/util/CollectionUtils.java
@@ -290,11 +290,11 @@
             if (cur instanceof ArraySet) {
                 ArraySet<T> arraySet = (ArraySet<T>) cur;
                 for (int i = 0; i < size; i++) {
-                    action.accept(arraySet.valueAt(i));
+                    action.acceptOrThrow(arraySet.valueAt(i));
                 }
             } else {
                 for (T t : cur) {
-                    action.accept(t);
+                    action.acceptOrThrow(t);
                 }
             }
         } catch (Exception e) {
diff --git a/core/java/com/android/internal/util/FunctionalUtils.java b/core/java/com/android/internal/util/FunctionalUtils.java
index eb92c1c..82ac241 100644
--- a/core/java/com/android/internal/util/FunctionalUtils.java
+++ b/core/java/com/android/internal/util/FunctionalUtils.java
@@ -16,6 +16,9 @@
 
 package com.android.internal.util;
 
+import android.os.RemoteException;
+
+import java.util.function.Consumer;
 import java.util.function.Supplier;
 
 /**
@@ -25,6 +28,21 @@
     private FunctionalUtils() {}
 
     /**
+     * Converts a lambda expression that throws a checked exception(s) into a regular
+     * {@link Consumer} by propagating any checked exceptions as {@link RuntimeException}
+     */
+    public static <T> Consumer<T> uncheckExceptions(ThrowingConsumer<T> action) {
+        return action;
+    }
+
+    /**
+     *
+     */
+    public static <T> Consumer<T> ignoreRemoteException(RemoteExceptionIgnoringConsumer<T> action) {
+        return action;
+    }
+
+    /**
      * An equivalent of {@link Runnable} that allows throwing checked exceptions
      *
      * This can be used to specify a lambda argument without forcing all the checked exceptions
@@ -47,13 +65,43 @@
     }
 
     /**
-     * An equivalent of {@link java.util.function.Consumer} that allows throwing checked exceptions
+     * A {@link Consumer} that allows throwing checked exceptions from its single abstract method.
      *
-     * This can be used to specify a lambda argument without forcing all the checked exceptions
-     * to be handled within it
+     * Can be used together with {@link #uncheckExceptions} to effectively turn a lambda expression
+     * that throws a checked exception into a regular {@link Consumer}
      */
     @FunctionalInterface
-    public interface ThrowingConsumer<T> {
-        void accept(T t) throws Exception;
+    @SuppressWarnings("FunctionalInterfaceMethodChanged")
+    public interface ThrowingConsumer<T> extends Consumer<T> {
+        void acceptOrThrow(T t) throws Exception;
+
+        @Override
+        default void accept(T t) {
+            try {
+                acceptOrThrow(t);
+            } catch (Exception ex) {
+                throw new RuntimeException(ex);
+            }
+        }
+    }
+
+    /**
+     * A {@link Consumer} that automatically ignores any {@link RemoteException}s.
+     *
+     * Used by {@link #ignoreRemoteException}
+     */
+    @FunctionalInterface
+    @SuppressWarnings("FunctionalInterfaceMethodChanged")
+    public interface RemoteExceptionIgnoringConsumer<T> extends Consumer<T> {
+        void acceptOrThrow(T t) throws RemoteException;
+
+        @Override
+        default void accept(T t) {
+            try {
+                acceptOrThrow(t);
+            } catch (RemoteException ex) {
+                // ignore
+            }
+        }
     }
 }
diff --git a/core/java/com/android/internal/widget/ILockSettings.aidl b/core/java/com/android/internal/widget/ILockSettings.aidl
index 164a745..43536a5 100644
--- a/core/java/com/android/internal/widget/ILockSettings.aidl
+++ b/core/java/com/android/internal/widget/ILockSettings.aidl
@@ -16,6 +16,7 @@
 
 package com.android.internal.widget;
 
+import android.app.PendingIntent;
 import android.app.trust.IStrongAuthTracker;
 import android.os.Bundle;
 import android.security.recoverablekeystore.KeyEntryRecoveryData;
@@ -24,6 +25,8 @@
 import com.android.internal.widget.ICheckCredentialProgressCallback;
 import com.android.internal.widget.VerifyCredentialResponse;
 
+import java.util.Map;
+
 /** {@hide} */
 interface ILockSettings {
     void setBoolean(in String key, in boolean value, in int userId);
@@ -63,8 +66,11 @@
     void initRecoveryService(in String rootCertificateAlias, in byte[] signedPublicKeyList,
             int userId);
     KeyStoreRecoveryData getRecoveryData(in byte[] account, int userId);
+    void setSnapshotCreatedPendingIntent(in PendingIntent intent, int userId);
+    Map getRecoverySnapshotVersions(int userId);
     void setServerParameters(long serverParameters, int userId);
     void setRecoveryStatus(in String packageName, in String[] aliases, int status, int userId);
+    Map getRecoveryStatus(in String packageName, int userId);
     void setRecoverySecretTypes(in int[] secretTypes, int userId);
     int[] getRecoverySecretTypes(int userId);
     int[] getPendingRecoverySecretTypes(int userId);
diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp
index 79c5a34..79aa5ac 100644
--- a/core/jni/android/graphics/BitmapFactory.cpp
+++ b/core/jni/android/graphics/BitmapFactory.cpp
@@ -385,10 +385,18 @@
             return nullObjectReturn("codec->getAndroidPixels() failed.");
     }
 
+    // This is weird so let me explain: we could use the scale parameter
+    // directly, but for historical reasons this is how the corresponding
+    // Dalvik code has always behaved. We simply recreate the behavior here.
+    // The result is slightly different from simply using scale because of
+    // the 0.5f rounding bias applied when computing the target image size
+    const float scaleX = scaledWidth / float(decodingBitmap.width());
+    const float scaleY = scaledHeight / float(decodingBitmap.height());
+
     jbyteArray ninePatchChunk = NULL;
     if (peeker.mPatch != NULL) {
         if (willScale) {
-            peeker.scale(scale, scale, scaledWidth, scaledHeight);
+            peeker.scale(scaleX, scaleY, scaledWidth, scaledHeight);
         }
 
         size_t ninePatchArraySize = peeker.mPatch->serializedSize();
@@ -419,14 +427,6 @@
 
     SkBitmap outputBitmap;
     if (willScale) {
-        // This is weird so let me explain: we could use the scale parameter
-        // directly, but for historical reasons this is how the corresponding
-        // Dalvik code has always behaved. We simply recreate the behavior here.
-        // The result is slightly different from simply using scale because of
-        // the 0.5f rounding bias applied when computing the target image size
-        const float sx = scaledWidth / float(decodingBitmap.width());
-        const float sy = scaledHeight / float(decodingBitmap.height());
-
         // Set the allocator for the outputBitmap.
         SkBitmap::Allocator* outputAllocator;
         if (javaBitmap != nullptr) {
@@ -456,7 +456,7 @@
         paint.setFilterQuality(kLow_SkFilterQuality); // bilinear filtering
 
         SkCanvas canvas(outputBitmap, SkCanvas::ColorBehavior::kLegacy);
-        canvas.scale(sx, sy);
+        canvas.scale(scaleX, scaleY);
         canvas.drawBitmap(decodingBitmap, 0.0f, 0.0f, &paint);
     } else {
         outputBitmap.swap(decodingBitmap);
diff --git a/core/jni/android/graphics/NinePatchPeeker.cpp b/core/jni/android/graphics/NinePatchPeeker.cpp
index 066d47b..9171fc6 100644
--- a/core/jni/android/graphics/NinePatchPeeker.cpp
+++ b/core/jni/android/graphics/NinePatchPeeker.cpp
@@ -76,13 +76,18 @@
     if (!mPatch) {
         return;
     }
-    mPatch->paddingLeft   = int(mPatch->paddingLeft   * scaleX + 0.5f);
-    mPatch->paddingTop    = int(mPatch->paddingTop    * scaleY + 0.5f);
-    mPatch->paddingRight  = int(mPatch->paddingRight  * scaleX + 0.5f);
-    mPatch->paddingBottom = int(mPatch->paddingBottom * scaleY + 0.5f);
 
     // The max value for the divRange is one pixel less than the actual max to ensure that the size
     // of the last div is not zero. A div of size 0 is considered invalid input and will not render.
-    scaleDivRange(mPatch->getXDivs(), mPatch->numXDivs, scaleX, scaledWidth - 1);
-    scaleDivRange(mPatch->getYDivs(), mPatch->numYDivs, scaleY, scaledHeight - 1);
+    if (!SkScalarNearlyEqual(scaleX, 1.0f)) {
+        mPatch->paddingLeft   = int(mPatch->paddingLeft   * scaleX + 0.5f);
+        mPatch->paddingRight  = int(mPatch->paddingRight  * scaleX + 0.5f);
+        scaleDivRange(mPatch->getXDivs(), mPatch->numXDivs, scaleX, scaledWidth - 1);
+    }
+
+    if (!SkScalarNearlyEqual(scaleY, 1.0f)) {
+        mPatch->paddingTop    = int(mPatch->paddingTop    * scaleY + 0.5f);
+        mPatch->paddingBottom = int(mPatch->paddingBottom * scaleY + 0.5f);
+        scaleDivRange(mPatch->getYDivs(), mPatch->numYDivs, scaleY, scaledHeight - 1);
+    }
 }
diff --git a/core/jni/android/opengl/util.cpp b/core/jni/android/opengl/util.cpp
index 1522c20..1676d4b 100644
--- a/core/jni/android/opengl/util.cpp
+++ b/core/jni/android/opengl/util.cpp
@@ -25,7 +25,8 @@
 #include <assert.h>
 #include <dlfcn.h>
 
-#include <GLES/gl.h>
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
 #include <ETC1/etc1.h>
 
 #include <SkBitmap.h>
@@ -639,6 +640,10 @@
                         return 0;
             }
             break;
+        case kRGBA_F16_SkColorType:
+            if (type == GL_HALF_FLOAT_OES && format == PIXEL_FORMAT_RGBA_FP16)
+                return 0;
+            break;
         default:
             break;
     }
@@ -656,6 +661,8 @@
             return GL_RGBA;
         case kRGB_565_SkColorType:
             return GL_RGB;
+        case kRGBA_F16_SkColorType:
+            return PIXEL_FORMAT_RGBA_FP16;
         default:
             return -1;
     }
@@ -672,6 +679,8 @@
             return GL_UNSIGNED_BYTE;
         case kRGB_565_SkColorType:
             return GL_UNSIGNED_SHORT_5_6_5;
+        case kRGBA_F16_SkColorType:
+            return GL_HALF_FLOAT_OES;
         default:
             return -1;
     }
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 13fedfe..e303927 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3632,6 +3632,11 @@
     <permission android:name="android.permission.READ_RUNTIME_PROFILES"
                 android:protectionLevel="signature|privileged" />
 
+    <!-- @SystemApi Allows an application to turn on / off quiet mode.
+         @hide <p>Not for use by third-party applications. -->
+    <permission android:name="android.permission.MODIFY_QUIET_MODE"
+                android:protectionLevel="signature|privileged" />
+
     <application android:process="system"
                  android:persistent="true"
                  android:hasCode="false"
diff --git a/core/res/res/drawable/ic_wifi_settings.xml b/core/res/res/drawable/ic_wifi_settings.xml
new file mode 100644
index 0000000..c678ad4
--- /dev/null
+++ b/core/res/res/drawable/ic_wifi_settings.xml
@@ -0,0 +1,41 @@
+<?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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+
+    <path
+        android:pathData="M 0 0 H 24 V 24 H 0 V 0 Z" />
+    <path
+        android:fillColor="#000000"
+        android:pathData="M12.584,15.93c0.026-0.194,0.044-0.397,0.044-0.608c0-0.211-0.018-0.405-0.044-0.608l1.304-1.022
+c0.115-0.088,0.15-0.256,0.071-0.397l-1.234-2.133c-0.071-0.132-0.238-0.185-0.379-0.132l-1.533,0.617
+c-0.317-0.247-0.67-0.449-1.04-0.608L9.535,9.4c-0.018-0.132-0.141-0.247-0.3-0.247H6.768c-0.15,0-0.282,0.115-0.3,0.256
+L6.23,11.048c-0.379,0.159-0.723,0.361-1.04,0.608l-1.533-0.617c-0.141-0.053-0.3,0-0.379,0.132l-1.234,2.133
+c-0.079,0.132-0.044,0.3,0.07,0.397l1.304,1.022c-0.026,0.194-0.044,0.405-0.044,0.608s0.018,0.405,0.044,0.608l-1.304,1.022
+c-0.115,0.088-0.15,0.256-0.07,0.397l1.234,2.133c0.07,0.132,0.238,0.185,0.379,0.132l1.533-0.617
+c0.317,0.247,0.67,0.449,1.04,0.608l0.238,1.639c0.018,0.15,0.15,0.256,0.3,0.256h2.467c0.159,0,0.282-0.115,0.3-0.256
+l0.238-1.639c0.379-0.15,0.723-0.361,1.04-0.608l1.533,0.617c0.141,0.053,0.3,0,0.379-0.132l1.234-2.133
+c0.071-0.132,0.044-0.3-0.07-0.397L12.584,15.93z
+M8.002,17.481c-1.19,0-2.159-0.969-2.159-2.159s0.969-2.159,2.159-2.159
+s2.159,0.969,2.159,2.159C10.161,16.512,9.191,17.481,8.002,17.481z" />
+    <path
+        android:fillColor="#000000"
+        android:pathData="M16.003,12.026l5.995-7.474c-0.229-0.172-2.537-2.06-6-2.06s-5.771,1.889-6,2.06l5.995,7.469l0.005,0.01L16.003,12.026z" />
+</vector>
\ No newline at end of file
diff --git a/core/res/res/raw/color_fade_vert.vert b/core/res/res/raw/color_fade_vert.vert
index d17437f..b501b06 100644
--- a/core/res/res/raw/color_fade_vert.vert
+++ b/core/res/res/raw/color_fade_vert.vert
@@ -1,6 +1,5 @@
 uniform mat4 proj_matrix;
 uniform mat4 tex_matrix;
-uniform float scale;
 attribute vec2 position;
 attribute vec2 uv;
 varying vec2 UV;
@@ -9,5 +8,5 @@
 {
     vec4 transformed_uv = tex_matrix * vec4(uv.x, uv.y, 1.0, 1.0);
     UV = transformed_uv.st / transformed_uv.q;
-    gl_Position = vec4(scale, scale, 1.0, 1.0) * (proj_matrix * vec4(position.x, position.y, 0.0, 1.0));
+    gl_Position = proj_matrix * vec4(position.x, position.y, 0.0, 1.0);
 }
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 5ccaf5c..5783435 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3014,6 +3014,13 @@
     <!-- Notification action name for opening the wifi picker, showing the user all the nearby networks. -->
     <string name="wifi_available_action_all_networks">All Networks</string>
 
+    <!--Notification title for Wi-Fi Wake onboarding. This is displayed the first time a user disables Wi-Fi with the feature enabled. -->
+    <string name="wifi_wakeup_onboarding_title">Wi\u2011Fi will turn on automatically</string>
+    <!--Notification subtext for Wi-Fi Wake onboarding.-->
+    <string name="wifi_wakeup_onboarding_subtext">When you\'re near a high quality saved network</string>
+    <!--Notification action to disable Wi-Fi Wake during onboarding.-->
+    <string name="wifi_wakeup_onboarding_action_disable">Don\'t turn back on</string>
+
     <!-- A notification is shown when a wifi captive portal network is detected.  This is the notification's title. -->
     <string name="wifi_available_sign_in">Sign in to Wi-Fi network</string>
 
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 4b2424f..7f71446 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1308,6 +1308,7 @@
   <java-symbol type="drawable" name="platlogo" />
   <java-symbol type="drawable" name="stat_notify_sync_error" />
   <java-symbol type="drawable" name="stat_notify_wifi_in_range" />
+  <java-symbol type="drawable" name="ic_wifi_settings" />
   <java-symbol type="drawable" name="ic_wifi_signal_0" />
   <java-symbol type="drawable" name="ic_wifi_signal_1" />
   <java-symbol type="drawable" name="ic_wifi_signal_2" />
@@ -1905,6 +1906,9 @@
   <java-symbol type="string" name="wifi_available_content_failed_to_connect" />
   <java-symbol type="string" name="wifi_available_action_connect" />
   <java-symbol type="string" name="wifi_available_action_all_networks" />
+  <java-symbol type="string" name="wifi_wakeup_onboarding_title" />
+  <java-symbol type="string" name="wifi_wakeup_onboarding_subtext" />
+  <java-symbol type="string" name="wifi_wakeup_onboarding_action_disable" />
   <java-symbol type="string" name="accessibility_binding_label" />
   <java-symbol type="string" name="adb_active_notification_message" />
   <java-symbol type="string" name="adb_active_notification_title" />
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index 3f2a46a..e094772 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -1051,7 +1051,7 @@
             </intent-filter>
         </activity>
 
-        <activity android:name=".menus.MenuLayoutPortrait" android:label="MenuLayoutPortrait"
+        <activity android:name="android.view.menu.MenuLayoutPortrait" android:label="MenuLayoutPortrait"
                 android:screenOrientation="portrait">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
diff --git a/core/tests/coretests/BstatsTestApp/AndroidManifest.xml b/core/tests/coretests/BstatsTestApp/AndroidManifest.xml
index 0cb5498..1e6bdc6 100644
--- a/core/tests/coretests/BstatsTestApp/AndroidManifest.xml
+++ b/core/tests/coretests/BstatsTestApp/AndroidManifest.xml
@@ -17,9 +17,15 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.android.coretests.apps.bstatstestapp">
 
+    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
+
+    <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="25"/>
+
     <application>
         <activity android:name=".TestActivity"
                   android:exported="true" />
+        <service android:name=".TestService"
+                  android:exported="true" />
         <service android:name=".IsolatedTestService"
                  android:exported="true"
                  android:isolatedProcess="true" />
diff --git a/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/BaseCmdReceiver.java b/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/BaseCmdReceiver.java
new file mode 100644
index 0000000..2601f35
--- /dev/null
+++ b/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/BaseCmdReceiver.java
@@ -0,0 +1,29 @@
+/*
+ * 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.coretests.apps.bstatstestapp;
+
+import android.os.RemoteException;
+
+import com.android.frameworks.coretests.aidl.ICmdReceiver;
+
+public class BaseCmdReceiver extends ICmdReceiver.Stub {
+    @Override
+    public void doSomeWork(int durationMs) {}
+    @Override
+    public void showApplicationOverlay() throws RemoteException {}
+    @Override
+    public void finishHost() {}
+}
diff --git a/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/Common.java b/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/Common.java
new file mode 100644
index 0000000..d192fbd
--- /dev/null
+++ b/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/Common.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.coretests.apps.bstatstestapp;
+
+import com.android.frameworks.coretests.aidl.ICmdCallback;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.util.Log;
+
+public class Common {
+    private static final String EXTRA_KEY_CMD_RECEIVER = "cmd_receiver";
+
+    public static void doSomeWork(int durationMs) {
+        final long endTime = SystemClock.currentThreadTimeMillis() + durationMs;
+        double x;
+        double y;
+        double z;
+        while (SystemClock.currentThreadTimeMillis() <= endTime) {
+            x = 0.02;
+            x *= 1000;
+            y = x % 5;
+            z = Math.sqrt(y / 100);
+        }
+    }
+
+    public static void notifyLaunched(Intent intent, IBinder binder, String tag) {
+        if (intent == null) {
+            return;
+        }
+
+        final Bundle extras = intent.getExtras();
+        if (extras == null) {
+            return;
+        }
+        final ICmdCallback callback = ICmdCallback.Stub.asInterface(
+                extras.getBinder(EXTRA_KEY_CMD_RECEIVER));
+        try {
+            callback.onLaunched(binder);
+        } catch (RemoteException e) {
+            Log.e(tag, "Error occured while notifying the test: " + e);
+        }
+    }
+}
diff --git a/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/IsolatedTestService.java b/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/IsolatedTestService.java
index 1f5f397..892f60e 100644
--- a/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/IsolatedTestService.java
+++ b/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/IsolatedTestService.java
@@ -15,17 +15,14 @@
  */
 package com.android.coretests.apps.bstatstestapp;
 
-import com.android.frameworks.coretests.aidl.ICmdReceiver;
-
 import android.app.Service;
 import android.content.Intent;
 import android.os.IBinder;
 import android.os.Process;
-import android.os.SystemClock;
 import android.util.Log;
 
 public class IsolatedTestService extends Service {
-    private static final String TAG = IsolatedTestService.class.getName();
+    private static final String TAG = IsolatedTestService.class.getSimpleName();
 
     @Override
     public void onCreate() {
@@ -34,23 +31,20 @@
 
     @Override
     public IBinder onBind(Intent intent) {
+        Log.d(TAG, "onBind called. myUid=" + Process.myUid());
         return mReceiver.asBinder();
     }
 
-    private ICmdReceiver mReceiver = new ICmdReceiver.Stub() {
+    @Override
+    public void onDestroy() {
+        Log.d(TAG, "onDestroy called. myUid=" + Process.myUid());
+    }
+
+    private BaseCmdReceiver mReceiver = new BaseCmdReceiver() {
         @Override
         public void doSomeWork(int durationMs) {
-            final long endTime = SystemClock.uptimeMillis() + durationMs;
-            double x;
-            double y;
-            double z;
-            while (SystemClock.uptimeMillis() <= endTime) {
-                x = 0.02;
-                x *= 1000;
-                y = x % 5;
-                z = Math.sqrt(y / 100);
-            }
-        };
+            Common.doSomeWork(durationMs);
+        }
 
         @Override
         public void finishHost() {
diff --git a/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/TestActivity.java b/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/TestActivity.java
index 87b14d9..5c551d5 100644
--- a/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/TestActivity.java
+++ b/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/TestActivity.java
@@ -15,19 +15,12 @@
  */
 package com.android.coretests.apps.bstatstestapp;
 
-import com.android.frameworks.coretests.aidl.ICmdCallback;
-import com.android.frameworks.coretests.aidl.ICmdReceiver;
-
 import android.app.Activity;
 import android.os.Bundle;
-import android.os.RemoteException;
-import android.os.SystemClock;
 import android.util.Log;
 
 public class TestActivity extends Activity {
-    private static final String TAG = TestActivity.class.getName();
-
-    private static final String EXTRA_KEY_CMD_RECEIVER = "cmd_receiver";
+    private static final String TAG = TestActivity.class.getSimpleName();
 
     @Override
     public void onCreate(Bundle icicle) {
@@ -37,21 +30,7 @@
     }
 
     private void notifyActivityLaunched() {
-        if (getIntent() == null) {
-            return;
-        }
-
-        final Bundle extras = getIntent().getExtras();
-        if (extras == null) {
-            return;
-        }
-        final ICmdCallback callback = ICmdCallback.Stub.asInterface(
-                extras.getBinder(EXTRA_KEY_CMD_RECEIVER));
-        try {
-            callback.onActivityLaunched(mReceiver.asBinder());
-        } catch (RemoteException e) {
-            Log.e(TAG, "Error occured while notifying the test: " + e);
-        }
+        Common.notifyLaunched(getIntent(), mReceiver.asBinder(), TAG);
     }
 
     @Override
@@ -60,24 +39,17 @@
         Log.d(TAG, "finish called");
     }
 
-    private ICmdReceiver mReceiver = new ICmdReceiver.Stub() {
+    private BaseCmdReceiver mReceiver = new BaseCmdReceiver() {
         @Override
         public void doSomeWork(int durationMs) {
-            final long endTime = SystemClock.uptimeMillis() + durationMs;
-            double x;
-            double y;
-            double z;
-            while (SystemClock.uptimeMillis() <= endTime) {
-                x = 0.02;
-                x *= 1000;
-                y = x % 5;
-                z = Math.sqrt(y / 100);
-            }
-        };
+            Common.doSomeWork(durationMs);
+        }
 
         @Override
         public void finishHost() {
-            finish();
+            if (!isFinishing()) {
+                finish();
+            }
         }
     };
 }
diff --git a/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/TestService.java b/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/TestService.java
new file mode 100644
index 0000000..8a22aca
--- /dev/null
+++ b/core/tests/coretests/BstatsTestApp/src/com/android/coretests/apps/bstatstestapp/TestService.java
@@ -0,0 +1,154 @@
+/*
+ * 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.coretests.apps.bstatstestapp;
+
+import android.R;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.Service;
+import android.content.Intent;
+import android.graphics.Color;
+import android.graphics.Point;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Process;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowManager;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class TestService extends Service {
+    private static final String TAG = TestService.class.getSimpleName();
+
+    private static final int FLAG_START_FOREGROUND = 1;
+
+    private static final String NOTIFICATION_CHANNEL_ID = TAG;
+    private static final int NOTIFICATION_ID = 42;
+
+    private static final int TIMEOUT_OVERLAY_SEC = 2;
+
+    private View mOverlay;
+
+    @Override
+    public void onCreate() {
+        Log.d(TAG, "onCreate called. myUid=" + Process.myUid());
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        Log.d(TAG, "onStartCommand called. myUid=" + Process.myUid());
+        if (intent != null && (intent.getFlags() & FLAG_START_FOREGROUND) != 0) {
+            startForeground();
+        }
+        notifyServiceLaunched(intent);
+        return START_STICKY;
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        Log.d(TAG, "onBind called. myUid=" + Process.myUid());
+        return null;
+    }
+
+    @Override
+    public void onDestroy() {
+        Log.d(TAG, "onDestroy called. myUid=" + Process.myUid());
+        removeOverlays();
+    }
+
+    private void notifyServiceLaunched(Intent intent) {
+        Common.notifyLaunched(intent, mReceiver.asBinder(), TAG);
+    }
+
+    private void startForeground() {
+        final NotificationManager noMan = getSystemService(NotificationManager.class);
+        noMan.createNotificationChannel(new NotificationChannel(
+                NOTIFICATION_CHANNEL_ID, NOTIFICATION_CHANNEL_ID,
+                NotificationManager.IMPORTANCE_DEFAULT));
+        Log.d(TAG, "Starting foreground. myUid=" + Process.myUid());
+        startForeground(NOTIFICATION_ID,
+                new Notification.Builder(this, NOTIFICATION_CHANNEL_ID)
+                        .setSmallIcon(R.drawable.ic_dialog_alert)
+                        .build());
+    }
+
+    private void removeOverlays() {
+        if (mOverlay != null) {
+            final WindowManager wm = TestService.this.getSystemService(WindowManager.class);
+            wm.removeView(mOverlay);
+            mOverlay = null;
+        }
+    }
+
+    private BaseCmdReceiver mReceiver = new BaseCmdReceiver() {
+        @Override
+        public void doSomeWork(int durationMs) {
+            Common.doSomeWork(durationMs);
+        }
+
+        @Override
+        public void showApplicationOverlay() throws RemoteException {
+            final WindowManager wm = TestService.this.getSystemService(WindowManager.class);
+            final Point size = new Point();
+            wm.getDefaultDisplay().getSize(size);
+
+            final WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(
+                    WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
+                    WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+                            | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
+                            | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
+            wmlp.width = size.x / 2;
+            wmlp.height = size.y / 2;
+            wmlp.gravity = Gravity.CENTER | Gravity.LEFT;
+            wmlp.setTitle(TAG);
+
+            final ViewGroup.LayoutParams vglp = new ViewGroup.LayoutParams(
+                    ViewGroup.LayoutParams.MATCH_PARENT,
+                    ViewGroup.LayoutParams.MATCH_PARENT);
+
+            mOverlay = new View(TestService.this);
+            mOverlay.setBackgroundColor(Color.GREEN);
+            mOverlay.setLayoutParams(vglp);
+
+            final CountDownLatch latch = new CountDownLatch(1);
+            final Handler handler = new Handler(TestService.this.getMainLooper());
+            handler.post(() -> {
+                wm.addView(mOverlay, wmlp);
+                latch.countDown();
+            });
+            try {
+                if (!latch.await(TIMEOUT_OVERLAY_SEC, TimeUnit.SECONDS)) {
+                    throw new RemoteException("Timed out waiting for the overlay");
+                }
+            } catch (InterruptedException e) {
+                throw new RemoteException("Error while adding overlay: " + e.toString());
+            }
+            Log.d(TAG, "Overlay displayed, myUid=" + Process.myUid());
+        }
+
+        @Override
+        public void finishHost() {
+            removeOverlays();
+            stopSelf();
+        }
+    };
+}
diff --git a/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ICmdCallback.aidl b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ICmdCallback.aidl
index 53a181a..6d0239b 100644
--- a/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ICmdCallback.aidl
+++ b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ICmdCallback.aidl
@@ -17,5 +17,5 @@
 package com.android.frameworks.coretests.aidl;
 
 interface ICmdCallback {
-    void onActivityLaunched(IBinder receiver);
+    void onLaunched(IBinder receiver);
 }
\ No newline at end of file
diff --git a/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ICmdReceiver.aidl b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ICmdReceiver.aidl
index c406570..cce8e28 100644
--- a/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ICmdReceiver.aidl
+++ b/core/tests/coretests/aidl/com/android/frameworks/coretests/aidl/ICmdReceiver.aidl
@@ -18,5 +18,6 @@
 
 interface ICmdReceiver {
     void doSomeWork(int durationMs);
+    void showApplicationOverlay();
     void finishHost();
 }
\ No newline at end of file
diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java
index c14dc90..7183934 100644
--- a/core/tests/coretests/src/android/app/NotificationTest.java
+++ b/core/tests/coretests/src/android/app/NotificationTest.java
@@ -214,6 +214,20 @@
         assertTrue(n.allPendingIntents.contains(intent));
     }
 
+    @Test
+    public void testMessagingStyle_isGroupConversation() {
+        Notification.MessagingStyle messagingStyle = new Notification.MessagingStyle("self name")
+                .setGroupConversation(true);
+        Notification notification = new Notification.Builder(mContext, "test id")
+                .setSmallIcon(1)
+                .setContentTitle("test title")
+                .setStyle(messagingStyle)
+                .build();
+
+        assertTrue(messagingStyle.isGroupConversation());
+        assertTrue(notification.extras.getBoolean(Notification.EXTRA_IS_GROUP_CONVERSATION));
+    }
+
     private Notification.Builder getMediaNotification() {
         MediaSession session = new MediaSession(mContext, "test");
         return new Notification.Builder(mContext, "color")
diff --git a/core/tests/coretests/src/android/os/WorkSourceTest.java b/core/tests/coretests/src/android/os/WorkSourceTest.java
new file mode 100644
index 0000000..7350db7
--- /dev/null
+++ b/core/tests/coretests/src/android/os/WorkSourceTest.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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 android.os;
+
+import android.os.WorkSource.WorkChain;
+
+import junit.framework.TestCase;
+
+import java.util.List;
+
+/**
+ * Provides unit tests for hidden / unstable WorkSource APIs that are not CTS testable.
+ *
+ * These tests will be moved to CTS when finalized.
+ */
+public class WorkSourceTest extends TestCase {
+    public void testWorkChain_add() {
+        WorkChain wc1 = new WorkChain();
+        wc1.addNode(56, null);
+
+        assertEquals(56, wc1.getUids()[0]);
+        assertEquals(null, wc1.getTags()[0]);
+        assertEquals(1, wc1.getSize());
+
+        wc1.addNode(57, "foo");
+        assertEquals(56, wc1.getUids()[0]);
+        assertEquals(null, wc1.getTags()[0]);
+        assertEquals(57, wc1.getUids()[1]);
+        assertEquals("foo", wc1.getTags()[1]);
+
+        assertEquals(2, wc1.getSize());
+    }
+
+    public void testWorkChain_equalsHashCode() {
+        WorkChain wc1 = new WorkChain();
+        WorkChain wc2 = new WorkChain();
+
+        assertEquals(wc1, wc2);
+        assertEquals(wc1.hashCode(), wc2.hashCode());
+
+        wc1.addNode(1, null);
+        wc2.addNode(1, null);
+        assertEquals(wc1, wc2);
+        assertEquals(wc1.hashCode(), wc2.hashCode());
+
+        wc1.addNode(2, "tag");
+        wc2.addNode(2, "tag");
+        assertEquals(wc1, wc2);
+        assertEquals(wc1.hashCode(), wc2.hashCode());
+
+        wc1 = new WorkChain();
+        wc2 = new WorkChain();
+        wc1.addNode(5, null);
+        wc2.addNode(6, null);
+        assertFalse(wc1.equals(wc2));
+        assertFalse(wc1.hashCode() == wc2.hashCode());
+
+        wc1 = new WorkChain();
+        wc2 = new WorkChain();
+        wc1.addNode(5, "tag1");
+        wc2.addNode(5, "tag2");
+        assertFalse(wc1.equals(wc2));
+        assertFalse(wc1.hashCode() == wc2.hashCode());
+    }
+
+    public void testWorkChain_constructor() {
+        WorkChain wc1 = new WorkChain();
+        wc1.addNode(1, "foo")
+            .addNode(2, null)
+            .addNode(3, "baz");
+
+        WorkChain wc2 = new WorkChain(wc1);
+        assertEquals(wc1, wc2);
+
+        wc1.addNode(4, "baz");
+        assertFalse(wc1.equals(wc2));
+    }
+
+    public void testDiff_workChains() {
+        WorkSource ws1 = new WorkSource();
+        ws1.add(50);
+        ws1.createWorkChain().addNode(52, "foo");
+        WorkSource ws2 = new WorkSource();
+        ws2.add(50);
+        ws2.createWorkChain().addNode(60, "bar");
+
+        // Diffs don't take WorkChains into account for the sake of backward compatibility.
+        assertFalse(ws1.diff(ws2));
+        assertFalse(ws2.diff(ws1));
+    }
+
+    public void testEquals_workChains() {
+        WorkSource ws1 = new WorkSource();
+        ws1.add(50);
+        ws1.createWorkChain().addNode(52, "foo");
+
+        WorkSource ws2 = new WorkSource();
+        ws2.add(50);
+        ws2.createWorkChain().addNode(52, "foo");
+
+        assertEquals(ws1, ws2);
+
+        // Unequal number of WorkChains.
+        ws2.createWorkChain().addNode(53, "baz");
+        assertFalse(ws1.equals(ws2));
+
+        // Different WorkChain contents.
+        WorkSource ws3 = new WorkSource();
+        ws3.add(50);
+        ws3.createWorkChain().addNode(60, "bar");
+
+        assertFalse(ws1.equals(ws3));
+        assertFalse(ws3.equals(ws1));
+    }
+
+    public void testWorkSourceParcelling() {
+        WorkSource ws = new WorkSource();
+
+        WorkChain wc = ws.createWorkChain();
+        wc.addNode(56, "foo");
+        wc.addNode(75, "baz");
+        WorkChain wc2 = ws.createWorkChain();
+        wc2.addNode(20, "foo2");
+        wc2.addNode(30, "baz2");
+
+        Parcel p = Parcel.obtain();
+        ws.writeToParcel(p, 0);
+        p.setDataPosition(0);
+
+        WorkSource unparcelled = WorkSource.CREATOR.createFromParcel(p);
+
+        assertEquals(unparcelled, ws);
+    }
+
+    public void testSet_workChains() {
+        WorkSource ws1 = new WorkSource();
+        ws1.add(50);
+
+        WorkSource ws2 = new WorkSource();
+        ws2.add(60);
+        WorkChain wc = ws2.createWorkChain();
+        wc.addNode(75, "tag");
+
+        ws1.set(ws2);
+
+        // Assert that the WorkChains are copied across correctly to the new WorkSource object.
+        List<WorkChain> workChains = ws1.getWorkChains();
+        assertEquals(1, workChains.size());
+
+        assertEquals(1, workChains.get(0).getSize());
+        assertEquals(75, workChains.get(0).getUids()[0]);
+        assertEquals("tag", workChains.get(0).getTags()[0]);
+
+        // Also assert that a deep copy of workchains is made, so the addition of a new WorkChain
+        // or the modification of an existing WorkChain has no effect.
+        ws2.createWorkChain();
+        assertEquals(1, ws1.getWorkChains().size());
+
+        wc.addNode(50, "tag2");
+        assertEquals(1, ws1.getWorkChains().size());
+        assertEquals(1, ws1.getWorkChains().get(0).getSize());
+    }
+
+    public void testSet_nullWorkChain() {
+        WorkSource ws = new WorkSource();
+        ws.add(60);
+        WorkChain wc = ws.createWorkChain();
+        wc.addNode(75, "tag");
+
+        ws.set(null);
+        assertEquals(0, ws.getWorkChains().size());
+    }
+
+    public void testAdd_workChains() {
+        WorkSource ws = new WorkSource();
+        ws.createWorkChain().addNode(70, "foo");
+
+        WorkSource ws2 = new WorkSource();
+        ws2.createWorkChain().addNode(60, "tag");
+
+        ws.add(ws2);
+
+        // Check that the new WorkChain is added to the end of the list.
+        List<WorkChain> workChains = ws.getWorkChains();
+        assertEquals(2, workChains.size());
+        assertEquals(1, workChains.get(1).getSize());
+        assertEquals(60, ws.getWorkChains().get(1).getUids()[0]);
+        assertEquals("tag", ws.getWorkChains().get(1).getTags()[0]);
+
+        // Adding the same WorkChain twice should be a no-op.
+        ws.add(ws2);
+        assertEquals(2, workChains.size());
+    }
+}
diff --git a/core/tests/coretests/src/android/view/DisplayCutoutTest.java b/core/tests/coretests/src/android/view/DisplayCutoutTest.java
index 6dd787d..0d8c679 100644
--- a/core/tests/coretests/src/android/view/DisplayCutoutTest.java
+++ b/core/tests/coretests/src/android/view/DisplayCutoutTest.java
@@ -25,6 +25,7 @@
 
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.graphics.Region;
 import android.os.Parcel;
 import android.platform.test.annotations.Presubmit;
 import android.support.test.filters.SmallTest;
@@ -34,7 +35,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.util.ArrayList;
 import java.util.Arrays;
 
 @RunWith(AndroidJUnit4.class)
@@ -45,19 +45,14 @@
     /** This is not a consistent cutout. Useful for verifying insets in one go though. */
     final DisplayCutout mCutoutNumbers = new DisplayCutout(
             new Rect(1, 2, 3, 4),
-            new Rect(5, 6, 7, 8),
-            Arrays.asList(
-                    new Point(9, 10),
-                    new Point(11, 12),
-                    new Point(13, 14),
-                    new Point(15, 16)));
+            new Region(5, 6, 7, 8));
 
     final DisplayCutout mCutoutTop = createCutoutTop();
 
     @Test
     public void hasCutout() throws Exception {
-        assertFalse(NO_CUTOUT.hasCutout());
-        assertTrue(mCutoutTop.hasCutout());
+        assertTrue(NO_CUTOUT.isEmpty());
+        assertFalse(mCutoutTop.isEmpty());
     }
 
     @Test
@@ -67,30 +62,12 @@
         assertEquals(3, mCutoutNumbers.getSafeInsetRight());
         assertEquals(4, mCutoutNumbers.getSafeInsetBottom());
 
-        Rect safeInsets = new Rect();
-        mCutoutNumbers.getSafeInsets(safeInsets);
-
-        assertEquals(new Rect(1, 2, 3, 4), safeInsets);
+        assertEquals(new Rect(1, 2, 3, 4), mCutoutNumbers.getSafeInsets());
     }
 
     @Test
     public void getBoundingRect() throws Exception {
-        Rect boundingRect = new Rect();
-        mCutoutTop.getBoundingRect(boundingRect);
-
-        assertEquals(new Rect(50, 0, 75, 100), boundingRect);
-    }
-
-    @Test
-    public void getBoundingPolygon() throws Exception {
-        ArrayList<Point> boundingPolygon = new ArrayList<>();
-        mCutoutTop.getBoundingPolygon(boundingPolygon);
-
-        assertEquals(Arrays.asList(
-                new Point(75, 0),
-                new Point(50, 0),
-                new Point(75, 100),
-                new Point(50, 100)), boundingPolygon);
+        assertEquals(new Rect(50, 0, 75, 100), mCutoutTop.getBoundingRect());
     }
 
     @Test
@@ -167,104 +144,61 @@
         assertEquals(cutout.getSafeInsetRight(), 0);
         assertEquals(cutout.getSafeInsetBottom(), 0);
 
-        assertFalse(cutout.hasCutout());
+        assertTrue(cutout.isEmpty());
     }
 
     @Test
     public void inset_bounds() throws Exception {
         DisplayCutout cutout = mCutoutTop.inset(1, 2, 3, 4);
 
-        Rect boundingRect = new Rect();
-        cutout.getBoundingRect(boundingRect);
-
-        assertEquals(new Rect(49, -2, 74, 98), boundingRect);
-
-        ArrayList<Point> boundingPolygon = new ArrayList<>();
-        cutout.getBoundingPolygon(boundingPolygon);
-
-        assertEquals(Arrays.asList(
-                new Point(74, -2),
-                new Point(49, -2),
-                new Point(74, 98),
-                new Point(49, 98)), boundingPolygon);
+        assertEquals(new Rect(49, -2, 74, 98), cutout.getBoundingRect());
     }
 
     @Test
     public void calculateRelativeTo_top() throws Exception {
         DisplayCutout cutout = mCutoutTop.calculateRelativeTo(new Rect(0, 0, 200, 400));
 
-        Rect insets = new Rect();
-        cutout.getSafeInsets(insets);
-
-        assertEquals(new Rect(0, 100, 0, 0), insets);
+        assertEquals(new Rect(0, 100, 0, 0), cutout.getSafeInsets());
     }
 
     @Test
     public void calculateRelativeTo_left() throws Exception {
         DisplayCutout cutout = mCutoutTop.calculateRelativeTo(new Rect(0, 0, 400, 200));
 
-        Rect insets = new Rect();
-        cutout.getSafeInsets(insets);
-
-        assertEquals(new Rect(75, 0, 0, 0), insets);
+        assertEquals(new Rect(75, 0, 0, 0), cutout.getSafeInsets());
     }
 
     @Test
     public void calculateRelativeTo_bottom() throws Exception {
         DisplayCutout cutout = mCutoutTop.calculateRelativeTo(new Rect(0, -300, 200, 100));
 
-        Rect insets = new Rect();
-        cutout.getSafeInsets(insets);
-
-        assertEquals(new Rect(0, 0, 0, 100), insets);
+        assertEquals(new Rect(0, 0, 0, 100), cutout.getSafeInsets());
     }
 
     @Test
     public void calculateRelativeTo_right() throws Exception {
         DisplayCutout cutout = mCutoutTop.calculateRelativeTo(new Rect(-400, -200, 100, 100));
 
-        Rect insets = new Rect();
-        cutout.getSafeInsets(insets);
-
-        assertEquals(new Rect(0, 0, 50, 0), insets);
+        assertEquals(new Rect(0, 0, 50, 0), cutout.getSafeInsets());
     }
 
     @Test
     public void calculateRelativeTo_bounds() throws Exception {
         DisplayCutout cutout = mCutoutTop.calculateRelativeTo(new Rect(-1000, -2000, 100, 200));
 
-
-        Rect boundingRect = new Rect();
-        cutout.getBoundingRect(boundingRect);
-        assertEquals(new Rect(1050, 2000, 1075, 2100), boundingRect);
-
-        ArrayList<Point> boundingPolygon = new ArrayList<>();
-        cutout.getBoundingPolygon(boundingPolygon);
-
-        assertEquals(Arrays.asList(
-                new Point(1075, 2000),
-                new Point(1050, 2000),
-                new Point(1075, 2100),
-                new Point(1050, 2100)), boundingPolygon);
+        assertEquals(new Rect(1050, 2000, 1075, 2100), cutout.getBoundingRect());
     }
 
     @Test
     public void fromBoundingPolygon() throws Exception {
         assertEquals(
-                new DisplayCutout(
-                        new Rect(0, 0, 0, 0), // fromBoundingPolygon won't calculate safe insets.
-                        new Rect(50, 0, 75, 100),
-                        Arrays.asList(
-                                new Point(75, 0),
-                                new Point(50, 0),
-                                new Point(75, 100),
-                                new Point(50, 100))),
+                new Rect(50, 0, 75, 100),
                 DisplayCutout.fromBoundingPolygon(
                         Arrays.asList(
                                 new Point(75, 0),
                                 new Point(50, 0),
                                 new Point(75, 100),
-                                new Point(50, 100))));
+                                new Point(50, 100))).getBounds().getBounds());
     }
 
     @Test
@@ -324,24 +258,12 @@
     }
 
     private static DisplayCutout createCutoutTop() {
-        return new DisplayCutout(
-                new Rect(0, 100, 0, 0),
-                new Rect(50, 0, 75, 100),
-                Arrays.asList(
-                        new Point(75, 0),
-                        new Point(50, 0),
-                        new Point(75, 100),
-                        new Point(50, 100)));
+        return createCutoutWithInsets(0, 100, 0, 0);
     }
 
     private static DisplayCutout createCutoutWithInsets(int left, int top, int right, int bottom) {
         return new DisplayCutout(
                 new Rect(left, top, right, bottom),
-                new Rect(50, 0, 75, 100),
-                Arrays.asList(
-                        new Point(75, 0),
-                        new Point(50, 0),
-                        new Point(75, 100),
-                        new Point(50, 100)));
+                new Region(50, 0, 75, 100));
     }
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsCpuTimesTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsCpuTimesTest.java
index 6ff0ab2..b5a7bec 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsCpuTimesTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsCpuTimesTest.java
@@ -960,7 +960,7 @@
     }
 
     @Test
-    public void testReadKernelUiidCpuFreqTimesLocked_invalidUid() {
+    public void testReadKernelUidCpuFreqTimesLocked_invalidUid() {
         // PRECONDITIONS
         updateTimeBasesLocked(true, Display.STATE_ON, 0, 0);
 
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java
new file mode 100644
index 0000000..3794b5f
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsImplTest.java
@@ -0,0 +1,336 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.os;
+
+import static android.os.BatteryStats.STATS_SINCE_CHARGED;
+import static android.os.BatteryStats.Uid.NUM_PROCESS_STATE;
+import static android.os.BatteryStats.Uid.PROCESS_STATE_BACKGROUND;
+import static android.os.BatteryStats.Uid.PROCESS_STATE_CACHED;
+import static android.os.BatteryStats.Uid.PROCESS_STATE_FOREGROUND_SERVICE;
+import static android.os.BatteryStats.Uid.PROCESS_STATE_TOP;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.when;
+
+import android.os.BatteryStats;
+import android.support.test.filters.LargeTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+import android.view.Display;
+
+import com.android.internal.util.ArrayUtils;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatcher;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Arrays;
+
+@LargeTest
+@RunWith(AndroidJUnit4.class)
+public class BatteryStatsImplTest {
+    @Mock
+    private KernelUidCpuFreqTimeReader mKernelUidCpuFreqTimeReader;
+    @Mock
+    private KernelSingleUidTimeReader mKernelSingleUidTimeReader;
+
+    private MockBatteryStatsImpl mBatteryStatsImpl;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        when(mKernelUidCpuFreqTimeReader.allUidTimesAvailable()).thenReturn(true);
+        when(mKernelSingleUidTimeReader.singleUidCpuTimesAvailable()).thenReturn(true);
+        mBatteryStatsImpl = new MockBatteryStatsImpl()
+                .setKernelUidCpuFreqTimeReader(mKernelUidCpuFreqTimeReader)
+                .setKernelSingleUidTimeReader(mKernelSingleUidTimeReader);
+    }
+
+    @Test
+    public void testUpdateProcStateCpuTimes() {
+        mBatteryStatsImpl.setOnBatteryInternal(true);
+        mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_ON, 0, 0);
+
+        final int[] testUids = {10032, 10048, 10145, 10139};
+        final int[] testProcStates = {
+                PROCESS_STATE_BACKGROUND,
+                PROCESS_STATE_FOREGROUND_SERVICE,
+                PROCESS_STATE_TOP,
+                PROCESS_STATE_CACHED
+        };
+        addPendingUids(testUids, testProcStates);
+        final long[][] cpuTimes = {
+                {349734983, 394982394832l, 909834, 348934, 9838},
+                {7498, 1239890, 988, 13298, 98980},
+                {989834, 384098, 98483, 23809, 4984},
+                {4859048, 348903, 4578967, 5973894, 298549}
+        };
+        for (int i = 0; i < testUids.length; ++i) {
+            when(mKernelSingleUidTimeReader.readDeltaMs(testUids[i])).thenReturn(cpuTimes[i]);
+
+            // Verify there are no cpu times initially.
+            final BatteryStats.Uid u = mBatteryStatsImpl.getUidStatsLocked(testUids[i]);
+            for (int procState = 0; procState < NUM_PROCESS_STATE; ++procState) {
+                assertNull(u.getCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+                assertNull(u.getScreenOffCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+            }
+        }
+
+        mBatteryStatsImpl.updateProcStateCpuTimes();
+
+        verifyNoPendingUids();
+        for (int i = 0; i < testUids.length; ++i) {
+            final BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]);
+            for (int procState = 0; procState < NUM_PROCESS_STATE; ++procState) {
+                if (procState == testProcStates[i]) {
+                    assertArrayEquals("Uid=" + testUids[i], cpuTimes[i],
+                            u.getCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+                } else {
+                    assertNull(u.getCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+                }
+                assertNull(u.getScreenOffCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+            }
+        }
+
+        final long[][] delta1 = {
+                {9589, 148934, 309894, 3098493, 98754},
+                {21983, 94983, 4983, 9878493, 84854},
+                {945894, 9089432, 19478, 3834, 7845},
+                {843895, 43948, 949582, 99, 384}
+        };
+        for (int i = 0; i < testUids.length; ++i) {
+            when(mKernelSingleUidTimeReader.readDeltaMs(testUids[i])).thenReturn(delta1[i]);
+        }
+        addPendingUids(testUids, testProcStates);
+
+        mBatteryStatsImpl.updateProcStateCpuTimes();
+
+        verifyNoPendingUids();
+        for (int i = 0; i < testUids.length; ++i) {
+            final BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]);
+            for (int procState = 0; procState < NUM_PROCESS_STATE; ++procState) {
+                if (procState == testProcStates[i]) {
+                    long[] expectedCpuTimes = cpuTimes[i].clone();
+                    for (int j = 0; j < expectedCpuTimes.length; ++j) {
+                        expectedCpuTimes[j] += delta1[i][j];
+                    }
+                    assertArrayEquals("Uid=" + testUids[i], expectedCpuTimes,
+                            u.getCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+                } else {
+                    assertNull(u.getCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+                }
+                assertNull(u.getScreenOffCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+            }
+        }
+
+        mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_OFF, 0, 0);
+        final long[][] delta2 = {
+                {95932, 2943, 49834, 89034, 139},
+                {349, 89605, 5896, 845, 98444},
+                {678, 7498, 9843, 889, 4894},
+                {488, 998, 8498, 394, 574}
+        };
+        for (int i = 0; i < testUids.length; ++i) {
+            when(mKernelSingleUidTimeReader.readDeltaMs(testUids[i])).thenReturn(delta2[i]);
+        }
+        addPendingUids(testUids, testProcStates);
+
+        mBatteryStatsImpl.updateProcStateCpuTimes();
+
+        verifyNoPendingUids();
+        for (int i = 0; i < testUids.length; ++i) {
+            final BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]);
+            for (int procState = 0; procState < NUM_PROCESS_STATE; ++procState) {
+                if (procState == testProcStates[i]) {
+                    long[] expectedCpuTimes = cpuTimes[i].clone();
+                    for (int j = 0; j < expectedCpuTimes.length; ++j) {
+                        expectedCpuTimes[j] += delta1[i][j] + delta2[i][j];
+                    }
+                    assertArrayEquals("Uid=" + testUids[i], expectedCpuTimes,
+                            u.getCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+                    assertArrayEquals("Uid=" + testUids[i], delta2[i],
+                            u.getScreenOffCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+                } else {
+                    assertNull(u.getCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+                    assertNull(u.getScreenOffCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+                }
+            }
+        }
+
+        final long[][] delta3 = {
+                {98545, 95768795, 76586, 548945, 57846},
+                {788876, 586, 578459, 8776984, 9578923},
+                {3049509483598l, 4597834, 377654, 94589035, 7854},
+                {9493, 784, 99895, 8974893, 9879843}
+        };
+        for (int i = 0; i < testUids.length; ++i) {
+            when(mKernelSingleUidTimeReader.readDeltaMs(testUids[i])).thenReturn(
+                    delta3[i].clone());
+        }
+        addPendingUids(testUids, testProcStates);
+        final int parentUid = testUids[1];
+        final int childUid = 99099;
+        addIsolatedUid(parentUid, childUid);
+        final long[] isolatedUidCpuTimes = {495784, 398473, 4895, 4905, 30984093};
+        when(mKernelSingleUidTimeReader.readDeltaMs(childUid)).thenReturn(isolatedUidCpuTimes);
+
+        mBatteryStatsImpl.updateProcStateCpuTimes();
+
+        verifyNoPendingUids();
+        for (int i = 0; i < testUids.length; ++i) {
+            final BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]);
+            for (int procState = 0; procState < NUM_PROCESS_STATE; ++procState) {
+                if (procState == testProcStates[i]) {
+                    long[] expectedCpuTimes = cpuTimes[i].clone();
+                    for (int j = 0; j < expectedCpuTimes.length; ++j) {
+                        expectedCpuTimes[j] += delta1[i][j] + delta2[i][j] + delta3[i][j]
+                                + (testUids[i] == parentUid ? isolatedUidCpuTimes[j] : 0);
+                    }
+                    assertArrayEquals("Uid=" + testUids[i], expectedCpuTimes,
+                            u.getCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+                    long[] expectedScreenOffTimes = delta2[i].clone();
+                    for (int j = 0; j < expectedScreenOffTimes.length; ++j) {
+                        expectedScreenOffTimes[j] += delta3[i][j]
+                                + (testUids[i] == parentUid ? isolatedUidCpuTimes[j] : 0);
+                    }
+                    assertArrayEquals("Uid=" + testUids[i], expectedScreenOffTimes,
+                            u.getScreenOffCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+                } else {
+                    assertNull(u.getCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+                    assertNull(u.getScreenOffCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testCopyFromAllUidsCpuTimes() {
+        mBatteryStatsImpl.setOnBatteryInternal(true);
+        mBatteryStatsImpl.updateTimeBasesLocked(true, Display.STATE_ON, 0, 0);
+
+        final int[] testUids = {10032, 10048, 10145, 10139};
+        final int[] testProcStates = {
+                PROCESS_STATE_BACKGROUND,
+                PROCESS_STATE_FOREGROUND_SERVICE,
+                PROCESS_STATE_TOP,
+                PROCESS_STATE_CACHED
+        };
+        final int[] pendingUidIdx = {1, 2};
+        updateProcessStates(testUids, testProcStates, pendingUidIdx);
+
+        final SparseArray<long[]> allUidCpuTimes = new SparseArray<>();
+        long[][] allCpuTimes = {
+                {938483, 4985984, 439893},
+                {499, 94904, 27694},
+                {302949085, 39789473, 34792839},
+                {9809485, 9083475, 347889834},
+        };
+        for (int i = 0; i < testUids.length; ++i) {
+            allUidCpuTimes.put(testUids[i], allCpuTimes[i]);
+        }
+        when(mKernelUidCpuFreqTimeReader.getAllUidCpuFreqTimeMs()).thenReturn(allUidCpuTimes);
+        long[][] expectedCpuTimes = {
+                {843598745, 397843, 32749, 99854},
+                {9834, 5885, 487589, 394},
+                {203984, 439, 9859, 30948},
+                {9389, 858, 239, 349}
+        };
+        for (int i = 0; i < testUids.length; ++i) {
+            final int idx = i;
+            final ArgumentMatcher<long[]> matcher = times -> Arrays.equals(times, allCpuTimes[idx]);
+            when(mKernelSingleUidTimeReader.computeDelta(eq(testUids[i]), argThat(matcher)))
+                    .thenReturn(expectedCpuTimes[i]);
+        }
+
+        mBatteryStatsImpl.copyFromAllUidsCpuTimes();
+
+        verifyNoPendingUids();
+        for (int i = 0; i < testUids.length; ++i) {
+            final BatteryStats.Uid u = mBatteryStatsImpl.getUidStats().get(testUids[i]);
+            for (int procState = 0; procState < NUM_PROCESS_STATE; ++procState) {
+                if (procState == testProcStates[i]) {
+                    assertArrayEquals("Uid=" + testUids[i], expectedCpuTimes[i],
+                            u.getCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+                } else {
+                    assertNull(u.getCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+                }
+                assertNull(u.getScreenOffCpuFreqTimes(STATS_SINCE_CHARGED, procState));
+            }
+        }
+    }
+
+    @Test
+    public void testAddCpuTimes() {
+        long[] timesA = null;
+        long[] timesB = null;
+        assertNull(mBatteryStatsImpl.addCpuTimes(timesA, timesB));
+
+        timesA = new long[] {34, 23, 45, 24};
+        assertArrayEquals(timesA, mBatteryStatsImpl.addCpuTimes(timesA, timesB));
+
+        timesB = timesA;
+        timesA = null;
+        assertArrayEquals(timesB, mBatteryStatsImpl.addCpuTimes(timesA, timesB));
+
+        final long[] expected = {434, 6784, 34987, 9984};
+        timesA = new long[timesB.length];
+        for (int i = 0; i < timesA.length; ++i) {
+            timesA[i] = expected[i] - timesB[i];
+        }
+        assertArrayEquals(expected, mBatteryStatsImpl.addCpuTimes(timesA, timesB));
+    }
+
+    private void addIsolatedUid(int parentUid, int childUid) {
+        final BatteryStatsImpl.Uid u = mBatteryStatsImpl.getUidStatsLocked(parentUid);
+        u.addIsolatedUid(childUid);
+    }
+
+    private void addPendingUids(int[] uids, int[] procStates) {
+        final SparseIntArray pendingUids = mBatteryStatsImpl.getPendingUids();
+        for (int i = 0; i < uids.length; ++i) {
+            pendingUids.put(uids[i], procStates[i]);
+        }
+    }
+
+    private void updateProcessStates(int[] uids, int[] procStates,
+            int[] pendingUidsIdx) {
+        final SparseIntArray pendingUids = mBatteryStatsImpl.getPendingUids();
+        for (int i = 0; i < uids.length; ++i) {
+            final BatteryStatsImpl.Uid u = mBatteryStatsImpl.getUidStatsLocked(uids[i]);
+            if (ArrayUtils.contains(pendingUidsIdx, i)) {
+                u.setProcessStateForTest(PROCESS_STATE_TOP);
+                pendingUids.put(uids[i], procStates[i]);
+            } else {
+                u.setProcessStateForTest(procStates[i]);
+            }
+        }
+    }
+
+    private void verifyNoPendingUids() {
+        final SparseIntArray pendingUids = mBatteryStatsImpl.getPendingUids();
+        assertEquals("There shouldn't be any pending uids left: " + pendingUids,
+                0, pendingUids.size());
+    }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
index 4e83221..0afec34 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsNoteTest.java
@@ -170,7 +170,6 @@
                 elapsedTimeUs, STATS_SINCE_CHARGED);
         expectedRunTimeMs = stateRuntimeMap.get(ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND)
                 + stateRuntimeMap.get(ActivityManager.PROCESS_STATE_BACKUP)
-                + stateRuntimeMap.get(ActivityManager.PROCESS_STATE_HEAVY_WEIGHT)
                 + stateRuntimeMap.get(ActivityManager.PROCESS_STATE_SERVICE)
                 + stateRuntimeMap.get(ActivityManager.PROCESS_STATE_RECEIVER);
         assertEquals(expectedRunTimeMs * 1000, actualRunTimeUs);
diff --git a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
index 12f5b70..e8f2456 100644
--- a/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
+++ b/core/tests/coretests/src/com/android/internal/os/BatteryStatsTests.java
@@ -26,6 +26,7 @@
         BatteryStatsDualTimerTest.class,
         BatteryStatsDurationTimerTest.class,
         BatteryStatsHelperTest.class,
+        BatteryStatsImplTest.class,
         BatteryStatsNoteTest.class,
         BatteryStatsSamplingTimerTest.class,
         BatteryStatsSensorTest.class,
@@ -36,6 +37,7 @@
         BatteryStatsUidTest.class,
         BatteryStatsUserLifecycleTests.class,
         KernelMemoryBandwidthStatsTest.class,
+        KernelSingleUidTimeReaderTest.class,
         KernelUidCpuFreqTimeReaderTest.class,
         KernelWakelockReaderTest.class,
         LongSamplingCounterArrayTest.class
diff --git a/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java b/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java
index 4b197e4..e54fe7d 100644
--- a/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/BstatsCpuTimesValidationTest.java
@@ -15,19 +15,25 @@
  */
 package com.android.internal.os;
 
-import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
+import static android.os.BatteryStats.UID_TIMES_TYPE_ALL;
+import static android.os.BatteryStats.Uid.NUM_PROCESS_STATE;
+import static android.os.BatteryStats.Uid.PROCESS_STATE_BACKGROUND;
+import static android.os.BatteryStats.Uid.PROCESS_STATE_CACHED;
+import static android.os.BatteryStats.Uid.PROCESS_STATE_FOREGROUND;
+import static android.os.BatteryStats.Uid.PROCESS_STATE_FOREGROUND_SERVICE;
+import static android.os.BatteryStats.Uid.PROCESS_STATE_TOP;
+import static android.os.BatteryStats.Uid.PROCESS_STATE_TOP_SLEEPING;
+import static android.os.BatteryStats.Uid.UID_PROCESS_TYPES;
 
-import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertNotNull;
 import static junit.framework.Assert.assertNull;
 import static junit.framework.Assert.assertTrue;
 import static junit.framework.Assert.fail;
 
-import static org.junit.Assume.assumeTrue;
-
 import com.android.frameworks.coretests.aidl.ICmdCallback;
 import com.android.frameworks.coretests.aidl.ICmdReceiver;
 
+import android.app.ActivityManager;
 import android.app.KeyguardManager;
 import android.content.ComponentName;
 import android.content.Context;
@@ -36,6 +42,7 @@
 import android.content.ServiceConnection;
 import android.content.pm.PackageManager;
 import android.os.BatteryManager;
+import android.os.BatteryStats;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.PowerManager;
@@ -45,9 +52,9 @@
 import android.support.test.filters.LargeTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.support.test.uiautomator.UiDevice;
+import android.util.DebugUtils;
 import android.util.Log;
 
-import org.junit.Assume;
 import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -61,22 +68,26 @@
 @LargeTest
 @RunWith(AndroidJUnit4.class)
 public class BstatsCpuTimesValidationTest {
-    private static final String TAG = BstatsCpuTimesValidationTest.class.getName();
+    private static final String TAG = BstatsCpuTimesValidationTest.class.getSimpleName();
 
     private static final String TEST_PKG = "com.android.coretests.apps.bstatstestapp";
     private static final String TEST_ACTIVITY = TEST_PKG + ".TestActivity";
+    private static final String TEST_SERVICE = TEST_PKG + ".TestService";
     private static final String ISOLATED_TEST_SERVICE = TEST_PKG + ".IsolatedTestService";
 
     private static final String EXTRA_KEY_CMD_RECEIVER = "cmd_receiver";
+    private static final int FLAG_START_FOREGROUND = 1;
 
     private static final int BATTERY_STATE_TIMEOUT_MS = 2000;
     private static final int BATTERY_STATE_CHECK_INTERVAL_MS = 200;
 
     private static final int START_ACTIVITY_TIMEOUT_MS = 2000;
+    private static final int START_FG_SERVICE_TIMEOUT_MS = 2000;
+    private static final int START_SERVICE_TIMEOUT_MS = 2000;
     private static final int START_ISOLATED_SERVICE_TIMEOUT_MS = 2000;
 
-    private static final int GENERAL_TIMEOUT_MS = 1000;
-    private static final int GENERAL_INTERVAL_MS = 100;
+    private static final int GENERAL_TIMEOUT_MS = 4000;
+    private static final int GENERAL_INTERVAL_MS = 200;
 
     private static final int WORK_DURATION_MS = 2000;
 
@@ -84,6 +95,7 @@
     private static UiDevice sUiDevice;
     private static int sTestPkgUid;
     private static boolean sCpuFreqTimesAvailable;
+    private static boolean sPerProcStateTimesAvailable;
 
     @BeforeClass
     public static void setupOnce() throws Exception {
@@ -92,14 +104,20 @@
         sContext.getPackageManager().setApplicationEnabledSetting(TEST_PKG,
                 PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0);
         sTestPkgUid = sContext.getPackageManager().getPackageUid(TEST_PKG, 0);
-        sCpuFreqTimesAvailable = cpuFreqTimesAvailable();
+        checkCpuTimesAvailability();
     }
 
     // Checks cpu freq times of system uid as an indication of whether /proc/uid_time_in_state
-    // kernel node is available.
-    private static boolean cpuFreqTimesAvailable() throws Exception {
-        final long[] cpuTimes = getAllCpuFreqTimes(Process.SYSTEM_UID);
-        return cpuTimes != null;
+    // and /proc/uid/<uid>/time_in_state kernel nodes are available.
+    private static void checkCpuTimesAvailability() throws Exception {
+        batteryOn();
+        SystemClock.sleep(GENERAL_TIMEOUT_MS);
+        batteryOff();
+        final long[] totalCpuTimes = getAllCpuFreqTimes(Process.SYSTEM_UID);
+        sCpuFreqTimesAvailable = totalCpuTimes != null;
+        final long[] fgSvcCpuTimes = getAllCpuFreqTimes(Process.SYSTEM_UID,
+                PROCESS_STATE_FOREGROUND_SERVICE);
+        sPerProcStateTimesAvailable = fgSvcCpuTimes != null;
     }
 
     @Test
@@ -112,7 +130,8 @@
         forceStop();
         resetBatteryStats();
         final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid);
-        assertNull("Initial snapshot should be null", initialSnapshot);
+        assertNull("Initial snapshot should be null, initial=" + Arrays.toString(initialSnapshot),
+                initialSnapshot);
         doSomeWork();
         forceStop();
 
@@ -136,7 +155,8 @@
         forceStop();
         resetBatteryStats();
         final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid);
-        assertNull("Initial snapshot should be null", initialSnapshot);
+        assertNull("Initial snapshot should be null, initial=" + Arrays.toString(initialSnapshot),
+                initialSnapshot);
         doSomeWork();
         forceStop();
 
@@ -166,7 +186,8 @@
         forceStop();
         resetBatteryStats();
         final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid);
-        assertNull("Initial snapshot should be null", initialSnapshot);
+        assertNull("Initial snapshot should be null, initial=" + Arrays.toString(initialSnapshot),
+                initialSnapshot);
         doSomeWorkInIsolatedProcess();
         forceStop();
 
@@ -180,6 +201,224 @@
         batteryOffScreenOn();
     }
 
+    @Test
+    public void testCpuFreqTimes_stateTop() throws Exception {
+        if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) {
+            return;
+        }
+
+        batteryOnScreenOn();
+        forceStop();
+        resetBatteryStats();
+        final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid);
+        assertNull("Initial snapshot should be null, initial=" + Arrays.toString(initialSnapshot),
+                initialSnapshot);
+        assertNull("Initial top state snapshot should be null",
+                getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_TOP));
+
+        doSomeWork(PROCESS_STATE_TOP);
+        forceStop();
+
+        final long[] cpuTimesMs = getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_TOP);
+        final String msgCpuTimes = getAllCpuTimesMsg();
+        assertCpuTimesValid(cpuTimesMs);
+        long actualCpuTimeMs = 0;
+        for (int i = 0; i < cpuTimesMs.length / 2; ++i) {
+            actualCpuTimeMs += cpuTimesMs[i];
+        }
+        assertApproximateValue("Incorrect total cpu time, " + msgCpuTimes,
+                WORK_DURATION_MS, actualCpuTimeMs);
+        batteryOffScreenOn();
+    }
+
+    @Test
+    public void testIsolatedCpuFreqTimes_stateService() throws Exception {
+        if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) {
+            return;
+        }
+
+        batteryOnScreenOn();
+        forceStop();
+        resetBatteryStats();
+        final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid);
+        assertNull("Initial snapshot should be null, initial=" + Arrays.toString(initialSnapshot),
+                initialSnapshot);
+        assertNull("Initial top state snapshot should be null",
+                getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_TOP));
+
+        final ICmdReceiver activityReceiver = ICmdReceiver.Stub.asInterface(startActivity());
+        final ICmdReceiver isolatedReceiver = ICmdReceiver.Stub.asInterface(startIsolatedService());
+        try {
+            assertProcState(PROCESS_STATE_TOP);
+            isolatedReceiver.doSomeWork(WORK_DURATION_MS);
+        } finally {
+            activityReceiver.finishHost();
+            isolatedReceiver.finishHost();
+        }
+        forceStop();
+
+        final long[] cpuTimesMs = getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_TOP);
+        final String msgCpuTimes = getAllCpuTimesMsg();
+        assertCpuTimesValid(cpuTimesMs);
+        long actualCpuTimeMs = 0;
+        for (int i = 0; i < cpuTimesMs.length / 2; ++i) {
+            actualCpuTimeMs += cpuTimesMs[i];
+        }
+        assertApproximateValue("Incorrect total cpu time, " + msgCpuTimes,
+                WORK_DURATION_MS, actualCpuTimeMs);
+        batteryOffScreenOn();
+    }
+
+    @Test
+    public void testCpuFreqTimes_stateTopSleeping() throws Exception {
+        if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) {
+            return;
+        }
+
+        batteryOnScreenOff();
+        forceStop();
+        resetBatteryStats();
+        final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid);
+        assertNull("Initial snapshot should be null, initial=" + Arrays.toString(initialSnapshot),
+                initialSnapshot);
+        assertNull("Initial top state snapshot should be null",
+                getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_TOP_SLEEPING));
+
+        doSomeWork(PROCESS_STATE_TOP_SLEEPING);
+        forceStop();
+
+        final long[] cpuTimesMs = getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_TOP_SLEEPING);
+        final String msgCpuTimes = getAllCpuTimesMsg();
+        assertCpuTimesValid(cpuTimesMs);
+        long actualCpuTimeMs = 0;
+        for (int i = cpuTimesMs.length / 2; i < cpuTimesMs.length; ++i) {
+            actualCpuTimeMs += cpuTimesMs[i];
+        }
+        assertApproximateValue("Incorrect total cpu time, " + msgCpuTimes,
+                WORK_DURATION_MS, actualCpuTimeMs);
+        batteryOffScreenOn();
+    }
+
+    @Test
+    public void testCpuFreqTimes_stateFgService() throws Exception {
+        if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) {
+            return;
+        }
+
+        batteryOnScreenOff();
+        forceStop();
+        resetBatteryStats();
+        final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid);
+        assertNull("Initial snapshot should be null, initial=" + Arrays.toString(initialSnapshot),
+                initialSnapshot);
+        assertNull("Initial top state snapshot should be null",
+                getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_FOREGROUND_SERVICE));
+
+        doSomeWork(PROCESS_STATE_FOREGROUND_SERVICE);
+        forceStop();
+
+        final long[] cpuTimesMs = getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_FOREGROUND_SERVICE);
+        final String msgCpuTimes = getAllCpuTimesMsg();
+        assertCpuTimesValid(cpuTimesMs);
+        long actualCpuTimeMs = 0;
+        for (int i = 0; i < cpuTimesMs.length / 2; ++i) {
+            actualCpuTimeMs += cpuTimesMs[i];
+        }
+        assertApproximateValue("Incorrect total cpu time, " + msgCpuTimes,
+                WORK_DURATION_MS, actualCpuTimeMs);
+        batteryOffScreenOn();
+    }
+
+    @Test
+    public void testCpuFreqTimes_stateFg() throws Exception {
+        if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) {
+            return;
+        }
+
+        batteryOnScreenOn();
+        forceStop();
+        resetBatteryStats();
+        final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid);
+        assertNull("Initial snapshot should be null, initial=" + Arrays.toString(initialSnapshot),
+                initialSnapshot);
+        assertNull("Initial top state snapshot should be null",
+                getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_FOREGROUND));
+
+        doSomeWork(PROCESS_STATE_FOREGROUND);
+        forceStop();
+
+        final long[] cpuTimesMs = getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_FOREGROUND);
+        final String msgCpuTimes = getAllCpuTimesMsg();
+        assertCpuTimesValid(cpuTimesMs);
+        long actualCpuTimeMs = 0;
+        for (int i = 0; i < cpuTimesMs.length / 2; ++i) {
+            actualCpuTimeMs += cpuTimesMs[i];
+        }
+        assertApproximateValue("Incorrect total cpu time, " + msgCpuTimes,
+                WORK_DURATION_MS, actualCpuTimeMs);
+        batteryOff();
+    }
+
+    @Test
+    public void testCpuFreqTimes_stateBg() throws Exception {
+        if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) {
+            return;
+        }
+
+        batteryOnScreenOff();
+        forceStop();
+        resetBatteryStats();
+        final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid);
+        assertNull("Initial snapshot should be null, initial=" + Arrays.toString(initialSnapshot),
+                initialSnapshot);
+        assertNull("Initial top state snapshot should be null",
+                getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_BACKGROUND));
+
+        doSomeWork(PROCESS_STATE_BACKGROUND);
+        forceStop();
+
+        final long[] cpuTimesMs = getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_BACKGROUND);
+        final String msgCpuTimes = getAllCpuTimesMsg();
+        assertCpuTimesValid(cpuTimesMs);
+        long actualCpuTimeMs = 0;
+        for (int i = 0; i < cpuTimesMs.length / 2; ++i) {
+            actualCpuTimeMs += cpuTimesMs[i];
+        }
+        assertApproximateValue("Incorrect total cpu time, " + msgCpuTimes,
+                WORK_DURATION_MS, actualCpuTimeMs);
+        batteryOffScreenOn();
+    }
+
+    @Test
+    public void testCpuFreqTimes_stateCached() throws Exception {
+        if (!sCpuFreqTimesAvailable || !sPerProcStateTimesAvailable) {
+            return;
+        }
+
+        batteryOnScreenOn();
+        forceStop();
+        resetBatteryStats();
+        final long[] initialSnapshot = getAllCpuFreqTimes(sTestPkgUid);
+        assertNull("Initial snapshot should be null, initial=" + Arrays.toString(initialSnapshot),
+                initialSnapshot);
+        assertNull("Initial top state snapshot should be null",
+                getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_CACHED));
+
+        doSomeWork(PROCESS_STATE_CACHED);
+        forceStop();
+
+        final long[] cpuTimesMs = getAllCpuFreqTimes(sTestPkgUid, PROCESS_STATE_CACHED);
+        final String msgCpuTimes = getAllCpuTimesMsg();
+        assertCpuTimesValid(cpuTimesMs);
+        long actualCpuTimeMs = 0;
+        for (int i = 0; i < cpuTimesMs.length / 2; ++i) {
+            actualCpuTimeMs += cpuTimesMs[i];
+        }
+        assertApproximateValue("Incorrect total cpu time, " + msgCpuTimes,
+                WORK_DURATION_MS, actualCpuTimeMs);
+        batteryOffScreenOn();
+    }
+
     private void assertCpuTimesValid(long[] cpuTimes) {
         assertNotNull(cpuTimes);
         for (int i = 0; i < cpuTimes.length; ++i) {
@@ -219,6 +458,66 @@
         receiver.finishHost();
     }
 
+    private void doSomeWork(int procState) throws Exception {
+        final ICmdReceiver receiver;
+        switch (procState) {
+            case PROCESS_STATE_TOP:
+                receiver = ICmdReceiver.Stub.asInterface(startActivity());
+                break;
+            case PROCESS_STATE_TOP_SLEEPING:
+                receiver = ICmdReceiver.Stub.asInterface(startActivity());
+                break;
+            case PROCESS_STATE_FOREGROUND_SERVICE:
+                receiver = ICmdReceiver.Stub.asInterface(startForegroundService());
+                break;
+            case PROCESS_STATE_FOREGROUND:
+                receiver = ICmdReceiver.Stub.asInterface(startService());
+                receiver.showApplicationOverlay();
+                break;
+            case PROCESS_STATE_BACKGROUND:
+                receiver = ICmdReceiver.Stub.asInterface(startService());
+                break;
+            case PROCESS_STATE_CACHED:
+                receiver = ICmdReceiver.Stub.asInterface(startActivity());
+                receiver.finishHost();
+                break;
+            default:
+                throw new IllegalArgumentException("Unknown state: " + procState);
+        }
+        try {
+            assertProcState(procState);
+            receiver.doSomeWork(WORK_DURATION_MS);
+        } finally {
+            receiver.finishHost();
+        }
+    }
+
+    private void assertProcState(String state) throws Exception {
+        final String expectedState = "(" + state + ")";
+        assertDelayedCondition("", () -> {
+            final String uidStateStr = executeCmd("cmd activity get-uid-state " + sTestPkgUid);
+            final String actualState = uidStateStr.split(" ")[1];
+            return expectedState.equals(actualState) ? null
+                    : "expected=" + expectedState + ", actual" + actualState;
+        });
+    }
+
+    private void assertProcState(int expectedState) throws Exception {
+        assertDelayedCondition("Unexpected proc state", () -> {
+            final String uidStateStr = executeCmd("cmd activity get-uid-state " + sTestPkgUid);
+            final int amProcState = Integer.parseInt(uidStateStr.split(" ")[0]);
+            final int actualState = BatteryStats.mapToInternalProcessState(amProcState);
+            return (actualState == expectedState) ? null
+                    : "expected=" + getStateName(BatteryStats.Uid.class, expectedState)
+                            + ", actual=" + getStateName(BatteryStats.Uid.class, actualState)
+                            + ", amState=" + getStateName(ActivityManager.class, amProcState);
+        });
+    }
+
+    private String getStateName(Class clazz, int procState) {
+        return DebugUtils.valueToString(clazz, "PROCESS_STATE_", procState);
+    }
+
     private IBinder startIsolatedService() throws Exception {
         final CountDownLatch latch = new CountDownLatch(1);
         final IBinder[] binders = new IBinder[1];
@@ -248,6 +547,59 @@
         return null;
     }
 
+    private IBinder startForegroundService() throws Exception {
+        final CountDownLatch latch = new CountDownLatch(1);
+        final Intent launchIntent = new Intent()
+                .setComponent(new ComponentName(TEST_PKG, TEST_SERVICE))
+                .setFlags(FLAG_START_FOREGROUND);
+        final Bundle extras = new Bundle();
+        final IBinder[] binders = new IBinder[1];
+        extras.putBinder(EXTRA_KEY_CMD_RECEIVER, new ICmdCallback.Stub() {
+            @Override
+            public void onLaunched(IBinder receiver) {
+                binders[0] = receiver;
+                latch.countDown();
+            }
+        });
+        launchIntent.putExtras(extras);
+        sContext.startForegroundService(launchIntent);
+        if (latch.await(START_FG_SERVICE_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+            if (binders[0] == null) {
+                fail("Receiver binder should not be null");
+            }
+            return binders[0];
+        } else {
+            fail("Timed out waiting for the test fg service to start; testUid=" + sTestPkgUid);
+        }
+        return null;
+    }
+
+    private IBinder startService() throws Exception {
+        final CountDownLatch latch = new CountDownLatch(1);
+        final Intent launchIntent = new Intent()
+                .setComponent(new ComponentName(TEST_PKG, TEST_SERVICE));
+        final Bundle extras = new Bundle();
+        final IBinder[] binders = new IBinder[1];
+        extras.putBinder(EXTRA_KEY_CMD_RECEIVER, new ICmdCallback.Stub() {
+            @Override
+            public void onLaunched(IBinder receiver) {
+                binders[0] = receiver;
+                latch.countDown();
+            }
+        });
+        launchIntent.putExtras(extras);
+        sContext.startService(launchIntent);
+        if (latch.await(START_SERVICE_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+            if (binders[0] == null) {
+                fail("Receiver binder should not be null");
+            }
+            return binders[0];
+        } else {
+            fail("Timed out waiting for the test service to start; testUid=" + sTestPkgUid);
+        }
+        return null;
+    }
+
     private IBinder startActivity() throws Exception {
         final CountDownLatch latch = new CountDownLatch(1);
         final Intent launchIntent = new Intent()
@@ -256,7 +608,7 @@
         final IBinder[] binders = new IBinder[1];
         extras.putBinder(EXTRA_KEY_CMD_RECEIVER, new ICmdCallback.Stub() {
             @Override
-            public void onActivityLaunched(IBinder receiver) {
+            public void onLaunched(IBinder receiver) {
                 binders[0] = receiver;
                 latch.countDown();
             }
@@ -274,21 +626,63 @@
         return null;
     }
 
+    private static String getAllCpuTimesMsg() throws Exception {
+        final StringBuilder sb = new StringBuilder();
+        sb.append("uid=" + sTestPkgUid + ";");
+        sb.append(UID_TIMES_TYPE_ALL + "=" + getMsgCpuTimesSum(getAllCpuFreqTimes(sTestPkgUid)));
+        for (int i = 0; i < NUM_PROCESS_STATE; ++i) {
+            sb.append("|");
+            sb.append(UID_PROCESS_TYPES[i] + "="
+                    + getMsgCpuTimesSum(getAllCpuFreqTimes(sTestPkgUid, i)));
+        }
+        return sb.toString();
+    }
+
+    private static String getMsgCpuTimesSum(long[] cpuTimes) throws Exception {
+        if (cpuTimes == null) {
+            return "(0,0)";
+        }
+        long totalTime = 0;
+        for (int i = 0; i < cpuTimes.length / 2; ++i) {
+            totalTime += cpuTimes[i];
+        }
+        long screenOffTime = 0;
+        for (int i = cpuTimes.length / 2; i < cpuTimes.length; ++i) {
+            screenOffTime += cpuTimes[i];
+        }
+        return "(" + totalTime + "," + screenOffTime + ")";
+    }
+
     private static long[] getAllCpuFreqTimes(int uid) throws Exception {
         final String checkinDump = executeCmdSilent("dumpsys batterystats --checkin");
-        final Pattern pattern = Pattern.compile(uid + ",l,ctf,A,(.*?)\n");
+        final Pattern pattern = Pattern.compile(uid + ",l,ctf," + UID_TIMES_TYPE_ALL + ",(.*?)\n");
         final Matcher matcher = pattern.matcher(checkinDump);
         if (!matcher.find()) {
             return null;
         }
-        final String[] uidTimesStr = matcher.group(1).split(",");
-        final int freqCount = Integer.parseInt(uidTimesStr[0]);
-        if (uidTimesStr.length != (2 * freqCount + 1)) {
-            fail("Malformed data: " + Arrays.toString(uidTimesStr));
+        return parseCpuTimesStr(matcher.group(1));
+    }
+
+    private static long[] getAllCpuFreqTimes(int uid, int procState) throws Exception {
+        final String checkinDump = executeCmdSilent("dumpsys batterystats --checkin");
+        final Pattern pattern = Pattern.compile(
+                uid + ",l,ctf," + UID_PROCESS_TYPES[procState] + ",(.*?)\n");
+        final Matcher matcher = pattern.matcher(checkinDump);
+        if (!matcher.find()) {
+            return null;
+        }
+        return parseCpuTimesStr(matcher.group(1));
+    }
+
+    private static long[] parseCpuTimesStr(String str) {
+        final String[] cpuTimesStr = str.split(",");
+        final int freqCount = Integer.parseInt(cpuTimesStr[0]);
+        if (cpuTimesStr.length != (2 * freqCount + 1)) {
+            fail("Malformed data: " + Arrays.toString(cpuTimesStr));
         }
         final long[] cpuTimes = new long[freqCount * 2];
         for (int i = 0; i < cpuTimes.length; ++i) {
-            cpuTimes[i] = Long.parseLong(uidTimesStr[i + 1]);
+            cpuTimes[i] = Long.parseLong(cpuTimesStr[i + 1]);
         }
         return cpuTimes;
     }
@@ -312,12 +706,12 @@
         screenOn();
     }
 
-    private void batteryOn() throws Exception {
+    private static void batteryOn() throws Exception {
         executeCmd("dumpsys battery unplug");
         assertBatteryState(false);
     }
 
-    private void batteryOff() throws Exception {
+    private static void batteryOff() throws Exception {
         executeCmd("dumpsys battery reset");
         assertBatteryState(true);
     }
@@ -336,43 +730,41 @@
 
     private void forceStop() throws Exception {
         executeCmd("cmd activity force-stop " + TEST_PKG);
-        assertUidState(PROCESS_STATE_NONEXISTENT);
+        assertProcState("NONEXISTENT");
     }
 
-    private void assertUidState(int state) throws Exception {
-        final String uidStateStr = executeCmd("cmd activity get-uid-state " + sTestPkgUid);
-        final int uidState = Integer.parseInt(uidStateStr.split(" ")[0]);
-        assertEquals(state, uidState);
-    }
-
-    private void assertKeyguardUnLocked() {
+    private void assertKeyguardUnLocked() throws Exception {
         final KeyguardManager keyguardManager =
                 (KeyguardManager) sContext.getSystemService(Context.KEYGUARD_SERVICE);
-        assertDelayedCondition("Keyguard should be unlocked",
-                () -> !keyguardManager.isKeyguardLocked());
+        assertDelayedCondition("Unexpected Keyguard state", () ->
+                keyguardManager.isKeyguardLocked() ? "expected=unlocked" : null
+        );
     }
 
-    private void assertScreenInteractive(boolean interactive) {
+    private void assertScreenInteractive(boolean interactive) throws Exception {
         final PowerManager powerManager =
                 (PowerManager) sContext.getSystemService(Context.POWER_SERVICE);
-        assertDelayedCondition("Unexpected screen interactive state",
-                () -> interactive == powerManager.isInteractive());
+        assertDelayedCondition("Unexpected screen interactive state", () ->
+                interactive == powerManager.isInteractive() ? null : "expected=" + interactive
+        );
     }
 
-    private void assertDelayedCondition(String errorMsg, ExpectedCondition condition) {
+    private void assertDelayedCondition(String errMsgPrefix, ExpectedCondition condition)
+            throws Exception {
         final long endTime = SystemClock.uptimeMillis() + GENERAL_TIMEOUT_MS;
         while (SystemClock.uptimeMillis() <= endTime) {
-            if (condition.isTrue()) {
+            if (condition.getErrIfNotTrue() == null) {
                 return;
             }
             SystemClock.sleep(GENERAL_INTERVAL_MS);
         }
-        if (!condition.isTrue()) {
-            fail(errorMsg);
+        final String errMsg = condition.getErrIfNotTrue();
+        if (errMsg != null) {
+            fail(errMsgPrefix + ": " + errMsg);
         }
     }
 
-    private void assertBatteryState(boolean pluggedIn) throws Exception {
+    private static void assertBatteryState(boolean pluggedIn) throws Exception {
         final long endTime = SystemClock.uptimeMillis() + BATTERY_STATE_TIMEOUT_MS;
         while (isDevicePluggedIn() != pluggedIn && SystemClock.uptimeMillis() <= endTime) {
             Thread.sleep(BATTERY_STATE_CHECK_INTERVAL_MS);
@@ -383,13 +775,13 @@
         }
     }
 
-    private boolean isDevicePluggedIn() {
+    private static boolean isDevicePluggedIn() {
         final Intent batteryIntent = sContext.registerReceiver(null,
                 new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
         return batteryIntent.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1) > 0;
     }
 
-    private String executeCmd(String cmd) throws Exception {
+    private static String executeCmd(String cmd) throws Exception {
         final String result = sUiDevice.executeShellCommand(cmd).trim();
         Log.d(TAG, String.format("Result for '%s': %s", cmd, result));
         return result;
@@ -400,6 +792,6 @@
     }
 
     private interface ExpectedCondition {
-        boolean isTrue();
+        String getErrIfNotTrue() throws Exception;
     }
 }
diff --git a/core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java b/core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java
new file mode 100644
index 0000000..5d72942
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/os/KernelSingleUidTimeReaderTest.java
@@ -0,0 +1,275 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.os;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.SparseArray;
+
+import com.android.internal.os.KernelSingleUidTimeReader.Injector;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Arrays;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class KernelSingleUidTimeReaderTest {
+    private final static int TEST_UID = 2222;
+    private final static int TEST_FREQ_COUNT = 5;
+
+    private KernelSingleUidTimeReader mReader;
+    private TestInjector mInjector;
+
+    @Before
+    public void setUp() {
+        mInjector = new TestInjector();
+        mReader = new KernelSingleUidTimeReader(TEST_FREQ_COUNT, mInjector);
+    }
+
+    @Test
+    public void readDelta() {
+        final SparseArray<long[]> allLastCpuTimes = mReader.getLastUidCpuTimeMs();
+        long[] latestCpuTimes = new long[] {120, 130, 140, 150, 160};
+        mInjector.setData(latestCpuTimes);
+        long[] deltaCpuTimes = mReader.readDeltaMs(TEST_UID);
+        assertCpuTimesEqual(latestCpuTimes, deltaCpuTimes);
+        assertCpuTimesEqual(latestCpuTimes, allLastCpuTimes.get(TEST_UID));
+
+        long[] expectedDeltaTimes = new long[] {200, 340, 1230, 490, 4890};
+        for (int i = 0; i < latestCpuTimes.length; ++i) {
+            latestCpuTimes[i] += expectedDeltaTimes[i];
+        }
+        mInjector.setData(latestCpuTimes);
+        deltaCpuTimes = mReader.readDeltaMs(TEST_UID);
+        assertCpuTimesEqual(expectedDeltaTimes, deltaCpuTimes);
+        assertCpuTimesEqual(latestCpuTimes, allLastCpuTimes.get(TEST_UID));
+
+        // delta should be null if cpu times haven't changed
+        deltaCpuTimes = mReader.readDeltaMs(TEST_UID);
+        assertCpuTimesEqual(null, deltaCpuTimes);
+        assertCpuTimesEqual(latestCpuTimes, allLastCpuTimes.get(TEST_UID));
+
+        // Malformed data (-ve)
+        long[] malformedLatestTimes = new long[latestCpuTimes.length];
+        for (int i = 0; i < latestCpuTimes.length; ++i) {
+            if (i == 1) {
+                malformedLatestTimes[i] = -4;
+            } else {
+                malformedLatestTimes[i] = latestCpuTimes[i] + i * 42;
+            }
+        }
+        mInjector.setData(malformedLatestTimes);
+        deltaCpuTimes = mReader.readDeltaMs(TEST_UID);
+        assertCpuTimesEqual(null, deltaCpuTimes);
+        assertCpuTimesEqual(latestCpuTimes, allLastCpuTimes.get(TEST_UID));
+
+        // Malformed data (decreased)
+        malformedLatestTimes = new long[latestCpuTimes.length];
+        for (int i = 0; i < latestCpuTimes.length; ++i) {
+            if (i == 1) {
+                malformedLatestTimes[i] = latestCpuTimes[i] - 4;
+            } else {
+                malformedLatestTimes[i] = latestCpuTimes[i] + i * 42;
+            }
+        }
+        mInjector.setData(malformedLatestTimes);
+        deltaCpuTimes = mReader.readDeltaMs(TEST_UID);
+        assertCpuTimesEqual(null, deltaCpuTimes);
+        assertCpuTimesEqual(latestCpuTimes, allLastCpuTimes.get(TEST_UID));
+    }
+
+    @Test
+    public void readDelta_fileNotAvailable() {
+        mInjector.letReadDataThrowException(true);
+
+        for (int i = 0; i < KernelSingleUidTimeReader.TOTAL_READ_ERROR_COUNT; ++i) {
+            assertTrue(mReader.singleUidCpuTimesAvailable());
+            mReader.readDeltaMs(TEST_UID);
+        }
+        assertFalse(mReader.singleUidCpuTimesAvailable());
+    }
+
+    @Test
+    public void testComputeDelta() {
+        // proc file not available
+        mReader.setSingleUidCpuTimesAvailable(false);
+        long[] latestCpuTimes = new long[] {12, 13, 14, 15, 16};
+        long[] deltaCpuTimes = mReader.computeDelta(TEST_UID, latestCpuTimes);
+        assertCpuTimesEqual(null, deltaCpuTimes);
+
+        // cpu times have changed
+        mReader.setSingleUidCpuTimesAvailable(true);
+        SparseArray<long[]> allLastCpuTimes = mReader.getLastUidCpuTimeMs();
+        long[] lastCpuTimes = new long[] {12, 13, 14, 15, 16};
+        allLastCpuTimes.put(TEST_UID, lastCpuTimes);
+        long[] expectedDeltaTimes = new long[] {123, 324, 43, 989, 80};
+        for (int i = 0; i < latestCpuTimes.length; ++i) {
+            latestCpuTimes[i] = lastCpuTimes[i] + expectedDeltaTimes[i];
+        }
+        deltaCpuTimes = mReader.computeDelta(TEST_UID, latestCpuTimes);
+        assertCpuTimesEqual(expectedDeltaTimes, deltaCpuTimes);
+        assertCpuTimesEqual(latestCpuTimes, allLastCpuTimes.get(TEST_UID));
+
+        // no change in cpu times
+        deltaCpuTimes = mReader.computeDelta(TEST_UID, latestCpuTimes);
+        assertCpuTimesEqual(null, deltaCpuTimes);
+        assertCpuTimesEqual(latestCpuTimes, allLastCpuTimes.get(TEST_UID));
+
+        // Malformed cpu times (-ve)
+        long[] malformedLatestTimes = new long[latestCpuTimes.length];
+        for (int i = 0; i < latestCpuTimes.length; ++i) {
+            if (i == 1) {
+                malformedLatestTimes[i] = -4;
+            } else {
+                malformedLatestTimes[i] = latestCpuTimes[i] + i * 42;
+            }
+        }
+        deltaCpuTimes = mReader.computeDelta(TEST_UID, malformedLatestTimes);
+        assertCpuTimesEqual(null, deltaCpuTimes);
+        assertCpuTimesEqual(latestCpuTimes, allLastCpuTimes.get(TEST_UID));
+
+        // Malformed cpu times (decreased)
+        for (int i = 0; i < latestCpuTimes.length; ++i) {
+            if (i == 1) {
+                malformedLatestTimes[i] = latestCpuTimes[i] - 4;
+            } else {
+                malformedLatestTimes[i] = latestCpuTimes[i] + i * 42;
+            }
+        }
+        deltaCpuTimes = mReader.computeDelta(TEST_UID, malformedLatestTimes);
+        assertCpuTimesEqual(null, deltaCpuTimes);
+        assertCpuTimesEqual(latestCpuTimes, allLastCpuTimes.get(TEST_UID));
+    }
+
+    @Test
+    public void testGetDelta() {
+        // No last cpu times
+        long[] lastCpuTimes = null;
+        long[] latestCpuTimes = new long[] {12, 13, 14, 15, 16};
+        long[] deltaCpuTimes = mReader.getDeltaLocked(lastCpuTimes, latestCpuTimes);
+        assertCpuTimesEqual(latestCpuTimes, deltaCpuTimes);
+
+        // Latest cpu times are -ve
+        lastCpuTimes = new long[] {12, 13, 14, 15, 16};
+        latestCpuTimes = new long[] {15, -10, 19, 21, 23};
+        deltaCpuTimes = mReader.getDeltaLocked(lastCpuTimes, latestCpuTimes);
+        assertCpuTimesEqual(null, deltaCpuTimes);
+
+        // Latest cpu times are less than last cpu times
+        lastCpuTimes = new long[] {12, 13, 14, 15, 16};
+        latestCpuTimes = new long[] {15, 11, 21, 34, 171};
+        deltaCpuTimes = mReader.getDeltaLocked(lastCpuTimes, latestCpuTimes);
+        assertCpuTimesEqual(null, deltaCpuTimes);
+
+        lastCpuTimes = new long[] {12, 13, 14, 15, 16};
+        latestCpuTimes = new long[] {112, 213, 314, 415, 516};
+        deltaCpuTimes = mReader.getDeltaLocked(lastCpuTimes, latestCpuTimes);
+        assertCpuTimesEqual(new long[] {100, 200, 300, 400, 500}, deltaCpuTimes);
+    }
+
+    @Test
+    public void testRemoveUid() {
+        final SparseArray<long[]> lastUidCpuTimes = mReader.getLastUidCpuTimeMs();
+        lastUidCpuTimes.put(12, new long[] {});
+        lastUidCpuTimes.put(16, new long[] {});
+
+        mReader.removeUid(12);
+        assertFalse("Removal failed, cpuTimes=" + lastUidCpuTimes,
+                lastUidCpuTimes.indexOfKey(12) >= 0);
+        mReader.removeUid(16);
+        assertFalse("Removal failed, cpuTimes=" + lastUidCpuTimes,
+                lastUidCpuTimes.indexOfKey(16) >= 0);
+    }
+
+    @Test
+    public void testRemoveUidsRange() {
+        final SparseArray<long[]> lastUidCpuTimes = mReader.getLastUidCpuTimeMs();
+        final int startUid = 12;
+        final int endUid = 24;
+
+        for (int i = startUid; i <= endUid; ++i) {
+            lastUidCpuTimes.put(startUid, new long[] {});
+        }
+        mReader.removeUidsInRange(startUid, endUid);
+        assertEquals("There shouldn't be any items left, cpuTimes=" + lastUidCpuTimes,
+                0, lastUidCpuTimes.size());
+
+        for (int i = startUid; i <= endUid; ++i) {
+            lastUidCpuTimes.put(startUid, new long[] {});
+        }
+        mReader.removeUidsInRange(startUid - 1, endUid);
+        assertEquals("There shouldn't be any items left, cpuTimes=" + lastUidCpuTimes,
+                0, lastUidCpuTimes.size());
+
+        for (int i = startUid; i <= endUid; ++i) {
+            lastUidCpuTimes.put(startUid, new long[] {});
+        }
+        mReader.removeUidsInRange(startUid, endUid + 1);
+        assertEquals("There shouldn't be any items left, cpuTimes=" + lastUidCpuTimes,
+                0, lastUidCpuTimes.size());
+
+        for (int i = startUid; i <= endUid; ++i) {
+            lastUidCpuTimes.put(startUid, new long[] {});
+        }
+        mReader.removeUidsInRange(startUid - 1, endUid + 1);
+        assertEquals("There shouldn't be any items left, cpuTimes=" + lastUidCpuTimes,
+                0, lastUidCpuTimes.size());
+    }
+
+    private void assertCpuTimesEqual(long[] expected, long[] actual) {
+        assertArrayEquals("Expected=" + Arrays.toString(expected)
+                + ", Actual=" + Arrays.toString(actual), expected, actual);
+    }
+
+    class TestInjector extends Injector {
+        private byte[] mData;
+        private boolean mThrowExcpetion;
+
+        @Override
+        public byte[] readData(String procFile) throws IOException {
+            if (mThrowExcpetion) {
+                throw new IOException("In the test");
+            } else {
+                return mData;
+            }
+        }
+
+        public void setData(long[] cpuTimes) {
+            final ByteBuffer buffer = ByteBuffer.allocate(cpuTimes.length * Long.BYTES);
+            buffer.order(ByteOrder.nativeOrder());
+            for (long time : cpuTimes) {
+                buffer.putLong(time / 10);
+            }
+            mData = buffer.array();
+        }
+
+        public void letReadDataThrowException(boolean throwException) {
+            mThrowExcpetion = throwException;
+        }
+    }
+}
diff --git a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
index 63d1e5a..de2fd12 100644
--- a/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
+++ b/core/tests/coretests/src/com/android/internal/os/MockBatteryStatsImpl.java
@@ -16,7 +16,10 @@
 
 package com.android.internal.os;
 
+import android.util.SparseIntArray;
+
 import java.util.ArrayList;
+import java.util.concurrent.Future;
 
 /**
  * Mocks a BatteryStatsImpl object.
@@ -33,6 +36,7 @@
         mScreenDozeTimer = new BatteryStatsImpl.StopwatchTimer(clocks, null, -1, null,
                 mOnBatteryTimeBase);
         mBluetoothScanTimer = new StopwatchTimer(mClocks, null, -14, null, mOnBatteryTimeBase);
+        setExternalStatsSyncLocked(new DummyExternalStatsSync());
     }
 
     MockBatteryStatsImpl() {
@@ -78,6 +82,11 @@
         return this;
     }
 
+    public MockBatteryStatsImpl setKernelSingleUidTimeReader(KernelSingleUidTimeReader reader) {
+        mKernelSingleUidTimeReader = reader;
+        return this;
+    }
+
     public MockBatteryStatsImpl setKernelCpuSpeedReaders(KernelCpuSpeedReader[] readers) {
         mKernelCpuSpeedReaders = readers;
         return this;
@@ -102,5 +111,32 @@
         mOnBatteryInternal = onBatteryInternal;
         return this;
     }
+
+    public SparseIntArray getPendingUids() {
+        return mPendingUids;
+    }
+
+    private class DummyExternalStatsSync implements ExternalStatsSync {
+        @Override
+        public Future<?> scheduleSync(String reason, int flags) {
+            return null;
+        }
+
+        @Override
+        public Future<?> scheduleCpuSyncDueToRemovedUid(int uid) {
+            return null;
+        }
+
+        @Override
+        public Future<?> scheduleReadProcStateCpuTimes() {
+            return null;
+        }
+
+        @Override
+        public Future<?> scheduleCopyFromAllUidsCpuTimes() {
+            return null;
+        }
+
+    }
 }
 
diff --git a/core/tests/packagemanagertests/Android.mk b/core/tests/packagemanagertests/Android.mk
index c1e8c98..5bfde78 100644
--- a/core/tests/packagemanagertests/Android.mk
+++ b/core/tests/packagemanagertests/Android.mk
@@ -10,7 +10,8 @@
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
     android-support-test \
-    frameworks-base-testutils
+    frameworks-base-testutils \
+    mockito-target-minus-junit4
 
 LOCAL_JAVA_LIBRARIES := android.test.runner
 LOCAL_PACKAGE_NAME := FrameworksCorePackageManagerTests
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 8e7147c..7bb2859 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -236,6 +236,7 @@
         <permission name="android.permission.CHANGE_CONFIGURATION"/>
         <permission name="android.permission.DELETE_PACKAGES"/>
         <permission name="android.permission.FORCE_STOP_PACKAGES"/>
+        <permission name="android.permission.LOCAL_MAC_ADDRESS"/>
         <permission name="android.permission.MANAGE_DEVICE_ADMINS"/>
         <permission name="android.permission.MANAGE_FINGERPRINT"/>
         <permission name="android.permission.MANAGE_USB"/>
diff --git a/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationApps.java b/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationApps.java
index 3f826cc..6025d68 100644
--- a/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationApps.java
+++ b/packages/SettingsLib/src/com/android/settingslib/location/RecentLocationApps.java
@@ -16,21 +16,21 @@
 
 package com.android.settingslib.location;
 
-import android.app.AppGlobals;
 import android.app.AppOpsManager;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
 import android.graphics.drawable.Drawable;
 import android.os.Process;
-import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.support.annotation.VisibleForTesting;
 import android.util.IconDrawableFactory;
 import android.util.Log;
-
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
 import java.util.List;
 
 /**
@@ -38,11 +38,13 @@
  */
 public class RecentLocationApps {
     private static final String TAG = RecentLocationApps.class.getSimpleName();
-    private static final String ANDROID_SYSTEM_PACKAGE_NAME = "android";
+    @VisibleForTesting
+    static final String ANDROID_SYSTEM_PACKAGE_NAME = "android";
 
     private static final int RECENT_TIME_INTERVAL_MILLIS = 15 * 60 * 1000;
 
-    private static final int[] LOCATION_OPS = new int[] {
+    @VisibleForTesting
+    static final int[] LOCATION_OPS = new int[] {
             AppOpsManager.OP_MONITOR_LOCATION,
             AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION,
     };
@@ -59,6 +61,7 @@
 
     /**
      * Fills a list of applications which queried location recently within specified time.
+     * Apps are sorted by recency. Apps with more recent location requests are in the front.
      */
     public List<Request> getAppList() {
         // Retrieve a location usage list from AppOps
@@ -91,7 +94,18 @@
                 requests.add(request);
             }
         }
+        return requests;
+    }
 
+    public List<Request> getAppListSorted() {
+        List<Request> requests = getAppList();
+        // Sort the list of Requests by recency. Most recent request first.
+        Collections.sort(requests, Collections.reverseOrder(new Comparator<Request>() {
+            @Override
+            public int compare(Request request1, Request request2) {
+                return Long.compare(request1.requestFinishTime, request2.requestFinishTime);
+            }
+        }));
         return requests;
     }
 
@@ -108,10 +122,12 @@
         List<AppOpsManager.OpEntry> entries = ops.getOps();
         boolean highBattery = false;
         boolean normalBattery = false;
+        long locationRequestFinishTime = 0L;
         // Earliest time for a location request to end and still be shown in list.
         long recentLocationCutoffTime = now - RECENT_TIME_INTERVAL_MILLIS;
         for (AppOpsManager.OpEntry entry : entries) {
             if (entry.isRunning() || entry.getTime() >= recentLocationCutoffTime) {
+                locationRequestFinishTime = entry.getTime() + entry.getDuration();
                 switch (entry.getOp()) {
                     case AppOpsManager.OP_MONITOR_LOCATION:
                         normalBattery = true;
@@ -133,15 +149,13 @@
         }
 
         // The package is fresh enough, continue.
-
         int uid = ops.getUid();
         int userId = UserHandle.getUserId(uid);
 
         Request request = null;
         try {
-            IPackageManager ipm = AppGlobals.getPackageManager();
-            ApplicationInfo appInfo =
-                    ipm.getApplicationInfo(packageName, PackageManager.GET_META_DATA, userId);
+            ApplicationInfo appInfo = mPackageManager.getApplicationInfoAsUser(
+                    packageName, PackageManager.GET_META_DATA, userId);
             if (appInfo == null) {
                 Log.w(TAG, "Null application info retrieved for package " + packageName
                         + ", userId " + userId);
@@ -158,12 +172,10 @@
                 badgedAppLabel = null;
             }
             request = new Request(packageName, userHandle, icon, appLabel, highBattery,
-                    badgedAppLabel);
-        } catch (RemoteException e) {
-            Log.w(TAG, "Error while retrieving application info for package " + packageName
-                    + ", userId " + userId, e);
+                    badgedAppLabel, locationRequestFinishTime);
+        } catch (NameNotFoundException e) {
+            Log.w(TAG, "package name not found for " + packageName + ", userId " + userId);
         }
-
         return request;
     }
 
@@ -174,15 +186,18 @@
         public final CharSequence label;
         public final boolean isHighBattery;
         public final CharSequence contentDescription;
+        public final long requestFinishTime;
 
         private Request(String packageName, UserHandle userHandle, Drawable icon,
-                CharSequence label, boolean isHighBattery, CharSequence contentDescription) {
+                CharSequence label, boolean isHighBattery, CharSequence contentDescription,
+                long requestFinishTime) {
             this.packageName = packageName;
             this.userHandle = userHandle;
             this.icon = icon;
             this.label = label;
             this.isHighBattery = isHighBattery;
             this.contentDescription = contentDescription;
+            this.requestFinishTime = requestFinishTime;
         }
     }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/location/RecentLocationAppsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/location/RecentLocationAppsTest.java
new file mode 100644
index 0000000..226166b
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/location/RecentLocationAppsTest.java
@@ -0,0 +1,162 @@
+package com.android.settingslib.location;
+
+import static org.mockito.Matchers.isA;
+import static org.mockito.Mockito.when;
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.AppOpsManager;
+import android.app.AppOpsManager.OpEntry;
+import android.app.AppOpsManager.PackageOps;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Resources;
+import android.os.Process;
+import android.os.UserHandle;
+import android.os.UserManager;
+import com.android.settingslib.SettingsLibRobolectricTestRunner;
+import com.android.settingslib.TestConfig;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+
+@RunWith(SettingsLibRobolectricTestRunner.class)
+@Config(
+        manifest = TestConfig.MANIFEST_PATH,
+        sdk = TestConfig.SDK_VERSION)
+public class RecentLocationAppsTest {
+
+    private static final int TEST_UID = 1234;
+    private static final long NOW = System.currentTimeMillis();
+    // App running duration in milliseconds
+    private static final int DURATION = 10;
+    private static final long ONE_MIN_AGO = NOW - TimeUnit.MINUTES.toMillis(1);
+    private static final long FOURTEEN_MIN_AGO = NOW - TimeUnit.MINUTES.toMillis(14);
+    private static final long TWENTY_MIN_AGO = NOW - TimeUnit.MINUTES.toMillis(20);
+    private static final String[] TEST_PACKAGE_NAMES =
+            {"package_1MinAgo", "package_14MinAgo", "package_20MinAgo"};
+
+    @Mock
+    private Context mContext;
+    @Mock
+    private PackageManager mPackageManager;
+    @Mock
+    private AppOpsManager mAppOpsManager;
+    @Mock
+    private Resources mResources;
+    @Mock
+    private UserManager mUserManager;
+    private int mTestUserId;
+    private RecentLocationApps mRecentLocationApps;
+
+
+
+    @Before
+    public void setUp() throws NameNotFoundException {
+        MockitoAnnotations.initMocks(this);
+
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+        when(mContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOpsManager);
+        when(mContext.getResources()).thenReturn(mResources);
+        when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager);
+        when(mContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
+        when(mPackageManager.getApplicationLabel(isA(ApplicationInfo.class)))
+                .thenReturn("testApplicationLabel");
+        when(mPackageManager.getUserBadgedLabel(isA(CharSequence.class), isA(UserHandle.class)))
+                .thenReturn("testUserBadgedLabel");
+        mTestUserId = UserHandle.getUserId(TEST_UID);
+        when(mUserManager.getUserProfiles())
+                .thenReturn(Collections.singletonList(new UserHandle(mTestUserId)));
+
+        long[] testRequestTime = {ONE_MIN_AGO, FOURTEEN_MIN_AGO, TWENTY_MIN_AGO};
+        List<PackageOps> appOps = createTestPackageOpsList(TEST_PACKAGE_NAMES, testRequestTime);
+        when(mAppOpsManager.getPackagesForOps(RecentLocationApps.LOCATION_OPS)).thenReturn(appOps);
+        mockTestApplicationInfos(mTestUserId, TEST_PACKAGE_NAMES);
+
+        mRecentLocationApps = new RecentLocationApps(mContext);
+    }
+
+    @Test
+    public void testGetAppList_shouldFilterRecentApps() {
+        List<RecentLocationApps.Request> requests = mRecentLocationApps.getAppList();
+        // Only two of the apps have requested location within 15 min.
+        assertThat(requests).hasSize(2);
+        // Make sure apps are ordered by recency
+        assertThat(requests.get(0).packageName).isEqualTo(TEST_PACKAGE_NAMES[0]);
+        assertThat(requests.get(0).requestFinishTime).isEqualTo(ONE_MIN_AGO + DURATION);
+        assertThat(requests.get(1).packageName).isEqualTo(TEST_PACKAGE_NAMES[1]);
+        assertThat(requests.get(1).requestFinishTime).isEqualTo(FOURTEEN_MIN_AGO + DURATION);
+    }
+
+    @Test
+    public void testGetAppList_shouldNotShowAndroidOS() throws NameNotFoundException {
+        // Add android OS to the list of apps.
+        PackageOps androidSystemPackageOps =
+                createPackageOps(
+                        RecentLocationApps.ANDROID_SYSTEM_PACKAGE_NAME,
+                        Process.SYSTEM_UID,
+                        AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION,
+                        ONE_MIN_AGO,
+                        DURATION);
+        long[] testRequestTime =
+                {ONE_MIN_AGO, FOURTEEN_MIN_AGO, TWENTY_MIN_AGO, ONE_MIN_AGO};
+        List<PackageOps> appOps = createTestPackageOpsList(TEST_PACKAGE_NAMES, testRequestTime);
+        appOps.add(androidSystemPackageOps);
+        when(mAppOpsManager.getPackagesForOps(RecentLocationApps.LOCATION_OPS)).thenReturn(appOps);
+        mockTestApplicationInfos(
+                Process.SYSTEM_UID, RecentLocationApps.ANDROID_SYSTEM_PACKAGE_NAME);
+
+        List<RecentLocationApps.Request> requests = mRecentLocationApps.getAppList();
+        // Android OS shouldn't show up in the list of apps.
+        assertThat(requests).hasSize(2);
+        // Make sure apps are ordered by recency
+        assertThat(requests.get(0).packageName).isEqualTo(TEST_PACKAGE_NAMES[0]);
+        assertThat(requests.get(0).requestFinishTime).isEqualTo(ONE_MIN_AGO + DURATION);
+        assertThat(requests.get(1).packageName).isEqualTo(TEST_PACKAGE_NAMES[1]);
+        assertThat(requests.get(1).requestFinishTime).isEqualTo(FOURTEEN_MIN_AGO + DURATION);
+    }
+
+    private void mockTestApplicationInfos(int userId, String... packageNameList)
+            throws NameNotFoundException {
+        for (String packageName : packageNameList) {
+            ApplicationInfo appInfo = new ApplicationInfo();
+            appInfo.packageName = packageName;
+            when(mPackageManager.getApplicationInfoAsUser(
+                    packageName, PackageManager.GET_META_DATA, userId)).thenReturn(appInfo);
+        }
+    }
+
+    private List<PackageOps> createTestPackageOpsList(String[] packageNameList, long[] time) {
+        List<PackageOps> packageOpsList = new ArrayList<>();
+        for (int i = 0; i < packageNameList.length ; i++) {
+            PackageOps packageOps = createPackageOps(
+                    packageNameList[i],
+                    TEST_UID,
+                    AppOpsManager.OP_MONITOR_LOCATION,
+                    time[i],
+                    DURATION);
+            packageOpsList.add(packageOps);
+        }
+        return packageOpsList;
+    }
+
+    private PackageOps createPackageOps(
+            String packageName, int uid, int op, long time, int duration) {
+        return new PackageOps(
+                packageName,
+                uid,
+                Collections.singletonList(createOpEntryWithTime(op, time, duration)));
+    }
+
+    private OpEntry createOpEntryWithTime(int op, long time, int duration) {
+        return new OpEntry(op, AppOpsManager.MODE_ALLOWED, time, 0L, duration, 0, "");
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/EmulatedDisplayCutout.java b/packages/SystemUI/src/com/android/systemui/EmulatedDisplayCutout.java
index edd1748..6aa465c 100644
--- a/packages/SystemUI/src/com/android/systemui/EmulatedDisplayCutout.java
+++ b/packages/SystemUI/src/com/android/systemui/EmulatedDisplayCutout.java
@@ -24,6 +24,7 @@
 import android.graphics.Path;
 import android.graphics.PixelFormat;
 import android.graphics.Point;
+import android.graphics.Region;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.UserHandle;
@@ -36,7 +37,8 @@
 import android.view.WindowInsets;
 import android.view.WindowManager;
 
-import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 
 /**
  * Emulates a display cutout by drawing its shape in an overlay as supplied by
@@ -85,6 +87,7 @@
                 PixelFormat.TRANSLUCENT);
         lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS
                 | WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
+        lp.flags2 |= WindowManager.LayoutParams.FLAG2_LAYOUT_IN_DISPLAY_CUTOUT_AREA;
         lp.setTitle("EmulatedDisplayCutout");
         lp.gravity = Gravity.TOP;
         return lp;
@@ -102,9 +105,8 @@
     };
 
     private static class CutoutView extends View {
-        private Paint mPaint = new Paint();
-        private Path mPath = new Path();
-        private ArrayList<Point> mBoundingPolygon = new ArrayList<>();
+        private final Paint mPaint = new Paint();
+        private final Path mBounds = new Path();
 
         CutoutView(Context context) {
             super(context);
@@ -112,28 +114,22 @@
 
         @Override
         public WindowInsets onApplyWindowInsets(WindowInsets insets) {
-            insets.getDisplayCutout().getBoundingPolygon(mBoundingPolygon);
+            if (insets.getDisplayCutout() != null) {
+                insets.getDisplayCutout().getBounds().getBoundaryPath(mBounds);
+            } else {
+                mBounds.reset();
+            }
             invalidate();
-            return insets.consumeCutout();
+            return insets.consumeDisplayCutout();
         }
 
         @Override
         protected void onDraw(Canvas canvas) {
-            if (!mBoundingPolygon.isEmpty()) {
+            if (!mBounds.isEmpty()) {
                 mPaint.setColor(Color.DKGRAY);
                 mPaint.setStyle(Paint.Style.FILL);
 
-                mPath.reset();
-                for (int i = 0; i < mBoundingPolygon.size(); i++) {
-                    Point point = mBoundingPolygon.get(i);
-                    if (i == 0) {
-                        mPath.moveTo(point.x, point.y);
-                    } else {
-                        mPath.lineTo(point.x, point.y);
-                    }
-                }
-                mPath.close();
-                canvas.drawPath(mPath, mPaint);
+                canvas.drawPath(mBounds, mPaint);
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java b/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java
index 50c1ede..ee41001 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeWallpaperState.java
@@ -53,6 +53,8 @@
         final boolean isAmbientMode;
         switch (newState) {
             case DOZE_AOD:
+            case DOZE_AOD_PAUSING:
+            case DOZE_AOD_PAUSED:
             case DOZE_REQUEST_PULSE:
             case DOZE_PULSING:
             case DOZE_PULSE_DONE:
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 75321fd..1cbb440 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -98,7 +98,7 @@
         setClipToActualHeight(false);
         setClipChildren(false);
         setClipToPadding(false);
-        mShelfIcons.setShowAllIcons(false);
+        mShelfIcons.setIsStaticLayout(false);
         mViewInvertHelper = new ViewInvertHelper(mShelfIcons,
                 NotificationPanelView.DOZE_ANIMATION_DURATION);
         mShelfState = new ShelfState();
@@ -681,7 +681,8 @@
         if (isLayoutRtl()) {
             start = getWidth() - start - mCollapsedIcons.getWidth();
         }
-        int width = (int) NotificationUtils.interpolate(start + mCollapsedIcons.getWidth(),
+        int width = (int) NotificationUtils.interpolate(
+                start + mCollapsedIcons.getFinalTranslationX(),
                 mShelfIcons.getWidth(),
                 openedAmount);
         mShelfIcons.setActualLayoutWidth(width);
@@ -691,6 +692,9 @@
             // we have to ensure that adding the low priority notification won't lead to an
             // overflow
             collapsedPadding -= (1.0f + OVERFLOW_EARLY_AMOUNT) * mCollapsedIcons.getIconSize();
+        } else {
+            // Partial overflow padding will fill enough space to add extra dots
+            collapsedPadding -= mCollapsedIcons.getPartialOverflowExtraPadding();
         }
         float padding = NotificationUtils.interpolate(collapsedPadding,
                 mShelfIcons.getPaddingEnd(),
@@ -700,7 +704,6 @@
                 mShelfIcons.getPaddingStart(), openedAmount);
         mShelfIcons.setActualPaddingStart(paddingStart);
         mShelfIcons.setOpenedAmount(openedAmount);
-        mShelfIcons.setVisualOverflowAdaption(mCollapsedIcons.getVisualOverflowAdaption());
     }
 
     public void setMaxLayoutHeight(int maxLayoutHeight) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
index 316bd5b..7f4deb0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ManagedProfileControllerImpl.java
@@ -61,14 +61,10 @@
     public void setWorkModeEnabled(boolean enableWorkMode) {
         synchronized (mProfiles) {
             for (UserInfo ui : mProfiles) {
-                if (enableWorkMode) {
-                    if (!mUserManager.trySetQuietModeDisabled(ui.id, null)) {
-                        StatusBarManager statusBarManager = (StatusBarManager) mContext
-                                .getSystemService(android.app.Service.STATUS_BAR_SERVICE);
-                        statusBarManager.collapsePanels();
-                    }
-                } else {
-                    mUserManager.setQuietModeEnabled(ui.id, true);
+                if (!mUserManager.trySetQuietModeEnabled(!enableWorkMode, UserHandle.of(ui.id))) {
+                    StatusBarManager statusBarManager = (StatusBarManager) mContext
+                            .getSystemService(android.app.Service.STATUS_BAR_SERVICE);
+                    statusBarManager.collapsePanels();
                 }
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index a1b49c1..91cae0a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -100,8 +100,10 @@
     }.setDuration(200).setDelay(50);
 
     public static final int MAX_VISIBLE_ICONS_WHEN_DARK = 5;
+    public static final int MAX_STATIC_ICONS = 4;
+    private static final int MAX_DOTS = 3;
 
-    private boolean mShowAllIcons = true;
+    private boolean mIsStaticLayout = true;
     private final HashMap<View, IconState> mIconStates = new HashMap<>();
     private int mDotPadding;
     private int mStaticDotRadius;
@@ -115,11 +117,13 @@
     private int mSpeedBumpIndex = -1;
     private int mIconSize;
     private float mOpenedAmount = 0.0f;
-    private float mVisualOverflowAdaption;
     private boolean mDisallowNextAnimation;
     private boolean mAnimationsEnabled = true;
     private ArrayMap<String, ArrayList<StatusBarIcon>> mReplacingIcons;
     private int mDarkOffsetX;
+    // Keep track of the last visible icon so collapsed container can report on its location
+    private IconState mLastVisibleIconState;
+
 
     public NotificationIconContainer(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -163,7 +167,7 @@
                 mIconSize = child.getWidth();
             }
         }
-        if (mShowAllIcons) {
+        if (mIsStaticLayout) {
             resetViewStates();
             calculateIconTranslations();
             applyIconStates();
@@ -287,7 +291,8 @@
         float translationX = getActualPaddingStart();
         int firstOverflowIndex = -1;
         int childCount = getChildCount();
-        int maxVisibleIcons = mDark ? MAX_VISIBLE_ICONS_WHEN_DARK : childCount;
+        int maxVisibleIcons = mDark ? MAX_VISIBLE_ICONS_WHEN_DARK :
+                    mIsStaticLayout ? MAX_STATIC_ICONS : childCount;
         float layoutEnd = getLayoutEnd();
         float overflowStart = layoutEnd - mIconSize * (2 + OVERFLOW_EARLY_AMOUNT);
         boolean hasAmbient = mSpeedBumpIndex != -1 && mSpeedBumpIndex < getChildCount();
@@ -320,23 +325,6 @@
                     visualOverflowStart += (translationX - overflowStart) / mIconSize
                             * (mStaticDotRadius * 2 + mDotPadding);
                 }
-                if (mShowAllIcons) {
-                    // We want to perfectly position the overflow in the static state, such that
-                    // it's perfectly centered instead of measuring it from the end.
-                    mVisualOverflowAdaption = 0;
-                    if (firstOverflowIndex != -1) {
-                        View firstOverflowView = getChildAt(i);
-                        IconState overflowState = mIconStates.get(firstOverflowView);
-                        float totalAmount = layoutEnd - overflowState.xTranslation;
-                        float newPosition = overflowState.xTranslation + totalAmount / 2
-                                - totalDotLength / 2
-                                - mIconSize * 0.5f + mStaticDotRadius;
-                        mVisualOverflowAdaption = newPosition - visualOverflowStart;
-                        visualOverflowStart = newPosition;
-                    }
-                } else {
-                    visualOverflowStart += mVisualOverflowAdaption * (1f - mOpenedAmount);
-                }
             }
             translationX += iconState.iconAppearAmount * view.getWidth() * drawingScale;
         }
@@ -348,20 +336,24 @@
                 IconState iconState = mIconStates.get(view);
                 int dotWidth = mStaticDotRadius * 2 + mDotPadding;
                 iconState.xTranslation = translationX;
-                if (numDots <= 3) {
+                if (numDots <= MAX_DOTS) {
                     if (numDots == 1 && iconState.iconAppearAmount < 0.8f) {
                         iconState.visibleState = StatusBarIconView.STATE_ICON;
                         numDots--;
                     } else {
                         iconState.visibleState = StatusBarIconView.STATE_DOT;
                     }
-                    translationX += (numDots == 3 ? 3 * dotWidth : dotWidth)
+                    translationX += (numDots == MAX_DOTS ? MAX_DOTS * dotWidth : dotWidth)
                             * iconState.iconAppearAmount;
+                    mLastVisibleIconState = iconState;
                 } else {
                     iconState.visibleState = StatusBarIconView.STATE_HIDDEN;
                 }
                 numDots++;
             }
+        } else if (childCount > 0) {
+            View lastChild = getChildAt(childCount - 1);
+            mLastVisibleIconState = mIconStates.get(lastChild);
         }
         boolean center = mDark;
         if (center && translationX < getLayoutEnd()) {
@@ -415,13 +407,13 @@
     }
 
     /**
-     * Sets whether the layout should always show all icons.
+     * Sets whether the layout should always show the same number of icons.
      * If this is true, the icon positions will be updated on layout.
      * If this if false, the layout is managed from the outside and layouting won't trigger a
      * repositioning of the icons.
      */
-    public void setShowAllIcons(boolean showAllIcons) {
-        mShowAllIcons = showAllIcons;
+    public void setIsStaticLayout(boolean isStaticLayout) {
+        mIsStaticLayout = isStaticLayout;
     }
 
     public void setActualLayoutWidth(int actualLayoutWidth) {
@@ -452,6 +444,14 @@
         return mActualLayoutWidth;
     }
 
+    public int getFinalTranslationX() {
+        if (mLastVisibleIconState == null) {
+            return 0;
+        }
+
+        return (int) (mLastVisibleIconState.xTranslation + mIconSize * (1 + OVERFLOW_EARLY_AMOUNT));
+    }
+
     public void setChangingViewPositions(boolean changingViewPositions) {
         mChangingViewPositions = changingViewPositions;
     }
@@ -479,19 +479,43 @@
         mOpenedAmount = expandAmount;
     }
 
-    public float getVisualOverflowAdaption() {
-        return mVisualOverflowAdaption;
-    }
-
-    public void setVisualOverflowAdaption(float visualOverflowAdaption) {
-        mVisualOverflowAdaption = visualOverflowAdaption;
-    }
-
     public boolean hasOverflow() {
+        if (mIsStaticLayout) {
+            return getChildCount() > MAX_STATIC_ICONS;
+        }
+
         float width = (getChildCount() + OVERFLOW_EARLY_AMOUNT) * mIconSize;
         return width - (getWidth() - getActualPaddingStart() - getActualPaddingEnd()) > 0;
     }
 
+    /**
+     * If the overflow is in the range [1, max_dots - 1) (basically 1 or 2 dots), then
+     * extra padding will have to be accounted for
+     *
+     * This method has no meaning for non-static containers
+     */
+    public boolean hasPartialOverflow() {
+        if (mIsStaticLayout) {
+            int count = getChildCount();
+            return count > MAX_STATIC_ICONS && count <= MAX_STATIC_ICONS + MAX_DOTS;
+        }
+
+        return false;
+    }
+
+    /**
+     * Get padding that can account for extra dots up to the max. The only valid values for
+     * this method are for 1 or 2 dots.
+     * @return only extraDotPadding or extraDotPadding * 2
+     */
+    public int getPartialOverflowExtraPadding() {
+        if (!hasPartialOverflow()) {
+            return 0;
+        }
+
+        return (MAX_STATIC_ICONS + MAX_DOTS - getChildCount()) * (mStaticDotRadius + mDotPadding);
+    }
+
     public int getIconSize() {
         return mIconSize;
     }
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityClientConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
similarity index 98%
rename from services/accessibility/java/com/android/server/accessibility/AccessibilityClientConnection.java
rename to services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 01679dd..3d7d6b7 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityClientConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -71,7 +71,7 @@
  * This class represents an accessibility client - either an AccessibilityService or a UiAutomation.
  * It is responsible for behavior common to both types of clients.
  */
-abstract class AccessibilityClientConnection extends IAccessibilityServiceConnection.Stub
+abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServiceConnection.Stub
         implements ServiceConnection, IBinder.DeathRecipient, KeyEventDispatcher.KeyEventFilter,
         FingerprintGestureDispatcher.FingerprintGestureClient {
     private static final boolean DEBUG = false;
@@ -238,7 +238,7 @@
                 int flags);
     }
 
-    public AccessibilityClientConnection(Context context, ComponentName componentName,
+    public AbstractAccessibilityServiceConnection(Context context, ComponentName componentName,
             AccessibilityServiceInfo accessibilityServiceInfo, int id, Handler mainHandler,
             Object lock, SecurityPolicy securityPolicy, SystemSupport systemSupport,
             WindowManagerInternal windowManagerInternal,
@@ -339,6 +339,11 @@
         }
     }
 
+    int getRelevantEventTypes() {
+        return (mUsesAccessibilityCache ? AccessibilityCache.CACHE_CRITICAL_EVENTS_MASK : 0)
+                | mEventTypes;
+    }
+
     @Override
     public void setServiceInfo(AccessibilityServiceInfo info) {
         final long identity = Binder.clearCallingIdentity();
@@ -954,13 +959,15 @@
 
     public void notifyAccessibilityEvent(AccessibilityEvent event) {
         synchronized (mLock) {
+            final int eventType = event.getEventType();
+
             final boolean serviceWantsEvent = wantsEventLocked(event);
-            if (!serviceWantsEvent && !mUsesAccessibilityCache &&
-                    ((AccessibilityCache.CACHE_CRITICAL_EVENTS_MASK & event.getEventType()) == 0)) {
+            final boolean requiredForCacheConsistency = mUsesAccessibilityCache
+                    && ((AccessibilityCache.CACHE_CRITICAL_EVENTS_MASK & eventType) != 0);
+            if (!serviceWantsEvent && !requiredForCacheConsistency) {
                 return;
             }
 
-            final int eventType = event.getEventType();
             // Make a copy since during dispatch it is possible the event to
             // be modified to remove its source if the receiving service does
             // not have permission to access the window content.
@@ -1226,6 +1233,10 @@
         return windowId;
     }
 
+    public ComponentName getComponentName() {
+        return mComponentName;
+    }
+
     private final class InvocationHandler extends Handler {
         public static final int MSG_ON_GESTURE = 1;
         public static final int MSG_CLEAR_ACCESSIBILITY_CACHE = 2;
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 270a762..d83f6ae 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -20,6 +20,8 @@
 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS;
 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS;
 
+import static com.android.internal.util.FunctionalUtils.ignoreRemoteException;
+
 import android.Manifest;
 import android.accessibilityservice.AccessibilityService;
 import android.accessibilityservice.AccessibilityServiceInfo;
@@ -84,7 +86,6 @@
 import android.view.View;
 import android.view.WindowInfo;
 import android.view.WindowManager;
-import android.view.accessibility.AccessibilityCache;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityInteractionClient;
 import android.view.accessibility.AccessibilityManager;
@@ -114,6 +115,7 @@
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -131,7 +133,7 @@
  * on the device. Events are dispatched to {@link AccessibilityService}s.
  */
 public class AccessibilityManagerService extends IAccessibilityManager.Stub
-        implements AccessibilityClientConnection.SystemSupport {
+        implements AbstractAccessibilityServiceConnection.SystemSupport {
 
     private static final boolean DEBUG = false;
 
@@ -455,7 +457,7 @@
     }
 
     @Override
-    public long addClient(IAccessibilityManagerClient client, int userId) {
+    public long addClient(IAccessibilityManagerClient callback, int userId) {
         synchronized (mLock) {
             // We treat calls from a profile as if made by its parent as profiles
             // share the accessibility state of the parent. The call below
@@ -467,15 +469,17 @@
             // the system UI or the system we add it to the global state that
             // is shared across users.
             UserState userState = getUserStateLocked(resolvedUserId);
+            Client client = new Client(callback, Binder.getCallingUid(), userState);
             if (mSecurityPolicy.isCallerInteractingAcrossUsers(userId)) {
-                mGlobalClients.register(client);
+                mGlobalClients.register(callback, client);
                 if (DEBUG) {
                     Slog.i(LOG_TAG, "Added global client for pid:" + Binder.getCallingPid());
                 }
                 return IntPair.of(
-                        userState.getClientState(), userState.mLastSentRelevantEventTypes);
+                        userState.getClientState(),
+                        client.mLastSentRelevantEventTypes);
             } else {
-                userState.mUserClients.register(client);
+                userState.mUserClients.register(callback, client);
                 // If this client is not for the current user we do not
                 // return a state since it is not for the foreground user.
                 // We will send the state to the client on a user switch.
@@ -485,7 +489,7 @@
                 }
                 return IntPair.of(
                         (resolvedUserId == mCurrentUserId) ? userState.getClientState() : 0,
-                        userState.mLastSentRelevantEventTypes);
+                        client.mLastSentRelevantEventTypes);
             }
         }
     }
@@ -951,7 +955,7 @@
      * Has no effect if no item has accessibility focus, if the item with accessibility
      * focus does not expose the specified action, or if the action fails.
      *
-     * @param actionId The id of the action to perform.
+     * @param action The action to perform.
      *
      * @return {@code true} if the action was performed. {@code false} if it was not.
      */
@@ -1329,33 +1333,67 @@
     }
 
     private void updateRelevantEventsLocked(UserState userState) {
-        int relevantEventTypes = AccessibilityCache.CACHE_CRITICAL_EVENTS_MASK;
-        for (AccessibilityServiceConnection service : userState.mBoundServices) {
-            relevantEventTypes |= service.mEventTypes;
-        }
-        relevantEventTypes |= mUiAutomationManager.getRequestedEventMaskLocked();
-        int finalRelevantEventTypes = relevantEventTypes;
+        mMainHandler.post(() -> {
+            broadcastToClients(userState, ignoreRemoteException(client -> {
+                int relevantEventTypes = computeRelevantEventTypes(userState, client);
 
-        if (userState.mLastSentRelevantEventTypes != finalRelevantEventTypes) {
-            userState.mLastSentRelevantEventTypes = finalRelevantEventTypes;
-            mMainHandler.obtainMessage(MainHandler.MSG_SEND_RELEVANT_EVENTS_CHANGED_TO_CLIENTS,
-                    userState.mUserId, finalRelevantEventTypes);
-            mMainHandler.post(() -> {
-                broadcastToClients(userState, (client) -> {
-                    try {
-                        client.setRelevantEventTypes(finalRelevantEventTypes);
-                    } catch (RemoteException re) {
-                        /* ignore */
-                    }
-                });
-            });
+                if (client.mLastSentRelevantEventTypes != relevantEventTypes) {
+                    client.mLastSentRelevantEventTypes = relevantEventTypes;
+                    client.mCallback.setRelevantEventTypes(relevantEventTypes);
+                }
+            }));
+        });
+    }
+
+    private int computeRelevantEventTypes(UserState userState, Client client) {
+        int relevantEventTypes = 0;
+
+        int numBoundServices = userState.mBoundServices.size();
+        for (int i = 0; i < numBoundServices; i++) {
+            AccessibilityServiceConnection service =
+                    userState.mBoundServices.get(i);
+            relevantEventTypes |= isClientInPackageWhitelist(service.getServiceInfo(), client)
+                    ? service.getRelevantEventTypes()
+                    : 0;
         }
+        relevantEventTypes |= isClientInPackageWhitelist(
+                mUiAutomationManager.getServiceInfo(), client)
+                ? mUiAutomationManager.getRelevantEventTypes()
+                : 0;
+        return relevantEventTypes;
+    }
+
+    private static boolean isClientInPackageWhitelist(
+            @Nullable AccessibilityServiceInfo serviceInfo, Client client) {
+        if (serviceInfo == null) return false;
+
+        String[] clientPackages = client.mPackageNames;
+        boolean result = ArrayUtils.isEmpty(serviceInfo.packageNames);
+        if (!result && clientPackages != null) {
+            for (String packageName : clientPackages) {
+                if (ArrayUtils.contains(serviceInfo.packageNames, packageName)) {
+                    result = true;
+                    break;
+                }
+            }
+        }
+        if (!result) {
+            if (DEBUG) {
+                Slog.d(LOG_TAG, "Dropping events: "
+                        + Arrays.toString(clientPackages) + " -> "
+                        + serviceInfo.getComponentName().flattenToShortString()
+                        + " due to not being in package whitelist "
+                        + Arrays.toString(serviceInfo.packageNames));
+            }
+        }
+
+        return result;
     }
 
     private void broadcastToClients(
-            UserState userState, Consumer<IAccessibilityManagerClient> clientAction) {
-        mGlobalClients.broadcast(clientAction);
-        userState.mUserClients.broadcast(clientAction);
+            UserState userState, Consumer<Client> clientAction) {
+        mGlobalClients.broadcastForEachCookie(clientAction);
+        userState.mUserClients.broadcastForEachCookie(clientAction);
     }
 
     private void unbindAllServicesLocked(UserState userState) {
@@ -2156,6 +2194,7 @@
      * permission to write secure settings, since someone with that permission can enable
      * accessibility services themselves.
      */
+    @Override
     public void performAccessibilityShortcut() {
         if ((UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID)
                 && (mContext.checkCallingPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
@@ -2445,13 +2484,8 @@
                     synchronized (mLock) {
                         userState = getUserStateLocked(userId);
                     }
-                    broadcastToClients(userState, (client) -> {
-                        try {
-                            client.setRelevantEventTypes(relevantEventTypes);
-                        } catch (RemoteException re) {
-                            /* ignore */
-                        }
-                    });
+                    broadcastToClients(userState, ignoreRemoteException(
+                            client -> client.mCallback.setRelevantEventTypes(relevantEventTypes)));
                 } break;
 
                case MSG_SEND_ACCESSIBILITY_BUTTON_TO_INPUT_FILTER: {
@@ -2500,30 +2534,14 @@
 
         private void sendStateToClients(int clientState,
                 RemoteCallbackList<IAccessibilityManagerClient> clients) {
-            clients.broadcast((client) -> {
-                try {
-                    client.setState(clientState);
-                } catch (RemoteException re) {
-                    /* ignore */
-                }
-            });
+            clients.broadcast(ignoreRemoteException(
+                    client -> client.setState(clientState)));
         }
 
         private void notifyClientsOfServicesStateChange(
                 RemoteCallbackList<IAccessibilityManagerClient> clients) {
-            try {
-                final int userClientCount = clients.beginBroadcast();
-                for (int i = 0; i < userClientCount; i++) {
-                    IAccessibilityManagerClient client = clients.getBroadcastItem(i);
-                    try {
-                        client.notifyServicesStateChanged();
-                    } catch (RemoteException re) {
-                        /* ignore */
-                    }
-                }
-            } finally {
-                clients.finishBroadcast();
-            }
+            clients.broadcast(ignoreRemoteException(
+                    client -> client.notifyServicesStateChanged()));
         }
     }
 
@@ -3335,20 +3353,20 @@
         }
 
         public boolean canGetAccessibilityNodeInfoLocked(
-                AccessibilityClientConnection service, int windowId) {
+                AbstractAccessibilityServiceConnection service, int windowId) {
             return canRetrieveWindowContentLocked(service) && isRetrievalAllowingWindow(windowId);
         }
 
-        public boolean canRetrieveWindowsLocked(AccessibilityClientConnection service) {
+        public boolean canRetrieveWindowsLocked(AbstractAccessibilityServiceConnection service) {
             return canRetrieveWindowContentLocked(service) && service.mRetrieveInteractiveWindows;
         }
 
-        public boolean canRetrieveWindowContentLocked(AccessibilityClientConnection service) {
+        public boolean canRetrieveWindowContentLocked(AbstractAccessibilityServiceConnection service) {
             return (service.mAccessibilityServiceInfo.getCapabilities()
                     & AccessibilityServiceInfo.CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT) != 0;
         }
 
-        public boolean canControlMagnification(AccessibilityClientConnection service) {
+        public boolean canControlMagnification(AbstractAccessibilityServiceConnection service) {
             return (service.mAccessibilityServiceInfo.getCapabilities()
                     & AccessibilityServiceInfo.CAPABILITY_CAN_CONTROL_MAGNIFICATION) != 0;
         }
@@ -3445,7 +3463,7 @@
                 final int windowCount = mWindows.size();
                 for (int i = 0; i < windowCount; i++) {
                     AccessibilityWindowInfo window = mWindows.get(i);
-                    if (window.inPictureInPicture()) {
+                    if (window.isInPictureInPictureMode()) {
                         return window;
                     }
                 }
@@ -3476,13 +3494,26 @@
         }
     }
 
+    /** Represents an {@link AccessibilityManager} */
+    class Client {
+        final IAccessibilityManagerClient mCallback;
+        final String[] mPackageNames;
+        int mLastSentRelevantEventTypes;
+
+        private Client(IAccessibilityManagerClient callback, int clientUid, UserState userState) {
+            mCallback = callback;
+            mPackageNames = mPackageManager.getPackagesForUid(clientUid);
+            mLastSentRelevantEventTypes = computeRelevantEventTypes(userState, this);
+        }
+    }
+
     public class UserState {
         public final int mUserId;
 
         // Non-transient state.
 
         public final RemoteCallbackList<IAccessibilityManagerClient> mUserClients =
-            new RemoteCallbackList<>();
+                new RemoteCallbackList<>();
 
         public final SparseArray<RemoteAccessibilityConnection> mInteractionConnections =
                 new SparseArray<>();
@@ -3494,8 +3525,6 @@
         public final CopyOnWriteArrayList<AccessibilityServiceConnection> mBoundServices =
                 new CopyOnWriteArrayList<>();
 
-        public int mLastSentRelevantEventTypes = AccessibilityEvent.TYPES_ALL_MASK;
-
         public final Map<ComponentName, AccessibilityServiceConnection> mComponentNameToServiceMap =
                 new HashMap<>();
 
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
index 9cafa1e..5f6efb6 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
@@ -19,9 +19,7 @@
 import static android.provider.Settings.Secure.SHOW_MODE_AUTO;
 
 import android.accessibilityservice.AccessibilityServiceInfo;
-import android.accessibilityservice.GestureDescription;
 import android.accessibilityservice.IAccessibilityServiceClient;
-import android.accessibilityservice.IAccessibilityServiceConnection;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -39,7 +37,6 @@
 import com.android.server.wm.WindowManagerInternal;
 
 import java.lang.ref.WeakReference;
-import java.util.List;
 import java.util.Set;
 
 /**
@@ -50,7 +47,7 @@
  * passed to the service it represents as soon it is bound. It also serves as the
  * connection for the service.
  */
-class AccessibilityServiceConnection extends AccessibilityClientConnection {
+class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnection {
     private static final String LOG_TAG = "AccessibilityServiceConnection";
     /*
      Holding a weak reference so there isn't a loop of references. UserState keeps lists of bound
diff --git a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
index 56a9534..ed3b3e7 100644
--- a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
@@ -18,6 +18,7 @@
 
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.accessibilityservice.IAccessibilityServiceClient;
+import android.annotation.Nullable;
 import android.app.UiAutomation;
 import android.content.ComponentName;
 import android.content.Context;
@@ -46,7 +47,7 @@
 
     private AccessibilityServiceInfo mUiAutomationServiceInfo;
 
-    private AccessibilityClientConnection.SystemSupport mSystemSupport;
+    private AbstractAccessibilityServiceConnection.SystemSupport mSystemSupport;
 
     private int mUiAutomationFlags;
 
@@ -78,7 +79,7 @@
             Context context, AccessibilityServiceInfo accessibilityServiceInfo,
             int id, Handler mainHandler, Object lock,
             AccessibilityManagerService.SecurityPolicy securityPolicy,
-            AccessibilityClientConnection.SystemSupport systemSupport,
+            AbstractAccessibilityServiceConnection.SystemSupport systemSupport,
             WindowManagerInternal windowManagerInternal,
             GlobalActionPerformer globalActionPerfomer, int flags) {
         accessibilityServiceInfo.setComponentName(COMPONENT_NAME);
@@ -157,6 +158,17 @@
         return mUiAutomationService.mEventTypes;
     }
 
+    int getRelevantEventTypes() {
+        if (mUiAutomationService == null) return 0;
+        return mUiAutomationService.getRelevantEventTypes();
+    }
+
+    @Nullable
+    AccessibilityServiceInfo getServiceInfo() {
+        if (mUiAutomationService == null) return null;
+        return mUiAutomationService.getServiceInfo();
+    }
+
     void dumpUiAutomationService(FileDescriptor fd, final PrintWriter pw, String[] args) {
         if (mUiAutomationService != null) {
             mUiAutomationService.dump(fd, pw, args);
@@ -176,7 +188,7 @@
         mSystemSupport.onClientChange(false);
     }
 
-    private class UiAutomationService extends AccessibilityClientConnection {
+    private class UiAutomationService extends AbstractAccessibilityServiceConnection {
         private final Handler mMainHandler;
 
         UiAutomationService(Context context, AccessibilityServiceInfo accessibilityServiceInfo,
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
index 690c45b..e1cb154 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerService.java
@@ -568,13 +568,12 @@
 
         @Override
         public FillEventHistory getFillEventHistory() throws RemoteException {
-            UserHandle user = getCallingUserHandle();
-            int uid = getCallingUid();
+            final int userId = UserHandle.getCallingUserId();
 
             synchronized (mLock) {
-                AutofillManagerServiceImpl service = peekServiceForUserLocked(user.getIdentifier());
+                final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
                 if (service != null) {
-                    return service.getFillEventHistory(uid);
+                    return service.getFillEventHistory(getCallingUid());
                 }
             }
 
@@ -583,13 +582,12 @@
 
         @Override
         public UserData getUserData() throws RemoteException {
-            UserHandle user = getCallingUserHandle();
-            int uid = getCallingUid();
+            final int userId = UserHandle.getCallingUserId();
 
             synchronized (mLock) {
-                AutofillManagerServiceImpl service = peekServiceForUserLocked(user.getIdentifier());
+                final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
                 if (service != null) {
-                    return service.getUserData(uid);
+                    return service.getUserData(getCallingUid());
                 }
             }
 
@@ -598,26 +596,24 @@
 
         @Override
         public void setUserData(UserData userData) throws RemoteException {
-            UserHandle user = getCallingUserHandle();
-            int uid = getCallingUid();
+            final int userId = UserHandle.getCallingUserId();
 
             synchronized (mLock) {
-                AutofillManagerServiceImpl service = peekServiceForUserLocked(user.getIdentifier());
+                final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
                 if (service != null) {
-                    service.setUserData(uid, userData);
+                    service.setUserData(getCallingUid(), userData);
                 }
             }
         }
 
         @Override
         public boolean isFieldClassificationEnabled() throws RemoteException {
-            UserHandle user = getCallingUserHandle();
-            int uid = getCallingUid();
+            final int userId = UserHandle.getCallingUserId();
 
             synchronized (mLock) {
-                AutofillManagerServiceImpl service = peekServiceForUserLocked(user.getIdentifier());
+                final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
                 if (service != null) {
-                    return service.isFieldClassificationEnabled();
+                    return service.isFieldClassificationEnabled(getCallingUid());
                 }
             }
 
@@ -625,6 +621,20 @@
         }
 
         @Override
+        public ComponentName getAutofillServiceComponentName() throws RemoteException {
+            final int userId = UserHandle.getCallingUserId();
+
+            synchronized (mLock) {
+                final AutofillManagerServiceImpl service = peekServiceForUserLocked(userId);
+                if (service != null) {
+                    return service.getServiceComponentName();
+                }
+            }
+
+            return null;
+        }
+
+        @Override
         public boolean restoreSession(int sessionId, IBinder activityToken, IBinder appCallback)
                 throws RemoteException {
             activityToken = Preconditions.checkNotNull(activityToken, "activityToken");
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 3361824..4e92108 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -835,7 +835,7 @@
             pw.println(mContext.getString(R.string.config_defaultAutofillService));
         pw.print(prefix); pw.print("Disabled: "); pw.println(mDisabled);
         pw.print(prefix); pw.print("Field classification enabled: ");
-            pw.println(isFieldClassificationEnabled());
+            pw.println(isFieldClassificationEnabledLocked());
         pw.print(prefix); pw.print("Setup complete: "); pw.println(mSetupComplete);
         pw.print(prefix); pw.print("Last prune: "); pw.println(mLastPrune);
 
@@ -1095,7 +1095,18 @@
         return false;
     }
 
-    boolean isFieldClassificationEnabled() {
+    // Called by AutofillManager, checks UID.
+    boolean isFieldClassificationEnabled(int uid) {
+        synchronized (mLock) {
+            if (!isCalledByServiceLocked("isFieldClassificationEnabled", uid)) {
+                return false;
+            }
+            return isFieldClassificationEnabledLocked();
+        }
+    }
+
+    // Called by internally, no need to check UID.
+    boolean isFieldClassificationEnabledLocked() {
         return Settings.Secure.getIntForUser(
                 mContext.getContentResolver(),
                 Settings.Secure.AUTOFILL_FEATURE_FIELD_CLASSIFICATION, 0,
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 7b85a6c..ad43ec2 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -500,6 +500,8 @@
     @Override
     public void onFillRequestSuccess(int requestFlags, @Nullable FillResponse response,
             @NonNull String servicePackageName) {
+        final AutofillId[] fieldClassificationIds;
+
         synchronized (mLock) {
             if (mDestroyed) {
                 Slog.w(TAG, "Call to Session#onFillRequestSuccess() rejected - session: "
@@ -510,13 +512,13 @@
                 processNullResponseLocked(requestFlags);
                 return;
             }
-        }
 
-        final AutofillId[] fieldClassificationIds = response.getFieldClassificationIds();
-        if (fieldClassificationIds != null && !mService.isFieldClassificationEnabled()) {
-            Slog.w(TAG, "Ignoring " + response + " because field detection is disabled");
-            processNullResponseLocked(requestFlags);
-            return;
+            fieldClassificationIds = response.getFieldClassificationIds();
+            if (fieldClassificationIds != null && !mService.isFieldClassificationEnabledLocked()) {
+                Slog.w(TAG, "Ignoring " + response + " because field detection is disabled");
+                processNullResponseLocked(requestFlags);
+                return;
+            }
         }
 
         mService.setLastResponse(id, response);
diff --git a/services/backup/java/com/android/server/backup/TransportManager.java b/services/backup/java/com/android/server/backup/TransportManager.java
index f185443..fbdb183 100644
--- a/services/backup/java/com/android/server/backup/TransportManager.java
+++ b/services/backup/java/com/android/server/backup/TransportManager.java
@@ -483,6 +483,7 @@
             description.currentDestinationString = currentDestinationString;
             description.dataManagementIntent = dataManagementIntent;
             description.dataManagementLabel = dataManagementLabel;
+            Slog.d(TAG, "Transport " + name + " updated its attributes");
         }
     }
 
diff --git a/services/core/java/com/android/server/IpSecService.java b/services/core/java/com/android/server/IpSecService.java
index a764808..d3ab125 100644
--- a/services/core/java/com/android/server/IpSecService.java
+++ b/services/core/java/com/android/server/IpSecService.java
@@ -988,12 +988,6 @@
             sockFd = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
             mUidFdTagger.tag(sockFd, callingUid);
 
-            if (port != 0) {
-                Log.v(TAG, "Binding to port " + port);
-                Os.bind(sockFd, INADDR_ANY, port);
-            } else {
-                port = bindToRandomPort(sockFd);
-            }
             // This code is common to both the unspecified and specified port cases
             Os.setsockoptInt(
                     sockFd,
@@ -1001,6 +995,14 @@
                     OsConstants.UDP_ENCAP,
                     OsConstants.UDP_ENCAP_ESPINUDP);
 
+            mSrvConfig.getNetdInstance().ipSecSetEncapSocketOwner(sockFd, callingUid);
+            if (port != 0) {
+                Log.v(TAG, "Binding to port " + port);
+                Os.bind(sockFd, INADDR_ANY, port);
+            } else {
+                port = bindToRandomPort(sockFd);
+            }
+
             userRecord.mEncapSocketRecords.put(
                     resourceId,
                     new RefcountedResource<EncapSocketRecord>(
diff --git a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
index f3ccba5..4582430 100644
--- a/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
+++ b/services/core/java/com/android/server/am/BatteryExternalStatsWorker.java
@@ -116,6 +116,50 @@
         return scheduleSyncLocked("remove-uid", UPDATE_CPU);
     }
 
+    @Override
+    public Future<?> scheduleReadProcStateCpuTimes() {
+        synchronized (mStats) {
+            if (!mStats.mPerProcStateCpuTimesAvailable) {
+                return null;
+            }
+        }
+        synchronized (BatteryExternalStatsWorker.this) {
+            if (!mExecutorService.isShutdown()) {
+                return mExecutorService.submit(mReadProcStateCpuTimesTask);
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public Future<?> scheduleCopyFromAllUidsCpuTimes() {
+        synchronized (mStats) {
+            if (!mStats.mPerProcStateCpuTimesAvailable) {
+                return null;
+            }
+        }
+        synchronized (BatteryExternalStatsWorker.this) {
+            if (!mExecutorService.isShutdown()) {
+                return mExecutorService.submit(mCopyFromAllUidsCpuTimesTask);
+            }
+        }
+        return null;
+    }
+
+    private final Runnable mReadProcStateCpuTimesTask = new Runnable() {
+        @Override
+        public void run() {
+            mStats.updateProcStateCpuTimes();
+        }
+    };
+
+    private final Runnable mCopyFromAllUidsCpuTimesTask = new Runnable() {
+        @Override
+        public void run() {
+            mStats.copyFromAllUidsCpuTimes();
+        }
+    };
+
     public synchronized Future<?> scheduleWrite() {
         if (mExecutorService.isShutdown()) {
             return CompletableFuture.failedFuture(new IllegalStateException("worker shutdown"));
@@ -185,6 +229,10 @@
                 }
             }
 
+            if ((updateFlags & UPDATE_CPU) != 0) {
+                mStats.copyFromAllUidsCpuTimes();
+            }
+
             // Clean up any UIDs if necessary.
             synchronized (mStats) {
                 for (int uid : uidsToRemove) {
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index 93fb3e3..a057a99 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -207,6 +207,10 @@
         }
     }
 
+    private void syncStats(String reason, int flags) {
+        awaitUninterruptibly(mWorker.scheduleSync(reason, flags));
+    }
+
     /**
      * At the time when the constructor runs, the power manager has not yet been
      * initialized.  So we initialize the low power observer later.
@@ -225,7 +229,7 @@
     public void shutdown() {
         Slog.w("BatteryStats", "Writing battery stats before shutdown...");
 
-        awaitUninterruptibly(mWorker.scheduleSync("shutdown", BatteryExternalStatsWorker.UPDATE_ALL));
+        syncStats("shutdown", BatteryExternalStatsWorker.UPDATE_ALL);
 
         synchronized (mStats) {
             mStats.shutdownLocked();
@@ -357,7 +361,7 @@
         //Slog.i("foo", "SENDING BATTERY INFO:");
         //mStats.dumpLocked(new LogPrinter(Log.INFO, "foo", Log.LOG_ID_SYSTEM));
         Parcel out = Parcel.obtain();
-        awaitUninterruptibly(mWorker.scheduleSync("get-stats", BatteryExternalStatsWorker.UPDATE_ALL));
+        syncStats("get-stats", BatteryExternalStatsWorker.UPDATE_ALL);
         synchronized (mStats) {
             mStats.writeToParcel(out, 0);
         }
@@ -372,7 +376,7 @@
         //Slog.i("foo", "SENDING BATTERY INFO:");
         //mStats.dumpLocked(new LogPrinter(Log.INFO, "foo", Log.LOG_ID_SYSTEM));
         Parcel out = Parcel.obtain();
-        awaitUninterruptibly(mWorker.scheduleSync("get-stats", BatteryExternalStatsWorker.UPDATE_ALL));
+        syncStats("get-stats", BatteryExternalStatsWorker.UPDATE_ALL);
         synchronized (mStats) {
             mStats.writeToParcel(out, 0);
         }
@@ -1237,8 +1241,7 @@
                     }
                     mWorker.scheduleSync("dump", BatteryExternalStatsWorker.UPDATE_ALL);
                 } else if ("--write".equals(arg)) {
-                    awaitUninterruptibly(mWorker.scheduleSync("dump",
-                            BatteryExternalStatsWorker.UPDATE_ALL));
+                    syncStats("dump", BatteryExternalStatsWorker.UPDATE_ALL);
                     synchronized (mStats) {
                         mStats.writeSyncLocked();
                         pw.println("Battery stats written.");
@@ -1302,7 +1305,7 @@
                 flags |= BatteryStats.DUMP_DEVICE_WIFI_ONLY;
             }
             // Fetch data from external sources and update the BatteryStatsImpl object with them.
-            awaitUninterruptibly(mWorker.scheduleSync("dump", BatteryExternalStatsWorker.UPDATE_ALL));
+            syncStats("dump", BatteryExternalStatsWorker.UPDATE_ALL);
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
@@ -1415,8 +1418,7 @@
         }
         long ident = Binder.clearCallingIdentity();
         try {
-            awaitUninterruptibly(mWorker.scheduleSync("get-health-stats-for-uids",
-                    BatteryExternalStatsWorker.UPDATE_ALL));
+            syncStats("get-health-stats-for-uids", BatteryExternalStatsWorker.UPDATE_ALL);
             synchronized (mStats) {
                 return getHealthStatsForUidLocked(requestUid);
             }
@@ -1440,8 +1442,7 @@
         long ident = Binder.clearCallingIdentity();
         int i=-1;
         try {
-            awaitUninterruptibly(mWorker.scheduleSync("get-health-stats-for-uids",
-                    BatteryExternalStatsWorker.UPDATE_ALL));
+            syncStats("get-health-stats-for-uids", BatteryExternalStatsWorker.UPDATE_ALL);
             synchronized (mStats) {
                 final int N = requestUids.length;
                 final HealthStatsParceler[] results = new HealthStatsParceler[N];
diff --git a/services/core/java/com/android/server/display/ColorFade.java b/services/core/java/com/android/server/display/ColorFade.java
index c2167eb..85686ae 100644
--- a/services/core/java/com/android/server/display/ColorFade.java
+++ b/services/core/java/com/android/server/display/ColorFade.java
@@ -99,7 +99,7 @@
     private final float mProjMatrix[] = new float[16];
     private final int[] mGLBuffers = new int[2];
     private int mTexCoordLoc, mVertexLoc, mTexUnitLoc, mProjMatrixLoc, mTexMatrixLoc;
-    private int mOpacityLoc, mScaleLoc, mGammaLoc, mSaturationLoc;
+    private int mOpacityLoc, mGammaLoc, mSaturationLoc;
     private int mProgram;
 
     // Vertex and corresponding texture coordinates.
@@ -246,7 +246,6 @@
         mOpacityLoc = GLES20.glGetUniformLocation(mProgram, "opacity");
         mGammaLoc = GLES20.glGetUniformLocation(mProgram, "gamma");
         mSaturationLoc = GLES20.glGetUniformLocation(mProgram, "saturation");
-        mScaleLoc = GLES20.glGetUniformLocation(mProgram, "scale");
         mTexUnitLoc = GLES20.glGetUniformLocation(mProgram, "texUnit");
 
         GLES20.glUseProgram(mProgram);
@@ -395,9 +394,8 @@
             double sign = cos < 0 ? -1 : 1;
             float opacity = (float) -Math.pow(one_minus_level, 2) + 1;
             float saturation = (float) Math.pow(level, 4);
-            float scale = (float) ((-Math.pow(one_minus_level, 2) + 1) * 0.1d + 0.9d);
             float gamma = (float) ((0.5d * sign * Math.pow(cos, 2) + 0.5d) * 0.9d + 0.1d);
-            drawFaded(opacity, 1.f / gamma, saturation, scale);
+            drawFaded(opacity, 1.f / gamma, saturation);
             if (checkGlErrors("drawFrame")) {
                 return false;
             }
@@ -409,10 +407,10 @@
         return showSurface(1.0f);
     }
 
-    private void drawFaded(float opacity, float gamma, float saturation, float scale) {
+    private void drawFaded(float opacity, float gamma, float saturation) {
         if (DEBUG) {
             Slog.d(TAG, "drawFaded: opacity=" + opacity + ", gamma=" + gamma +
-                        ", saturation=" + saturation + ", scale=" + scale);
+                        ", saturation=" + saturation);
         }
         // Use shaders
         GLES20.glUseProgram(mProgram);
@@ -423,7 +421,6 @@
         GLES20.glUniform1f(mOpacityLoc, opacity);
         GLES20.glUniform1f(mGammaLoc, gamma);
         GLES20.glUniform1f(mSaturationLoc, saturation);
-        GLES20.glUniform1f(mScaleLoc, scale);
 
         // Use textures
         GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index aa55930..0481dab 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -1943,6 +1943,15 @@
         return mRecoverableKeyStoreManager.getRecoveryData(account, userId);
     }
 
+    public void setSnapshotCreatedPendingIntent(@Nullable PendingIntent intent, int userId)
+            throws RemoteException {
+        mRecoverableKeyStoreManager.setSnapshotCreatedPendingIntent(intent, userId);
+    }
+
+    public Map getRecoverySnapshotVersions(int userId) throws RemoteException {
+        return mRecoverableKeyStoreManager.getRecoverySnapshotVersions(userId);
+    }
+
     @Override
     public void setServerParameters(long serverParameters, int userId) throws RemoteException {
         mRecoverableKeyStoreManager.setServerParameters(serverParameters, userId);
@@ -1954,6 +1963,10 @@
         mRecoverableKeyStoreManager.setRecoveryStatus(packageName, aliases, status, userId);
     }
 
+    public Map getRecoveryStatus(@Nullable String packageName, int userId) throws RemoteException {
+        return mRecoverableKeyStoreManager.getRecoveryStatus(packageName, userId);
+    }
+
     @Override
     public void setRecoverySecretTypes(@NonNull @KeyStoreRecoveryMetadata.UserSecretType
             int[] secretTypes, int userId) throws RemoteException {
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/AndroidKeyStoreFactory.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/AndroidKeyStoreFactory.java
new file mode 100644
index 0000000..9a4d051
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/AndroidKeyStoreFactory.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.locksettings.recoverablekeystore;
+
+import android.security.keystore.AndroidKeyStoreProvider;
+
+import java.security.KeyStoreException;
+import java.security.NoSuchProviderException;
+
+public interface AndroidKeyStoreFactory {
+    KeyStoreProxy getKeyStoreForUid(int uid) throws KeyStoreException, NoSuchProviderException;
+
+    class Impl implements AndroidKeyStoreFactory {
+        @Override
+        public KeyStoreProxy getKeyStoreForUid(int uid)
+                throws KeyStoreException, NoSuchProviderException {
+            return new KeyStoreProxyImpl(AndroidKeyStoreProvider.getKeyStoreForUid(uid));
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeyStoreProxy.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeyStoreProxy.java
index 7c9b395..8103177 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeyStoreProxy.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeyStoreProxy.java
@@ -40,4 +40,7 @@
     /** @see KeyStore#setEntry(String, KeyStore.Entry, KeyStore.ProtectionParameter) */
     void setEntry(String alias, KeyStore.Entry entry, KeyStore.ProtectionParameter protParam)
             throws KeyStoreException;
+
+    /** @see KeyStore#deleteEntry(String) */
+    void deleteEntry(String alias) throws KeyStoreException;
 }
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeyStoreProxyImpl.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeyStoreProxyImpl.java
index ceee381..59132da 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeyStoreProxyImpl.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeyStoreProxyImpl.java
@@ -52,4 +52,9 @@
             throws KeyStoreException {
         mKeyStore.setEntry(alias, entry, protParam);
     }
+
+    @Override
+    public void deleteEntry(String alias) throws KeyStoreException {
+        mKeyStore.deleteEntry(alias);
+    }
 }
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java
index 37aeb3a..25428e7 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/KeySyncUtils.java
@@ -20,10 +20,13 @@
 
 import java.nio.charset.StandardCharsets;
 import java.security.InvalidKeyException;
+import java.security.KeyFactory;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
 import java.security.PublicKey;
 import java.security.SecureRandom;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.X509EncodedKeySpec;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -39,6 +42,7 @@
  */
 public class KeySyncUtils {
 
+    private static final String PUBLIC_KEY_FACTORY_ALGORITHM = "EC";
     private static final String RECOVERY_KEY_ALGORITHM = "AES";
     private static final int RECOVERY_KEY_SIZE_BITS = 256;
 
@@ -237,6 +241,21 @@
     }
 
     /**
+     * Deserializes a X509 public key.
+     *
+     * @param key The bytes of the key.
+     * @return The key.
+     * @throws NoSuchAlgorithmException if the public key algorithm is unavailable.
+     * @throws InvalidKeySpecException if the bytes of the key are not a valid key.
+     */
+    public static PublicKey deserializePublicKey(byte[] key)
+            throws NoSuchAlgorithmException, InvalidKeySpecException {
+        KeyFactory keyFactory = KeyFactory.getInstance(PUBLIC_KEY_FACTORY_ALGORITHM);
+        X509EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(key);
+        return keyFactory.generatePublic(publicKeySpec);
+    }
+
+    /**
      * Returns the concatenation of all the given {@code arrays}.
      */
     @VisibleForTesting
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/ListenersStorage.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/ListenersStorage.java
new file mode 100644
index 0000000..0f17294
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/ListenersStorage.java
@@ -0,0 +1,68 @@
+/*
+ * 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.locksettings.recoverablekeystore;
+
+import android.annotation.Nullable;
+import android.app.PendingIntent;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.Map;
+import java.util.HashMap;
+
+/**
+ * In memory storage for listeners to be notified when new recovery snapshot is available.
+ * Note: implementation is not thread safe and it is used to mock final {@link PendingIntent}
+ * class.
+ *
+ * @hide
+ */
+public class ListenersStorage {
+    private Map<Integer, PendingIntent> mAgentIntents = new HashMap<>();
+
+    private static final ListenersStorage mInstance = new ListenersStorage();
+    public static ListenersStorage getInstance() {
+        return mInstance;
+    }
+
+    /**
+     * Sets new listener for the recovery agent, identified by {@code uid}
+     *
+     * @param recoveryAgentUid uid
+     * @param intent PendingIntent which will be triggered than new snapshot is available.
+     */
+    public void setSnapshotListener(int recoveryAgentUid, @Nullable PendingIntent intent) {
+        mAgentIntents.put(recoveryAgentUid, intent);
+    }
+
+    /**
+     * Notifies recovery agent, that new snapshot is available.
+     * Does nothing if a listener was not registered.
+     *
+     * @param recoveryAgentUid uid.
+     */
+    public void recoverySnapshotAvailable(int recoveryAgentUid) {
+        PendingIntent intent = mAgentIntents.get(recoveryAgentUid);
+        if (intent != null) {
+            try {
+                intent.send();
+            } catch (PendingIntent.CanceledException e) {
+                // Ignore - sending intent is not allowed.
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java
index 074c596..24f3f65 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/PlatformKeyManager.java
@@ -18,16 +18,14 @@
 
 import android.app.KeyguardManager;
 import android.content.Context;
-import android.content.SharedPreferences;
-import android.os.Environment;
 import android.security.keystore.AndroidKeyStoreSecretKey;
 import android.security.keystore.KeyProperties;
 import android.security.keystore.KeyProtection;
 import android.util.Log;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
 
-import java.io.File;
 import java.io.IOException;
 import java.security.KeyStore;
 import java.security.KeyStoreException;
@@ -64,8 +62,6 @@
 
     private static final String KEY_ALGORITHM = "AES";
     private static final int KEY_SIZE_BITS = 256;
-    private static final String SHARED_PREFS_KEY_GENERATION_ID = "generationId";
-    private static final String SHARED_PREFS_PATH = "/system/recoverablekeystore/platform_keys.xml";
     private static final String KEY_ALIAS_PREFIX =
             "com.android.server.locksettings.recoverablekeystore/platform/";
     private static final String ENCRYPT_KEY_ALIAS_SUFFIX = "encrypt";
@@ -74,7 +70,7 @@
 
     private final Context mContext;
     private final KeyStoreProxy mKeyStore;
-    private final SharedPreferences mSharedPreferences;
+    private final RecoverableKeyStoreDb mDatabase;
     private final int mUserId;
 
     private static final String ANDROID_KEY_STORE_PROVIDER = "AndroidKeyStore";
@@ -92,17 +88,14 @@
      *
      * @hide
      */
-    public static PlatformKeyManager getInstance(Context context, int userId)
+    public static PlatformKeyManager getInstance(Context context, RecoverableKeyStoreDb database, int userId)
             throws KeyStoreException, NoSuchAlgorithmException, InsecureUserException {
         context = context.getApplicationContext();
-        File sharedPreferencesFile = new File(
-                Environment.getDataDirectory().getAbsoluteFile(), SHARED_PREFS_PATH);
-        sharedPreferencesFile.mkdirs();
         PlatformKeyManager keyManager = new PlatformKeyManager(
                 userId,
                 context,
                 new KeyStoreProxyImpl(getAndLoadAndroidKeyStore()),
-                context.getSharedPreferences(sharedPreferencesFile, Context.MODE_PRIVATE));
+                database);
         keyManager.init();
         return keyManager;
     }
@@ -112,11 +105,11 @@
             int userId,
             Context context,
             KeyStoreProxy keyStore,
-            SharedPreferences sharedPreferences) {
+            RecoverableKeyStoreDb database) {
         mUserId = userId;
         mKeyStore = keyStore;
         mContext = context;
-        mSharedPreferences = sharedPreferences;
+        mDatabase = database;
     }
 
     /**
@@ -127,7 +120,11 @@
      * @hide
      */
     public int getGenerationId() {
-        return mSharedPreferences.getInt(getGenerationIdKey(), 1);
+        int generationId = mDatabase.getPlatformKeyGenerationId(mUserId);
+        if (generationId == -1) {
+            return 1;
+        }
+        return generationId;
     }
 
     /**
@@ -150,9 +147,9 @@
      * @hide
      */
     public void regenerate() throws NoSuchAlgorithmException, KeyStoreException {
-        int generationId = getGenerationId();
-        generateAndLoadKey(generationId + 1);
-        setGenerationId(generationId + 1);
+        int nextId = getGenerationId() + 1;
+        generateAndLoadKey(nextId);
+        setGenerationId(nextId);
     }
 
     /**
@@ -252,14 +249,7 @@
      * Sets the current generation ID to {@code generationId}.
      */
     private void setGenerationId(int generationId) {
-        mSharedPreferences.edit().putInt(getGenerationIdKey(), generationId).commit();
-    }
-
-    /**
-     * Returns the current user's generation ID key in the shared preferences.
-     */
-    private String getGenerationIdKey() {
-        return SHARED_PREFS_KEY_GENERATION_ID + "/" + mUserId;
+        mDatabase.setPlatformKeyGenerationId(mUserId, generationId);
     }
 
     /**
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGenerator.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGenerator.java
index 54deec2..d50a736 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGenerator.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGenerator.java
@@ -16,16 +16,18 @@
 
 package com.android.server.locksettings.recoverablekeystore;
 
-import android.security.keystore.AndroidKeyStoreSecretKey;
 import android.security.keystore.KeyProperties;
 import android.security.keystore.KeyProtection;
 import android.util.Log;
 
-import java.io.IOException;
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
+
 import java.security.InvalidKeyException;
+import java.security.KeyStore;
 import java.security.KeyStoreException;
 import java.security.NoSuchAlgorithmException;
-import java.security.UnrecoverableEntryException;
+import java.security.NoSuchProviderException;
+import java.util.Locale;
 
 import javax.crypto.KeyGenerator;
 import javax.crypto.SecretKey;
@@ -42,39 +44,39 @@
  */
 public class RecoverableKeyGenerator {
     private static final String TAG = "RecoverableKeyGenerator";
+
+    private static final int RESULT_CANNOT_INSERT_ROW = -1;
     private static final String KEY_GENERATOR_ALGORITHM = "AES";
     private static final int KEY_SIZE_BITS = 256;
 
     /**
      * A new {@link RecoverableKeyGenerator} instance.
      *
-     * @param platformKey Secret key used to wrap generated keys before persisting to disk.
-     * @param recoverableKeyStorage Class that manages persisting wrapped keys to disk.
      * @throws NoSuchAlgorithmException if "AES" key generation or "AES/GCM/NoPadding" cipher is
      *     unavailable. Should never happen.
      *
      * @hide
      */
-    public static RecoverableKeyGenerator newInstance(
-            PlatformEncryptionKey platformKey, RecoverableKeyStorage recoverableKeyStorage)
+    public static RecoverableKeyGenerator newInstance(RecoverableKeyStoreDb database)
             throws NoSuchAlgorithmException {
         // NB: This cannot use AndroidKeyStore as the provider, as we need access to the raw key
         // material, so that it can be synced to disk in encrypted form.
         KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_GENERATOR_ALGORITHM);
-        return new RecoverableKeyGenerator(keyGenerator, platformKey, recoverableKeyStorage);
+        return new RecoverableKeyGenerator(
+                keyGenerator, database, new AndroidKeyStoreFactory.Impl());
     }
 
     private final KeyGenerator mKeyGenerator;
-    private final RecoverableKeyStorage mRecoverableKeyStorage;
-    private final PlatformEncryptionKey mPlatformKey;
+    private final RecoverableKeyStoreDb mDatabase;
+    private final AndroidKeyStoreFactory mAndroidKeyStoreFactory;
 
     private RecoverableKeyGenerator(
             KeyGenerator keyGenerator,
-            PlatformEncryptionKey platformKey,
-            RecoverableKeyStorage recoverableKeyStorage) {
+            RecoverableKeyStoreDb recoverableKeyStoreDb,
+            AndroidKeyStoreFactory androidKeyStoreFactory) {
         mKeyGenerator = keyGenerator;
-        mRecoverableKeyStorage = recoverableKeyStorage;
-        mPlatformKey = platformKey;
+        mAndroidKeyStoreFactory = androidKeyStoreFactory;
+        mDatabase = recoverableKeyStoreDb;
     }
 
     /**
@@ -84,50 +86,70 @@
      * persisted to disk so that it can be synced remotely, and then recovered on another device.
      * The generated key allows encrypt/decrypt only using AES/GCM/NoPadding.
      *
-     * <p>The key handle returned to the caller is a reference to the AndroidKeyStore key,
-     * meaning that the caller is never able to access the raw, unencrypted key.
-     *
+     * @param platformKey The user's platform key, with which to wrap the generated key.
+     * @param uid The uid of the application that will own the key.
      * @param alias The alias by which the key will be known in AndroidKeyStore.
+     * @throws RecoverableKeyStorageException if there is some error persisting the key either to
+     *     the AndroidKeyStore or the database.
+     * @throws KeyStoreException if there is a KeyStore error wrapping the generated key.
      * @throws InvalidKeyException if the platform key cannot be used to wrap keys.
-     * @throws IOException if there was an issue writing the wrapped key to the wrapped key store.
-     * @throws UnrecoverableEntryException if could not retrieve key after putting it in
-     *     AndroidKeyStore. This should not happen.
-     * @return A handle to the AndroidKeyStore key.
      *
      * @hide
      */
-    public SecretKey generateAndStoreKey(String alias) throws KeyStoreException,
-            InvalidKeyException, IOException, UnrecoverableEntryException {
+    public void generateAndStoreKey(PlatformEncryptionKey platformKey, int uid, String alias)
+            throws RecoverableKeyStorageException, KeyStoreException, InvalidKeyException {
         mKeyGenerator.init(KEY_SIZE_BITS);
         SecretKey key = mKeyGenerator.generateKey();
 
-        mRecoverableKeyStorage.importIntoAndroidKeyStore(
-                alias,
-                key,
-                new KeyProtection.Builder(
-                        KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
-                        .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
-                        .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
-                        .build());
-        WrappedKey wrappedKey = WrappedKey.fromSecretKey(mPlatformKey, key);
+        KeyStoreProxy keyStore;
 
         try {
+            keyStore = mAndroidKeyStoreFactory.getKeyStoreForUid(uid);
+        } catch (NoSuchProviderException e) {
+            throw new RecoverableKeyStorageException(
+                    "Impossible: AndroidKeyStore provider did not exist", e);
+        } catch (KeyStoreException e) {
+            throw new RecoverableKeyStorageException(
+                    "Could not load AndroidKeyStore for " + uid, e);
+        }
+
+        try {
+            keyStore.setEntry(
+                    alias,
+                    new KeyStore.SecretKeyEntry(key),
+                    new KeyProtection.Builder(
+                            KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
+                            .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
+                            .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
+                            .build());
+        } catch (KeyStoreException e) {
+            throw new RecoverableKeyStorageException(
+                    "Failed to load (%d, %s) into AndroidKeyStore", e);
+        }
+
+        WrappedKey wrappedKey = WrappedKey.fromSecretKey(platformKey, key);
+        try {
             // Keep raw key material in memory for minimum possible time.
             key.destroy();
         } catch (DestroyFailedException e) {
             Log.w(TAG, "Could not destroy SecretKey.");
         }
+        long result = mDatabase.insertKey(uid, alias, wrappedKey);
 
-        mRecoverableKeyStorage.persistToDisk(alias, wrappedKey);
+        if (result == RESULT_CANNOT_INSERT_ROW) {
+            // Attempt to clean up
+            try {
+                keyStore.deleteEntry(alias);
+            } catch (KeyStoreException e) {
+                Log.e(TAG, String.format(Locale.US,
+                        "Could not delete recoverable key (%d, %s) from "
+                                + "AndroidKeyStore after error writing to database.", uid, alias),
+                        e);
+            }
 
-        try {
-            // Reload from the keystore, so that the caller is only provided with the handle of the
-            // key, not the raw key material.
-            return mRecoverableKeyStorage.loadFromAndroidKeyStore(alias);
-        } catch (NoSuchAlgorithmException e) {
-            throw new RuntimeException(
-                    "Impossible: NoSuchAlgorithmException when attempting to retrieve a key "
-                            + "that has only just been stored in AndroidKeyStore.", e);
+            throw new RecoverableKeyStorageException(
+                    String.format(
+                            Locale.US, "Failed writing (%d, %s) to database.", uid, alias));
         }
     }
 }
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStorage.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStorage.java
deleted file mode 100644
index 6a189ef..0000000
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStorage.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.locksettings.recoverablekeystore;
-
-import android.security.keystore.KeyProtection;
-
-import java.io.IOException;
-import java.security.KeyStoreException;
-import java.security.NoSuchAlgorithmException;
-import java.security.UnrecoverableEntryException;
-
-import javax.crypto.SecretKey;
-
-/**
- * Stores wrapped keys to disk, so they can be synced on the next screen unlock event.
- *
- * @hide
- */
-public interface RecoverableKeyStorage {
-
-    /**
-     * Writes {@code wrappedKey} to disk, keyed by the application's uid and the {@code alias}.
-     *
-     * @throws IOException if an error occurred writing to disk.
-     *
-     * @hide
-     */
-    void persistToDisk(String alias, WrappedKey wrappedKey) throws IOException;
-
-    /**
-     * Imports {@code key} into AndroidKeyStore, keyed by the application's uid and
-     * the {@code alias}.
-     *
-     * @param alias The alias of the key.
-     * @param key The key.
-     * @param keyProtection Protection params denoting what the key can be used for. (e.g., what
-     *                      Cipher modes, whether for encrpyt/decrypt or signing, etc.)
-     * @throws KeyStoreException if an error occurred loading the key into the AndroidKeyStore.
-     *
-     * @hide
-     */
-    void importIntoAndroidKeyStore(String alias, SecretKey key, KeyProtection keyProtection) throws
-            KeyStoreException;
-
-    /**
-     * Loads a key handle from AndroidKeyStore.
-     *
-     * @param alias Alias of the key to load.
-     * @return The key handle.
-     * @throws KeyStoreException if an error occurred loading the key from AndroidKeyStore.
-     *
-     * @hide
-     */
-    SecretKey loadFromAndroidKeyStore(String alias) throws KeyStoreException,
-            NoSuchAlgorithmException,
-            UnrecoverableEntryException;
-
-    /**
-     * Removes the entry with the given {@code alias} from AndroidKeyStore.
-     *
-     * @throws KeyStoreException if an error occurred deleting the key from AndroidKeyStore.
-     *
-     * @hide
-     */
-    void removeFromAndroidKeyStore(String alias) throws KeyStoreException;
-}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStorageException.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStorageException.java
new file mode 100644
index 0000000..f9d28f1
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStorageException.java
@@ -0,0 +1,35 @@
+/*
+ * 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.locksettings.recoverablekeystore;
+
+/**
+ * Error thrown when there was a problem writing or reading recoverable key information to or from
+ * storage.
+ *
+ * <p>Storage is typically
+ * {@link com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb} or
+ * AndroidKeyStore.
+ */
+public class RecoverableKeyStorageException extends Exception {
+    public RecoverableKeyStorageException(String message) {
+        super(message);
+    }
+
+    public RecoverableKeyStorageException(String message, Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStorageImpl.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStorageImpl.java
deleted file mode 100644
index d4dede1..0000000
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStorageImpl.java
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.locksettings.recoverablekeystore;
-
-import android.security.keystore.AndroidKeyStoreProvider;
-import android.security.keystore.KeyProtection;
-
-import java.io.IOException;
-import java.security.KeyStore;
-import java.security.KeyStoreException;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.UnrecoverableEntryException;
-
-import javax.crypto.SecretKey;
-
-/**
- * Implementation of {@link RecoverableKeyStorage} for a specific application.
- *
- * <p>Persists wrapped keys to disk, and loads raw keys into AndroidKeyStore.
- *
- * @hide
- */
-public class RecoverableKeyStorageImpl implements RecoverableKeyStorage {
-    private final KeyStore mKeyStore;
-
-    /**
-     * A new instance, storing recoverable keys for the given {@code userId}.
-     *
-     * @throws KeyStoreException if unable to load AndroidKeyStore.
-     * @throws NoSuchProviderException if AndroidKeyStore is not in this version of Android. Should
-     *     never occur.
-     *
-     * @hide
-     */
-    public static RecoverableKeyStorageImpl newInstance(int userId) throws KeyStoreException,
-            NoSuchProviderException {
-        KeyStore keyStore = AndroidKeyStoreProvider.getKeyStoreForUid(userId);
-        return new RecoverableKeyStorageImpl(keyStore);
-    }
-
-    private RecoverableKeyStorageImpl(KeyStore keyStore) {
-        mKeyStore = keyStore;
-    }
-
-    /**
-     * Writes {@code wrappedKey} to disk, keyed by the application's uid and the {@code alias}.
-     *
-     * @throws IOException if an error occurred writing to disk.
-     *
-     * @hide
-     */
-    @Override
-    public void persistToDisk(String alias, WrappedKey wrappedKey) throws IOException {
-        // TODO(robertberry) Add implementation.
-        throw new UnsupportedOperationException();
-    }
-
-    /**
-     * Imports {@code key} into the application's AndroidKeyStore, keyed by {@code alias}.
-     *
-     * @param alias The alias of the key.
-     * @param key The key.
-     * @param keyProtection Protection params denoting what the key can be used for. (e.g., what
-     *                      Cipher modes, whether for encrpyt/decrypt or signing, etc.)
-     * @throws KeyStoreException if an error occurred loading the key into the AndroidKeyStore.
-     *
-     * @hide
-     */
-    @Override
-    public void importIntoAndroidKeyStore(String alias, SecretKey key, KeyProtection keyProtection)
-            throws KeyStoreException {
-        mKeyStore.setEntry(alias, new KeyStore.SecretKeyEntry(key), keyProtection);
-    }
-
-    /**
-     * Loads a key handle from the application's AndroidKeyStore.
-     *
-     * @param alias Alias of the key to load.
-     * @return The key handle.
-     * @throws KeyStoreException if an error occurred loading the key from AndroidKeyStore.
-     *
-     * @hide
-     */
-    @Override
-    public SecretKey loadFromAndroidKeyStore(String alias)
-            throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableEntryException {
-        return ((SecretKey) mKeyStore.getKey(alias, /*password=*/ null));
-    }
-
-    /**
-     * Removes the entry with the given {@code alias} from the application's AndroidKeyStore.
-     *
-     * @throws KeyStoreException if an error occurred deleting the key from AndroidKeyStore.
-     *
-     * @hide
-     */
-    @Override
-    public void removeFromAndroidKeyStore(String alias) throws KeyStoreException {
-        mKeyStore.deleteEntry(alias);
-    }
-}
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
index e459f28..48f4626 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.PendingIntent;
 import android.content.Context;
 import android.os.Binder;
 import android.os.RemoteException;
@@ -30,9 +31,16 @@
 import android.security.recoverablekeystore.RecoverableKeyStoreLoader;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverySessionStorage;
 
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.spec.InvalidKeySpecException;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 
 /**
  * Class with {@link RecoverableKeyStoreLoader} API implementation and internal methods to interact
@@ -44,7 +52,10 @@
     private static final String TAG = "RecoverableKeyStoreManager";
 
     private static RecoverableKeyStoreManager mInstance;
-    private Context mContext;
+
+    private final Context mContext;
+    private final RecoverableKeyStoreDb mDatabase;
+    private final RecoverySessionStorage mRecoverySessionStorage;
 
     /**
      * Returns a new or existing instance.
@@ -53,14 +64,23 @@
      */
     public static synchronized RecoverableKeyStoreManager getInstance(Context mContext) {
         if (mInstance == null) {
-            mInstance = new RecoverableKeyStoreManager(mContext);
+            RecoverableKeyStoreDb db = RecoverableKeyStoreDb.newInstance(mContext);
+            mInstance = new RecoverableKeyStoreManager(
+                    mContext.getApplicationContext(),
+                    db,
+                    new RecoverySessionStorage());
         }
         return mInstance;
     }
 
     @VisibleForTesting
-    RecoverableKeyStoreManager(Context context) {
+    RecoverableKeyStoreManager(
+            Context context,
+            RecoverableKeyStoreDb recoverableKeyStoreDb,
+            RecoverySessionStorage recoverySessionStorage) {
         mContext = context;
+        mDatabase = recoverableKeyStoreDb;
+        mRecoverySessionStorage = recoverySessionStorage;
     }
 
     public int initRecoveryService(
@@ -77,7 +97,7 @@
      * @return recovery data
      * @hide
      */
-    public KeyStoreRecoveryData getRecoveryData(@NonNull byte[] account, int userId)
+    public @NonNull KeyStoreRecoveryData getRecoveryData(@NonNull byte[] account, int userId)
             throws RemoteException {
         checkRecoverKeyStorePermission();
         final int callingUid = Binder.getCallingUid(); // Recovery agent uid.
@@ -100,6 +120,24 @@
                 RecoverableKeyStoreLoader.UNINITIALIZED_RECOVERY_PUBLIC_KEY);
     }
 
+    public void setSnapshotCreatedPendingIntent(@Nullable PendingIntent intent, int userId)
+            throws RemoteException {
+        checkRecoverKeyStorePermission();
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * Gets recovery snapshot versions for all accounts. Note that snapshot may have 0 application
+     * keys, but it still needs to be synced, if previous versions were not empty.
+     *
+     * @return Map from Recovery agent account to snapshot version.
+     */
+    public @NonNull Map<byte[], Integer> getRecoverySnapshotVersions(int userId)
+            throws RemoteException {
+        checkRecoverKeyStorePermission();
+        throw new UnsupportedOperationException();
+    }
+
     public void setServerParameters(long serverParameters, int userId) throws RemoteException {
         checkRecoverKeyStorePermission();
         throw new UnsupportedOperationException();
@@ -113,6 +151,21 @@
     }
 
     /**
+     * Gets recovery status for keys {@code packageName}.
+     *
+     * @param packageName which recoverable keys statuses will be returned
+     * @return Map from KeyStore alias to recovery status
+     */
+    public @NonNull Map<String, Integer> getRecoveryStatus(@Nullable String packageName, int userId)
+            throws RemoteException {
+        // Any application should be able to check status for its own keys.
+        // If caller is a recovery agent it can check statuses for other packages, but
+        // only for recoverable keys it manages.
+        checkRecoverKeyStorePermission();
+        throw new UnsupportedOperationException();
+    }
+
+    /**
      * Sets recovery secrets list used by all recovery agents for given {@code userId}
      *
      * @hide
@@ -130,7 +183,7 @@
      * @return secret types
      * @hide
      */
-    public int[] getRecoverySecretTypes(int userId) throws RemoteException {
+    public @NonNull int[] getRecoverySecretTypes(int userId) throws RemoteException {
         checkRecoverKeyStorePermission();
         throw new UnsupportedOperationException();
     }
@@ -141,7 +194,7 @@
      * @return secret types
      * @hide
      */
-    public int[] getPendingRecoverySecretTypes(int userId) throws RemoteException {
+    public @NonNull int[] getPendingRecoverySecretTypes(int userId) throws RemoteException {
         checkRecoverKeyStorePermission();
         throw new UnsupportedOperationException();
     }
@@ -161,10 +214,16 @@
     /**
      * Initializes recovery session.
      *
-     * @return recovery claim
+     * @param sessionId A unique ID to identify the recovery session.
+     * @param verifierPublicKey X509-encoded public key.
+     * @param vaultParams Additional params associated with vault.
+     * @param vaultChallenge Challenge issued by vault service.
+     * @param secrets Lock-screen hashes. Should have a single element. TODO: why is this a list?
+     * @return Encrypted bytes of recovery claim. This can then be issued to the vault service.
+     *
      * @hide
      */
-    public byte[] startRecoverySession(
+    public @NonNull byte[] startRecoverySession(
             @NonNull String sessionId,
             @NonNull byte[] verifierPublicKey,
             @NonNull byte[] vaultParams,
@@ -173,7 +232,40 @@
             int userId)
             throws RemoteException {
         checkRecoverKeyStorePermission();
-        throw new UnsupportedOperationException();
+
+        if (secrets.size() != 1) {
+            // TODO: support multiple secrets
+            throw new RemoteException("Only a single KeyStoreRecoveryMetadata is supported");
+        }
+
+        byte[] keyClaimant = KeySyncUtils.generateKeyClaimant();
+        byte[] kfHash = secrets.get(0).getSecret();
+        mRecoverySessionStorage.add(
+                userId, new RecoverySessionStorage.Entry(sessionId, kfHash, keyClaimant));
+
+        try {
+            byte[] thmKfHash = KeySyncUtils.calculateThmKfHash(kfHash);
+            PublicKey publicKey = KeySyncUtils.deserializePublicKey(verifierPublicKey);
+            return KeySyncUtils.encryptRecoveryClaim(
+                    publicKey,
+                    vaultParams,
+                    vaultChallenge,
+                    thmKfHash,
+                    keyClaimant);
+        } catch (NoSuchAlgorithmException e) {
+            // Should never happen: all the algorithms used are required by AOSP implementations.
+            throw new RemoteException(
+                    "Missing required algorithm",
+                    e,
+                    /*enableSuppression=*/ true,
+                    /*writeableStackTrace=*/ true);
+        } catch (InvalidKeySpecException | InvalidKeyException e) {
+            throw new RemoteException(
+                    "Not a valid X509 key",
+                    e,
+                    /*enableSuppression=*/ true,
+                    /*writeableStackTrace=*/ true);
+        }
     }
 
     public void recoverKeys(
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorage.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorage.java
new file mode 100644
index 0000000..bc56ae1
--- /dev/null
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorage.java
@@ -0,0 +1,173 @@
+/*
+ * 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.locksettings.recoverablekeystore.storage;
+
+import android.annotation.Nullable;
+import android.util.SparseArray;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+
+import javax.security.auth.Destroyable;
+
+/**
+ * Stores pending recovery sessions in memory. We do not write these to disk, as it contains hashes
+ * of the user's lock screen.
+ *
+ * @hide
+ */
+public class RecoverySessionStorage implements Destroyable {
+
+    private final SparseArray<ArrayList<Entry>> mSessionsByUid = new SparseArray<>();
+
+    /**
+     * Returns the session for the given user with the given id.
+     *
+     * @param uid The uid of the recovery agent who created the session.
+     * @param sessionId The unique identifier for the session.
+     * @return The session info.
+     *
+     * @hide
+     */
+    @Nullable
+    public Entry get(int uid, String sessionId) {
+        ArrayList<Entry> userEntries = mSessionsByUid.get(uid);
+        if (userEntries == null) {
+            return null;
+        }
+        for (Entry entry : userEntries) {
+            if (sessionId.equals(entry.mSessionId)) {
+                return entry;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Adds a pending session for the given user.
+     *
+     * @param uid The uid of the recovery agent who created the session.
+     * @param entry The session info.
+     *
+     * @hide
+     */
+    public void add(int uid, Entry entry) {
+        if (mSessionsByUid.get(uid) == null) {
+            mSessionsByUid.put(uid, new ArrayList<>());
+        }
+        mSessionsByUid.get(uid).add(entry);
+    }
+
+    /**
+     * Removes all sessions associated with the given recovery agent uid.
+     *
+     * @param uid The uid of the recovery agent whose sessions to remove.
+     *
+     * @hide
+     */
+    public void remove(int uid) {
+        ArrayList<Entry> entries = mSessionsByUid.get(uid);
+        if (entries == null) {
+            return;
+        }
+        for (Entry entry : entries) {
+            entry.destroy();
+        }
+        mSessionsByUid.remove(uid);
+    }
+
+    /**
+     * Returns the total count of pending sessions.
+     *
+     * @hide
+     */
+    public int size() {
+        int size = 0;
+        int numberOfUsers = mSessionsByUid.size();
+        for (int i = 0; i < numberOfUsers; i++) {
+            ArrayList<Entry> entries = mSessionsByUid.valueAt(i);
+            size += entries.size();
+        }
+        return size;
+    }
+
+    /**
+     * Wipes the memory of any sensitive information (i.e., lock screen hashes and key claimants).
+     *
+     * @hide
+     */
+    @Override
+    public void destroy() {
+        int numberOfUids = mSessionsByUid.size();
+        for (int i = 0; i < numberOfUids; i++) {
+            ArrayList<Entry> entries = mSessionsByUid.valueAt(i);
+            for (Entry entry : entries) {
+                entry.destroy();
+            }
+        }
+    }
+
+    /**
+     * Information about a recovery session.
+     *
+     * @hide
+     */
+    public static class Entry implements Destroyable {
+        private final byte[] mLskfHash;
+        private final byte[] mKeyClaimant;
+        private final String mSessionId;
+
+        /**
+         * @hide
+         */
+        public Entry(String sessionId, byte[] lskfHash, byte[] keyClaimant) {
+            this.mLskfHash = lskfHash;
+            this.mSessionId = sessionId;
+            this.mKeyClaimant = keyClaimant;
+        }
+
+        /**
+         * Returns the hash of the lock screen associated with the recovery attempt.
+         *
+         * @hide
+         */
+        public byte[] getLskfHash() {
+            return mLskfHash;
+        }
+
+        /**
+         * Returns the key generated for this recovery attempt (used to decrypt data returned by
+         * the server).
+         *
+         * @hide
+         */
+        public byte[] getKeyClaimant() {
+            return mKeyClaimant;
+        }
+
+        /**
+         * Overwrites the memory for the lskf hash and key claimant.
+         *
+         * @hide
+         */
+        @Override
+        public void destroy() {
+            Arrays.fill(mLskfHash, (byte) 0);
+            Arrays.fill(mKeyClaimant, (byte) 0);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 03cd4f1..768eb8f 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -27,6 +27,7 @@
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
 import android.app.ActivityManagerNative;
+import android.app.AppOpsManager;
 import android.app.IActivityManager;
 import android.app.IStopUserCallback;
 import android.app.KeyguardManager;
@@ -38,6 +39,7 @@
 import android.content.IntentSender;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ShortcutServiceInternal;
 import android.content.pm.UserInfo;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
@@ -386,7 +388,7 @@
     /**
      * Start an {@link IntentSender} when user is unlocked after disabling quiet mode.
      *
-     * @see {@link #trySetQuietModeDisabled(int, IntentSender)}
+     * @see {@link #trySetQuietModeEnabled(String, boolean, int, IntentSender)}
      */
     private class DisableQuietModeUserUnlockedCallback extends IProgressListener.Stub {
         private final IntentSender mTarget;
@@ -784,48 +786,114 @@
     }
 
     @Override
-    public void setQuietModeEnabled(int userHandle, boolean enableQuietMode, IntentSender target) {
-        checkManageUsersPermission("silence profile");
-        boolean changed = false;
-        UserInfo profile, parent;
-        synchronized (mPackagesLock) {
-            synchronized (mUsersLock) {
-                profile = getUserInfoLU(userHandle);
-                parent = getProfileParentLU(userHandle);
+    public boolean trySetQuietModeEnabled(@NonNull String callingPackage, boolean enableQuietMode,
+            int userHandle, @Nullable IntentSender target) {
+        Preconditions.checkNotNull(callingPackage);
 
+        if (enableQuietMode && target != null) {
+            throw new IllegalArgumentException(
+                    "target should only be specified when we are disabling quiet mode.");
+        }
+
+        if (!isAllowedToSetWorkMode(callingPackage, Binder.getCallingUid())) {
+            throw new SecurityException("Not allowed to call trySetQuietModeEnabled, "
+                    + "caller is foreground default launcher "
+                    + "nor with MANAGE_USERS/MODIFY_QUIET_MODE permission");
+        }
+
+        final long identity = Binder.clearCallingIdentity();
+        try {
+            if (enableQuietMode) {
+                setQuietModeEnabled(userHandle, true /* enableQuietMode */, target);
+                return true;
+            } else {
+                boolean needToShowConfirmCredential =
+                        mLockPatternUtils.isSecure(userHandle)
+                                && !StorageManager.isUserKeyUnlocked(userHandle);
+                if (needToShowConfirmCredential) {
+                    showConfirmCredentialToDisableQuietMode(userHandle, target);
+                    return false;
+                } else {
+                    setQuietModeEnabled(userHandle, false /* enableQuietMode */, target);
+                    return true;
+                }
             }
+        } finally {
+            Binder.restoreCallingIdentity(identity);
+        }
+    }
+
+    /**
+     * An app can modify quiet mode if the caller meets one of the condition:
+     * <ul>
+     *     <li>Has system UID or root UID</li>
+     *     <li>Has {@link Manifest.permission#MODIFY_QUIET_MODE}</li>
+     *     <li>Has {@link Manifest.permission#MANAGE_USERS}</li>
+     * </ul>
+     */
+    private boolean isAllowedToSetWorkMode(String callingPackage, int callingUid) {
+        if (hasManageUsersPermission()) {
+            return true;
+        }
+
+        final boolean hasModifyQuietModePermission = ActivityManager.checkComponentPermission(
+                Manifest.permission.MODIFY_QUIET_MODE,
+                callingUid, -1, true) == PackageManager.PERMISSION_GRANTED;
+        if (hasModifyQuietModePermission) {
+            return true;
+        }
+
+        final ShortcutServiceInternal shortcutInternal =
+                LocalServices.getService(ShortcutServiceInternal.class);
+        if (shortcutInternal != null) {
+            boolean isForegroundLauncher =
+                    shortcutInternal.isForegroundDefaultLauncher(callingPackage, callingUid);
+            if (isForegroundLauncher) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private void setQuietModeEnabled(
+            int userHandle, boolean enableQuietMode, IntentSender target) {
+        final UserInfo profile, parent;
+        final UserData profileUserData;
+        synchronized (mUsersLock) {
+            profile = getUserInfoLU(userHandle);
+            parent = getProfileParentLU(userHandle);
+
             if (profile == null || !profile.isManagedProfile()) {
                 throw new IllegalArgumentException("User " + userHandle + " is not a profile");
             }
-            if (profile.isQuietModeEnabled() != enableQuietMode) {
-                profile.flags ^= UserInfo.FLAG_QUIET_MODE;
-                writeUserLP(getUserDataLU(profile.id));
-                changed = true;
+            if (profile.isQuietModeEnabled() == enableQuietMode) {
+                Slog.i(LOG_TAG, "Quiet mode is already " + enableQuietMode);
+                return;
             }
+            profile.flags ^= UserInfo.FLAG_QUIET_MODE;
+            profileUserData = getUserDataLU(profile.id);
         }
-        if (changed) {
-            long identity = Binder.clearCallingIdentity();
-            try {
-                if (enableQuietMode) {
-                    ActivityManager.getService().stopUser(userHandle, /* force */true, null);
-                    LocalServices.getService(ActivityManagerInternal.class)
-                            .killForegroundAppsForUser(userHandle);
-                } else {
-                    IProgressListener callback = target != null
-                            ? new DisableQuietModeUserUnlockedCallback(target)
-                            : null;
-                    ActivityManager.getService().startUserInBackgroundWithListener(
-                            userHandle, callback);
-                }
-            } catch (RemoteException e) {
-                Slog.e(LOG_TAG, "fail to start/stop user for quiet mode", e);
-            } finally {
-                Binder.restoreCallingIdentity(identity);
+        synchronized (mPackagesLock) {
+            writeUserLP(profileUserData);
+        }
+        try {
+            if (enableQuietMode) {
+                ActivityManager.getService().stopUser(userHandle, /* force */true, null);
+                LocalServices.getService(ActivityManagerInternal.class)
+                        .killForegroundAppsForUser(userHandle);
+            } else {
+                IProgressListener callback = target != null
+                        ? new DisableQuietModeUserUnlockedCallback(target)
+                        : null;
+                ActivityManager.getService().startUserInBackgroundWithListener(
+                        userHandle, callback);
             }
-
-            broadcastProfileAvailabilityChanges(profile.getUserHandle(), parent.getUserHandle(),
-                    enableQuietMode);
+        } catch (RemoteException e) {
+            // Should not happen, same process.
+            e.rethrowAsRuntimeException();
         }
+        broadcastProfileAvailabilityChanges(profile.getUserHandle(), parent.getUserHandle(),
+                enableQuietMode);
     }
 
     @Override
@@ -842,54 +910,42 @@
         }
     }
 
-    @Override
-    public boolean trySetQuietModeDisabled(
+    /**
+     * Show confirm credential screen to unlock user in order to turn off quiet mode.
+     */
+    private void showConfirmCredentialToDisableQuietMode(
             @UserIdInt int userHandle, @Nullable IntentSender target) {
-        checkManageUsersPermission("silence profile");
-        if (StorageManager.isUserKeyUnlocked(userHandle)
-                || !mLockPatternUtils.isSecure(userHandle)) {
-            // if the user is already unlocked, no need to show a profile challenge
-            setQuietModeEnabled(userHandle, false, target);
-            return true;
+        // otherwise, we show a profile challenge to trigger decryption of the user
+        final KeyguardManager km = (KeyguardManager) mContext.getSystemService(
+                Context.KEYGUARD_SERVICE);
+        // We should use userHandle not credentialOwnerUserId here, as even if it is unified
+        // lock, confirm screenlock page will know and show personal challenge, and unlock
+        // work profile when personal challenge is correct
+        final Intent unlockIntent = km.createConfirmDeviceCredentialIntent(null, null,
+                userHandle);
+        if (unlockIntent == null) {
+            return;
         }
-
-        long identity = Binder.clearCallingIdentity();
-        try {
-            // otherwise, we show a profile challenge to trigger decryption of the user
-            final KeyguardManager km = (KeyguardManager) mContext.getSystemService(
-                    Context.KEYGUARD_SERVICE);
-            // We should use userHandle not credentialOwnerUserId here, as even if it is unified
-            // lock, confirm screenlock page will know and show personal challenge, and unlock
-            // work profile when personal challenge is correct
-            final Intent unlockIntent = km.createConfirmDeviceCredentialIntent(null, null,
-                    userHandle);
-            if (unlockIntent == null) {
-                return false;
-            }
-            final Intent callBackIntent = new Intent(
-                    ACTION_DISABLE_QUIET_MODE_AFTER_UNLOCK);
-            if (target != null) {
-                callBackIntent.putExtra(Intent.EXTRA_INTENT, target);
-            }
-            callBackIntent.putExtra(Intent.EXTRA_USER_ID, userHandle);
-            callBackIntent.setPackage(mContext.getPackageName());
-            callBackIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-            final PendingIntent pendingIntent = PendingIntent.getBroadcast(
-                    mContext,
-                    0,
-                    callBackIntent,
-                    PendingIntent.FLAG_CANCEL_CURRENT |
-                            PendingIntent.FLAG_ONE_SHOT |
-                            PendingIntent.FLAG_IMMUTABLE);
-            // After unlocking the challenge, it will disable quiet mode and run the original
-            // intentSender
-            unlockIntent.putExtra(Intent.EXTRA_INTENT, pendingIntent.getIntentSender());
-            unlockIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
-            mContext.startActivity(unlockIntent);
-        } finally {
-            Binder.restoreCallingIdentity(identity);
+        final Intent callBackIntent = new Intent(
+                ACTION_DISABLE_QUIET_MODE_AFTER_UNLOCK);
+        if (target != null) {
+            callBackIntent.putExtra(Intent.EXTRA_INTENT, target);
         }
-        return false;
+        callBackIntent.putExtra(Intent.EXTRA_USER_ID, userHandle);
+        callBackIntent.setPackage(mContext.getPackageName());
+        callBackIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+        final PendingIntent pendingIntent = PendingIntent.getBroadcast(
+                mContext,
+                0,
+                callBackIntent,
+                PendingIntent.FLAG_CANCEL_CURRENT |
+                        PendingIntent.FLAG_ONE_SHOT |
+                        PendingIntent.FLAG_IMMUTABLE);
+        // After unlocking the challenge, it will disable quiet mode and run the original
+        // intentSender
+        unlockIntent.putExtra(Intent.EXTRA_INTENT, pendingIntent.getIntentSender());
+        unlockIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+        mContext.startActivity(unlockIntent);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/vr/VrManagerService.java b/services/core/java/com/android/server/vr/VrManagerService.java
index 21c6889..de723c6 100644
--- a/services/core/java/com/android/server/vr/VrManagerService.java
+++ b/services/core/java/com/android/server/vr/VrManagerService.java
@@ -59,6 +59,8 @@
 import android.util.ArraySet;
 import android.util.Slog;
 import android.util.SparseArray;
+
+import com.android.server.FgThread;
 import com.android.server.wm.WindowManagerInternal;
 import android.view.inputmethod.InputMethodManagerInternal;
 
@@ -825,9 +827,11 @@
 
     @Override
     public void onSwitchUser(int userHandle) {
-        synchronized (mLock) {
-            mComponentObserver.onUsersChanged();
-        }
+        FgThread.getHandler().post(() -> {
+            synchronized (mLock) {
+                mComponentObserver.onUsersChanged();
+            }
+        });
 
     }
 
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 2bda80d..163b160 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static android.view.WindowManager.LayoutParams.TYPE_DOCK_DIVIDER;
 import static android.view.WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY;
 
@@ -1068,8 +1069,11 @@
                         continue;
                     }
 
-                    // If the window is not touchable - ignore.
-                    if ((flags & WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) != 0) {
+                    // Ignore non-touchable windows, except the split-screen divider, which is
+                    // occasionally non-touchable but still useful for identifying split-screen
+                    // mode.
+                    if (((flags & WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) != 0)
+                            && (windowState.mAttrs.type != TYPE_DOCK_DIVIDER)) {
                         continue;
                     }
 
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
index c14f74c..0462b14 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/AccessibilityServiceConnectionTest.java
@@ -62,7 +62,7 @@
     @Mock AccessibilityServiceInfo mMockServiceInfo;
     @Mock ResolveInfo mMockResolveInfo;
     @Mock AccessibilityManagerService.SecurityPolicy mMockSecurityPolicy;
-    @Mock AccessibilityClientConnection.SystemSupport mMockSystemSupport;
+    @Mock AbstractAccessibilityServiceConnection.SystemSupport mMockSystemSupport;
     @Mock WindowManagerInternal mMockWindowManagerInternal;
     @Mock GlobalActionPerformer mMockGlobalActionPerformer;
     @Mock KeyEventDispatcher mMockKeyEventDispatcher;
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java
index 45ecbfb..8853db2 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/UiAutomationManagerTest.java
@@ -59,7 +59,7 @@
     @Mock AccessibilityServiceInfo mMockServiceInfo;
     @Mock ResolveInfo mMockResolveInfo;
     @Mock AccessibilityManagerService.SecurityPolicy mMockSecurityPolicy;
-    @Mock AccessibilityClientConnection.SystemSupport mMockSystemSupport;
+    @Mock AbstractAccessibilityServiceConnection.SystemSupport mMockSystemSupport;
     @Mock WindowManagerInternal mMockWindowManagerInternal;
     @Mock GlobalActionPerformer mMockGlobalActionPerformer;
     @Mock IBinder mMockOwner;
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java
index a997770..e20f664 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/PlatformKeyManagerTest.java
@@ -36,6 +36,8 @@
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -45,6 +47,7 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.io.File;
 import java.security.KeyStore;
 import java.util.List;
 
@@ -52,9 +55,9 @@
 @RunWith(AndroidJUnit4.class)
 public class PlatformKeyManagerTest {
 
+    private static final String DATABASE_FILE_NAME = "recoverablekeystore.db";
     private static final int USER_AUTHENTICATION_VALIDITY_DURATION_SECONDS = 15;
     private static final int USER_ID_FIXTURE = 42;
-    private static final String TEST_SHARED_PREFS_NAME = "PlatformKeyManagerTestPrefs";
 
     @Mock private Context mContext;
     @Mock private KeyStoreProxy mKeyStoreProxy;
@@ -63,18 +66,20 @@
     @Captor private ArgumentCaptor<KeyStore.ProtectionParameter> mProtectionParameterCaptor;
     @Captor private ArgumentCaptor<KeyStore.Entry> mEntryArgumentCaptor;
 
-    private SharedPreferences mSharedPreferences;
+    private RecoverableKeyStoreDb mRecoverableKeyStoreDb;
+    private File mDatabaseFile;
+
     private PlatformKeyManager mPlatformKeyManager;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
 
-        Context testContext = InstrumentationRegistry.getTargetContext();
-        mSharedPreferences = testContext.getSharedPreferences(
-                TEST_SHARED_PREFS_NAME, Context.MODE_PRIVATE);
+        Context context = InstrumentationRegistry.getTargetContext();
+        mDatabaseFile = context.getDatabasePath(DATABASE_FILE_NAME);
+        mRecoverableKeyStoreDb = RecoverableKeyStoreDb.newInstance(context);
         mPlatformKeyManager = new PlatformKeyManager(
-                USER_ID_FIXTURE, mContext, mKeyStoreProxy, mSharedPreferences);
+                USER_ID_FIXTURE, mContext, mKeyStoreProxy, mRecoverableKeyStoreDb);
 
         when(mContext.getSystemService(anyString())).thenReturn(mKeyguardManager);
         when(mContext.getSystemServiceName(any())).thenReturn("test");
@@ -83,7 +88,8 @@
 
     @After
     public void tearDown() {
-        mSharedPreferences.edit().clear().commit();
+        mRecoverableKeyStoreDb.close();
+        mDatabaseFile.delete();
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGeneratorTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGeneratorTest.java
index 12dbdb3..3012931 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGeneratorTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyGeneratorTest.java
@@ -16,62 +16,71 @@
 
 package com.android.server.locksettings.recoverablekeystore;
 
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
+import static junit.framework.Assert.fail;
 
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.security.keystore.AndroidKeyStoreProvider;
 import android.security.keystore.AndroidKeyStoreSecretKey;
 import android.security.keystore.KeyGenParameterSpec;
 import android.security.keystore.KeyProperties;
-import android.security.keystore.KeyProtection;
+import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
+
+import com.google.common.collect.ImmutableMap;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
 
+import java.io.File;
+import java.nio.charset.StandardCharsets;
+import java.security.InvalidKeyException;
 import java.security.KeyStore;
+import java.util.Arrays;
 
+import javax.crypto.Cipher;
 import javax.crypto.KeyGenerator;
 import javax.crypto.SecretKey;
+import javax.crypto.spec.GCMParameterSpec;
 
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 public class RecoverableKeyGeneratorTest {
+    private static final String DATABASE_FILE_NAME = "recoverablekeystore.db";
     private static final int TEST_GENERATION_ID = 3;
     private static final String ANDROID_KEY_STORE_PROVIDER = "AndroidKeyStore";
     private static final String KEY_ALGORITHM = "AES";
+    private static final String SUPPORTED_CIPHER_ALGORITHM = "AES/GCM/NoPadding";
+    private static final String UNSUPPORTED_CIPHER_ALGORITHM = "AES/CTR/NoPadding";
     private static final String TEST_ALIAS = "karlin";
     private static final String WRAPPING_KEY_ALIAS = "RecoverableKeyGeneratorTestWrappingKey";
-
-    @Mock
-    RecoverableKeyStorage mRecoverableKeyStorage;
-
-    @Captor ArgumentCaptor<KeyProtection> mKeyProtectionArgumentCaptor;
+    private static final int KEYSTORE_UID_SELF = -1;
+    private static final int GCM_TAG_LENGTH_BITS = 128;
+    private static final int GCM_NONCE_LENGTH_BYTES = 12;
 
     private PlatformEncryptionKey mPlatformKey;
-    private SecretKey mKeyHandle;
+    private PlatformDecryptionKey mDecryptKey;
+    private RecoverableKeyStoreDb mRecoverableKeyStoreDb;
+    private File mDatabaseFile;
     private RecoverableKeyGenerator mRecoverableKeyGenerator;
 
     @Before
     public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-        mPlatformKey = new PlatformEncryptionKey(TEST_GENERATION_ID, generateAndroidKeyStoreKey());
-        mKeyHandle = generateKey();
-        mRecoverableKeyGenerator = RecoverableKeyGenerator.newInstance(
-                mPlatformKey, mRecoverableKeyStorage);
+        Context context = InstrumentationRegistry.getTargetContext();
+        mDatabaseFile = context.getDatabasePath(DATABASE_FILE_NAME);
+        mRecoverableKeyStoreDb = RecoverableKeyStoreDb.newInstance(context);
 
-        when(mRecoverableKeyStorage.loadFromAndroidKeyStore(any())).thenReturn(mKeyHandle);
+        AndroidKeyStoreSecretKey platformKey = generateAndroidKeyStoreKey();
+        mPlatformKey = new PlatformEncryptionKey(TEST_GENERATION_ID, platformKey);
+        mDecryptKey = new PlatformDecryptionKey(TEST_GENERATION_ID, platformKey);
+        mRecoverableKeyGenerator = RecoverableKeyGenerator.newInstance(mRecoverableKeyStoreDb);
     }
 
     @After
@@ -79,67 +88,69 @@
         KeyStore keyStore = KeyStore.getInstance(ANDROID_KEY_STORE_PROVIDER);
         keyStore.load(/*param=*/ null);
         keyStore.deleteEntry(WRAPPING_KEY_ALIAS);
+
+        mRecoverableKeyStoreDb.close();
+        mDatabaseFile.delete();
     }
 
     @Test
     public void generateAndStoreKey_setsKeyInKeyStore() throws Exception {
-        mRecoverableKeyGenerator.generateAndStoreKey(TEST_ALIAS);
+        mRecoverableKeyGenerator.generateAndStoreKey(mPlatformKey, KEYSTORE_UID_SELF, TEST_ALIAS);
 
-        verify(mRecoverableKeyStorage, times(1))
-                .importIntoAndroidKeyStore(eq(TEST_ALIAS), any(), any());
+        KeyStore keyStore = AndroidKeyStoreProvider.getKeyStoreForUid(KEYSTORE_UID_SELF);
+        assertTrue(keyStore.containsAlias(TEST_ALIAS));
     }
 
     @Test
-    public void generateAndStoreKey_storesKeyEnabledForEncryptDecrypt() throws Exception {
-        mRecoverableKeyGenerator.generateAndStoreKey(TEST_ALIAS);
+    public void generateAndStoreKey_storesKeyEnabledForAesGcmNoPaddingEncryptDecrypt()
+            throws Exception {
+        mRecoverableKeyGenerator.generateAndStoreKey(mPlatformKey, KEYSTORE_UID_SELF, TEST_ALIAS);
 
-        KeyProtection keyProtection = getKeyProtectionUsed();
-        assertEquals(KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT,
-                keyProtection.getPurposes());
+        KeyStore keyStore = AndroidKeyStoreProvider.getKeyStoreForUid(KEYSTORE_UID_SELF);
+        SecretKey key = (SecretKey) keyStore.getKey(TEST_ALIAS, /*password=*/ null);
+        Cipher cipher = Cipher.getInstance(SUPPORTED_CIPHER_ALGORITHM);
+        cipher.init(Cipher.ENCRYPT_MODE, key);
+        byte[] nonce = new byte[GCM_NONCE_LENGTH_BYTES];
+        Arrays.fill(nonce, (byte) 0);
+        cipher.init(Cipher.DECRYPT_MODE, key, new GCMParameterSpec(GCM_TAG_LENGTH_BITS, nonce));
     }
 
     @Test
-    public void generateAndStoreKey_storesKeyEnabledForGCM() throws Exception {
-        mRecoverableKeyGenerator.generateAndStoreKey(TEST_ALIAS);
+    public void generateAndStoreKey_storesKeyDisabledForOtherModes() throws Exception {
+        mRecoverableKeyGenerator.generateAndStoreKey(mPlatformKey, KEYSTORE_UID_SELF, TEST_ALIAS);
 
-        KeyProtection keyProtection = getKeyProtectionUsed();
-        assertArrayEquals(new String[] { KeyProperties.BLOCK_MODE_GCM },
-                keyProtection.getBlockModes());
-    }
+        KeyStore keyStore = AndroidKeyStoreProvider.getKeyStoreForUid(KEYSTORE_UID_SELF);
+        SecretKey key = (SecretKey) keyStore.getKey(TEST_ALIAS, /*password=*/ null);
+        Cipher cipher = Cipher.getInstance(UNSUPPORTED_CIPHER_ALGORITHM);
 
-    @Test
-    public void generateAndStoreKey_storesKeyEnabledForNoPadding() throws Exception {
-        mRecoverableKeyGenerator.generateAndStoreKey(TEST_ALIAS);
-
-        KeyProtection keyProtection = getKeyProtectionUsed();
-        assertArrayEquals(new String[] { KeyProperties.ENCRYPTION_PADDING_NONE },
-                keyProtection.getEncryptionPaddings());
+        try {
+            cipher.init(Cipher.ENCRYPT_MODE, key);
+            fail("Should not be able to use key for " + UNSUPPORTED_CIPHER_ALGORITHM);
+        } catch (InvalidKeyException e) {
+            // expected
+        }
     }
 
     @Test
     public void generateAndStoreKey_storesWrappedKey() throws Exception {
-        mRecoverableKeyGenerator.generateAndStoreKey(TEST_ALIAS);
+        mRecoverableKeyGenerator.generateAndStoreKey(mPlatformKey, KEYSTORE_UID_SELF, TEST_ALIAS);
 
-        verify(mRecoverableKeyStorage, times(1)).persistToDisk(eq(TEST_ALIAS), any());
-    }
+        KeyStore keyStore = AndroidKeyStoreProvider.getKeyStoreForUid(KEYSTORE_UID_SELF);
+        SecretKey key = (SecretKey) keyStore.getKey(TEST_ALIAS, /*password=*/ null);
+        WrappedKey wrappedKey = mRecoverableKeyStoreDb.getKey(KEYSTORE_UID_SELF, TEST_ALIAS);
+        SecretKey unwrappedKey = WrappedKey
+                .unwrapKeys(mDecryptKey, ImmutableMap.of(TEST_ALIAS, wrappedKey))
+                .get(TEST_ALIAS);
 
-    @Test
-    public void generateAndStoreKey_returnsKeyHandle() throws Exception {
-        SecretKey secretKey = mRecoverableKeyGenerator.generateAndStoreKey(TEST_ALIAS);
-
-        assertEquals(mKeyHandle, secretKey);
-    }
-
-    private KeyProtection getKeyProtectionUsed() throws Exception {
-        verify(mRecoverableKeyStorage, times(1)).importIntoAndroidKeyStore(
-                any(), any(), mKeyProtectionArgumentCaptor.capture());
-        return mKeyProtectionArgumentCaptor.getValue();
-    }
-
-    private SecretKey generateKey() throws Exception {
-        KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
-        keyGenerator.init(/*keySize=*/ 256);
-        return keyGenerator.generateKey();
+        // key and unwrappedKey should be equivalent. let's check!
+        byte[] plaintext = getUtf8Bytes("dtianpos");
+        Cipher cipher = Cipher.getInstance(SUPPORTED_CIPHER_ALGORITHM);
+        cipher.init(Cipher.ENCRYPT_MODE, key);
+        byte[] encrypted = cipher.doFinal(plaintext);
+        byte[] iv = cipher.getIV();
+        cipher.init(Cipher.DECRYPT_MODE, unwrappedKey, new GCMParameterSpec(128, iv));
+        byte[] decrypted = cipher.doFinal(encrypted);
+        assertArrayEquals(decrypted, plaintext);
     }
 
     private AndroidKeyStoreSecretKey generateAndroidKeyStoreKey() throws Exception {
@@ -153,4 +164,8 @@
                     .build());
         return (AndroidKeyStoreSecretKey) keyGenerator.generateKey();
     }
+
+    private static byte[] getUtf8Bytes(String s) {
+        return s.getBytes(StandardCharsets.UTF_8);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStorageImplTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStorageImplTest.java
deleted file mode 100644
index fb4e75e..0000000
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStorageImplTest.java
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.locksettings.recoverablekeystore;
-
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.fail;
-
-import android.security.keystore.KeyProperties;
-import android.security.keystore.KeyProtection;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import java.nio.charset.StandardCharsets;
-import java.security.InvalidKeyException;
-import java.security.KeyStoreException;
-import java.util.Random;
-
-import javax.crypto.Cipher;
-import javax.crypto.KeyGenerator;
-import javax.crypto.Mac;
-import javax.crypto.SecretKey;
-import javax.crypto.spec.GCMParameterSpec;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class RecoverableKeyStorageImplTest {
-    private static final String KEY_ALGORITHM = "AES";
-    private static final int GCM_TAG_LENGTH_BYTES = 16;
-    private static final int BITS_PER_BYTE = 8;
-    private static final int GCM_TAG_LENGTH_BITS = GCM_TAG_LENGTH_BYTES * BITS_PER_BYTE;
-    private static final int GCM_NONCE_LENGTH_BYTES = 12;
-    private static final String TEST_KEY_ALIAS = "RecoverableKeyStorageImplTestKey";
-    private static final int KEYSTORE_UID_SELF = -1;
-
-    private RecoverableKeyStorageImpl mRecoverableKeyStorage;
-
-    @Before
-    public void setUp() throws Exception {
-        mRecoverableKeyStorage = RecoverableKeyStorageImpl.newInstance(
-                /*userId=*/ KEYSTORE_UID_SELF);
-    }
-
-    @After
-    public void tearDown() {
-        try {
-            mRecoverableKeyStorage.removeFromAndroidKeyStore(TEST_KEY_ALIAS);
-        } catch (KeyStoreException e) {
-            // Do nothing.
-        }
-    }
-
-    @Test
-    public void loadFromAndroidKeyStore_loadsAKeyThatWasImported() throws Exception {
-        SecretKey key = generateKey();
-        mRecoverableKeyStorage.importIntoAndroidKeyStore(
-                TEST_KEY_ALIAS,
-                key,
-                getKeyProperties());
-
-        assertKeysAreEquivalent(
-                key, mRecoverableKeyStorage.loadFromAndroidKeyStore(TEST_KEY_ALIAS));
-    }
-
-    @Test
-    public void importIntoAndroidKeyStore_importsWithKeyProperties() throws Exception {
-        mRecoverableKeyStorage.importIntoAndroidKeyStore(
-                TEST_KEY_ALIAS,
-                generateKey(),
-                getKeyProperties());
-
-        SecretKey key = mRecoverableKeyStorage.loadFromAndroidKeyStore(TEST_KEY_ALIAS);
-
-        Mac mac = Mac.getInstance("HmacSHA256");
-        try {
-            // Fails because missing PURPOSE_SIGN or PURPOSE_VERIFY
-            mac.init(key);
-            fail("Was able to initialize Mac with an ENCRYPT/DECRYPT-only key.");
-        } catch (InvalidKeyException e) {
-            // expect exception
-        }
-    }
-
-    @Test
-    public void removeFromAndroidKeyStore_removesAnEntry() throws Exception {
-        mRecoverableKeyStorage.importIntoAndroidKeyStore(
-                TEST_KEY_ALIAS,
-                generateKey(),
-                getKeyProperties());
-
-        mRecoverableKeyStorage.removeFromAndroidKeyStore(TEST_KEY_ALIAS);
-
-        assertNull(mRecoverableKeyStorage.loadFromAndroidKeyStore(TEST_KEY_ALIAS));
-    }
-
-    private static KeyProtection getKeyProperties() {
-        return new KeyProtection.Builder(
-                KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
-                .setBlockModes(KeyProperties.BLOCK_MODE_GCM)
-                .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
-                .build();
-    }
-
-    /**
-     * Asserts that {@code b} key can decrypt data encrypted with {@code a} key. Otherwise throws.
-     */
-    private static void assertKeysAreEquivalent(SecretKey a, SecretKey b) throws Exception {
-        byte[] plaintext = "doge".getBytes(StandardCharsets.UTF_8);
-        byte[] nonce = generateGcmNonce();
-
-        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
-        cipher.init(Cipher.ENCRYPT_MODE, a, new GCMParameterSpec(GCM_TAG_LENGTH_BITS, nonce));
-        byte[] encrypted = cipher.doFinal(plaintext);
-
-        cipher.init(Cipher.DECRYPT_MODE, b, new GCMParameterSpec(GCM_TAG_LENGTH_BITS, nonce));
-        byte[] decrypted = cipher.doFinal(encrypted);
-
-        assertArrayEquals(decrypted, plaintext);
-    }
-
-    /**
-     * Returns a new random GCM nonce.
-     */
-    private static byte[] generateGcmNonce() {
-        Random random = new Random();
-        byte[] nonce = new byte[GCM_NONCE_LENGTH_BYTES];
-        random.nextBytes(nonce);
-        return nonce;
-    }
-
-    private static SecretKey generateKey() throws Exception {
-        KeyGenerator keyGenerator = KeyGenerator.getInstance(KEY_ALGORITHM);
-        keyGenerator.init(/*keySize=*/ 256);
-        return keyGenerator.generateKey();
-    }
-}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
new file mode 100644
index 0000000..35b18b1
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java
@@ -0,0 +1,186 @@
+/*
+ * 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.locksettings.recoverablekeystore;
+
+import static android.security.recoverablekeystore.KeyStoreRecoveryMetadata.TYPE_LOCKSCREEN;
+import static android.security.recoverablekeystore.KeyStoreRecoveryMetadata.TYPE_PASSWORD;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.os.RemoteException;
+import android.security.recoverablekeystore.KeyDerivationParameters;
+import android.security.recoverablekeystore.KeyStoreRecoveryMetadata;
+import android.security.recoverablekeystore.RecoverableKeyStoreLoader;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDb;
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverySessionStorage;
+
+import com.google.common.collect.ImmutableList;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.nio.charset.StandardCharsets;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class RecoverableKeyStoreManagerTest {
+    private static final String DATABASE_FILE_NAME = "recoverablekeystore.db";
+
+    private static final String TEST_SESSION_ID = "karlin";
+    private static final byte[] TEST_PUBLIC_KEY = new byte[] {
+        (byte) 0x30, (byte) 0x59, (byte) 0x30, (byte) 0x13, (byte) 0x06, (byte) 0x07, (byte) 0x2a,
+        (byte) 0x86, (byte) 0x48, (byte) 0xce, (byte) 0x3d, (byte) 0x02, (byte) 0x01, (byte) 0x06,
+        (byte) 0x08, (byte) 0x2a, (byte) 0x86, (byte) 0x48, (byte) 0xce, (byte) 0x3d, (byte) 0x03,
+        (byte) 0x01, (byte) 0x07, (byte) 0x03, (byte) 0x42, (byte) 0x00, (byte) 0x04, (byte) 0xb8,
+        (byte) 0x00, (byte) 0x11, (byte) 0x18, (byte) 0x98, (byte) 0x1d, (byte) 0xf0, (byte) 0x6e,
+        (byte) 0xb4, (byte) 0x94, (byte) 0xfe, (byte) 0x86, (byte) 0xda, (byte) 0x1c, (byte) 0x07,
+        (byte) 0x8d, (byte) 0x01, (byte) 0xb4, (byte) 0x3a, (byte) 0xf6, (byte) 0x8d, (byte) 0xdc,
+        (byte) 0x61, (byte) 0xd0, (byte) 0x46, (byte) 0x49, (byte) 0x95, (byte) 0x0f, (byte) 0x10,
+        (byte) 0x86, (byte) 0x93, (byte) 0x24, (byte) 0x66, (byte) 0xe0, (byte) 0x3f, (byte) 0xd2,
+        (byte) 0xdf, (byte) 0xf3, (byte) 0x79, (byte) 0x20, (byte) 0x1d, (byte) 0x91, (byte) 0x55,
+        (byte) 0xb0, (byte) 0xe5, (byte) 0xbd, (byte) 0x7a, (byte) 0x8b, (byte) 0x32, (byte) 0x7d,
+        (byte) 0x25, (byte) 0x53, (byte) 0xa2, (byte) 0xfc, (byte) 0xa5, (byte) 0x65, (byte) 0xe1,
+        (byte) 0xbd, (byte) 0x21, (byte) 0x44, (byte) 0x7e, (byte) 0x78, (byte) 0x52, (byte) 0xfa};
+    private static final byte[] TEST_SALT = getUtf8Bytes("salt");
+    private static final byte[] TEST_SECRET = getUtf8Bytes("password1234");
+    private static final byte[] TEST_VAULT_CHALLENGE = getUtf8Bytes("vault_challenge");
+    private static final byte[] TEST_VAULT_PARAMS = getUtf8Bytes("vault_params");
+    private static final int TEST_USER_ID = 10009;
+    private static final int KEY_CLAIMANT_LENGTH_BYTES = 16;
+
+    @Mock private Context mMockContext;
+
+    private RecoverableKeyStoreDb mRecoverableKeyStoreDb;
+    private File mDatabaseFile;
+    private RecoverableKeyStoreManager mRecoverableKeyStoreManager;
+    private RecoverySessionStorage mRecoverySessionStorage;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        Context context = InstrumentationRegistry.getTargetContext();
+        mDatabaseFile = context.getDatabasePath(DATABASE_FILE_NAME);
+        mRecoverableKeyStoreDb = RecoverableKeyStoreDb.newInstance(context);
+        mRecoverySessionStorage = new RecoverySessionStorage();
+        mRecoverableKeyStoreManager = new RecoverableKeyStoreManager(
+                mMockContext,
+                mRecoverableKeyStoreDb,
+                mRecoverySessionStorage);
+    }
+
+    @After
+    public void tearDown() {
+        mRecoverableKeyStoreDb.close();
+        mDatabaseFile.delete();
+    }
+
+    @Test
+    public void startRecoverySession_checksPermissionFirst() throws Exception {
+        mRecoverableKeyStoreManager.startRecoverySession(
+                TEST_SESSION_ID,
+                TEST_PUBLIC_KEY,
+                TEST_VAULT_PARAMS,
+                TEST_VAULT_CHALLENGE,
+                ImmutableList.of(new KeyStoreRecoveryMetadata(
+                        TYPE_LOCKSCREEN,
+                        TYPE_PASSWORD,
+                        KeyDerivationParameters.createSHA256Parameters(TEST_SALT),
+                        TEST_SECRET)),
+                TEST_USER_ID);
+
+        verify(mMockContext, times(1)).enforceCallingOrSelfPermission(
+                eq(RecoverableKeyStoreLoader.PERMISSION_RECOVER_KEYSTORE),
+                any());
+    }
+
+    @Test
+    public void startRecoverySession_storesTheSessionInfo() throws Exception {
+        mRecoverableKeyStoreManager.startRecoverySession(
+                TEST_SESSION_ID,
+                TEST_PUBLIC_KEY,
+                TEST_VAULT_PARAMS,
+                TEST_VAULT_CHALLENGE,
+                ImmutableList.of(new KeyStoreRecoveryMetadata(
+                        TYPE_LOCKSCREEN,
+                        TYPE_PASSWORD,
+                        KeyDerivationParameters.createSHA256Parameters(TEST_SALT),
+                        TEST_SECRET)),
+                TEST_USER_ID);
+
+        assertEquals(1, mRecoverySessionStorage.size());
+        RecoverySessionStorage.Entry entry = mRecoverySessionStorage.get(
+                TEST_USER_ID, TEST_SESSION_ID);
+        assertArrayEquals(TEST_SECRET, entry.getLskfHash());
+        assertEquals(KEY_CLAIMANT_LENGTH_BYTES, entry.getKeyClaimant().length);
+    }
+
+    @Test
+    public void startRecoverySession_throwsIfBadNumberOfSecrets() throws Exception {
+        try {
+            mRecoverableKeyStoreManager.startRecoverySession(
+                    TEST_SESSION_ID,
+                    TEST_PUBLIC_KEY,
+                    TEST_VAULT_PARAMS,
+                    TEST_VAULT_CHALLENGE,
+                    ImmutableList.of(),
+                    TEST_USER_ID);
+        } catch (RemoteException e) {
+            assertEquals("Only a single KeyStoreRecoveryMetadata is supported",
+                    e.getMessage());
+        }
+    }
+
+    @Test
+    public void startRecoverySession_throwsIfBadKey() throws Exception {
+        try {
+            mRecoverableKeyStoreManager.startRecoverySession(
+                    TEST_SESSION_ID,
+                    getUtf8Bytes("0"),
+                    TEST_VAULT_PARAMS,
+                    TEST_VAULT_CHALLENGE,
+                    ImmutableList.of(new KeyStoreRecoveryMetadata(
+                            TYPE_LOCKSCREEN,
+                            TYPE_PASSWORD,
+                            KeyDerivationParameters.createSHA256Parameters(TEST_SALT),
+                            TEST_SECRET)),
+                    TEST_USER_ID);
+        } catch (RemoteException e) {
+            assertEquals("Not a valid X509 key",
+                    e.getMessage());
+        }
+    }
+
+    private static byte[] getUtf8Bytes(String s) {
+        return s.getBytes(StandardCharsets.UTF_8);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorageTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorageTest.java
new file mode 100644
index 0000000..6aeff28
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverySessionStorageTest.java
@@ -0,0 +1,132 @@
+/*
+ * 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.locksettings.recoverablekeystore.storage;
+
+import static junit.framework.Assert.fail;
+
+import static org.junit.Assert.assertEquals;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class RecoverySessionStorageTest {
+
+    private static final String TEST_SESSION_ID = "peter";
+    private static final int TEST_USER_ID = 696;
+    private static final byte[] TEST_LSKF_HASH = getUtf8Bytes("lskf");
+    private static final byte[] TEST_KEY_CLAIMANT = getUtf8Bytes("0000111122223333");
+
+    @Test
+    public void size_isZeroForEmpty() {
+        assertEquals(0, new RecoverySessionStorage().size());
+    }
+
+    @Test
+    public void size_incrementsAfterAdd() {
+        RecoverySessionStorage storage = new RecoverySessionStorage();
+        storage.add(TEST_USER_ID, new RecoverySessionStorage.Entry(
+                TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture()));
+
+        assertEquals(1, storage.size());
+    }
+
+    @Test
+    public void size_decrementsAfterRemove() {
+        RecoverySessionStorage storage = new RecoverySessionStorage();
+        storage.add(TEST_USER_ID, new RecoverySessionStorage.Entry(
+                TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture()));
+        storage.remove(TEST_USER_ID);
+
+        assertEquals(0, storage.size());
+    }
+
+    @Test
+    public void remove_overwritesLskfHashMemory() {
+        RecoverySessionStorage storage = new RecoverySessionStorage();
+        RecoverySessionStorage.Entry entry = new RecoverySessionStorage.Entry(
+                TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture());
+        storage.add(TEST_USER_ID, entry);
+
+        storage.remove(TEST_USER_ID);
+
+        assertZeroedOut(entry.getLskfHash());
+    }
+
+    @Test
+    public void remove_overwritesKeyClaimantMemory() {
+        RecoverySessionStorage storage = new RecoverySessionStorage();
+        RecoverySessionStorage.Entry entry = new RecoverySessionStorage.Entry(
+                TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture());
+        storage.add(TEST_USER_ID, entry);
+
+        storage.remove(TEST_USER_ID);
+
+        assertZeroedOut(entry.getKeyClaimant());
+    }
+
+    @Test
+    public void destroy_overwritesLskfHashMemory() {
+        RecoverySessionStorage storage = new RecoverySessionStorage();
+        RecoverySessionStorage.Entry entry = new RecoverySessionStorage.Entry(
+                TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture());
+        storage.add(TEST_USER_ID, entry);
+
+        storage.destroy();
+
+        assertZeroedOut(entry.getLskfHash());
+    }
+
+    @Test
+    public void destroy_overwritesKeyClaimantMemory() {
+        RecoverySessionStorage storage = new RecoverySessionStorage();
+        RecoverySessionStorage.Entry entry = new RecoverySessionStorage.Entry(
+                TEST_SESSION_ID, lskfHashFixture(), keyClaimantFixture());
+        storage.add(TEST_USER_ID, entry);
+
+        storage.destroy();
+
+        assertZeroedOut(entry.getKeyClaimant());
+    }
+
+    private static void assertZeroedOut(byte[] bytes) {
+        for (byte b : bytes) {
+            if (b != (byte) 0) {
+                fail("Bytes were not all zeroed out.");
+            }
+        }
+    }
+
+    private static byte[] lskfHashFixture() {
+        return Arrays.copyOf(TEST_LSKF_HASH, TEST_LSKF_HASH.length);
+    }
+
+    private static byte[] keyClaimantFixture() {
+        return Arrays.copyOf(TEST_KEY_CLAIMANT, TEST_KEY_CLAIMANT.length);
+    }
+
+    private static byte[] getUtf8Bytes(String s) {
+        return s.getBytes(StandardCharsets.UTF_8);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java
index 337fd50..0959df2 100644
--- a/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowFrameTests.java
@@ -451,9 +451,9 @@
     private DisplayCutout createDisplayCutoutFromRect(int left, int top, int right, int bottom) {
         return DisplayCutout.fromBoundingPolygon(Arrays.asList(
                 new Point(left, top),
-                new Point (left, bottom),
-                new Point (right, bottom),
-                new Point (left, bottom)
+                new Point(left, bottom),
+                new Point(right, bottom),
+                new Point(right, top)
         ));
     }
 }
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index c0685f9..44f5551 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -67,6 +67,7 @@
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.Preconditions;
+import com.android.server.FgThread;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.UiThread;
@@ -389,11 +390,13 @@
         }
 
         public void switchUser(int userHandle) {
-            synchronized (this) {
-                mCurUser = userHandle;
-                mCurUserUnlocked = false;
-                switchImplementationIfNeededLocked(false);
-            }
+            FgThread.getHandler().post(() -> {
+                synchronized (this) {
+                    mCurUser = userHandle;
+                    mCurUserUnlocked = false;
+                    switchImplementationIfNeededLocked(false);
+                }
+            });
         }
 
         void switchImplementationIfNeeded(boolean force) {
diff --git a/tests/net/java/com/android/server/IpSecServiceTest.java b/tests/net/java/com/android/server/IpSecServiceTest.java
index f38a9a3..5d1e10e 100644
--- a/tests/net/java/com/android/server/IpSecServiceTest.java
+++ b/tests/net/java/com/android/server/IpSecServiceTest.java
@@ -475,4 +475,26 @@
         testIpSecService.closeUdpEncapsulationSocket(udpEncapResp.resourceId);
         udpEncapResp.fileDescriptor.close();
     }
+
+    @Test
+    public void testOpenUdpEncapsulationSocketCallsSetEncapSocketOwner() throws Exception {
+        IpSecUdpEncapResponse udpEncapResp =
+                mIpSecService.openUdpEncapsulationSocket(0, new Binder());
+
+        FileDescriptor sockFd = udpEncapResp.fileDescriptor.getFileDescriptor();
+        ArgumentMatcher<FileDescriptor> fdMatcher = (arg) -> {
+                    try {
+                        StructStat sockStat = Os.fstat(sockFd);
+                        StructStat argStat = Os.fstat(arg);
+
+                        return sockStat.st_ino == argStat.st_ino
+                                && sockStat.st_dev == argStat.st_dev;
+                    } catch (ErrnoException e) {
+                        return false;
+                    }
+                };
+
+        verify(mMockNetd).ipSecSetEncapSocketOwner(argThat(fdMatcher), eq(Os.getuid()));
+        mIpSecService.closeUdpEncapsulationSocket(udpEncapResp.resourceId);
+    }
 }
diff --git a/tests/utils/testutils/Android.mk b/tests/utils/testutils/Android.mk
index 543c652..a76616f 100644
--- a/tests/utils/testutils/Android.mk
+++ b/tests/utils/testutils/Android.mk
@@ -24,9 +24,12 @@
 LOCAL_SRC_FILES := $(call all-java-files-under,java)
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
-    android-support-test \
-    mockito-target-minus-junit4
+    android-support-test
 
-LOCAL_JAVA_LIBRARIES := android.test.runner android.test.base android.test.mock
+LOCAL_JAVA_LIBRARIES := \
+    android.test.runner \
+    android.test.base \
+    android.test.mock \
+    mockito-target-minus-junit4 \
 
 include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/tools/aapt2/cmd/Optimize.cpp b/tools/aapt2/cmd/Optimize.cpp
index d8bb999..9c76119 100644
--- a/tools/aapt2/cmd/Optimize.cpp
+++ b/tools/aapt2/cmd/Optimize.cpp
@@ -377,44 +377,10 @@
   }
 
   const std::string& apk_path = flags.GetArgs()[0];
-  std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(apk_path, context.GetDiagnostics());
-  if (!apk) {
-    return 1;
-  }
 
   context.SetVerbose(verbose);
   IDiagnostics* diag = context.GetDiagnostics();
 
-  if (target_densities) {
-    // Parse the target screen densities.
-    for (const StringPiece& config_str : util::Tokenize(target_densities.value(), ',')) {
-      Maybe<uint16_t> target_density = ParseTargetDensityParameter(config_str, diag);
-      if (!target_density) {
-        return 1;
-      }
-      options.table_splitter_options.preferred_densities.push_back(target_density.value());
-    }
-  }
-
-  std::unique_ptr<IConfigFilter> filter;
-  if (!configs.empty()) {
-    filter = ParseConfigFilterParameters(configs, diag);
-    if (filter == nullptr) {
-      return 1;
-    }
-    options.table_splitter_options.config_filter = filter.get();
-  }
-
-  // Parse the split parameters.
-  for (const std::string& split_arg : split_args) {
-    options.split_paths.emplace_back();
-    options.split_constraints.emplace_back();
-    if (!ParseSplitParameter(split_arg, diag, &options.split_paths.back(),
-                             &options.split_constraints.back())) {
-      return 1;
-    }
-  }
-
   if (config_path) {
     std::string& path = config_path.value();
     Maybe<ConfigurationParser> for_path = ConfigurationParser::ForPath(path);
@@ -456,6 +422,41 @@
     return 1;
   }
 
+  std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(apk_path, context.GetDiagnostics());
+  if (!apk) {
+    return 1;
+  }
+
+  if (target_densities) {
+    // Parse the target screen densities.
+    for (const StringPiece& config_str : util::Tokenize(target_densities.value(), ',')) {
+      Maybe<uint16_t> target_density = ParseTargetDensityParameter(config_str, diag);
+      if (!target_density) {
+        return 1;
+      }
+      options.table_splitter_options.preferred_densities.push_back(target_density.value());
+    }
+  }
+
+  std::unique_ptr<IConfigFilter> filter;
+  if (!configs.empty()) {
+    filter = ParseConfigFilterParameters(configs, diag);
+    if (filter == nullptr) {
+      return 1;
+    }
+    options.table_splitter_options.config_filter = filter.get();
+  }
+
+  // Parse the split parameters.
+  for (const std::string& split_arg : split_args) {
+    options.split_paths.emplace_back();
+    options.split_constraints.emplace_back();
+    if (!ParseSplitParameter(split_arg, diag, &options.split_paths.back(),
+                             &options.split_constraints.back())) {
+      return 1;
+    }
+  }
+
   if (options.table_flattener_options.collapse_key_stringpool) {
     if (whitelist_path) {
       std::string& path = whitelist_path.value();
diff --git a/tools/aapt2/configuration/ConfigurationParser.cpp b/tools/aapt2/configuration/ConfigurationParser.cpp
index ebc523f..eabeb47 100644
--- a/tools/aapt2/configuration/ConfigurationParser.cpp
+++ b/tools/aapt2/configuration/ConfigurationParser.cpp
@@ -49,13 +49,15 @@
 using ::aapt::configuration::ConfiguredArtifact;
 using ::aapt::configuration::DeviceFeature;
 using ::aapt::configuration::Entry;
+using ::aapt::configuration::ExtractConfiguration;
 using ::aapt::configuration::GlTexture;
 using ::aapt::configuration::Group;
 using ::aapt::configuration::Locale;
+using ::aapt::configuration::OrderedEntry;
 using ::aapt::configuration::OutputArtifact;
 using ::aapt::configuration::PostProcessingConfiguration;
 using ::aapt::configuration::handler::AbiGroupTagHandler;
-using ::aapt::configuration::handler::AndroidSdkGroupTagHandler;
+using ::aapt::configuration::handler::AndroidSdkTagHandler;
 using ::aapt::configuration::handler::ArtifactFormatTagHandler;
 using ::aapt::configuration::handler::ArtifactTagHandler;
 using ::aapt::configuration::handler::DeviceFeatureGroupTagHandler;
@@ -130,7 +132,7 @@
     return false;
   }
 
-  for (const T& item : group->second) {
+  for (const T& item : group->second.entry) {
     target->push_back(item);
   }
   return true;
@@ -188,61 +190,6 @@
   };
 }
 
-/** Returns the binary reprasentation of the XML configuration. */
-Maybe<PostProcessingConfiguration> ExtractConfiguration(const std::string& contents,
-                                                        IDiagnostics* diag) {
-  StringInputStream in(contents);
-  std::unique_ptr<xml::XmlResource> doc = xml::Inflate(&in, diag, Source("config.xml"));
-  if (!doc) {
-    return {};
-  }
-
-  // Strip any namespaces from the XML as the XmlActionExecutor ignores anything with a namespace.
-  Element* root = doc->root.get();
-  if (root == nullptr) {
-    diag->Error(DiagMessage() << "Could not find the root element in the XML document");
-    return {};
-  }
-
-  std::string& xml_ns = root->namespace_uri;
-  if (!xml_ns.empty()) {
-    if (xml_ns != kAaptXmlNs) {
-      diag->Error(DiagMessage() << "Unknown namespace found on root element: " << xml_ns);
-      return {};
-    }
-
-    xml_ns.clear();
-    NamespaceVisitor visitor;
-    root->Accept(&visitor);
-  }
-
-  XmlActionExecutor executor;
-  XmlNodeAction& root_action = executor["post-process"];
-  XmlNodeAction& artifacts_action = root_action["artifacts"];
-  XmlNodeAction& groups_action = root_action["groups"];
-
-  PostProcessingConfiguration config;
-
-  // Parse the artifact elements.
-  artifacts_action["artifact"].Action(Bind(&config, ArtifactTagHandler));
-  artifacts_action["artifact-format"].Action(Bind(&config, ArtifactFormatTagHandler));
-
-  // Parse the different configuration groups.
-  groups_action["abi-group"].Action(Bind(&config, AbiGroupTagHandler));
-  groups_action["screen-density-group"].Action(Bind(&config, ScreenDensityGroupTagHandler));
-  groups_action["locale-group"].Action(Bind(&config, LocaleGroupTagHandler));
-  groups_action["android-sdk-group"].Action(Bind(&config, AndroidSdkGroupTagHandler));
-  groups_action["gl-texture-group"].Action(Bind(&config, GlTextureGroupTagHandler));
-  groups_action["device-feature-group"].Action(Bind(&config, DeviceFeatureGroupTagHandler));
-
-  if (!executor.Execute(XmlActionExecutorPolicy::kNone, diag, doc.get())) {
-    diag->Error(DiagMessage() << "Could not process XML document");
-    return {};
-  }
-
-  return {config};
-}
-
 /** Converts a ConfiguredArtifact into an OutputArtifact. */
 Maybe<OutputArtifact> ToOutputArtifact(const ConfiguredArtifact& artifact,
                                        const std::string& apk_name,
@@ -302,11 +249,11 @@
     has_errors = true;
   }
 
-  if (artifact.android_sdk_group) {
-    auto entry = config.android_sdk_groups.find(artifact.android_sdk_group.value());
-    if (entry == config.android_sdk_groups.end()) {
+  if (artifact.android_sdk) {
+    auto entry = config.android_sdks.find(artifact.android_sdk.value());
+    if (entry == config.android_sdks.end()) {
       src_diag.Error(DiagMessage() << "Could not lookup required Android SDK version: "
-                                   << artifact.android_sdk_group.value());
+                                   << artifact.android_sdk.value());
       has_errors = true;
     } else {
       output_artifact.android_sdk = {entry->second};
@@ -323,6 +270,64 @@
 
 namespace configuration {
 
+/** Returns the binary reprasentation of the XML configuration. */
+Maybe<PostProcessingConfiguration> ExtractConfiguration(const std::string& contents,
+                                                        const std::string& config_path,
+                                                        IDiagnostics* diag) {
+  StringInputStream in(contents);
+  std::unique_ptr<xml::XmlResource> doc = xml::Inflate(&in, diag, Source(config_path));
+  if (!doc) {
+    return {};
+  }
+
+  // Strip any namespaces from the XML as the XmlActionExecutor ignores anything with a namespace.
+  Element* root = doc->root.get();
+  if (root == nullptr) {
+    diag->Error(DiagMessage() << "Could not find the root element in the XML document");
+    return {};
+  }
+
+  std::string& xml_ns = root->namespace_uri;
+  if (!xml_ns.empty()) {
+    if (xml_ns != kAaptXmlNs) {
+      diag->Error(DiagMessage() << "Unknown namespace found on root element: " << xml_ns);
+      return {};
+    }
+
+    xml_ns.clear();
+    NamespaceVisitor visitor;
+    root->Accept(&visitor);
+  }
+
+  XmlActionExecutor executor;
+  XmlNodeAction& root_action = executor["post-process"];
+  XmlNodeAction& artifacts_action = root_action["artifacts"];
+
+  PostProcessingConfiguration config;
+
+  // Parse the artifact elements.
+  artifacts_action["artifact"].Action(Bind(&config, ArtifactTagHandler));
+  artifacts_action["artifact-format"].Action(Bind(&config, ArtifactFormatTagHandler));
+
+  // Parse the different configuration groups.
+  root_action["abi-groups"]["abi-group"].Action(Bind(&config, AbiGroupTagHandler));
+  root_action["screen-density-groups"]["screen-density-group"].Action(
+      Bind(&config, ScreenDensityGroupTagHandler));
+  root_action["locale-groups"]["locale-group"].Action(Bind(&config, LocaleGroupTagHandler));
+  root_action["android-sdks"]["android-sdk"].Action(Bind(&config, AndroidSdkTagHandler));
+  root_action["gl-texture-groups"]["gl-texture-group"].Action(
+      Bind(&config, GlTextureGroupTagHandler));
+  root_action["device-feature-groups"]["device-feature-group"].Action(
+      Bind(&config, DeviceFeatureGroupTagHandler));
+
+  if (!executor.Execute(XmlActionExecutorPolicy::kNone, diag, doc.get())) {
+    diag->Error(DiagMessage() << "Could not process XML document");
+    return {};
+  }
+
+  return {config};
+}
+
 const StringPiece& AbiToString(Abi abi) {
   return kAbiToStringMap.at(static_cast<size_t>(abi));
 }
@@ -383,7 +388,7 @@
     return {};
   }
 
-  if (!ReplacePlaceholder("${sdk}", android_sdk_group, &result, diag)) {
+  if (!ReplacePlaceholder("${sdk}", android_sdk, &result, diag)) {
     return {};
   }
 
@@ -414,47 +419,37 @@
   if (!ReadFileToString(path, &contents, true)) {
     return {};
   }
-  return ConfigurationParser(contents);
+  return ConfigurationParser(contents, path);
 }
 
-ConfigurationParser::ConfigurationParser(std::string contents)
-    : contents_(std::move(contents)),
-      diag_(&noop_) {
+ConfigurationParser::ConfigurationParser(std::string contents, const std::string& config_path)
+    : contents_(std::move(contents)), config_path_(config_path), diag_(&noop_) {
 }
 
 Maybe<std::vector<OutputArtifact>> ConfigurationParser::Parse(
     const android::StringPiece& apk_path) {
-  Maybe<PostProcessingConfiguration> maybe_config = ExtractConfiguration(contents_, diag_);
+  Maybe<PostProcessingConfiguration> maybe_config =
+      ExtractConfiguration(contents_, config_path_, diag_);
   if (!maybe_config) {
     return {};
   }
-  const PostProcessingConfiguration& config = maybe_config.value();
-
-  // TODO: Automatically arrange artifacts so that they match Play Store multi-APK requirements.
-  // see: https://developer.android.com/google/play/publishing/multiple-apks.html
-  //
-  // For now, make sure the version codes are unique.
-  std::vector<ConfiguredArtifact> artifacts = config.artifacts;
-  std::sort(artifacts.begin(), artifacts.end());
-  if (std::adjacent_find(artifacts.begin(), artifacts.end()) != artifacts.end()) {
-    diag_->Error(DiagMessage() << "Configuration has duplicate versions");
-    return {};
-  }
-
-  const std::string& apk_name = file::GetFilename(apk_path).to_string();
-  const StringPiece ext = file::GetExtension(apk_name);
-  const std::string base_name = apk_name.substr(0, apk_name.size() - ext.size());
 
   // Convert from a parsed configuration to a list of artifacts for processing.
+  const std::string& apk_name = file::GetFilename(apk_path).to_string();
   std::vector<OutputArtifact> output_artifacts;
   bool has_errors = false;
 
-  for (const ConfiguredArtifact& artifact : artifacts) {
+  PostProcessingConfiguration& config = maybe_config.value();
+  config.SortArtifacts();
+
+  int version = 1;
+  for (const ConfiguredArtifact& artifact : config.artifacts) {
     Maybe<OutputArtifact> output_artifact = ToOutputArtifact(artifact, apk_name, config, diag_);
     if (!output_artifact) {
       // Defer return an error condition so that all errors are reported.
       has_errors = true;
     } else {
+      output_artifact.value().version = version++;
       output_artifacts.push_back(std::move(output_artifact.value()));
     }
   }
@@ -470,24 +465,18 @@
 
 bool ArtifactTagHandler(PostProcessingConfiguration* config, Element* root_element,
                         IDiagnostics* diag) {
-  // This will be incremented later so the first version will always be different to the base APK.
-  int current_version = (config->artifacts.empty()) ? 0 : config->artifacts.back().version;
-
   ConfiguredArtifact artifact{};
-  Maybe<int> version;
   for (const auto& attr : root_element->attributes) {
     if (attr.name == "name") {
       artifact.name = attr.value;
-    } else if (attr.name == "version") {
-      version = std::stoi(attr.value);
     } else if (attr.name == "abi-group") {
       artifact.abi_group = {attr.value};
     } else if (attr.name == "screen-density-group") {
       artifact.screen_density_group = {attr.value};
     } else if (attr.name == "locale-group") {
       artifact.locale_group = {attr.value};
-    } else if (attr.name == "android-sdk-group") {
-      artifact.android_sdk_group = {attr.value};
+    } else if (attr.name == "android-sdk") {
+      artifact.android_sdk = {attr.value};
     } else if (attr.name == "gl-texture-group") {
       artifact.gl_texture_group = {attr.value};
     } else if (attr.name == "device-feature-group") {
@@ -497,9 +486,6 @@
                                << attr.value);
     }
   }
-
-  artifact.version = (version) ? version.value() : current_version + 1;
-
   config->artifacts.push_back(artifact);
   return true;
 };
@@ -523,9 +509,19 @@
     return false;
   }
 
-  auto& group = config->abi_groups[label];
+  auto& group = GetOrCreateGroup(label, &config->abi_groups);
   bool valid = true;
 
+  // Special case for empty abi-group tag. Label will be used as the ABI.
+  if (root_element->GetChildElements().empty()) {
+    auto abi = kStringToAbiMap.find(label);
+    if (abi == kStringToAbiMap.end()) {
+      return false;
+    }
+    group.push_back(abi->second);
+    return true;
+  }
+
   for (auto* child : root_element->GetChildElements()) {
     if (child->name != "abi") {
       diag->Error(DiagMessage() << "Unexpected element in ABI group: " << child->name);
@@ -534,7 +530,13 @@
       for (auto& node : child->children) {
         xml::Text* t;
         if ((t = NodeCast<xml::Text>(node.get())) != nullptr) {
-          group.push_back(kStringToAbiMap.at(TrimWhitespace(t->text).to_string()));
+          auto abi = kStringToAbiMap.find(TrimWhitespace(t->text).to_string());
+          if (abi != kStringToAbiMap.end()) {
+            group.push_back(abi->second);
+          } else {
+            diag->Error(DiagMessage() << "Could not parse ABI value: " << t->text);
+            valid = false;
+          }
           break;
         }
       }
@@ -551,9 +553,28 @@
     return false;
   }
 
-  auto& group = config->screen_density_groups[label];
+  auto& group = GetOrCreateGroup(label, &config->screen_density_groups);
   bool valid = true;
 
+  // Special case for empty screen-density-group tag. Label will be used as the screen density.
+  if (root_element->GetChildElements().empty()) {
+    ConfigDescription config_descriptor;
+    bool parsed = ConfigDescription::Parse(label, &config_descriptor);
+    if (parsed &&
+        (config_descriptor.CopyWithoutSdkVersion().diff(ConfigDescription::DefaultConfig()) ==
+            android::ResTable_config::CONFIG_DENSITY)) {
+      // Copy the density with the minimum SDK version stripped out.
+      group.push_back(config_descriptor.CopyWithoutSdkVersion());
+    } else {
+      diag->Error(DiagMessage()
+                      << "Could not parse config descriptor for empty screen-density-group: "
+                      << label);
+      valid = false;
+    }
+
+    return valid;
+  }
+
   for (auto* child : root_element->GetChildElements()) {
     if (child->name != "screen-density") {
       diag->Error(DiagMessage() << "Unexpected root_element in screen density group: "
@@ -592,9 +613,28 @@
     return false;
   }
 
-  auto& group = config->locale_groups[label];
+  auto& group = GetOrCreateGroup(label, &config->locale_groups);
   bool valid = true;
 
+  // Special case to auto insert a locale for an empty group. Label will be used for locale.
+  if (root_element->GetChildElements().empty()) {
+    ConfigDescription config_descriptor;
+    bool parsed = ConfigDescription::Parse(label, &config_descriptor);
+    if (parsed &&
+        (config_descriptor.CopyWithoutSdkVersion().diff(ConfigDescription::DefaultConfig()) ==
+            android::ResTable_config::CONFIG_LOCALE)) {
+      // Copy the locale with the minimum SDK version stripped out.
+      group.push_back(config_descriptor.CopyWithoutSdkVersion());
+    } else {
+      diag->Error(DiagMessage()
+                      << "Could not parse config descriptor for empty screen-density-group: "
+                      << label);
+      valid = false;
+    }
+
+    return valid;
+  }
+
   for (auto* child : root_element->GetChildElements()) {
     if (child->name != "locale") {
       diag->Error(DiagMessage() << "Unexpected root_element in screen density group: "
@@ -626,61 +666,58 @@
   return valid;
 };
 
-bool AndroidSdkGroupTagHandler(PostProcessingConfiguration* config, Element* root_element,
-                               IDiagnostics* diag) {
-  std::string label = GetLabel(root_element, diag);
-  if (label.empty()) {
-    return false;
-  }
-
+bool AndroidSdkTagHandler(PostProcessingConfiguration* config, Element* root_element,
+                          IDiagnostics* diag) {
+  AndroidSdk entry = AndroidSdk::ForMinSdk(-1);
   bool valid = true;
-  bool found = false;
+  for (const auto& attr : root_element->attributes) {
+    bool valid_attr = false;
+    if (attr.name == "label") {
+      entry.label = attr.value;
+      valid_attr = true;
+    } else if (attr.name == "minSdkVersion") {
+      Maybe<int> version = ResourceUtils::ParseSdkVersion(attr.value);
+      if (version) {
+        valid_attr = true;
+        entry.min_sdk_version = version.value();
+      }
+    } else if (attr.name == "targetSdkVersion") {
+      Maybe<int> version = ResourceUtils::ParseSdkVersion(attr.value);
+      if (version) {
+        valid_attr = true;
+        entry.target_sdk_version = version;
+      }
+    } else if (attr.name == "maxSdkVersion") {
+      Maybe<int> version = ResourceUtils::ParseSdkVersion(attr.value);
+      if (version) {
+        valid_attr = true;
+        entry.max_sdk_version = version;
+      }
+    }
 
-  for (auto* child : root_element->GetChildElements()) {
-    if (child->name != "android-sdk") {
-      diag->Error(DiagMessage() << "Unexpected root_element in ABI group: " << child->name);
+    if (!valid_attr) {
+      diag->Error(DiagMessage() << "Invalid attribute: " << attr.name << " = " << attr.value);
       valid = false;
-    } else {
-      AndroidSdk entry;
-      for (const auto& attr : child->attributes) {
-        Maybe<int>* target = nullptr;
-        if (attr.name == "minSdkVersion") {
-          target = &entry.min_sdk_version;
-        } else if (attr.name == "targetSdkVersion") {
-          target = &entry.target_sdk_version;
-        } else if (attr.name == "maxSdkVersion") {
-          target = &entry.max_sdk_version;
-        } else {
-          diag->Warn(DiagMessage() << "Unknown attribute: " << attr.name << " = " << attr.value);
-          continue;
-        }
-
-        *target = ResourceUtils::ParseSdkVersion(attr.value);
-        if (!*target) {
-          diag->Error(DiagMessage() << "Invalid attribute: " << attr.name << " = " << attr.value);
-          valid = false;
-        }
-      }
-
-      // TODO: Fill in the manifest details when they are finalised.
-      for (auto node : child->GetChildElements()) {
-        if (node->name == "manifest") {
-          if (entry.manifest) {
-            diag->Warn(DiagMessage() << "Found multiple manifest tags. Ignoring duplicates.");
-            continue;
-          }
-          entry.manifest = {AndroidManifest()};
-        }
-      }
-
-      config->android_sdk_groups[label] = entry;
-      if (found) {
-        valid = false;
-      }
-      found = true;
     }
   }
 
+  if (entry.min_sdk_version == -1) {
+    diag->Error(DiagMessage() << "android-sdk is missing minSdkVersion attribute");
+    valid = false;
+  }
+
+  // TODO: Fill in the manifest details when they are finalised.
+  for (auto node : root_element->GetChildElements()) {
+    if (node->name == "manifest") {
+      if (entry.manifest) {
+        diag->Warn(DiagMessage() << "Found multiple manifest tags. Ignoring duplicates.");
+        continue;
+      }
+      entry.manifest = {AndroidManifest()};
+    }
+  }
+
+  config->android_sdks[entry.label] = entry;
   return valid;
 };
 
@@ -691,7 +728,7 @@
     return false;
   }
 
-  auto& group = config->gl_texture_groups[label];
+  auto& group = GetOrCreateGroup(label, &config->gl_texture_groups);
   bool valid = true;
 
   GlTexture result;
@@ -734,7 +771,7 @@
     return false;
   }
 
-  auto& group = config->device_feature_groups[label];
+  auto& group = GetOrCreateGroup(label, &config->device_feature_groups);
   bool valid = true;
 
   for (auto* child : root_element->GetChildElements()) {
diff --git a/tools/aapt2/configuration/ConfigurationParser.h b/tools/aapt2/configuration/ConfigurationParser.h
index ca58910..7f1d445 100644
--- a/tools/aapt2/configuration/ConfigurationParser.h
+++ b/tools/aapt2/configuration/ConfigurationParser.h
@@ -71,7 +71,8 @@
 };
 
 struct AndroidSdk {
-  Maybe<int> min_sdk_version;
+  std::string label;
+  int min_sdk_version;  // min_sdk_version is mandatory if splitting by SDK.
   Maybe<int> target_sdk_version;
   Maybe<int> max_sdk_version;
   Maybe<AndroidManifest> manifest;
@@ -113,15 +114,19 @@
   Maybe<AndroidSdk> android_sdk;
   std::vector<DeviceFeature> features;
   std::vector<GlTexture> textures;
+
+  inline int GetMinSdk(int default_value = -1) const {
+    if (!android_sdk) {
+      return default_value;
+    }
+    return android_sdk.value().min_sdk_version;
+  }
 };
 
 }  // namespace configuration
 
 // Forward declaration of classes used in the API.
 struct IDiagnostics;
-namespace xml {
-class Element;
-}
 
 /**
  * XML configuration file parser for the split and optimize commands.
@@ -133,8 +138,8 @@
   static Maybe<ConfigurationParser> ForPath(const std::string& path);
 
   /** Returns a ConfigurationParser for the configuration in the provided file contents. */
-  static ConfigurationParser ForContents(const std::string& contents) {
-    ConfigurationParser parser{contents};
+  static ConfigurationParser ForContents(const std::string& contents, const std::string& path) {
+    ConfigurationParser parser{contents, path};
     return parser;
   }
 
@@ -156,7 +161,7 @@
    * diagnostics context. The default diagnostics context can be overridden with a call to
    * WithDiagnostics(IDiagnostics *).
    */
-  explicit ConfigurationParser(std::string contents);
+  ConfigurationParser(std::string contents, const std::string& config_path);
 
   /** Returns the current diagnostics context to any subclasses. */
   IDiagnostics* diagnostics() {
@@ -166,6 +171,8 @@
  private:
   /** The contents of the configuration file to parse. */
   const std::string contents_;
+  /** Path to the input configuration. */
+  const std::string config_path_;
   /** The diagnostics context to send messages to. */
   IDiagnostics* diag_;
 };
diff --git a/tools/aapt2/configuration/ConfigurationParser.internal.h b/tools/aapt2/configuration/ConfigurationParser.internal.h
index 7657ebd..a583057 100644
--- a/tools/aapt2/configuration/ConfigurationParser.internal.h
+++ b/tools/aapt2/configuration/ConfigurationParser.internal.h
@@ -17,35 +17,105 @@
 #ifndef AAPT2_CONFIGURATIONPARSER_INTERNAL_H
 #define AAPT2_CONFIGURATIONPARSER_INTERNAL_H
 
+#include "configuration/ConfigurationParser.h"
+
+#include <algorithm>
+#include <limits>
+
 namespace aapt {
+
+// Forward declaration of classes used in the API.
+namespace xml {
+class Element;
+}
+
 namespace configuration {
 
+template <typename T>
+struct OrderedEntry {
+  size_t order;
+  std::vector<T> entry;
+};
+
 /** A mapping of group labels to group of configuration items. */
 template <class T>
-using Group = std::unordered_map<std::string, std::vector<T>>;
+using Group = std::unordered_map<std::string, OrderedEntry<T>>;
 
 /** A mapping of group label to a single configuration item. */
 template <class T>
 using Entry = std::unordered_map<std::string, T>;
 
+/** Retrieves an entry from the provided Group, creating a new instance if one does not exist. */
+template <typename T>
+std::vector<T>& GetOrCreateGroup(std::string label, Group<T>* group) {
+  OrderedEntry<T>& entry = (*group)[label];
+  // If this is a new entry, set the order.
+  if (entry.order == 0) {
+    entry.order = group->size();
+  }
+  return entry.entry;
+}
+
+/**
+ * A ComparisonChain is a grouping of comparisons to perform when sorting groups that have a well
+ * defined order of precedence. Comparisons are only made if none of the previous comparisons had a
+ * definite result. A comparison has a result if at least one of the items has an entry for that
+ * value and that they are not equal.
+ */
+class ComparisonChain {
+ public:
+  /**
+   * Adds a new comparison of items in a group to the chain. The new comparison is only used if we
+   * have not been able to determine the sort order with the previous comparisons.
+   */
+  template <typename T>
+  ComparisonChain& Add(const Group<T>& groups, const Maybe<std::string>& lhs,
+                       const Maybe<std::string>& rhs) {
+    return Add(GetGroupOrder(groups, lhs), GetGroupOrder(groups, rhs));
+  }
+
+  /**
+   * Adds a new comparison to the chain. The new comparison is only used if we have not been able to
+   * determine the sort order with the previous comparisons.
+   */
+  ComparisonChain& Add(int lhs, int rhs) {
+    if (!has_result_) {
+      has_result_ = (lhs != rhs);
+      result_ = (lhs < rhs);
+    }
+    return *this;
+  }
+
+  /** Returns true if the left hand side should come before the right hand side. */
+  bool Compare() {
+    return result_;
+  }
+
+ private:
+  template <typename T>
+  inline size_t GetGroupOrder(const Group<T>& groups, const Maybe<std::string>& label) {
+    if (!label) {
+      return std::numeric_limits<size_t>::max();
+    }
+    return groups.at(label.value()).order;
+  }
+
+  bool has_result_ = false;
+  bool result_ = false;
+};
+
 /** Output artifact configuration options. */
 struct ConfiguredArtifact {
   /** Name to use for output of processing foo.apk -> foo.<name>.apk. */
   Maybe<std::string> name;
-  /**
-   * Value to add to the base Android manifest versionCode. If it is not present in the
-   * configuration file, it is set to the previous artifact + 1. If the first artifact does not have
-   * a value, artifacts are a 1 based index.
-   */
-  int version;
   /** If present, uses the ABI group with this name. */
   Maybe<std::string> abi_group;
   /** If present, uses the screen density group with this name. */
   Maybe<std::string> screen_density_group;
   /** If present, uses the locale group with this name. */
   Maybe<std::string> locale_group;
-  /** If present, uses the Android SDK group with this name. */
-  Maybe<std::string> android_sdk_group;
+  /** If present, uses the Android SDK with this name. */
+  Maybe<std::string> android_sdk;
   /** If present, uses the device feature group with this name. */
   Maybe<std::string> device_feature_group;
   /** If present, uses the OpenGL texture group with this name. */
@@ -57,31 +127,71 @@
 
   /** Convert an artifact name template into a name string based on configuration contents. */
   Maybe<std::string> Name(const android::StringPiece& apk_name, IDiagnostics* diag) const;
-
-  bool operator<(const ConfiguredArtifact& rhs) const {
-    // TODO(safarmer): Order by play store multi-APK requirements.
-    return version < rhs.version;
-  }
-
-  bool operator==(const ConfiguredArtifact& rhs) const {
-    return version == rhs.version;
-  }
 };
 
 /** AAPT2 XML configuration file binary representation. */
 struct PostProcessingConfiguration {
-  // TODO: Support named artifacts?
   std::vector<ConfiguredArtifact> artifacts;
   Maybe<std::string> artifact_format;
 
   Group<Abi> abi_groups;
   Group<ConfigDescription> screen_density_groups;
   Group<ConfigDescription> locale_groups;
-  Entry<AndroidSdk> android_sdk_groups;
   Group<DeviceFeature> device_feature_groups;
   Group<GlTexture> gl_texture_groups;
+  Entry<AndroidSdk> android_sdks;
+
+  /**
+   * Sorts the configured artifacts based on the ordering of the groups in the configuration file.
+   * The only exception to this rule is Android SDK versions. Larger SDK versions will have a larger
+   * versionCode to ensure users get the correct APK when they upgrade their OS.
+   */
+  void SortArtifacts() {
+    std::sort(artifacts.begin(), artifacts.end(), *this);
+  }
+
+  /** Comparator that ensures artifacts are in the preferred order for versionCode rewriting. */
+  bool operator()(const ConfiguredArtifact& lhs, const ConfiguredArtifact& rhs) {
+    // Split dimensions are added in the order of precedence. Items higher in the list result in
+    // higher version codes.
+    return ComparisonChain()
+        // All splits with a minSdkVersion specified must be last to ensure the application will be
+        // updated if a user upgrades the version of Android on their device.
+        .Add(GetMinSdk(lhs), GetMinSdk(rhs))
+        // ABI version is important, especially on x86 phones where they may begin to run in ARM
+        // emulation mode on newer Android versions. This allows us to ensure that the x86 version
+        // is installed on these devices rather than ARM.
+        .Add(abi_groups, lhs.abi_group, rhs.abi_group)
+        // The rest are in arbitrary order based on estimated usage.
+        .Add(screen_density_groups, lhs.screen_density_group, rhs.screen_density_group)
+        .Add(locale_groups, lhs.locale_group, rhs.locale_group)
+        .Add(gl_texture_groups, lhs.gl_texture_group, rhs.gl_texture_group)
+        .Add(device_feature_groups, lhs.device_feature_group, rhs.device_feature_group)
+        .Compare();
+  }
+
+ private:
+  /**
+   * Returns the min_sdk_version from the provided artifact or 0 if none is present. This allows
+   * artifacts that have an Android SDK version to have a higher versionCode than those that do not.
+   */
+  inline int GetMinSdk(const ConfiguredArtifact& artifact) {
+    if (!artifact.android_sdk) {
+      return 0;
+    }
+    const auto& entry = android_sdks.find(artifact.android_sdk.value());
+    if (entry == android_sdks.end()) {
+      return 0;
+    }
+    return entry->second.min_sdk_version;
+  }
 };
 
+/** Parses the provided XML document returning the post processing configuration. */
+Maybe<PostProcessingConfiguration> ExtractConfiguration(const std::string& contents,
+                                                        const std::string& config_path,
+                                                        IDiagnostics* diag);
+
 namespace handler {
 
 /** Handler for <artifact> tags. */
@@ -104,9 +214,9 @@
 bool LocaleGroupTagHandler(configuration::PostProcessingConfiguration* config,
                            xml::Element* element, IDiagnostics* diag);
 
-/** Handler for <android-sdk-group> tags. */
-bool AndroidSdkGroupTagHandler(configuration::PostProcessingConfiguration* config,
-                               xml::Element* element, IDiagnostics* diag);
+/** Handler for <android-sdk> tags. */
+bool AndroidSdkTagHandler(configuration::PostProcessingConfiguration* config, xml::Element* element,
+                          IDiagnostics* diag);
 
 /** Handler for <gl-texture-group> tags. */
 bool GlTextureGroupTagHandler(configuration::PostProcessingConfiguration* config,
diff --git a/tools/aapt2/configuration/ConfigurationParser_test.cpp b/tools/aapt2/configuration/ConfigurationParser_test.cpp
index 3f356d7..0329846 100644
--- a/tools/aapt2/configuration/ConfigurationParser_test.cpp
+++ b/tools/aapt2/configuration/ConfigurationParser_test.cpp
@@ -30,11 +30,33 @@
 
 namespace configuration {
 void PrintTo(const AndroidSdk& sdk, std::ostream* os) {
-  *os << "SDK: min=" << sdk.min_sdk_version.value_or_default(-1)
+  *os << "SDK: min=" << sdk.min_sdk_version
       << ", target=" << sdk.target_sdk_version.value_or_default(-1)
       << ", max=" << sdk.max_sdk_version.value_or_default(-1);
 }
 
+bool operator==(const ConfiguredArtifact& lhs, const ConfiguredArtifact& rhs) {
+  return lhs.name == rhs.name && lhs.abi_group == rhs.abi_group &&
+         lhs.screen_density_group == rhs.screen_density_group &&
+         lhs.locale_group == rhs.locale_group && lhs.android_sdk == rhs.android_sdk &&
+         lhs.device_feature_group == rhs.device_feature_group &&
+         lhs.gl_texture_group == rhs.gl_texture_group;
+}
+
+std::ostream& operator<<(std::ostream& out, const Maybe<std::string>& value) {
+  PrintTo(value, &out);
+  return out;
+}
+
+void PrintTo(const ConfiguredArtifact& artifact, std::ostream* os) {
+  *os << "\n{"
+      << "\n  name: " << artifact.name << "\n  sdk: " << artifact.android_sdk
+      << "\n  abi: " << artifact.abi_group << "\n  density: " << artifact.screen_density_group
+      << "\n  locale: " << artifact.locale_group
+      << "\n  features: " << artifact.device_feature_group
+      << "\n  textures: " << artifact.gl_texture_group << "\n}\n";
+}
+
 namespace handler {
 
 namespace {
@@ -44,6 +66,7 @@
 using ::aapt::configuration::AndroidSdk;
 using ::aapt::configuration::ConfiguredArtifact;
 using ::aapt::configuration::DeviceFeature;
+using ::aapt::configuration::ExtractConfiguration;
 using ::aapt::configuration::GlTexture;
 using ::aapt::configuration::Locale;
 using ::aapt::configuration::PostProcessingConfiguration;
@@ -52,11 +75,13 @@
 using ::android::ResTable_config;
 using ::android::base::StringPrintf;
 using ::testing::ElementsAre;
+using ::testing::Eq;
 using ::testing::SizeIs;
+using ::testing::StrEq;
 
 constexpr const char* kValidConfig = R"(<?xml version="1.0" encoding="utf-8" ?>
 <post-process xmlns="http://schemas.android.com/tools/aapt">
-  <groups>
+  <abi-groups>
     <abi-group label="arm">
       <abi>armeabi-v7a</abi>
       <abi>arm64-v8a</abi>
@@ -65,6 +90,8 @@
       <abi>x86</abi>
       <abi>mips</abi>
     </abi-group>
+  </abi-groups>
+  <screen-density-groups>
     <screen-density-group label="large">
       <screen-density>xhdpi</screen-density>
       <screen-density>xxhdpi</screen-density>
@@ -78,6 +105,8 @@
       <screen-density>xxhdpi</screen-density>
       <screen-density>xxxhdpi</screen-density>
     </screen-density-group>
+  </screen-density-groups>
+  <locale-groups>
     <locale-group label="europe">
       <locale>en</locale>
       <locale>es</locale>
@@ -89,25 +118,30 @@
       <locale>es-rMX</locale>
       <locale>fr-rCA</locale>
     </locale-group>
-    <android-sdk-group label="v19">
-      <android-sdk
-          minSdkVersion="19"
-          targetSdkVersion="24"
-          maxSdkVersion="25">
-        <manifest>
-          <!--- manifest additions here XSLT? TODO -->
-        </manifest>
-      </android-sdk>
-    </android-sdk-group>
+  </locale-groups>
+  <android-sdks>
+    <android-sdk
+    	  label="v19"
+        minSdkVersion="19"
+        targetSdkVersion="24"
+        maxSdkVersion="25">
+      <manifest>
+        <!--- manifest additions here XSLT? TODO -->
+      </manifest>
+    </android-sdk>
+  </android-sdks>
+  <gl-texture-groups>
     <gl-texture-group label="dxt1">
       <gl-texture name="GL_EXT_texture_compression_dxt1">
         <texture-path>assets/dxt1/*</texture-path>
       </gl-texture>
     </gl-texture-group>
+  </gl-texture-groups>
+  <device-feature-groups>
     <device-feature-group label="low-latency">
       <supports-feature>android.hardware.audio.low_latency</supports-feature>
     </device-feature-group>
-  </groups>
+  </device-feature-groups>
   <artifacts>
     <artifact-format>
       ${base}.${abi}.${screen-density}.${locale}.${sdk}.${gl}.${feature}.release
@@ -117,7 +151,7 @@
         abi-group="arm"
         screen-density-group="large"
         locale-group="europe"
-        android-sdk-group="v19"
+        android-sdk="v19"
         gl-texture-group="dxt1"
         device-feature-group="low-latency"/>
     <artifact
@@ -125,7 +159,7 @@
         abi-group="other"
         screen-density-group="alldpi"
         locale-group="north-america"
-        android-sdk-group="v19"
+        android-sdk="v19"
         gl-texture-group="dxt1"
         device-feature-group="low-latency"/>
   </artifacts>
@@ -134,7 +168,8 @@
 
 class ConfigurationParserTest : public ConfigurationParser, public ::testing::Test {
  public:
-  ConfigurationParserTest() : ConfigurationParser("") {}
+  ConfigurationParserTest() : ConfigurationParser("", "config.xml") {
+  }
 
  protected:
   StdErrDiagnostics diag_;
@@ -145,8 +180,31 @@
   EXPECT_FALSE(result);
 }
 
+TEST_F(ConfigurationParserTest, ExtractConfiguration) {
+  Maybe<PostProcessingConfiguration> maybe_config =
+      ExtractConfiguration(kValidConfig, "dummy.xml", &diag_);
+
+  PostProcessingConfiguration config = maybe_config.value();
+
+  auto& arm = config.abi_groups["arm"];
+  auto& other = config.abi_groups["other"];
+  EXPECT_EQ(arm.order, 1ul);
+  EXPECT_EQ(other.order, 2ul);
+
+  auto& large = config.screen_density_groups["large"];
+  auto& alldpi = config.screen_density_groups["alldpi"];
+  EXPECT_EQ(large.order, 1ul);
+  EXPECT_EQ(alldpi.order, 2ul);
+
+  auto& north_america = config.locale_groups["north-america"];
+  auto& europe = config.locale_groups["europe"];
+  // Checked in reverse to make sure access order does not matter.
+  EXPECT_EQ(north_america.order, 2ul);
+  EXPECT_EQ(europe.order, 1ul);
+}
+
 TEST_F(ConfigurationParserTest, ValidateFile) {
-  auto parser = ConfigurationParser::ForContents(kValidConfig).WithDiagnostics(&diag_);
+  auto parser = ConfigurationParser::ForContents(kValidConfig, "conf.xml").WithDiagnostics(&diag_);
   auto result = parser.Parse("test.apk");
   ASSERT_TRUE(result);
   const std::vector<OutputArtifact>& value = result.value();
@@ -154,6 +212,7 @@
 
   const OutputArtifact& a1 = value[0];
   EXPECT_EQ(a1.name, "art1.apk");
+  EXPECT_EQ(a1.version, 1);
   EXPECT_THAT(a1.abis, ElementsAre(Abi::kArmV7a, Abi::kArm64V8a));
   EXPECT_THAT(a1.screen_densities,
               ElementsAre(test::ParseConfigOrDie("xhdpi").CopyWithoutSdkVersion(),
@@ -161,12 +220,15 @@
                           test::ParseConfigOrDie("xxxhdpi").CopyWithoutSdkVersion()));
   EXPECT_THAT(a1.locales, ElementsAre(test::ParseConfigOrDie("en"), test::ParseConfigOrDie("es"),
                                       test::ParseConfigOrDie("fr"), test::ParseConfigOrDie("de")));
-  EXPECT_EQ(a1.android_sdk.value().min_sdk_version.value(), 19l);
+  ASSERT_TRUE(a1.android_sdk);
+  ASSERT_TRUE(a1.android_sdk.value().min_sdk_version);
+  EXPECT_EQ(a1.android_sdk.value().min_sdk_version, 19l);
   EXPECT_THAT(a1.textures, SizeIs(1ul));
   EXPECT_THAT(a1.features, SizeIs(1ul));
 
   const OutputArtifact& a2 = value[1];
   EXPECT_EQ(a2.name, "art2.apk");
+  EXPECT_EQ(a2.version, 2);
   EXPECT_THAT(a2.abis, ElementsAre(Abi::kX86, Abi::kMips));
   EXPECT_THAT(a2.screen_densities,
               ElementsAre(test::ParseConfigOrDie("ldpi").CopyWithoutSdkVersion(),
@@ -178,124 +240,138 @@
   EXPECT_THAT(a2.locales,
               ElementsAre(test::ParseConfigOrDie("en"), test::ParseConfigOrDie("es-rMX"),
                           test::ParseConfigOrDie("fr-rCA")));
-  EXPECT_EQ(a2.android_sdk.value().min_sdk_version.value(), 19l);
+  ASSERT_TRUE(a2.android_sdk);
+  ASSERT_TRUE(a2.android_sdk.value().min_sdk_version);
+  EXPECT_EQ(a2.android_sdk.value().min_sdk_version, 19l);
   EXPECT_THAT(a2.textures, SizeIs(1ul));
   EXPECT_THAT(a2.features, SizeIs(1ul));
 }
 
+TEST_F(ConfigurationParserTest, ConfiguredArtifactOrdering) {
+  // Create a base builder with the configuration groups but no artifacts to allow it to be copied.
+  test::PostProcessingConfigurationBuilder base_builder = test::PostProcessingConfigurationBuilder()
+                                                              .AddAbiGroup("arm")
+                                                              .AddAbiGroup("arm64")
+                                                              .AddAndroidSdk("v23", 23)
+                                                              .AddAndroidSdk("v19", 19);
+
+  {
+    // Test version ordering.
+    ConfiguredArtifact v23;
+    v23.android_sdk = {"v23"};
+    ConfiguredArtifact v19;
+    v19.android_sdk = {"v19"};
+
+    test::PostProcessingConfigurationBuilder builder = base_builder;
+    PostProcessingConfiguration config = builder.AddArtifact(v23).AddArtifact(v19).Build();
+
+    config.SortArtifacts();
+    ASSERT_THAT(config.artifacts, SizeIs(2));
+    EXPECT_THAT(config.artifacts[0], Eq(v19));
+    EXPECT_THAT(config.artifacts[1], Eq(v23));
+  }
+
+  {
+    // Test ABI ordering.
+    ConfiguredArtifact arm;
+    arm.android_sdk = {"v19"};
+    arm.abi_group = {"arm"};
+    ConfiguredArtifact arm64;
+    arm64.android_sdk = {"v19"};
+    arm64.abi_group = {"arm64"};
+
+    test::PostProcessingConfigurationBuilder builder = base_builder;
+    PostProcessingConfiguration config = builder.AddArtifact(arm64).AddArtifact(arm).Build();
+
+    config.SortArtifacts();
+    ASSERT_THAT(config.artifacts, SizeIs(2));
+    EXPECT_THAT(config.artifacts[0], Eq(arm));
+    EXPECT_THAT(config.artifacts[1], Eq(arm64));
+  }
+
+  {
+    // Test Android SDK has precedence over ABI.
+    ConfiguredArtifact arm;
+    arm.android_sdk = {"v23"};
+    arm.abi_group = {"arm"};
+    ConfiguredArtifact arm64;
+    arm64.android_sdk = {"v19"};
+    arm64.abi_group = {"arm64"};
+
+    test::PostProcessingConfigurationBuilder builder = base_builder;
+    PostProcessingConfiguration config = builder.AddArtifact(arm64).AddArtifact(arm).Build();
+
+    config.SortArtifacts();
+    ASSERT_THAT(config.artifacts, SizeIs(2));
+    EXPECT_THAT(config.artifacts[0], Eq(arm64));
+    EXPECT_THAT(config.artifacts[1], Eq(arm));
+  }
+
+  {
+    // Test version is better than ABI.
+    ConfiguredArtifact arm;
+    arm.abi_group = {"arm"};
+    ConfiguredArtifact v19;
+    v19.android_sdk = {"v19"};
+
+    test::PostProcessingConfigurationBuilder builder = base_builder;
+    PostProcessingConfiguration config = builder.AddArtifact(v19).AddArtifact(arm).Build();
+
+    config.SortArtifacts();
+    ASSERT_THAT(config.artifacts, SizeIs(2));
+    EXPECT_THAT(config.artifacts[0], Eq(arm));
+    EXPECT_THAT(config.artifacts[1], Eq(v19));
+  }
+
+  {
+    // Test version is sorted higher than no version.
+    ConfiguredArtifact arm;
+    arm.abi_group = {"arm"};
+    ConfiguredArtifact v19;
+    v19.abi_group = {"arm"};
+    v19.android_sdk = {"v19"};
+
+    test::PostProcessingConfigurationBuilder builder = base_builder;
+    PostProcessingConfiguration config = builder.AddArtifact(v19).AddArtifact(arm).Build();
+
+    config.SortArtifacts();
+    ASSERT_THAT(config.artifacts, SizeIs(2));
+    EXPECT_THAT(config.artifacts[0], Eq(arm));
+    EXPECT_THAT(config.artifacts[1], Eq(v19));
+  }
+}
+
 TEST_F(ConfigurationParserTest, InvalidNamespace) {
   constexpr const char* invalid_ns = R"(<?xml version="1.0" encoding="utf-8" ?>
-  <post-process xmlns="http://schemas.android.com/tools/another-unknown-tool" />)";
+    <post-process xmlns="http://schemas.android.com/tools/another-unknown-tool" />)";
 
-  auto result = ConfigurationParser::ForContents(invalid_ns).Parse("test.apk");
+  auto result = ConfigurationParser::ForContents(invalid_ns, "config.xml").Parse("test.apk");
   ASSERT_FALSE(result);
 }
 
 TEST_F(ConfigurationParserTest, ArtifactAction) {
   PostProcessingConfiguration config;
-  {
-    const auto doc = test::BuildXmlDom(R"xml(
+  const auto doc = test::BuildXmlDom(R"xml(
       <artifact
           abi-group="arm"
           screen-density-group="large"
           locale-group="europe"
-          android-sdk-group="v19"
+          android-sdk="v19"
           gl-texture-group="dxt1"
           device-feature-group="low-latency"/>)xml");
 
-    ASSERT_TRUE(ArtifactTagHandler(&config, NodeCast<Element>(doc->root.get()), &diag_));
+  ASSERT_TRUE(ArtifactTagHandler(&config, NodeCast<Element>(doc->root.get()), &diag_));
 
-    EXPECT_THAT(config.artifacts, SizeIs(1ul));
+  EXPECT_THAT(config.artifacts, SizeIs(1ul));
 
-    auto& artifact = config.artifacts.back();
-    EXPECT_FALSE(artifact.name);  // TODO: make this fail.
-    EXPECT_EQ(1, artifact.version);
-    EXPECT_EQ("arm", artifact.abi_group.value());
-    EXPECT_EQ("large", artifact.screen_density_group.value());
-    EXPECT_EQ("europe", artifact.locale_group.value());
-    EXPECT_EQ("v19", artifact.android_sdk_group.value());
-    EXPECT_EQ("dxt1", artifact.gl_texture_group.value());
-    EXPECT_EQ("low-latency", artifact.device_feature_group.value());
-  }
-
-  {
-    // Perform a second action to ensure we get 2 artifacts.
-    const auto doc = test::BuildXmlDom(R"xml(
-      <artifact
-          abi-group="other"
-          screen-density-group="large"
-          locale-group="europe"
-          android-sdk-group="v19"
-          gl-texture-group="dxt1"
-          device-feature-group="low-latency"/>)xml");
-
-    ASSERT_TRUE(ArtifactTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_));
-    EXPECT_THAT(config.artifacts, SizeIs(2ul));
-    EXPECT_EQ(2, config.artifacts.back().version);
-  }
-
-  {
-    // Perform a third action with a set version code.
-    const auto doc = test::BuildXmlDom(R"xml(
-    <artifact
-        version="5"
-        abi-group="other"
-        screen-density-group="large"
-        locale-group="europe"
-        android-sdk-group="v19"
-        gl-texture-group="dxt1"
-        device-feature-group="low-latency"/>)xml");
-
-    ASSERT_TRUE(ArtifactTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_));
-    EXPECT_THAT(config.artifacts, SizeIs(3ul));
-    EXPECT_EQ(5, config.artifacts.back().version);
-  }
-
-  {
-    // Perform a fourth action to ensure the version code still increments.
-    const auto doc = test::BuildXmlDom(R"xml(
-    <artifact
-        abi-group="other"
-        screen-density-group="large"
-        locale-group="europe"
-        android-sdk-group="v19"
-        gl-texture-group="dxt1"
-        device-feature-group="low-latency"/>)xml");
-
-    ASSERT_TRUE(ArtifactTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_));
-    EXPECT_THAT(config.artifacts, SizeIs(4ul));
-    EXPECT_EQ(6, config.artifacts.back().version);
-  }
-}
-
-TEST_F(ConfigurationParserTest, DuplicateArtifactVersion) {
-  static constexpr const char* configuration = R"xml(<?xml version="1.0" encoding="utf-8" ?>
-      <pst-process xmlns="http://schemas.android.com/tools/aapt">>
-        <artifacts>
-          <artifact-format>
-            ${base}.${abi}.${screen-density}.${locale}.${sdk}.${gl}.${feature}.release
-          </artifact-format>
-          <artifact
-              name="art1"
-              abi-group="arm"
-              screen-density-group="large"
-              locale-group="europe"
-              android-sdk-group="v19"
-              gl-texture-group="dxt1"
-              device-feature-group="low-latency"/>
-          <artifact
-              name="art2"
-              version = "1"
-              abi-group="other"
-              screen-density-group="alldpi"
-              locale-group="north-america"
-              android-sdk-group="v19"
-              gl-texture-group="dxt1"
-              device-feature-group="low-latency"/>
-        </artifacts>
-      </post-process>)xml";
-  auto result = ConfigurationParser::ForContents(configuration).Parse("test.apk");
-  ASSERT_FALSE(result);
+  auto& artifact = config.artifacts.back();
+  EXPECT_FALSE(artifact.name);  // TODO: make this fail.
+  EXPECT_EQ("arm", artifact.abi_group.value());
+  EXPECT_EQ("large", artifact.screen_density_group.value());
+  EXPECT_EQ("europe", artifact.locale_group.value());
+  EXPECT_EQ("v19", artifact.android_sdk.value());
+  EXPECT_EQ("dxt1", artifact.gl_texture_group.value());
+  EXPECT_EQ("low-latency", artifact.device_feature_group.value());
 }
 
 TEST_F(ConfigurationParserTest, ArtifactFormatAction) {
@@ -334,10 +410,36 @@
   EXPECT_THAT(config.abi_groups, SizeIs(1ul));
   ASSERT_EQ(1u, config.abi_groups.count("arm"));
 
-  auto& out = config.abi_groups["arm"];
+  auto& out = config.abi_groups["arm"].entry;
   ASSERT_THAT(out, ElementsAre(Abi::kArmV7a, Abi::kArm64V8a));
 }
 
+TEST_F(ConfigurationParserTest, AbiGroupAction_EmptyGroup) {
+  static constexpr const char* xml = R"xml(<abi-group label="arm64-v8a"/>)xml";
+
+  auto doc = test::BuildXmlDom(xml);
+
+  PostProcessingConfiguration config;
+  bool ok = AbiGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+  ASSERT_TRUE(ok);
+
+  EXPECT_THAT(config.abi_groups, SizeIs(1ul));
+  ASSERT_EQ(1u, config.abi_groups.count("arm64-v8a"));
+
+  auto& out = config.abi_groups["arm64-v8a"].entry;
+  ASSERT_THAT(out, ElementsAre(Abi::kArm64V8a));
+}
+
+TEST_F(ConfigurationParserTest, AbiGroupAction_InvalidEmptyGroup) {
+  static constexpr const char* xml = R"xml(<abi-group label="arm"/>)xml";
+
+  auto doc = test::BuildXmlDom(xml);
+
+  PostProcessingConfiguration config;
+  bool ok = AbiGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+  ASSERT_FALSE(ok);
+}
+
 TEST_F(ConfigurationParserTest, ScreenDensityGroupAction) {
   static constexpr const char* xml = R"xml(
     <screen-density-group label="large">
@@ -364,10 +466,39 @@
   ConfigDescription xxxhdpi;
   xxxhdpi.density = ResTable_config::DENSITY_XXXHIGH;
 
-  auto& out = config.screen_density_groups["large"];
+  auto& out = config.screen_density_groups["large"].entry;
   ASSERT_THAT(out, ElementsAre(xhdpi, xxhdpi, xxxhdpi));
 }
 
+TEST_F(ConfigurationParserTest, ScreenDensityGroupAction_EmtpyGroup) {
+  static constexpr const char* xml = R"xml(<screen-density-group label="xhdpi"/>)xml";
+
+  auto doc = test::BuildXmlDom(xml);
+
+  PostProcessingConfiguration config;
+  bool ok = ScreenDensityGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+  ASSERT_TRUE(ok);
+
+  EXPECT_THAT(config.screen_density_groups, SizeIs(1ul));
+  ASSERT_EQ(1u, config.screen_density_groups.count("xhdpi"));
+
+  ConfigDescription xhdpi;
+  xhdpi.density = ResTable_config::DENSITY_XHIGH;
+
+  auto& out = config.screen_density_groups["xhdpi"].entry;
+  ASSERT_THAT(out, ElementsAre(xhdpi));
+}
+
+TEST_F(ConfigurationParserTest, ScreenDensityGroupAction_InvalidEmtpyGroup) {
+  static constexpr const char* xml = R"xml(<screen-density-group label="really-big-screen"/>)xml";
+
+  auto doc = test::BuildXmlDom(xml);
+
+  PostProcessingConfiguration config;
+  bool ok = ScreenDensityGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+  ASSERT_FALSE(ok);
+}
+
 TEST_F(ConfigurationParserTest, LocaleGroupAction) {
   static constexpr const char* xml = R"xml(
     <locale-group label="europe">
@@ -386,7 +517,7 @@
   ASSERT_EQ(1ul, config.locale_groups.size());
   ASSERT_EQ(1u, config.locale_groups.count("europe"));
 
-  const auto& out = config.locale_groups["europe"];
+  const auto& out = config.locale_groups["europe"].entry;
 
   ConfigDescription en = test::ParseConfigOrDie("en");
   ConfigDescription es = test::ParseConfigOrDie("es");
@@ -396,29 +527,56 @@
   ASSERT_THAT(out, ElementsAre(en, es, fr, de));
 }
 
+TEST_F(ConfigurationParserTest, LocaleGroupAction_EmtpyGroup) {
+  static constexpr const char* xml = R"xml(<locale-group label="en"/>)xml";
+
+  auto doc = test::BuildXmlDom(xml);
+
+  PostProcessingConfiguration config;
+  bool ok = LocaleGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+  ASSERT_TRUE(ok);
+
+  ASSERT_EQ(1ul, config.locale_groups.size());
+  ASSERT_EQ(1u, config.locale_groups.count("en"));
+
+  const auto& out = config.locale_groups["en"].entry;
+
+  ConfigDescription en = test::ParseConfigOrDie("en");
+
+  ASSERT_THAT(out, ElementsAre(en));
+}
+
+TEST_F(ConfigurationParserTest, LocaleGroupAction_InvalidEmtpyGroup) {
+  static constexpr const char* xml = R"xml(<locale-group label="arm64"/>)xml";
+
+  auto doc = test::BuildXmlDom(xml);
+
+  PostProcessingConfiguration config;
+  bool ok = LocaleGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+  ASSERT_FALSE(ok);
+}
+
 TEST_F(ConfigurationParserTest, AndroidSdkGroupAction) {
   static constexpr const char* xml = R"xml(
-    <android-sdk-group label="v19">
-      <android-sdk
+      <android-sdk label="v19"
           minSdkVersion="19"
           targetSdkVersion="24"
           maxSdkVersion="25">
         <manifest>
           <!--- manifest additions here XSLT? TODO -->
         </manifest>
-      </android-sdk>
-    </android-sdk-group>)xml";
+      </android-sdk>)xml";
 
   auto doc = test::BuildXmlDom(xml);
 
   PostProcessingConfiguration config;
-  bool ok = AndroidSdkGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+  bool ok = AndroidSdkTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
   ASSERT_TRUE(ok);
 
-  ASSERT_EQ(1ul, config.android_sdk_groups.size());
-  ASSERT_EQ(1u, config.android_sdk_groups.count("v19"));
+  ASSERT_EQ(1ul, config.android_sdks.size());
+  ASSERT_EQ(1u, config.android_sdks.count("v19"));
 
-  auto& out = config.android_sdk_groups["v19"];
+  auto& out = config.android_sdks["v19"];
 
   AndroidSdk sdk;
   sdk.min_sdk_version = 19;
@@ -431,98 +589,86 @@
 
 TEST_F(ConfigurationParserTest, AndroidSdkGroupAction_SingleVersion) {
   {
-    static constexpr const char* xml = R"xml(
-      <android-sdk-group label="v19">
-        <android-sdk minSdkVersion="19"></android-sdk>
-      </android-sdk-group>)xml";
-
+    const char* xml = "<android-sdk label='v19' minSdkVersion='19'></android-sdk>";
     auto doc = test::BuildXmlDom(xml);
 
     PostProcessingConfiguration config;
-    bool ok = AndroidSdkGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+    bool ok = AndroidSdkTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
     ASSERT_TRUE(ok);
 
-    ASSERT_EQ(1ul, config.android_sdk_groups.size());
-    ASSERT_EQ(1u, config.android_sdk_groups.count("v19"));
+    ASSERT_EQ(1ul, config.android_sdks.size());
+    ASSERT_EQ(1u, config.android_sdks.count("v19"));
 
-    auto& out = config.android_sdk_groups["v19"];
-    EXPECT_EQ(19, out.min_sdk_version.value());
+    auto& out = config.android_sdks["v19"];
+    EXPECT_EQ(19, out.min_sdk_version);
     EXPECT_FALSE(out.max_sdk_version);
     EXPECT_FALSE(out.target_sdk_version);
   }
 
   {
-    static constexpr const char* xml = R"xml(
-      <android-sdk-group label="v19">
-        <android-sdk maxSdkVersion="19"></android-sdk>
-      </android-sdk-group>)xml";
-
+    const char* xml =
+        "<android-sdk label='v19' minSdkVersion='19' maxSdkVersion='19'></android-sdk>";
     auto doc = test::BuildXmlDom(xml);
 
     PostProcessingConfiguration config;
-    bool ok = AndroidSdkGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+    bool ok = AndroidSdkTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
     ASSERT_TRUE(ok);
 
-    ASSERT_EQ(1ul, config.android_sdk_groups.size());
-    ASSERT_EQ(1u, config.android_sdk_groups.count("v19"));
+    ASSERT_EQ(1ul, config.android_sdks.size());
+    ASSERT_EQ(1u, config.android_sdks.count("v19"));
 
-    auto& out = config.android_sdk_groups["v19"];
+    auto& out = config.android_sdks["v19"];
     EXPECT_EQ(19, out.max_sdk_version.value());
-    EXPECT_FALSE(out.min_sdk_version);
+    EXPECT_EQ(19, out.min_sdk_version);
     EXPECT_FALSE(out.target_sdk_version);
   }
 
   {
-    static constexpr const char* xml = R"xml(
-      <android-sdk-group label="v19">
-        <android-sdk targetSdkVersion="19"></android-sdk>
-      </android-sdk-group>)xml";
-
+    const char* xml =
+        "<android-sdk label='v19' minSdkVersion='19' targetSdkVersion='19'></android-sdk>";
     auto doc = test::BuildXmlDom(xml);
 
     PostProcessingConfiguration config;
-    bool ok = AndroidSdkGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+    bool ok = AndroidSdkTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
     ASSERT_TRUE(ok);
 
-    ASSERT_EQ(1ul, config.android_sdk_groups.size());
-    ASSERT_EQ(1u, config.android_sdk_groups.count("v19"));
+    ASSERT_EQ(1ul, config.android_sdks.size());
+    ASSERT_EQ(1u, config.android_sdks.count("v19"));
 
-    auto& out = config.android_sdk_groups["v19"];
+    auto& out = config.android_sdks["v19"];
     EXPECT_EQ(19, out.target_sdk_version.value());
-    EXPECT_FALSE(out.min_sdk_version);
+    EXPECT_EQ(19, out.min_sdk_version);
     EXPECT_FALSE(out.max_sdk_version);
   }
 }
 
 TEST_F(ConfigurationParserTest, AndroidSdkGroupAction_InvalidVersion) {
   static constexpr const char* xml = R"xml(
-    <android-sdk-group label="v19">
-      <android-sdk
-          minSdkVersion="v19"
-          targetSdkVersion="v24"
-          maxSdkVersion="v25">
-        <manifest>
-          <!--- manifest additions here XSLT? TODO -->
-        </manifest>
-      </android-sdk>
-    </android-sdk-group>)xml";
+    <android-sdk
+        label="v19"
+        minSdkVersion="v19"
+        targetSdkVersion="v24"
+        maxSdkVersion="v25">
+      <manifest>
+        <!--- manifest additions here XSLT? TODO -->
+      </manifest>
+    </android-sdk>)xml";
 
   auto doc = test::BuildXmlDom(xml);
 
   PostProcessingConfiguration config;
-  bool ok = AndroidSdkGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+  bool ok = AndroidSdkTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
   ASSERT_FALSE(ok);
 }
 
 TEST_F(ConfigurationParserTest, AndroidSdkGroupAction_NonNumeric) {
   static constexpr const char* xml = R"xml(
-    <android-sdk-group label="P">
       <android-sdk
+          label="P"
           minSdkVersion="25"
           targetSdkVersion="%s"
           maxSdkVersion="%s">
-      </android-sdk>
-    </android-sdk-group>)xml";
+      </android-sdk>)xml";
 
   const auto& dev_sdk = GetDevelopmentSdkCodeNameAndVersion();
   const char* codename = dev_sdk.first.data();
@@ -531,13 +677,13 @@
   auto doc = test::BuildXmlDom(StringPrintf(xml, codename, codename));
 
   PostProcessingConfiguration config;
-  bool ok = AndroidSdkGroupTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
+  bool ok = AndroidSdkTagHandler(&config, NodeCast<Element>(doc.get()->root.get()), &diag_);
   ASSERT_TRUE(ok);
 
-  ASSERT_EQ(1ul, config.android_sdk_groups.size());
-  ASSERT_EQ(1u, config.android_sdk_groups.count("P"));
+  ASSERT_EQ(1ul, config.android_sdks.size());
+  ASSERT_EQ(1u, config.android_sdks.count("P"));
 
-  auto& out = config.android_sdk_groups["P"];
+  auto& out = config.android_sdks["P"];
 
   AndroidSdk sdk;
   sdk.min_sdk_version = 25;
@@ -567,7 +713,7 @@
   EXPECT_THAT(config.gl_texture_groups, SizeIs(1ul));
   ASSERT_EQ(1u, config.gl_texture_groups.count("dxt1"));
 
-  auto& out = config.gl_texture_groups["dxt1"];
+  auto& out = config.gl_texture_groups["dxt1"].entry;
 
   GlTexture texture{
       std::string("GL_EXT_texture_compression_dxt1"),
@@ -596,7 +742,7 @@
   EXPECT_THAT(config.device_feature_groups, SizeIs(1ul));
   ASSERT_EQ(1u, config.device_feature_groups.count("low-latency"));
 
-  auto& out = config.device_feature_groups["low-latency"];
+  auto& out = config.device_feature_groups["low-latency"].entry;
 
   DeviceFeature low_latency = "android.hardware.audio.low_latency";
   DeviceFeature pro = "android.hardware.audio.pro";
@@ -650,7 +796,7 @@
   artifact.device_feature_group = {"df1"};
   artifact.gl_texture_group = {"glx1"};
   artifact.locale_group = {"en-AU"};
-  artifact.android_sdk_group = {"v26"};
+  artifact.android_sdk = {"v26"};
 
   {
     auto result = artifact.ToArtifactName(
diff --git a/tools/aapt2/configuration/aapt2.xsd b/tools/aapt2/configuration/aapt2.xsd
index 134153a..fb2f49b 100644
--- a/tools/aapt2/configuration/aapt2.xsd
+++ b/tools/aapt2/configuration/aapt2.xsd
@@ -8,22 +8,52 @@
   <xsd:element name="post-process">
     <xsd:complexType>
       <xsd:sequence>
-        <xsd:element name="groups" type="groups"/>
         <xsd:element name="artifacts" type="artifacts"/>
+        <xsd:element name="android-sdks" type="android-sdks"/>
+        <xsd:element name="abi-groups" type="abi-groups"/>
+        <xsd:element name="screen-density-groups" type="screen-density-groups"/>
+        <xsd:element name="locale-groups" type="locale-groups"/>
+        <xsd:element name="gl-texture-groups" type="gl-texture-groups"/>
+        <xsd:element name="device-feature-groups" type="device-feature-groups"/>
       </xsd:sequence>
     </xsd:complexType>
   </xsd:element>
 
-  <xsd:complexType name="groups">
+  <xsd:complexType name="android-sdks">
+    <xsd:sequence>
+      <xsd:element name="android-sdk" type="android-sdk" maxOccurs="unbounded"/>
+    </xsd:sequence>
+  </xsd:complexType>
+
+  <xsd:complexType name="abi-groups">
     <xsd:sequence>
       <xsd:element name="abi-group" type="abi-group" maxOccurs="unbounded"/>
+    </xsd:sequence>
+  </xsd:complexType>
+
+  <xsd:complexType name="screen-density-groups">
+    <xsd:sequence>
       <xsd:element name="screen-density-group" type="screen-density-group" maxOccurs="unbounded"/>
+    </xsd:sequence>
+  </xsd:complexType>
+
+  <xsd:complexType name="locale-groups">
+    <xsd:sequence>
       <xsd:element name="locale-group" type="locale-group" maxOccurs="unbounded"/>
-      <xsd:element name="android-sdk-group" type="android-sdk-group" maxOccurs="unbounded"/>
+    </xsd:sequence>
+  </xsd:complexType>
+
+  <xsd:complexType name="gl-texture-groups">
+    <xsd:sequence>
       <xsd:element
           name="gl-texture-group"
           type="gl-texture-group"
           maxOccurs="unbounded"/>
+    </xsd:sequence>
+  </xsd:complexType>
+
+  <xsd:complexType name="device-feature-groups">
+    <xsd:sequence>
       <xsd:element name="device-feature-group" type="device-feature-group" maxOccurs="unbounded"/>
     </xsd:sequence>
   </xsd:complexType>
@@ -38,8 +68,6 @@
 
   <!-- Groups output artifacts together by dimension labels. -->
   <xsd:complexType name="artifact">
-    <xsd:attribute name="name" type="xsd:string"/>
-    <xsd:attribute name="version" type="xsd:integer"/>
     <xsd:attribute name="abi-group" type="xsd:string"/>
     <xsd:attribute name="android-sdk-group" type="xsd:string"/>
     <xsd:attribute name="device-feature-group" type="xsd:string"/>
@@ -52,7 +80,7 @@
     <xsd:sequence>
       <xsd:element name="gl-texture" type="gl-texture" maxOccurs="unbounded"/>
     </xsd:sequence>
-    <xsd:attribute name="label" type="xsd:string" use="optional"/>
+    <xsd:attribute name="label" type="xsd:string"/>
   </xsd:complexType>
 
   <xsd:complexType name="gl-texture">
@@ -66,14 +94,14 @@
     <xsd:sequence>
       <xsd:element name="supports-feature" type="xsd:string" maxOccurs="unbounded"/>
     </xsd:sequence>
-    <xsd:attribute name="label" type="xsd:string" use="optional"/>
+    <xsd:attribute name="label" type="xsd:string"/>
   </xsd:complexType>
 
   <xsd:complexType name="abi-group">
     <xsd:sequence>
       <xsd:element name="abi" type="abi-name" maxOccurs="unbounded"/>
     </xsd:sequence>
-    <xsd:attribute name="label" type="xsd:string" use="optional"/>
+    <xsd:attribute name="label" type="xsd:string"/>
   </xsd:complexType>
 
   <xsd:simpleType name="abi-name">
@@ -93,7 +121,7 @@
     <xsd:sequence>
       <xsd:element name="screen-density" type="screen-density" maxOccurs="unbounded"/>
     </xsd:sequence>
-    <xsd:attribute name="label" type="xsd:string" use="optional"/>
+    <xsd:attribute name="label" type="xsd:string"/>
   </xsd:complexType>
 
   <xsd:simpleType name="screen-density">
@@ -108,20 +136,14 @@
     </xsd:restriction>
   </xsd:simpleType>
 
-  <xsd:complexType name="android-sdk-group">
-    <xsd:sequence>
-      <xsd:element name="android-sdk" type="android-sdk" maxOccurs="unbounded"/>
-    </xsd:sequence>
-    <xsd:attribute name="label" type="xsd:string" use="optional"/>
-  </xsd:complexType>
-
   <xsd:complexType name="android-sdk">
     <!-- TODO(safarmer): Add permissions to add/remove. -->
     <!-- TODO(safarmer): Add option for uncompressed native libs. -->
     <xsd:sequence>
       <xsd:element name="manifest" type="manifest"/>
     </xsd:sequence>
-    <xsd:attribute name="minSdkVersion" type="xsd:integer"/>
+    <xsd:attribute name="label" type="xsd:string" use="required"/>
+    <xsd:attribute name="minSdkVersion" type="xsd:integer" use="required"/>
     <xsd:attribute name="targetSdkVersion" type="xsd:integer"/>
     <xsd:attribute name="maxSdkVersion" type="xsd:integer"/>
   </xsd:complexType>
@@ -135,7 +157,7 @@
     <xsd:sequence>
       <xsd:element name="locale" type="locale" maxOccurs="unbounded"/>
     </xsd:sequence>
-    <xsd:attribute name="label" type="xsd:string" use="optional"/>
+    <xsd:attribute name="label" type="xsd:string"/>
   </xsd:complexType>
 
   <xsd:complexType name="locale">
diff --git a/tools/aapt2/configuration/example/config.xml b/tools/aapt2/configuration/example/config.xml
index ce31e61..d8aba09 100644
--- a/tools/aapt2/configuration/example/config.xml
+++ b/tools/aapt2/configuration/example/config.xml
@@ -1,70 +1,5 @@
 <?xml version="1.0" encoding="utf-8" ?>
 <post-process xmlns="http://schemas.android.com/tools/aapt">
-  <groups>
-    <abi-group label="arm">
-      <abi>armeabi-v7a</abi>
-      <abi>arm64-v8a</abi>
-    </abi-group>
-
-    <abi-group label="other">
-      <abi>x86</abi>
-      <abi>mips</abi>
-    </abi-group>
-
-    <screen-density-group label="large">
-      <screen-density>xhdpi</screen-density>
-      <screen-density>xxhdpi</screen-density>
-      <screen-density>xxxhdpi</screen-density>
-    </screen-density-group>
-
-    <screen-density-group label="alldpi">
-      <screen-density>ldpi</screen-density>
-      <screen-density>mdpi</screen-density>
-      <screen-density>hdpi</screen-density>
-      <screen-density>xhdpi</screen-density>
-      <screen-density>xxhdpi</screen-density>
-      <screen-density>xxxhdpi</screen-density>
-    </screen-density-group>
-
-    <locale-group label="europe">
-      <locale lang="en"/>
-      <locale lang="es"/>
-      <locale lang="fr"/>
-      <locale lang="de" compressed="true"/>
-    </locale-group>
-
-    <locale-group label="north-america">
-      <locale lang="en"/>
-      <locale lang="es" region="MX"/>
-      <locale lang="fr" region="CA" compressed="true"/>
-    </locale-group>
-
-    <locale-group label="all">
-      <locale compressed="true"/>
-    </locale-group>
-
-    <android-sdk-group label="19">
-      <android-sdk
-          minSdkVersion="19"
-          targetSdkVersion="24"
-          maxSdkVersion="25">
-        <manifest>
-          <!--- manifest additions here XSLT? TODO -->
-        </manifest>
-      </android-sdk>
-    </android-sdk-group>
-
-    <gl-texture-group label="dxt1">
-      <gl-texture name="GL_EXT_texture_compression_dxt1">
-        <texture-path>assets/dxt1/*</texture-path>
-      </gl-texture>
-    </gl-texture-group>
-
-    <device-feature-group label="low-latency">
-      <supports-feature>android.hardware.audio.low_latency</supports-feature>
-    </device-feature-group>
-  </groups>
-
   <artifacts>
     <artifact-format>
       ${base}.${abi}.${screen-density}.${locale}.${sdk}.${gl}.${feature}.release
@@ -87,4 +22,79 @@
         device-feature-group="low-latency"/>
 
   </artifacts>
+
+  <android-sdks>
+    <android-sdk
+        label="19"
+        minSdkVersion="19"
+        targetSdkVersion="24"
+        maxSdkVersion="25">
+      <manifest>
+        <!--- manifest additions here XSLT? TODO -->
+      </manifest>
+    </android-sdk>
+  </android-sdks>
+
+  <abi-groups>
+    <abi-group label="arm">
+      <abi>armeabi-v7a</abi>
+      <abi>arm64-v8a</abi>
+    </abi-group>
+
+    <abi-group label="other">
+      <abi>x86</abi>
+      <abi>mips</abi>
+    </abi-group>
+  </abi-groups>
+
+  <screen-density-groups>
+    <screen-density-group label="large">
+      <screen-density>xhdpi</screen-density>
+      <screen-density>xxhdpi</screen-density>
+      <screen-density>xxxhdpi</screen-density>
+    </screen-density-group>
+
+    <screen-density-group label="alldpi">
+      <screen-density>ldpi</screen-density>
+      <screen-density>mdpi</screen-density>
+      <screen-density>hdpi</screen-density>
+      <screen-density>xhdpi</screen-density>
+      <screen-density>xxhdpi</screen-density>
+      <screen-density>xxxhdpi</screen-density>
+    </screen-density-group>
+  </screen-density-groups>
+
+  <locale-groups>
+    <locale-group label="europe">
+      <locale lang="en"/>
+      <locale lang="es"/>
+      <locale lang="fr"/>
+      <locale lang="de" compressed="true"/>
+    </locale-group>
+
+    <locale-group label="north-america">
+      <locale lang="en"/>
+      <locale lang="es" region="MX"/>
+      <locale lang="fr" region="CA" compressed="true"/>
+    </locale-group>
+
+    <locale-group label="all">
+      <locale compressed="true"/>
+    </locale-group>
+  </locale-groups>
+
+  <gl-texture-groups>
+    <gl-texture-group label="dxt1">
+      <gl-texture name="GL_EXT_texture_compression_dxt1">
+        <texture-path>assets/dxt1/*</texture-path>
+      </gl-texture>
+    </gl-texture-group>
+  </gl-texture-groups>
+
+  <device-feature-groups>
+    <device-feature-group label="low-latency">
+      <supports-feature>android.hardware.audio.low_latency</supports-feature>
+    </device-feature-group>
+  </device-feature-groups>
+
 </post-process>
diff --git a/tools/aapt2/optimize/MultiApkGenerator.cpp b/tools/aapt2/optimize/MultiApkGenerator.cpp
index 16898d6..991faad 100644
--- a/tools/aapt2/optimize/MultiApkGenerator.cpp
+++ b/tools/aapt2/optimize/MultiApkGenerator.cpp
@@ -120,8 +120,6 @@
 }
 
 bool MultiApkGenerator::FromBaseApk(const MultiApkGeneratorOptions& options) {
-  // TODO(safarmer): Handle APK version codes for the generated APKs.
-
   std::unordered_set<std::string> artifacts_to_keep = options.kept_artifacts;
   std::unordered_set<std::string> filtered_artifacts;
   std::unordered_set<std::string> kept_artifacts;
@@ -237,8 +235,8 @@
     splits.config_filter = &axis_filter;
   }
 
-  if (artifact.android_sdk && artifact.android_sdk.value().min_sdk_version) {
-    wrapped_context.SetMinSdkVersion(artifact.android_sdk.value().min_sdk_version.value());
+  if (artifact.android_sdk) {
+    wrapped_context.SetMinSdkVersion(artifact.android_sdk.value().min_sdk_version);
   }
 
   std::unique_ptr<ResourceTable> table = old_table.Clone();
@@ -301,7 +299,7 @@
       if (xml::Attribute* min_sdk_attr =
               uses_sdk_el->FindAttribute(xml::kSchemaAndroid, "minSdkVersion")) {
         // Populate with a pre-compiles attribute to we don't need to relink etc.
-        const std::string& min_sdk_str = std::to_string(android_sdk.min_sdk_version.value());
+        const std::string& min_sdk_str = std::to_string(android_sdk.min_sdk_version);
         min_sdk_attr->compiled_value = ResourceUtils::TryParseInt(min_sdk_str);
       } else {
         // There was no minSdkVersion. This is strange since at this point we should have been
diff --git a/tools/aapt2/test/Builders.cpp b/tools/aapt2/test/Builders.cpp
index a3e2f80..495a48a 100644
--- a/tools/aapt2/test/Builders.cpp
+++ b/tools/aapt2/test/Builders.cpp
@@ -26,6 +26,7 @@
 using ::aapt::configuration::Abi;
 using ::aapt::configuration::AndroidSdk;
 using ::aapt::configuration::ConfiguredArtifact;
+using ::aapt::configuration::GetOrCreateGroup;
 using ::aapt::io::StringInputStream;
 using ::android::StringPiece;
 
@@ -227,6 +228,11 @@
   return *this;
 }
 
+ArtifactBuilder& ArtifactBuilder::SetVersion(int version) {
+  artifact_.version = version;
+  return *this;
+}
+
 ArtifactBuilder& ArtifactBuilder::AddAbi(configuration::Abi abi) {
   artifact_.abis.push_back(abi);
   return *this;
@@ -251,5 +257,54 @@
   return artifact_;
 }
 
+PostProcessingConfigurationBuilder& PostProcessingConfigurationBuilder::AddAbiGroup(
+    const std::string& label, std::vector<configuration::Abi> abis) {
+  return AddGroup(label, &config_.abi_groups, std::move(abis));
+}
+
+PostProcessingConfigurationBuilder& PostProcessingConfigurationBuilder::AddDensityGroup(
+    const std::string& label, std::vector<std::string> densities) {
+  std::vector<ConfigDescription> configs;
+  for (const auto& density : densities) {
+    configs.push_back(test::ParseConfigOrDie(density));
+  }
+  return AddGroup(label, &config_.screen_density_groups, configs);
+}
+
+PostProcessingConfigurationBuilder& PostProcessingConfigurationBuilder::AddLocaleGroup(
+    const std::string& label, std::vector<std::string> locales) {
+  std::vector<ConfigDescription> configs;
+  for (const auto& locale : locales) {
+    configs.push_back(test::ParseConfigOrDie(locale));
+  }
+  return AddGroup(label, &config_.locale_groups, configs);
+}
+
+PostProcessingConfigurationBuilder& PostProcessingConfigurationBuilder::AddDeviceFeatureGroup(
+    const std::string& label) {
+  return AddGroup(label, &config_.device_feature_groups);
+}
+
+PostProcessingConfigurationBuilder& PostProcessingConfigurationBuilder::AddGlTextureGroup(
+    const std::string& label) {
+  return AddGroup(label, &config_.gl_texture_groups);
+}
+
+PostProcessingConfigurationBuilder& PostProcessingConfigurationBuilder::AddAndroidSdk(
+    std::string label, int min_sdk) {
+  config_.android_sdks[label] = AndroidSdk::ForMinSdk(min_sdk);
+  return *this;
+}
+
+PostProcessingConfigurationBuilder& PostProcessingConfigurationBuilder::AddArtifact(
+    configuration::ConfiguredArtifact artifact) {
+  config_.artifacts.push_back(std::move(artifact));
+  return *this;
+}
+
+configuration::PostProcessingConfiguration PostProcessingConfigurationBuilder::Build() {
+  return config_;
+}
+
 }  // namespace test
 }  // namespace aapt
diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h
index b456908..0d7451b 100644
--- a/tools/aapt2/test/Builders.h
+++ b/tools/aapt2/test/Builders.h
@@ -160,6 +160,7 @@
   ArtifactBuilder() = default;
 
   ArtifactBuilder& SetName(const std::string& name);
+  ArtifactBuilder& SetVersion(int version);
   ArtifactBuilder& AddAbi(configuration::Abi abi);
   ArtifactBuilder& AddDensity(const ConfigDescription& density);
   ArtifactBuilder& AddLocale(const ConfigDescription& locale);
@@ -167,9 +168,41 @@
   configuration::OutputArtifact Build();
 
  private:
+  DISALLOW_COPY_AND_ASSIGN(ArtifactBuilder);
+
   configuration::OutputArtifact artifact_;
 };
 
+class PostProcessingConfigurationBuilder {
+ public:
+  PostProcessingConfigurationBuilder() = default;
+
+  PostProcessingConfigurationBuilder& AddAbiGroup(const std::string& label,
+                                                  std::vector<configuration::Abi> abis = {});
+  PostProcessingConfigurationBuilder& AddDensityGroup(const std::string& label,
+                                                      std::vector<std::string> densities = {});
+  PostProcessingConfigurationBuilder& AddLocaleGroup(const std::string& label,
+                                                     std::vector<std::string> locales = {});
+  PostProcessingConfigurationBuilder& AddDeviceFeatureGroup(const std::string& label);
+  PostProcessingConfigurationBuilder& AddGlTextureGroup(const std::string& label);
+  PostProcessingConfigurationBuilder& AddAndroidSdk(std::string label, int min_sdk);
+  PostProcessingConfigurationBuilder& AddArtifact(configuration::ConfiguredArtifact artrifact);
+
+  configuration::PostProcessingConfiguration Build();
+
+ private:
+  template <typename T>
+  inline PostProcessingConfigurationBuilder& AddGroup(const std::string& label,
+                                                      configuration::Group<T>* group,
+                                                      std::vector<T> to_add = {}) {
+    auto& values = GetOrCreateGroup(label, group);
+    values.insert(std::begin(values), std::begin(to_add), std::end(to_add));
+    return *this;
+  }
+
+  configuration::PostProcessingConfiguration config_;
+};
+
 }  // namespace test
 }  // namespace aapt