Merge "Add more methods to RecoverableKeyStoreLoader."
diff --git a/Android.bp b/Android.bp
index 3754c81..e23d9c2 100644
--- a/Android.bp
+++ b/Android.bp
@@ -693,7 +693,6 @@
     srcs: [
         "core/proto/**/*.proto",
         "libs/incident/**/*.proto",
-        "tools/streaming_proto/stream.proto",
     ],
 
     target: {
diff --git a/Android.mk b/Android.mk
index 00f877a..995630b 100644
--- a/Android.mk
+++ b/Android.mk
@@ -1003,7 +1003,6 @@
     -Iexternal/protobuf/src
 LOCAL_SOURCE_FILES_ALL_GENERATED := true
 LOCAL_SRC_FILES := \
-    tools/streaming_proto/stream.proto \
     cmds/am/proto/instrumentation_data.proto \
     $(call all-proto-files-under, core/proto) \
     $(call all-proto-files-under, libs/incident/proto) \
diff --git a/api/current.txt b/api/current.txt
index c4f9e4a..60d314f 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -3961,7 +3961,8 @@
     field public static final int IMPORTANCE_PERCEPTIBLE = 230; // 0xe6
     field public static final int IMPORTANCE_PERCEPTIBLE_PRE_26 = 130; // 0x82
     field public static final int IMPORTANCE_SERVICE = 300; // 0x12c
-    field public static final int IMPORTANCE_TOP_SLEEPING = 150; // 0x96
+    field public static final int IMPORTANCE_TOP_SLEEPING = 325; // 0x145
+    field public static final deprecated int IMPORTANCE_TOP_SLEEPING_PRE_28 = 150; // 0x96
     field public static final int IMPORTANCE_VISIBLE = 200; // 0xc8
     field public static final int REASON_PROVIDER_IN_USE = 1; // 0x1
     field public static final int REASON_SERVICE_IN_USE = 2; // 0x2
@@ -5199,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";
@@ -5482,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
   }
 
@@ -6450,6 +6454,7 @@
     method public void setDeviceOwnerLockScreenInfo(android.content.ComponentName, java.lang.CharSequence);
     method public void setGlobalSetting(android.content.ComponentName, java.lang.String, java.lang.String);
     method public void setKeepUninstalledPackages(android.content.ComponentName, java.util.List<java.lang.String>);
+    method public boolean setKeyPairCertificate(android.content.ComponentName, java.lang.String, java.util.List<java.security.cert.Certificate>, boolean);
     method public boolean setKeyguardDisabled(android.content.ComponentName, boolean);
     method public void setKeyguardDisabledFeatures(android.content.ComponentName, int);
     method public void setLockTaskFeatures(android.content.ComponentName, int);
@@ -7030,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";
@@ -8221,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);
@@ -32257,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";
@@ -41064,6 +41072,7 @@
     method public java.lang.String getMeid(int);
     method public java.lang.String getMmsUAProfUrl();
     method public java.lang.String getMmsUserAgent();
+    method public java.lang.String getNai();
     method public deprecated java.util.List<android.telephony.NeighboringCellInfo> getNeighboringCellInfo();
     method public java.lang.String getNetworkCountryIso();
     method public java.lang.String getNetworkOperator();
@@ -41907,9 +41916,9 @@
   }
 
   public class DynamicLayout extends android.text.Layout {
-    ctor public DynamicLayout(java.lang.CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, boolean);
-    ctor public DynamicLayout(java.lang.CharSequence, java.lang.CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, boolean);
-    ctor public DynamicLayout(java.lang.CharSequence, java.lang.CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, boolean, android.text.TextUtils.TruncateAt, int);
+    ctor public deprecated DynamicLayout(java.lang.CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, boolean);
+    ctor public deprecated DynamicLayout(java.lang.CharSequence, java.lang.CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, boolean);
+    ctor public deprecated DynamicLayout(java.lang.CharSequence, java.lang.CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, boolean, android.text.TextUtils.TruncateAt, int);
     method public int getBottomPadding();
     method public int getEllipsisCount(int);
     method public int getEllipsisStart(int);
@@ -42296,9 +42305,9 @@
   }
 
   public class StaticLayout extends android.text.Layout {
-    ctor public StaticLayout(java.lang.CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, boolean);
-    ctor public StaticLayout(java.lang.CharSequence, int, int, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, boolean);
-    ctor public StaticLayout(java.lang.CharSequence, int, int, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, boolean, android.text.TextUtils.TruncateAt, int);
+    ctor public deprecated StaticLayout(java.lang.CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, boolean);
+    ctor public deprecated StaticLayout(java.lang.CharSequence, int, int, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, boolean);
+    ctor public deprecated StaticLayout(java.lang.CharSequence, int, int, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, boolean, android.text.TextUtils.TruncateAt, int);
     method public int getBottomPadding();
     method public int getEllipsisCount(int);
     method public int getEllipsisStart(int);
@@ -44715,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();
@@ -47668,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();
@@ -47729,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
@@ -47823,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;
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 5859504..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";
@@ -1339,7 +1340,7 @@
 
 package android.hardware.location {
 
-  public class ContextHubInfo {
+  public class ContextHubInfo implements android.os.Parcelable {
     ctor public ContextHubInfo();
     method public int describeContents();
     method public int getId();
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/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 19e3a23..1adae7a 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -458,16 +458,17 @@
     public static final int USER_OP_ERROR_RELATED_USERS_CANNOT_STOP = -4;
 
     /**
+     * @hide
      * Process states, describing the kind of state a particular process is in.
      * When updating these, make sure to also check all related references to the
      * constant in code, and update these arrays:
      *
-     * com.android.internal.app.procstats.ProcessState#PROCESS_STATE_TO_STATE
-     * com.android.server.am.ProcessList#sProcStateToProcMem
-     * com.android.server.am.ProcessList#sFirstAwakePssTimes
-     * com.android.server.am.ProcessList#sSameAwakePssTimes
-     * com.android.server.am.ProcessList#sTestFirstPssTimes
-     * com.android.server.am.ProcessList#sTestSamePssTimes
+     * @see com.android.internal.app.procstats.ProcessState#PROCESS_STATE_TO_STATE
+     * @see com.android.server.am.ProcessList#sProcStateToProcMem
+     * @see com.android.server.am.ProcessList#sFirstAwakePssTimes
+     * @see com.android.server.am.ProcessList#sSameAwakePssTimes
+     * @see com.android.server.am.ProcessList#sTestFirstPssTimes
+     * @see com.android.server.am.ProcessList#sTestSamePssTimes
      */
 
     /** @hide Not a real process state. */
@@ -489,31 +490,31 @@
     /** @hide Process is hosting a foreground service. */
     public static final int PROCESS_STATE_FOREGROUND_SERVICE = 4;
 
-    /** @hide Same as {@link #PROCESS_STATE_TOP} but while device is sleeping. */
-    public static final int PROCESS_STATE_TOP_SLEEPING = 5;
-
     /** @hide Process is important to the user, and something they are aware of. */
-    public static final int PROCESS_STATE_IMPORTANT_FOREGROUND = 6;
+    public static final int PROCESS_STATE_IMPORTANT_FOREGROUND = 5;
 
     /** @hide Process is important to the user, but not something they are aware of. */
-    public static final int PROCESS_STATE_IMPORTANT_BACKGROUND = 7;
+    public static final int PROCESS_STATE_IMPORTANT_BACKGROUND = 6;
 
     /** @hide Process is in the background transient so we will try to keep running. */
-    public static final int PROCESS_STATE_TRANSIENT_BACKGROUND = 8;
+    public static final int PROCESS_STATE_TRANSIENT_BACKGROUND = 7;
 
     /** @hide Process is in the background running a backup/restore operation. */
-    public static final int PROCESS_STATE_BACKUP = 9;
+    public static final int PROCESS_STATE_BACKUP = 8;
 
     /** @hide Process is in the background running a service.  Unlike oom_adj, this level
      * is used for both the normal running in background state and the executing
      * operations state. */
-    public static final int PROCESS_STATE_SERVICE = 10;
+    public static final int PROCESS_STATE_SERVICE = 9;
 
     /** @hide Process is in the background running a receiver.   Note that from the
      * perspective of oom_adj, receivers run at a higher foreground level, but for our
      * prioritization here that is not necessary and putting them below services means
      * many fewer changes in some process states as they receive broadcasts. */
-    public static final int PROCESS_STATE_RECEIVER = 11;
+    public static final int PROCESS_STATE_RECEIVER = 10;
+
+    /** @hide Same as {@link #PROCESS_STATE_TOP} but while device is sleeping. */
+    public static final int PROCESS_STATE_TOP_SLEEPING = 11;
 
     /** @hide Process is in the background, but it can't restore its state so we want
      * to try to avoid killing it. */
@@ -2897,13 +2898,13 @@
         public static final int IMPORTANCE_FOREGROUND_SERVICE = 125;
 
         /**
-         * Constant for {@link #importance}: This process is running the foreground
-         * UI, but the device is asleep so it is not visible to the user.  This means
-         * the user is not really aware of the process, because they can not see or
-         * interact with it, but it is quite important because it what they expect to
-         * return to once unlocking the device.
+         * @deprecated Pre-{@link android.os.Build.VERSION_CODES#P} version of
+         * {@link #IMPORTANCE_TOP_SLEEPING}.  As of Android
+         * {@link android.os.Build.VERSION_CODES#P}, this is considered much less
+         * important since we want to reduce what apps can do when the screen is off.
          */
-        public static final int IMPORTANCE_TOP_SLEEPING = 150;
+        @Deprecated
+        public static final int IMPORTANCE_TOP_SLEEPING_PRE_28 = 150;
 
         /**
          * Constant for {@link #importance}: This process is running something
@@ -2964,6 +2965,15 @@
         public static final int IMPORTANCE_SERVICE = 300;
 
         /**
+         * Constant for {@link #importance}: This process is running the foreground
+         * UI, but the device is asleep so it is not visible to the user.  Though the
+         * system will try hard to keep its process from being killed, in all other
+         * ways we consider it a kind of cached process, with the limitations that go
+         * along with that state: network access, running background services, etc.
+         */
+        public static final int IMPORTANCE_TOP_SLEEPING = 325;
+
+        /**
          * Constant for {@link #importance}: This process is running an
          * application that can not save its state, and thus can't be killed
          * while in the background.  This will be used with apps that have
@@ -3008,14 +3018,14 @@
                 return IMPORTANCE_CACHED;
             } else if (procState == PROCESS_STATE_HEAVY_WEIGHT) {
                 return IMPORTANCE_CANT_SAVE_STATE;
+            } else if (procState >= PROCESS_STATE_TOP_SLEEPING) {
+                return IMPORTANCE_TOP_SLEEPING;
             } else if (procState >= PROCESS_STATE_SERVICE) {
                 return IMPORTANCE_SERVICE;
             } else if (procState >= PROCESS_STATE_TRANSIENT_BACKGROUND) {
                 return IMPORTANCE_PERCEPTIBLE;
             } else if (procState >= PROCESS_STATE_IMPORTANT_FOREGROUND) {
                 return IMPORTANCE_VISIBLE;
-            } else if (procState >= PROCESS_STATE_TOP_SLEEPING) {
-                return IMPORTANCE_TOP_SLEEPING;
             } else if (procState >= PROCESS_STATE_FOREGROUND_SERVICE) {
                 return IMPORTANCE_FOREGROUND_SERVICE;
             } else {
@@ -3049,6 +3059,8 @@
                 switch (importance) {
                     case IMPORTANCE_PERCEPTIBLE:
                         return IMPORTANCE_PERCEPTIBLE_PRE_26;
+                    case IMPORTANCE_TOP_SLEEPING:
+                        return IMPORTANCE_TOP_SLEEPING_PRE_28;
                     case IMPORTANCE_CANT_SAVE_STATE:
                         return IMPORTANCE_CANT_SAVE_STATE_PRE_26;
                 }
@@ -3062,16 +3074,18 @@
                 return PROCESS_STATE_NONEXISTENT;
             } else if (importance >= IMPORTANCE_CACHED) {
                 return PROCESS_STATE_HOME;
-            } else if (importance == IMPORTANCE_CANT_SAVE_STATE) {
+            } else if (importance >= IMPORTANCE_CANT_SAVE_STATE) {
                 return PROCESS_STATE_HEAVY_WEIGHT;
+            } else if (importance >= IMPORTANCE_TOP_SLEEPING) {
+                return PROCESS_STATE_TOP_SLEEPING;
             } else if (importance >= IMPORTANCE_SERVICE) {
                 return PROCESS_STATE_SERVICE;
             } else if (importance >= IMPORTANCE_PERCEPTIBLE) {
                 return PROCESS_STATE_TRANSIENT_BACKGROUND;
             } else if (importance >= IMPORTANCE_VISIBLE) {
                 return PROCESS_STATE_IMPORTANT_FOREGROUND;
-            } else if (importance >= IMPORTANCE_TOP_SLEEPING) {
-                return PROCESS_STATE_TOP_SLEEPING;
+            } else if (importance >= IMPORTANCE_TOP_SLEEPING_PRE_28) {
+                return PROCESS_STATE_FOREGROUND_SERVICE;
             } else if (importance >= IMPORTANCE_FOREGROUND_SERVICE) {
                 return PROCESS_STATE_FOREGROUND_SERVICE;
             } else {
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..86d0fd62 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/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 4bb4c50..9816297 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -2635,10 +2635,121 @@
     }
 
     /**
+     * The maximum number of characters allowed in the password blacklist.
+     */
+    private static final int PASSWORD_BLACKLIST_CHARACTER_LIMIT = 128 * 1000;
+
+    /**
+     * Throws an exception if the password blacklist is too large.
+     *
+     * @hide
+     */
+    public static void enforcePasswordBlacklistSize(List<String> blacklist) {
+        if (blacklist == null) {
+            return;
+        }
+        long characterCount = 0;
+        for (final String item : blacklist) {
+            characterCount += item.length();
+        }
+        if (characterCount > PASSWORD_BLACKLIST_CHARACTER_LIMIT) {
+            throw new IllegalArgumentException("128 thousand blacklist character limit exceeded by "
+                      + (characterCount - PASSWORD_BLACKLIST_CHARACTER_LIMIT) + " characters");
+        }
+    }
+
+    /**
+     * Called by an application that is administering the device to blacklist passwords.
+     * <p>
+     * Any blacklisted password or PIN is prevented from being enrolled by the user or the admin.
+     * Note that the match against the blacklist is case insensitive. The blacklist applies for all
+     * password qualities requested by {@link #setPasswordQuality} however it is not taken into
+     * consideration by {@link #isActivePasswordSufficient}.
+     * <p>
+     * The blacklist can be cleared by passing {@code null} or an empty list. The blacklist is
+     * given a name that is used to track which blacklist is currently set by calling {@link
+     * #getPasswordBlacklistName}. If the blacklist is being cleared, the name is ignored and {@link
+     * #getPasswordBlacklistName} will return {@code null}. The name can only be {@code null} when
+     * the blacklist is being cleared.
+     * <p>
+     * The blacklist is limited to a total of 128 thousand characters rather than limiting to a
+     * number of entries.
+     * <p>
+     * This method can be called on the {@link DevicePolicyManager} instance returned by
+     * {@link #getParentProfileInstance(ComponentName)} in order to set restrictions on the parent
+     * profile.
+     *
+     * @param admin the {@link DeviceAdminReceiver} this request is associated with
+     * @param name name to associate with the blacklist
+     * @param blacklist list of passwords to blacklist or {@code null} to clear the blacklist
+     * @return whether the new blacklist was successfully installed
+     * @throws SecurityException if {@code admin} is not a device or profile owner
+     * @throws IllegalArgumentException if the blacklist surpasses the character limit
+     * @throws NullPointerException if {@code name} is {@code null} when setting a non-empty list
+     *
+     * @see #getPasswordBlacklistName
+     * @see #isActivePasswordSufficient
+     * @see #resetPasswordWithToken
+     *
+     * TODO(63578054): unhide for P
+     * @hide
+     */
+    public boolean setPasswordBlacklist(@NonNull ComponentName admin, @Nullable String name,
+            @Nullable List<String> blacklist) {
+        enforcePasswordBlacklistSize(blacklist);
+
+        try {
+            return mService.setPasswordBlacklist(admin, name, blacklist, mParentInstance);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Get the name of the password blacklist set by the given admin.
+     *
+     * @param admin the {@link DeviceAdminReceiver} this request is associated with
+     * @return the name of the blacklist or {@code null} if no blacklist is set
+     *
+     * @see #setPasswordBlacklist
+     *
+     * TODO(63578054): unhide for P
+     * @hide
+     */
+    public @Nullable String getPasswordBlacklistName(@NonNull ComponentName admin) {
+        try {
+            return mService.getPasswordBlacklistName(admin, myUserId(), mParentInstance);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Test if a given password is blacklisted.
+     *
+     * @param userId the user to valiate for
+     * @param password the password to check against the blacklist
+     * @return whether the password is blacklisted
+     *
+     * @see #setPasswordBlacklist
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.TEST_BLACKLISTED_PASSWORD)
+    public boolean isPasswordBlacklisted(@UserIdInt int userId, @NonNull String password) {
+        try {
+            return mService.isPasswordBlacklisted(userId, password);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Determine whether the current password the user has set is sufficient to meet the policy
      * requirements (e.g. quality, minimum length) that have been requested by the admins of this
      * user and its participating profiles. Restrictions on profiles that have a separate challenge
-     * are not taken into account. The user must be unlocked in order to perform the check.
+     * are not taken into account. The user must be unlocked in order to perform the check. The
+     * password blacklist is not considered when checking sufficiency.
      * <p>
      * The calling device admin must have requested
      * {@link DeviceAdminInfo#USES_POLICY_LIMIT_PASSWORD} to be able to call this method; if it has
@@ -4063,6 +4174,52 @@
         return null;
     }
 
+
+    /**
+     * Called by a device or profile owner, or delegated certificate installer, to associate
+     * certificates with a key pair that was generated using {@link #generateKeyPair}, and
+     * set whether the key is available for the user to choose in the certificate selection
+     * prompt.
+     *
+     * @param admin Which {@link DeviceAdminReceiver} this request is associated with, or
+     *            {@code null} if calling from a delegated certificate installer.
+     * @param alias The private key alias under which to install the certificate. The {@code alias}
+     *        should denote an existing private key. If a certificate with that alias already
+     *        exists, it will be overwritten.
+     * @param certs The certificate chain to install. The chain should start with the leaf
+     *        certificate and include the chain of trust in order. This will be returned by
+     *        {@link android.security.KeyChain#getCertificateChain}.
+     * @param isUserSelectable {@code true} to indicate that a user can select this key via the
+     *        certificate selection prompt, {@code false} to indicate that this key can only be
+     *        granted access by implementing
+     *        {@link android.app.admin.DeviceAdminReceiver#onChoosePrivateKeyAlias}.
+     * @return {@code true} if the provided {@code alias} exists and the certificates has been
+     *        successfully associated with it, {@code false} otherwise.
+     * @throws SecurityException if {@code admin} is not {@code null} and not a device or profile
+     *         owner, or {@code admin} is null but the calling application is not a delegated
+     *         certificate installer.
+     */
+    public boolean setKeyPairCertificate(@Nullable ComponentName admin,
+            @NonNull String alias, @NonNull List<Certificate> certs, boolean isUserSelectable) {
+        throwIfParentInstance("setKeyPairCertificate");
+        try {
+            final byte[] pemCert = Credentials.convertToPem(certs.get(0));
+            byte[] pemChain = null;
+            if (certs.size() > 1) {
+                pemChain = Credentials.convertToPem(
+                        certs.subList(1, certs.size()).toArray(new Certificate[0]));
+            }
+            return mService.setKeyPairCertificate(admin, mContext.getPackageName(), alias, pemCert,
+                    pemChain, isUserSelectable);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        } catch (CertificateException | IOException e) {
+            Log.w(TAG, "Could not pem-encode certificate", e);
+        }
+        return false;
+    }
+
+
     /**
      * @return the alias of a given CA certificate in the certificate store, or {@code null} if it
      * doesn't exist.
diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl
index 9128208..5b02c22 100644
--- a/core/java/android/app/admin/IDevicePolicyManager.aidl
+++ b/core/java/android/app/admin/IDevicePolicyManager.aidl
@@ -78,6 +78,10 @@
 
     long getPasswordExpiration(in ComponentName who, int userHandle, boolean parent);
 
+    boolean setPasswordBlacklist(in ComponentName who, String name, in List<String> blacklist, boolean parent);
+    String getPasswordBlacklistName(in ComponentName who, int userId, boolean parent);
+    boolean isPasswordBlacklisted(int userId, String password);
+
     boolean isActivePasswordSufficient(int userHandle, boolean parent);
     boolean isProfileActivePasswordSufficientForParent(int userHandle);
     boolean isUsingUnifiedPassword(in ComponentName admin);
@@ -171,6 +175,8 @@
     boolean generateKeyPair(in ComponentName who, in String callerPackage, in String algorithm,
             in ParcelableKeyGenParameterSpec keySpec,
             out KeymasterCertificateChain attestationChain);
+    boolean setKeyPairCertificate(in ComponentName who, in String callerPackage, in String alias,
+            in byte[] certBuffer, in byte[] certChainBuffer, boolean isUserSelectable);
     void choosePrivateKeyAlias(int uid, in Uri uri, in String alias, IBinder aliasCallback);
 
     void setDelegatedScopes(in ComponentName who, in String delegatePackage, in List<String> scopes);
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/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java
index 7409671..a85b5f7 100644
--- a/core/java/android/hardware/camera2/params/OutputConfiguration.java
+++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java
@@ -486,6 +486,7 @@
         this.mConfiguredSize = other.mConfiguredSize;
         this.mConfiguredGenerationId = other.mConfiguredGenerationId;
         this.mIsDeferredConfig = other.mIsDeferredConfig;
+        this.mIsShared = other.mIsShared;
     }
 
     /**
@@ -498,6 +499,7 @@
         int width = source.readInt();
         int height = source.readInt();
         boolean isDeferred = source.readInt() == 1;
+        boolean isShared = source.readInt() == 1;
         ArrayList<Surface> surfaces = new ArrayList<Surface>();
         source.readTypedList(surfaces, Surface.CREATOR);
 
@@ -508,6 +510,7 @@
         mSurfaces = surfaces;
         mConfiguredSize = new Size(width, height);
         mIsDeferredConfig = isDeferred;
+        mIsShared = isShared;
         mSurfaces = surfaces;
         if (mSurfaces.size() > 0) {
             mSurfaceType = SURFACE_TYPE_UNKNOWN;
diff --git a/core/java/android/hardware/location/ContextHubInfo.java b/core/java/android/hardware/location/ContextHubInfo.java
index e1137aa..c2b2800 100644
--- a/core/java/android/hardware/location/ContextHubInfo.java
+++ b/core/java/android/hardware/location/ContextHubInfo.java
@@ -26,7 +26,7 @@
  * @hide
  */
 @SystemApi
-public class ContextHubInfo {
+public class ContextHubInfo implements Parcelable {
     private int mId;
     private String mName;
     private String mVendor;
@@ -262,7 +262,7 @@
     @Override
     public String toString() {
         String retVal = "";
-        retVal += "Id : " + mId;
+        retVal += "ID/handle : " + mId;
         retVal += ", Name : " + mName;
         retVal += "\n\tVendor : " + mVendor;
         retVal += ", Toolchain : " + mToolchain;
@@ -275,8 +275,6 @@
         retVal += ", StoppedPowerDraw : " + mStoppedPowerDrawMw + " mW";
         retVal += ", PeakPowerDraw : " + mPeakPowerDrawMw + " mW";
         retVal += ", MaxPacketLength : " + mMaxPacketLengthBytes + " Bytes";
-        retVal += "\n\tSupported sensors : " + Arrays.toString(mSupportedSensors);
-        retVal += "\n\tMemory Regions : " + Arrays.toString(mMemoryRegions);
 
         return retVal;
     }
diff --git a/core/java/android/hardware/location/ContextHubManager.java b/core/java/android/hardware/location/ContextHubManager.java
index 5b89f54..6da6fb7 100644
--- a/core/java/android/hardware/location/ContextHubManager.java
+++ b/core/java/android/hardware/location/ContextHubManager.java
@@ -258,9 +258,9 @@
     }
 
     /**
-     * Returns the list of context hubs in the system.
+     * Returns the list of ContextHubInfo objects describing the available Context Hubs.
      *
-     * @return the list of context hub informations
+     * @return the list of ContextHubInfo objects
      *
      * @see ContextHubInfo
      *
@@ -268,7 +268,11 @@
      */
     @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
     public List<ContextHubInfo> getContextHubs() {
-        throw new UnsupportedOperationException("TODO: Implement this");
+        try {
+            return mService.getContextHubs();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
     /**
diff --git a/core/java/android/hardware/location/IContextHubService.aidl b/core/java/android/hardware/location/IContextHubService.aidl
index db5bd36..233e857 100644
--- a/core/java/android/hardware/location/IContextHubService.aidl
+++ b/core/java/android/hardware/location/IContextHubService.aidl
@@ -43,23 +43,26 @@
     ContextHubInfo getContextHubInfo(int contextHubHandle);
 
     // Loads a nanoapp at the specified hub (old API)
-    int loadNanoApp(int hubHandle, in NanoApp app);
+    int loadNanoApp(int contextHubHandle, in NanoApp nanoApp);
 
     // Unloads a nanoapp given its instance ID (old API)
-    int unloadNanoApp(int nanoAppInstanceHandle);
+    int unloadNanoApp(int nanoAppHandle);
 
     // Gets the NanoAppInstanceInfo of a nanoapp give its instance ID
-    NanoAppInstanceInfo getNanoAppInstanceInfo(int nanoAppInstanceHandle);
+    NanoAppInstanceInfo getNanoAppInstanceInfo(int nanoAppHandle);
 
     // Finds all nanoApp instances matching some filter
-    int[] findNanoAppOnHub(int hubHandle, in NanoAppFilter filter);
+    int[] findNanoAppOnHub(int contextHubHandle, in NanoAppFilter filter);
 
     // Sends a message to a nanoApp
-    int sendMessage(int hubHandle, int nanoAppHandle, in ContextHubMessage msg);
+    int sendMessage(int contextHubHandle, int nanoAppHandle, in ContextHubMessage msg);
 
     // Creates a client to send and receive messages
     IContextHubClient createClient(in IContextHubClientCallback client, int contextHubId);
 
+    // Returns a list of ContextHub objects of available hubs
+    List<ContextHubInfo> getContextHubs();
+
     // Loads a nanoapp at the specified hub (new API)
     void loadNanoAppOnHub(
             int contextHubId, in IContextHubTransactionCallback transactionCallback,
diff --git a/core/java/android/hardware/location/NanoAppInstanceInfo.java b/core/java/android/hardware/location/NanoAppInstanceInfo.java
index b7e6b66..f73fd87 100644
--- a/core/java/android/hardware/location/NanoAppInstanceInfo.java
+++ b/core/java/android/hardware/location/NanoAppInstanceInfo.java
@@ -27,15 +27,13 @@
  * Describes an instance of a nanoapp, used by the internal state manged by ContextHubService.
  *
  * TODO(b/69270990) Remove this class once the old API is deprecated.
- * TODO(b/70624255) Clean up toString() by removing unnecessary fields
  *
  * @hide
  */
 @SystemApi
 public class NanoAppInstanceInfo {
-    private static final String PRE_LOADED_GENERIC_UNKNOWN = "Preloaded app, unknown";
-    private String mPublisher = PRE_LOADED_GENERIC_UNKNOWN;
-    private String mName = PRE_LOADED_GENERIC_UNKNOWN;
+    private String mPublisher = "Unknown";
+    private String mName = "Unknown";
 
     private int mHandle;
     private long mAppId;
@@ -227,9 +225,7 @@
     public String toString() {
         String retVal = "handle : " + mHandle;
         retVal += ", Id : 0x" + Long.toHexString(mAppId);
-        retVal += ", Version : " + mAppVersion;
-        retVal += ", Name : " + mName;
-        retVal += ", Publisher : " + mPublisher;
+        retVal += ", Version : 0x" + Integer.toHexString(mAppVersion);
 
         return retVal;
     }
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 2e9eeb1..9513b9b 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -658,32 +658,40 @@
          */
         public static final int PROCESS_STATE_FOREGROUND_SERVICE = 1;
         /**
-         * Time this uid has any process that is top while the device is sleeping, but none
-         * in the "foreground service" or better state.
-         */
-        public static final int PROCESS_STATE_TOP_SLEEPING = 2;
-        /**
          * Time this uid has any process in an active foreground state, but none in the
          * "top sleeping" or better state.
          */
-        public static final int PROCESS_STATE_FOREGROUND = 3;
+        public static final int PROCESS_STATE_FOREGROUND = 2;
         /**
          * Time this uid has any process in an active background state, but none in the
          * "foreground" or better state.
          */
-        public static final int PROCESS_STATE_BACKGROUND = 4;
+        public static final int PROCESS_STATE_BACKGROUND = 3;
+        /**
+         * Time this uid has any process that is top while the device is sleeping, but not
+         * active for any other reason.  We kind-of consider it a kind of cached process
+         * for execution restrictions.
+         */
+        public static final int PROCESS_STATE_TOP_SLEEPING = 4;
+        /**
+         * Time this uid has any process that is in the background but it has an activity
+         * marked as "can't save state".  This is essentially a cached process, though the
+         * system will try much harder than normal to avoid killing it.
+         */
+        public static final int PROCESS_STATE_HEAVY_WEIGHT = 5;
         /**
          * Time this uid has any processes that are sitting around cached, not in one of the
          * other active states.
          */
-        public static final int PROCESS_STATE_CACHED = 5;
+        public static final int PROCESS_STATE_CACHED = 6;
         /**
          * Total number of process states we track.
          */
-        public static final int NUM_PROCESS_STATE = 6;
+        public static final int NUM_PROCESS_STATE = 7;
 
         static final String[] PROCESS_STATE_NAMES = {
-            "Top", "Fg Service", "Top Sleeping", "Foreground", "Background", "Cached"
+                "Top", "Fg Service", "Foreground", "Background", "Top Sleeping", "Heavy Weight",
+                "Cached"
         };
 
         public abstract long getProcessStateTime(int state, long elapsedRealtimeUs, int which);
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 ecec448..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,24 +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;
     }
 
-    /** @hide */
-    public WorkSource stripNames() {
-        if (mNum <= 0) {
-            return new WorkSource();
+    /**
+     * Create a new {@code WorkChain} associated with this WorkSource and return it.
+     *
+     * @hide
+     */
+    public WorkChain createWorkChain() {
+        if (mChains == null) {
+            mChains = new ArrayList<>(4);
         }
-        WorkSource result = new WorkSource();
-        int lastUid = -1;
-        for (int i=0; i<mNum; i++) {
-            int uid = mUids[i];
-            if (i == 0 || lastUid != uid) {
-                result.add(uid);
-            }
-        }
-        return result;
+
+        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) {
@@ -664,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;
@@ -674,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
@@ -690,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/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/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java
index 71e039a..6bca37a 100644
--- a/core/java/android/text/DynamicLayout.java
+++ b/core/java/android/text/DynamicLayout.java
@@ -42,8 +42,7 @@
  * {@link android.graphics.Canvas#drawText(java.lang.CharSequence, int, int, float, float, android.graphics.Paint)
  *  Canvas.drawText()} directly.</p>
  */
-public class DynamicLayout extends Layout
-{
+public class DynamicLayout extends Layout {
     private static final int PRIORITY = 128;
     private static final int BLOCK_MINIMUM_CHARACTER_LENGTH = 400;
 
@@ -303,8 +302,9 @@
     }
 
     /**
-     * Make a layout for the specified text that will be updated as the text is changed.
+     * @deprecated Use {@link Builder} instead.
      */
+    @Deprecated
     public DynamicLayout(@NonNull CharSequence base,
                          @NonNull TextPaint paint,
                          @IntRange(from = 0) int width, @NonNull Alignment align,
@@ -315,9 +315,9 @@
     }
 
     /**
-     * Make a layout for the transformed text (password transformation being the primary example of
-     * a transformation) that will be updated as the base text is changed.
+     * @deprecated Use {@link Builder} instead.
      */
+    @Deprecated
     public DynamicLayout(@NonNull CharSequence base, @NonNull CharSequence display,
                          @NonNull TextPaint paint,
                          @IntRange(from = 0) int width, @NonNull Alignment align,
@@ -328,10 +328,9 @@
     }
 
     /**
-     * Make a layout for the transformed text (password transformation being the primary example of
-     * a transformation) that will be updated as the base text is changed. If ellipsize is non-null,
-     * the Layout will ellipsize the text down to ellipsizedWidth.
+     * @deprecated Use {@link Builder} instead.
      */
+    @Deprecated
     public DynamicLayout(@NonNull CharSequence base, @NonNull CharSequence display,
                          @NonNull TextPaint paint,
                          @IntRange(from = 0) int width, @NonNull Alignment align,
@@ -351,7 +350,9 @@
      * the Layout will ellipsize the text down to ellipsizedWidth.
      *
      * @hide
+     * @deprecated Use {@link Builder} instead.
      */
+    @Deprecated
     public DynamicLayout(@NonNull CharSequence base, @NonNull CharSequence display,
                          @NonNull TextPaint paint,
                          @IntRange(from = 0) int width,
diff --git a/core/java/android/text/OWNERS b/core/java/android/text/OWNERS
new file mode 100644
index 0000000..0f85e1f
--- /dev/null
+++ b/core/java/android/text/OWNERS
@@ -0,0 +1,4 @@
+siyamed@google.com
+nona@google.com
+clarabayarri@google.com
+toki@google.com
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 2e10fe8d..d69b119 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -454,6 +454,10 @@
         private static final SynchronizedPool<Builder> sPool = new SynchronizedPool<>(3);
     }
 
+    /**
+     * @deprecated Use {@link Builder} instead.
+     */
+    @Deprecated
     public StaticLayout(CharSequence source, TextPaint paint,
                         int width,
                         Alignment align, float spacingmult, float spacingadd,
@@ -463,16 +467,9 @@
     }
 
     /**
-     * @hide
+     * @deprecated Use {@link Builder} instead.
      */
-    public StaticLayout(CharSequence source, TextPaint paint,
-            int width, Alignment align, TextDirectionHeuristic textDir,
-            float spacingmult, float spacingadd,
-            boolean includepad) {
-        this(source, 0, source.length(), paint, width, align, textDir,
-                spacingmult, spacingadd, includepad);
-    }
-
+    @Deprecated
     public StaticLayout(CharSequence source, int bufstart, int bufend,
                         TextPaint paint, int outerwidth,
                         Alignment align,
@@ -483,17 +480,9 @@
     }
 
     /**
-     * @hide
+     * @deprecated Use {@link Builder} instead.
      */
-    public StaticLayout(CharSequence source, int bufstart, int bufend,
-            TextPaint paint, int outerwidth,
-            Alignment align, TextDirectionHeuristic textDir,
-            float spacingmult, float spacingadd,
-            boolean includepad) {
-        this(source, bufstart, bufend, paint, outerwidth, align, textDir,
-                spacingmult, spacingadd, includepad, null, 0, Integer.MAX_VALUE);
-}
-
+    @Deprecated
     public StaticLayout(CharSequence source, int bufstart, int bufend,
             TextPaint paint, int outerwidth,
             Alignment align,
@@ -507,7 +496,9 @@
 
     /**
      * @hide
+     * @deprecated Use {@link Builder} instead.
      */
+    @Deprecated
     public StaticLayout(CharSequence source, int bufstart, int bufend,
                         TextPaint paint, int outerwidth,
                         Alignment align, TextDirectionHeuristic textDir,
@@ -565,6 +556,9 @@
         Builder.recycle(b);
     }
 
+    /**
+     * Used by DynamicLayout.
+     */
     /* package */ StaticLayout(@Nullable CharSequence text) {
         super(text, null, 0, null, 0, 0);
 
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..81b065c 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;
     }
@@ -1139,10 +1139,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 +1744,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/widget/OWNERS b/core/java/android/widget/OWNERS
new file mode 100644
index 0000000..8f0d02f
--- /dev/null
+++ b/core/java/android/widget/OWNERS
@@ -0,0 +1,12 @@
+per-file TextView.java = siyamed@google.com
+per-file TextView.java = nona@google.com
+per-file TextView.java = clarabayarri@google.com
+per-file TextView.java = toki@google.com
+per-file EditText.java = siyamed@google.com
+per-file EditText.java = nona@google.com
+per-file EditText.java = clarabayarri@google.com
+per-file EditText.java = toki@google.com
+per-file Editor.java = siyamed@google.com
+per-file Editor.java = nona@google.com
+per-file Editor.java = clarabayarri@google.com
+per-file Editor.java = toki@google.com
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/app/procstats/ProcessState.java b/core/java/com/android/internal/app/procstats/ProcessState.java
index 3aca798..efc9c02 100644
--- a/core/java/com/android/internal/app/procstats/ProcessState.java
+++ b/core/java/com/android/internal/app/procstats/ProcessState.java
@@ -91,13 +91,13 @@
         STATE_TOP,                      // ActivityManager.PROCESS_STATE_TOP
         STATE_IMPORTANT_FOREGROUND,     // ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
         STATE_IMPORTANT_FOREGROUND,     // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
-        STATE_TOP,                      // ActivityManager.PROCESS_STATE_TOP_SLEEPING
         STATE_IMPORTANT_FOREGROUND,     // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
         STATE_IMPORTANT_BACKGROUND,     // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
         STATE_IMPORTANT_BACKGROUND,     // ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND
         STATE_BACKUP,                   // ActivityManager.PROCESS_STATE_BACKUP
         STATE_SERVICE,                  // ActivityManager.PROCESS_STATE_SERVICE
         STATE_RECEIVER,                 // ActivityManager.PROCESS_STATE_RECEIVER
+        STATE_TOP,                      // ActivityManager.PROCESS_STATE_TOP_SLEEPING
         STATE_HEAVY_WEIGHT,             // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT
         STATE_HOME,                     // ActivityManager.PROCESS_STATE_HOME
         STATE_LAST_ACTIVITY,            // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index bef247f..6510a70 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -120,7 +120,7 @@
     private static final int MAGIC = 0xBA757475; // 'BATSTATS'
 
     // Current on-disk Parcel version
-    private static final int VERSION = 170 + (USE_OLD_HISTORY ? 1000 : 0);
+    private static final int VERSION = 171 + (USE_OLD_HISTORY ? 1000 : 0);
 
     // Maximum number of items we will record in the history.
     private static final int MAX_HISTORY_ITEMS;
@@ -8678,13 +8678,15 @@
             } 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_TOP_SLEEPING) {
-                uidRunningState = PROCESS_STATE_TOP_SLEEPING;
             } 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;
             }
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/jni/Android.bp b/core/jni/Android.bp
index 8df0fb8..297bae2 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -123,6 +123,7 @@
         "android_graphics_Picture.cpp",
         "android/graphics/Bitmap.cpp",
         "android/graphics/BitmapFactory.cpp",
+        "android/graphics/ByteBufferStreamAdaptor.cpp",
         "android/graphics/Camera.cpp",
         "android/graphics/CanvasProperty.cpp",
         "android/graphics/ColorFilter.cpp",
@@ -134,6 +135,7 @@
         "android/graphics/GraphicBuffer.cpp",
         "android/graphics/Graphics.cpp",
         "android/graphics/HarfBuzzNGFaceSkia.cpp",
+        "android/graphics/ImageDecoder.cpp",
         "android/graphics/Interpolator.cpp",
         "android/graphics/MaskFilter.cpp",
         "android/graphics/Matrix.cpp",
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 9e907bd..b5d1868 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -57,10 +57,12 @@
 extern int register_android_graphics_Bitmap(JNIEnv*);
 extern int register_android_graphics_BitmapFactory(JNIEnv*);
 extern int register_android_graphics_BitmapRegionDecoder(JNIEnv*);
+extern int register_android_graphics_ByteBufferStreamAdaptor(JNIEnv* env);
 extern int register_android_graphics_Camera(JNIEnv* env);
 extern int register_android_graphics_CreateJavaOutputStreamAdaptor(JNIEnv* env);
 extern int register_android_graphics_GraphicBuffer(JNIEnv* env);
 extern int register_android_graphics_Graphics(JNIEnv* env);
+extern int register_android_graphics_ImageDecoder(JNIEnv*);
 extern int register_android_graphics_Interpolator(JNIEnv* env);
 extern int register_android_graphics_MaskFilter(JNIEnv* env);
 extern int register_android_graphics_Movie(JNIEnv* env);
@@ -644,6 +646,7 @@
     char methodTraceFileBuf[sizeof("-Xmethod-trace-file:") + PROPERTY_VALUE_MAX];
     char methodTraceFileSizeBuf[sizeof("-Xmethod-trace-file-size:") + PROPERTY_VALUE_MAX];
     std::string fingerprintBuf;
+    char jdwpProviderBuf[sizeof("-XjdwpProvider:") - 1 + PROPERTY_VALUE_MAX];
 
     bool checkJni = false;
     property_get("dalvik.vm.checkjni", propBuf, "");
@@ -766,9 +769,15 @@
      * Set suspend=y to pause during VM init and use android ADB transport.
      */
     if (zygote) {
-      addOption("-agentlib:jdwp=transport=dt_android_adb,suspend=n,server=y");
+      addOption("-XjdwpOptions:suspend=n,server=y");
     }
 
+    // Set the JDWP provider. By default let the runtime choose.
+    parseRuntimeOption("dalvik.vm.jdwp-provider",
+                       jdwpProviderBuf,
+                       "-XjdwpProvider:",
+                       "default");
+
     parseRuntimeOption("dalvik.vm.lockprof.threshold",
                        lockProfThresholdBuf,
                        "-Xlockprofthreshold:");
@@ -1380,6 +1389,7 @@
     REG_JNI(register_android_graphics_Bitmap),
     REG_JNI(register_android_graphics_BitmapFactory),
     REG_JNI(register_android_graphics_BitmapRegionDecoder),
+    REG_JNI(register_android_graphics_ByteBufferStreamAdaptor),
     REG_JNI(register_android_graphics_Camera),
     REG_JNI(register_android_graphics_CreateJavaOutputStreamAdaptor),
     REG_JNI(register_android_graphics_CanvasProperty),
@@ -1387,6 +1397,7 @@
     REG_JNI(register_android_graphics_DrawFilter),
     REG_JNI(register_android_graphics_FontFamily),
     REG_JNI(register_android_graphics_GraphicBuffer),
+    REG_JNI(register_android_graphics_ImageDecoder),
     REG_JNI(register_android_graphics_Interpolator),
     REG_JNI(register_android_graphics_MaskFilter),
     REG_JNI(register_android_graphics_Matrix),
diff --git a/core/jni/android/graphics/BitmapFactory.cpp b/core/jni/android/graphics/BitmapFactory.cpp
index 2e8c27a..79aa5ac 100644
--- a/core/jni/android/graphics/BitmapFactory.cpp
+++ b/core/jni/android/graphics/BitmapFactory.cpp
@@ -47,9 +47,6 @@
 
 jfieldID gBitmap_ninePatchInsetsFieldID;
 
-jclass gInsetStruct_class;
-jmethodID gInsetStruct_constructorMethodID;
-
 jclass gBitmapConfig_class;
 jmethodID gBitmapConfig_nativeToConfigMethodID;
 
@@ -99,43 +96,6 @@
     return jstr;
 }
 
-static void scaleDivRange(int32_t* divs, int count, float scale, int maxValue) {
-    for (int i = 0; i < count; i++) {
-        divs[i] = int32_t(divs[i] * scale + 0.5f);
-        if (i > 0 && divs[i] == divs[i - 1]) {
-            divs[i]++; // avoid collisions
-        }
-    }
-
-    if (CC_UNLIKELY(divs[count - 1] > maxValue)) {
-        // if the collision avoidance above put some divs outside the bounds of the bitmap,
-        // slide outer stretchable divs inward to stay within bounds
-        int highestAvailable = maxValue;
-        for (int i = count - 1; i >= 0; i--) {
-            divs[i] = highestAvailable;
-            if (i > 0 && divs[i] <= divs[i-1]){
-                // keep shifting
-                highestAvailable = divs[i] - 1;
-            } else {
-                break;
-            }
-        }
-    }
-}
-
-static void scaleNinePatchChunk(android::Res_png_9patch* chunk, float scale,
-        int scaledWidth, int scaledHeight) {
-    chunk->paddingLeft = int(chunk->paddingLeft * scale + 0.5f);
-    chunk->paddingTop = int(chunk->paddingTop * scale + 0.5f);
-    chunk->paddingRight = int(chunk->paddingRight * scale + 0.5f);
-    chunk->paddingBottom = int(chunk->paddingBottom * scale + 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(chunk->getXDivs(), chunk->numXDivs, scale, scaledWidth - 1);
-    scaleDivRange(chunk->getYDivs(), chunk->numYDivs, scale, scaledHeight - 1);
-}
-
 class ScaleCheckingAllocator : public SkBitmap::HeapAllocator {
 public:
     ScaleCheckingAllocator(float scale, int size)
@@ -425,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) {
-            scaleNinePatchChunk(peeker.mPatch, scale, scaledWidth, scaledHeight);
+            peeker.scale(scaleX, scaleY, scaledWidth, scaledHeight);
         }
 
         size_t ninePatchArraySize = peeker.mPatch->serializedSize();
@@ -448,12 +416,7 @@
 
     jobject ninePatchInsets = NULL;
     if (peeker.mHasInsets) {
-        ninePatchInsets = env->NewObject(gInsetStruct_class, gInsetStruct_constructorMethodID,
-                peeker.mOpticalInsets[0], peeker.mOpticalInsets[1],
-                peeker.mOpticalInsets[2], peeker.mOpticalInsets[3],
-                peeker.mOutlineInsets[0], peeker.mOutlineInsets[1],
-                peeker.mOutlineInsets[2], peeker.mOutlineInsets[3],
-                peeker.mOutlineRadius, peeker.mOutlineAlpha, scale);
+        ninePatchInsets = peeker.createNinePatchInsets(env, scale);
         if (ninePatchInsets == NULL) {
             return nullObjectReturn("nine patch insets == null");
         }
@@ -464,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) {
@@ -501,20 +456,14 @@
         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);
     }
 
     if (padding) {
-        if (peeker.mPatch != NULL) {
-            GraphicsJNI::set_jrect(env, padding,
-                    peeker.mPatch->paddingLeft, peeker.mPatch->paddingTop,
-                    peeker.mPatch->paddingRight, peeker.mPatch->paddingBottom);
-        } else {
-            GraphicsJNI::set_jrect(env, padding, -1, -1, -1, -1);
-        }
+        peeker.getPadding(env, padding);
     }
 
     // If we get here, the outputBitmap should have an installed pixelref.
@@ -705,11 +654,6 @@
     gBitmap_ninePatchInsetsFieldID = GetFieldIDOrDie(env, bitmap_class, "mNinePatchInsets",
             "Landroid/graphics/NinePatch$InsetStruct;");
 
-    gInsetStruct_class = MakeGlobalRefOrDie(env, FindClassOrDie(env,
-        "android/graphics/NinePatch$InsetStruct"));
-    gInsetStruct_constructorMethodID = GetMethodIDOrDie(env, gInsetStruct_class, "<init>",
-                                                        "(IIIIIIIIFIF)V");
-
     gBitmapConfig_class = MakeGlobalRefOrDie(env, FindClassOrDie(env,
             "android/graphics/Bitmap$Config"));
     gBitmapConfig_nativeToConfigMethodID = GetStaticMethodIDOrDie(env, gBitmapConfig_class,
diff --git a/core/jni/android/graphics/ByteBufferStreamAdaptor.cpp b/core/jni/android/graphics/ByteBufferStreamAdaptor.cpp
new file mode 100644
index 0000000..115edd4
--- /dev/null
+++ b/core/jni/android/graphics/ByteBufferStreamAdaptor.cpp
@@ -0,0 +1,323 @@
+#include "ByteBufferStreamAdaptor.h"
+#include "core_jni_helpers.h"
+
+#include <SkStream.h>
+
+using namespace android;
+
+static jmethodID gByteBuffer_getMethodID;
+static jmethodID gByteBuffer_setPositionMethodID;
+
+static JNIEnv* get_env_or_die(JavaVM* jvm) {
+    JNIEnv* env;
+    if (jvm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+        LOG_ALWAYS_FATAL("Failed to get JNIEnv for JavaVM: %p", jvm);
+    }
+    return env;
+}
+
+class ByteBufferStream : public SkStreamAsset {
+private:
+    ByteBufferStream(JavaVM* jvm, jobject jbyteBuffer, size_t initialPosition, size_t length,
+                     jbyteArray storage)
+            : mJvm(jvm)
+            , mByteBuffer(jbyteBuffer)
+            , mPosition(0)
+            , mInitialPosition(initialPosition)
+            , mLength(length)
+            , mStorage(storage) {}
+
+public:
+    static ByteBufferStream* Create(JavaVM* jvm, JNIEnv* env, jobject jbyteBuffer,
+                                    size_t position, size_t length) {
+        // This object outlives its native method call.
+        jbyteBuffer = env->NewGlobalRef(jbyteBuffer);
+        if (!jbyteBuffer) {
+            return nullptr;
+        }
+
+        jbyteArray storage = env->NewByteArray(kStorageSize);
+        if (!storage) {
+            env->DeleteGlobalRef(jbyteBuffer);
+            return nullptr;
+        }
+
+        // This object outlives its native method call.
+        storage = static_cast<jbyteArray>(env->NewGlobalRef(storage));
+        if (!storage) {
+            env->DeleteGlobalRef(jbyteBuffer);
+            return nullptr;
+        }
+
+        return new ByteBufferStream(jvm, jbyteBuffer, position, length, storage);
+    }
+
+    ~ByteBufferStream() override {
+        auto* env = get_env_or_die(mJvm);
+        env->DeleteGlobalRef(mByteBuffer);
+        env->DeleteGlobalRef(mStorage);
+    }
+
+    size_t read(void* buffer, size_t size) override {
+        if (size > mLength - mPosition) {
+            size = mLength - mPosition;
+        }
+        if (!size) {
+            return 0;
+        }
+
+        if (!buffer) {
+            return this->setPosition(mPosition + size);
+        }
+
+        auto* env = get_env_or_die(mJvm);
+        size_t bytesRead = 0;
+        do {
+            const size_t requested = (size > kStorageSize) ? kStorageSize : size;
+            const jint jrequested = static_cast<jint>(requested);
+            env->CallObjectMethod(mByteBuffer, gByteBuffer_getMethodID, mStorage, 0, jrequested);
+            if (env->ExceptionCheck()) {
+                ALOGE("Error in ByteBufferStream::read - was the ByteBuffer modified externally?");
+                env->ExceptionDescribe();
+                env->ExceptionClear();
+                mPosition = mLength;
+                return bytesRead;
+            }
+
+            env->GetByteArrayRegion(mStorage, 0, requested, reinterpret_cast<jbyte*>(buffer));
+            if (env->ExceptionCheck()) {
+                ALOGE("Internal error in ByteBufferStream::read");
+                env->ExceptionDescribe();
+                env->ExceptionClear();
+                mPosition = mLength;
+                return bytesRead;
+            }
+
+            mPosition += requested;
+            buffer = reinterpret_cast<void*>(reinterpret_cast<char*>(buffer) + requested);
+            bytesRead += requested;
+            size -= requested;
+        } while (size);
+        return bytesRead;
+    }
+
+    bool isAtEnd() const override { return mLength == mPosition; }
+
+    // SkStreamRewindable overrides
+    bool rewind() override { return this->setPosition(0); }
+
+    SkStreamAsset* onDuplicate() const override {
+        // SkStreamRewindable requires overriding this, but it is not called by
+        // decoders, so does not need a true implementation. A proper
+        // implementation would require duplicating the ByteBuffer, which has
+        // its own internal position state.
+        return nullptr;
+    }
+
+    // SkStreamSeekable overrides
+    size_t getPosition() const override { return mPosition; }
+
+    bool seek(size_t position) override {
+        return this->setPosition(position > mLength ? mLength : position);
+    }
+
+    bool move(long offset) override {
+        long newPosition = mPosition + offset;
+        if (newPosition < 0) {
+            return this->setPosition(0);
+        }
+        return this->seek(static_cast<size_t>(newPosition));
+    }
+
+    SkStreamAsset* onFork() const override {
+        // SkStreamSeekable requires overriding this, but it is not called by
+        // decoders, so does not need a true implementation. A proper
+        // implementation would require duplicating the ByteBuffer, which has
+        // its own internal position state.
+        return nullptr;
+    }
+
+    // SkStreamAsset overrides
+    size_t getLength() const override { return mLength; }
+
+private:
+    JavaVM*          mJvm;
+    jobject          mByteBuffer;
+    // Logical position of the SkStream, between 0 and mLength.
+    size_t           mPosition;
+    // Initial position of mByteBuffer, treated as mPosition 0.
+    const size_t     mInitialPosition;
+    // Logical length of the SkStream, from mInitialPosition to
+    // mByteBuffer.limit().
+    const size_t     mLength;
+
+    // Range has already been checked by the caller.
+    bool setPosition(size_t newPosition) {
+        auto* env = get_env_or_die(mJvm);
+        env->CallObjectMethod(mByteBuffer, gByteBuffer_setPositionMethodID,
+                              newPosition + mInitialPosition);
+        if (env->ExceptionCheck()) {
+            ALOGE("Internal error in ByteBufferStream::setPosition");
+            env->ExceptionDescribe();
+            env->ExceptionClear();
+            mPosition = mLength;
+            return false;
+        }
+        mPosition = newPosition;
+        return true;
+    }
+
+    // FIXME: This is an arbitrary storage size, which should be plenty for
+    // some formats (png, gif, many bmps). But for jpeg, the more we can supply
+    // in one call the better, and webp really wants all of the data. How to
+    // best choose the amount of storage used?
+    static constexpr size_t kStorageSize = 4096;
+    jbyteArray mStorage;
+};
+
+class ByteArrayStream : public SkStreamAsset {
+private:
+    ByteArrayStream(JavaVM* jvm, jbyteArray jarray, size_t offset, size_t length)
+            : mJvm(jvm), mByteArray(jarray), mOffset(offset), mPosition(0), mLength(length) {}
+
+public:
+    static ByteArrayStream* Create(JavaVM* jvm, JNIEnv* env, jbyteArray jarray, size_t offset,
+                                   size_t length) {
+        // This object outlives its native method call.
+        jarray = static_cast<jbyteArray>(env->NewGlobalRef(jarray));
+        if (!jarray) {
+            return nullptr;
+        }
+        return new ByteArrayStream(jvm, jarray, offset, length);
+    }
+
+    ~ByteArrayStream() override {
+        auto* env = get_env_or_die(mJvm);
+        env->DeleteGlobalRef(mByteArray);
+    }
+
+    size_t read(void* buffer, size_t size) override {
+        if (size > mLength - mPosition) {
+            size = mLength - mPosition;
+        }
+        if (!size) {
+            return 0;
+        }
+
+        auto* env = get_env_or_die(mJvm);
+        if (buffer) {
+            env->GetByteArrayRegion(mByteArray, mPosition + mOffset, size,
+                                    reinterpret_cast<jbyte*>(buffer));
+            if (env->ExceptionCheck()) {
+                ALOGE("Internal error in ByteArrayStream::read");
+                env->ExceptionDescribe();
+                env->ExceptionClear();
+                mPosition = mLength;
+                return 0;
+            }
+        }
+
+        mPosition += size;
+        return size;
+    }
+
+    bool isAtEnd() const override { return mLength == mPosition; }
+
+    // SkStreamRewindable overrides
+    bool rewind() override {
+        mPosition = 0;
+        return true;
+    }
+    SkStreamAsset* onDuplicate() const override {
+        // SkStreamRewindable requires overriding this, but it is not called by
+        // decoders, so does not need a true implementation. Note that a proper
+        // implementation is fairly straightforward
+        return nullptr;
+    }
+
+    // SkStreamSeekable overrides
+    size_t getPosition() const override { return mPosition; }
+
+    bool seek(size_t position) override {
+        mPosition = (position > mLength) ? mLength : position;
+        return true;
+    }
+
+    bool move(long offset) override {
+        long newPosition = mPosition + offset;
+        if (newPosition < 0) {
+            return this->seek(0);
+        }
+        return this->seek(static_cast<size_t>(newPosition));
+    }
+
+    SkStreamAsset* onFork() const override {
+        // SkStreamSeekable requires overriding this, but it is not called by
+        // decoders, so does not need a true implementation. Note that a proper
+        // implementation is fairly straightforward
+        return nullptr;
+    }
+
+    // SkStreamAsset overrides
+    size_t getLength() const override { return mLength; }
+
+private:
+    JavaVM*      mJvm;
+    jbyteArray   mByteArray;
+    // Offset in mByteArray. Only used when communicating with Java.
+    const size_t mOffset;
+    // Logical position of the SkStream, between 0 and mLength.
+    size_t       mPosition;
+    const size_t mLength;
+};
+
+struct release_proc_context {
+    JavaVM* jvm;
+    jobject jbyteBuffer;
+};
+
+std::unique_ptr<SkStream> CreateByteBufferStreamAdaptor(JNIEnv* env, jobject jbyteBuffer,
+                                                        size_t position, size_t limit) {
+    JavaVM* jvm;
+    LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&jvm) != JNI_OK);
+
+    const size_t length = limit - position;
+    void* addr = env->GetDirectBufferAddress(jbyteBuffer);
+    if (addr) {
+        addr = reinterpret_cast<void*>(reinterpret_cast<char*>(addr) + position);
+        jbyteBuffer = env->NewGlobalRef(jbyteBuffer);
+        if (!jbyteBuffer) {
+            return nullptr;
+        }
+
+        auto* context = new release_proc_context{jvm, jbyteBuffer};
+        auto releaseProc = [](const void*, void* context) {
+            auto* c = reinterpret_cast<release_proc_context*>(context);
+            JNIEnv* env = get_env_or_die(c->jvm);
+            env->DeleteGlobalRef(c->jbyteBuffer);
+            delete c;
+        };
+        auto data = SkData::MakeWithProc(addr, length, releaseProc, context);
+        // The new SkMemoryStream will read directly from addr.
+        return std::unique_ptr<SkStream>(new SkMemoryStream(std::move(data)));
+    }
+
+    // Non-direct, or direct access is not supported.
+    return std::unique_ptr<SkStream>(ByteBufferStream::Create(jvm, env, jbyteBuffer, position,
+                                                              length));
+}
+
+std::unique_ptr<SkStream> CreateByteArrayStreamAdaptor(JNIEnv* env, jbyteArray array, size_t offset,
+                                                       size_t length) {
+    JavaVM* jvm;
+    LOG_ALWAYS_FATAL_IF(env->GetJavaVM(&jvm) != JNI_OK);
+
+    return std::unique_ptr<SkStream>(ByteArrayStream::Create(jvm, env, array, offset, length));
+}
+
+int register_android_graphics_ByteBufferStreamAdaptor(JNIEnv* env) {
+    jclass byteBuffer_class = FindClassOrDie(env, "java/nio/ByteBuffer");
+    gByteBuffer_getMethodID         = GetMethodIDOrDie(env, byteBuffer_class, "get", "([BII)Ljava/nio/ByteBuffer;");
+    gByteBuffer_setPositionMethodID = GetMethodIDOrDie(env, byteBuffer_class, "position", "(I)Ljava/nio/Buffer;");
+    return true;
+}
diff --git a/core/jni/android/graphics/ByteBufferStreamAdaptor.h b/core/jni/android/graphics/ByteBufferStreamAdaptor.h
new file mode 100644
index 0000000..367a48f
--- /dev/null
+++ b/core/jni/android/graphics/ByteBufferStreamAdaptor.h
@@ -0,0 +1,37 @@
+#ifndef _ANDROID_GRAPHICS_BYTE_BUFFER_STREAM_ADAPTOR_H_
+#define _ANDROID_GRAPHICS_BYTE_BUFFER_STREAM_ADAPTOR_H_
+
+#include <jni.h>
+#include <memory>
+
+class SkStream;
+
+/**
+ * Create an adaptor for treating a java.nio.ByteBuffer as an SkStream.
+ *
+ * This will special case direct ByteBuffers, but not the case where a byte[]
+ * can be used directly. For that, use CreateByteArrayStreamAdaptor.
+ *
+ * @param jbyteBuffer corresponding to the java ByteBuffer. This method will
+ *      add a global ref.
+ * @param initialPosition returned by ByteBuffer.position(). Decoding starts
+ *      from here.
+ * @param limit returned by ByteBuffer.limit().
+ *
+ * Returns null on failure.
+ */
+std::unique_ptr<SkStream> CreateByteBufferStreamAdaptor(JNIEnv*, jobject jbyteBuffer,
+                                                        size_t initialPosition, size_t limit);
+
+/**
+ * Create an adaptor for treating a Java byte[] as an SkStream.
+ *
+ * @param offset into the byte[] of the beginning of the data to use.
+ * @param length of data to use, starting from offset.
+ *
+ * Returns null on failure.
+ */
+std::unique_ptr<SkStream> CreateByteArrayStreamAdaptor(JNIEnv*, jbyteArray array, size_t offset,
+                                                       size_t length);
+
+#endif  // _ANDROID_GRAPHICS_BYTE_BUFFER_STREAM_ADAPTOR_H_
diff --git a/core/jni/android/graphics/ImageDecoder.cpp b/core/jni/android/graphics/ImageDecoder.cpp
new file mode 100644
index 0000000..bacab2a
--- /dev/null
+++ b/core/jni/android/graphics/ImageDecoder.cpp
@@ -0,0 +1,473 @@
+/*
+ * 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.
+ */
+
+#include "Bitmap.h"
+#include "ByteBufferStreamAdaptor.h"
+#include "GraphicsJNI.h"
+#include "NinePatchPeeker.h"
+#include "Utils.h"
+#include "core_jni_helpers.h"
+
+#include <hwui/Bitmap.h>
+#include <hwui/Canvas.h>
+
+#include <SkAndroidCodec.h>
+#include <SkEncodedImageFormat.h>
+#include <SkStream.h>
+
+#include <androidfw/Asset.h>
+#include <jni.h>
+
+using namespace android;
+
+static jclass    gImageDecoder_class;
+static jclass    gPoint_class;
+static jclass    gIncomplete_class;
+static jclass    gCorrupt_class;
+static jclass    gCanvas_class;
+static jmethodID gImageDecoder_constructorMethodID;
+static jmethodID gPoint_constructorMethodID;
+static jmethodID gIncomplete_constructorMethodID;
+static jmethodID gCorrupt_constructorMethodID;
+static jmethodID gCallback_onExceptionMethodID;
+static jmethodID gPostProcess_postProcessMethodID;
+static jmethodID gCanvas_constructorMethodID;
+static jmethodID gCanvas_releaseMethodID;
+
+struct ImageDecoder {
+    // These need to stay in sync with ImageDecoder.java's Allocator constants.
+    enum Allocator {
+        kDefault_Allocator      = 0,
+        kSoftware_Allocator     = 1,
+        kSharedMemory_Allocator = 2,
+        kHardware_Allocator     = 3,
+    };
+
+    // These need to stay in sync with PixelFormat.java's Format constants.
+    enum PixelFormat {
+        kUnknown     =  0,
+        kTranslucent = -3,
+        kOpaque      = -1,
+    };
+
+    std::unique_ptr<SkAndroidCodec> mCodec;
+    NinePatchPeeker mPeeker;
+};
+
+static jobject native_create(JNIEnv* env, std::unique_ptr<SkStream> stream) {
+    if (!stream.get()) {
+        return nullObjectReturn("Failed to create a stream");
+    }
+    std::unique_ptr<ImageDecoder> decoder(new ImageDecoder);
+    decoder->mCodec = SkAndroidCodec::MakeFromStream(std::move(stream), &decoder->mPeeker);
+    if (!decoder->mCodec.get()) {
+        // FIXME: Add an error code to SkAndroidCodec::MakeFromStream, like
+        // SkCodec? Then this can print a more informative error message.
+        // (Or we can print one from within SkCodec.)
+        ALOGE("Failed to create an SkCodec");
+        return nullptr;
+    }
+
+    const auto& info = decoder->mCodec->getInfo();
+    const int width = info.width();
+    const int height = info.height();
+    return env->NewObject(gImageDecoder_class, gImageDecoder_constructorMethodID,
+                          reinterpret_cast<jlong>(decoder.release()), width, height);
+}
+
+static jobject ImageDecoder_nCreate(JNIEnv* env, jobject /*clazz*/, jlong assetPtr) {
+    Asset* asset = reinterpret_cast<Asset*>(assetPtr);
+    std::unique_ptr<SkStream> stream(new AssetStreamAdaptor(asset));
+    return native_create(env, std::move(stream));
+}
+
+static jobject ImageDecoder_nCreateByteBuffer(JNIEnv* env, jobject /*clazz*/, jobject jbyteBuffer,
+                                              jint initialPosition, jint limit) {
+    std::unique_ptr<SkStream> stream = CreateByteBufferStreamAdaptor(env, jbyteBuffer,
+                                                                     initialPosition, limit);
+    if (!stream) {
+        return nullptr;
+    }
+    return native_create(env, std::move(stream));
+}
+
+static jobject ImageDecoder_nCreateByteArray(JNIEnv* env, jobject /*clazz*/, jbyteArray byteArray,
+                                             jint offset, jint length) {
+    std::unique_ptr<SkStream> stream(CreateByteArrayStreamAdaptor(env, byteArray, offset, length));
+    return native_create(env, std::move(stream));
+}
+
+static bool supports_any_down_scale(const SkAndroidCodec* codec) {
+    return codec->getEncodedFormat() == SkEncodedImageFormat::kWEBP;
+}
+
+static jobject ImageDecoder_nDecodeBitmap(JNIEnv* env, jobject /*clazz*/, jlong nativePtr,
+                                          jobject jcallback, jobject jpostProcess,
+                                          jint desiredWidth, jint desiredHeight, jobject jsubset,
+                                          jboolean requireMutable, jint allocator,
+                                          jboolean requireUnpremul, jboolean preferRamOverQuality,
+                                          jboolean asAlphaMask) {
+    auto* decoder = reinterpret_cast<ImageDecoder*>(nativePtr);
+    SkAndroidCodec* codec = decoder->mCodec.get();
+    SkImageInfo decodeInfo = codec->getInfo();
+    bool scale = false;
+    int sampleSize = 1;
+    if (desiredWidth != decodeInfo.width() || desiredHeight != decodeInfo.height()) {
+        bool match = false;
+        if (desiredWidth < decodeInfo.width() && desiredHeight < decodeInfo.height()) {
+            if (supports_any_down_scale(codec)) {
+                match = true;
+                decodeInfo = decodeInfo.makeWH(desiredWidth, desiredHeight);
+            } else {
+                int sampleX = decodeInfo.width()  / desiredWidth;
+                int sampleY = decodeInfo.height() / desiredHeight;
+                sampleSize = std::min(sampleX, sampleY);
+                SkISize sampledSize = codec->getSampledDimensions(sampleSize);
+                decodeInfo = decodeInfo.makeWH(sampledSize.width(), sampledSize.height());
+                if (decodeInfo.width() == desiredWidth && decodeInfo.height() == desiredHeight) {
+                    match = true;
+                }
+            }
+        }
+        if (!match) {
+            scale = true;
+            if (requireUnpremul && kOpaque_SkAlphaType != decodeInfo.alphaType()) {
+                doThrowISE(env, "Cannot scale unpremultiplied pixels!");
+                return nullptr;
+            }
+        }
+    }
+
+    switch (decodeInfo.alphaType()) {
+        case kUnpremul_SkAlphaType:
+            if (!requireUnpremul) {
+                decodeInfo = decodeInfo.makeAlphaType(kPremul_SkAlphaType);
+            }
+            break;
+        case kPremul_SkAlphaType:
+            if (requireUnpremul) {
+                decodeInfo = decodeInfo.makeAlphaType(kUnpremul_SkAlphaType);
+            }
+            break;
+        case kOpaque_SkAlphaType:
+            break;
+        case kUnknown_SkAlphaType:
+            return nullObjectReturn("Unknown alpha type");
+    }
+
+    SkColorType colorType = kN32_SkColorType;
+    if (asAlphaMask && decodeInfo.colorType() == kGray_8_SkColorType) {
+        // We have to trick Skia to decode this to a single channel.
+        colorType = kGray_8_SkColorType;
+    } else if (preferRamOverQuality) {
+        // FIXME: The post-process might add alpha, which would make a 565
+        // result incorrect. If we call the postProcess before now and record
+        // to a picture, we can know whether alpha was added, and if not, we
+        // can still use 565.
+        if (decodeInfo.alphaType() == kOpaque_SkAlphaType && !jpostProcess) {
+            // If the final result will be hardware, decoding to 565 and then
+            // uploading to the gpu as 8888 will not save memory. This still
+            // may save us from using F16, but do not go down to 565.
+            if (allocator != ImageDecoder::kHardware_Allocator &&
+               (allocator != ImageDecoder::kDefault_Allocator || requireMutable)) {
+                colorType = kRGB_565_SkColorType;
+            }
+        }
+        // Otherwise, stick with N32
+    } else {
+        // This is currently the only way to know that we should decode to F16.
+        colorType = codec->computeOutputColorType(colorType);
+    }
+    sk_sp<SkColorSpace> colorSpace = codec->computeOutputColorSpace(colorType);
+    decodeInfo = decodeInfo.makeColorType(colorType).makeColorSpace(colorSpace);
+
+    SkBitmap bm;
+    auto bitmapInfo = decodeInfo;
+    if (asAlphaMask && colorType == kGray_8_SkColorType) {
+        bitmapInfo = bitmapInfo.makeColorType(kAlpha_8_SkColorType);
+    }
+    if (!bm.setInfo(bitmapInfo)) {
+        return nullObjectReturn("Failed to setInfo properly");
+    }
+
+    sk_sp<Bitmap> nativeBitmap;
+    // If we are going to scale or subset, we will create a new bitmap later on,
+    // so use the heap for the temporary.
+    // FIXME: Use scanline decoding on only a couple lines to save memory. b/70709380.
+    if (allocator == ImageDecoder::kSharedMemory_Allocator && !scale && !jsubset) {
+        nativeBitmap = Bitmap::allocateAshmemBitmap(&bm);
+    } else {
+        nativeBitmap = Bitmap::allocateHeapBitmap(&bm);
+    }
+    if (!nativeBitmap) {
+        ALOGE("OOM allocating Bitmap with dimensions %i x %i",
+              decodeInfo.width(), decodeInfo.height());
+        doThrowOOME(env);
+        return nullptr;
+    }
+
+    jobject jexception = nullptr;
+    SkAndroidCodec::AndroidOptions options;
+    options.fSampleSize = sampleSize;
+    auto result = codec->getAndroidPixels(decodeInfo, bm.getPixels(), bm.rowBytes(), &options);
+    switch (result) {
+        case SkCodec::kSuccess:
+            break;
+        case SkCodec::kIncompleteInput:
+            if (jcallback) {
+                jexception = env->NewObject(gIncomplete_class, gIncomplete_constructorMethodID);
+            }
+            break;
+        case SkCodec::kErrorInInput:
+            if (jcallback) {
+                jexception = env->NewObject(gCorrupt_class, gCorrupt_constructorMethodID);
+            }
+            break;
+        default:
+            ALOGE("getPixels failed with error %i", result);
+            return nullptr;
+    }
+
+    if (jexception) {
+        if (!env->CallBooleanMethod(jcallback, gCallback_onExceptionMethodID, jexception) ||
+            env->ExceptionCheck()) {
+            return nullptr;
+        }
+    }
+
+    float scaleX = 1.0f;
+    float scaleY = 1.0f;
+    if (scale) {
+        scaleX = (float) desiredWidth  / decodeInfo.width();
+        scaleY = (float) desiredHeight / decodeInfo.height();
+    }
+
+    jbyteArray ninePatchChunk = nullptr;
+    jobject ninePatchInsets = nullptr;
+
+    // Ignore ninepatch when post-processing.
+    if (!jpostProcess) {
+        // FIXME: Share more code with BitmapFactory.cpp.
+        if (decoder->mPeeker.mPatch != nullptr) {
+            if (scale) {
+                decoder->mPeeker.scale(scaleX, scaleY, desiredWidth, desiredHeight);
+            }
+            size_t ninePatchArraySize = decoder->mPeeker.mPatch->serializedSize();
+            ninePatchChunk = env->NewByteArray(ninePatchArraySize);
+            if (ninePatchChunk == nullptr) {
+                return nullObjectReturn("ninePatchChunk == null");
+            }
+
+            env->SetByteArrayRegion(ninePatchChunk, 0, decoder->mPeeker.mPatchSize,
+                                    reinterpret_cast<jbyte*>(decoder->mPeeker.mPatch));
+        }
+
+        if (decoder->mPeeker.mHasInsets) {
+            ninePatchInsets = decoder->mPeeker.createNinePatchInsets(env, 1.0f);
+            if (ninePatchInsets == nullptr) {
+                return nullObjectReturn("nine patch insets == null");
+            }
+        }
+    }
+
+    if (scale || jsubset) {
+        int translateX = 0;
+        int translateY = 0;
+        if (jsubset) {
+            SkIRect subset;
+            GraphicsJNI::jrect_to_irect(env, jsubset, &subset);
+
+            // FIXME: If there is no scale, should this instead call
+            // SkBitmap::extractSubset? If we could upload a subset
+            // (b/70626068), this would save memory and time. Even for a
+            // software Bitmap, the extra speed might be worth the memory
+            // tradeoff if the subset is large?
+            translateX    = -subset.fLeft;
+            translateY    = -subset.fTop;
+            desiredWidth  =  subset.width();
+            desiredHeight =  subset.height();
+        }
+        SkImageInfo scaledInfo = bitmapInfo.makeWH(desiredWidth, desiredHeight);
+        SkBitmap scaledBm;
+        if (!scaledBm.setInfo(scaledInfo)) {
+            nullObjectReturn("Failed scaled setInfo");
+        }
+
+        sk_sp<Bitmap> scaledPixelRef;
+        if (allocator == ImageDecoder::kSharedMemory_Allocator) {
+            scaledPixelRef = Bitmap::allocateAshmemBitmap(&scaledBm);
+        } else {
+            scaledPixelRef = Bitmap::allocateHeapBitmap(&scaledBm);
+        }
+        if (!scaledPixelRef) {
+            ALOGE("OOM allocating scaled Bitmap with dimensions %i x %i",
+                  desiredWidth, desiredHeight);
+            doThrowOOME(env);
+            return nullptr;
+        }
+
+        SkPaint paint;
+        paint.setBlendMode(SkBlendMode::kSrc);
+        paint.setFilterQuality(kLow_SkFilterQuality);  // bilinear filtering
+
+        SkCanvas canvas(scaledBm, SkCanvas::ColorBehavior::kLegacy);
+        canvas.translate(translateX, translateY);
+        canvas.scale(scaleX, scaleY);
+        canvas.drawBitmap(bm, 0.0f, 0.0f, &paint);
+
+        bm.swap(scaledBm);
+        nativeBitmap = scaledPixelRef;
+    }
+
+    if (jpostProcess) {
+        std::unique_ptr<Canvas> canvas(Canvas::create_canvas(bm));
+        if (!canvas) {
+            return nullObjectReturn("Failed to create Canvas for PostProcess!");
+        }
+        jobject jcanvas = env->NewObject(gCanvas_class, gCanvas_constructorMethodID,
+                                         reinterpret_cast<jlong>(canvas.get()));
+        if (!jcanvas) {
+            return nullObjectReturn("Failed to create Java Canvas for PostProcess!");
+        }
+        // jcanvas will now own canvas.
+        canvas.release();
+
+        jint pixelFormat = env->CallIntMethod(jpostProcess, gPostProcess_postProcessMethodID,
+                                              jcanvas, bm.width(), bm.height());
+        if (env->ExceptionCheck()) {
+            return nullptr;
+        }
+
+        // The Canvas objects are no longer needed, and will not remain valid.
+        env->CallVoidMethod(jcanvas, gCanvas_releaseMethodID);
+        if (env->ExceptionCheck()) {
+            return nullptr;
+        }
+
+        SkAlphaType newAlphaType = bm.alphaType();
+        switch (pixelFormat) {
+            case ImageDecoder::kUnknown:
+                break;
+            case ImageDecoder::kTranslucent:
+                newAlphaType = kPremul_SkAlphaType;
+                break;
+            case ImageDecoder::kOpaque:
+                newAlphaType = kOpaque_SkAlphaType;
+                break;
+            default:
+                ALOGE("invalid return from postProcess: %i", pixelFormat);
+                doThrowIAE(env);
+                return nullptr;
+        }
+
+        if (newAlphaType != bm.alphaType()) {
+            if (!bm.setAlphaType(newAlphaType)) {
+                ALOGE("incompatible return from postProcess: %i", pixelFormat);
+                doThrowIAE(env);
+                return nullptr;
+            }
+            nativeBitmap->setAlphaType(newAlphaType);
+        }
+    }
+
+    int bitmapCreateFlags = 0x0;
+    if (!requireUnpremul) {
+        // Even if the image is opaque, setting this flag means that
+        // if alpha is added (e.g. by PostProcess), it will be marked as
+        // premultiplied.
+        bitmapCreateFlags |= bitmap::kBitmapCreateFlag_Premultiplied;
+    }
+
+    if (requireMutable) {
+        bitmapCreateFlags |= bitmap::kBitmapCreateFlag_Mutable;
+    } else {
+        if ((allocator == ImageDecoder::kDefault_Allocator ||
+             allocator == ImageDecoder::kHardware_Allocator)
+            && bm.colorType() != kAlpha_8_SkColorType)
+        {
+            sk_sp<Bitmap> hwBitmap = Bitmap::allocateHardwareBitmap(bm);
+            if (hwBitmap) {
+                hwBitmap->setImmutable();
+                return bitmap::createBitmap(env, hwBitmap.release(), bitmapCreateFlags,
+                                            ninePatchChunk, ninePatchInsets);
+            }
+            if (allocator == ImageDecoder::kHardware_Allocator) {
+                return nullObjectReturn("failed to allocate hardware Bitmap!");
+            }
+            // If we failed to create a hardware bitmap, go ahead and create a
+            // software one.
+        }
+
+        nativeBitmap->setImmutable();
+    }
+    return bitmap::createBitmap(env, nativeBitmap.release(), bitmapCreateFlags, ninePatchChunk,
+                                ninePatchInsets);
+}
+
+static jobject ImageDecoder_nGetSampledSize(JNIEnv* env, jobject /*clazz*/, jlong nativePtr,
+                                            jint sampleSize) {
+    auto* decoder = reinterpret_cast<ImageDecoder*>(nativePtr);
+    SkISize size = decoder->mCodec->getSampledDimensions(sampleSize);
+    return env->NewObject(gPoint_class, gPoint_constructorMethodID, size.width(), size.height());
+}
+
+static void ImageDecoder_nGetPadding(JNIEnv* env, jobject /*clazz*/, jlong nativePtr,
+                                     jobject outPadding) {
+    auto* decoder = reinterpret_cast<ImageDecoder*>(nativePtr);
+    decoder->mPeeker.getPadding(env, outPadding);
+}
+
+static void ImageDecoder_nRecycle(JNIEnv* /*env*/, jobject /*clazz*/, jlong nativePtr) {
+    delete reinterpret_cast<ImageDecoder*>(nativePtr);
+}
+
+static const JNINativeMethod gImageDecoderMethods[] = {
+    { "nCreate",        "(J)Landroid/graphics/ImageDecoder;",    (void*) ImageDecoder_nCreate },
+    { "nCreate",        "(Ljava/nio/ByteBuffer;II)Landroid/graphics/ImageDecoder;", (void*) ImageDecoder_nCreateByteBuffer },
+    { "nCreate",        "([BII)Landroid/graphics/ImageDecoder;", (void*) ImageDecoder_nCreateByteArray },
+    { "nDecodeBitmap",  "(JLandroid/graphics/ImageDecoder$OnExceptionListener;Landroid/graphics/PostProcess;IILandroid/graphics/Rect;ZIZZZ)Landroid/graphics/Bitmap;",
+                                                                 (void*) ImageDecoder_nDecodeBitmap },
+    { "nGetSampledSize","(JI)Landroid/graphics/Point;",          (void*) ImageDecoder_nGetSampledSize },
+    { "nGetPadding",    "(JLandroid/graphics/Rect;)V",           (void*) ImageDecoder_nGetPadding },
+    { "nRecycle",       "(J)V",                                  (void*) ImageDecoder_nRecycle},
+};
+
+int register_android_graphics_ImageDecoder(JNIEnv* env) {
+    gImageDecoder_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/ImageDecoder"));
+    gImageDecoder_constructorMethodID = GetMethodIDOrDie(env, gImageDecoder_class, "<init>", "(JII)V");
+
+    gPoint_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/Point"));
+    gPoint_constructorMethodID = GetMethodIDOrDie(env, gPoint_class, "<init>", "(II)V");
+
+    gIncomplete_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/ImageDecoder$IncompleteException"));
+    gIncomplete_constructorMethodID = GetMethodIDOrDie(env, gIncomplete_class, "<init>", "()V");
+
+    gCorrupt_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/ImageDecoder$CorruptException"));
+    gCorrupt_constructorMethodID = GetMethodIDOrDie(env, gCorrupt_class, "<init>", "()V");
+
+    jclass callback_class = FindClassOrDie(env, "android/graphics/ImageDecoder$OnExceptionListener");
+    gCallback_onExceptionMethodID = GetMethodIDOrDie(env, callback_class, "onException", "(Ljava/lang/Exception;)Z");
+
+    jclass postProcess_class = FindClassOrDie(env, "android/graphics/PostProcess");
+    gPostProcess_postProcessMethodID = GetMethodIDOrDie(env, postProcess_class, "postProcess", "(Landroid/graphics/Canvas;II)I");
+
+    gCanvas_class = MakeGlobalRefOrDie(env, FindClassOrDie(env, "android/graphics/Canvas"));
+    gCanvas_constructorMethodID = GetMethodIDOrDie(env, gCanvas_class, "<init>", "(J)V");
+    gCanvas_releaseMethodID = GetMethodIDOrDie(env, gCanvas_class, "release", "()V");
+
+    return android::RegisterMethodsOrDie(env, "android/graphics/ImageDecoder", gImageDecoderMethods,
+                                         NELEM(gImageDecoderMethods));
+}
diff --git a/core/jni/android/graphics/NinePatch.cpp b/core/jni/android/graphics/NinePatch.cpp
index 564afeb..2619107 100644
--- a/core/jni/android/graphics/NinePatch.cpp
+++ b/core/jni/android/graphics/NinePatch.cpp
@@ -29,11 +29,15 @@
 #include "SkLatticeIter.h"
 #include "SkRegion.h"
 #include "GraphicsJNI.h"
+#include "NinePatchPeeker.h"
 #include "NinePatchUtils.h"
 
 #include <nativehelper/JNIHelp.h>
 #include "core_jni_helpers.h"
 
+jclass      gInsetStruct_class;
+jmethodID   gInsetStruct_constructorMethodID;
+
 using namespace android;
 
 /**
@@ -128,6 +132,30 @@
 
 };
 
+jobject NinePatchPeeker::createNinePatchInsets(JNIEnv* env, float scale) const {
+    if (!mHasInsets) {
+        return nullptr;
+    }
+
+    return env->NewObject(gInsetStruct_class, gInsetStruct_constructorMethodID,
+            mOpticalInsets[0], mOpticalInsets[1],
+            mOpticalInsets[2], mOpticalInsets[3],
+            mOutlineInsets[0], mOutlineInsets[1],
+            mOutlineInsets[2], mOutlineInsets[3],
+            mOutlineRadius, mOutlineAlpha, scale);
+}
+
+void NinePatchPeeker::getPadding(JNIEnv* env, jobject outPadding) const {
+    if (mPatch) {
+        GraphicsJNI::set_jrect(env, outPadding,
+                mPatch->paddingLeft, mPatch->paddingTop,
+                mPatch->paddingRight, mPatch->paddingBottom);
+
+    } else {
+        GraphicsJNI::set_jrect(env, outPadding, -1, -1, -1, -1);
+    }
+}
+
 /////////////////////////////////////////////////////////////////////////////////////////
 
 static const JNINativeMethod gNinePatchMethods[] = {
@@ -140,6 +168,10 @@
 };
 
 int register_android_graphics_NinePatch(JNIEnv* env) {
+    gInsetStruct_class = MakeGlobalRefOrDie(env, FindClassOrDie(env,
+            "android/graphics/NinePatch$InsetStruct"));
+    gInsetStruct_constructorMethodID = GetMethodIDOrDie(env, gInsetStruct_class, "<init>",
+            "(IIIIIIIIFIF)V");
     return android::RegisterMethodsOrDie(env,
             "android/graphics/NinePatch", gNinePatchMethods, NELEM(gNinePatchMethods));
 }
diff --git a/core/jni/android/graphics/NinePatchPeeker.cpp b/core/jni/android/graphics/NinePatchPeeker.cpp
index 1ea5650..9171fc6 100644
--- a/core/jni/android/graphics/NinePatchPeeker.cpp
+++ b/core/jni/android/graphics/NinePatchPeeker.cpp
@@ -16,7 +16,8 @@
 
 #include "NinePatchPeeker.h"
 
-#include "SkBitmap.h"
+#include <SkBitmap.h>
+#include <cutils/compiler.h>
 
 using namespace android;
 
@@ -46,3 +47,47 @@
     }
     return true;    // keep on decoding
 }
+
+static void scaleDivRange(int32_t* divs, int count, float scale, int maxValue) {
+    for (int i = 0; i < count; i++) {
+        divs[i] = int32_t(divs[i] * scale + 0.5f);
+        if (i > 0 && divs[i] == divs[i - 1]) {
+            divs[i]++; // avoid collisions
+        }
+    }
+
+    if (CC_UNLIKELY(divs[count - 1] > maxValue)) {
+        // if the collision avoidance above put some divs outside the bounds of the bitmap,
+        // slide outer stretchable divs inward to stay within bounds
+        int highestAvailable = maxValue;
+        for (int i = count - 1; i >= 0; i--) {
+            divs[i] = highestAvailable;
+            if (i > 0 && divs[i] <= divs[i-1]) {
+                // keep shifting
+                highestAvailable = divs[i] - 1;
+            } else {
+                break;
+            }
+        }
+    }
+}
+
+void NinePatchPeeker::scale(float scaleX, float scaleY, int scaledWidth, int scaledHeight) {
+    if (!mPatch) {
+        return;
+    }
+
+    // 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.
+    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/graphics/NinePatchPeeker.h b/core/jni/android/graphics/NinePatchPeeker.h
index 126eab2..e4e58dd 100644
--- a/core/jni/android/graphics/NinePatchPeeker.h
+++ b/core/jni/android/graphics/NinePatchPeeker.h
@@ -20,7 +20,7 @@
 #include "SkPngChunkReader.h"
 #include <androidfw/ResourceTypes.h>
 
-class SkImageDecoder;
+#include <jni.h>
 
 using namespace android;
 
@@ -42,9 +42,14 @@
 
     bool readChunk(const char tag[], const void* data, size_t length) override;
 
+    jobject createNinePatchInsets(JNIEnv*, float scale) const;
+    void getPadding(JNIEnv*, jobject outPadding) const;
+    void scale(float scaleX, float scaleY, int scaledWidth, int scaledHeight);
+
     Res_png_9patch* mPatch;
     size_t mPatchSize;
     bool mHasInsets;
+private:
     int32_t mOpticalInsets[4];
     int32_t mOutlineInsets[4];
     float mOutlineRadius;
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/proto/android/os/cpuinfo.proto b/core/proto/android/os/cpuinfo.proto
index a95fa57..522ff24 100644
--- a/core/proto/android/os/cpuinfo.proto
+++ b/core/proto/android/os/cpuinfo.proto
@@ -18,8 +18,6 @@
 option java_multiple_files = true;
 option java_outer_classname = "CpuInfoProto";
 
-import "frameworks/base/tools/streaming_proto/stream.proto";
-
 package android.os;
 
 /**
@@ -31,8 +29,6 @@
 message CpuInfo {
 
     message TaskStats {
-        option (stream_proto.stream_msg).enable_fields_mapping = true;
-
         optional int32 total = 1;    // total number of cpu tasks
         optional int32 running = 2;  // number of running tasks
         optional int32 sleeping = 3; // number of sleeping tasks
@@ -42,8 +38,6 @@
     optional TaskStats task_stats = 1;
 
     message MemStats { // unit in kB
-        option (stream_proto.stream_msg).enable_fields_mapping = true;
-
         optional int32 total = 1;
         optional int32 used = 2;
         optional int32 free = 3;
@@ -54,8 +48,6 @@
     optional MemStats swap = 3;
 
     message CpuUsage { // unit is percentage %
-        option (stream_proto.stream_msg).enable_fields_mapping = true;
-
         optional int32 cpu = 1;   // 400% cpu indicates 4 cores
         optional int32 user = 2;
         optional int32 nice = 3;
@@ -70,8 +62,6 @@
 
     // Next Tag: 13
     message Task {
-        option (stream_proto.stream_msg).enable_fields_mapping = true;
-
         optional int32 pid = 1;
         optional int32 tid = 2;
         optional string user = 3;
@@ -80,8 +70,6 @@
         optional float cpu = 6;     // precentage of cpu usage of the task
 
         enum Status {
-            option (stream_proto.stream_enum).enable_enums_mapping = true;
-
             STATUS_UNKNOWN = 0;
             STATUS_D = 1;  // uninterruptible sleep
             STATUS_R = 2;  // running
@@ -95,8 +83,6 @@
 
         // How Android memory manager will treat the task
         enum Policy {
-            option (stream_proto.stream_enum).enable_enums_mapping = true;
-
             POLICY_UNKNOWN = 0;
             POLICY_fg = 1;  // foreground, the name is lower case for parsing the value
             POLICY_bg = 2;  // background, the name is lower case for parsing the value
diff --git a/core/proto/android/os/kernelwake.proto b/core/proto/android/os/kernelwake.proto
index eaad37a..7e5be9d 100644
--- a/core/proto/android/os/kernelwake.proto
+++ b/core/proto/android/os/kernelwake.proto
@@ -18,8 +18,6 @@
 option java_multiple_files = true;
 option java_outer_classname = "WakeupSourcesProto";
 
-import "frameworks/base/tools/streaming_proto/stream.proto";
-
 package android.os;
 
 message KernelWakeSources {
@@ -29,8 +27,6 @@
 
 // Next Tag: 11
 message WakeupSourceProto {
-    option (stream_proto.stream_msg).enable_fields_mapping = true;
-
     // Name of the event which triggers application processor
     optional string name = 1;
 
diff --git a/core/proto/android/os/pagetypeinfo.proto b/core/proto/android/os/pagetypeinfo.proto
index b86ee01..f82ea76 100644
--- a/core/proto/android/os/pagetypeinfo.proto
+++ b/core/proto/android/os/pagetypeinfo.proto
@@ -18,8 +18,6 @@
 option java_multiple_files = true;
 option java_outer_classname = "PageTypeInfoProto";
 
-import "frameworks/base/tools/streaming_proto/stream.proto";
-
 package android.os;
 
 /*
@@ -63,7 +61,6 @@
 
 // Next tag: 9
 message BlockProto {
-    option (stream_proto.stream_msg).enable_fields_mapping = true;
 
     optional int32 node = 1;
 
diff --git a/core/proto/android/os/procrank.proto b/core/proto/android/os/procrank.proto
index f684c84..204a5af 100644
--- a/core/proto/android/os/procrank.proto
+++ b/core/proto/android/os/procrank.proto
@@ -19,7 +19,6 @@
 option java_outer_classname = "ProcrankProto";
 
 import "frameworks/base/libs/incident/proto/android/privacy.proto";
-import "frameworks/base/tools/streaming_proto/stream.proto";
 
 package android.os;
 
@@ -36,7 +35,6 @@
 
 // Next Tag: 11
 message ProcessProto {
-    option (stream_proto.stream_msg).enable_fields_mapping = true;
     option (android.msg_privacy).dest = DEST_AUTOMATIC;
 
     // ID of the process
diff --git a/core/proto/android/os/system_properties.proto b/core/proto/android/os/system_properties.proto
index 76a108b..a51253f 100644
--- a/core/proto/android/os/system_properties.proto
+++ b/core/proto/android/os/system_properties.proto
@@ -19,14 +19,12 @@
 option java_multiple_files = true;
 
 import "frameworks/base/libs/incident/proto/android/privacy.proto";
-import "frameworks/base/tools/streaming_proto/stream.proto";
 
 package android.os;
 
 // Android Platform Exported System Properties
 // TODO: This is not the completed list, new properties need to be whitelisted.
 message SystemPropertiesProto {
-    option (stream_proto.stream_msg).enable_fields_mapping_recursively = true;
 
     // Properties that are not specified below would be appended here.
     // These values stay on device only.
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 15e439e..e303927 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1620,6 +1620,11 @@
     <permission android:name="android.permission.ACCESS_PDB_STATE"
         android:protectionLevel="signature" />
 
+    <!-- Allows testing if a passwords is forbidden by the admins.
+         @hide <p>Not for use by third-party applications. -->
+    <permission android:name="android.permission.TEST_BLACKLISTED_PASSWORD"
+        android:protectionLevel="signature" />
+
     <!-- @hide Allows system update service to notify device owner about pending updates.
    <p>Not for use by third-party applications. -->
     <permission android:name="android.permission.NOTIFY_PENDING_SYSTEM_UPDATE"
@@ -3627,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/layout/notification_template_material_ambient.xml b/core/res/res/layout/notification_template_material_ambient.xml
index 865685f..435289d 100644
--- a/core/res/res/layout/notification_template_material_ambient.xml
+++ b/core/res/res/layout/notification_template_material_ambient.xml
@@ -72,7 +72,7 @@
                 android:textColor="#eeffffff"
                 android:layout_marginTop="4dp"
                 android:ellipsize="end"
-                android:maxLines="7"
+                android:maxLines="3"
             />
         </LinearLayout>
     </LinearLayout>
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/src/android/animation/AnimatorInflaterTest.java b/core/tests/coretests/src/android/animation/AnimatorInflaterTest.java
index 3c81853..e26bdf5 100644
--- a/core/tests/coretests/src/android/animation/AnimatorInflaterTest.java
+++ b/core/tests/coretests/src/android/animation/AnimatorInflaterTest.java
@@ -15,6 +15,7 @@
 */
 package android.animation;
 
+import android.support.test.filters.LargeTest;
 import android.test.ActivityInstrumentationTestCase2;
 
 import java.util.HashSet;
@@ -24,6 +25,7 @@
 
 import com.android.frameworks.coretests.R;
 
+@LargeTest
 public class AnimatorInflaterTest extends ActivityInstrumentationTestCase2<BasicAnimatorActivity>  {
     Set<Integer> identityHashes = new HashSet<Integer>();
 
diff --git a/core/tests/coretests/src/android/animation/StateListAnimatorTest.java b/core/tests/coretests/src/android/animation/StateListAnimatorTest.java
index 38df78d..a9961e1 100644
--- a/core/tests/coretests/src/android/animation/StateListAnimatorTest.java
+++ b/core/tests/coretests/src/android/animation/StateListAnimatorTest.java
@@ -17,6 +17,7 @@
 
 package android.animation;
 
+import android.support.test.filters.LargeTest;
 import android.test.ActivityInstrumentationTestCase2;
 import android.test.UiThreadTest;
 import android.util.StateSet;
@@ -27,7 +28,7 @@
 
 import java.util.concurrent.atomic.AtomicInteger;
 
-
+@LargeTest
 public class StateListAnimatorTest extends ActivityInstrumentationTestCase2<BasicAnimatorActivity> {
 
     public StateListAnimatorTest() {
diff --git a/core/tests/coretests/src/android/app/ApplicationPackageManagerTest.java b/core/tests/coretests/src/android/app/ApplicationPackageManagerTest.java
index f60bf94..063bef7 100644
--- a/core/tests/coretests/src/android/app/ApplicationPackageManagerTest.java
+++ b/core/tests/coretests/src/android/app/ApplicationPackageManagerTest.java
@@ -22,6 +22,7 @@
 import android.content.pm.PackageInfo;
 import android.os.storage.StorageManager;
 import android.os.storage.VolumeInfo;
+import android.support.test.filters.LargeTest;
 
 import junit.framework.TestCase;
 
@@ -34,6 +35,7 @@
 import static android.os.storage.VolumeInfo.STATE_MOUNTED;
 import static android.os.storage.VolumeInfo.STATE_UNMOUNTED;
 
+@LargeTest
 public class ApplicationPackageManagerTest extends TestCase {
     private static final String sInternalVolPath = "/data";
     private static final String sAdoptedVolPath = "/mnt/expand/123";
diff --git a/core/tests/coretests/src/android/app/InstrumentationTest.java b/core/tests/coretests/src/android/app/InstrumentationTest.java
index ee3834c..9b59da4 100644
--- a/core/tests/coretests/src/android/app/InstrumentationTest.java
+++ b/core/tests/coretests/src/android/app/InstrumentationTest.java
@@ -17,8 +17,10 @@
 package android.app;
 
 import android.os.Bundle;
+import android.support.test.filters.LargeTest;
 import android.test.InstrumentationTestCase;
 
+@LargeTest
 public class InstrumentationTest extends InstrumentationTestCase {
 
     /**
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/app/activity/BroadcastTest.java b/core/tests/coretests/src/android/app/activity/BroadcastTest.java
index e9e8bfc..13e70eb 100644
--- a/core/tests/coretests/src/android/app/activity/BroadcastTest.java
+++ b/core/tests/coretests/src/android/app/activity/BroadcastTest.java
@@ -27,12 +27,13 @@
 import android.os.IBinder;
 import android.os.Parcel;
 import android.os.UserHandle;
+import android.support.test.filters.LargeTest;
 import android.test.FlakyTest;
-import android.test.suitebuilder.annotation.Suppress;
 import android.util.Log;
 
 import java.util.Arrays;
 
+@LargeTest
 public class BroadcastTest extends ActivityTestsBase {
     public static final int BROADCAST_TIMEOUT = 5 * 1000;
 
diff --git a/core/tests/coretests/src/android/app/activity/IntentSenderTest.java b/core/tests/coretests/src/android/app/activity/IntentSenderTest.java
index 3c30915..8c1d79b 100644
--- a/core/tests/coretests/src/android/app/activity/IntentSenderTest.java
+++ b/core/tests/coretests/src/android/app/activity/IntentSenderTest.java
@@ -20,10 +20,10 @@
 import android.app.PendingIntent;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.test.suitebuilder.annotation.Suppress;
 import android.os.Bundle;
-import android.test.suitebuilder.annotation.Suppress;
+import android.support.test.filters.LargeTest;
 
+@LargeTest
 public class IntentSenderTest extends BroadcastTest {
 
     public void testRegisteredReceivePermissionGranted() throws Exception {
diff --git a/core/tests/coretests/src/android/app/backup/BackupDataTest.java b/core/tests/coretests/src/android/app/backup/BackupDataTest.java
index 0c204e0..5b8e481 100644
--- a/core/tests/coretests/src/android/app/backup/BackupDataTest.java
+++ b/core/tests/coretests/src/android/app/backup/BackupDataTest.java
@@ -23,6 +23,7 @@
 import android.os.Bundle;
 import android.os.Environment;
 import android.os.ParcelFileDescriptor;
+import android.support.test.filters.LargeTest;
 import android.test.AndroidTestCase;
 import android.test.InstrumentationTestCase;
 import android.util.Base64;
@@ -41,6 +42,7 @@
 import java.lang.Exception;
 import java.nio.ByteBuffer;
 
+@LargeTest
 public class BackupDataTest extends AndroidTestCase {
     private static final String KEY1 = "key1";
     private static final String KEY2 = "key2a";
diff --git a/core/tests/coretests/src/android/app/backup/FullBackupTest.java b/core/tests/coretests/src/android/app/backup/FullBackupTest.java
index 3869cd2..bc6fc15 100644
--- a/core/tests/coretests/src/android/app/backup/FullBackupTest.java
+++ b/core/tests/coretests/src/android/app/backup/FullBackupTest.java
@@ -20,6 +20,7 @@
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
+import android.support.test.filters.LargeTest;
 import android.test.AndroidTestCase;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -36,6 +37,7 @@
 import java.util.Map;
 import java.util.Set;
 
+@LargeTest
 public class FullBackupTest extends AndroidTestCase {
     private XmlPullParserFactory mFactory;
     private XmlPullParser mXpp;
diff --git a/core/tests/coretests/src/android/app/timezone/DistroFormatVersionTest.java b/core/tests/coretests/src/android/app/timezone/DistroFormatVersionTest.java
index 70a0877..e20645c 100644
--- a/core/tests/coretests/src/android/app/timezone/DistroFormatVersionTest.java
+++ b/core/tests/coretests/src/android/app/timezone/DistroFormatVersionTest.java
@@ -21,12 +21,14 @@
 import static org.junit.Assert.assertTrue;
 
 import android.os.Parcel;
+import android.support.test.filters.LargeTest;
 
 import org.junit.Test;
 
 /**
  * Tests for {@link DistroFormatVersion}.
  */
+@LargeTest
 public class DistroFormatVersionTest {
 
     @Test
diff --git a/core/tests/coretests/src/android/app/timezone/DistroRulesVersionTest.java b/core/tests/coretests/src/android/app/timezone/DistroRulesVersionTest.java
index eecae46..b69054c 100644
--- a/core/tests/coretests/src/android/app/timezone/DistroRulesVersionTest.java
+++ b/core/tests/coretests/src/android/app/timezone/DistroRulesVersionTest.java
@@ -21,12 +21,14 @@
 import static org.junit.Assert.assertTrue;
 
 import android.os.Parcel;
+import android.support.test.filters.LargeTest;
 
 import org.junit.Test;
 
 /**
  * Tests for {@link DistroRulesVersion}.
  */
+@LargeTest
 public class DistroRulesVersionTest {
 
     @Test
diff --git a/core/tests/coretests/src/android/app/timezone/RulesStateTest.java b/core/tests/coretests/src/android/app/timezone/RulesStateTest.java
index 99abe24..dd46240 100644
--- a/core/tests/coretests/src/android/app/timezone/RulesStateTest.java
+++ b/core/tests/coretests/src/android/app/timezone/RulesStateTest.java
@@ -23,12 +23,14 @@
 import static org.junit.Assert.assertTrue;
 
 import android.os.Parcel;
+import android.support.test.filters.LargeTest;
 
 import org.junit.Test;
 
 /**
  * Tests for {@link RulesState}.
  */
+@LargeTest
 public class RulesStateTest {
 
     @Test
diff --git a/core/tests/coretests/src/android/app/timezone/RulesUpdaterContractTest.java b/core/tests/coretests/src/android/app/timezone/RulesUpdaterContractTest.java
index 91f8ebc..e4aac50 100644
--- a/core/tests/coretests/src/android/app/timezone/RulesUpdaterContractTest.java
+++ b/core/tests/coretests/src/android/app/timezone/RulesUpdaterContractTest.java
@@ -24,6 +24,7 @@
 
 import android.content.Context;
 import android.content.Intent;
+import android.support.test.filters.LargeTest;
 
 import org.hamcrest.BaseMatcher;
 import org.hamcrest.Description;
@@ -33,6 +34,7 @@
 /**
  * Tests for {@link RulesUpdaterContract}.
  */
+@LargeTest
 public class RulesUpdaterContractTest {
 
     @Test
diff --git a/core/tests/coretests/src/android/content/RestrictionsManagerTest.java b/core/tests/coretests/src/android/content/RestrictionsManagerTest.java
index d92eece..10d74f7 100644
--- a/core/tests/coretests/src/android/content/RestrictionsManagerTest.java
+++ b/core/tests/coretests/src/android/content/RestrictionsManagerTest.java
@@ -17,6 +17,7 @@
 
 import android.os.Bundle;
 import android.os.Parcelable;
+import android.support.test.filters.LargeTest;
 import android.test.AndroidTestCase;
 
 import java.util.Arrays;
@@ -24,6 +25,7 @@
 import java.util.List;
 import java.util.Set;
 
+@LargeTest
 public class RestrictionsManagerTest extends AndroidTestCase {
     private RestrictionsManager mRm;
 
diff --git a/core/tests/coretests/src/android/content/pm/MacAuthenticatedInputStreamTest.java b/core/tests/coretests/src/android/content/pm/MacAuthenticatedInputStreamTest.java
index 948e722..659f9ea 100644
--- a/core/tests/coretests/src/android/content/pm/MacAuthenticatedInputStreamTest.java
+++ b/core/tests/coretests/src/android/content/pm/MacAuthenticatedInputStreamTest.java
@@ -16,6 +16,7 @@
 
 package android.content.pm;
 
+import android.support.test.filters.LargeTest;
 import android.test.AndroidTestCase;
 
 import java.io.ByteArrayInputStream;
@@ -27,6 +28,7 @@
 
 import libcore.io.Streams;
 
+@LargeTest
 public class MacAuthenticatedInputStreamTest extends AndroidTestCase {
 
     private static final SecretKey HMAC_KEY_1 = new SecretKeySpec("test_key_1".getBytes(), "HMAC");
diff --git a/core/tests/coretests/src/android/content/pm/ParceledListSliceTest.java b/core/tests/coretests/src/android/content/pm/ParceledListSliceTest.java
index a9d19b4..952bb55 100644
--- a/core/tests/coretests/src/android/content/pm/ParceledListSliceTest.java
+++ b/core/tests/coretests/src/android/content/pm/ParceledListSliceTest.java
@@ -2,6 +2,7 @@
 
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.support.test.filters.LargeTest;
 
 import junit.framework.TestCase;
 
@@ -9,6 +10,7 @@
 import java.util.Collections;
 import java.util.List;
 
+@LargeTest
 public class ParceledListSliceTest extends TestCase {
 
     public void testSmallList() throws Exception {
diff --git a/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java b/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java
index 271c639..d3d1f22a 100644
--- a/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java
+++ b/core/tests/coretests/src/android/content/pm/RegisteredServicesCacheTest.java
@@ -21,6 +21,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.UserHandle;
+import android.support.test.filters.LargeTest;
 import android.test.AndroidTestCase;
 import android.util.AttributeSet;
 import android.util.SparseArray;
@@ -43,6 +44,7 @@
 /**
  * Tests for {@link android.content.pm.RegisteredServicesCache}
  */
+@LargeTest
 public class RegisteredServicesCacheTest extends AndroidTestCase {
     private static final int U0 = 0;
     private static final int U1 = 1;
diff --git a/core/tests/coretests/src/android/content/pm/SignatureTest.java b/core/tests/coretests/src/android/content/pm/SignatureTest.java
index 89d5997..a3fa1a9 100644
--- a/core/tests/coretests/src/android/content/pm/SignatureTest.java
+++ b/core/tests/coretests/src/android/content/pm/SignatureTest.java
@@ -16,8 +16,11 @@
 
 package android.content.pm;
 
+import android.support.test.filters.LargeTest;
+
 import junit.framework.TestCase;
 
+@LargeTest
 public class SignatureTest extends TestCase {
 
     /** Cert A with valid syntax */
diff --git a/core/tests/coretests/src/android/content/pm/VerificationParamsTest.java b/core/tests/coretests/src/android/content/pm/VerificationParamsTest.java
index d963812..68942cb 100644
--- a/core/tests/coretests/src/android/content/pm/VerificationParamsTest.java
+++ b/core/tests/coretests/src/android/content/pm/VerificationParamsTest.java
@@ -19,6 +19,7 @@
 import android.content.pm.VerificationParams;
 import android.net.Uri;
 import android.os.Parcel;
+import android.support.test.filters.LargeTest;
 import android.test.AndroidTestCase;
 
 /**
@@ -27,6 +28,7 @@
  * To test run:
  * ./development/testrunner/runtest.py frameworks-core -c android.content.pm.VerificationParamsTest
  */
+@LargeTest
 public class VerificationParamsTest extends AndroidTestCase {
 
     private final static String VERIFICATION_URI_STRING = "http://verification.uri/path";
diff --git a/core/tests/coretests/src/android/content/pm/VerifierDeviceIdentityTest.java b/core/tests/coretests/src/android/content/pm/VerifierDeviceIdentityTest.java
index cb13eb7..88d7a59 100644
--- a/core/tests/coretests/src/android/content/pm/VerifierDeviceIdentityTest.java
+++ b/core/tests/coretests/src/android/content/pm/VerifierDeviceIdentityTest.java
@@ -17,9 +17,11 @@
 package android.content.pm;
 
 import android.os.Parcel;
+import android.support.test.filters.LargeTest;
 
 import java.util.Random;
 
+@LargeTest
 public class VerifierDeviceIdentityTest extends android.test.AndroidTestCase {
     private static final long TEST_1 = 0x7A5F00FF5A55AAA5L;
 
diff --git a/core/tests/coretests/src/android/graphics/drawable/AdaptiveIconDrawableTest.java b/core/tests/coretests/src/android/graphics/drawable/AdaptiveIconDrawableTest.java
index 7550cb5..b28a4b5 100644
--- a/core/tests/coretests/src/android/graphics/drawable/AdaptiveIconDrawableTest.java
+++ b/core/tests/coretests/src/android/graphics/drawable/AdaptiveIconDrawableTest.java
@@ -15,6 +15,7 @@
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.Region;
+import android.support.test.filters.LargeTest;
 import android.test.AndroidTestCase;
 import android.util.Log;
 import android.util.PathParser;
@@ -23,6 +24,7 @@
 import java.util.Arrays;
 import org.junit.Test;
 
+@LargeTest
 public class AdaptiveIconDrawableTest extends AndroidTestCase {
 
     public static final String TAG = AdaptiveIconDrawableTest.class.getSimpleName();
diff --git a/core/tests/coretests/src/android/hardware/display/VirtualDisplayTest.java b/core/tests/coretests/src/android/hardware/display/VirtualDisplayTest.java
index a2e9ae8..f30b1a2 100644
--- a/core/tests/coretests/src/android/hardware/display/VirtualDisplayTest.java
+++ b/core/tests/coretests/src/android/hardware/display/VirtualDisplayTest.java
@@ -30,6 +30,7 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.SystemClock;
+import android.support.test.filters.LargeTest;
 import android.test.AndroidTestCase;
 import android.util.DisplayMetrics;
 import android.util.Log;
@@ -49,6 +50,7 @@
  * Contains additional tests that cannot be included in CTS because they require
  * system permissions.  See also the CTS version of VirtualDisplayTest.
  */
+@LargeTest
 public class VirtualDisplayTest extends AndroidTestCase {
     private static final String TAG = "VirtualDisplayTest";
 
diff --git a/core/tests/coretests/src/android/metrics/LogMakerTest.java b/core/tests/coretests/src/android/metrics/LogMakerTest.java
index ada59cd..3be776d 100644
--- a/core/tests/coretests/src/android/metrics/LogMakerTest.java
+++ b/core/tests/coretests/src/android/metrics/LogMakerTest.java
@@ -15,9 +15,13 @@
  */
 package android.metrics;
 
+import android.support.test.filters.LargeTest;
+
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
+
 import junit.framework.TestCase;
 
+@LargeTest
 public class LogMakerTest extends TestCase {
 
     public void testSerialize() {
diff --git a/core/tests/coretests/src/android/metrics/MetricsReaderTest.java b/core/tests/coretests/src/android/metrics/MetricsReaderTest.java
index d10b351..784a12f 100644
--- a/core/tests/coretests/src/android/metrics/MetricsReaderTest.java
+++ b/core/tests/coretests/src/android/metrics/MetricsReaderTest.java
@@ -16,6 +16,7 @@
 package android.metrics;
 
 import android.metrics.MetricsReader.Event;
+import android.support.test.filters.LargeTest;
 
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 
@@ -23,6 +24,7 @@
 
 import java.util.Collection;
 
+@LargeTest
 public class MetricsReaderTest extends TestCase {
     private static final int FULL_N = 10;
     private static final int CHECKPOINTED_N = 4;
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/preference/ListPreferenceTest.java b/core/tests/coretests/src/android/preference/ListPreferenceTest.java
index 41f8e03..72f62f1 100644
--- a/core/tests/coretests/src/android/preference/ListPreferenceTest.java
+++ b/core/tests/coretests/src/android/preference/ListPreferenceTest.java
@@ -17,8 +17,10 @@
 package android.preference;
 
 import android.preference.ListPreference;
+import android.support.test.filters.LargeTest;
 import android.test.AndroidTestCase;
 
+@LargeTest
 public class ListPreferenceTest extends AndroidTestCase {
     public void testListPreferenceSummaryFromEntries() {
         String[] entries = { "one", "two", "three" };
diff --git a/core/tests/coretests/src/android/text/OWNERS b/core/tests/coretests/src/android/text/OWNERS
new file mode 100644
index 0000000..0f85e1f
--- /dev/null
+++ b/core/tests/coretests/src/android/text/OWNERS
@@ -0,0 +1,4 @@
+siyamed@google.com
+nona@google.com
+clarabayarri@google.com
+toki@google.com
diff --git a/core/tests/coretests/src/android/text/StaticLayoutTest.java b/core/tests/coretests/src/android/text/StaticLayoutTest.java
index f4514b5..d817278 100644
--- a/core/tests/coretests/src/android/text/StaticLayoutTest.java
+++ b/core/tests/coretests/src/android/text/StaticLayoutTest.java
@@ -697,9 +697,13 @@
     public void testGetOffset_UNICODE_Hebrew() {
         String testString = "\u05DE\u05E1\u05E2\u05D3\u05D4"; // Hebrew Characters
         for (CharSequence seq: buildTestCharSequences(testString, Normalizer.Form.values())) {
-            StaticLayout layout = new StaticLayout(seq, mDefaultPaint,
-                    DEFAULT_OUTER_WIDTH, DEFAULT_ALIGN,
-                    TextDirectionHeuristics.RTL, SPACE_MULTI, SPACE_ADD, true);
+            StaticLayout.Builder b = StaticLayout.Builder.obtain(
+                    seq, 0, seq.length(), mDefaultPaint, DEFAULT_OUTER_WIDTH)
+                    .setAlignment(DEFAULT_ALIGN)
+                    .setTextDirection(TextDirectionHeuristics.RTL)
+                    .setLineSpacing(SPACE_ADD, SPACE_MULTI)
+                    .setIncludePad(true);
+            StaticLayout layout = b.build();
 
             String testLabel = buildTestMessage(seq);
 
diff --git a/core/tests/coretests/src/android/transition/TransitionTest.java b/core/tests/coretests/src/android/transition/TransitionTest.java
index ab4320c..7e629f9 100644
--- a/core/tests/coretests/src/android/transition/TransitionTest.java
+++ b/core/tests/coretests/src/android/transition/TransitionTest.java
@@ -19,6 +19,7 @@
 import android.animation.AnimatorSetActivity;
 import android.app.Activity;
 import android.graphics.Rect;
+import android.support.test.filters.LargeTest;
 import android.test.ActivityInstrumentationTestCase2;
 import android.transition.Transition.EpicenterCallback;
 import android.util.ArrayMap;
@@ -30,6 +31,7 @@
 
 import java.lang.reflect.Field;
 
+@LargeTest
 public class TransitionTest extends ActivityInstrumentationTestCase2<AnimatorSetActivity> {
     Activity mActivity;
     public TransitionTest() {
diff --git a/core/tests/coretests/src/android/util/ArrayMapTest.java b/core/tests/coretests/src/android/util/ArrayMapTest.java
index 32aa29f..f0cc7f7 100644
--- a/core/tests/coretests/src/android/util/ArrayMapTest.java
+++ b/core/tests/coretests/src/android/util/ArrayMapTest.java
@@ -14,6 +14,7 @@
 
 package android.util;
 
+import android.support.test.filters.LargeTest;
 import android.util.ArrayMap;
 
 import junit.framework.TestCase;
@@ -24,6 +25,7 @@
 /**
  * Unit tests for ArrayMap that don't belong in CTS.
  */
+@LargeTest
 public class ArrayMapTest extends TestCase {
     private static final String TAG = "ArrayMapTest";
     ArrayMap<String, String> map = new ArrayMap<>();
diff --git a/core/tests/coretests/src/android/util/Base64Test.java b/core/tests/coretests/src/android/util/Base64Test.java
index 53368d4..3aee583 100644
--- a/core/tests/coretests/src/android/util/Base64Test.java
+++ b/core/tests/coretests/src/android/util/Base64Test.java
@@ -16,15 +16,17 @@
 
 package android.util;
 
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Arrays;
-import junit.framework.TestCase;
+import android.support.test.filters.LargeTest;
 
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Arrays;
 import java.util.Random;
+import junit.framework.TestCase;
 
+@LargeTest
 public class Base64Test extends TestCase {
     private static final String TAG = "Base64Test";
 
diff --git a/core/tests/coretests/src/android/util/LocalLogTest.java b/core/tests/coretests/src/android/util/LocalLogTest.java
index a63c8a0..5144c85e 100644
--- a/core/tests/coretests/src/android/util/LocalLogTest.java
+++ b/core/tests/coretests/src/android/util/LocalLogTest.java
@@ -16,6 +16,8 @@
 
 package android.util;
 
+import android.support.test.filters.LargeTest;
+
 import junit.framework.TestCase;
 
 import java.io.PrintWriter;
@@ -24,7 +26,7 @@
 import java.util.Collections;
 import java.util.List;
 
-
+@LargeTest
 public class LocalLogTest extends TestCase {
 
     public void testA() {
diff --git a/core/tests/coretests/src/android/util/LongSparseLongArrayTest.java b/core/tests/coretests/src/android/util/LongSparseLongArrayTest.java
index cb468bc..3f03db9 100644
--- a/core/tests/coretests/src/android/util/LongSparseLongArrayTest.java
+++ b/core/tests/coretests/src/android/util/LongSparseLongArrayTest.java
@@ -16,6 +16,8 @@
 
 package android.util;
 
+import android.support.test.filters.LargeTest;
+
 import junit.framework.TestCase;
 
 import java.util.HashMap;
@@ -26,6 +28,7 @@
 /**
  * Tests for {@link LongSparseLongArray}.
  */
+@LargeTest
 public class LongSparseLongArrayTest extends TestCase {
     private static final String TAG = "LongSparseLongArrayTest";
 
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/android/view/ScaleGestureDetectorTest.java b/core/tests/coretests/src/android/view/ScaleGestureDetectorTest.java
index 23d0251..fba8eae 100644
--- a/core/tests/coretests/src/android/view/ScaleGestureDetectorTest.java
+++ b/core/tests/coretests/src/android/view/ScaleGestureDetectorTest.java
@@ -17,6 +17,7 @@
 package android.view;
 
 import android.content.Context;
+import android.support.test.filters.LargeTest;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.runner.AndroidJUnit4;
 import android.test.ActivityInstrumentationTestCase2;
@@ -36,6 +37,7 @@
 import static android.support.test.espresso.matcher.ViewMatchers.withId;
 import static android.support.test.espresso.Espresso.onView;
 
+@LargeTest
 public class ScaleGestureDetectorTest extends ActivityInstrumentationTestCase2<ScaleGesture> {
     private ScaleGesture mScaleGestureActivity;
 
diff --git a/core/tests/coretests/src/android/view/ViewAttachTest.java b/core/tests/coretests/src/android/view/ViewAttachTest.java
index 44fcd13..aa8f8d8 100644
--- a/core/tests/coretests/src/android/view/ViewAttachTest.java
+++ b/core/tests/coretests/src/android/view/ViewAttachTest.java
@@ -20,6 +20,7 @@
 import android.content.pm.ActivityInfo;
 import android.graphics.PixelFormat;
 import android.os.SystemClock;
+import android.support.test.filters.LargeTest;
 import android.test.ActivityInstrumentationTestCase2;
 import android.test.UiThreadTest;
 import android.view.View;
@@ -28,6 +29,7 @@
 
 import com.android.frameworks.coretests.R;
 
+@LargeTest
 public class ViewAttachTest extends
         ActivityInstrumentationTestCase2<ViewAttachTestActivity> {
 
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java
index 2f32d13..4de8155 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityCacheTest.java
@@ -28,6 +28,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.support.test.filters.LargeTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.view.View;
 
@@ -42,7 +43,7 @@
 import java.util.List;
 import java.util.concurrent.atomic.AtomicInteger;
 
-
+@LargeTest
 @RunWith(AndroidJUnit4.class)
 public class AccessibilityCacheTest {
     private static final int WINDOW_ID_1 = 0xBEEF;
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java
index c1b2309..318d122 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityInteractionClientTest.java
@@ -22,6 +22,7 @@
 
 import android.os.Bundle;
 import android.os.RemoteException;
+import android.support.test.filters.LargeTest;
 import android.support.test.runner.AndroidJUnit4;
 
 import libcore.util.EmptyArray;
@@ -36,6 +37,7 @@
 /**
  * Tests for AccessibilityInteractionClient
  */
+@LargeTest
 @RunWith(AndroidJUnit4.class)
 public class AccessibilityInteractionClientTest {
     private static final int MOCK_CONNECTION_ID = 0xabcd;
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
index 79e6316..b135025 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityNodeInfoTest.java
@@ -18,6 +18,7 @@
 
 import static org.junit.Assert.fail;
 
+import android.support.test.filters.LargeTest;
 import android.support.test.runner.AndroidJUnit4;
 import android.util.ArraySet;
 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
@@ -29,6 +30,7 @@
 
 import java.util.ArrayList;
 
+@LargeTest
 @RunWith(AndroidJUnit4.class)
 public class AccessibilityNodeInfoTest {
 
diff --git a/core/tests/coretests/src/android/view/inputmethod/InputMethodInfoTest.java b/core/tests/coretests/src/android/view/inputmethod/InputMethodInfoTest.java
index e3c91e6..9edbf3e 100644
--- a/core/tests/coretests/src/android/view/inputmethod/InputMethodInfoTest.java
+++ b/core/tests/coretests/src/android/view/inputmethod/InputMethodInfoTest.java
@@ -26,6 +26,7 @@
 import android.content.pm.ServiceInfo;
 import android.os.Bundle;
 import android.os.Parcel;
+import android.support.test.filters.LargeTest;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.runner.AndroidJUnit4;
 
@@ -34,6 +35,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+@LargeTest
 @RunWith(AndroidJUnit4.class)
 public class InputMethodInfoTest {
 
diff --git a/core/tests/coretests/src/android/view/menu/MenuLayoutLandscapeTest.java b/core/tests/coretests/src/android/view/menu/MenuLayoutLandscapeTest.java
index 9347b27..8ed0d86 100644
--- a/core/tests/coretests/src/android/view/menu/MenuLayoutLandscapeTest.java
+++ b/core/tests/coretests/src/android/view/menu/MenuLayoutLandscapeTest.java
@@ -21,8 +21,10 @@
 import com.android.internal.view.menu.MenuBuilder;
 
 import android.content.pm.ActivityInfo;
+import android.support.test.filters.LargeTest;
 import android.test.ActivityInstrumentationTestCase;
 
+@LargeTest
 public class MenuLayoutLandscapeTest extends ActivityInstrumentationTestCase<MenuLayoutLandscape> {
     private static final String LONG_TITLE = "Really really really really really really really really really really long title";
     private static final String SHORT_TITLE = "Item";
diff --git a/core/tests/coretests/src/android/view/menu/MenuLayoutPortraitTest.java b/core/tests/coretests/src/android/view/menu/MenuLayoutPortraitTest.java
index b053699..ccf1264 100644
--- a/core/tests/coretests/src/android/view/menu/MenuLayoutPortraitTest.java
+++ b/core/tests/coretests/src/android/view/menu/MenuLayoutPortraitTest.java
@@ -16,13 +16,12 @@
 
 package android.view.menu;
 
-import android.util.KeyUtils;
-import com.android.internal.view.menu.IconMenuView;
-import com.android.internal.view.menu.MenuBuilder;
-
 import android.content.pm.ActivityInfo;
+import android.support.test.filters.LargeTest;
 import android.test.ActivityInstrumentationTestCase;
+import android.util.KeyUtils;
 
+@LargeTest
 public class MenuLayoutPortraitTest extends ActivityInstrumentationTestCase<MenuLayoutPortrait> {
     private static final String LONG_TITLE = "Really really really really really really really really really really long title";
     private static final String SHORT_TITLE = "Item";
diff --git a/core/tests/coretests/src/android/widget/DatePickerFocusTest.java b/core/tests/coretests/src/android/widget/DatePickerFocusTest.java
index 513e40f..be85450 100644
--- a/core/tests/coretests/src/android/widget/DatePickerFocusTest.java
+++ b/core/tests/coretests/src/android/widget/DatePickerFocusTest.java
@@ -19,6 +19,7 @@
 import android.app.Activity;
 import android.app.Instrumentation;
 import android.os.SystemClock;
+import android.support.test.filters.LargeTest;
 import android.test.ActivityInstrumentationTestCase2;
 import android.view.KeyEvent;
 import android.view.View;
@@ -28,6 +29,7 @@
 /**
  * Test {@link DatePicker} focus changes.
  */
+@LargeTest
 public class DatePickerFocusTest extends ActivityInstrumentationTestCase2<DatePickerActivity> {
 
     private Activity mActivity;
diff --git a/core/tests/coretests/src/android/widget/SelectionActionModeHelperTest.java b/core/tests/coretests/src/android/widget/SelectionActionModeHelperTest.java
index 28a3b67..2add221 100644
--- a/core/tests/coretests/src/android/widget/SelectionActionModeHelperTest.java
+++ b/core/tests/coretests/src/android/widget/SelectionActionModeHelperTest.java
@@ -22,6 +22,7 @@
 
 import android.graphics.PointF;
 import android.graphics.RectF;
+import android.support.test.filters.LargeTest;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -31,6 +32,7 @@
 import java.util.Arrays;
 import java.util.List;
 
+@LargeTest
 @RunWith(JUnit4.class)
 public final class SelectionActionModeHelperTest {
 
diff --git a/core/tests/coretests/src/android/widget/focus/HorizontalFocusSearchTest.java b/core/tests/coretests/src/android/widget/focus/HorizontalFocusSearchTest.java
index b591e5f..43986ee 100644
--- a/core/tests/coretests/src/android/widget/focus/HorizontalFocusSearchTest.java
+++ b/core/tests/coretests/src/android/widget/focus/HorizontalFocusSearchTest.java
@@ -18,6 +18,7 @@
 
 import android.widget.focus.HorizontalFocusSearch;
 
+import android.support.test.filters.LargeTest;
 import android.test.ActivityInstrumentationTestCase;
 import android.test.suitebuilder.annotation.Suppress;
 import android.widget.LinearLayout;
@@ -32,6 +33,7 @@
  * various widths and vertical placements.
  */
 // Suppress until bug http://b/issue?id=1416545 is fixed.
+@LargeTest
 @Suppress
 public class HorizontalFocusSearchTest extends ActivityInstrumentationTestCase<HorizontalFocusSearch> {
 
diff --git a/core/tests/coretests/src/android/widget/focus/VerticalFocusSearchTest.java b/core/tests/coretests/src/android/widget/focus/VerticalFocusSearchTest.java
index f05d83a..f01422e 100644
--- a/core/tests/coretests/src/android/widget/focus/VerticalFocusSearchTest.java
+++ b/core/tests/coretests/src/android/widget/focus/VerticalFocusSearchTest.java
@@ -18,6 +18,7 @@
 
 import android.widget.focus.VerticalFocusSearch;
 
+import android.support.test.filters.LargeTest;
 import android.test.ActivityInstrumentationTestCase;
 import android.test.suitebuilder.annotation.Suppress;
 import android.view.FocusFinder;
@@ -31,6 +32,7 @@
  * various widths and horizontal placements.
  */
 // Suppress until bug http://b/issue?id=1416545 is fixed
+@LargeTest
 @Suppress 
 public class VerticalFocusSearchTest extends ActivityInstrumentationTestCase<VerticalFocusSearch> {
 
diff --git a/core/tests/coretests/src/android/widget/listview/arrowscroll/ListWithFirstScreenUnSelectableTest.java b/core/tests/coretests/src/android/widget/listview/arrowscroll/ListWithFirstScreenUnSelectableTest.java
index 400fd7d..9a8e634 100644
--- a/core/tests/coretests/src/android/widget/listview/arrowscroll/ListWithFirstScreenUnSelectableTest.java
+++ b/core/tests/coretests/src/android/widget/listview/arrowscroll/ListWithFirstScreenUnSelectableTest.java
@@ -16,12 +16,14 @@
 
 package android.widget.listview.arrowscroll;
 
-import android.widget.listview.ListWithFirstScreenUnSelectable;
+import android.support.test.filters.LargeTest;
 import android.test.ActivityInstrumentationTestCase2;
 import android.view.KeyEvent;
 import android.widget.ListView;
+import android.widget.listview.ListWithFirstScreenUnSelectable;
 import android.widget.AdapterView;
 
+@LargeTest
 public class ListWithFirstScreenUnSelectableTest
         extends ActivityInstrumentationTestCase2<ListWithFirstScreenUnSelectable> {
     private ListView mListView;
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/graphics/java/android/graphics/ImageDecoder.java b/graphics/java/android/graphics/ImageDecoder.java
new file mode 100644
index 0000000..60416a7
--- /dev/null
+++ b/graphics/java/android/graphics/ImageDecoder.java
@@ -0,0 +1,665 @@
+/*
+ * 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 android.graphics;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RawRes;
+import android.content.res.AssetManager;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.NinePatchDrawable;
+
+import java.nio.ByteBuffer;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.ArrayIndexOutOfBoundsException;
+import java.lang.NullPointerException;
+import java.lang.RuntimeException;
+import java.lang.annotation.Retention;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ *  Class for decoding images as {@link Bitmap}s or {@link Drawable}s.
+ *  @hide
+ */
+public final class ImageDecoder {
+    /**
+     *  Source of the encoded image data.
+     */
+    public static abstract class Source {
+        /* @hide */
+        Resources getResources() { return null; }
+
+        /* @hide */
+        void close() {}
+
+        /* @hide */
+        abstract ImageDecoder createImageDecoder();
+    };
+
+    private static class ByteArraySource extends Source {
+        ByteArraySource(byte[] data, int offset, int length) {
+            mData = data;
+            mOffset = offset;
+            mLength = length;
+        };
+        private final byte[] mData;
+        private final int    mOffset;
+        private final int    mLength;
+
+        @Override
+        public ImageDecoder createImageDecoder() {
+            return nCreate(mData, mOffset, mLength);
+        }
+    }
+
+    private static class ByteBufferSource extends Source {
+        ByteBufferSource(ByteBuffer buffer) {
+            mBuffer = buffer;
+        }
+        private final ByteBuffer mBuffer;
+
+        @Override
+        public ImageDecoder createImageDecoder() {
+            if (!mBuffer.isDirect() && mBuffer.hasArray()) {
+                int offset = mBuffer.arrayOffset() + mBuffer.position();
+                int length = mBuffer.limit() - mBuffer.position();
+                return nCreate(mBuffer.array(), offset, length);
+            }
+            return nCreate(mBuffer, mBuffer.position(), mBuffer.limit());
+        }
+    }
+
+    private static class ResourceSource extends Source {
+        ResourceSource(Resources res, int resId)
+                throws Resources.NotFoundException {
+            // Test that the resource can be found.
+            InputStream is = null;
+            try {
+                is = res.openRawResource(resId);
+            } finally {
+                if (is != null) {
+                    try {
+                        is.close();
+                    } catch (IOException e) {
+                    }
+                }
+            }
+
+            mResources = res;
+            mResId = resId;
+        }
+
+        final Resources mResources;
+        final int       mResId;
+        // This is just stored here in order to keep the underlying Asset
+        // alive. FIXME: Can I access the Asset (and keep it alive) without
+        // this object?
+        InputStream mInputStream;
+
+        @Override
+        public Resources getResources() { return mResources; }
+
+        @Override
+        public ImageDecoder createImageDecoder() {
+            // FIXME: Can I bypass creating the stream?
+            try {
+                mInputStream = mResources.openRawResource(mResId);
+            } catch (Resources.NotFoundException e) {
+                // This should never happen, since we already tested in the
+                // constructor.
+            }
+            if (!(mInputStream instanceof AssetManager.AssetInputStream)) {
+                // This should never happen.
+                throw new RuntimeException("Resource is not an asset?");
+            }
+            long asset = ((AssetManager.AssetInputStream) mInputStream).getNativeAsset();
+            return nCreate(asset);
+        }
+
+        @Override
+        public void close() {
+            try {
+                mInputStream.close();
+            } catch (IOException e) {
+            } finally {
+                mInputStream = null;
+            }
+        }
+    }
+
+    /**
+     *  Contains information about the encoded image.
+     */
+    public static class ImageInfo {
+        public final int width;
+        public final int height;
+        // TODO?: Add more info? mimetype, ninepatch etc?
+
+        ImageInfo(int width, int height) {
+            this.width = width;
+            this.height = height;
+        }
+    };
+
+    /**
+     *  Used if the provided data is incomplete.
+     *
+     *  There may be a partial image to display.
+     */
+    public class IncompleteException extends Exception {};
+
+    /**
+     *  Used if the provided data is corrupt.
+     *
+     *  There may be a partial image to display.
+     */
+    public class CorruptException extends Exception {};
+
+    /**
+     *  Optional listener supplied to {@link #decodeDrawable} or
+     *  {@link #decodeBitmap}.
+     */
+    public static interface OnHeaderDecodedListener {
+        /**
+         *  Called when the header is decoded and the size is known.
+         *
+         *  @param info Information about the encoded image.
+         *  @param decoder allows changing the default settings of the decode.
+         */
+        public void onHeaderDecoded(ImageInfo info, ImageDecoder decoder);
+
+    };
+
+    /**
+     *  Optional listener supplied to the ImageDecoder.
+     */
+    public static interface OnExceptionListener {
+        /**
+         *  Called when there is a problem in the stream or in the data.
+         *  FIXME: Or do not allow streams?
+         *  FIXME: Report how much of the image has been decoded?
+         *
+         *  @param e Exception containing information about the error.
+         *  @return True to create and return a {@link Drawable}/
+         *      {@link Bitmap} with partial data. False to return
+         *      {@code null}. True is the default.
+         */
+        public boolean onException(Exception e);
+    };
+
+    // Fields
+    private long      mNativePtr;
+    private final int mWidth;
+    private final int mHeight;
+
+    private int     mDesiredWidth;
+    private int     mDesiredHeight;
+    private int     mAllocator = DEFAULT_ALLOCATOR;
+    private boolean mRequireUnpremultiplied = false;
+    private boolean mMutable = false;
+    private boolean mPreferRamOverQuality = false;
+    private boolean mAsAlphaMask = false;
+    private Rect    mCropRect;
+
+    private PostProcess         mPostProcess;
+    private OnExceptionListener mOnExceptionListener;
+
+
+    /**
+     * Private constructor called by JNI. {@link #recycle} must be
+     * called after decoding to delete native resources.
+     */
+    @SuppressWarnings("unused")
+    private ImageDecoder(long nativePtr, int width, int height) {
+        mNativePtr = nativePtr;
+        mWidth = width;
+        mHeight = height;
+        mDesiredWidth = width;
+        mDesiredHeight = height;
+    }
+
+    /**
+     * Create a new {@link Source} from an asset.
+     *
+     * @param res the {@link Resources} object containing the image data.
+     * @param resId resource ID of the image data.
+     *      // FIXME: Can be an @DrawableRes?
+     * @return a new Source object, which can be passed to
+     *      {@link #decodeDrawable} or {@link #decodeBitmap}.
+     * @throws Resources.NotFoundException if the asset does not exist.
+     */
+    public static Source createSource(@NonNull Resources res, @RawRes int resId)
+            throws Resources.NotFoundException {
+        return new ResourceSource(res, resId);
+    }
+
+    /**
+     * Create a new {@link Source} from a byte array.
+     * @param data byte array of compressed image data.
+     * @param offset offset into data for where the decoder should begin
+     *      parsing.
+     * @param length number of bytes, beginning at offset, to parse.
+     * @throws NullPointerException if data is null.
+     * @throws ArrayIndexOutOfBoundsException if offset and length are
+     *      not within data.
+     */
+    // TODO: Overloads that don't use offset, length
+    public static Source createSource(@NonNull byte[] data, int offset,
+            int length) throws ArrayIndexOutOfBoundsException {
+        if (data == null) {
+            throw new NullPointerException("null byte[] in createSource!");
+        }
+        if (offset < 0 || length < 0 || offset >= data.length ||
+                offset + length > data.length) {
+            throw new ArrayIndexOutOfBoundsException(
+                    "invalid offset/length!");
+        }
+        return new ByteArraySource(data, offset, length);
+    }
+
+    /**
+     * Create a new {@link Source} from a {@link java.nio.ByteBuffer}.
+     *
+     * The returned {@link Source} effectively takes ownership of the
+     * {@link java.nio.ByteBuffer}; i.e. no other code should modify it after
+     * this call.
+     *
+     * Decoding will start from {@link java.nio.ByteBuffer#position()}.
+     */
+    public static Source createSource(ByteBuffer buffer) {
+        return new ByteBufferSource(buffer);
+    }
+
+    /**
+     *  Return the width and height of a given sample size.
+     *
+     *  This takes an input that functions like
+     *  {@link BitmapFactory.Options#inSampleSize}. It returns a width and
+     *  height that can be acheived by sampling the encoded image. Other widths
+     *  and heights may be supported, but will require an additional (internal)
+     *  scaling step. Such internal scaling is *not* supported with
+     *  {@link #requireUnpremultiplied}.
+     *
+     *  @param sampleSize Sampling rate of the encoded image.
+     *  @return Point {@link Point#x} and {@link Point#y} correspond to the
+     *      width and height after sampling.
+     */
+    public Point getSampledSize(int sampleSize) {
+        if (sampleSize <= 0) {
+            throw new IllegalArgumentException("sampleSize must be positive! "
+                    + "provided " + sampleSize);
+        }
+        if (mNativePtr == 0) {
+            throw new IllegalStateException("ImageDecoder is recycled!");
+        }
+
+        return nGetSampledSize(mNativePtr, sampleSize);
+    }
+
+    // Modifiers
+    /**
+     *  Resize the output to have the following size.
+     *
+     *  @param width must be greater than 0.
+     *  @param height must be greater than 0.
+     */
+    public void resize(int width, int height) {
+        if (width <= 0 || height <= 0) {
+            throw new IllegalArgumentException("Dimensions must be positive! "
+                    + "provided (" + width + ", " + height + ")");
+        }
+
+        mDesiredWidth = width;
+        mDesiredHeight = height;
+    }
+
+    /**
+     *  Resize based on a sample size.
+     *
+     *  This has the same effect as passing the result of
+     *  {@link #getSampledSize} to {@link #resize(int, int)}.
+     *
+     *  @param sampleSize Sampling rate of the encoded image.
+     */
+    public void resize(int sampleSize) {
+        Point dimensions = this.getSampledSize(sampleSize);
+        this.resize(dimensions.x, dimensions.y);
+    }
+
+    // These need to stay in sync with ImageDecoder.cpp's Allocator enum.
+    /**
+     *  Use the default allocation for the pixel memory.
+     *
+     *  Will typically result in a {@link Bitmap.Config#HARDWARE}
+     *  allocation, but may be software for small images. In addition, this will
+     *  switch to software when HARDWARE is incompatible, e.g.
+     *  {@link #setMutable}, {@link #setAsAlphaMask}.
+     */
+    public static final int DEFAULT_ALLOCATOR = 0;
+
+    /**
+     *  Use a software allocation for the pixel memory.
+     *
+     *  Useful for drawing to a software {@link Canvas} or for
+     *  accessing the pixels on the final output.
+     */
+    public static final int SOFTWARE_ALLOCATOR = 1;
+
+    /**
+     *  Use shared memory for the pixel memory.
+     *
+     *  Useful for sharing across processes.
+     */
+    public static final int SHARED_MEMORY_ALLOCATOR = 2;
+
+    /**
+     *  Require a {@link Bitmap.Config#HARDWARE} {@link Bitmap}.
+     *
+     *  This will throw an {@link java.lang.IllegalStateException} when combined
+     *  with incompatible options, like {@link #setMutable} or
+     *  {@link #setAsAlphaMask}.
+     */
+    public static final int HARDWARE_ALLOCATOR = 3;
+
+    /** @hide **/
+    @Retention(SOURCE)
+    @IntDef({ DEFAULT_ALLOCATOR, SOFTWARE_ALLOCATOR, SHARED_MEMORY_ALLOCATOR,
+              HARDWARE_ALLOCATOR })
+    public @interface Allocator {};
+
+    /**
+     *  Choose the backing for the pixel memory.
+     *
+     *  This is ignored for animated drawables.
+     *
+     *  TODO: Allow accessing the backing from the Bitmap.
+     *
+     *  @param allocator Type of allocator to use.
+     */
+    public void setAllocator(@Allocator int allocator) {
+        if (allocator < DEFAULT_ALLOCATOR || allocator > HARDWARE_ALLOCATOR) {
+            throw new IllegalArgumentException("invalid allocator " + allocator);
+        }
+        mAllocator = allocator;
+    }
+
+    /**
+     *  Create a {@link Bitmap} with unpremultiplied pixels.
+     *
+     *  By default, ImageDecoder will create a {@link Bitmap} with
+     *  premultiplied pixels, which is required for drawing with the
+     *  {@link android.view.View} system (i.e. to a {@link Canvas}). Calling
+     *  this method will result in {@link #decodeBitmap} returning a
+     *  {@link Bitmap} with unpremultiplied pixels. See
+     *  {@link Bitmap#isPremultiplied}. Incompatible with
+     *  {@link #decodeDrawable}; attempting to decode an unpremultiplied
+     *  {@link Drawable} will throw an {@link java.lang.IllegalStateException}.
+     */
+    public void requireUnpremultiplied() {
+        mRequireUnpremultiplied = true;
+    }
+
+    /**
+     *  Modify the image after decoding and scaling.
+     *
+     *  This allows adding effects prior to returning a {@link Drawable} or
+     *  {@link Bitmap}. For a {@code Drawable} or an immutable {@code Bitmap},
+     *  this is the only way to process the image after decoding.
+     *
+     *  If set on a nine-patch image, the nine-patch data is ignored.
+     *
+     *  For an animated image, the drawing commands drawn on the {@link Canvas}
+     *  will be recorded immediately and then applied to each frame.
+     */
+    public void setPostProcess(PostProcess p) {
+        mPostProcess = p;
+    }
+
+    /**
+     *  Set (replace) the {@link OnExceptionListener} on this object.
+     *
+     *  Will be called if there is an error in the input. Without one, a
+     *  partial {@link Bitmap} will be created.
+     */
+    public void setOnExceptionListener(OnExceptionListener l) {
+        mOnExceptionListener = l;
+    }
+
+    /**
+     *  Crop the output to {@code subset} of the (possibly) scaled image.
+     *
+     *  {@code subset} must be contained within the size set by {@link #resize}
+     *  or the bounds of the image if resize was not called. Otherwise an
+     *  {@link IllegalStateException} will be thrown.
+     *
+     *  NOT intended as a replacement for
+     *  {@link BitmapRegionDecoder#decodeRegion}. This supports all formats,
+     *  but merely crops the output.
+     */
+    public void crop(Rect subset) {
+        mCropRect = subset;
+    }
+
+    /**
+     *  Create a mutable {@link Bitmap}.
+     *
+     *  By default, a {@link Bitmap} created will be immutable, but that can be
+     *  changed with this call.
+     *
+     *  Incompatible with {@link #HARDWARE_ALLOCATOR}, because
+     *  {@link Bitmap.Config#HARDWARE} Bitmaps cannot be mutable. Attempting to
+     *  combine them will throw an {@link java.lang.IllegalStateException}.
+     *
+     *  Incompatible with {@link #decodeDrawable}, which would require
+     *  retrieving the Bitmap from the returned Drawable in order to modify.
+     *  Attempting to decode a mutable {@link Drawable} will throw an
+     *  {@link java.lang.IllegalStateException}
+     */
+    public void setMutable() {
+        mMutable = true;
+    }
+
+    /**
+     *  Potentially save RAM at the expense of quality.
+     *
+     *  This may result in a {@link Bitmap} with a denser {@link Bitmap.Config},
+     *  depending on the image. For example, for an opaque {@link Bitmap}, this
+     *  may result in a {@link Bitmap.Config} with no alpha information.
+     */
+    public void setPreferRamOverQuality() {
+        mPreferRamOverQuality = true;
+    }
+
+    /**
+     *  Potentially treat the output as an alpha mask.
+     *
+     *  If the image is encoded in a format with only one channel, treat that
+     *  channel as alpha. Otherwise this call has no effect.
+     *
+     *  Incompatible with {@link #HARDWARE_ALLOCATOR}. Trying to combine them
+     *  will throw an {@link java.lang.IllegalStateException}.
+     */
+    public void setAsAlphaMask() {
+        mAsAlphaMask = true;
+    }
+
+    /**
+     *  Clean up resources.
+     *
+     *  ImageDecoder has a private constructor, and will always be recycled
+     *  by decodeDrawable or decodeBitmap which creates it, so there is no
+     *  need for a finalizer.
+     */
+    private void recycle() {
+        if (mNativePtr == 0) {
+            return;
+        }
+        nRecycle(mNativePtr);
+        mNativePtr = 0;
+    }
+
+    private void checkState() {
+        if (mNativePtr == 0) {
+            throw new IllegalStateException("Cannot reuse ImageDecoder.Source!");
+        }
+
+        checkSubset(mDesiredWidth, mDesiredHeight, mCropRect);
+
+        if (mAllocator == HARDWARE_ALLOCATOR) {
+            if (mMutable) {
+                throw new IllegalStateException("Cannot make mutable HARDWARE Bitmap!");
+            }
+            if (mAsAlphaMask) {
+                throw new IllegalStateException("Cannot make HARDWARE Alpha mask Bitmap!");
+            }
+        }
+
+        if (mPostProcess != null && mRequireUnpremultiplied) {
+            throw new IllegalStateException("Cannot draw to unpremultiplied pixels!");
+        }
+    }
+
+    private static void checkSubset(int width, int height, Rect r) {
+        if (r == null) {
+            return;
+        }
+        if (r.left < 0 || r.top < 0 || r.right > width || r.bottom > height) {
+            throw new IllegalStateException("Subset " + r + " not contained by "
+                    + "scaled image bounds: (" + width + " x " + height + ")");
+        }
+    }
+
+    /**
+     *  Create a {@link Drawable}.
+     */
+    public static Drawable decodeDrawable(Source src, OnHeaderDecodedListener listener) {
+        ImageDecoder decoder = src.createImageDecoder();
+        if (decoder == null) {
+            return null;
+        }
+
+        if (listener != null) {
+            ImageInfo info = new ImageInfo(decoder.mWidth, decoder.mHeight);
+            listener.onHeaderDecoded(info, decoder);
+        }
+
+        decoder.checkState();
+
+        if (decoder.mRequireUnpremultiplied) {
+            // Though this could be supported (ignored) for opaque images, it
+            // seems better to always report this error.
+            throw new IllegalStateException("Cannot decode a Drawable with" +
+                                            " unpremultiplied pixels!");
+        }
+
+        if (decoder.mMutable) {
+            throw new IllegalStateException("Cannot decode a mutable Drawable!");
+        }
+
+        try {
+            Bitmap bm = nDecodeBitmap(decoder.mNativePtr,
+                                      decoder.mOnExceptionListener,
+                                      decoder.mPostProcess,
+                                      decoder.mDesiredWidth, decoder.mDesiredHeight,
+                                      decoder.mCropRect,
+                                      false,    // decoder.mMutable
+                                      decoder.mAllocator,
+                                      false,    // decoder.mRequireUnpremultiplied
+                                      decoder.mPreferRamOverQuality,
+                                      decoder.mAsAlphaMask
+                                      );
+            if (bm == null) {
+                return null;
+            }
+
+            Resources res = src.getResources();
+            if (res == null) {
+                bm.setDensity(Bitmap.DENSITY_NONE);
+            }
+
+            byte[] np = bm.getNinePatchChunk();
+            if (np != null && NinePatch.isNinePatchChunk(np)) {
+                Rect opticalInsets = new Rect();
+                bm.getOpticalInsets(opticalInsets);
+                Rect padding = new Rect();
+                nGetPadding(decoder.mNativePtr, padding);
+                return new NinePatchDrawable(res, bm, np, padding,
+                        opticalInsets, null);
+            }
+
+            // TODO: Handle animation.
+            return new BitmapDrawable(res, bm);
+        } finally {
+            decoder.recycle();
+            src.close();
+        }
+    }
+
+    /**
+     * Create a {@link Bitmap}.
+     */
+    public static Bitmap decodeBitmap(Source src, OnHeaderDecodedListener listener) {
+        ImageDecoder decoder = src.createImageDecoder();
+        if (decoder == null) {
+            return null;
+        }
+
+        if (listener != null) {
+            ImageInfo info = new ImageInfo(decoder.mWidth, decoder.mHeight);
+            listener.onHeaderDecoded(info, decoder);
+        }
+
+        decoder.checkState();
+
+        try {
+            return nDecodeBitmap(decoder.mNativePtr,
+                                 decoder.mOnExceptionListener,
+                                 decoder.mPostProcess,
+                                 decoder.mDesiredWidth, decoder.mDesiredHeight,
+                                 decoder.mCropRect,
+                                 decoder.mMutable,
+                                 decoder.mAllocator,
+                                 decoder.mRequireUnpremultiplied,
+                                 decoder.mPreferRamOverQuality,
+                                 decoder.mAsAlphaMask);
+        } finally {
+            decoder.recycle();
+            src.close();
+        }
+    }
+
+    private static native ImageDecoder nCreate(long asset);
+    private static native ImageDecoder nCreate(ByteBuffer buffer,
+                                               int position,
+                                               int limit);
+    private static native ImageDecoder nCreate(byte[] data, int offset,
+                                               int length);
+    private static native Bitmap nDecodeBitmap(long nativePtr,
+            OnExceptionListener listener,
+            PostProcess postProcess,
+            int width, int height,
+            Rect cropRect, boolean mutable,
+            int allocator, boolean requireUnpremul,
+            boolean preferRamOverQuality, boolean asAlphaMask);
+    private static native Point nGetSampledSize(long nativePtr,
+                                                int sampleSize);
+    private static native void nGetPadding(long nativePtr, Rect outRect);
+    private static native void nRecycle(long nativePtr);
+}
diff --git a/graphics/java/android/graphics/PostProcess.java b/graphics/java/android/graphics/PostProcess.java
new file mode 100644
index 0000000..c5a31e8
--- /dev/null
+++ b/graphics/java/android/graphics/PostProcess.java
@@ -0,0 +1,91 @@
+/*
+ * 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 android.graphics;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.graphics.drawable.Drawable;
+
+
+/**
+ *  Helper interface for adding custom processing to an image.
+ *
+ *  The image being processed may be a {@link Drawable}, {@link Bitmap} or frame
+ *  of an animated image produced by {@link ImageDecoder}. This is called before
+ *  the requested object is returned.
+ *
+ *  This custom processing also applies to image types that are otherwise
+ *  immutable, such as {@link Bitmap.Config#HARDWARE}.
+ *
+ *  On an animated image, the callback will only be called once, but the drawing
+ *  commands will be applied to each frame, as if the {@code Canvas} had been
+ *  returned by {@link Picture#beginRecording}.
+ *
+ *  Supplied to ImageDecoder via {@link ImageDecoder#setPostProcess}.
+ *  @hide
+ */
+public interface PostProcess {
+    /**
+     *  Do any processing after (for example) decoding.
+     *
+     *  Drawing to the {@link Canvas} will behave as if the initial processing
+     *  (e.g. decoding) already exists in the Canvas. An implementation can draw
+     *  effects on top of this, or it can even draw behind it using
+     *  {@link PorterDuff.Mode#DST_OVER}. A common effect is to add transparency
+     *  to the corners to achieve rounded corners. That can be done with the
+     *  following code:
+     *
+     *  <code>
+     *      Path path = new Path();
+     *      path.setFillType(Path.FillType.INVERSE_EVEN_ODD);
+     *      path.addRoundRect(0, 0, width, height, 20, 20, Path.Direction.CW);
+     *      Paint paint = new Paint();
+     *      paint.setAntiAlias(true);
+     *      paint.setColor(Color.TRANSPARENT);
+     *      paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
+     *      canvas.drawPath(path, paint);
+     *      return PixelFormat.TRANSLUCENT;
+     *  </code>
+     *
+     *
+     *  @param canvas The {@link Canvas} to draw to.
+     *  @param width Width of {@code canvas}. Anything drawn outside of this
+     *      will be ignored.
+     *  @param height Height of {@code canvas}. Anything drawn outside of this
+     *      will be ignored.
+     *  @return Opacity of the result after drawing.
+     *      {@link PixelFormat#UNKNOWN} means that the implementation did not
+     *      change whether the image has alpha. Return this unless you added
+     *      transparency (e.g. with the code above, in which case you should
+     *      return {@code PixelFormat.TRANSLUCENT}) or you forced the image to
+     *      be opaque (e.g. by drawing everywhere with an opaque color and
+     *      {@code PorterDuff.Mode.DST_OVER}, in which case you should return
+     *      {@code PixelFormat.OPAQUE}).
+     *      {@link PixelFormat#TRANSLUCENT} means that the implementation added
+     *      transparency. This is safe to return even if the image already had
+     *      transparency. This is also safe to return if the result is opaque,
+     *      though it may draw more slowly.
+     *      {@link PixelFormat#OPAQUE} means that the implementation forced the
+     *      image to be opaque. This is safe to return even if the image was
+     *      already opaque.
+     *      {@link PixelFormat#TRANSPARENT} (or any other integer) is not
+     *      allowed, and will result in throwing an
+     *      {@link java.lang.IllegalArgumentException}.
+     */
+    @PixelFormat.Opacity
+    public int postProcess(@NonNull Canvas canvas, int width, int height);
+}
diff --git a/keystore/java/android/security/IKeyChainService.aidl b/keystore/java/android/security/IKeyChainService.aidl
index eca52cc..7c7417d 100644
--- a/keystore/java/android/security/IKeyChainService.aidl
+++ b/keystore/java/android/security/IKeyChainService.aidl
@@ -35,6 +35,7 @@
 
     boolean generateKeyPair(in String algorithm, in ParcelableKeyGenParameterSpec spec);
     boolean attestKey(in String alias, in byte[] challenge, out KeymasterCertificateChain chain);
+    boolean setKeyPairCertificate(String alias, in byte[] userCert, in byte[] certChain);
 
     // APIs used by CertInstaller and DevicePolicyManager
     String installCaCertificate(in byte[] caCertificate);
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 3fe75cf..5b95c81 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -6880,6 +6880,9 @@
         return UNKNOWN_ERROR;
     }
 
+    // The number of resources overlaid that were not explicitly marked overlayable.
+    size_t forcedOverlayCount = 0u;
+
     KeyedVector<uint8_t, IdmapTypeMap> map;
 
     // overlaid packages are assumed to contain only one package group
@@ -6919,6 +6922,7 @@
                 continue;
             }
 
+            uint32_t typeSpecFlags = 0u;
             const String16 overlayType(resName.type, resName.typeLen);
             const String16 overlayName(resName.name, resName.nameLen);
             uint32_t overlayResID = overlay.identifierForName(overlayName.string(),
@@ -6926,14 +6930,23 @@
                                                               overlayType.string(),
                                                               overlayType.size(),
                                                               overlayPackage.string(),
-                                                              overlayPackage.size());
+                                                              overlayPackage.size(),
+                                                              &typeSpecFlags);
             if (overlayResID == 0) {
+                // No such target resource was found.
                 if (typeMap.entryMap.isEmpty()) {
                     typeMap.entryOffset++;
                 }
                 continue;
             }
 
+            // Now that we know this is being overlaid, check if it can be, and emit a warning if
+            // it can't.
+            if ((dtohl(typeConfigs->typeSpecFlags[entryIndex]) &
+                    ResTable_typeSpec::SPEC_OVERLAYABLE) == 0) {
+                forcedOverlayCount++;
+            }
+
             if (typeMap.overlayTypeId == -1) {
                 typeMap.overlayTypeId = Res_GETTYPE(overlayResID) + 1;
             }
@@ -7012,6 +7025,10 @@
         typeData += entryCount * 2;
     }
 
+    if (forcedOverlayCount > 0) {
+        ALOGW("idmap: overlaid %zu resources not marked overlayable", forcedOverlayCount);
+    }
+
     return NO_ERROR;
 }
 
diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h
index 20d0178..8cf4de9 100644
--- a/libs/androidfw/include/androidfw/ResourceTypes.h
+++ b/libs/androidfw/include/androidfw/ResourceTypes.h
@@ -1339,9 +1339,13 @@
     // Number of uint32_t entry configuration masks that follow.
     uint32_t entryCount;
 
-    enum {
+    enum : uint32_t {
         // Additional flag indicating an entry is public.
-        SPEC_PUBLIC = 0x40000000
+        SPEC_PUBLIC = 0x40000000u,
+
+        // Additional flag indicating an entry is overlayable at runtime.
+        // Added in Android-P.
+        SPEC_OVERLAYABLE = 0x80000000u,
     };
 };
 
diff --git a/libs/androidfw/tests/data/basic/basic.apk b/libs/androidfw/tests/data/basic/basic.apk
index 0c17328..18ef75e 100644
--- a/libs/androidfw/tests/data/basic/basic.apk
+++ b/libs/androidfw/tests/data/basic/basic.apk
Binary files differ
diff --git a/libs/androidfw/tests/data/basic/basic_de_fr.apk b/libs/androidfw/tests/data/basic/basic_de_fr.apk
index e45258c..767dff6 100644
--- a/libs/androidfw/tests/data/basic/basic_de_fr.apk
+++ b/libs/androidfw/tests/data/basic/basic_de_fr.apk
Binary files differ
diff --git a/libs/androidfw/tests/data/basic/basic_hdpi-v4.apk b/libs/androidfw/tests/data/basic/basic_hdpi-v4.apk
index 4ae1a7c..58953f5 100644
--- a/libs/androidfw/tests/data/basic/basic_hdpi-v4.apk
+++ b/libs/androidfw/tests/data/basic/basic_hdpi-v4.apk
Binary files differ
diff --git a/libs/androidfw/tests/data/basic/basic_xhdpi-v4.apk b/libs/androidfw/tests/data/basic/basic_xhdpi-v4.apk
index a240d4c..103f656 100644
--- a/libs/androidfw/tests/data/basic/basic_xhdpi-v4.apk
+++ b/libs/androidfw/tests/data/basic/basic_xhdpi-v4.apk
Binary files differ
diff --git a/libs/androidfw/tests/data/basic/basic_xxhdpi-v4.apk b/libs/androidfw/tests/data/basic/basic_xxhdpi-v4.apk
index fd3d9b2..61369d5 100644
--- a/libs/androidfw/tests/data/basic/basic_xxhdpi-v4.apk
+++ b/libs/androidfw/tests/data/basic/basic_xxhdpi-v4.apk
Binary files differ
diff --git a/libs/androidfw/tests/data/basic/build b/libs/androidfw/tests/data/basic/build
index d619800..5682ed4 100755
--- a/libs/androidfw/tests/data/basic/build
+++ b/libs/androidfw/tests/data/basic/build
@@ -19,11 +19,15 @@
 
 PATH_TO_FRAMEWORK_RES=${ANDROID_BUILD_TOP}/prebuilts/sdk/current/android.jar
 
-aapt package \
-    -M AndroidManifest.xml \
-    -S res \
-    -A assets \
+aapt2 compile --dir res -o compiled.flata
+aapt2 link \
     -I $PATH_TO_FRAMEWORK_RES \
-    --split hdpi --split xhdpi --split xxhdpi --split fr,de \
-    -F basic.apk \
-    -f
+    --manifest AndroidManifest.xml \
+    -A assets \
+    --split basic_hdpi-v4.apk:hdpi \
+    --split basic_xhdpi-v4.apk:xhdpi \
+    --split basic_xxhdpi-v4.apk:xxhdpi \
+    --split basic_de_fr.apk:de,fr \
+    -o basic.apk \
+    compiled.flata
+rm compiled.flata
diff --git a/libs/androidfw/tests/data/basic/res/values/values.xml b/libs/androidfw/tests/data/basic/res/values/values.xml
index 638c983..6c47459 100644
--- a/libs/androidfw/tests/data/basic/res/values/values.xml
+++ b/libs/androidfw/tests/data/basic/res/values/values.xml
@@ -60,4 +60,9 @@
         <item>2</item>
         <item>3</item>
     </integer-array>
+
+    <overlayable>
+        <item type="string" name="test2" />
+        <item type="array" name="integerArray1" />
+    </overlayable>
 </resources>
diff --git a/libs/androidfw/tests/data/overlay/build b/libs/androidfw/tests/data/overlay/build
index 112f373..716b1bd 100755
--- a/libs/androidfw/tests/data/overlay/build
+++ b/libs/androidfw/tests/data/overlay/build
@@ -17,4 +17,6 @@
 
 set -e
 
-aapt package -M AndroidManifest.xml -S res -F overlay.apk -f
+aapt2 compile --dir res -o compiled.flata
+aapt2 link --manifest AndroidManifest.xml -o overlay.apk compiled.flata
+rm compiled.flata
diff --git a/libs/androidfw/tests/data/overlay/overlay.apk b/libs/androidfw/tests/data/overlay/overlay.apk
index 40bf17c..33f9611 100644
--- a/libs/androidfw/tests/data/overlay/overlay.apk
+++ b/libs/androidfw/tests/data/overlay/overlay.apk
Binary files differ
diff --git a/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java b/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
index a8262c8..974b2a4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
+++ b/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
@@ -271,7 +271,7 @@
      * @param now The current time, used to tell whether daylight savings is active.
      * @return A CharSequence suitable for display as the offset label of {@code tz}.
      */
-    private static CharSequence getGmtOffsetText(TimeZoneFormat tzFormatter, Locale locale,
+    public static CharSequence getGmtOffsetText(TimeZoneFormat tzFormatter, Locale locale,
             TimeZone tz, Date now) {
         final SpannableStringBuilder builder = new SpannableStringBuilder();
 
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/res/drawable/ic_cast.xml b/packages/SystemUI/res/drawable/ic_cast.xml
new file mode 100644
index 0000000..b86dfea
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_cast.xml
@@ -0,0 +1,31 @@
+<!--
+  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.0"
+        android:viewportHeight="24.0"
+        android:tint="?android:attr/colorControlNormal">
+    <path
+        android:fillColor="#FFFFFFFF"
+        android:pathData="M1 18v2c0 .55 .45 1 1 1h2c0-1.66-1.34-3-3-3zm0-2.94c-.01 .51 .32 .93 .82 1.02
+2.08 .36 3.74 2 4.1 4.08 .09 .48 .5 .84 .99 .84 .61 0 1.09-.54 1-1.14a6.996
+6.996 0 0 0-5.8-5.78c-.59-.09-1.09 .38 -1.11 .98 zm0-4.03c-.01 .52 .34 .96 .85
+1.01 4.26 .43 7.68 3.82 8.1 8.08 .05 .5 .48 .88 .99 .88 .59 0 1.06-.51
+1-1.1-.52-5.21-4.66-9.34-9.87-9.85-.57-.05-1.05 .4 -1.07 .98 zM21 3H3c-1.1 0-2
+.9-2 2v3h2V5h18v14h-7v2h7c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2z"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_speaker.xml b/packages/SystemUI/res/drawable/ic_speaker.xml
new file mode 100644
index 0000000..1ea293c
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_speaker.xml
@@ -0,0 +1,26 @@
+<!--
+  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.0"
+        android:viewportHeight="24.0"
+        android:tint="?android:attr/colorControlNormal" >
+    <path
+        android:pathData="M17,2L7,2c-1.1,0 -2,0.9 -2,2v16c0,1.1 0.9,1.99 2,1.99L17,22c1.1,0 2,-0.9 2,-2L19,4c0,-1.1 -0.9,-2 -2,-2zM12,4c1.1,0 2,0.9 2,2s-0.9,2 -2,2c-1.11,0 -2,-0.9 -2,-2s0.89,-2 2,-2zM12,20c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5zM12,12c-1.66,0 -3,1.34 -3,3s1.34,3 3,3 3,-1.34 3,-3 -1.34,-3 -3,-3z"
+        android:fillColor="#FFFFFFFF"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_speaker_group.xml b/packages/SystemUI/res/drawable/ic_speaker_group.xml
new file mode 100644
index 0000000..d6867d7
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_speaker_group.xml
@@ -0,0 +1,32 @@
+<!--
+  Copyright (C) 2017 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="24dp"
+        android:height="24dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0"
+        android:tint="?android:attr/colorControlNormal" >
+    <path
+        android:pathData="M18.2,1L9.8,1C8.81,1 8,1.81 8,2.8v14.4c0,0.99 0.81,1.79 1.8,1.79l8.4,0.01c0.99,0 1.8,-0.81 1.8,-1.8L20,2.8c0,-0.99 -0.81,-1.8 -1.8,-1.8zM14,3c1.1,0 2,0.89 2,2s-0.9,2 -2,2 -2,-0.89 -2,-2 0.9,-2 2,-2zM14,16.5c-2.21,0 -4,-1.79 -4,-4s1.79,-4 4,-4 4,1.79 4,4 -1.79,4 -4,4z"
+        android:fillColor="#FFFFFFFF"/>
+    <path
+        android:pathData="M14,12.5m-2.5,0a2.5,2.5 0,1 1,5 0a2.5,2.5 0,1 1,-5 0"
+        android:fillColor="#FFFFFFFF"/>
+    <path
+        android:pathData="M6,5H4v16c0,1.1 0.89,2 2,2h10v-2H6V5z"
+        android:fillColor="#FFFFFFFF"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/ic_tv.xml b/packages/SystemUI/res/drawable/ic_tv.xml
new file mode 100644
index 0000000..cc2ae91
--- /dev/null
+++ b/packages/SystemUI/res/drawable/ic_tv.xml
@@ -0,0 +1,26 @@
+<!--
+  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.0"
+        android:viewportHeight="24.0"
+        android:tint="?android:attr/colorControlNormal" >
+    <path
+        android:pathData="M21,3L3,3c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h5v2h8v-2h5c1.1,0 1.99,-0.9 1.99,-2L23,5c0,-1.1 -0.9,-2 -2,-2zM21,17L3,17L3,5h18v12z"
+        android:fillColor="#FFFFFFFF"/>
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/output_chooser.xml b/packages/SystemUI/res/layout/output_chooser.xml
index 22c3bcf..3d0ab35 100644
--- a/packages/SystemUI/res/layout/output_chooser.xml
+++ b/packages/SystemUI/res/layout/output_chooser.xml
@@ -19,6 +19,8 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:sysui="http://schemas.android.com/apk/res-auto"
     android:id="@+id/output_chooser"
+    android:minWidth="320dp"
+    android:minHeight="320dp"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:padding="20dp" >
@@ -39,12 +41,6 @@
         android:gravity="center"
         android:orientation="vertical">
 
-        <ImageView
-            android:id="@android:id/icon"
-            android:layout_width="56dp"
-            android:layout_height="56dp"
-            android:tint="?android:attr/textColorSecondary" />
-
         <TextView
             android:id="@android:id/title"
             android:layout_width="wrap_content"
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 78e621e..fd205dd 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1269,6 +1269,14 @@
     <string name="volume_dialog_accessibility_shown_message">%s volume controls shown. Swipe up to dismiss.</string>
     <string name="volume_dialog_accessibility_dismissed_message">Volume controls hidden</string>
 
+    <string name="output_title">Media output</string>
+    <string name="output_calls_title">Phone call output</string>
+    <string name="output_none_found">No devices found</string>
+    <string name="output_none_found_service_off">No devices found. Try turning on <xliff:g id="service" example="Bluetooth">%1$s</xliff:g></string>
+    <string name="output_service_bt">Bluetooth</string>
+    <string name="output_service_wifi">Wi-Fi</string>
+    <string name="output_service_bt_wifi">Bluetooth and Wi-Fi</string>
+
     <!-- Name of special SystemUI debug settings -->
     <string name="system_ui_tuner">System UI Tuner</string>
 
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..91cae0af 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/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
index 8abc3f4..14329b5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java
@@ -255,8 +255,13 @@
             mKeyguardFadeoutAnimation.cancel();
         }
 
-        // Do not let the device sleep until we're done with all animations
-        holdWakeLock();
+        // The device might sleep if it's entering AOD, we need to make sure that
+        // the animation plays properly until the last frame.
+        // It's important to avoid holding the wakelock unless necessary because
+        // WakeLock#aqcuire will trigger an IPC and will cause jank.
+        if (mState == ScrimState.AOD) {
+            holdWakeLock();
+        }
 
         // AOD wallpapers should fade away after a while
         if (mWallpaperSupportsAmbientMode && mDozeParameters.getAlwaysOn()
diff --git a/packages/SystemUI/src/com/android/systemui/volume/OutputChooserDialog.java b/packages/SystemUI/src/com/android/systemui/volume/OutputChooserDialog.java
index fa82e33..f8843a9 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/OutputChooserDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/OutputChooserDialog.java
@@ -16,6 +16,10 @@
 
 package com.android.systemui.volume;
 
+import static android.support.v7.media.MediaRouter.RouteInfo.CONNECTION_STATE_CONNECTED;
+import static android.support.v7.media.MediaRouter.RouteInfo.CONNECTION_STATE_CONNECTING;
+import static android.support.v7.media.MediaRouter.UNSELECT_REASON_DISCONNECTED;
+
 import static com.android.settingslib.bluetooth.Utils.getBtClassDrawableWithDescription;
 
 import android.bluetooth.BluetoothClass;
@@ -27,7 +31,15 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.net.wifi.WifiManager;
 import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.SystemClock;
+import android.support.v7.media.MediaControlIntent;
+import android.support.v7.media.MediaRouteSelector;
+import android.support.v7.media.MediaRouter;
 import android.util.Log;
 import android.util.Pair;
 
@@ -38,8 +50,13 @@
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 import com.android.systemui.statusbar.policy.BluetoothController;
 
+import java.io.IOException;
+import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
 
 public class OutputChooserDialog extends SystemUIDialog
         implements DialogInterface.OnDismissListener, OutputChooserLayout.Callback {
@@ -47,15 +64,33 @@
     private static final String TAG = Util.logTag(OutputChooserDialog.class);
     private static final int MAX_DEVICES = 10;
 
+    private static final long UPDATE_DELAY_MS = 300L;
+    static final int MSG_UPDATE_ITEMS = 1;
+
     private final Context mContext;
     private final BluetoothController mController;
+    private final WifiManager mWifiManager;
     private OutputChooserLayout mView;
+    private final MediaRouter mRouter;
+    private final MediaRouterCallback mRouterCallback;
+    private long mLastUpdateTime;
 
+    private final MediaRouteSelector mRouteSelector;
+    private Drawable mDefaultIcon;
+    private Drawable mTvIcon;
+    private Drawable mSpeakerIcon;
+    private Drawable mSpeakerGroupIcon;
 
     public OutputChooserDialog(Context context) {
         super(context);
         mContext = context;
         mController = Dependency.get(BluetoothController.class);
+        mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
+        mRouter = MediaRouter.getInstance(context);
+        mRouterCallback = new MediaRouterCallback();
+        mRouteSelector = new MediaRouteSelector.Builder()
+                .addControlCategory(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK)
+                .build();
 
         final IntentFilter filter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
         context.registerReceiver(mReceiver, filter);
@@ -67,10 +102,21 @@
         setContentView(R.layout.output_chooser);
         setCanceledOnTouchOutside(true);
         setOnDismissListener(this::onDismiss);
+        setTitle(R.string.output_title);
+
         mView = findViewById(R.id.output_chooser);
         mView.setCallback(this);
-        updateItems();
-        mController.addCallback(mCallback);
+
+        mDefaultIcon = mContext.getDrawable(R.drawable.ic_cast);
+        mTvIcon = mContext.getDrawable(R.drawable.ic_tv);
+        mSpeakerIcon = mContext.getDrawable(R.drawable.ic_speaker);
+        mSpeakerGroupIcon = mContext.getDrawable(R.drawable.ic_speaker_group);
+
+        final boolean wifiOff = !mWifiManager.isWifiEnabled();
+        final boolean btOff = !mController.isBluetoothEnabled();
+        if (wifiOff || btOff) {
+            mView.setEmptyState(getDisabledServicesMessage(wifiOff, btOff));
+        }
     }
 
     protected void cleanUp() {}
@@ -82,43 +128,97 @@
     }
 
     @Override
+    public void onAttachedToWindow() {
+        super.onAttachedToWindow();
+
+        mRouter.addCallback(mRouteSelector, mRouterCallback,
+                MediaRouter.CALLBACK_FLAG_PERFORM_ACTIVE_SCAN);
+        mController.addCallback(mCallback);
+    }
+
+    @Override
+    public void onDetachedFromWindow() {
+        mRouter.removeCallback(mRouterCallback);
+        mController.removeCallback(mCallback);
+        super.onDetachedFromWindow();
+    }
+
+    @Override
     public void onDismiss(DialogInterface unused) {
         mContext.unregisterReceiver(mReceiver);
-        mController.removeCallback(mCallback);
         cleanUp();
     }
 
     @Override
     public void onDetailItemClick(OutputChooserLayout.Item item) {
         if (item == null || item.tag == null) return;
-        final CachedBluetoothDevice device = (CachedBluetoothDevice) item.tag;
-        if (device != null && device.getMaxConnectionState()
-                == BluetoothProfile.STATE_DISCONNECTED) {
-            mController.connect(device);
+        if (item.deviceType == OutputChooserLayout.Item.DEVICE_TYPE_BT) {
+            final CachedBluetoothDevice device = (CachedBluetoothDevice) item.tag;
+            if (device != null && device.getMaxConnectionState()
+                    == BluetoothProfile.STATE_DISCONNECTED) {
+                mController.connect(device);
+            }
+        } else if (item.deviceType == OutputChooserLayout.Item.DEVICE_TYPE_MEDIA_ROUTER) {
+            final MediaRouter.RouteInfo route = (MediaRouter.RouteInfo) item.tag;
+            if (route.isEnabled()) {
+                route.select();
+            }
         }
     }
 
     @Override
     public void onDetailItemDisconnect(OutputChooserLayout.Item item) {
         if (item == null || item.tag == null) return;
-        final CachedBluetoothDevice device = (CachedBluetoothDevice) item.tag;
-        if (device != null) {
-            mController.disconnect(device);
+        if (item.deviceType == OutputChooserLayout.Item.DEVICE_TYPE_BT) {
+            final CachedBluetoothDevice device = (CachedBluetoothDevice) item.tag;
+            if (device != null) {
+                mController.disconnect(device);
+            }
+        } else if (item.deviceType == OutputChooserLayout.Item.DEVICE_TYPE_MEDIA_ROUTER) {
+            mRouter.unselect(UNSELECT_REASON_DISCONNECTED);
         }
     }
 
     private void updateItems() {
-        if (mView == null) return;
-        if (mController.isBluetoothEnabled()) {
-            mView.setEmptyState(R.drawable.ic_qs_bluetooth_detail_empty,
-                    R.string.quick_settings_bluetooth_detail_empty_text);
-            mView.setItemsVisible(true);
-        } else {
-            mView.setEmptyState(R.drawable.ic_qs_bluetooth_detail_empty,
-                    R.string.bt_is_off);
-            mView.setItemsVisible(false);
+        if (SystemClock.uptimeMillis() - mLastUpdateTime < UPDATE_DELAY_MS) {
+            mHandler.removeMessages(MSG_UPDATE_ITEMS);
+            mHandler.sendMessageAtTime(mHandler.obtainMessage(MSG_UPDATE_ITEMS),
+                    mLastUpdateTime + UPDATE_DELAY_MS);
+            return;
         }
+        mLastUpdateTime = SystemClock.uptimeMillis();
+        if (mView == null) return;
         ArrayList<OutputChooserLayout.Item> items = new ArrayList<>();
+
+        // Add bluetooth devices
+        addBluetoothDevices(items);
+
+        // Add remote displays
+        addRemoteDisplayRoutes(items);
+
+        Collections.sort(items, ItemComparator.sInstance);
+
+        if (items.size() == 0) {
+            String emptyMessage = mContext.getString(R.string.output_none_found);
+            final boolean wifiOff = !mWifiManager.isWifiEnabled();
+            final boolean btOff = !mController.isBluetoothEnabled();
+            if (wifiOff || btOff) {
+                emptyMessage = getDisabledServicesMessage(wifiOff, btOff);
+            }
+            mView.setEmptyState(emptyMessage);
+        }
+
+        mView.setItems(items.toArray(new OutputChooserLayout.Item[items.size()]));
+    }
+
+    private String getDisabledServicesMessage(boolean wifiOff, boolean btOff) {
+        return mContext.getString(R.string.output_none_found_service_off,
+                wifiOff && btOff ? mContext.getString(R.string.output_service_bt_wifi)
+                        : wifiOff ? mContext.getString(R.string.output_service_wifi)
+                                : mContext.getString(R.string.output_service_bt));
+    }
+
+    private void addBluetoothDevices(List<OutputChooserLayout.Item> items) {
         final Collection<CachedBluetoothDevice> devices = mController.getDevices();
         if (devices != null) {
             int connectedDevices = 0;
@@ -134,6 +234,7 @@
                 item.iconResId = R.drawable.ic_qs_bluetooth_on;
                 item.line1 = device.getName();
                 item.tag = device;
+                item.deviceType = OutputChooserLayout.Item.DEVICE_TYPE_BT;
                 int state = device.getMaxConnectionState();
                 if (state == BluetoothProfile.STATE_CONNECTED) {
                     item.iconResId = R.drawable.ic_qs_bluetooth_connected;
@@ -163,7 +264,87 @@
                 }
             }
         }
-        mView.setItems(items.toArray(new OutputChooserLayout.Item[items.size()]));
+    }
+
+    private void addRemoteDisplayRoutes(List<OutputChooserLayout.Item> items) {
+        List<MediaRouter.RouteInfo> routes = mRouter.getRoutes();
+        for(MediaRouter.RouteInfo route : routes) {
+            if (route.isDefaultOrBluetooth() || !route.isEnabled()
+                    || !route.matchesSelector(mRouteSelector)) {
+                continue;
+            }
+            final OutputChooserLayout.Item item = new OutputChooserLayout.Item();
+            item.icon = getIconDrawable(route);
+            item.line1 = route.getName();
+            item.tag = route;
+            item.deviceType = OutputChooserLayout.Item.DEVICE_TYPE_MEDIA_ROUTER;
+            if (route.getConnectionState() == CONNECTION_STATE_CONNECTING) {
+                mContext.getString(R.string.quick_settings_connecting);
+            } else {
+                item.line2 = route.getDescription();
+            }
+
+            if (route.getConnectionState() == CONNECTION_STATE_CONNECTED) {
+                item.canDisconnect = true;
+            }
+            items.add(item);
+        }
+    }
+
+    private Drawable getIconDrawable(MediaRouter.RouteInfo route) {
+        Uri iconUri = route.getIconUri();
+        if (iconUri != null) {
+            try {
+                InputStream is = getContext().getContentResolver().openInputStream(iconUri);
+                Drawable drawable = Drawable.createFromStream(is, null);
+                if (drawable != null) {
+                    return drawable;
+                }
+            } catch (IOException e) {
+                Log.w(TAG, "Failed to load " + iconUri, e);
+                // Falls back.
+            }
+        }
+        return getDefaultIconDrawable(route);
+    }
+
+    private Drawable getDefaultIconDrawable(MediaRouter.RouteInfo route) {
+        // If the type of the receiver device is specified, use it.
+        switch (route.getDeviceType()) {
+            case  MediaRouter.RouteInfo.DEVICE_TYPE_TV:
+                return mTvIcon;
+            case MediaRouter.RouteInfo.DEVICE_TYPE_SPEAKER:
+                return mSpeakerIcon;
+        }
+
+        // Otherwise, make the best guess based on other route information.
+        if (route instanceof MediaRouter.RouteGroup) {
+            // Only speakers can be grouped for now.
+            return mSpeakerGroupIcon;
+        }
+        return mDefaultIcon;
+    }
+
+    private final class MediaRouterCallback extends MediaRouter.Callback {
+        @Override
+        public void onRouteAdded(MediaRouter router, MediaRouter.RouteInfo info) {
+            updateItems();
+        }
+
+        @Override
+        public void onRouteRemoved(MediaRouter router, MediaRouter.RouteInfo info) {
+            updateItems();
+        }
+
+        @Override
+        public void onRouteChanged(MediaRouter router, MediaRouter.RouteInfo info) {
+            updateItems();
+        }
+
+        @Override
+        public void onRouteSelected(MediaRouter router, MediaRouter.RouteInfo route) {
+            dismiss();
+        }
     }
 
     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@@ -188,4 +369,33 @@
             updateItems();
         }
     };
+
+    static final class ItemComparator implements Comparator<OutputChooserLayout.Item> {
+        public static final ItemComparator sInstance = new ItemComparator();
+
+        @Override
+        public int compare(OutputChooserLayout.Item lhs, OutputChooserLayout.Item rhs) {
+            // Connected item(s) first
+            if (lhs.canDisconnect != rhs.canDisconnect) {
+                return Boolean.compare(rhs.canDisconnect, lhs.canDisconnect);
+            }
+            // Bluetooth items before media routes
+            if (lhs.deviceType != rhs.deviceType) {
+                return Integer.compare(lhs.deviceType, rhs.deviceType);
+            }
+            // then by name
+            return lhs.line1.toString().compareToIgnoreCase(rhs.line1.toString());
+        }
+    }
+
+    private final Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message message) {
+            switch (message.what) {
+                case MSG_UPDATE_ITEMS:
+                    updateItems();
+                    break;
+            }
+        }
+    };
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/volume/OutputChooserLayout.java b/packages/SystemUI/src/com/android/systemui/volume/OutputChooserLayout.java
index e8be4fd..22ced60 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/OutputChooserLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/OutputChooserLayout.java
@@ -55,7 +55,6 @@
     private AutoSizingList mItemList;
     private View mEmpty;
     private TextView mEmptyText;
-    private ImageView mEmptyIcon;
 
     private Item[] mItems;
 
@@ -76,7 +75,6 @@
         mEmpty = findViewById(android.R.id.empty);
         mEmpty.setVisibility(GONE);
         mEmptyText = mEmpty.findViewById(android.R.id.title);
-        mEmptyIcon = mEmpty.findViewById(android.R.id.icon);
     }
 
     @Override
@@ -93,9 +91,8 @@
         }
     }
 
-    public void setEmptyState(int icon, int text) {
+    public void setEmptyState(String text) {
         mEmpty.post(() -> {
-            mEmptyIcon.setImageResource(icon);
             mEmptyText.setText(text);
         });
     }
@@ -241,6 +238,8 @@
     }
 
     public static class Item {
+        public static int DEVICE_TYPE_BT = 1;
+        public static int DEVICE_TYPE_MEDIA_ROUTER = 2;
         public int iconResId;
         public Drawable icon;
         public Drawable overlay;
@@ -249,6 +248,7 @@
         public Object tag;
         public boolean canDisconnect;
         public int icon2 = -1;
+        public int deviceType = 0;
     }
 
     public interface Callback {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
index 9b7efe9..6d2691c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java
@@ -27,6 +27,7 @@
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
 import android.animation.Animator;
@@ -275,7 +276,7 @@
     }
 
     @Test
-    public void testHoldsWakeLock() {
+    public void testHoldsWakeLock_whenAOD() {
         mScrimController.transitionTo(ScrimState.AOD);
         verify(mWakeLock).acquire();
         verify(mWakeLock, never()).release();
@@ -284,6 +285,13 @@
     }
 
     @Test
+    public void testDoesNotHoldWakeLock_whenUnlocking() {
+        mScrimController.transitionTo(ScrimState.UNLOCKED);
+        mScrimController.finishAnimationsImmediately();
+        verifyZeroInteractions(mWakeLock);
+    }
+
+    @Test
     public void testCallbackInvokedOnSameStateTransition() {
         mScrimController.transitionTo(ScrimState.UNLOCKED);
         mScrimController.finishAnimationsImmediately();
diff --git a/packages/VpnDialogs/res/values-ne/strings.xml b/packages/VpnDialogs/res/values-ne/strings.xml
index c19ae52..5019a06 100644
--- a/packages/VpnDialogs/res/values-ne/strings.xml
+++ b/packages/VpnDialogs/res/values-ne/strings.xml
@@ -25,8 +25,8 @@
     <string name="data_received" msgid="4062776929376067820">"प्राप्त भयो:"</string>
     <string name="data_value_format" msgid="2192466557826897580">"<xliff:g id="NUMBER_0">%1$s</xliff:g> बाइटहरू / <xliff:g id="NUMBER_1">%2$s</xliff:g> प्याकेटहरू"</string>
     <string name="always_on_disconnected_title" msgid="1906740176262776166">"सधैँ-सक्रिय रहने VPN सेवामा जडान गर्न सकिँदैन"</string>
-    <string name="always_on_disconnected_message" msgid="555634519845992917">"<xliff:g id="VPN_APP_0">%1$s</xliff:g> लाई सधैँ जडान भइरहनेगरि सेट अप गरिएको छ तर यसलाई अहिले नै जडान गर्न मिल्दैन। तपाईंको फोन <xliff:g id="VPN_APP_1">%1$s</xliff:g> मा पुन: जडान नहुँदासम्म यसले कुनै सार्वजनिक नेटवर्क प्रयोग गर्नेछ।"</string>
-    <string name="always_on_disconnected_message_lockdown" msgid="4232225539869452120">"<xliff:g id="VPN_APP">%1$s</xliff:g> लाई सधैँ पनि जडान भइरहनेगरि सेट अप गरिएको छ तर यसलाई अहिले नै जडान गर्न मिल्दैन। VPN पुन: जडान नहुँदासम्म तपाईंसँग कुनै पनि इन्टरनेट रहनेछैन।"</string>
+    <string name="always_on_disconnected_message" msgid="555634519845992917">"<xliff:g id="VPN_APP_0">%1$s</xliff:g> लाई सधैँ जडान भइरहनेगरि सेटअप गरिएको छ तर यसलाई अहिले नै जडान गर्न मिल्दैन। तपाईंको फोन <xliff:g id="VPN_APP_1">%1$s</xliff:g> मा पुन: जडान नहुँदासम्म यसले कुनै सार्वजनिक नेटवर्क प्रयोग गर्नेछ।"</string>
+    <string name="always_on_disconnected_message_lockdown" msgid="4232225539869452120">"<xliff:g id="VPN_APP">%1$s</xliff:g> लाई सधैँ पनि जडान भइरहनेगरि सेटअप गरिएको छ तर यसलाई अहिले नै जडान गर्न मिल्दैन। VPN पुन: जडान नहुँदासम्म तपाईंसँग कुनै पनि इन्टरनेट रहनेछैन।"</string>
     <string name="always_on_disconnected_message_separator" msgid="3310614409322581371">" "</string>
     <string name="always_on_disconnected_message_settings_link" msgid="6172280302829992412">"VPN सम्बन्धी सेटिङहरू परिवर्तन गर्नुहोस्"</string>
     <string name="configure" msgid="4905518375574791375">"कन्फिगर गर्नुहोस्"</string>
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/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/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index 0ffc779..31aea63 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -1406,7 +1406,7 @@
             mLocalUnlockedUsers.put(userId, true);
         }
         if (userId < 1) return;
-        syncSharedAccounts(userId);
+        mHandler.post(() -> syncSharedAccounts(userId));
     }
 
     private void syncSharedAccounts(int userId) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 2ca4fdb..7dfde56 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -14146,8 +14146,7 @@
             for (int i = mLruProcesses.size() - 1 ; i >= 0 ; i--) {
                 ProcessRecord proc = mLruProcesses.get(i);
                 if (proc.notCachedSinceIdle) {
-                    if (proc.setProcState != ActivityManager.PROCESS_STATE_TOP_SLEEPING
-                            && proc.setProcState >= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
+                    if (proc.setProcState >= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
                             && proc.setProcState <= ActivityManager.PROCESS_STATE_SERVICE) {
                         if (doKilling && proc.initialIdlePss != 0
                                 && proc.lastPss > ((proc.initialIdlePss*3)/2)) {
@@ -21692,7 +21691,7 @@
         int procState;
         boolean foregroundActivities = false;
         mTmpBroadcastQueue.clear();
-        if (app == TOP_APP) {
+        if (PROCESS_STATE_CUR_TOP == ActivityManager.PROCESS_STATE_TOP && app == TOP_APP) {
             // The last app on the list is the foreground app.
             adj = ProcessList.FOREGROUND_APP_ADJ;
             schedGroup = ProcessList.SCHED_GROUP_TOP_APP;
@@ -21728,6 +21727,13 @@
             procState = ActivityManager.PROCESS_STATE_SERVICE;
             if (DEBUG_OOM_ADJ_REASON) Slog.d(TAG, "Making exec-service: " + app);
             //Slog.i(TAG, "EXEC " + (app.execServicesFg ? "FG" : "BG") + ": " + app);
+        } else if (app == TOP_APP) {
+            adj = ProcessList.FOREGROUND_APP_ADJ;
+            schedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
+            app.adjType = "top-sleeping";
+            foregroundActivities = true;
+            procState = PROCESS_STATE_CUR_TOP;
+            if (DEBUG_OOM_ADJ_REASON) Slog.d(TAG, "Making top: " + app);
         } else {
             // As far as we know the process is empty.  We may change our mind later.
             schedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
@@ -23176,7 +23182,7 @@
         if (app.curProcState <= ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE) {
             isInteraction = true;
             app.fgInteractionTime = 0;
-        } else if (app.curProcState <= ActivityManager.PROCESS_STATE_TOP_SLEEPING) {
+        } else if (app.curProcState <= ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
             if (app.fgInteractionTime == 0) {
                 app.fgInteractionTime = nowElapsed;
                 isInteraction = false;
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index c9c26ef1..ab5d64c 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -364,9 +364,6 @@
             case ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE:
                 procState = "FGS ";
                 break;
-            case ActivityManager.PROCESS_STATE_TOP_SLEEPING:
-                procState = "TPSL";
-                break;
             case ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND:
                 procState = "IMPF";
                 break;
@@ -385,6 +382,9 @@
             case ActivityManager.PROCESS_STATE_RECEIVER:
                 procState = "RCVR";
                 break;
+            case ActivityManager.PROCESS_STATE_TOP_SLEEPING:
+                procState = "TPSL";
+                break;
             case ActivityManager.PROCESS_STATE_HEAVY_WEIGHT:
                 procState = "HVY ";
                 break;
@@ -485,13 +485,13 @@
         PROC_MEM_TOP,                   // ActivityManager.PROCESS_STATE_TOP
         PROC_MEM_IMPORTANT,             // ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
         PROC_MEM_IMPORTANT,             // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
-        PROC_MEM_TOP,                   // ActivityManager.PROCESS_STATE_TOP_SLEEPING
         PROC_MEM_IMPORTANT,             // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
         PROC_MEM_IMPORTANT,             // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
         PROC_MEM_IMPORTANT,             // ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND
         PROC_MEM_IMPORTANT,             // ActivityManager.PROCESS_STATE_BACKUP
         PROC_MEM_SERVICE,               // ActivityManager.PROCESS_STATE_SERVICE
         PROC_MEM_CACHED,                // ActivityManager.PROCESS_STATE_RECEIVER
+        PROC_MEM_TOP,                   // ActivityManager.PROCESS_STATE_TOP_SLEEPING
         PROC_MEM_IMPORTANT,             // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT
         PROC_MEM_CACHED,                // ActivityManager.PROCESS_STATE_HOME
         PROC_MEM_CACHED,                // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
@@ -507,14 +507,14 @@
         PSS_FIRST_TOP_INTERVAL,         // ActivityManager.PROCESS_STATE_TOP
         PSS_FIRST_BACKGROUND_INTERVAL,  // ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
         PSS_FIRST_BACKGROUND_INTERVAL,  // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
-        PSS_FIRST_BACKGROUND_INTERVAL,  // ActivityManager.PROCESS_STATE_TOP_SLEEPING
         PSS_FIRST_BACKGROUND_INTERVAL,  // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
         PSS_FIRST_BACKGROUND_INTERVAL,  // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
         PSS_FIRST_BACKGROUND_INTERVAL,  // ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND
         PSS_FIRST_BACKGROUND_INTERVAL,  // ActivityManager.PROCESS_STATE_BACKUP
         PSS_FIRST_BACKGROUND_INTERVAL,  // ActivityManager.PROCESS_STATE_SERVICE
         PSS_FIRST_CACHED_INTERVAL,      // ActivityManager.PROCESS_STATE_RECEIVER
-        PSS_FIRST_BACKGROUND_INTERVAL,  // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT
+        PSS_FIRST_CACHED_INTERVAL,      // ActivityManager.PROCESS_STATE_TOP_SLEEPING
+        PSS_FIRST_CACHED_INTERVAL,      // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT
         PSS_FIRST_CACHED_INTERVAL,      // ActivityManager.PROCESS_STATE_HOME
         PSS_FIRST_CACHED_INTERVAL,      // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
         PSS_FIRST_CACHED_INTERVAL,      // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
@@ -529,14 +529,14 @@
         PSS_SHORT_INTERVAL,             // ActivityManager.PROCESS_STATE_TOP
         PSS_SAME_IMPORTANT_INTERVAL,    // ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
         PSS_SAME_IMPORTANT_INTERVAL,    // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
-        PSS_SAME_IMPORTANT_INTERVAL,    // ActivityManager.PROCESS_STATE_TOP_SLEEPING
         PSS_SAME_IMPORTANT_INTERVAL,    // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
         PSS_SAME_IMPORTANT_INTERVAL,    // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
         PSS_SAME_IMPORTANT_INTERVAL,    // ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND
         PSS_SAME_IMPORTANT_INTERVAL,    // ActivityManager.PROCESS_STATE_BACKUP
         PSS_SAME_SERVICE_INTERVAL,      // ActivityManager.PROCESS_STATE_SERVICE
         PSS_SAME_SERVICE_INTERVAL,      // ActivityManager.PROCESS_STATE_RECEIVER
-        PSS_SAME_IMPORTANT_INTERVAL,    // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT
+        PSS_SAME_CACHED_INTERVAL,       // ActivityManager.PROCESS_STATE_TOP_SLEEPING
+        PSS_SAME_CACHED_INTERVAL,       // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT
         PSS_SAME_CACHED_INTERVAL,       // ActivityManager.PROCESS_STATE_HOME
         PSS_SAME_CACHED_INTERVAL,       // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
         PSS_SAME_CACHED_INTERVAL,       // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
@@ -549,15 +549,15 @@
         PSS_TEST_FIRST_TOP_INTERVAL,        // ActivityManager.PROCESS_STATE_PERSISTENT
         PSS_TEST_FIRST_TOP_INTERVAL,        // ActivityManager.PROCESS_STATE_PERSISTENT_UI
         PSS_TEST_FIRST_TOP_INTERVAL,        // ActivityManager.PROCESS_STATE_TOP
-        PSS_FIRST_BACKGROUND_INTERVAL,      // ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
-        PSS_FIRST_BACKGROUND_INTERVAL,      // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
-        PSS_FIRST_BACKGROUND_INTERVAL,      // ActivityManager.PROCESS_STATE_TOP_SLEEPING
+        PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
+        PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
         PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
         PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
         PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND
         PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_BACKUP
         PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_SERVICE
         PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_RECEIVER
+        PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_TOP_SLEEPING
         PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT
         PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_HOME
         PSS_TEST_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
@@ -573,14 +573,14 @@
         PSS_TEST_SAME_IMPORTANT_INTERVAL,   // ActivityManager.PROCESS_STATE_TOP
         PSS_TEST_SAME_IMPORTANT_INTERVAL,   // ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE
         PSS_TEST_SAME_IMPORTANT_INTERVAL,   // ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE
-        PSS_TEST_SAME_IMPORTANT_INTERVAL,   // ActivityManager.PROCESS_STATE_TOP_SLEEPING
         PSS_TEST_SAME_IMPORTANT_INTERVAL,   // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
         PSS_TEST_SAME_IMPORTANT_INTERVAL,   // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND
         PSS_TEST_SAME_IMPORTANT_INTERVAL,   // ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND
         PSS_TEST_SAME_IMPORTANT_INTERVAL,   // ActivityManager.PROCESS_STATE_BACKUP
         PSS_TEST_SAME_BACKGROUND_INTERVAL,  // ActivityManager.PROCESS_STATE_SERVICE
         PSS_TEST_SAME_BACKGROUND_INTERVAL,  // ActivityManager.PROCESS_STATE_RECEIVER
-        PSS_TEST_SAME_IMPORTANT_INTERVAL,   // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT
+        PSS_TEST_SAME_BACKGROUND_INTERVAL,  // ActivityManager.PROCESS_STATE_TOP_SLEEPING
+        PSS_TEST_SAME_BACKGROUND_INTERVAL,  // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT
         PSS_TEST_SAME_BACKGROUND_INTERVAL,  // ActivityManager.PROCESS_STATE_HOME
         PSS_TEST_SAME_BACKGROUND_INTERVAL,  // ActivityManager.PROCESS_STATE_LAST_ACTIVITY
         PSS_TEST_SAME_BACKGROUND_INTERVAL,  // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY
diff --git a/services/core/java/com/android/server/location/ContextHubService.java b/services/core/java/com/android/server/location/ContextHubService.java
index 7f88663..26edf62 100644
--- a/services/core/java/com/android/server/location/ContextHubService.java
+++ b/services/core/java/com/android/server/location/ContextHubService.java
@@ -79,7 +79,8 @@
 
     private final Context mContext;
 
-    private final ContextHubInfo[] mContextHubInfo;
+    private final Map<Integer, ContextHubInfo> mContextHubIdToInfoMap;
+    private final List<ContextHubInfo> mContextHubInfoList;
     private final RemoteCallbackList<IContextHubCallback> mCallbacksList =
             new RemoteCallbackList<>();
 
@@ -141,8 +142,9 @@
         if (mContextHubProxy == null) {
             mTransactionManager = null;
             mClientManager = null;
-            mDefaultClientMap = Collections.EMPTY_MAP;
-            mContextHubInfo = new ContextHubInfo[0];
+            mDefaultClientMap = Collections.emptyMap();
+            mContextHubIdToInfoMap = Collections.emptyMap();
+            mContextHubInfoList = Collections.emptyList();
             return;
         }
 
@@ -157,20 +159,16 @@
             Log.e(TAG, "RemoteException while getting Context Hub info", e);
             hubList = Collections.emptyList();
         }
-        mContextHubInfo = ContextHubServiceUtil.createContextHubInfoArray(hubList);
+        mContextHubIdToInfoMap = Collections.unmodifiableMap(
+                ContextHubServiceUtil.createContextHubInfoMap(hubList));
+        mContextHubInfoList = new ArrayList<>(mContextHubIdToInfoMap.values());
 
         HashMap<Integer, IContextHubClient> defaultClientMap = new HashMap<>();
-        for (ContextHubInfo contextHubInfo : mContextHubInfo) {
-            int contextHubId = contextHubInfo.getId();
-
+        for (int contextHubId : mContextHubIdToInfoMap.keySet()) {
             IContextHubClient client = mClientManager.registerClient(
                     createDefaultClientCallback(contextHubId), contextHubId);
             defaultClientMap.put(contextHubId, client);
-        }
-        mDefaultClientMap = Collections.unmodifiableMap(defaultClientMap);
 
-        for (ContextHubInfo contextHubInfo : mContextHubInfo) {
-            int contextHubId = contextHubInfo.getId();
             try {
                 mContextHubProxy.registerCallback(
                         contextHubId, new ContextHubServiceCallback(contextHubId));
@@ -178,18 +176,12 @@
                 Log.e(TAG, "RemoteException while registering service callback for hub (ID = "
                         + contextHubId + ")", e);
             }
-        }
 
-        // Do a query to initialize the service cache list of nanoapps
-        // TODO(b/69270990): Remove this when old API is deprecated
-        for (ContextHubInfo contextHubInfo : mContextHubInfo) {
-            queryNanoAppsInternal(contextHubInfo.getId());
+            // Do a query to initialize the service cache list of nanoapps
+            // TODO(b/69270990): Remove this when old API is deprecated
+            queryNanoAppsInternal(contextHubId);
         }
-
-        for (int i = 0; i < mContextHubInfo.length; i++) {
-            Log.d(TAG, "ContextHub[" + i + "] id: " + mContextHubInfo[i].getId()
-                    + ", name:  " + mContextHubInfo[i].getName());
-        }
+        mDefaultClientMap = Collections.unmodifiableMap(defaultClientMap);
     }
 
     /**
@@ -267,23 +259,29 @@
     @Override
     public int[] getContextHubHandles() throws RemoteException {
         checkPermissions();
-        int[] returnArray = new int[mContextHubInfo.length];
-
-        for (int i = 0; i < returnArray.length; ++i) {
-            returnArray[i] = i;
-        }
-        return returnArray;
+        return ContextHubServiceUtil.createPrimitiveIntArray(mContextHubIdToInfoMap.keySet());
     }
 
     @Override
-    public ContextHubInfo getContextHubInfo(int contextHubId) throws RemoteException {
+    public ContextHubInfo getContextHubInfo(int contextHubHandle) throws RemoteException {
         checkPermissions();
-        if (!(contextHubId >= 0 && contextHubId < mContextHubInfo.length)) {
-            Log.e(TAG, "Invalid context hub handle " + contextHubId);
-            return null; // null means fail
+        if (!mContextHubIdToInfoMap.containsKey(contextHubHandle)) {
+            Log.e(TAG, "Invalid Context Hub handle " + contextHubHandle + " in getContextHubInfo");
+            return null;
         }
 
-        return mContextHubInfo[contextHubId];
+        return mContextHubIdToInfoMap.get(contextHubHandle);
+    }
+
+    /**
+     * Returns a List of ContextHubInfo object describing the available hubs.
+     *
+     * @return the List of ContextHubInfo objects
+     */
+    @Override
+    public List<ContextHubInfo> getContextHubs() throws RemoteException {
+        checkPermissions();
+        return mContextHubInfoList;
     }
 
     /**
@@ -347,28 +345,27 @@
     }
 
     @Override
-    public int loadNanoApp(int contextHubId, NanoApp app) throws RemoteException {
+    public int loadNanoApp(int contextHubHandle, NanoApp nanoApp) throws RemoteException {
         checkPermissions();
         if (mContextHubProxy == null) {
             return -1;
         }
-
-        if (!(contextHubId >= 0 && contextHubId < mContextHubInfo.length)) {
-            Log.e(TAG, "Invalid contextHubhandle " + contextHubId);
+        if (!isValidContextHubId(contextHubHandle)) {
+            Log.e(TAG, "Invalid Context Hub handle " + contextHubHandle + " in loadNanoApp");
             return -1;
         }
-        if (app == null) {
-            Log.e(TAG, "Invalid null app");
+        if (nanoApp == null) {
+            Log.e(TAG, "NanoApp cannot be null in loadNanoApp");
             return -1;
         }
 
         // Create an internal IContextHubTransactionCallback for the old API clients
-        NanoAppBinary nanoAppBinary = new NanoAppBinary(app.getAppBinary());
+        NanoAppBinary nanoAppBinary = new NanoAppBinary(nanoApp.getAppBinary());
         IContextHubTransactionCallback onCompleteCallback =
-                createLoadTransactionCallback(contextHubId, nanoAppBinary);
+                createLoadTransactionCallback(contextHubHandle, nanoAppBinary);
 
         ContextHubServiceTransaction transaction = mTransactionManager.createLoadTransaction(
-                contextHubId, nanoAppBinary, onCompleteCallback);
+                contextHubHandle, nanoAppBinary, onCompleteCallback);
 
         mTransactionManager.addTransaction(transaction);
         return 0;
@@ -384,7 +381,7 @@
         NanoAppInstanceInfo info =
                 mNanoAppStateManager.getNanoAppInstanceInfo(nanoAppHandle);
         if (info == null) {
-            Log.e(TAG, "Cannot find nanoapp with handle " + nanoAppHandle);
+            Log.e(TAG, "Invalid nanoapp handle " + nanoAppHandle + " in unloadNanoApp");
             return -1;
         }
 
@@ -407,7 +404,8 @@
     }
 
     @Override
-    public int[] findNanoAppOnHub(int hubHandle, NanoAppFilter filter) throws RemoteException {
+    public int[] findNanoAppOnHub(
+            int contextHubHandle, NanoAppFilter filter) throws RemoteException {
         checkPermissions();
 
         ArrayList<Integer> foundInstances = new ArrayList<>();
@@ -450,29 +448,29 @@
     }
 
     @Override
-    public int sendMessage(
-            int hubHandle, int nanoAppHandle, ContextHubMessage msg) throws RemoteException {
+    public int sendMessage(int contextHubHandle, int nanoAppHandle, ContextHubMessage msg)
+            throws RemoteException {
         checkPermissions();
         if (mContextHubProxy == null) {
             return -1;
         }
         if (msg == null) {
-            Log.e(TAG, "ContextHubMessage cannot be null");
+            Log.e(TAG, "ContextHubMessage cannot be null in sendMessage");
             return -1;
         }
         if (msg.getData() == null) {
-            Log.e(TAG, "ContextHubMessage message body cannot be null");
+            Log.e(TAG, "ContextHubMessage message body cannot be null in sendMessage");
             return -1;
         }
-        if (!mDefaultClientMap.containsKey(hubHandle)) {
-            Log.e(TAG, "Hub with ID " + hubHandle + " does not exist");
+        if (!isValidContextHubId(contextHubHandle)) {
+            Log.e(TAG, "Invalid Context Hub handle " + contextHubHandle + " in sendMessage");
             return -1;
         }
 
         boolean success = false;
         if (nanoAppHandle == OS_APP_INSTANCE) {
             if (msg.getMsgType() == MSG_QUERY_NANO_APPS) {
-                success = (queryNanoAppsInternal(hubHandle) == Result.OK);
+                success = (queryNanoAppsInternal(contextHubHandle) == Result.OK);
             } else {
                 Log.e(TAG, "Invalid OS message params of type " + msg.getMsgType());
             }
@@ -482,7 +480,7 @@
                 NanoAppMessage message = NanoAppMessage.createMessageToNanoApp(
                         info.getAppId(), msg.getMsgType(), msg.getData());
 
-                IContextHubClient client = mDefaultClientMap.get(hubHandle);
+                IContextHubClient client = mDefaultClientMap.get(contextHubHandle);
                 success = (client.sendMessageToNanoApp(message) ==
                         ContextHubTransaction.TRANSACTION_SUCCESS);
             } else {
@@ -595,13 +593,7 @@
      * @return {@code true} if the ID represents that of an available hub, {@code false} otherwise
      */
     private boolean isValidContextHubId(int contextHubId) {
-        for (ContextHubInfo hubInfo : mContextHubInfo) {
-            if (hubInfo.getId() == contextHubId) {
-                return true;
-            }
-        }
-
-        return false;
+        return mContextHubIdToInfoMap.containsKey(contextHubId);
     }
 
     /**
@@ -762,8 +754,8 @@
         pw.println("");
         // dump ContextHubInfo
         pw.println("=================== CONTEXT HUBS ====================");
-        for (int i = 0; i < mContextHubInfo.length; i++) {
-            pw.println("Handle " + i + " : " + mContextHubInfo[i].toString());
+        for (ContextHubInfo hubInfo : mContextHubIdToInfoMap.values()) {
+            pw.println(hubInfo);
         }
         pw.println("");
         pw.println("=================== NANOAPPS ====================");
@@ -779,7 +771,8 @@
         ContextHubServiceUtil.checkPermissions(mContext);
     }
 
-    private int onMessageReceiptOldApi(int msgType, int hubHandle, int appInstance, byte[] data) {
+    private int onMessageReceiptOldApi(
+            int msgType, int contextHubHandle, int appInstance, byte[] data) {
         if (data == null) {
             return -1;
         }
@@ -787,7 +780,8 @@
         int msgVersion = 0;
         int callbacksCount = mCallbacksList.beginBroadcast();
         Log.d(TAG, "Sending message " + msgType + " version " + msgVersion + " from hubHandle " +
-                hubHandle + ", appInstance " + appInstance + ", callBackCount " + callbacksCount);
+                contextHubHandle + ", appInstance " + appInstance + ", callBackCount "
+                + callbacksCount);
 
         if (callbacksCount < 1) {
             Log.v(TAG, "No message callbacks registered.");
@@ -798,7 +792,7 @@
         for (int i = 0; i < callbacksCount; ++i) {
             IContextHubCallback callback = mCallbacksList.getBroadcastItem(i);
             try {
-                callback.onMessageReceipt(hubHandle, appInstance, msg);
+                callback.onMessageReceipt(contextHubHandle, appInstance, msg);
             } catch (RemoteException e) {
                 Log.i(TAG, "Exception (" + e + ") calling remote callback (" + callback + ").");
                 continue;
diff --git a/services/core/java/com/android/server/location/ContextHubServiceUtil.java b/services/core/java/com/android/server/location/ContextHubServiceUtil.java
index 6faeb72..7a57dd3 100644
--- a/services/core/java/com/android/server/location/ContextHubServiceUtil.java
+++ b/services/core/java/com/android/server/location/ContextHubServiceUtil.java
@@ -30,6 +30,9 @@
 import android.hardware.location.NanoAppState;
 import android.util.Log;
 
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
 import java.util.ArrayList;
 
@@ -43,19 +46,20 @@
             + HARDWARE_PERMISSION + "' not granted to access ContextHub Hardware";
 
     /**
-     * Creates a ContextHubInfo array from an ArrayList of HIDL ContextHub objects.
+     * Creates a ConcurrentHashMap of the Context Hub ID to the ContextHubInfo object given an
+     * ArrayList of HIDL ContextHub objects.
      *
      * @param hubList the ContextHub ArrayList
-     * @return the ContextHubInfo array
+     * @return the HashMap object
      */
     /* package */
-    static ContextHubInfo[] createContextHubInfoArray(List<ContextHub> hubList) {
-        ContextHubInfo[] contextHubInfoList = new ContextHubInfo[hubList.size()];
-        for (int i = 0; i < hubList.size(); i++) {
-            contextHubInfoList[i] = new ContextHubInfo(hubList.get(i));
+    static HashMap<Integer, ContextHubInfo> createContextHubInfoMap(List<ContextHub> hubList) {
+        HashMap<Integer, ContextHubInfo> contextHubIdToInfoMap = new HashMap<>();
+        for (ContextHub contextHub : hubList) {
+            contextHubIdToInfoMap.put(contextHub.hubId, new ContextHubInfo(contextHub));
         }
 
-        return contextHubInfoList;
+        return contextHubIdToInfoMap;
     }
 
     /**
@@ -90,6 +94,22 @@
     }
 
     /**
+     * Creates a primitive integer array given a Collection<Integer>.
+     * @param collection the collection to iterate
+     * @return the primitive integer array
+     */
+    static int[] createPrimitiveIntArray(Collection<Integer> collection) {
+        int[] primitiveArray = new int[collection.size()];
+
+        int i = 0;
+        for (int contextHubId : collection) {
+            primitiveArray[i++] = contextHubId;
+        }
+
+        return primitiveArray;
+    }
+
+    /**
      * Generates the Context Hub HAL's NanoAppBinary object from the client-facing
      * android.hardware.location.NanoAppBinary object.
      *
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 f87f7fa..48f4626 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManager.java
@@ -31,7 +31,13 @@
 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;
@@ -46,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.
@@ -55,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(
@@ -196,7 +214,13 @@
     /**
      * 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 @NonNull byte[] startRecoverySession(
@@ -208,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/SecureBox.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/SecureBox.java
index 742cb45..d8a2d31 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/SecureBox.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/SecureBox.java
@@ -17,23 +17,159 @@
 package com.android.server.locksettings.recoverablekeystore;
 
 import android.annotation.Nullable;
-
+import com.android.internal.annotations.VisibleForTesting;
+import java.math.BigInteger;
+import java.nio.BufferUnderflowException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+import java.security.InvalidAlgorithmParameterException;
 import java.security.InvalidKeyException;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
 import java.security.NoSuchAlgorithmException;
 import java.security.PrivateKey;
 import java.security.PublicKey;
-
+import java.security.SecureRandom;
+import java.security.interfaces.ECPublicKey;
+import java.security.spec.ECFieldFp;
+import java.security.spec.ECGenParameterSpec;
+import java.security.spec.ECParameterSpec;
+import java.security.spec.ECPoint;
+import java.security.spec.ECPublicKeySpec;
+import java.security.spec.EllipticCurve;
+import java.security.spec.InvalidKeySpecException;
+import java.util.Arrays;
 import javax.crypto.AEADBadTagException;
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.KeyAgreement;
+import javax.crypto.Mac;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.GCMParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
 
 /**
- * TODO(b/69056040) Add implementation of SecureBox. This is a placeholder so KeySyncUtils compiles.
+ * Implementation of the SecureBox v2 crypto functions.
+ *
+ * <p>Securebox v2 provides a simple interface to perform encryptions by using any of the following
+ * credential types:
+ *
+ * <ul>
+ *   <li>A public key owned by the recipient,
+ *   <li>A secret shared between the sender and the recipient, or
+ *   <li>Both a recipient's public key and a shared secret.
+ * </ul>
  *
  * @hide
  */
 public class SecureBox {
+
+    private static final byte[] VERSION = new byte[] {(byte) 0x02, 0}; // LITTLE_ENDIAN_TWO_BYTES(2)
+    private static final byte[] HKDF_SALT =
+            concat("SECUREBOX".getBytes(StandardCharsets.UTF_8), VERSION);
+    private static final byte[] HKDF_INFO_WITH_PUBLIC_KEY =
+            "P256 HKDF-SHA-256 AES-128-GCM".getBytes(StandardCharsets.UTF_8);
+    private static final byte[] HKDF_INFO_WITHOUT_PUBLIC_KEY =
+            "SHARED HKDF-SHA-256 AES-128-GCM".getBytes(StandardCharsets.UTF_8);
+    private static final byte[] CONSTANT_01 = {(byte) 0x01};
+    private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
+    private static final byte EC_PUBLIC_KEY_PREFIX = (byte) 0x04;
+
+    private static final String CIPHER_ALG = "AES";
+    private static final String EC_ALG = "EC";
+    private static final String EC_P256_COMMON_NAME = "secp256r1";
+    private static final String EC_P256_OPENSSL_NAME = "prime256v1";
+    private static final String ENC_ALG = "AES/GCM/NoPadding";
+    private static final String KA_ALG = "ECDH";
+    private static final String MAC_ALG = "HmacSHA256";
+
+    private static final int EC_COORDINATE_LEN_BYTES = 32;
+    private static final int EC_PUBLIC_KEY_LEN_BYTES = 2 * EC_COORDINATE_LEN_BYTES + 1;
+    private static final int GCM_NONCE_LEN_BYTES = 12;
+    private static final int GCM_KEY_LEN_BYTES = 16;
+    private static final int GCM_TAG_LEN_BYTES = 16;
+
+    private static final BigInteger BIG_INT_02 = BigInteger.valueOf(2);
+
+    private enum AesGcmOperation {
+        ENCRYPT,
+        DECRYPT
+    }
+
+    // Parameters for the NIST P-256 curve y^2 = x^3 + ax + b (mod p)
+    private static final BigInteger EC_PARAM_P =
+            new BigInteger("ffffffff00000001000000000000000000000000ffffffffffffffffffffffff", 16);
+    private static final BigInteger EC_PARAM_A = EC_PARAM_P.subtract(new BigInteger("3"));
+    private static final BigInteger EC_PARAM_B =
+            new BigInteger("5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b", 16);
+
+    @VisibleForTesting static final ECParameterSpec EC_PARAM_SPEC;
+
+    static {
+        EllipticCurve curveSpec =
+                new EllipticCurve(new ECFieldFp(EC_PARAM_P), EC_PARAM_A, EC_PARAM_B);
+        ECPoint generator =
+                new ECPoint(
+                        new BigInteger(
+                                "6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296",
+                                16),
+                        new BigInteger(
+                                "4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5",
+                                16));
+        BigInteger generatorOrder =
+                new BigInteger(
+                        "ffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551", 16);
+        EC_PARAM_SPEC = new ECParameterSpec(curveSpec, generator, generatorOrder, /* cofactor */ 1);
+    }
+
+    private SecureBox() {}
+
     /**
-     * TODO(b/69056040) Add implementation of encrypt.
+     * Randomly generates a public-key pair that can be used for the functions {@link #encrypt} and
+     * {@link #decrypt}.
      *
+     * @return the randomly generated public-key pair
+     * @throws NoSuchAlgorithmException if the underlying crypto algorithm is not supported
+     * @hide
+     */
+    public static KeyPair genKeyPair() throws NoSuchAlgorithmException {
+        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(EC_ALG);
+        try {
+            // Try using the OpenSSL provider first
+            keyPairGenerator.initialize(new ECGenParameterSpec(EC_P256_OPENSSL_NAME));
+            return keyPairGenerator.generateKeyPair();
+        } catch (InvalidAlgorithmParameterException ex) {
+            // Try another name for NIST P-256
+        }
+        try {
+            keyPairGenerator.initialize(new ECGenParameterSpec(EC_P256_COMMON_NAME));
+            return keyPairGenerator.generateKeyPair();
+        } catch (InvalidAlgorithmParameterException ex) {
+            throw new NoSuchAlgorithmException("Unable to find the NIST P-256 curve", ex);
+        }
+    }
+
+    /**
+     * Encrypts {@code payload} by using {@code theirPublicKey} and/or {@code sharedSecret}. At
+     * least one of {@code theirPublicKey} and {@code sharedSecret} must be non-null, and an empty
+     * {@code sharedSecret} is equivalent to null.
+     *
+     * <p>Note that {@code header} will be authenticated (but not encrypted) together with {@code
+     * payload}, and the same {@code header} has to be provided for {@link #decrypt}.
+     *
+     * @param theirPublicKey the recipient's public key, or null if the payload is to be encrypted
+     *     only with the shared secret
+     * @param sharedSecret the secret shared between the sender and the recipient, or null if the
+     *     payload is to be encrypted only with the recipient's public key
+     * @param header the data that will be authenticated with {@code payload} but not encrypted, or
+     *     null if the data is empty
+     * @param payload the data to be encrypted, or null if the data is empty
+     * @return the encrypted payload
+     * @throws NoSuchAlgorithmException if any underlying crypto algorithm is not supported
+     * @throws InvalidKeyException if the provided key is invalid for underlying crypto algorithms
      * @hide
      */
     public static byte[] encrypt(
@@ -42,12 +178,59 @@
             @Nullable byte[] header,
             @Nullable byte[] payload)
             throws NoSuchAlgorithmException, InvalidKeyException {
-        throw new UnsupportedOperationException("Needs to be implemented.");
+        sharedSecret = emptyByteArrayIfNull(sharedSecret);
+        if (theirPublicKey == null && sharedSecret.length == 0) {
+            throw new IllegalArgumentException("Both the public key and shared secret are empty");
+        }
+        header = emptyByteArrayIfNull(header);
+        payload = emptyByteArrayIfNull(payload);
+
+        KeyPair senderKeyPair;
+        byte[] dhSecret;
+        byte[] hkdfInfo;
+        if (theirPublicKey == null) {
+            senderKeyPair = null;
+            dhSecret = EMPTY_BYTE_ARRAY;
+            hkdfInfo = HKDF_INFO_WITHOUT_PUBLIC_KEY;
+        } else {
+            senderKeyPair = genKeyPair();
+            dhSecret = dhComputeSecret(senderKeyPair.getPrivate(), theirPublicKey);
+            hkdfInfo = HKDF_INFO_WITH_PUBLIC_KEY;
+        }
+
+        byte[] randNonce = genRandomNonce();
+        byte[] keyingMaterial = concat(dhSecret, sharedSecret);
+        SecretKey encryptionKey = hkdfDeriveKey(keyingMaterial, HKDF_SALT, hkdfInfo);
+        byte[] ciphertext = aesGcmEncrypt(encryptionKey, randNonce, payload, header);
+        if (senderKeyPair == null) {
+            return concat(VERSION, randNonce, ciphertext);
+        } else {
+            return concat(
+                    VERSION, encodePublicKey(senderKeyPair.getPublic()), randNonce, ciphertext);
+        }
     }
 
     /**
-     * TODO(b/69056040) Add implementation of decrypt.
+     * Decrypts {@code encryptedPayload} by using {@code ourPrivateKey} and/or {@code sharedSecret}.
+     * At least one of {@code ourPrivateKey} and {@code sharedSecret} must be non-null, and an empty
+     * {@code sharedSecret} is equivalent to null.
      *
+     * <p>Note that {@code header} should be the same data used for {@link #encrypt}, which is
+     * authenticated (but not encrypted) together with {@code payload}; otherwise, an {@code
+     * AEADBadTagException} will be thrown.
+     *
+     * @param ourPrivateKey the recipient's private key, or null if the payload was encrypted only
+     *     with the shared secret
+     * @param sharedSecret the secret shared between the sender and the recipient, or null if the
+     *     payload was encrypted only with the recipient's public key
+     * @param header the data that was authenticated with the original payload but not encrypted, or
+     *     null if the data is empty
+     * @param encryptedPayload the data to be decrypted
+     * @return the original payload that was encrypted
+     * @throws NoSuchAlgorithmException if any underlying crypto algorithm is not supported
+     * @throws InvalidKeyException if the provided key is invalid for underlying crypto algorithms
+     * @throws AEADBadTagException if the authentication tag contained in {@code encryptedPayload}
+     *     cannot be validated
      * @hide
      */
     public static byte[] decrypt(
@@ -56,6 +239,224 @@
             @Nullable byte[] header,
             byte[] encryptedPayload)
             throws NoSuchAlgorithmException, InvalidKeyException, AEADBadTagException {
-        throw new UnsupportedOperationException("Needs to be implemented.");
+        sharedSecret = emptyByteArrayIfNull(sharedSecret);
+        if (ourPrivateKey == null && sharedSecret.length == 0) {
+            throw new IllegalArgumentException("Both the private key and shared secret are empty");
+        }
+        header = emptyByteArrayIfNull(header);
+        encryptedPayload = emptyByteArrayIfNull(encryptedPayload);
+
+        ByteBuffer ciphertextBuffer = ByteBuffer.wrap(encryptedPayload);
+        byte[] version = readEncryptedPayload(ciphertextBuffer, VERSION.length);
+        if (!Arrays.equals(version, VERSION)) {
+            throw new IllegalArgumentException("The payload was not encrypted by SecureBox v2");
+        }
+
+        byte[] senderPublicKeyBytes;
+        byte[] dhSecret;
+        byte[] hkdfInfo;
+        if (ourPrivateKey == null) {
+            dhSecret = EMPTY_BYTE_ARRAY;
+            hkdfInfo = HKDF_INFO_WITHOUT_PUBLIC_KEY;
+        } else {
+            senderPublicKeyBytes = readEncryptedPayload(ciphertextBuffer, EC_PUBLIC_KEY_LEN_BYTES);
+            dhSecret = dhComputeSecret(ourPrivateKey, decodePublicKey(senderPublicKeyBytes));
+            hkdfInfo = HKDF_INFO_WITH_PUBLIC_KEY;
+        }
+
+        byte[] randNonce = readEncryptedPayload(ciphertextBuffer, GCM_NONCE_LEN_BYTES);
+        byte[] ciphertext = readEncryptedPayload(ciphertextBuffer, ciphertextBuffer.remaining());
+        byte[] keyingMaterial = concat(dhSecret, sharedSecret);
+        SecretKey decryptionKey = hkdfDeriveKey(keyingMaterial, HKDF_SALT, hkdfInfo);
+        return aesGcmDecrypt(decryptionKey, randNonce, ciphertext, header);
+    }
+
+    private static byte[] readEncryptedPayload(ByteBuffer buffer, int length) {
+        byte[] output = new byte[length];
+        try {
+            buffer.get(output);
+        } catch (BufferUnderflowException ex) {
+            throw new IllegalArgumentException("The encrypted payload is too short");
+        }
+        return output;
+    }
+
+    private static byte[] dhComputeSecret(PrivateKey ourPrivateKey, PublicKey theirPublicKey)
+            throws NoSuchAlgorithmException, InvalidKeyException {
+        KeyAgreement agreement = KeyAgreement.getInstance(KA_ALG);
+        try {
+            agreement.init(ourPrivateKey);
+        } catch (RuntimeException ex) {
+            // Rethrow the RuntimeException as InvalidKeyException
+            throw new InvalidKeyException(ex);
+        }
+        agreement.doPhase(theirPublicKey, /*lastPhase=*/ true);
+        return agreement.generateSecret();
+    }
+
+    /** Derives a 128-bit AES key. */
+    private static SecretKey hkdfDeriveKey(byte[] secret, byte[] salt, byte[] info)
+            throws NoSuchAlgorithmException {
+        Mac mac = Mac.getInstance(MAC_ALG);
+        try {
+            mac.init(new SecretKeySpec(salt, MAC_ALG));
+        } catch (InvalidKeyException ex) {
+            // This should never happen
+            throw new RuntimeException(ex);
+        }
+        byte[] pseudorandomKey = mac.doFinal(secret);
+
+        try {
+            mac.init(new SecretKeySpec(pseudorandomKey, MAC_ALG));
+        } catch (InvalidKeyException ex) {
+            // This should never happen
+            throw new RuntimeException(ex);
+        }
+        mac.update(info);
+        // Hashing just one block will yield 256 bits, which is enough to construct the AES key
+        byte[] hkdfOutput = mac.doFinal(CONSTANT_01);
+
+        return new SecretKeySpec(Arrays.copyOf(hkdfOutput, GCM_KEY_LEN_BYTES), CIPHER_ALG);
+    }
+
+    private static byte[] aesGcmEncrypt(SecretKey key, byte[] nonce, byte[] plaintext, byte[] aad)
+            throws NoSuchAlgorithmException, InvalidKeyException {
+        try {
+            return aesGcmInternal(AesGcmOperation.ENCRYPT, key, nonce, plaintext, aad);
+        } catch (AEADBadTagException ex) {
+            // This should never happen
+            throw new RuntimeException(ex);
+        }
+    }
+
+    private static byte[] aesGcmDecrypt(SecretKey key, byte[] nonce, byte[] ciphertext, byte[] aad)
+            throws NoSuchAlgorithmException, InvalidKeyException, AEADBadTagException {
+        return aesGcmInternal(AesGcmOperation.DECRYPT, key, nonce, ciphertext, aad);
+    }
+
+    private static byte[] aesGcmInternal(
+            AesGcmOperation operation, SecretKey key, byte[] nonce, byte[] text, byte[] aad)
+            throws NoSuchAlgorithmException, InvalidKeyException, AEADBadTagException {
+        Cipher cipher;
+        try {
+            cipher = Cipher.getInstance(ENC_ALG);
+        } catch (NoSuchPaddingException ex) {
+            // This should never happen because AES-GCM doesn't use padding
+            throw new RuntimeException(ex);
+        }
+        GCMParameterSpec spec = new GCMParameterSpec(GCM_TAG_LEN_BYTES * 8, nonce);
+        try {
+            if (operation == AesGcmOperation.DECRYPT) {
+                cipher.init(Cipher.DECRYPT_MODE, key, spec);
+            } else {
+                cipher.init(Cipher.ENCRYPT_MODE, key, spec);
+            }
+        } catch (InvalidAlgorithmParameterException ex) {
+            // This should never happen
+            throw new RuntimeException(ex);
+        }
+        try {
+            cipher.updateAAD(aad);
+            return cipher.doFinal(text);
+        } catch (AEADBadTagException ex) {
+            // Catch and rethrow AEADBadTagException first because it's a subclass of
+            // BadPaddingException
+            throw ex;
+        } catch (IllegalBlockSizeException | BadPaddingException ex) {
+            // This should never happen because AES-GCM can handle inputs of any length without
+            // padding
+            throw new RuntimeException(ex);
+        }
+    }
+
+    @VisibleForTesting
+    static byte[] encodePublicKey(PublicKey publicKey) {
+        ECPoint point = ((ECPublicKey) publicKey).getW();
+        byte[] x = point.getAffineX().toByteArray();
+        byte[] y = point.getAffineY().toByteArray();
+
+        byte[] output = new byte[EC_PUBLIC_KEY_LEN_BYTES];
+        // The order of arraycopy() is important, because the coordinates may have a one-byte
+        // leading 0 for the sign bit of two's complement form
+        System.arraycopy(y, 0, output, EC_PUBLIC_KEY_LEN_BYTES - y.length, y.length);
+        System.arraycopy(x, 0, output, 1 + EC_COORDINATE_LEN_BYTES - x.length, x.length);
+        output[0] = EC_PUBLIC_KEY_PREFIX;
+        return output;
+    }
+
+    @VisibleForTesting
+    static PublicKey decodePublicKey(byte[] keyBytes)
+            throws NoSuchAlgorithmException, InvalidKeyException {
+        BigInteger x =
+                new BigInteger(
+                        /*signum=*/ 1,
+                        Arrays.copyOfRange(keyBytes, 1, 1 + EC_COORDINATE_LEN_BYTES));
+        BigInteger y =
+                new BigInteger(
+                        /*signum=*/ 1,
+                        Arrays.copyOfRange(
+                                keyBytes, 1 + EC_COORDINATE_LEN_BYTES, EC_PUBLIC_KEY_LEN_BYTES));
+
+        // Checks if the point is indeed on the P-256 curve for security considerations
+        validateEcPoint(x, y);
+
+        KeyFactory keyFactory = KeyFactory.getInstance(EC_ALG);
+        try {
+            return keyFactory.generatePublic(new ECPublicKeySpec(new ECPoint(x, y), EC_PARAM_SPEC));
+        } catch (InvalidKeySpecException ex) {
+            // This should never happen
+            throw new RuntimeException(ex);
+        }
+    }
+
+    private static void validateEcPoint(BigInteger x, BigInteger y) throws InvalidKeyException {
+        if (x.compareTo(EC_PARAM_P) >= 0
+                || y.compareTo(EC_PARAM_P) >= 0
+                || x.signum() == -1
+                || y.signum() == -1) {
+            throw new InvalidKeyException("Point lies outside of the expected curve");
+        }
+
+        // Points on the curve satisfy y^2 = x^3 + ax + b (mod p)
+        BigInteger lhs = y.modPow(BIG_INT_02, EC_PARAM_P);
+        BigInteger rhs =
+                x.modPow(BIG_INT_02, EC_PARAM_P) // x^2
+                        .add(EC_PARAM_A) // x^2 + a
+                        .mod(EC_PARAM_P) // This will speed up the next multiplication
+                        .multiply(x) // (x^2 + a) * x = x^3 + ax
+                        .add(EC_PARAM_B) // x^3 + ax + b
+                        .mod(EC_PARAM_P);
+        if (!lhs.equals(rhs)) {
+            throw new InvalidKeyException("Point lies outside of the expected curve");
+        }
+    }
+
+    private static byte[] genRandomNonce() throws NoSuchAlgorithmException {
+        byte[] nonce = new byte[GCM_NONCE_LEN_BYTES];
+        new SecureRandom().nextBytes(nonce);
+        return nonce;
+    }
+
+    @VisibleForTesting
+    static byte[] concat(byte[]... inputs) {
+        int length = 0;
+        for (int i = 0; i < inputs.length; i++) {
+            if (inputs[i] == null) {
+                inputs[i] = EMPTY_BYTE_ARRAY;
+            }
+            length += inputs[i].length;
+        }
+
+        byte[] output = new byte[length];
+        int outputPos = 0;
+        for (byte[] input : inputs) {
+            System.arraycopy(input, /*srcPos=*/ 0, output, outputPos, input.length);
+            outputPos += input.length;
+        }
+        return output;
+    }
+
+    private static byte[] emptyByteArrayIfNull(@Nullable byte[] input) {
+        return input == null ? EMPTY_BYTE_ARRAY : input;
     }
 }
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java
index 7986533..3644d36 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDb.java
@@ -25,6 +25,7 @@
 
 import com.android.server.locksettings.recoverablekeystore.WrappedKey;
 import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.KeysEntry;
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.UserMetadataEntry;
 
 import java.util.HashMap;
 import java.util.Locale;
@@ -176,6 +177,52 @@
     }
 
     /**
+     * Sets the {@code generationId} of the platform key for the account owned by {@code userId}.
+     *
+     * @return The primary key ID of the relation.
+     */
+    public long setPlatformKeyGenerationId(int userId, int generationId) {
+        SQLiteDatabase db = mKeyStoreDbHelper.getWritableDatabase();
+        ContentValues values = new ContentValues();
+        values.put(UserMetadataEntry.COLUMN_NAME_USER_ID, userId);
+        values.put(UserMetadataEntry.COLUMN_NAME_PLATFORM_KEY_GENERATION_ID, generationId);
+        return db.replace(
+                UserMetadataEntry.TABLE_NAME, /*nullColumnHack=*/ null, values);
+    }
+
+    /**
+     * Returns the generation ID associated with the platform key of the user with {@code userId}.
+     */
+    public int getPlatformKeyGenerationId(int userId) {
+        SQLiteDatabase db = mKeyStoreDbHelper.getReadableDatabase();
+        String[] projection = {
+                UserMetadataEntry.COLUMN_NAME_PLATFORM_KEY_GENERATION_ID};
+        String selection =
+                UserMetadataEntry.COLUMN_NAME_USER_ID + " = ?";
+        String[] selectionArguments = {
+                Integer.toString(userId)};
+
+        try (
+            Cursor cursor = db.query(
+                UserMetadataEntry.TABLE_NAME,
+                projection,
+                selection,
+                selectionArguments,
+                /*groupBy=*/ null,
+                /*having=*/ null,
+                /*orderBy=*/ null)
+        ) {
+            if (cursor.getCount() == 0) {
+                return -1;
+            }
+            cursor.moveToFirst();
+            return cursor.getInt(
+                    cursor.getColumnIndexOrThrow(
+                            UserMetadataEntry.COLUMN_NAME_PLATFORM_KEY_GENERATION_ID));
+        }
+    }
+
+    /**
      * Closes all open connections to the database.
      */
     public void close() {
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java
index c54d0a6..b6c168f 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbContract.java
@@ -58,4 +58,22 @@
          */
         static final String COLUMN_NAME_LAST_SYNCED_AT = "last_synced_at";
     }
+
+    /**
+     * Recoverable KeyStore metadata for a specific user profile.
+     */
+    static class UserMetadataEntry implements BaseColumns {
+        static final String TABLE_NAME = "user_metadata";
+
+        /**
+         * User ID of the profile.
+         */
+        static final String COLUMN_NAME_USER_ID = "user_id";
+
+        /**
+         * Every time a new platform key is generated for a user, this increments. The platform key
+         * is used to wrap recoverable keys on disk.
+         */
+        static final String COLUMN_NAME_PLATFORM_KEY_GENERATION_ID = "platform_key_generation_id";
+    }
 }
diff --git a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java
index e3783c4..6868203 100644
--- a/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java
+++ b/services/core/java/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbHelper.java
@@ -5,6 +5,7 @@
 import android.database.sqlite.SQLiteOpenHelper;
 
 import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.KeysEntry;
+import com.android.server.locksettings.recoverablekeystore.storage.RecoverableKeyStoreDbContract.UserMetadataEntry;
 
 /**
  * Helper for creating the recoverable key database.
@@ -13,31 +14,44 @@
     private static final int DATABASE_VERSION = 1;
     private static final String DATABASE_NAME = "recoverablekeystore.db";
 
-    private static final String SQL_CREATE_ENTRIES =
+    private static final String SQL_CREATE_KEYS_ENTRY =
             "CREATE TABLE " + KeysEntry.TABLE_NAME + "( "
                     + KeysEntry._ID + " INTEGER PRIMARY KEY,"
-                    + KeysEntry.COLUMN_NAME_UID + " INTEGER UNIQUE,"
-                    + KeysEntry.COLUMN_NAME_ALIAS + " TEXT UNIQUE,"
+                    + KeysEntry.COLUMN_NAME_UID + " INTEGER,"
+                    + KeysEntry.COLUMN_NAME_ALIAS + " TEXT,"
                     + KeysEntry.COLUMN_NAME_NONCE + " BLOB,"
                     + KeysEntry.COLUMN_NAME_WRAPPED_KEY + " BLOB,"
                     + KeysEntry.COLUMN_NAME_GENERATION_ID + " INTEGER,"
-                    + KeysEntry.COLUMN_NAME_LAST_SYNCED_AT + " INTEGER)";
+                    + KeysEntry.COLUMN_NAME_LAST_SYNCED_AT + " INTEGER,"
+                    + "UNIQUE(" + KeysEntry.COLUMN_NAME_UID + ","
+                    + KeysEntry.COLUMN_NAME_ALIAS + "))";
 
-    private static final String SQL_DELETE_ENTRIES =
+    private static final String SQL_CREATE_USER_METADATA_ENTRY =
+            "CREATE TABLE " + UserMetadataEntry.TABLE_NAME + "( "
+                    + UserMetadataEntry._ID + " INTEGER PRIMARY KEY,"
+                    + UserMetadataEntry.COLUMN_NAME_USER_ID + " INTEGER UNIQUE,"
+                    + UserMetadataEntry.COLUMN_NAME_PLATFORM_KEY_GENERATION_ID + " INTEGER)";
+
+    private static final String SQL_DELETE_KEYS_ENTRY =
             "DROP TABLE IF EXISTS " + KeysEntry.TABLE_NAME;
 
+    private static final String SQL_DELETE_USER_METADATA_ENTRY =
+            "DROP TABLE IF EXISTS " + UserMetadataEntry.TABLE_NAME;
+
     RecoverableKeyStoreDbHelper(Context context) {
         super(context, DATABASE_NAME, null, DATABASE_VERSION);
     }
 
     @Override
     public void onCreate(SQLiteDatabase db) {
-        db.execSQL(SQL_CREATE_ENTRIES);
+        db.execSQL(SQL_CREATE_KEYS_ENTRY);
+        db.execSQL(SQL_CREATE_USER_METADATA_ENTRY);
     }
 
     @Override
     public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
-        db.execSQL(SQL_DELETE_ENTRIES);
+        db.execSQL(SQL_DELETE_KEYS_ENTRY);
+        db.execSQL(SQL_DELETE_USER_METADATA_ENTRY);
         onCreate(db);
     }
 }
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/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
index 5b9e3a1..e55d4ea 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/BaseIDevicePolicyManager.java
@@ -15,6 +15,7 @@
  */
 package com.android.server.devicepolicy;
 
+import android.annotation.UserIdInt;
 import android.app.admin.IDevicePolicyManager;
 import android.content.ComponentName;
 import android.os.PersistableBundle;
@@ -24,6 +25,8 @@
 import com.android.internal.R;
 import com.android.server.SystemService;
 
+import java.util.List;
+
 /**
  * Defines the required interface for IDevicePolicyManager implemenation.
  *
@@ -68,7 +71,29 @@
         return false;
     }
 
+    @Override
+    public boolean setPasswordBlacklist(ComponentName who, String name, List<String> blacklist,
+            boolean parent) {
+        return false;
+    }
+
+    @Override
+    public String getPasswordBlacklistName(ComponentName who, @UserIdInt int userId,
+            boolean parent) {
+        return null;
+    }
+
+    @Override
+    public boolean isPasswordBlacklisted(@UserIdInt int userId, String password) {
+        return false;
+    }
+
     public boolean isUsingUnifiedPassword(ComponentName who) {
         return true;
     }
+
+    public boolean setKeyPairCertificate(ComponentName who, String callerPackage, String alias,
+            byte[] cert, byte[] chain, boolean isUserSelectable) {
+        return false;
+    }
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index bead31f..387818b 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -734,6 +734,7 @@
         private static final String TAG_PASSWORD_HISTORY_LENGTH = "password-history-length";
         private static final String TAG_MIN_PASSWORD_LENGTH = "min-password-length";
         private static final String ATTR_VALUE = "value";
+        private static final String TAG_PASSWORD_BLACKLIST = "password-blacklist";
         private static final String TAG_PASSWORD_QUALITY = "password-quality";
         private static final String TAG_POLICIES = "policies";
         private static final String TAG_CROSS_PROFILE_WIDGET_PROVIDERS =
@@ -866,6 +867,9 @@
         // Default title of confirm credentials screen
         String organizationName = null;
 
+        // The blacklist data is stored in a file whose name is stored in the XML
+        String passwordBlacklistFile = null;
+
         ActiveAdmin(DeviceAdminInfo _info, boolean parent) {
             info = _info;
             isParent = parent;
@@ -947,6 +951,11 @@
                     out.endTag(null, TAG_MIN_PASSWORD_NONLETTER);
                 }
             }
+            if (passwordBlacklistFile != null) {
+                out.startTag(null, TAG_PASSWORD_BLACKLIST);
+                out.attribute(null, ATTR_VALUE, passwordBlacklistFile);
+                out.endTag(null, TAG_PASSWORD_BLACKLIST);
+            }
             if (maximumTimeToUnlock != DEF_MAXIMUM_TIME_TO_UNLOCK) {
                 out.startTag(null, TAG_MAX_TIME_TO_UNLOCK);
                 out.attribute(null, ATTR_VALUE, Long.toString(maximumTimeToUnlock));
@@ -1186,7 +1195,9 @@
                 } else if (TAG_MIN_PASSWORD_NONLETTER.equals(tag)) {
                     minimumPasswordMetrics.nonLetter = Integer.parseInt(
                             parser.getAttributeValue(null, ATTR_VALUE));
-                } else if (TAG_MAX_TIME_TO_UNLOCK.equals(tag)) {
+                } else if (TAG_PASSWORD_BLACKLIST.equals(tag)) {
+                    passwordBlacklistFile = parser.getAttributeValue(null, ATTR_VALUE);
+                }else if (TAG_MAX_TIME_TO_UNLOCK.equals(tag)) {
                     maximumTimeToUnlock = Long.parseLong(
                             parser.getAttributeValue(null, ATTR_VALUE));
                 } else if (TAG_STRONG_AUTH_UNLOCK_TIMEOUT.equals(tag)) {
@@ -1441,6 +1452,8 @@
                     pw.println(minimumPasswordMetrics.symbols);
             pw.print(prefix); pw.print("minimumPasswordNonLetter=");
                     pw.println(minimumPasswordMetrics.nonLetter);
+            pw.print(prefix); pw.print("passwordBlacklist=");
+                    pw.println(passwordBlacklistFile != null);
             pw.print(prefix); pw.print("maximumTimeToUnlock=");
                     pw.println(maximumTimeToUnlock);
             pw.print(prefix); pw.print("strongAuthUnlockTimeout=");
@@ -1693,6 +1706,10 @@
             return new LockPatternUtils(mContext);
         }
 
+        PasswordBlacklist newPasswordBlacklist(File file) {
+            return new PasswordBlacklist(file);
+        }
+
         boolean storageManagerIsFileBasedEncryptionEnabled() {
             return StorageManager.isFileEncryptedNativeOnly();
         }
@@ -2589,11 +2606,15 @@
         }
     }
 
-    private JournaledFile makeJournaledFile(int userHandle) {
-        final String base = userHandle == UserHandle.USER_SYSTEM
-                ? mInjector.getDevicePolicyFilePathForSystemUser() + DEVICE_POLICIES_XML
-                : new File(mInjector.environmentGetUserSystemDirectory(userHandle),
-                        DEVICE_POLICIES_XML).getAbsolutePath();
+    private File getPolicyFileDirectory(@UserIdInt int userId) {
+        return userId == UserHandle.USER_SYSTEM
+                ? new File(mInjector.getDevicePolicyFilePathForSystemUser())
+                : mInjector.environmentGetUserSystemDirectory(userId);
+    }
+
+    private JournaledFile makeJournaledFile(@UserIdInt int userId) {
+        final String base = new File(getPolicyFileDirectory(userId), DEVICE_POLICIES_XML)
+                .getAbsolutePath();
         if (VERBOSE_LOG) {
             Log.v(LOG_TAG, "Opening " + base);
         }
@@ -4064,6 +4085,136 @@
         }
     }
 
+    /* @return the password blacklist set by the admin or {@code null} if none. */
+    PasswordBlacklist getAdminPasswordBlacklistLocked(@NonNull ActiveAdmin admin) {
+        final int userId = UserHandle.getUserId(admin.getUid());
+        return admin.passwordBlacklistFile == null ? null : new PasswordBlacklist(
+                new File(getPolicyFileDirectory(userId), admin.passwordBlacklistFile));
+    }
+
+    private static final String PASSWORD_BLACKLIST_FILE_PREFIX = "password-blacklist-";
+    private static final String PASSWORD_BLACKLIST_FILE_SUFFIX = "";
+
+    @Override
+    public boolean setPasswordBlacklist(ComponentName who, String name, List<String> blacklist,
+            boolean parent) {
+        if (!mHasFeature) {
+            return false;
+        }
+        Preconditions.checkNotNull(who, "who is null");
+
+        synchronized (this) {
+            final ActiveAdmin admin = getActiveAdminForCallerLocked(
+                    who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, parent);
+            final int userId = mInjector.userHandleGetCallingUserId();
+            PasswordBlacklist adminBlacklist = getAdminPasswordBlacklistLocked(admin);
+
+            if (blacklist == null || blacklist.isEmpty()) {
+                // Remove the adminBlacklist
+                admin.passwordBlacklistFile = null;
+                saveSettingsLocked(userId);
+                if (adminBlacklist != null) {
+                    adminBlacklist.delete();
+                }
+                return true;
+            }
+
+            // Validate server side
+            Preconditions.checkNotNull(name, "name is null");
+            DevicePolicyManager.enforcePasswordBlacklistSize(blacklist);
+
+            // Blacklist is case insensitive so normalize to lower case
+            final int blacklistSize = blacklist.size();
+            for (int i = 0; i < blacklistSize; ++i) {
+                blacklist.set(i, blacklist.get(i).toLowerCase());
+            }
+
+            final boolean isNewBlacklist = adminBlacklist == null;
+            if (isNewBlacklist) {
+                // Create a new file for the blacklist. There could be multiple admins, each setting
+                // different blacklists, to restrict a user's credential, for example a managed
+                // profile can impose restrictions on its parent while the parent is already
+                // restricted by its own admin. A deterministic naming scheme would be fragile if
+                // new types of admin are introduced so we generate and save the file name instead.
+                // This isn't a temporary file but it reuses the name generation logic
+                final File file;
+                try {
+                    file = File.createTempFile(PASSWORD_BLACKLIST_FILE_PREFIX,
+                            PASSWORD_BLACKLIST_FILE_SUFFIX, getPolicyFileDirectory(userId));
+                } catch (IOException e) {
+                    Slog.e(LOG_TAG, "Failed to make a file for the blacklist", e);
+                    return false;
+                }
+                adminBlacklist = mInjector.newPasswordBlacklist(file);
+            }
+
+            if (adminBlacklist.savePasswordBlacklist(name, blacklist)) {
+                if (isNewBlacklist) {
+                    // The blacklist was saved so point the admin to the file
+                    admin.passwordBlacklistFile = adminBlacklist.getFile().getName();
+                    saveSettingsLocked(userId);
+                }
+                return true;
+            }
+        }
+
+        return false;
+    }
+
+    @Override
+    public String getPasswordBlacklistName(ComponentName who, @UserIdInt int userId,
+            boolean parent) {
+        if (!mHasFeature) {
+            return null;
+        }
+        Preconditions.checkNotNull(who, "who is null");
+        enforceFullCrossUsersPermission(userId);
+        synchronized (this) {
+            final ActiveAdmin admin = getActiveAdminForCallerLocked(
+                    who, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER, parent);
+            final PasswordBlacklist blacklist = getAdminPasswordBlacklistLocked(admin);
+            if (blacklist == null) {
+                return null;
+            }
+            return blacklist.getName();
+        }
+    }
+
+    @Override
+    public boolean isPasswordBlacklisted(@UserIdInt int userId, String password) {
+        if (!mHasFeature) {
+            return false;
+        }
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.TEST_BLACKLISTED_PASSWORD, null);
+        return isPasswordBlacklistedInternal(userId, password);
+    }
+
+    private boolean isPasswordBlacklistedInternal(@UserIdInt int userId, String password) {
+        Preconditions.checkNotNull(password, "Password is null");
+        enforceFullCrossUsersPermission(userId);
+
+        // Normalize to lower case for case insensitive blacklist match
+        final String lowerCasePassword = password.toLowerCase();
+
+        synchronized (this) {
+            final List<ActiveAdmin> admins =
+                    getActiveAdminsForLockscreenPoliciesLocked(userId, /* parent */ false);
+            final int N = admins.size();
+            for (int i = 0; i < N; i++) {
+                final PasswordBlacklist blacklist
+                        = getAdminPasswordBlacklistLocked(admins.get(i));
+                if (blacklist != null) {
+                    if (blacklist.isPasswordBlacklisted(lowerCasePassword)) {
+                        return true;
+                    }
+                }
+            }
+        }
+
+        return false;
+    }
+
     @Override
     public boolean isActivePasswordSufficient(int userHandle, boolean parent) {
         if (!mHasFeature) {
@@ -4420,6 +4571,11 @@
                     return false;
                 }
             }
+
+            if (isPasswordBlacklistedInternal(userHandle, password)) {
+                Slog.w(LOG_TAG, "resetPassword: the password is blacklisted");
+                return false;
+            }
         }
 
         DevicePolicyData policy = getUserData(userHandle);
@@ -4998,6 +5154,33 @@
     }
 
     @Override
+    public boolean setKeyPairCertificate(ComponentName who, String callerPackage, String alias,
+            byte[] cert, byte[] chain, boolean isUserSelectable) {
+        enforceCanManageScope(who, callerPackage, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER,
+                DELEGATION_CERT_INSTALL);
+
+        final int callingUid = mInjector.binderGetCallingUid();
+        final long id = mInjector.binderClearCallingIdentity();
+        try (final KeyChainConnection keyChainConnection =
+                KeyChain.bindAsUser(mContext, UserHandle.getUserHandleForUid(callingUid))) {
+            IKeyChainService keyChain = keyChainConnection.getService();
+            if (!keyChain.setKeyPairCertificate(alias, cert, chain)) {
+                return false;
+            }
+            keyChain.setUserSelectable(alias, isUserSelectable);
+            return true;
+        } catch (InterruptedException e) {
+            Log.w(LOG_TAG, "Interrupted while setting keypair certificate", e);
+            Thread.currentThread().interrupt();
+        } catch (RemoteException e) {
+            Log.e(LOG_TAG, "Failed setting keypair certificate", e);
+        } finally {
+            mInjector.binderRestoreCallingIdentity(id);
+        }
+        return false;
+    }
+
+    @Override
     public void choosePrivateKeyAlias(final int uid, final Uri uri, final String alias,
             final IBinder response) {
         // Caller UID needs to be trusted, so we restrict this method to SYSTEM_UID callers.
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PasswordBlacklist.java b/services/devicepolicy/java/com/android/server/devicepolicy/PasswordBlacklist.java
new file mode 100644
index 0000000..6a9b53a
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PasswordBlacklist.java
@@ -0,0 +1,165 @@
+/*
+ * 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.devicepolicy;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.util.AtomicFile;
+import android.util.Slog;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * Manages the blacklisted passwords.
+ *
+ * This caller must ensure synchronized access.
+ */
+public class PasswordBlacklist {
+    private static final String TAG = "PasswordBlacklist";
+
+    private final AtomicFile mFile;
+
+    /**
+     * Create an object to manage the password blacklist.
+     *
+     * This is a lightweight operation to prepare variables but not perform any IO.
+     */
+    public PasswordBlacklist(File file) {
+        mFile = new AtomicFile(file);
+    }
+
+    /**
+     * Atomically replace the blacklist.
+     *
+     * Pass {@code null} for an empty list.
+     */
+    public boolean savePasswordBlacklist(@NonNull String name, @NonNull List<String> blacklist) {
+        FileOutputStream fos = null;
+        try {
+            fos = mFile.startWrite();
+            final DataOutputStream out = buildStreamForWriting(fos);
+            final Header header = new Header(Header.VERSION_1, name, blacklist.size());
+            header.write(out);
+            final int blacklistSize = blacklist.size();
+            for (int i = 0; i < blacklistSize; ++i) {
+                out.writeUTF(blacklist.get(i));
+            }
+            out.flush();
+            mFile.finishWrite(fos);
+            return true;
+        } catch (IOException e) {
+            mFile.failWrite(fos);
+            return false;
+        }
+    }
+
+    /** @return the name of the blacklist or {@code null} if none set. */
+    public String getName() {
+        try (DataInputStream in = openForReading()) {
+            return Header.read(in).mName;
+        } catch (IOException e) {
+            Slog.wtf(TAG, "Failed to read blacklist file", e);
+        }
+        return null;
+    }
+
+    /** @return the number of blacklisted passwords. */
+    public int getSize() {
+        final int blacklistSize;
+        try (DataInputStream in = openForReading()) {
+            return Header.read(in).mSize;
+        } catch (IOException e) {
+            Slog.wtf(TAG, "Failed to read blacklist file", e);
+        }
+        return 0;
+    }
+
+    /** @return whether the password matches an blacklisted item. */
+    public boolean isPasswordBlacklisted(@NonNull String password) {
+        final int blacklistSize;
+        try (DataInputStream in = openForReading()) {
+            final Header header = Header.read(in);
+            for (int i = 0; i < header.mSize; ++i) {
+                if (in.readUTF().equals(password)) {
+                    return true;
+                }
+            }
+        } catch (IOException e) {
+            Slog.wtf(TAG, "Failed to read blacklist file", e);
+            // Fail safe and block all passwords. Setting a new blacklist should resolve this
+            // problem which can be identified by examining the log.
+            return true;
+        }
+        return false;
+    }
+
+    /** Delete the blacklist completely from disk. */
+    public void delete() {
+        mFile.delete();
+    }
+
+    /** Get the file the blacklist is stored in. */
+    public File getFile() {
+        return mFile.getBaseFile();
+    }
+
+    private DataOutputStream buildStreamForWriting(FileOutputStream fos) {
+        return new DataOutputStream(new BufferedOutputStream(fos));
+    }
+
+    private DataInputStream openForReading() throws IOException {
+        return new DataInputStream(new BufferedInputStream(mFile.openRead()));
+    }
+
+    /**
+     * Helper to read and write the header of the blacklist file.
+     */
+    private static class Header {
+        static final int VERSION_1 = 1;
+
+        final int mVersion; // File format version
+        final String mName;
+        final int mSize;
+
+        Header(int version, String name, int size) {
+            mVersion = version;
+            mName = name;
+            mSize = size;
+        }
+
+        void write(DataOutputStream out) throws IOException {
+            out.writeInt(mVersion);
+            out.writeUTF(mName);
+            out.writeInt(mSize);
+        }
+
+        static Header read(DataInputStream in) throws IOException {
+            final int version = in.readInt();
+            final String name = in.readUTF();
+            final int size = in.readInt();
+            return new Header(version, name, size);
+        }
+    }
+}
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/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
index d168479..0650acb 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -102,6 +102,11 @@
         this.context = injector.context;
     }
 
+    @Override
+    public boolean isPasswordBlacklisted(int userId, String password) {
+        return false;
+    }
+
 
     public void notifyChangeToContentObserver(Uri uri, int userHandle) {
         ContentObserver co = mMockInjector.mContentObservers.get(new Pair<>(uri, userHandle));
@@ -205,6 +210,11 @@
         }
 
         @Override
+        PasswordBlacklist newPasswordBlacklist(File file) {
+            return services.passwordBlacklist;
+        }
+
+        @Override
         boolean storageManagerIsFileBasedEncryptionEnabled() {
             return services.storageManager.isFileBasedEncryptionEnabled();
         }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index ca918c6..4779474 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -3765,6 +3765,36 @@
         assertTrue(dpm.clearResetPasswordToken(admin1));
     }
 
+    public void testSetPasswordBlacklistCannotBeCalledByNonAdmin() throws Exception {
+        assertExpectException(SecurityException.class, /* messageRegex= */ null,
+                () -> dpm.setPasswordBlacklist(admin1, null, null));
+        verifyZeroInteractions(getServices().passwordBlacklist);
+    }
+
+    public void testClearingPasswordBlacklistDoesNotCreateNewBlacklist() throws Exception {
+        setupProfileOwner();
+        dpm.setPasswordBlacklist(admin1, null, null);
+        verifyZeroInteractions(getServices().passwordBlacklist);
+    }
+
+    public void testSetPasswordBlacklistCreatesNewBlacklist() throws Exception {
+        final String name = "myblacklist";
+        final List<String> explicit = Arrays.asList("password", "letmein");
+        setupProfileOwner();
+        dpm.setPasswordBlacklist(admin1, name, explicit);
+        verify(getServices().passwordBlacklist).savePasswordBlacklist(name, explicit);
+    }
+
+    public void testSetPasswordBlacklistOnlyConvertsExplicitToLowerCase() throws Exception {
+        final List<String> mixedCase = Arrays.asList("password", "LETMEIN", "FooTBAll");
+        final List<String> lowerCase = Arrays.asList("password", "letmein", "football");
+        mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
+        setupDeviceOwner();
+        final String name = "Name of the Blacklist";
+        dpm.setPasswordBlacklist(admin1, name, mixedCase);
+        verify(getServices().passwordBlacklist).savePasswordBlacklist(name, lowerCase);
+    }
+
     public void testIsActivePasswordSufficient() throws Exception {
         mContext.binder.callingUid = DpmMockContext.CALLER_SYSTEM_USER_UID;
         mContext.packageName = admin1.getPackageName();
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
index 4ee5ba6..8cb0459 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/MockSystemServices.java
@@ -86,6 +86,7 @@
     public final IBackupManager ibackupManager;
     public final IAudioService iaudioService;
     public final LockPatternUtils lockPatternUtils;
+    public final PasswordBlacklist passwordBlacklist;
     public final StorageManagerForMock storageManager;
     public final WifiManager wifiManager;
     public final SettingsForMock settings;
@@ -120,6 +121,7 @@
         ibackupManager = mock(IBackupManager.class);
         iaudioService = mock(IAudioService.class);
         lockPatternUtils = mock(LockPatternUtils.class);
+        passwordBlacklist = mock(PasswordBlacklist.class);
         storageManager = mock(StorageManagerForMock.class);
         wifiManager = mock(WifiManager.class);
         settings = mock(SettingsForMock.class);
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/PasswordBlacklistTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/PasswordBlacklistTest.java
new file mode 100644
index 0000000..1b3fc2c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/PasswordBlacklistTest.java
@@ -0,0 +1,110 @@
+/*
+ * 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.devicepolicy;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Unit tests for {@link PasswordBlacklist}.
+ *
+ * bit FrameworksServicesTests:com.android.server.devicepolicy.PasswordBlacklistTest
+ * runtest -x frameworks/base/services/tests/servicestests/src/com/android/server/devicepolicy/PasswordBlacklistTest.java
+ */
+@RunWith(AndroidJUnit4.class)
+public final class PasswordBlacklistTest {
+    private File mBlacklistFile;
+    private PasswordBlacklist mBlacklist;
+
+    @Before
+    public void setUp() throws IOException {
+        mBlacklistFile = File.createTempFile("pwdbl", null);
+        mBlacklist = new PasswordBlacklist(mBlacklistFile);
+    }
+
+    @After
+    public void tearDown() {
+        mBlacklist.delete();
+    }
+
+    @Test
+    public void matchIsExact() {
+        // Note: Case sensitivity is handled by the user of PasswordBlacklist by normalizing the
+        // values stored in and tested against it.
+        mBlacklist.savePasswordBlacklist("matchIsExact", Arrays.asList("password", "qWERty"));
+        assertTrue(mBlacklist.isPasswordBlacklisted("password"));
+        assertTrue(mBlacklist.isPasswordBlacklisted("qWERty"));
+        assertFalse(mBlacklist.isPasswordBlacklisted("Password"));
+        assertFalse(mBlacklist.isPasswordBlacklisted("qwert"));
+        assertFalse(mBlacklist.isPasswordBlacklisted("letmein"));
+    }
+
+    @Test
+    public void matchIsNotRegex() {
+        mBlacklist.savePasswordBlacklist("matchIsNotRegex", Arrays.asList("a+b*"));
+        assertTrue(mBlacklist.isPasswordBlacklisted("a+b*"));
+        assertFalse(mBlacklist.isPasswordBlacklisted("aaaa"));
+        assertFalse(mBlacklist.isPasswordBlacklisted("abbbb"));
+        assertFalse(mBlacklist.isPasswordBlacklisted("aaaa"));
+    }
+
+    @Test
+    public void matchFailsSafe() throws IOException {
+        try (FileOutputStream fos = new FileOutputStream(mBlacklistFile)) {
+            // Write a malformed blacklist file
+            fos.write(17);
+        }
+        assertTrue(mBlacklist.isPasswordBlacklisted("anything"));
+        assertTrue(mBlacklist.isPasswordBlacklisted("at"));
+        assertTrue(mBlacklist.isPasswordBlacklisted("ALL"));
+    }
+
+    @Test
+    public void blacklistCanBeNamed() {
+        final String name = "identifier";
+        mBlacklist.savePasswordBlacklist(name, Arrays.asList("one", "two", "three"));
+        assertEquals(mBlacklist.getName(), name);
+    }
+
+    @Test
+    public void reportsTheCorrectNumberOfEntries() {
+        mBlacklist.savePasswordBlacklist("Count Entries", Arrays.asList("1", "2", "3", "4"));
+        assertEquals(mBlacklist.getSize(), 4);
+    }
+
+    @Test
+    public void reportsBlacklistFile() {
+        assertEquals(mBlacklistFile, mBlacklist.getFile());
+    }
+}
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/SecureBoxTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/SecureBoxTest.java
new file mode 100644
index 0000000..72b69f0
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/SecureBoxTest.java
@@ -0,0 +1,365 @@
+/*
+ * 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 com.google.common.truth.Truth.assertThat;
+import static org.testng.Assert.assertThrows;
+import static org.testng.Assert.expectThrows;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import java.math.BigInteger;
+import java.nio.charset.StandardCharsets;
+import java.security.InvalidKeyException;
+import java.security.KeyFactory;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.spec.ECPrivateKeySpec;
+import javax.crypto.AEADBadTagException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class SecureBoxTest {
+
+    private static final int EC_PUBLIC_KEY_LEN_BYTES = 65;
+    private static final int NUM_TEST_ITERATIONS = 100;
+    private static final int VERSION_LEN_BYTES = 2;
+
+    // The following fixtures were produced by the C implementation of SecureBox v2. We use these to
+    // cross-verify the two implementations.
+    private static final byte[] VAULT_PARAMS =
+            new byte[] {
+                (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, (byte) 0x31,
+                (byte) 0x32, (byte) 0x33, (byte) 0x34, (byte) 0x00, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00, (byte) 0x78, (byte) 0x56, (byte) 0x34, (byte) 0x12, (byte) 0x00,
+                (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x0a, (byte) 0x00, (byte) 0x00,
+                (byte) 0x00
+            };
+    private static final byte[] VAULT_CHALLENGE = getBytes("Not a real vault challenge");
+    private static final byte[] THM_KF_HASH = getBytes("12345678901234567890123456789012");
+    private static final byte[] ENCRYPTED_RECOVERY_KEY =
+            new byte[] {
+                (byte) 0x02, (byte) 0x00, (byte) 0x04, (byte) 0xe3, (byte) 0xa8, (byte) 0xd0,
+                (byte) 0x32, (byte) 0x3c, (byte) 0xc7, (byte) 0xe5, (byte) 0xe8, (byte) 0xc1,
+                (byte) 0x73, (byte) 0x4c, (byte) 0x75, (byte) 0x20, (byte) 0x2e, (byte) 0xb7,
+                (byte) 0xba, (byte) 0xef, (byte) 0x3e, (byte) 0x3e, (byte) 0xa6, (byte) 0x93,
+                (byte) 0xe9, (byte) 0xde, (byte) 0xa7, (byte) 0x00, (byte) 0x09, (byte) 0xba,
+                (byte) 0xa8, (byte) 0x9c, (byte) 0xac, (byte) 0x72, (byte) 0xff, (byte) 0xf6,
+                (byte) 0x84, (byte) 0x16, (byte) 0xb0, (byte) 0xff, (byte) 0x47, (byte) 0x98,
+                (byte) 0x53, (byte) 0xc4, (byte) 0xa3, (byte) 0x4a, (byte) 0x54, (byte) 0x21,
+                (byte) 0x8e, (byte) 0x00, (byte) 0x4b, (byte) 0xfa, (byte) 0xce, (byte) 0xe3,
+                (byte) 0x79, (byte) 0x8e, (byte) 0x20, (byte) 0x7c, (byte) 0x9b, (byte) 0xc4,
+                (byte) 0x7c, (byte) 0xd5, (byte) 0x33, (byte) 0x70, (byte) 0x96, (byte) 0xdc,
+                (byte) 0xa0, (byte) 0x1f, (byte) 0x6e, (byte) 0xbb, (byte) 0x5d, (byte) 0x0c,
+                (byte) 0x64, (byte) 0x5f, (byte) 0xed, (byte) 0xbf, (byte) 0x79, (byte) 0x8a,
+                (byte) 0x0e, (byte) 0xd6, (byte) 0x4b, (byte) 0x93, (byte) 0xc9, (byte) 0xcd,
+                (byte) 0x25, (byte) 0x06, (byte) 0x73, (byte) 0x5e, (byte) 0xdb, (byte) 0xac,
+                (byte) 0xa8, (byte) 0xeb, (byte) 0x6e, (byte) 0x26, (byte) 0x77, (byte) 0x56,
+                (byte) 0xd1, (byte) 0x23, (byte) 0x48, (byte) 0xb6, (byte) 0x6a, (byte) 0x15,
+                (byte) 0xd4, (byte) 0x3e, (byte) 0x38, (byte) 0x7d, (byte) 0x6f, (byte) 0x6f,
+                (byte) 0x7c, (byte) 0x0b, (byte) 0x93, (byte) 0x4e, (byte) 0xb3, (byte) 0x21,
+                (byte) 0x44, (byte) 0x86, (byte) 0xf3, (byte) 0x2e
+            };
+    private static final byte[] KEY_CLAIMANT = getBytes("asdfasdfasdfasdf");
+    private static final byte[] RECOVERY_CLAIM =
+            new byte[] {
+                (byte) 0x02, (byte) 0x00, (byte) 0x04, (byte) 0x16, (byte) 0x75, (byte) 0x5b,
+                (byte) 0xa2, (byte) 0xdc, (byte) 0x2b, (byte) 0x58, (byte) 0xb9, (byte) 0x66,
+                (byte) 0xcb, (byte) 0x6f, (byte) 0xb1, (byte) 0xc1, (byte) 0xb0, (byte) 0x1d,
+                (byte) 0x82, (byte) 0x29, (byte) 0x97, (byte) 0xec, (byte) 0x65, (byte) 0x5e,
+                (byte) 0xef, (byte) 0x14, (byte) 0xc7, (byte) 0xf0, (byte) 0xf1, (byte) 0x83,
+                (byte) 0x15, (byte) 0x0b, (byte) 0xcb, (byte) 0x33, (byte) 0x2d, (byte) 0x05,
+                (byte) 0x20, (byte) 0xdc, (byte) 0xc7, (byte) 0x0d, (byte) 0xc8, (byte) 0xc0,
+                (byte) 0xc9, (byte) 0xa8, (byte) 0x67, (byte) 0xc8, (byte) 0x16, (byte) 0xfe,
+                (byte) 0xfb, (byte) 0xb0, (byte) 0x28, (byte) 0x8e, (byte) 0x4f, (byte) 0xd5,
+                (byte) 0x31, (byte) 0xa7, (byte) 0x94, (byte) 0x33, (byte) 0x23, (byte) 0x15,
+                (byte) 0x04, (byte) 0xbf, (byte) 0x13, (byte) 0x6a, (byte) 0x28, (byte) 0x8f,
+                (byte) 0xa6, (byte) 0xfc, (byte) 0x01, (byte) 0xd5, (byte) 0x69, (byte) 0x3d,
+                (byte) 0x96, (byte) 0x0c, (byte) 0x37, (byte) 0xb4, (byte) 0x1e, (byte) 0x13,
+                (byte) 0x40, (byte) 0xcc, (byte) 0x44, (byte) 0x19, (byte) 0xf2, (byte) 0xdb,
+                (byte) 0x49, (byte) 0x80, (byte) 0x9f, (byte) 0xef, (byte) 0xee, (byte) 0x41,
+                (byte) 0xe6, (byte) 0x3f, (byte) 0xa8, (byte) 0xea, (byte) 0x89, (byte) 0xfe,
+                (byte) 0x56, (byte) 0x20, (byte) 0xba, (byte) 0x90, (byte) 0x9a, (byte) 0xba,
+                (byte) 0x0e, (byte) 0x30, (byte) 0xa7, (byte) 0x2b, (byte) 0x0a, (byte) 0x12,
+                (byte) 0x0b, (byte) 0x03, (byte) 0xd1, (byte) 0x0c, (byte) 0x8e, (byte) 0x82,
+                (byte) 0x03, (byte) 0xa1, (byte) 0x7f, (byte) 0xc8, (byte) 0xd0, (byte) 0xa9,
+                (byte) 0x86, (byte) 0x55, (byte) 0x63, (byte) 0xdc, (byte) 0x70, (byte) 0x34,
+                (byte) 0x21, (byte) 0x2a, (byte) 0x41, (byte) 0x3f, (byte) 0xbb, (byte) 0x82,
+                (byte) 0x82, (byte) 0xf9, (byte) 0x2b, (byte) 0xd2, (byte) 0x33, (byte) 0x03,
+                (byte) 0x50, (byte) 0xd2, (byte) 0x27, (byte) 0xeb, (byte) 0x1a
+            };
+
+    private static final byte[] TEST_SHARED_SECRET = getBytes("TEST_SHARED_SECRET");
+    private static final byte[] TEST_HEADER = getBytes("TEST_HEADER");
+    private static final byte[] TEST_PAYLOAD = getBytes("TEST_PAYLOAD");
+
+    private static final PublicKey THM_PUBLIC_KEY;
+    private static final PrivateKey THM_PRIVATE_KEY;
+
+    static {
+        try {
+            THM_PUBLIC_KEY =
+                    SecureBox.decodePublicKey(
+                            new byte[] {
+                                (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
+                            });
+            THM_PRIVATE_KEY =
+                    decodePrivateKey(
+                            new byte[] {
+                                (byte) 0x70, (byte) 0x01, (byte) 0xc7, (byte) 0x87, (byte) 0x32,
+                                (byte) 0x2f, (byte) 0x1c, (byte) 0x9a, (byte) 0x6e, (byte) 0xb1,
+                                (byte) 0x91, (byte) 0xca, (byte) 0x4e, (byte) 0xb5, (byte) 0x44,
+                                (byte) 0xba, (byte) 0xc8, (byte) 0x68, (byte) 0xc6, (byte) 0x0a,
+                                (byte) 0x76, (byte) 0xcb, (byte) 0xd3, (byte) 0x63, (byte) 0x67,
+                                (byte) 0x7c, (byte) 0xb0, (byte) 0x11, (byte) 0x82, (byte) 0x65,
+                                (byte) 0x77, (byte) 0x01
+                            });
+        } catch (Exception ex) {
+            throw new RuntimeException(ex);
+        }
+    }
+
+    @Test
+    public void genKeyPair_alwaysReturnsANewKeyPair() throws Exception {
+        KeyPair keyPair1 = SecureBox.genKeyPair();
+        KeyPair keyPair2 = SecureBox.genKeyPair();
+        assertThat(keyPair1).isNotEqualTo(keyPair2);
+    }
+
+    @Test
+    public void decryptRecoveryClaim() throws Exception {
+        byte[] claimContent =
+                SecureBox.decrypt(
+                        THM_PRIVATE_KEY,
+                        /*sharedSecret=*/ null,
+                        SecureBox.concat(getBytes("V1 KF_claim"), VAULT_PARAMS, VAULT_CHALLENGE),
+                        RECOVERY_CLAIM);
+        assertThat(claimContent).isEqualTo(SecureBox.concat(THM_KF_HASH, KEY_CLAIMANT));
+    }
+
+    @Test
+    public void decryptRecoveryKey_doesNotThrowForValidAuthenticationTag() throws Exception {
+        SecureBox.decrypt(
+                THM_PRIVATE_KEY,
+                THM_KF_HASH,
+                SecureBox.concat(getBytes("V1 THM_encrypted_recovery_key"), VAULT_PARAMS),
+                ENCRYPTED_RECOVERY_KEY);
+    }
+
+    @Test
+    public void encryptThenDecrypt() throws Exception {
+        byte[] state = TEST_PAYLOAD;
+        // Iterate multiple times to amplify any errors
+        for (int i = 0; i < NUM_TEST_ITERATIONS; i++) {
+            state = SecureBox.encrypt(THM_PUBLIC_KEY, TEST_SHARED_SECRET, TEST_HEADER, state);
+        }
+        for (int i = 0; i < NUM_TEST_ITERATIONS; i++) {
+            state = SecureBox.decrypt(THM_PRIVATE_KEY, TEST_SHARED_SECRET, TEST_HEADER, state);
+        }
+        assertThat(state).isEqualTo(TEST_PAYLOAD);
+    }
+
+    @Test
+    public void encryptThenDecrypt_nullPublicPrivateKeys() throws Exception {
+        byte[] encrypted =
+                SecureBox.encrypt(
+                        /*theirPublicKey=*/ null, TEST_SHARED_SECRET, TEST_HEADER, TEST_PAYLOAD);
+        byte[] decrypted =
+                SecureBox.decrypt(
+                        /*ourPrivateKey=*/ null, TEST_SHARED_SECRET, TEST_HEADER, encrypted);
+        assertThat(decrypted).isEqualTo(TEST_PAYLOAD);
+    }
+
+    @Test
+    public void encryptThenDecrypt_nullSharedSecret() throws Exception {
+        byte[] encrypted =
+                SecureBox.encrypt(
+                        THM_PUBLIC_KEY, /*sharedSecret=*/ null, TEST_HEADER, TEST_PAYLOAD);
+        byte[] decrypted =
+                SecureBox.decrypt(THM_PRIVATE_KEY, /*sharedSecret=*/ null, TEST_HEADER, encrypted);
+        assertThat(decrypted).isEqualTo(TEST_PAYLOAD);
+    }
+
+    @Test
+    public void encryptThenDecrypt_nullHeader() throws Exception {
+        byte[] encrypted =
+                SecureBox.encrypt(
+                        THM_PUBLIC_KEY, TEST_SHARED_SECRET, /*header=*/ null, TEST_PAYLOAD);
+        byte[] decrypted =
+                SecureBox.decrypt(THM_PRIVATE_KEY, TEST_SHARED_SECRET, /*header=*/ null, encrypted);
+        assertThat(decrypted).isEqualTo(TEST_PAYLOAD);
+    }
+
+    @Test
+    public void encryptThenDecrypt_nullPayload() throws Exception {
+        byte[] encrypted =
+                SecureBox.encrypt(
+                        THM_PUBLIC_KEY, TEST_SHARED_SECRET, TEST_HEADER, /*payload=*/ null);
+        byte[] decrypted =
+                SecureBox.decrypt(
+                        THM_PRIVATE_KEY,
+                        TEST_SHARED_SECRET,
+                        TEST_HEADER,
+                        /*encryptedPayload=*/ encrypted);
+        assertThat(decrypted.length).isEqualTo(0);
+    }
+
+    @Test
+    public void encrypt_nullPublicKeyAndSharedSecret() throws Exception {
+        IllegalArgumentException expected =
+                expectThrows(
+                        IllegalArgumentException.class,
+                        () ->
+                                SecureBox.encrypt(
+                                        /*theirPublicKey=*/ null,
+                                        /*sharedSecret=*/ null,
+                                        TEST_HEADER,
+                                        TEST_PAYLOAD));
+        assertThat(expected.getMessage()).contains("public key and shared secret");
+    }
+
+    @Test
+    public void decrypt_nullPrivateKeyAndSharedSecret() throws Exception {
+        IllegalArgumentException expected =
+                expectThrows(
+                        IllegalArgumentException.class,
+                        () ->
+                                SecureBox.decrypt(
+                                        /*ourPrivateKey=*/ null,
+                                        /*sharedSecret=*/ null,
+                                        TEST_HEADER,
+                                        TEST_PAYLOAD));
+        assertThat(expected.getMessage()).contains("private key and shared secret");
+    }
+
+    @Test
+    public void decrypt_nullEncryptedPayload() throws Exception {
+        IllegalArgumentException expected =
+                expectThrows(
+                        IllegalArgumentException.class,
+                        () ->
+                                SecureBox.decrypt(
+                                        THM_PRIVATE_KEY,
+                                        TEST_SHARED_SECRET,
+                                        TEST_HEADER,
+                                        /*encryptedPayload=*/ null));
+        assertThat(expected.getMessage()).contains("payload");
+    }
+
+    @Test
+    public void decrypt_badAuthenticationTag() throws Exception {
+        byte[] encrypted =
+                SecureBox.encrypt(THM_PUBLIC_KEY, TEST_SHARED_SECRET, TEST_HEADER, TEST_PAYLOAD);
+        encrypted[encrypted.length - 1] ^= (byte) 1;
+
+        assertThrows(
+                AEADBadTagException.class,
+                () ->
+                        SecureBox.decrypt(
+                                THM_PRIVATE_KEY, TEST_SHARED_SECRET, TEST_HEADER, encrypted));
+    }
+
+    @Test
+    public void encrypt_invalidPublicKey() throws Exception {
+        KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
+        keyGen.initialize(2048);
+        PublicKey publicKey = keyGen.genKeyPair().getPublic();
+
+        assertThrows(
+                InvalidKeyException.class,
+                () -> SecureBox.encrypt(publicKey, TEST_SHARED_SECRET, TEST_HEADER, TEST_PAYLOAD));
+    }
+
+    @Test
+    public void decrypt_invalidPrivateKey() throws Exception {
+        byte[] encrypted =
+                SecureBox.encrypt(THM_PUBLIC_KEY, TEST_SHARED_SECRET, TEST_HEADER, TEST_PAYLOAD);
+        KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
+        keyGen.initialize(2048);
+        PrivateKey privateKey = keyGen.genKeyPair().getPrivate();
+
+        assertThrows(
+                InvalidKeyException.class,
+                () -> SecureBox.decrypt(privateKey, TEST_SHARED_SECRET, TEST_HEADER, encrypted));
+    }
+
+    @Test
+    public void decrypt_publicKeyOutsideCurve() throws Exception {
+        byte[] encrypted =
+                SecureBox.encrypt(THM_PUBLIC_KEY, TEST_SHARED_SECRET, TEST_HEADER, TEST_PAYLOAD);
+        // Flip the least significant bit of the encoded public key
+        encrypted[VERSION_LEN_BYTES + EC_PUBLIC_KEY_LEN_BYTES - 1] ^= (byte) 1;
+
+        InvalidKeyException expected =
+                expectThrows(
+                        InvalidKeyException.class,
+                        () ->
+                                SecureBox.decrypt(
+                                        THM_PRIVATE_KEY,
+                                        TEST_SHARED_SECRET,
+                                        TEST_HEADER,
+                                        encrypted));
+        assertThat(expected.getMessage()).contains("expected curve");
+    }
+
+    @Test
+    public void encodeThenDecodePublicKey() throws Exception {
+        for (int i = 0; i < NUM_TEST_ITERATIONS; i++) {
+            PublicKey originalKey = SecureBox.genKeyPair().getPublic();
+            byte[] encodedKey = SecureBox.encodePublicKey(originalKey);
+            PublicKey decodedKey = SecureBox.decodePublicKey(encodedKey);
+            assertThat(originalKey).isEqualTo(decodedKey);
+        }
+    }
+
+    private static byte[] getBytes(String str) {
+        return str.getBytes(StandardCharsets.UTF_8);
+    }
+
+    private static PrivateKey decodePrivateKey(byte[] keyBytes) throws Exception {
+        assertThat(keyBytes.length).isEqualTo(32);
+        BigInteger priv = new BigInteger(/*signum=*/ 1, keyBytes);
+        KeyFactory keyFactory = KeyFactory.getInstance("EC");
+        return keyFactory.generatePrivate(new ECPrivateKeySpec(priv, SecureBox.EC_PARAM_SPEC));
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java
index 3d5b958..d5ad959 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/storage/RecoverableKeyStoreDbTest.java
@@ -30,7 +30,6 @@
 import android.support.test.InstrumentationRegistry;
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
-
 import com.android.server.locksettings.recoverablekeystore.WrappedKey;
 
 import java.io.File;
@@ -65,7 +64,7 @@
         WrappedKey oldWrappedKey = new WrappedKey(
                 getUtf8Bytes("nonce1"),
                 getUtf8Bytes("keymaterial1"),
-                /*platformKeyGenerationId=*/1);
+                /*platformKeyGenerationId=*/ 1);
         mRecoverableKeyStoreDb.insertKey(
                 userId, alias, oldWrappedKey);
         byte[] nonce = getUtf8Bytes("nonce2");
@@ -83,6 +82,29 @@
     }
 
     @Test
+    public void insertKey_allowsTwoUidsToHaveSameAlias() {
+        String alias = "pcoulton";
+        WrappedKey key1 = new WrappedKey(
+                getUtf8Bytes("nonce1"),
+                getUtf8Bytes("key1"),
+                /*platformKeyGenerationId=*/ 1);
+        WrappedKey key2 = new WrappedKey(
+                getUtf8Bytes("nonce2"),
+                getUtf8Bytes("key2"),
+                /*platformKeyGenerationId=*/ 1);
+
+        mRecoverableKeyStoreDb.insertKey(/*uid=*/ 1, alias, key1);
+        mRecoverableKeyStoreDb.insertKey(/*uid=*/ 2, alias, key2);
+
+        assertArrayEquals(
+                getUtf8Bytes("nonce1"),
+                mRecoverableKeyStoreDb.getKey(1, alias).getNonce());
+        assertArrayEquals(
+                getUtf8Bytes("nonce2"),
+                mRecoverableKeyStoreDb.getKey(2, alias).getNonce());
+    }
+
+    @Test
     public void getKey_returnsNullIfNoKey() {
         WrappedKey key = mRecoverableKeyStoreDb.getKey(
                 /*userId=*/ 1, /*alias=*/ "hello");
@@ -157,6 +179,29 @@
         assertTrue(keys.isEmpty());
     }
 
+    @Test
+    public void getPlatformKeyGenerationId_returnsGenerationId() {
+        int userId = 42;
+        int generationId = 24;
+        mRecoverableKeyStoreDb.setPlatformKeyGenerationId(userId, generationId);
+
+        assertEquals(generationId, mRecoverableKeyStoreDb.getPlatformKeyGenerationId(userId));
+    }
+
+    @Test
+    public void getPlatformKeyGenerationId_returnsMinusOneIfNoEntry() {
+        assertEquals(-1, mRecoverableKeyStoreDb.getPlatformKeyGenerationId(42));
+    }
+
+    @Test
+    public void setPlatformKeyGenerationId_replacesOldEntry() {
+        int userId = 42;
+        mRecoverableKeyStoreDb.setPlatformKeyGenerationId(userId, 1);
+        mRecoverableKeyStoreDb.setPlatformKeyGenerationId(userId, 2);
+
+        assertEquals(2, mRecoverableKeyStoreDb.getPlatformKeyGenerationId(userId));
+    }
+
     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/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 93c0c51..9e77992 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -1176,12 +1176,14 @@
     }
 
     /**
-     * Returns the NAI. Return null if NAI is not available.
-     *
+     * Returns the Network Access Identifier (NAI). Return null if NAI is not available.
+     * <p>
+     * Requires Permission:
+     *   {@link android.Manifest.permission#READ_PHONE_STATE READ_PHONE_STATE}
      */
-    /** {@hide}*/
+    @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
     public String getNai() {
-        return getNai(getSlotIndex());
+        return getNaiBySubscriberId(getSubId());
     }
 
     /**
@@ -1192,11 +1194,18 @@
     /** {@hide}*/
     public String getNai(int slotIndex) {
         int[] subId = SubscriptionManager.getSubId(slotIndex);
+        if (subId == null) {
+            return null;
+        }
+        return getNaiBySubscriberId(subId[0]);
+    }
+
+    private String getNaiBySubscriberId(int subId) {
         try {
             IPhoneSubInfo info = getSubscriberInfo();
             if (info == null)
                 return null;
-            String nai = info.getNaiForSubscriber(subId[0], mContext.getOpPackageName());
+            String nai = info.getNaiForSubscriber(subId, mContext.getOpPackageName());
             if (Log.isLoggable(TAG, Log.VERBOSE)) {
                 Rlog.v(TAG, "Nai = " + nai);
             }
diff --git a/telephony/java/android/telephony/euicc/EuiccManager.java b/telephony/java/android/telephony/euicc/EuiccManager.java
index 2507cfee..973df31 100644
--- a/telephony/java/android/telephony/euicc/EuiccManager.java
+++ b/telephony/java/android/telephony/euicc/EuiccManager.java
@@ -168,12 +168,10 @@
     public static final String META_DATA_CARRIER_ICON = "android.telephony.euicc.carriericon";
 
     private final Context mContext;
-    private final IEuiccController mController;
 
     /** @hide */
     public EuiccManager(Context context) {
         mContext = context;
-        mController = IEuiccController.Stub.asInterface(ServiceManager.getService("econtroller"));
     }
 
     /**
@@ -189,7 +187,7 @@
     public boolean isEnabled() {
         // In the future, this may reach out to IEuiccController (if non-null) to check any dynamic
         // restrictions.
-        return mController != null;
+        return getIEuiccController() != null;
     }
 
     /**
@@ -206,7 +204,7 @@
             return null;
         }
         try {
-            return mController.getEid();
+            return getIEuiccController().getEid();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -232,7 +230,7 @@
             return;
         }
         try {
-            mController.downloadSubscription(subscription, switchAfterDownload,
+            getIEuiccController().downloadSubscription(subscription, switchAfterDownload,
                     mContext.getOpPackageName(), callbackIntent);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -296,7 +294,7 @@
             return;
         }
         try {
-            mController.continueOperation(resolutionIntent, resolutionExtras);
+            getIEuiccController().continueOperation(resolutionIntent, resolutionExtras);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -328,7 +326,7 @@
             return;
         }
         try {
-            mController.getDownloadableSubscriptionMetadata(
+            getIEuiccController().getDownloadableSubscriptionMetadata(
                     subscription, mContext.getOpPackageName(), callbackIntent);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -358,7 +356,7 @@
             return;
         }
         try {
-            mController.getDefaultDownloadableSubscriptionList(
+            getIEuiccController().getDefaultDownloadableSubscriptionList(
                     mContext.getOpPackageName(), callbackIntent);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -377,7 +375,7 @@
             return null;
         }
         try {
-            return mController.getEuiccInfo();
+            return getIEuiccController().getEuiccInfo();
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -402,7 +400,7 @@
             return;
         }
         try {
-            mController.deleteSubscription(
+            getIEuiccController().deleteSubscription(
                     subscriptionId, mContext.getOpPackageName(), callbackIntent);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -429,7 +427,7 @@
             return;
         }
         try {
-            mController.switchToSubscription(
+            getIEuiccController().switchToSubscription(
                     subscriptionId, mContext.getOpPackageName(), callbackIntent);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -455,7 +453,8 @@
             return;
         }
         try {
-            mController.updateSubscriptionNickname(subscriptionId, nickname, callbackIntent);
+            getIEuiccController().updateSubscriptionNickname(
+                    subscriptionId, nickname, callbackIntent);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -477,7 +476,7 @@
             return;
         }
         try {
-            mController.eraseSubscriptions(callbackIntent);
+            getIEuiccController().eraseSubscriptions(callbackIntent);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -507,7 +506,7 @@
             return;
         }
         try {
-            mController.retainSubscriptionsForFactoryReset(callbackIntent);
+            getIEuiccController().retainSubscriptionsForFactoryReset(callbackIntent);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -520,4 +519,8 @@
             // Caller canceled the callback; do nothing.
         }
     }
+
+    private static IEuiccController getIEuiccController() {
+        return IEuiccController.Stub.asInterface(ServiceManager.getService("econtroller"));
+    }
 }
diff --git a/test-mock/Android.mk b/test-mock/Android.mk
index a761a07..7926a77 100644
--- a/test-mock/Android.mk
+++ b/test-mock/Android.mk
@@ -16,7 +16,12 @@
 
 LOCAL_PATH:= $(call my-dir)
 
-android_test_mock_source_files := $(call all-java-files-under, src/android/test/mock)
+# Includes the main framework source to ensure that doclava has access to the
+# visibility information for the base classes of the mock classes. Without it
+# otherwise hidden methods could be visible.
+android_test_mock_source_files := \
+    $(call all-java-files-under, src/android/test/mock) \
+    $(call all-java-files-under, ../core/java) \
 
 # For unbundled build we'll use the prebuilt jar from prebuilts/sdk.
 ifeq (,$(TARGET_BUILD_APPS)$(filter true,$(TARGET_BUILD_PDK)))
@@ -67,6 +72,8 @@
 LOCAL_ADDITIONAL_DEPENDENCIES := $(android_test_mock_gen_stamp)
 android_test_mock_gen_stamp :=
 
+LOCAL_SDK_VERSION := current
+
 include $(BUILD_STATIC_JAVA_LIBRARY)
 
 # Archive a copy of the classes.jar in SDK build.
@@ -104,4 +111,91 @@
 	@echo Copying removed.txt
 	$(hide) $(ACP) $(ANDROID_TEST_MOCK_OUTPUT_REMOVED_API_FILE) $(ANDROID_TEST_MOCK_REMOVED_API_FILE)
 
+# Generate the stub source files for android.test.mock.stubs-system
+# =================================================================
+include $(CLEAR_VARS)
+LOCAL_SRC_FILES := $(android_test_mock_source_files)
+
+LOCAL_JAVA_LIBRARIES := core-oj core-libart framework
+LOCAL_MODULE_CLASS := JAVA_LIBRARIES
+LOCAL_DROIDDOC_SOURCE_PATH := $(LOCAL_PATH)/src/android/test/mock
+
+ANDROID_TEST_MOCK_SYSTEM_OUTPUT_API_FILE := $(TARGET_OUT_COMMON_INTERMEDIATES)/JAVA_LIBRARIES/android.test.mock.stubs-system_intermediates/api.txt
+ANDROID_TEST_MOCK_SYSTEM_OUTPUT_REMOVED_API_FILE := $(TARGET_OUT_COMMON_INTERMEDIATES)/JAVA_LIBRARIES/android.test.mock.stubs-system_intermediates/removed.txt
+
+ANDROID_TEST_MOCK_SYSTEM_API_FILE := $(LOCAL_PATH)/api/android-test-mock-system-current.txt
+ANDROID_TEST_MOCK_SYSTEM_REMOVED_API_FILE := $(LOCAL_PATH)/api/android-test-mock-system-removed.txt
+
+LOCAL_DROIDDOC_OPTIONS:= \
+    -stubpackages android.test.mock \
+    -stubs $(TARGET_OUT_COMMON_INTERMEDIATES)/JAVA_LIBRARIES/android.test.mock.stubs-system_intermediates/src \
+    -nodocs \
+    -showAnnotation android.annotation.SystemApi \
+    -api $(ANDROID_TEST_MOCK_SYSTEM_OUTPUT_API_FILE) \
+    -removedApi $(ANDROID_TEST_MOCK_SYSTEM_OUTPUT_REMOVED_API_FILE) \
+
+LOCAL_UNINSTALLABLE_MODULE := true
+LOCAL_MODULE := android-test-mock-system-api-stubs-gen
+
+include $(BUILD_DROIDDOC)
+
+# Remember the target that will trigger the code generation.
+android_test_mock_system_gen_stamp := $(full_target)
+
+# Add some additional dependencies
+$(ANDROID_TEST_MOCK_SYSTEM_OUTPUT_API_FILE): $(full_target)
+$(ANDROID_TEST_MOCK_SYSTEM_OUTPUT_REMOVED_API_FILE): $(full_target)
+
+# Build the android.test.mock.stubs-system library
+# ================================================
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := android.test.mock.stubs-system
+
+LOCAL_SOURCE_FILES_ALL_GENERATED := true
+
+# Make sure to run droiddoc first to generate the stub source files.
+LOCAL_ADDITIONAL_DEPENDENCIES := $(android_test_mock_system_gen_stamp)
+android_test_mock_system_gen_stamp :=
+
+LOCAL_SDK_VERSION := system_current
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+# Archive a copy of the classes.jar in SDK build.
+$(call dist-for-goals,sdk win_sdk,$(full_classes_jar):android.test.mock.stubs_system.jar)
+
+# Check that the android.test.mock.stubs-system library has not changed
+# =====================================================================
+
+# Check that the API we're building hasn't changed from the not-yet-released
+# SDK version.
+$(eval $(call check-api, \
+    check-android-test-mock-system-api-current, \
+    $(ANDROID_TEST_MOCK_SYSTEM_API_FILE), \
+    $(ANDROID_TEST_MOCK_SYSTEM_OUTPUT_API_FILE), \
+    $(ANDROID_TEST_MOCK_SYSTEM_REMOVED_API_FILE), \
+    $(ANDROID_TEST_MOCK_SYSTEM_OUTPUT_REMOVED_API_FILE), \
+    -error 2 -error 3 -error 4 -error 5 -error 6 \
+    -error 7 -error 8 -error 9 -error 10 -error 11 -error 12 -error 13 -error 14 -error 15 \
+    -error 16 -error 17 -error 18 -error 19 -error 20 -error 21 -error 23 -error 24 \
+    -error 25 -error 26 -error 27, \
+    cat $(LOCAL_PATH)/api/apicheck_msg_android_test_mock-system.txt, \
+    check-android-test-mock-system-api, \
+    $(call doc-timestamp-for,android-test-mock-system-api-stubs-gen) \
+    ))
+
+.PHONY: check-android-test-mock-system-api
+checkapi: check-android-test-mock-system-api
+
+.PHONY: update-android-test-mock-system-api
+update-api: update-android-test-mock-system-api
+
+update-android-test-mock-system-api: $(ANDROID_TEST_MOCK_SYSTEM_OUTPUT_API_FILE) | $(ACP)
+	@echo Copying current.txt
+	$(hide) $(ACP) $(ANDROID_TEST_MOCK_SYSTEM_OUTPUT_API_FILE) $(ANDROID_TEST_MOCK_SYSTEM_API_FILE)
+	@echo Copying removed.txt
+	$(hide) $(ACP) $(ANDROID_TEST_MOCK_SYSTEM_OUTPUT_REMOVED_API_FILE) $(ANDROID_TEST_MOCK_SYSTEM_REMOVED_API_FILE)
+
 endif  # not TARGET_BUILD_APPS not TARGET_BUILD_PDK=true
+
diff --git a/test-mock/api/android-test-mock-current.txt b/test-mock/api/android-test-mock-current.txt
index 3aa350b..48a1f80 100644
--- a/test-mock/api/android-test-mock-current.txt
+++ b/test-mock/api/android-test-mock-current.txt
@@ -10,7 +10,6 @@
     ctor public MockContentProvider(android.content.Context, java.lang.String, java.lang.String, android.content.pm.PathPermission[]);
     method public android.content.ContentProviderResult[] applyBatch(java.util.ArrayList<android.content.ContentProviderOperation>);
     method public int delete(android.net.Uri, java.lang.String, java.lang.String[]);
-    method public final android.content.IContentProvider getIContentProvider();
     method public java.lang.String getType(android.net.Uri);
     method public android.net.Uri insert(android.net.Uri, android.content.ContentValues);
     method public boolean onCreate();
@@ -22,37 +21,26 @@
   public class MockContentResolver extends android.content.ContentResolver {
     ctor public MockContentResolver();
     ctor public MockContentResolver(android.content.Context);
-    method protected android.content.IContentProvider acquireProvider(android.content.Context, java.lang.String);
-    method protected android.content.IContentProvider acquireUnstableProvider(android.content.Context, java.lang.String);
     method public void addProvider(java.lang.String, android.content.ContentProvider);
-    method public boolean releaseProvider(android.content.IContentProvider);
-    method public boolean releaseUnstableProvider(android.content.IContentProvider);
-    method public void unstableProviderDied(android.content.IContentProvider);
   }
 
   public class MockContext extends android.content.Context {
     ctor public MockContext();
     method public boolean bindService(android.content.Intent, android.content.ServiceConnection, int);
-    method public boolean canLoadUnsafeResources();
     method public int checkCallingOrSelfPermission(java.lang.String);
     method public int checkCallingOrSelfUriPermission(android.net.Uri, int);
     method public int checkCallingPermission(java.lang.String);
     method public int checkCallingUriPermission(android.net.Uri, int);
     method public int checkPermission(java.lang.String, int, int);
-    method public int checkPermission(java.lang.String, int, int, android.os.IBinder);
     method public int checkSelfPermission(java.lang.String);
     method public int checkUriPermission(android.net.Uri, int, int, int);
-    method public int checkUriPermission(android.net.Uri, int, int, int, android.os.IBinder);
     method public int checkUriPermission(android.net.Uri, java.lang.String, java.lang.String, int, int, int);
     method public void clearWallpaper();
-    method public android.content.Context createApplicationContext(android.content.pm.ApplicationInfo, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method public android.content.Context createConfigurationContext(android.content.res.Configuration);
     method public android.content.Context createContextForSplit(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public android.content.Context createCredentialProtectedStorageContext();
     method public android.content.Context createDeviceProtectedStorageContext();
     method public android.content.Context createDisplayContext(android.view.Display);
     method public android.content.Context createPackageContext(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public android.content.Context createPackageContextAsUser(java.lang.String, int, android.os.UserHandle) throws android.content.pm.PackageManager.NameNotFoundException;
     method public java.lang.String[] databaseList();
     method public boolean deleteDatabase(java.lang.String);
     method public boolean deleteFile(java.lang.String);
@@ -68,7 +56,6 @@
     method public android.content.Context getApplicationContext();
     method public android.content.pm.ApplicationInfo getApplicationInfo();
     method public android.content.res.AssetManager getAssets();
-    method public java.lang.String getBasePackageName();
     method public java.io.File getCacheDir();
     method public java.lang.ClassLoader getClassLoader();
     method public java.io.File getCodeCacheDir();
@@ -76,8 +63,6 @@
     method public java.io.File getDataDir();
     method public java.io.File getDatabasePath(java.lang.String);
     method public java.io.File getDir(java.lang.String, int);
-    method public android.view.Display getDisplay();
-    method public android.view.DisplayAdjustments getDisplayAdjustments(int);
     method public java.io.File getExternalCacheDir();
     method public java.io.File[] getExternalCacheDirs();
     method public java.io.File getExternalFilesDir(java.lang.String);
@@ -89,25 +74,19 @@
     method public java.io.File getNoBackupFilesDir();
     method public java.io.File getObbDir();
     method public java.io.File[] getObbDirs();
-    method public java.lang.String getOpPackageName();
     method public java.lang.String getPackageCodePath();
     method public android.content.pm.PackageManager getPackageManager();
     method public java.lang.String getPackageName();
     method public java.lang.String getPackageResourcePath();
-    method public java.io.File getPreloadsFileCache();
     method public android.content.res.Resources getResources();
     method public android.content.SharedPreferences getSharedPreferences(java.lang.String, int);
-    method public android.content.SharedPreferences getSharedPreferences(java.io.File, int);
-    method public java.io.File getSharedPreferencesPath(java.lang.String);
     method public java.lang.Object getSystemService(java.lang.String);
     method public java.lang.String getSystemServiceName(java.lang.Class<?>);
     method public android.content.res.Resources.Theme getTheme();
-    method public int getUserId();
     method public android.graphics.drawable.Drawable getWallpaper();
     method public int getWallpaperDesiredMinimumHeight();
     method public int getWallpaperDesiredMinimumWidth();
     method public void grantUriPermission(java.lang.String, android.net.Uri, int);
-    method public boolean isCredentialProtectedStorage();
     method public boolean isDeviceProtectedStorage();
     method public boolean moveDatabaseFrom(android.content.Context, java.lang.String);
     method public boolean moveSharedPreferencesFrom(android.content.Context, java.lang.String);
@@ -120,31 +99,19 @@
     method public android.content.Intent registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter, int);
     method public android.content.Intent registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter, java.lang.String, android.os.Handler);
     method public android.content.Intent registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter, java.lang.String, android.os.Handler, int);
-    method public android.content.Intent registerReceiverAsUser(android.content.BroadcastReceiver, android.os.UserHandle, android.content.IntentFilter, java.lang.String, android.os.Handler);
-    method public void reloadSharedPreferences();
     method public void removeStickyBroadcast(android.content.Intent);
     method public void removeStickyBroadcastAsUser(android.content.Intent, android.os.UserHandle);
     method public void revokeUriPermission(android.net.Uri, int);
     method public void revokeUriPermission(java.lang.String, android.net.Uri, int);
     method public void sendBroadcast(android.content.Intent);
     method public void sendBroadcast(android.content.Intent, java.lang.String);
-    method public void sendBroadcast(android.content.Intent, java.lang.String, android.os.Bundle);
-    method public void sendBroadcast(android.content.Intent, java.lang.String, int);
     method public void sendBroadcastAsUser(android.content.Intent, android.os.UserHandle);
     method public void sendBroadcastAsUser(android.content.Intent, android.os.UserHandle, java.lang.String);
-    method public void sendBroadcastAsUser(android.content.Intent, android.os.UserHandle, java.lang.String, android.os.Bundle);
-    method public void sendBroadcastAsUser(android.content.Intent, android.os.UserHandle, java.lang.String, int);
-    method public void sendBroadcastMultiplePermissions(android.content.Intent, java.lang.String[]);
     method public void sendOrderedBroadcast(android.content.Intent, java.lang.String);
     method public void sendOrderedBroadcast(android.content.Intent, java.lang.String, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle);
-    method public void sendOrderedBroadcast(android.content.Intent, java.lang.String, android.os.Bundle, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle);
-    method public void sendOrderedBroadcast(android.content.Intent, java.lang.String, int, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle);
     method public void sendOrderedBroadcastAsUser(android.content.Intent, android.os.UserHandle, java.lang.String, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle);
-    method public void sendOrderedBroadcastAsUser(android.content.Intent, android.os.UserHandle, java.lang.String, int, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle);
-    method public void sendOrderedBroadcastAsUser(android.content.Intent, android.os.UserHandle, java.lang.String, int, android.os.Bundle, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle);
     method public void sendStickyBroadcast(android.content.Intent);
     method public void sendStickyBroadcastAsUser(android.content.Intent, android.os.UserHandle);
-    method public void sendStickyBroadcastAsUser(android.content.Intent, android.os.UserHandle, android.os.Bundle);
     method public void sendStickyOrderedBroadcast(android.content.Intent, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle);
     method public void sendStickyOrderedBroadcastAsUser(android.content.Intent, android.os.UserHandle, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle);
     method public void setTheme(int);
@@ -155,17 +122,13 @@
     method public void startActivity(android.content.Intent);
     method public void startActivity(android.content.Intent, android.os.Bundle);
     method public android.content.ComponentName startForegroundService(android.content.Intent);
-    method public android.content.ComponentName startForegroundServiceAsUser(android.content.Intent, android.os.UserHandle);
     method public boolean startInstrumentation(android.content.ComponentName, java.lang.String, android.os.Bundle);
     method public void startIntentSender(android.content.IntentSender, android.content.Intent, int, int, int) throws android.content.IntentSender.SendIntentException;
     method public void startIntentSender(android.content.IntentSender, android.content.Intent, int, int, int, android.os.Bundle) throws android.content.IntentSender.SendIntentException;
     method public android.content.ComponentName startService(android.content.Intent);
-    method public android.content.ComponentName startServiceAsUser(android.content.Intent, android.os.UserHandle);
     method public boolean stopService(android.content.Intent);
-    method public boolean stopServiceAsUser(android.content.Intent, android.os.UserHandle);
     method public void unbindService(android.content.ServiceConnection);
     method public void unregisterReceiver(android.content.BroadcastReceiver);
-    method public void updateDisplay(int);
   }
 
   public deprecated class MockCursor implements android.database.Cursor {
@@ -221,8 +184,6 @@
 
   public deprecated class MockPackageManager extends android.content.pm.PackageManager {
     ctor public MockPackageManager();
-    method public void addCrossProfileIntentFilter(android.content.IntentFilter, int, int, int);
-    method public void addOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener);
     method public void addPackageToPreferred(java.lang.String);
     method public boolean addPermission(android.content.pm.PermissionInfo);
     method public boolean addPermissionAsync(android.content.pm.PermissionInfo);
@@ -232,19 +193,10 @@
     method public int checkPermission(java.lang.String, java.lang.String);
     method public int checkSignatures(java.lang.String, java.lang.String);
     method public int checkSignatures(int, int);
-    method public void clearApplicationUserData(java.lang.String, android.content.pm.IPackageDataObserver);
-    method public void clearCrossProfileIntentFilters(int);
     method public void clearInstantAppCookie();
     method public void clearPackagePreferredActivities(java.lang.String);
     method public java.lang.String[] currentToCanonicalPackageNames(java.lang.String[]);
-    method public void deleteApplicationCacheFiles(java.lang.String, android.content.pm.IPackageDataObserver);
-    method public void deleteApplicationCacheFilesAsUser(java.lang.String, int, android.content.pm.IPackageDataObserver);
-    method public void deletePackage(java.lang.String, android.content.pm.IPackageDeleteObserver, int);
-    method public void deletePackageAsUser(java.lang.String, android.content.pm.IPackageDeleteObserver, int, int);
     method public void extendVerificationTimeout(int, int, long);
-    method public void flushPackageRestrictionsAsUser(int);
-    method public void freeStorage(java.lang.String, long, android.content.IntentSender);
-    method public void freeStorageAndNotify(java.lang.String, long, android.content.pm.IPackageDataObserver);
     method public android.graphics.drawable.Drawable getActivityBanner(android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException;
     method public android.graphics.drawable.Drawable getActivityBanner(android.content.Intent) throws android.content.pm.PackageManager.NameNotFoundException;
     method public android.graphics.drawable.Drawable getActivityIcon(android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -257,148 +209,75 @@
     method public android.graphics.drawable.Drawable getApplicationBanner(android.content.pm.ApplicationInfo);
     method public android.graphics.drawable.Drawable getApplicationBanner(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
     method public int getApplicationEnabledSetting(java.lang.String);
-    method public boolean getApplicationHiddenSettingAsUser(java.lang.String, android.os.UserHandle);
     method public android.graphics.drawable.Drawable getApplicationIcon(android.content.pm.ApplicationInfo);
     method public android.graphics.drawable.Drawable getApplicationIcon(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
     method public android.content.pm.ApplicationInfo getApplicationInfo(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public android.content.pm.ApplicationInfo getApplicationInfoAsUser(java.lang.String, int, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method public java.lang.CharSequence getApplicationLabel(android.content.pm.ApplicationInfo);
     method public android.graphics.drawable.Drawable getApplicationLogo(android.content.pm.ApplicationInfo);
     method public android.graphics.drawable.Drawable getApplicationLogo(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
     method public android.content.pm.ChangedPackages getChangedPackages(int);
     method public int getComponentEnabledSetting(android.content.ComponentName);
     method public android.graphics.drawable.Drawable getDefaultActivityIcon();
-    method public java.lang.String getDefaultBrowserPackageNameAsUser(int);
     method public android.graphics.drawable.Drawable getDrawable(java.lang.String, int, android.content.pm.ApplicationInfo);
-    method public android.content.ComponentName getHomeActivities(java.util.List<android.content.pm.ResolveInfo>);
-    method public int getInstallReason(java.lang.String, android.os.UserHandle);
     method public java.util.List<android.content.pm.ApplicationInfo> getInstalledApplications(int);
-    method public java.util.List<android.content.pm.ApplicationInfo> getInstalledApplicationsAsUser(int, int);
     method public java.util.List<android.content.pm.PackageInfo> getInstalledPackages(int);
-    method public java.util.List<android.content.pm.PackageInfo> getInstalledPackagesAsUser(int, int);
     method public java.lang.String getInstallerPackageName(java.lang.String);
-    method public java.lang.String getInstantAppAndroidId(java.lang.String, android.os.UserHandle);
     method public byte[] getInstantAppCookie();
     method public int getInstantAppCookieMaxBytes();
-    method public int getInstantAppCookieMaxSize();
-    method public android.graphics.drawable.Drawable getInstantAppIcon(java.lang.String);
-    method public android.content.ComponentName getInstantAppInstallerComponent();
-    method public android.content.ComponentName getInstantAppResolverSettingsComponent();
-    method public java.util.List<android.content.pm.InstantAppInfo> getInstantApps();
     method public android.content.pm.InstrumentationInfo getInstrumentationInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public java.util.List<android.content.pm.IntentFilterVerificationInfo> getIntentFilterVerifications(java.lang.String);
-    method public int getIntentVerificationStatusAsUser(java.lang.String, int);
-    method public android.content.pm.KeySet getKeySetByAlias(java.lang.String, java.lang.String);
     method public android.content.Intent getLaunchIntentForPackage(java.lang.String);
     method public android.content.Intent getLeanbackLaunchIntentForPackage(java.lang.String);
-    method public int getMoveStatus(int);
     method public java.lang.String getNameForUid(int);
-    method public java.lang.String[] getNamesForUids(int[]);
-    method public java.util.List<android.os.storage.VolumeInfo> getPackageCandidateVolumes(android.content.pm.ApplicationInfo);
-    method public android.os.storage.VolumeInfo getPackageCurrentVolume(android.content.pm.ApplicationInfo);
     method public int[] getPackageGids(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
     method public int[] getPackageGids(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method public android.content.pm.PackageInfo getPackageInfo(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method public android.content.pm.PackageInfo getPackageInfo(android.content.pm.VersionedPackage, int) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public android.content.pm.PackageInfo getPackageInfoAsUser(java.lang.String, int, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method public android.content.pm.PackageInstaller getPackageInstaller();
-    method public void getPackageSizeInfoAsUser(java.lang.String, int, android.content.pm.IPackageStatsObserver);
     method public int getPackageUid(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public int getPackageUidAsUser(java.lang.String, int, int) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public int getPackageUidAsUser(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method public java.lang.String[] getPackagesForUid(int);
     method public java.util.List<android.content.pm.PackageInfo> getPackagesHoldingPermissions(java.lang.String[], int);
-    method public java.lang.String getPermissionControllerPackageName();
-    method public int getPermissionFlags(java.lang.String, java.lang.String, android.os.UserHandle);
     method public android.content.pm.PermissionGroupInfo getPermissionGroupInfo(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method public android.content.pm.PermissionInfo getPermissionInfo(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method public int getPreferredActivities(java.util.List<android.content.IntentFilter>, java.util.List<android.content.ComponentName>, java.lang.String);
     method public java.util.List<android.content.pm.PackageInfo> getPreferredPackages(int);
-    method public java.util.List<android.os.storage.VolumeInfo> getPrimaryStorageCandidateVolumes();
-    method public android.os.storage.VolumeInfo getPrimaryStorageCurrentVolume();
     method public android.content.pm.ProviderInfo getProviderInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method public android.content.pm.ActivityInfo getReceiverInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method public android.content.res.Resources getResourcesForActivity(android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException;
     method public android.content.res.Resources getResourcesForApplication(android.content.pm.ApplicationInfo);
     method public android.content.res.Resources getResourcesForApplication(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public android.content.res.Resources getResourcesForApplicationAsUser(java.lang.String, int);
     method public android.content.pm.ServiceInfo getServiceInfo(android.content.ComponentName, int) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public java.lang.String getServicesSystemSharedLibraryPackageName();
     method public java.util.List<android.content.pm.SharedLibraryInfo> getSharedLibraries(int);
-    method public java.util.List<android.content.pm.SharedLibraryInfo> getSharedLibrariesAsUser(int, int);
-    method public java.lang.String getSharedSystemSharedLibraryPackageName();
-    method public android.content.pm.KeySet getSigningKeySet(java.lang.String);
     method public android.content.pm.FeatureInfo[] getSystemAvailableFeatures();
     method public java.lang.String[] getSystemSharedLibraryNames();
     method public java.lang.CharSequence getText(java.lang.String, int, android.content.pm.ApplicationInfo);
-    method public int getUidForSharedUser(java.lang.String);
-    method public android.graphics.drawable.Drawable getUserBadgeForDensity(android.os.UserHandle, int);
-    method public android.graphics.drawable.Drawable getUserBadgeForDensityNoBackground(android.os.UserHandle, int);
     method public android.graphics.drawable.Drawable getUserBadgedDrawableForDensity(android.graphics.drawable.Drawable, android.os.UserHandle, android.graphics.Rect, int);
     method public android.graphics.drawable.Drawable getUserBadgedIcon(android.graphics.drawable.Drawable, android.os.UserHandle);
     method public java.lang.CharSequence getUserBadgedLabel(java.lang.CharSequence, android.os.UserHandle);
-    method public android.content.pm.VerifierDeviceIdentity getVerifierDeviceIdentity();
     method public android.content.res.XmlResourceParser getXml(java.lang.String, int, android.content.pm.ApplicationInfo);
-    method public void grantRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle);
     method public boolean hasSystemFeature(java.lang.String);
     method public boolean hasSystemFeature(java.lang.String, int);
-    method public int installExistingPackage(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public int installExistingPackage(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public int installExistingPackageAsUser(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public void installPackage(android.net.Uri, android.app.PackageInstallObserver, int, java.lang.String);
     method public boolean isInstantApp();
     method public boolean isInstantApp(java.lang.String);
-    method public boolean isPackageAvailable(java.lang.String);
-    method public boolean isPackageSuspendedForUser(java.lang.String, int);
-    method public boolean isPermissionReviewModeEnabled();
     method public boolean isPermissionRevokedByPolicy(java.lang.String, java.lang.String);
     method public boolean isSafeMode();
-    method public boolean isSignedBy(java.lang.String, android.content.pm.KeySet);
-    method public boolean isSignedByExactly(java.lang.String, android.content.pm.KeySet);
-    method public boolean isUpgrade();
-    method public android.graphics.drawable.Drawable loadItemIcon(android.content.pm.PackageItemInfo, android.content.pm.ApplicationInfo);
-    method public android.graphics.drawable.Drawable loadUnbadgedItemIcon(android.content.pm.PackageItemInfo, android.content.pm.ApplicationInfo);
-    method public int movePackage(java.lang.String, android.os.storage.VolumeInfo);
-    method public int movePrimaryStorage(android.os.storage.VolumeInfo);
     method public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceivers(android.content.Intent, int);
-    method public java.util.List<android.content.pm.ResolveInfo> queryBroadcastReceiversAsUser(android.content.Intent, int, int);
     method public java.util.List<android.content.pm.ProviderInfo> queryContentProviders(java.lang.String, int, int);
     method public java.util.List<android.content.pm.InstrumentationInfo> queryInstrumentation(java.lang.String, int);
     method public java.util.List<android.content.pm.ResolveInfo> queryIntentActivities(android.content.Intent, int);
-    method public java.util.List<android.content.pm.ResolveInfo> queryIntentActivitiesAsUser(android.content.Intent, int, int);
     method public java.util.List<android.content.pm.ResolveInfo> queryIntentActivityOptions(android.content.ComponentName, android.content.Intent[], android.content.Intent, int);
     method public java.util.List<android.content.pm.ResolveInfo> queryIntentContentProviders(android.content.Intent, int);
-    method public java.util.List<android.content.pm.ResolveInfo> queryIntentContentProvidersAsUser(android.content.Intent, int, int);
     method public java.util.List<android.content.pm.ResolveInfo> queryIntentServices(android.content.Intent, int);
-    method public java.util.List<android.content.pm.ResolveInfo> queryIntentServicesAsUser(android.content.Intent, int, int);
     method public java.util.List<android.content.pm.PermissionInfo> queryPermissionsByGroup(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
-    method public void registerDexModule(java.lang.String, android.content.pm.PackageManager.DexModuleRegisterCallback);
-    method public void registerMoveCallback(android.content.pm.PackageManager.MoveCallback, android.os.Handler);
-    method public void removeOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener);
     method public void removePackageFromPreferred(java.lang.String);
     method public void removePermission(java.lang.String);
-    method public void replacePreferredActivity(android.content.IntentFilter, int, android.content.ComponentName[], android.content.ComponentName);
     method public android.content.pm.ResolveInfo resolveActivity(android.content.Intent, int);
-    method public android.content.pm.ResolveInfo resolveActivityAsUser(android.content.Intent, int, int);
     method public android.content.pm.ProviderInfo resolveContentProvider(java.lang.String, int);
-    method public android.content.pm.ProviderInfo resolveContentProviderAsUser(java.lang.String, int, int);
     method public android.content.pm.ResolveInfo resolveService(android.content.Intent, int);
-    method public void revokeRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle);
     method public void setApplicationCategoryHint(java.lang.String, int);
     method public void setApplicationEnabledSetting(java.lang.String, int, int);
-    method public boolean setApplicationHiddenSettingAsUser(java.lang.String, boolean, android.os.UserHandle);
     method public void setComponentEnabledSetting(android.content.ComponentName, int, int);
-    method public boolean setDefaultBrowserPackageNameAsUser(java.lang.String, int);
     method public void setInstallerPackageName(java.lang.String, java.lang.String);
-    method public boolean setInstantAppCookie(byte[]);
-    method public java.lang.String[] setPackagesSuspendedAsUser(java.lang.String[], boolean, int);
-    method public void setUpdateAvailable(java.lang.String, boolean);
-    method public boolean shouldShowRequestPermissionRationale(java.lang.String);
-    method public void unregisterMoveCallback(android.content.pm.PackageManager.MoveCallback);
     method public void updateInstantAppCookie(byte[]);
-    method public boolean updateIntentVerificationStatusAsUser(java.lang.String, int, int);
-    method public void updatePermissionFlags(java.lang.String, java.lang.String, int, int, android.os.UserHandle);
-    method public void verifyIntentFilter(int, int, java.util.List<java.lang.String>);
     method public void verifyPendingInstall(int, int);
   }
 
diff --git a/test-mock/api/android-test-mock-removed.txt b/test-mock/api/android-test-mock-removed.txt
index 5b358cf..bd109a8 100644
--- a/test-mock/api/android-test-mock-removed.txt
+++ b/test-mock/api/android-test-mock-removed.txt
@@ -8,6 +8,7 @@
   public deprecated class MockPackageManager extends android.content.pm.PackageManager {
     method public deprecated java.lang.String getDefaultBrowserPackageName(int);
     method public deprecated boolean setDefaultBrowserPackageName(java.lang.String, int);
+    method public boolean setInstantAppCookie(byte[]);
   }
 
 }
diff --git a/test-mock/api/android-test-mock-system-current.txt b/test-mock/api/android-test-mock-system-current.txt
new file mode 100644
index 0000000..20401a5
--- /dev/null
+++ b/test-mock/api/android-test-mock-system-current.txt
@@ -0,0 +1,38 @@
+package android.test.mock {
+
+  public class MockContext extends android.content.Context {
+    method public android.content.Context createCredentialProtectedStorageContext();
+    method public java.io.File getPreloadsFileCache();
+    method public boolean isCredentialProtectedStorage();
+    method public void sendBroadcast(android.content.Intent, java.lang.String, android.os.Bundle);
+    method public void sendBroadcastAsUser(android.content.Intent, android.os.UserHandle, java.lang.String, android.os.Bundle);
+    method public void sendOrderedBroadcast(android.content.Intent, java.lang.String, android.os.Bundle, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle);
+  }
+
+  public deprecated class MockPackageManager extends android.content.pm.PackageManager {
+    method public void addOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener);
+    method public java.util.List<android.content.IntentFilter> getAllIntentFilters(java.lang.String);
+    method public java.lang.String getDefaultBrowserPackageNameAsUser(int);
+    method public java.util.List<android.content.pm.PackageInfo> getInstalledPackagesAsUser(int, int);
+    method public android.graphics.drawable.Drawable getInstantAppIcon(java.lang.String);
+    method public android.content.ComponentName getInstantAppInstallerComponent();
+    method public android.content.ComponentName getInstantAppResolverSettingsComponent();
+    method public java.util.List<android.content.pm.InstantAppInfo> getInstantApps();
+    method public java.util.List<android.content.pm.IntentFilterVerificationInfo> getIntentFilterVerifications(java.lang.String);
+    method public int getIntentVerificationStatusAsUser(java.lang.String, int);
+    method public int getPermissionFlags(java.lang.String, java.lang.String, android.os.UserHandle);
+    method public void grantRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle);
+    method public int installExistingPackage(java.lang.String) throws android.content.pm.PackageManager.NameNotFoundException;
+    method public int installExistingPackage(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
+    method public void registerDexModule(java.lang.String, android.content.pm.PackageManager.DexModuleRegisterCallback);
+    method public void removeOnPermissionsChangeListener(android.content.pm.PackageManager.OnPermissionsChangedListener);
+    method public void revokeRuntimePermission(java.lang.String, java.lang.String, android.os.UserHandle);
+    method public boolean setDefaultBrowserPackageNameAsUser(java.lang.String, int);
+    method public void setUpdateAvailable(java.lang.String, boolean);
+    method public boolean updateIntentVerificationStatusAsUser(java.lang.String, int, int);
+    method public void updatePermissionFlags(java.lang.String, java.lang.String, int, int, android.os.UserHandle);
+    method public void verifyIntentFilter(int, int, java.util.List<java.lang.String>);
+  }
+
+}
+
diff --git a/test-mock/api/android-test-mock-system-removed.txt b/test-mock/api/android-test-mock-system-removed.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test-mock/api/android-test-mock-system-removed.txt
diff --git a/test-mock/api/apicheck_msg_android_test_mock-system.txt b/test-mock/api/apicheck_msg_android_test_mock-system.txt
new file mode 100644
index 0000000..3a97117
--- /dev/null
+++ b/test-mock/api/apicheck_msg_android_test_mock-system.txt
@@ -0,0 +1,17 @@
+
+******************************
+You have tried to change the API from what has been previously approved.
+
+To make these errors go away, you have two choices:
+   1) You can add "@hide" javadoc comments to the methods, etc. listed in the
+      errors above.
+
+   2) You can update android-test-mock-current.txt by executing the following command:
+         make update-android-test-mock-system-api
+
+      To submit the revised android-test-mock-system-current.txt to the main Android repository,
+      you will need approval.
+******************************
+
+
+
diff --git a/tests/AppLaunch/Android.mk b/tests/AppLaunch/Android.mk
index 09739e5..917293f 100644
--- a/tests/AppLaunch/Android.mk
+++ b/tests/AppLaunch/Android.mk
@@ -13,6 +13,8 @@
 
 LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
 
+LOCAL_COMPATIBILITY_SUITE := device-tests
+
 include $(BUILD_PACKAGE)
 
 # Use the following include to make our test apk.
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/Debug.cpp b/tools/aapt2/Debug.cpp
index 0f6fb50..5831875 100644
--- a/tools/aapt2/Debug.cpp
+++ b/tools/aapt2/Debug.cpp
@@ -294,36 +294,42 @@
         printer->Print("/");
         printer->Print(entry->name);
 
-        switch (entry->symbol_status.state) {
-          case SymbolState::kPublic:
+        switch (entry->visibility.level) {
+          case Visibility::Level::kPublic:
             printer->Print(" PUBLIC");
             break;
-          case SymbolState::kPrivate:
+          case Visibility::Level::kPrivate:
             printer->Print(" _PRIVATE_");
             break;
-          case SymbolState::kUndefined:
+          case Visibility::Level::kUndefined:
             // Print nothing.
             break;
         }
 
+        if (entry->overlayable) {
+          printer->Print(" OVERLAYABLE");
+        }
+
         printer->Println();
 
-        printer->Indent();
-        for (const auto& value : entry->values) {
-          printer->Print("(");
-          printer->Print(value->config.to_string());
-          printer->Print(") ");
-          value->value->Accept(&headline_printer);
-          if (options.show_sources && !value->value->GetSource().path.empty()) {
-            printer->Print(" src=");
-            printer->Print(value->value->GetSource().to_string());
-          }
-          printer->Println();
+        if (options.show_values) {
           printer->Indent();
-          value->value->Accept(&body_printer);
+          for (const auto& value : entry->values) {
+            printer->Print("(");
+            printer->Print(value->config.to_string());
+            printer->Print(") ");
+            value->value->Accept(&headline_printer);
+            if (options.show_sources && !value->value->GetSource().path.empty()) {
+              printer->Print(" src=");
+              printer->Print(value->value->GetSource().to_string());
+            }
+            printer->Println();
+            printer->Indent();
+            value->value->Accept(&body_printer);
+            printer->Undent();
+          }
           printer->Undent();
         }
-        printer->Undent();
       }
       printer->Undent();
     }
diff --git a/tools/aapt2/Debug.h b/tools/aapt2/Debug.h
index 3c1ee4c..6209a04 100644
--- a/tools/aapt2/Debug.h
+++ b/tools/aapt2/Debug.h
@@ -29,6 +29,7 @@
 
 struct DebugPrintTableOptions {
   bool show_sources = false;
+  bool show_values = true;
 };
 
 struct Debug {
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index 4cc60a8..24b28dd 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -30,7 +30,7 @@
 #include "util/Util.h"
 #include "xml/XmlPullParser.h"
 
-using android::StringPiece;
+using ::android::StringPiece;
 
 namespace aapt {
 
@@ -90,9 +90,12 @@
   ConfigDescription config;
   std::string product;
   Source source;
+
   ResourceId id;
-  Maybe<SymbolState> symbol_state;
+  Visibility::Level visibility_level = Visibility::Level::kUndefined;
   bool allow_new = false;
+  bool overlayable = false;
+
   std::string comment;
   std::unique_ptr<Value> value;
   std::list<ParsedResource> child_resources;
@@ -106,24 +109,41 @@
     res->comment = trimmed_comment.to_string();
   }
 
-  if (res->symbol_state) {
-    Symbol symbol;
-    symbol.state = res->symbol_state.value();
-    symbol.source = res->source;
-    symbol.comment = res->comment;
-    symbol.allow_new = res->allow_new;
-    if (!table->SetSymbolState(res->name, res->id, symbol, diag)) {
+  if (res->visibility_level != Visibility::Level::kUndefined) {
+    Visibility visibility;
+    visibility.level = res->visibility_level;
+    visibility.source = res->source;
+    visibility.comment = res->comment;
+    if (!table->SetVisibilityWithId(res->name, visibility, res->id, diag)) {
       return false;
     }
   }
 
-  if (res->value) {
+  if (res->allow_new) {
+    AllowNew allow_new;
+    allow_new.source = res->source;
+    allow_new.comment = res->comment;
+    if (!table->SetAllowNew(res->name, allow_new, diag)) {
+      return false;
+    }
+  }
+
+  if (res->overlayable) {
+    Overlayable overlayable;
+    overlayable.source = res->source;
+    overlayable.comment = res->comment;
+    if (!table->SetOverlayable(res->name, overlayable, diag)) {
+      return false;
+    }
+  }
+
+  if (res->value != nullptr) {
     // Attach the comment, source and config to the value.
     res->value->SetComment(std::move(res->comment));
     res->value->SetSource(std::move(res->source));
 
-    if (!table->AddResource(res->name, res->id, res->config, res->product, std::move(res->value),
-                            diag)) {
+    if (!table->AddResourceWithId(res->name, res->id, res->config, res->product,
+                                  std::move(res->value), diag)) {
       return false;
     }
   }
@@ -601,8 +621,7 @@
 
   // Process the raw value.
   std::unique_ptr<Item> processed_item =
-      ResourceUtils::TryParseItemForAttribute(raw_value, type_mask,
-                                              on_create_reference);
+      ResourceUtils::TryParseItemForAttribute(raw_value, type_mask, on_create_reference);
   if (processed_item) {
     // Fix up the reference.
     if (Reference* ref = ValueCast<Reference>(processed_item.get())) {
@@ -689,8 +708,7 @@
   return true;
 }
 
-bool ResourceParser::ParsePublic(xml::XmlPullParser* parser,
-                                 ParsedResource* out_resource) {
+bool ResourceParser::ParsePublic(xml::XmlPullParser* parser, ParsedResource* out_resource) {
   if (out_resource->config != ConfigDescription::DefaultConfig()) {
     diag_->Warn(DiagMessage(out_resource->source)
                 << "ignoring configuration '" << out_resource->config << "' for <public> tag");
@@ -728,7 +746,7 @@
     out_resource->value = util::make_unique<Id>();
   }
 
-  out_resource->symbol_state = SymbolState::kPublic;
+  out_resource->visibility_level = Visibility::Level::kPublic;
   return true;
 }
 
@@ -818,7 +836,7 @@
       child_resource.id = next_id;
       child_resource.comment = std::move(comment);
       child_resource.source = item_source;
-      child_resource.symbol_state = SymbolState::kPublic;
+      child_resource.visibility_level = Visibility::Level::kPublic;
       out_resource->child_resources.push_back(std::move(child_resource));
 
       next_id.id += 1;
@@ -864,7 +882,7 @@
     return false;
   }
 
-  out_resource->symbol_state = SymbolState::kPrivate;
+  out_resource->visibility_level = Visibility::Level::kPrivate;
   return true;
 }
 
@@ -920,8 +938,12 @@
         continue;
       }
 
-      // TODO(b/64980941): Mark the symbol as overlayable and allow marking which entity can overlay
-      // the resource (system/app).
+      ParsedResource child_resource;
+      child_resource.name.type = *type;
+      child_resource.name.entry = maybe_name.value().to_string();
+      child_resource.source = item_source;
+      child_resource.overlayable = true;
+      out_resource->child_resources.push_back(std::move(child_resource));
 
       xml::XmlPullParser::SkipCurrentElement(parser);
     } else if (!ShouldIgnoreElement(element_namespace, element_name)) {
@@ -932,10 +954,9 @@
   return !error;
 }
 
-bool ResourceParser::ParseAddResource(xml::XmlPullParser* parser,
-                                      ParsedResource* out_resource) {
+bool ResourceParser::ParseAddResource(xml::XmlPullParser* parser, ParsedResource* out_resource) {
   if (ParseSymbolImpl(parser, out_resource)) {
-    out_resource->symbol_state = SymbolState::kUndefined;
+    out_resource->visibility_level = Visibility::Level::kUndefined;
     out_resource->allow_new = true;
     return true;
   }
@@ -1395,9 +1416,8 @@
                                            ParsedResource* out_resource) {
   out_resource->name.type = ResourceType::kStyleable;
 
-  // Declare-styleable is kPrivate by default, because it technically only
-  // exists in R.java.
-  out_resource->symbol_state = SymbolState::kPublic;
+  // Declare-styleable is kPrivate by default, because it technically only exists in R.java.
+  out_resource->visibility_level = Visibility::Level::kPublic;
 
   // Declare-styleable only ends up in default config;
   if (out_resource->config != ConfigDescription::DefaultConfig()) {
diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp
index 9a5cd3e..618c8ed 100644
--- a/tools/aapt2/ResourceParser_test.cpp
+++ b/tools/aapt2/ResourceParser_test.cpp
@@ -29,8 +29,8 @@
 using ::aapt::io::StringInputStream;
 using ::aapt::test::StrValueEq;
 using ::aapt::test::ValueEq;
-using ::android::ResTable_map;
 using ::android::Res_value;
+using ::android::ResTable_map;
 using ::android::StringPiece;
 using ::testing::Eq;
 using ::testing::IsEmpty;
@@ -38,6 +38,7 @@
 using ::testing::NotNull;
 using ::testing::Pointee;
 using ::testing::SizeIs;
+using ::testing::StrEq;
 
 namespace aapt {
 
@@ -482,7 +483,7 @@
   Maybe<ResourceTable::SearchResult> result =
       table_.FindResource(test::ParseNameOrDie("styleable/foo"));
   ASSERT_TRUE(result);
-  EXPECT_THAT(result.value().entry->symbol_status.state, Eq(SymbolState::kPublic));
+  EXPECT_THAT(result.value().entry->visibility.level, Eq(Visibility::Level::kPublic));
 
   Attribute* attr = test::GetValue<Attribute>(&table_, "attr/bar");
   ASSERT_THAT(attr, NotNull());
@@ -718,6 +719,26 @@
   EXPECT_THAT(actual_id, Eq(ResourceId(0x01010041)));
 }
 
+TEST_F(ResourceParserTest, StrongestSymbolVisibilityWins) {
+  std::string input = R"(
+      <!-- private -->
+      <java-symbol type="string" name="foo" />
+      <!-- public -->
+      <public type="string" name="foo" id="0x01020000" />
+      <!-- private2 -->
+      <java-symbol type="string" name="foo" />)";
+  ASSERT_TRUE(TestParse(input));
+
+  Maybe<ResourceTable::SearchResult> result =
+      table_.FindResource(test::ParseNameOrDie("string/foo"));
+  ASSERT_TRUE(result);
+
+  ResourceEntry* entry = result.value().entry;
+  ASSERT_THAT(entry, NotNull());
+  EXPECT_THAT(entry->visibility.level, Eq(Visibility::Level::kPublic));
+  EXPECT_THAT(entry->visibility.comment, StrEq("public"));
+}
+
 TEST_F(ResourceParserTest, ExternalTypesShouldOnlyBeReferences) {
   ASSERT_TRUE(TestParse(R"(<item type="layout" name="foo">@layout/bar</item>)"));
   ASSERT_FALSE(TestParse(R"(<item type="layout" name="bar">"this is a string"</item>)"));
@@ -731,8 +752,8 @@
   ASSERT_TRUE(result);
   const ResourceEntry* entry = result.value().entry;
   ASSERT_THAT(entry, NotNull());
-  EXPECT_THAT(entry->symbol_status.state, Eq(SymbolState::kUndefined));
-  EXPECT_TRUE(entry->symbol_status.allow_new);
+  EXPECT_THAT(entry->visibility.level, Eq(Visibility::Level::kUndefined));
+  EXPECT_TRUE(entry->allow_new);
 }
 
 TEST_F(ResourceParserTest, ParseItemElementWithFormat) {
@@ -833,6 +854,22 @@
         <item type="string" name="bar" />
       </overlayable>)";
   ASSERT_TRUE(TestParse(input));
+
+  Maybe<ResourceTable::SearchResult> search_result =
+      table_.FindResource(test::ParseNameOrDie("string/bar"));
+  ASSERT_TRUE(search_result);
+  ASSERT_THAT(search_result.value().entry, NotNull());
+  EXPECT_THAT(search_result.value().entry->visibility.level, Eq(Visibility::Level::kUndefined));
+  EXPECT_TRUE(search_result.value().entry->overlayable);
+}
+
+TEST_F(ResourceParserTest, DuplicateOverlayableIsError) {
+  std::string input = R"(
+      <overlayable>
+        <item type="string" name="foo" />
+        <item type="string" name="foo" />
+      </overlayable>)";
+  EXPECT_FALSE(TestParse(input));
 }
 
 }  // namespace aapt
diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp
index 0304e21..3172892 100644
--- a/tools/aapt2/ResourceTable.cpp
+++ b/tools/aapt2/ResourceTable.cpp
@@ -22,6 +22,7 @@
 #include <tuple>
 
 #include "android-base/logging.h"
+#include "android-base/stringprintf.h"
 #include "androidfw/ResourceTypes.h"
 
 #include "ConfigDescription.h"
@@ -33,6 +34,7 @@
 
 using ::aapt::text::IsValidResourceEntryName;
 using ::android::StringPiece;
+using ::android::base::StringPrintf;
 
 namespace aapt {
 
@@ -45,7 +47,7 @@
   return lhs->name.compare(0, lhs->name.size(), rhs.data(), rhs.size()) < 0;
 }
 
-ResourceTablePackage* ResourceTable::FindPackage(const StringPiece& name) {
+ResourceTablePackage* ResourceTable::FindPackage(const StringPiece& name) const {
   const auto last = packages.end();
   auto iter = std::lower_bound(packages.begin(), last, name,
                                less_than_struct_with_name<ResourceTablePackage>);
@@ -55,7 +57,7 @@
   return nullptr;
 }
 
-ResourceTablePackage* ResourceTable::FindPackageById(uint8_t id) {
+ResourceTablePackage* ResourceTable::FindPackageById(uint8_t id) const {
   for (auto& package : packages) {
     if (package->id && package->id.value() == id) {
       return package.get();
@@ -206,30 +208,23 @@
   return results;
 }
 
-/**
- * The default handler for collisions.
- *
- * Typically, a weak value will be overridden by a strong value. An existing
- * weak
- * value will not be overridden by an incoming weak value.
- *
- * There are some exceptions:
- *
- * Attributes: There are two types of Attribute values: USE and DECL.
- *
- * USE is anywhere an Attribute is declared without a format, and in a place
- * that would
- * be legal to declare if the Attribute already existed. This is typically in a
- * <declare-styleable> tag. Attributes defined in a <declare-styleable> are also
- * weak.
- *
- * DECL is an absolute declaration of an Attribute and specifies an explicit
- * format.
- *
- * A DECL will override a USE without error. Two DECLs must match in their
- * format for there to be
- * no error.
- */
+// The default handler for collisions.
+//
+// Typically, a weak value will be overridden by a strong value. An existing weak
+// value will not be overridden by an incoming weak value.
+//
+// There are some exceptions:
+//
+// Attributes: There are two types of Attribute values: USE and DECL.
+//
+// USE is anywhere an Attribute is declared without a format, and in a place that would
+// be legal to declare if the Attribute already existed. This is typically in a
+// <declare-styleable> tag. Attributes defined in a <declare-styleable> are also weak.
+//
+// DECL is an absolute declaration of an Attribute and specifies an explicit format.
+//
+// A DECL will override a USE without error. Two DECLs must match in their format for there to be
+// no error.
 ResourceTable::CollisionResult ResourceTable::ResolveValueCollision(Value* existing,
                                                                     Value* incoming) {
   Attribute* existing_attr = ValueCast<Attribute>(existing);
@@ -287,14 +282,14 @@
   return CollisionResult::kConflict;
 }
 
-static StringPiece ValidateName(const StringPiece& name) {
+static StringPiece ResourceNameValidator(const StringPiece& name) {
   if (!IsValidResourceEntryName(name)) {
     return name;
   }
   return {};
 }
 
-static StringPiece SkipValidateName(const StringPiece& /*name*/) {
+static StringPiece SkipNameValidator(const StringPiece& /*name*/) {
   return {};
 }
 
@@ -303,17 +298,14 @@
                                 const StringPiece& product,
                                 std::unique_ptr<Value> value,
                                 IDiagnostics* diag) {
-  return AddResourceImpl(name, {}, config, product, std::move(value), ValidateName,
+  return AddResourceImpl(name, {}, config, product, std::move(value), ResourceNameValidator,
                          ResolveValueCollision, diag);
 }
 
-bool ResourceTable::AddResource(const ResourceNameRef& name,
-                                const ResourceId& res_id,
-                                const ConfigDescription& config,
-                                const StringPiece& product,
-                                std::unique_ptr<Value> value,
-                                IDiagnostics* diag) {
-  return AddResourceImpl(name, res_id, config, product, std::move(value), ValidateName,
+bool ResourceTable::AddResourceWithId(const ResourceNameRef& name, const ResourceId& res_id,
+                                      const ConfigDescription& config, const StringPiece& product,
+                                      std::unique_ptr<Value> value, IDiagnostics* diag) {
+  return AddResourceImpl(name, res_id, config, product, std::move(value), ResourceNameValidator,
                          ResolveValueCollision, diag);
 }
 
@@ -322,14 +314,14 @@
                                      const Source& source,
                                      const StringPiece& path,
                                      IDiagnostics* diag) {
-  return AddFileReferenceImpl(name, config, source, path, nullptr, ValidateName, diag);
+  return AddFileReferenceImpl(name, config, source, path, nullptr, ResourceNameValidator, diag);
 }
 
-bool ResourceTable::AddFileReferenceAllowMangled(
-    const ResourceNameRef& name, const ConfigDescription& config,
-    const Source& source, const StringPiece& path, io::IFile* file,
-    IDiagnostics* diag) {
-  return AddFileReferenceImpl(name, config, source, path, file, SkipValidateName, diag);
+bool ResourceTable::AddFileReferenceMangled(const ResourceNameRef& name,
+                                            const ConfigDescription& config, const Source& source,
+                                            const StringPiece& path, io::IFile* file,
+                                            IDiagnostics* diag) {
+  return AddFileReferenceImpl(name, config, source, path, file, SkipNameValidator, diag);
 }
 
 bool ResourceTable::AddFileReferenceImpl(const ResourceNameRef& name,
@@ -344,88 +336,85 @@
                          name_validator, ResolveValueCollision, diag);
 }
 
-bool ResourceTable::AddResourceAllowMangled(const ResourceNameRef& name,
-                                            const ConfigDescription& config,
-                                            const StringPiece& product,
-                                            std::unique_ptr<Value> value,
-                                            IDiagnostics* diag) {
-  return AddResourceImpl(name, ResourceId{}, config, product, std::move(value), SkipValidateName,
+bool ResourceTable::AddResourceMangled(const ResourceNameRef& name, const ConfigDescription& config,
+                                       const StringPiece& product, std::unique_ptr<Value> value,
+                                       IDiagnostics* diag) {
+  return AddResourceImpl(name, ResourceId{}, config, product, std::move(value), SkipNameValidator,
                          ResolveValueCollision, diag);
 }
 
-bool ResourceTable::AddResourceAllowMangled(const ResourceNameRef& name,
-                                            const ResourceId& id,
-                                            const ConfigDescription& config,
-                                            const StringPiece& product,
-                                            std::unique_ptr<Value> value,
-                                            IDiagnostics* diag) {
-  return AddResourceImpl(name, id, config, product, std::move(value), SkipValidateName,
+bool ResourceTable::AddResourceWithIdMangled(const ResourceNameRef& name, const ResourceId& id,
+                                             const ConfigDescription& config,
+                                             const StringPiece& product,
+                                             std::unique_ptr<Value> value, IDiagnostics* diag) {
+  return AddResourceImpl(name, id, config, product, std::move(value), SkipNameValidator,
                          ResolveValueCollision, diag);
 }
 
+bool ResourceTable::ValidateName(NameValidator name_validator, const ResourceNameRef& name,
+                                 const Source& source, IDiagnostics* diag) {
+  const StringPiece bad_char = name_validator(name.entry);
+  if (!bad_char.empty()) {
+    diag->Error(DiagMessage(source) << "resource '" << name << "' has invalid entry name '"
+                                    << name.entry << "'. Invalid character '" << bad_char << "'");
+    return false;
+  }
+  return true;
+}
+
 bool ResourceTable::AddResourceImpl(const ResourceNameRef& name, const ResourceId& res_id,
                                     const ConfigDescription& config, const StringPiece& product,
                                     std::unique_ptr<Value> value, NameValidator name_validator,
-                                    const CollisionResolverFunc& conflictResolver,
+                                    const CollisionResolverFunc& conflict_resolver,
                                     IDiagnostics* diag) {
   CHECK(value != nullptr);
   CHECK(diag != nullptr);
 
-  const StringPiece bad_char = name_validator(name.entry);
-  if (!bad_char.empty()) {
-    diag->Error(DiagMessage(value->GetSource()) << "resource '" << name
-                                                << "' has invalid entry name '" << name.entry
-                                                << "'. Invalid character '" << bad_char << "'");
-
+  const Source& source = value->GetSource();
+  if (!ValidateName(name_validator, name, source, diag)) {
     return false;
   }
 
   ResourceTablePackage* package = FindOrCreatePackage(name.package);
   if (res_id.is_valid_dynamic() && package->id && package->id.value() != res_id.package_id()) {
-    diag->Error(DiagMessage(value->GetSource())
-                << "trying to add resource '" << name << "' with ID " << res_id
-                << " but package '" << package->name << "' already has ID "
-                << std::hex << (int)package->id.value() << std::dec);
+    diag->Error(DiagMessage(source) << "trying to add resource '" << name << "' with ID " << res_id
+                                    << " but package '" << package->name << "' already has ID "
+                                    << StringPrintf("%02x", package->id.value()));
     return false;
   }
 
   ResourceTableType* type = package->FindOrCreateType(name.type);
   if (res_id.is_valid_dynamic() && type->id && type->id.value() != res_id.type_id()) {
-    diag->Error(DiagMessage(value->GetSource())
-                << "trying to add resource '" << name << "' with ID " << res_id
-                << " but type '" << type->type << "' already has ID "
-                << std::hex << (int)type->id.value() << std::dec);
+    diag->Error(DiagMessage(source)
+                << "trying to add resource '" << name << "' with ID " << res_id << " but type '"
+                << type->type << "' already has ID " << StringPrintf("%02x", type->id.value()));
     return false;
   }
 
   ResourceEntry* entry = type->FindOrCreateEntry(name.entry);
   if (res_id.is_valid_dynamic() && entry->id && entry->id.value() != res_id.entry_id()) {
-    diag->Error(DiagMessage(value->GetSource())
+    diag->Error(DiagMessage(source)
                 << "trying to add resource '" << name << "' with ID " << res_id
                 << " but resource already has ID "
-                << ResourceId(package->id.value(), type->id.value(),
-                              entry->id.value()));
+                << ResourceId(package->id.value(), type->id.value(), entry->id.value()));
     return false;
   }
 
   ResourceConfigValue* config_value = entry->FindOrCreateValue(config, product);
-  if (!config_value->value) {
+  if (config_value->value == nullptr) {
     // Resource does not exist, add it now.
     config_value->value = std::move(value);
-
   } else {
-    switch (conflictResolver(config_value->value.get(), value.get())) {
+    switch (conflict_resolver(config_value->value.get(), value.get())) {
       case CollisionResult::kTakeNew:
         // Take the incoming value.
         config_value->value = std::move(value);
         break;
 
       case CollisionResult::kConflict:
-        diag->Error(DiagMessage(value->GetSource())
-                    << "duplicate value for resource '" << name << "' "
-                    << "with config '" << config << "'");
-        diag->Error(DiagMessage(config_value->value->GetSource())
-                    << "resource previously defined here");
+        diag->Error(DiagMessage(source) << "duplicate value for resource '" << name << "' "
+                                        << "with config '" << config << "'");
+        diag->Error(DiagMessage(source) << "resource previously defined here");
         return false;
 
       case CollisionResult::kKeepOriginal:
@@ -441,52 +430,56 @@
   return true;
 }
 
-bool ResourceTable::SetSymbolState(const ResourceNameRef& name, const ResourceId& res_id,
-                                   const Symbol& symbol, IDiagnostics* diag) {
-  return SetSymbolStateImpl(name, res_id, symbol, ValidateName, diag);
+bool ResourceTable::SetVisibility(const ResourceNameRef& name, const Visibility& visibility,
+                                  IDiagnostics* diag) {
+  return SetVisibilityImpl(name, visibility, ResourceId{}, ResourceNameValidator, diag);
 }
 
-bool ResourceTable::SetSymbolStateAllowMangled(const ResourceNameRef& name,
-                                               const ResourceId& res_id,
-                                               const Symbol& symbol,
-                                               IDiagnostics* diag) {
-  return SetSymbolStateImpl(name, res_id, symbol, SkipValidateName, diag);
+bool ResourceTable::SetVisibilityMangled(const ResourceNameRef& name, const Visibility& visibility,
+                                         IDiagnostics* diag) {
+  return SetVisibilityImpl(name, visibility, ResourceId{}, SkipNameValidator, diag);
 }
 
-bool ResourceTable::SetSymbolStateImpl(const ResourceNameRef& name, const ResourceId& res_id,
-                                       const Symbol& symbol, NameValidator name_validator,
-                                       IDiagnostics* diag) {
+bool ResourceTable::SetVisibilityWithId(const ResourceNameRef& name, const Visibility& visibility,
+                                        const ResourceId& res_id, IDiagnostics* diag) {
+  return SetVisibilityImpl(name, visibility, res_id, ResourceNameValidator, diag);
+}
+
+bool ResourceTable::SetVisibilityWithIdMangled(const ResourceNameRef& name,
+                                               const Visibility& visibility,
+                                               const ResourceId& res_id, IDiagnostics* diag) {
+  return SetVisibilityImpl(name, visibility, res_id, SkipNameValidator, diag);
+}
+
+bool ResourceTable::SetVisibilityImpl(const ResourceNameRef& name, const Visibility& visibility,
+                                      const ResourceId& res_id, NameValidator name_validator,
+                                      IDiagnostics* diag) {
   CHECK(diag != nullptr);
 
-  const StringPiece bad_char = name_validator(name.entry);
-  if (!bad_char.empty()) {
-    diag->Error(DiagMessage(symbol.source) << "resource '" << name << "' has invalid entry name '"
-                                           << name.entry << "'. Invalid character '" << bad_char
-                                           << "'");
+  const Source& source = visibility.source;
+  if (!ValidateName(name_validator, name, source, diag)) {
     return false;
   }
 
   ResourceTablePackage* package = FindOrCreatePackage(name.package);
   if (res_id.is_valid_dynamic() && package->id && package->id.value() != res_id.package_id()) {
-    diag->Error(DiagMessage(symbol.source)
-                << "trying to add resource '" << name << "' with ID " << res_id
-                << " but package '" << package->name << "' already has ID "
-                << std::hex << (int)package->id.value() << std::dec);
+    diag->Error(DiagMessage(source) << "trying to add resource '" << name << "' with ID " << res_id
+                                    << " but package '" << package->name << "' already has ID "
+                                    << StringPrintf("%02x", package->id.value()));
     return false;
   }
 
   ResourceTableType* type = package->FindOrCreateType(name.type);
   if (res_id.is_valid_dynamic() && type->id && type->id.value() != res_id.type_id()) {
-    diag->Error(DiagMessage(symbol.source)
-                << "trying to add resource '" << name << "' with ID " << res_id
-                << " but type '" << type->type << "' already has ID "
-                << std::hex << (int)type->id.value() << std::dec);
+    diag->Error(DiagMessage(source)
+                << "trying to add resource '" << name << "' with ID " << res_id << " but type '"
+                << type->type << "' already has ID " << StringPrintf("%02x", type->id.value()));
     return false;
   }
 
   ResourceEntry* entry = type->FindOrCreateEntry(name.entry);
   if (res_id.is_valid_dynamic() && entry->id && entry->id.value() != res_id.entry_id()) {
-    diag->Error(DiagMessage(symbol.source)
+    diag->Error(DiagMessage(source)
                 << "trying to add resource '" << name << "' with ID " << res_id
                 << " but resource already has ID "
                 << ResourceId(package->id.value(), type->id.value(), entry->id.value()));
@@ -499,48 +492,96 @@
     entry->id = res_id.entry_id();
   }
 
-  // Only mark the type state as public, it doesn't care about being private.
-  if (symbol.state == SymbolState::kPublic) {
-    type->symbol_status.state = SymbolState::kPublic;
+  // Only mark the type visibility level as public, it doesn't care about being private.
+  if (visibility.level == Visibility::Level::kPublic) {
+    type->visibility_level = Visibility::Level::kPublic;
   }
 
-  if (symbol.allow_new) {
-    // This symbol can be added as a new resource when merging (if it belongs to an overlay).
-    entry->symbol_status.allow_new = true;
-  }
-
-  if (symbol.state == SymbolState::kUndefined &&
-      entry->symbol_status.state != SymbolState::kUndefined) {
+  if (visibility.level == Visibility::Level::kUndefined &&
+      entry->visibility.level != Visibility::Level::kUndefined) {
     // We can't undefine a symbol (remove its visibility). Ignore.
     return true;
   }
 
-  if (symbol.state == SymbolState::kPrivate &&
-      entry->symbol_status.state == SymbolState::kPublic) {
+  if (visibility.level < entry->visibility.level) {
     // We can't downgrade public to private. Ignore.
     return true;
   }
 
   // This symbol definition takes precedence, replace.
-  entry->symbol_status.state = symbol.state;
-  entry->symbol_status.source = symbol.source;
-  entry->symbol_status.comment = symbol.comment;
+  entry->visibility = visibility;
   return true;
 }
 
-Maybe<ResourceTable::SearchResult> ResourceTable::FindResource(const ResourceNameRef& name) {
+bool ResourceTable::SetAllowNew(const ResourceNameRef& name, const AllowNew& allow_new,
+                                IDiagnostics* diag) {
+  return SetAllowNewImpl(name, allow_new, ResourceNameValidator, diag);
+}
+
+bool ResourceTable::SetAllowNewMangled(const ResourceNameRef& name, const AllowNew& allow_new,
+                                       IDiagnostics* diag) {
+  return SetAllowNewImpl(name, allow_new, SkipNameValidator, diag);
+}
+
+bool ResourceTable::SetAllowNewImpl(const ResourceNameRef& name, const AllowNew& allow_new,
+                                    NameValidator name_validator, IDiagnostics* diag) {
+  CHECK(diag != nullptr);
+
+  if (!ValidateName(name_validator, name, allow_new.source, diag)) {
+    return false;
+  }
+
+  ResourceTablePackage* package = FindOrCreatePackage(name.package);
+  ResourceTableType* type = package->FindOrCreateType(name.type);
+  ResourceEntry* entry = type->FindOrCreateEntry(name.entry);
+  entry->allow_new = allow_new;
+  return true;
+}
+
+bool ResourceTable::SetOverlayable(const ResourceNameRef& name, const Overlayable& overlayable,
+                                   IDiagnostics* diag) {
+  return SetOverlayableImpl(name, overlayable, ResourceNameValidator, diag);
+}
+
+bool ResourceTable::SetOverlayableMangled(const ResourceNameRef& name,
+                                          const Overlayable& overlayable, IDiagnostics* diag) {
+  return SetOverlayableImpl(name, overlayable, SkipNameValidator, diag);
+}
+
+bool ResourceTable::SetOverlayableImpl(const ResourceNameRef& name, const Overlayable& overlayable,
+                                       NameValidator name_validator, IDiagnostics* diag) {
+  CHECK(diag != nullptr);
+
+  if (!ValidateName(name_validator, name, overlayable.source, diag)) {
+    return false;
+  }
+
+  ResourceTablePackage* package = FindOrCreatePackage(name.package);
+  ResourceTableType* type = package->FindOrCreateType(name.type);
+  ResourceEntry* entry = type->FindOrCreateEntry(name.entry);
+  if (entry->overlayable) {
+    diag->Error(DiagMessage(overlayable.source)
+                << "duplicate overlayable declaration for resource '" << name << "'");
+    diag->Error(DiagMessage(entry->overlayable.value().source) << "previous declaration here");
+    return false;
+  }
+  entry->overlayable = overlayable;
+  return true;
+}
+
+Maybe<ResourceTable::SearchResult> ResourceTable::FindResource(const ResourceNameRef& name) const {
   ResourceTablePackage* package = FindPackage(name.package);
-  if (!package) {
+  if (package == nullptr) {
     return {};
   }
 
   ResourceTableType* type = package->FindType(name.type);
-  if (!type) {
+  if (type == nullptr) {
     return {};
   }
 
   ResourceEntry* entry = type->FindEntry(name.entry);
-  if (!entry) {
+  if (entry == nullptr) {
     return {};
   }
   return SearchResult{package, type, entry};
@@ -552,23 +593,20 @@
     ResourceTablePackage* new_pkg = new_table->CreatePackage(pkg->name, pkg->id);
     for (const auto& type : pkg->types) {
       ResourceTableType* new_type = new_pkg->FindOrCreateType(type->type);
-      if (!new_type->id) {
-        new_type->id = type->id;
-        new_type->symbol_status = type->symbol_status;
-      }
+      new_type->id = type->id;
+      new_type->visibility_level = type->visibility_level;
 
       for (const auto& entry : type->entries) {
         ResourceEntry* new_entry = new_type->FindOrCreateEntry(entry->name);
-        if (!new_entry->id) {
-          new_entry->id = entry->id;
-          new_entry->symbol_status = entry->symbol_status;
-        }
+        new_entry->id = entry->id;
+        new_entry->visibility = entry->visibility;
+        new_entry->allow_new = entry->allow_new;
+        new_entry->overlayable = entry->overlayable;
 
         for (const auto& config_value : entry->values) {
           ResourceConfigValue* new_value =
               new_entry->FindOrCreateValue(config_value->config, config_value->product);
-          Value* value = config_value->value->Clone(&new_table->string_pool);
-          new_value->value = std::unique_ptr<Value>(value);
+          new_value->value.reset(config_value->value->Clone(&new_table->string_pool));
         }
       }
     }
diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h
index d5db67e..eaa2d0b 100644
--- a/tools/aapt2/ResourceTable.h
+++ b/tools/aapt2/ResourceTable.h
@@ -38,40 +38,40 @@
 
 namespace aapt {
 
-enum class SymbolState {
-  kUndefined,
-  kPrivate,
-  kPublic,
+// The Public status of a resource.
+struct Visibility {
+  enum class Level {
+    kUndefined,
+    kPrivate,
+    kPublic,
+  };
+
+  Level level = Level::kUndefined;
+  Source source;
+  std::string comment;
 };
 
-/**
- * The Public status of a resource.
- */
-struct Symbol {
-  SymbolState state = SymbolState::kUndefined;
+// Represents <add-resource> in an overlay.
+struct AllowNew {
   Source source;
+  std::string comment;
+};
 
-  // Whether this entry (originating from an overlay) can be added as a new resource.
-  bool allow_new = false;
-
+// The policy dictating whether an entry is overlayable at runtime by RROs.
+struct Overlayable {
+  Source source;
   std::string comment;
 };
 
 class ResourceConfigValue {
  public:
-  /**
-   * The configuration for which this value is defined.
-   */
+  // The configuration for which this value is defined.
   const ConfigDescription config;
 
-  /**
-   * The product for which this value is defined.
-   */
+  // The product for which this value is defined.
   const std::string product;
 
-  /**
-   * The actual Value.
-   */
+  // The actual Value.
   std::unique_ptr<Value> value;
 
   ResourceConfigValue(const ConfigDescription& config, const android::StringPiece& product)
@@ -87,27 +87,21 @@
  */
 class ResourceEntry {
  public:
-  /**
-   * The name of the resource. Immutable, as
-   * this determines the order of this resource
-   * when doing lookups.
-   */
+  // The name of the resource. Immutable, as this determines the order of this resource
+  // when doing lookups.
   const std::string name;
 
-  /**
-   * The entry ID for this resource.
-   */
+  // The entry ID for this resource (the EEEE in 0xPPTTEEEE).
   Maybe<uint16_t> id;
 
-  /**
-   * Whether this resource is public (and must maintain the same entry ID across
-   * builds).
-   */
-  Symbol symbol_status;
+  // Whether this resource is public (and must maintain the same entry ID across builds).
+  Visibility visibility;
 
-  /**
-   * The resource's values for each configuration.
-   */
+  Maybe<AllowNew> allow_new;
+
+  Maybe<Overlayable> overlayable;
+
+  // The resource's values for each configuration.
   std::vector<std::unique_ptr<ResourceConfigValue>> values;
 
   explicit ResourceEntry(const android::StringPiece& name) : name(name.to_string()) {}
@@ -125,31 +119,19 @@
   DISALLOW_COPY_AND_ASSIGN(ResourceEntry);
 };
 
-/**
- * Represents a resource type, which holds entries defined
- * for this type.
- */
+// Represents a resource type (eg. string, drawable, layout, etc.) containing resource entries.
 class ResourceTableType {
  public:
-  /**
-   * The logical type of resource (string, drawable, layout, etc.).
-   */
+  // The logical type of resource (string, drawable, layout, etc.).
   const ResourceType type;
 
-  /**
-   * The type ID for this resource.
-   */
+  // The type ID for this resource (the TT in 0xPPTTEEEE).
   Maybe<uint8_t> id;
 
-  /**
-   * Whether this type is public (and must maintain the same
-   * type ID across builds).
-   */
-  Symbol symbol_status;
+  // Whether this type is public (and must maintain the same type ID across builds).
+  Visibility::Level visibility_level = Visibility::Level::kUndefined;
 
-  /**
-   * List of resources for this type.
-   */
+  // List of resources for this type.
   std::vector<std::unique_ptr<ResourceEntry>> entries;
 
   explicit ResourceTableType(const ResourceType type) : type(type) {}
@@ -163,9 +145,11 @@
 
 class ResourceTablePackage {
  public:
-  Maybe<uint8_t> id;
   std::string name;
 
+  // The package ID (the PP in 0xPPTTEEEE).
+  Maybe<uint8_t> id;
+
   std::vector<std::unique_ptr<ResourceTableType>> types;
 
   ResourceTablePackage() = default;
@@ -176,10 +160,7 @@
   DISALLOW_COPY_AND_ASSIGN(ResourceTablePackage);
 };
 
-/**
- * The container and index for all resources defined for an app. This gets
- * flattened into a binary resource table (resources.arsc).
- */
+// The container and index for all resources defined for an app.
 class ResourceTable {
  public:
   ResourceTable() = default;
@@ -188,47 +169,51 @@
 
   using CollisionResolverFunc = std::function<CollisionResult(Value*, Value*)>;
 
-  /**
-   * When a collision of resources occurs, this method decides which value to
-   * keep.
-   */
+  // When a collision of resources occurs, this method decides which value to keep.
   static CollisionResult ResolveValueCollision(Value* existing, Value* incoming);
 
   bool AddResource(const ResourceNameRef& name, const ConfigDescription& config,
                    const android::StringPiece& product, std::unique_ptr<Value> value,
                    IDiagnostics* diag);
 
-  bool AddResource(const ResourceNameRef& name, const ResourceId& res_id,
-                   const ConfigDescription& config, const android::StringPiece& product,
-                   std::unique_ptr<Value> value, IDiagnostics* diag);
+  bool AddResourceWithId(const ResourceNameRef& name, const ResourceId& res_id,
+                         const ConfigDescription& config, const android::StringPiece& product,
+                         std::unique_ptr<Value> value, IDiagnostics* diag);
 
   bool AddFileReference(const ResourceNameRef& name, const ConfigDescription& config,
                         const Source& source, const android::StringPiece& path, IDiagnostics* diag);
 
-  bool AddFileReferenceAllowMangled(const ResourceNameRef& name, const ConfigDescription& config,
-                                    const Source& source, const android::StringPiece& path,
-                                    io::IFile* file, IDiagnostics* diag);
+  bool AddFileReferenceMangled(const ResourceNameRef& name, const ConfigDescription& config,
+                               const Source& source, const android::StringPiece& path,
+                               io::IFile* file, IDiagnostics* diag);
 
-  /**
-   * Same as AddResource, but doesn't verify the validity of the name. This is
-   * used
-   * when loading resources from an existing binary resource table that may have
-   * mangled
-   * names.
-   */
-  bool AddResourceAllowMangled(const ResourceNameRef& name, const ConfigDescription& config,
-                               const android::StringPiece& product, std::unique_ptr<Value> value,
-                               IDiagnostics* diag);
+  // Same as AddResource, but doesn't verify the validity of the name. This is used
+  // when loading resources from an existing binary resource table that may have mangled names.
+  bool AddResourceMangled(const ResourceNameRef& name, const ConfigDescription& config,
+                          const android::StringPiece& product, std::unique_ptr<Value> value,
+                          IDiagnostics* diag);
 
-  bool AddResourceAllowMangled(const ResourceNameRef& name, const ResourceId& id,
-                               const ConfigDescription& config, const android::StringPiece& product,
-                               std::unique_ptr<Value> value, IDiagnostics* diag);
+  bool AddResourceWithIdMangled(const ResourceNameRef& name, const ResourceId& id,
+                                const ConfigDescription& config,
+                                const android::StringPiece& product, std::unique_ptr<Value> value,
+                                IDiagnostics* diag);
 
-  bool SetSymbolState(const ResourceNameRef& name, const ResourceId& res_id,
-                      const Symbol& symbol, IDiagnostics* diag);
+  bool SetVisibility(const ResourceNameRef& name, const Visibility& visibility, IDiagnostics* diag);
+  bool SetVisibilityMangled(const ResourceNameRef& name, const Visibility& visibility,
+                            IDiagnostics* diag);
+  bool SetVisibilityWithId(const ResourceNameRef& name, const Visibility& visibility,
+                           const ResourceId& res_id, IDiagnostics* diag);
+  bool SetVisibilityWithIdMangled(const ResourceNameRef& name, const Visibility& visibility,
+                                  const ResourceId& res_id, IDiagnostics* diag);
 
-  bool SetSymbolStateAllowMangled(const ResourceNameRef& name, const ResourceId& res_id,
-                                  const Symbol& symbol, IDiagnostics* diag);
+  bool SetOverlayable(const ResourceNameRef& name, const Overlayable& overlayable,
+                      IDiagnostics* diag);
+  bool SetOverlayableMangled(const ResourceNameRef& name, const Overlayable& overlayable,
+                             IDiagnostics* diag);
+
+  bool SetAllowNew(const ResourceNameRef& name, const AllowNew& allow_new, IDiagnostics* diag);
+  bool SetAllowNewMangled(const ResourceNameRef& name, const AllowNew& allow_new,
+                          IDiagnostics* diag);
 
   struct SearchResult {
     ResourceTablePackage* package;
@@ -236,40 +221,28 @@
     ResourceEntry* entry;
   };
 
-  Maybe<SearchResult> FindResource(const ResourceNameRef& name);
+  Maybe<SearchResult> FindResource(const ResourceNameRef& name) const;
 
-  /**
-   * Returns the package struct with the given name, or nullptr if such a
-   * package does not
-   * exist. The empty string is a valid package and typically is used to
-   * represent the
-   * 'current' package before it is known to the ResourceTable.
-   */
-  ResourceTablePackage* FindPackage(const android::StringPiece& name);
+  // Returns the package struct with the given name, or nullptr if such a package does not
+  // exist. The empty string is a valid package and typically is used to represent the
+  // 'current' package before it is known to the ResourceTable.
+  ResourceTablePackage* FindPackage(const android::StringPiece& name) const;
 
-  ResourceTablePackage* FindPackageById(uint8_t id);
+  ResourceTablePackage* FindPackageById(uint8_t id) const;
 
   ResourceTablePackage* CreatePackage(const android::StringPiece& name, Maybe<uint8_t> id = {});
 
   std::unique_ptr<ResourceTable> Clone() const;
 
-  /**
-   * The string pool used by this resource table. Values that reference strings
-   * must use
-   * this pool to create their strings.
-   *
-   * NOTE: `string_pool` must come before `packages` so that it is destroyed
-   * after.
-   * When `string_pool` references are destroyed (as they will be when
-   * `packages`
-   * is destroyed), they decrement a refCount, which would cause invalid
-   * memory access if the pool was already destroyed.
-   */
+  // The string pool used by this resource table. Values that reference strings must use
+  // this pool to create their strings.
+  // NOTE: `string_pool` must come before `packages` so that it is destroyed after.
+  // When `string_pool` references are destroyed (as they will be when `packages` is destroyed),
+  // they decrement a refCount, which would cause invalid memory access if the pool was already
+  // destroyed.
   StringPool string_pool;
 
-  /**
-   * The list of packages in this table, sorted alphabetically by package name.
-   */
+  // The list of packages in this table, sorted alphabetically by package name.
   std::vector<std::unique_ptr<ResourceTablePackage>> packages;
 
   // Set of dynamic packages that this table may reference. Their package names get encoded
@@ -284,6 +257,9 @@
 
   ResourceTablePackage* FindOrCreatePackage(const android::StringPiece& name);
 
+  bool ValidateName(NameValidator validator, const ResourceNameRef& name, const Source& source,
+                    IDiagnostics* diag);
+
   bool AddResourceImpl(const ResourceNameRef& name, const ResourceId& res_id,
                        const ConfigDescription& config, const android::StringPiece& product,
                        std::unique_ptr<Value> value, NameValidator name_validator,
@@ -293,8 +269,19 @@
                             const Source& source, const android::StringPiece& path, io::IFile* file,
                             NameValidator name_validator, IDiagnostics* diag);
 
+  bool SetVisibilityImpl(const ResourceNameRef& name, const Visibility& visibility,
+                         const ResourceId& res_id, NameValidator name_validator,
+                         IDiagnostics* diag);
+
+  bool SetAllowNewImpl(const ResourceNameRef& name, const AllowNew& allow_new,
+                       NameValidator name_validator, IDiagnostics* diag);
+
+  bool SetOverlayableImpl(const ResourceNameRef& name, const Overlayable& overlayable,
+                          NameValidator name_validator, IDiagnostics* diag);
+
   bool SetSymbolStateImpl(const ResourceNameRef& name, const ResourceId& res_id,
-                          const Symbol& symbol, NameValidator name_validator, IDiagnostics* diag);
+                          const Visibility& symbol, NameValidator name_validator,
+                          IDiagnostics* diag);
 
   DISALLOW_COPY_AND_ASSIGN(ResourceTable);
 };
diff --git a/tools/aapt2/ResourceTable_test.cpp b/tools/aapt2/ResourceTable_test.cpp
index 2a3c131..eb75f94 100644
--- a/tools/aapt2/ResourceTable_test.cpp
+++ b/tools/aapt2/ResourceTable_test.cpp
@@ -24,7 +24,10 @@
 #include <ostream>
 #include <string>
 
+using ::android::StringPiece;
+using ::testing::Eq;
 using ::testing::NotNull;
+using ::testing::StrEq;
 
 namespace aapt {
 
@@ -45,7 +48,7 @@
 TEST(ResourceTableTest, AddResourceWithWeirdNameWhenAddingMangledResources) {
   ResourceTable table;
 
-  EXPECT_TRUE(table.AddResourceAllowMangled(
+  EXPECT_TRUE(table.AddResourceMangled(
       test::ParseNameOrDie("android:id/heythere       "), ConfigDescription{}, "",
       test::ValueBuilder<Id>().SetSource("test.xml", 21u).Build(), test::GetDiagnostics()));
 }
@@ -141,4 +144,104 @@
   EXPECT_EQ(std::string("tablet"), values[1]->product);
 }
 
+static StringPiece LevelToString(Visibility::Level level) {
+  switch (level) {
+    case Visibility::Level::kPrivate:
+      return "private";
+    case Visibility::Level::kPublic:
+      return "private";
+    default:
+      return "undefined";
+  }
+}
+
+static ::testing::AssertionResult VisibilityOfResource(const ResourceTable& table,
+                                                       const ResourceNameRef& name,
+                                                       Visibility::Level level,
+                                                       const StringPiece& comment) {
+  Maybe<ResourceTable::SearchResult> result = table.FindResource(name);
+  if (!result) {
+    return ::testing::AssertionFailure() << "no resource '" << name << "' found in table";
+  }
+
+  const Visibility& visibility = result.value().entry->visibility;
+  if (visibility.level != level) {
+    return ::testing::AssertionFailure() << "expected visibility " << LevelToString(level)
+                                         << " but got " << LevelToString(visibility.level);
+  }
+
+  if (visibility.comment != comment) {
+    return ::testing::AssertionFailure() << "expected visibility comment '" << comment
+                                         << "' but got '" << visibility.comment << "'";
+  }
+  return ::testing::AssertionSuccess();
+}
+
+TEST(ResourceTableTest, SetVisibility) {
+  using Level = Visibility::Level;
+
+  ResourceTable table;
+  const ResourceName name = test::ParseNameOrDie("android:string/foo");
+
+  Visibility visibility;
+  visibility.level = Visibility::Level::kPrivate;
+  visibility.comment = "private";
+  ASSERT_TRUE(table.SetVisibility(name, visibility, test::GetDiagnostics()));
+  ASSERT_TRUE(VisibilityOfResource(table, name, Level::kPrivate, "private"));
+
+  visibility.level = Visibility::Level::kUndefined;
+  visibility.comment = "undefined";
+  ASSERT_TRUE(table.SetVisibility(name, visibility, test::GetDiagnostics()));
+  ASSERT_TRUE(VisibilityOfResource(table, name, Level::kPrivate, "private"));
+
+  visibility.level = Visibility::Level::kPublic;
+  visibility.comment = "public";
+  ASSERT_TRUE(table.SetVisibility(name, visibility, test::GetDiagnostics()));
+  ASSERT_TRUE(VisibilityOfResource(table, name, Level::kPublic, "public"));
+
+  visibility.level = Visibility::Level::kPrivate;
+  visibility.comment = "private";
+  ASSERT_TRUE(table.SetVisibility(name, visibility, test::GetDiagnostics()));
+  ASSERT_TRUE(VisibilityOfResource(table, name, Level::kPublic, "public"));
+}
+
+TEST(ResourceTableTest, SetAllowNew) {
+  ResourceTable table;
+  const ResourceName name = test::ParseNameOrDie("android:string/foo");
+
+  AllowNew allow_new;
+  Maybe<ResourceTable::SearchResult> result;
+
+  allow_new.comment = "first";
+  ASSERT_TRUE(table.SetAllowNew(name, allow_new, test::GetDiagnostics()));
+  result = table.FindResource(name);
+  ASSERT_TRUE(result);
+  ASSERT_TRUE(result.value().entry->allow_new);
+  ASSERT_THAT(result.value().entry->allow_new.value().comment, StrEq("first"));
+
+  allow_new.comment = "second";
+  ASSERT_TRUE(table.SetAllowNew(name, allow_new, test::GetDiagnostics()));
+  result = table.FindResource(name);
+  ASSERT_TRUE(result);
+  ASSERT_TRUE(result.value().entry->allow_new);
+  ASSERT_THAT(result.value().entry->allow_new.value().comment, StrEq("second"));
+}
+
+TEST(ResourceTableTest, SetOverlayable) {
+  ResourceTable table;
+  const ResourceName name = test::ParseNameOrDie("android:string/foo");
+
+  Overlayable overlayable;
+
+  overlayable.comment = "first";
+  ASSERT_TRUE(table.SetOverlayable(name, overlayable, test::GetDiagnostics()));
+  Maybe<ResourceTable::SearchResult> result = table.FindResource(name);
+  ASSERT_TRUE(result);
+  ASSERT_TRUE(result.value().entry->overlayable);
+  ASSERT_THAT(result.value().entry->overlayable.value().comment, StrEq("first"));
+
+  overlayable.comment = "second";
+  ASSERT_FALSE(table.SetOverlayable(name, overlayable, test::GetDiagnostics()));
+}
+
 }  // namespace aapt
diff --git a/tools/aapt2/Resources.proto b/tools/aapt2/Resources.proto
index 7e7c86d..8552195 100644
--- a/tools/aapt2/Resources.proto
+++ b/tools/aapt2/Resources.proto
@@ -93,11 +93,10 @@
   repeated Entry entry = 3;
 }
 
-// The status of a symbol/entry. This contains information like visibility (public/private),
-// comments, and whether the entry can be overridden.
-message SymbolStatus {
+// The Visibility of a symbol/entry (public, private, undefined).
+message Visibility {
   // The visibility of the resource outside of its package.
-  enum Visibility {
+  enum Level {
     // No visibility was explicitly specified. This is typically treated as private.
     // The distinction is important when two separate R.java files are generated: a public and
     // private one. An unknown visibility, in this case, would cause the resource to be omitted
@@ -115,17 +114,32 @@
     PUBLIC = 2;
   }
 
-  Visibility visibility = 1;
+  Level level = 1;
 
   // The path at which this entry's visibility was defined (eg. public.xml).
   Source source = 2;
 
   // The comment associated with the <public> tag.
   string comment = 3;
+}
 
-  // Whether the symbol can be merged into another resource table without there being an existing
-  // definition to override. Used for overlays and set to true when <add-resource> is specified.
-  bool allow_new = 4;
+// Whether a resource comes from a compile-time overlay and is explicitly allowed to not overlay an
+// existing resource.
+message AllowNew {
+  // Where this was defined in source.
+  Source source = 1;
+
+  // Any comment associated with the declaration.
+  string comment = 2;
+}
+
+// Whether a resource is overlayable by runtime resource overlays (RRO).
+message Overlayable {
+  // Where this declaration was defined in source.
+  Source source = 1;
+
+  // Any comment associated with the declaration.
+  string comment = 2;
 }
 
 // An entry ID in the range [0x0000, 0xffff].
@@ -147,12 +161,19 @@
   // form package:type/entry.
   string name = 2;
 
-  // The symbol status of this entry, which includes visibility information.
-  SymbolStatus symbol_status = 3;
+  // The visibility of this entry (public, private, undefined).
+  Visibility visibility = 3;
+
+  // Whether this resource, when originating from a compile-time overlay, is allowed to NOT overlay
+  // any existing resources.
+  AllowNew allow_new = 4;
+
+  // Whether this resource can be overlaid by a runtime resource overlay (RRO).
+  Overlayable overlayable = 5;
 
   // The set of values defined for this entry, each corresponding to a different
   // configuration/variant.
-  repeated ConfigValue config_value = 4;
+  repeated ConfigValue config_value = 6;
 }
 
 // A Configuration/Value pair.
diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp
index 83512b9..7c1e96e 100644
--- a/tools/aapt2/cmd/Compile.cpp
+++ b/tools/aapt2/cmd/Compile.cpp
@@ -49,6 +49,7 @@
 #include "xml/XmlPullParser.h"
 
 using ::aapt::io::FileInputStream;
+using ::aapt::text::Printer;
 using ::android::StringPiece;
 using ::android::base::SystemErrorCodeToString;
 using ::google::protobuf::io::CopyingOutputStreamAdaptor;
@@ -112,6 +113,7 @@
 struct CompileOptions {
   std::string output_path;
   Maybe<std::string> res_dir;
+  Maybe<std::string> generate_text_symbols_path;
   bool pseudolocalize = false;
   bool no_png_crunch = false;
   bool legacy_mode = false;
@@ -261,6 +263,58 @@
     context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to finish entry");
     return false;
   }
+
+  if (options.generate_text_symbols_path) {
+    io::FileOutputStream fout_text(options.generate_text_symbols_path.value());
+
+    if (fout_text.HadError()) {
+      context->GetDiagnostics()->Error(DiagMessage()
+                                       << "failed writing to'"
+                                       << options.generate_text_symbols_path.value()
+                                       << "': " << fout_text.GetError());
+      return false;
+    }
+
+    Printer r_txt_printer(&fout_text);
+    for (const auto& package : table.packages) {
+      for (const auto& type : package->types) {
+        for (const auto& entry : type->entries) {
+          // Check access modifiers.
+          switch(entry->visibility.level) {
+            case Visibility::Level::kUndefined :
+              r_txt_printer.Print("default ");
+              break;
+            case Visibility::Level::kPublic :
+              r_txt_printer.Print("public ");
+              break;
+            case Visibility::Level::kPrivate :
+              r_txt_printer.Print("private ");
+          }
+
+          if (type->type != ResourceType::kStyleable) {
+            r_txt_printer.Print("int ");
+            r_txt_printer.Print(to_string(type->type));
+            r_txt_printer.Print(" ");
+            r_txt_printer.Println(entry->name);
+          } else {
+            r_txt_printer.Print("int[] styleable ");
+            r_txt_printer.Println(entry->name);
+
+            if (!entry->values.empty()) {
+              auto styleable = static_cast<const Styleable*>(entry->values.front()->value.get());
+              for (const auto& attr : styleable->entries) {
+                r_txt_printer.Print("default int styleable ");
+                r_txt_printer.Print(entry->name);
+                r_txt_printer.Print("_");
+                r_txt_printer.Println(attr.name.value().entry);
+              }
+            }
+          }
+        }
+      }
+    }
+  }
+
   return true;
 }
 
@@ -402,6 +456,31 @@
     context->GetDiagnostics()->Error(DiagMessage(output_path) << "failed to finish writing data");
     return false;
   }
+
+  if (options.generate_text_symbols_path) {
+    io::FileOutputStream fout_text(options.generate_text_symbols_path.value());
+
+    if (fout_text.HadError()) {
+      context->GetDiagnostics()->Error(DiagMessage()
+                                       << "failed writing to'"
+                                       << options.generate_text_symbols_path.value()
+                                       << "': " << fout_text.GetError());
+      return false;
+    }
+
+    Printer r_txt_printer(&fout_text);
+    for (const auto res : xmlres->file.exported_symbols) {
+      r_txt_printer.Print("default int id ");
+      r_txt_printer.Println(res.name.entry);
+    }
+
+    // And print ourselves.
+    r_txt_printer.Print("default int ");
+    r_txt_printer.Print(path_data.resource_dir);
+    r_txt_printer.Print(" ");
+    r_txt_printer.Println(path_data.name);
+  }
+
   return true;
 }
 
@@ -609,6 +688,10 @@
       Flags()
           .RequiredFlag("-o", "Output path", &options.output_path)
           .OptionalFlag("--dir", "Directory to scan for resources", &options.res_dir)
+          .OptionalFlag("--output-text-symbols",
+                        "Generates a text file containing the resource symbols in the\n"
+                        "specified file",
+                        &options.generate_text_symbols_path)
           .OptionalSwitch("--pseudo-localize",
                           "Generate resources for pseudo-locales "
                           "(en-XA and ar-XB)",
diff --git a/tools/aapt2/cmd/Diff.cpp b/tools/aapt2/cmd/Diff.cpp
index fc1f1d6..12113ed 100644
--- a/tools/aapt2/cmd/Diff.cpp
+++ b/tools/aapt2/cmd/Diff.cpp
@@ -75,14 +75,14 @@
   std::cerr << source << ": " << message << "\n";
 }
 
-static bool IsSymbolVisibilityDifferent(const Symbol& symbol_a, const Symbol& symbol_b) {
-  return symbol_a.state != symbol_b.state;
+static bool IsSymbolVisibilityDifferent(const Visibility& vis_a, const Visibility& vis_b) {
+  return vis_a.level != vis_b.level;
 }
 
 template <typename Id>
-static bool IsIdDiff(const Symbol& symbol_a, const Maybe<Id>& id_a, const Symbol& symbol_b,
-                     const Maybe<Id>& id_b) {
-  if (symbol_a.state == SymbolState::kPublic || symbol_b.state == SymbolState::kPublic) {
+static bool IsIdDiff(const Visibility::Level& level_a, const Maybe<Id>& id_a,
+                     const Visibility::Level& level_b, const Maybe<Id>& id_b) {
+  if (level_a == Visibility::Level::kPublic || level_b == Visibility::Level::kPublic) {
     return id_a != id_b;
   }
   return false;
@@ -157,17 +157,17 @@
       EmitDiffLine(apk_b->GetSource(), str_stream.str());
       diff = true;
     } else {
-      if (IsSymbolVisibilityDifferent(entry_a->symbol_status, entry_b->symbol_status)) {
+      if (IsSymbolVisibilityDifferent(entry_a->visibility, entry_b->visibility)) {
         std::stringstream str_stream;
         str_stream << pkg_a->name << ":" << type_a->type << "/" << entry_a->name
                    << " has different visibility (";
-        if (entry_b->symbol_status.state == SymbolState::kPublic) {
+        if (entry_b->visibility.level == Visibility::Level::kPublic) {
           str_stream << "PUBLIC";
         } else {
           str_stream << "PRIVATE";
         }
         str_stream << " vs ";
-        if (entry_a->symbol_status.state == SymbolState::kPublic) {
+        if (entry_a->visibility.level == Visibility::Level::kPublic) {
           str_stream << "PUBLIC";
         } else {
           str_stream << "PRIVATE";
@@ -175,7 +175,7 @@
         str_stream << ")";
         EmitDiffLine(apk_b->GetSource(), str_stream.str());
         diff = true;
-      } else if (IsIdDiff(entry_a->symbol_status, entry_a->id, entry_b->symbol_status,
+      } else if (IsIdDiff(entry_a->visibility.level, entry_a->id, entry_b->visibility.level,
                           entry_b->id)) {
         std::stringstream str_stream;
         str_stream << pkg_a->name << ":" << type_a->type << "/" << entry_a->name
@@ -225,16 +225,16 @@
       EmitDiffLine(apk_a->GetSource(), str_stream.str());
       diff = true;
     } else {
-      if (IsSymbolVisibilityDifferent(type_a->symbol_status, type_b->symbol_status)) {
+      if (type_a->visibility_level != type_b->visibility_level) {
         std::stringstream str_stream;
         str_stream << pkg_a->name << ":" << type_a->type << " has different visibility (";
-        if (type_b->symbol_status.state == SymbolState::kPublic) {
+        if (type_b->visibility_level == Visibility::Level::kPublic) {
           str_stream << "PUBLIC";
         } else {
           str_stream << "PRIVATE";
         }
         str_stream << " vs ";
-        if (type_a->symbol_status.state == SymbolState::kPublic) {
+        if (type_a->visibility_level == Visibility::Level::kPublic) {
           str_stream << "PUBLIC";
         } else {
           str_stream << "PRIVATE";
@@ -242,7 +242,8 @@
         str_stream << ")";
         EmitDiffLine(apk_b->GetSource(), str_stream.str());
         diff = true;
-      } else if (IsIdDiff(type_a->symbol_status, type_a->id, type_b->symbol_status, type_b->id)) {
+      } else if (IsIdDiff(type_a->visibility_level, type_a->id, type_b->visibility_level,
+                          type_b->id)) {
         std::stringstream str_stream;
         str_stream << pkg_a->name << ":" << type_a->type << " has different public ID (";
         if (type_b->id) {
diff --git a/tools/aapt2/cmd/Dump.cpp b/tools/aapt2/cmd/Dump.cpp
index bc7f5a8..3d2fb55 100644
--- a/tools/aapt2/cmd/Dump.cpp
+++ b/tools/aapt2/cmd/Dump.cpp
@@ -69,15 +69,13 @@
   printer->Println(StringPrintf("Data:     offset=%" PRIi64 " length=%zd", offset, len));
 }
 
-static bool TryDumpFile(IAaptContext* context, const std::string& file_path) {
+static bool TryDumpFile(IAaptContext* context, const std::string& file_path,
+                        const DebugPrintTableOptions& print_options) {
   // Use a smaller buffer so that there is less latency for dumping to stdout.
   constexpr size_t kStdOutBufferSize = 1024u;
   io::FileOutputStream fout(STDOUT_FILENO, kStdOutBufferSize);
   Printer printer(&fout);
 
-  DebugPrintTableOptions print_options;
-  print_options.show_sources = true;
-
   std::string err;
   std::unique_ptr<io::ZipFileCollection> zip = io::ZipFileCollection::Create(file_path, &err);
   if (zip) {
@@ -244,7 +242,12 @@
 // Entry point for dump command.
 int Dump(const std::vector<StringPiece>& args) {
   bool verbose = false;
-  Flags flags = Flags().OptionalSwitch("-v", "increase verbosity of output", &verbose);
+  bool no_values = false;
+  Flags flags = Flags()
+                    .OptionalSwitch("--no-values",
+                                    "Suppresses output of values when displaying resource tables.",
+                                    &no_values)
+                    .OptionalSwitch("-v", "increase verbosity of output", &verbose);
   if (!flags.Parse("aapt2 dump", args, &std::cerr)) {
     return 1;
   }
@@ -252,8 +255,11 @@
   DumpContext context;
   context.SetVerbose(verbose);
 
+  DebugPrintTableOptions dump_table_options;
+  dump_table_options.show_sources = true;
+  dump_table_options.show_values = !no_values;
   for (const std::string& arg : flags.GetArgs()) {
-    if (!TryDumpFile(&context, arg)) {
+    if (!TryDumpFile(&context, arg, dump_table_options)) {
       return 1;
     }
   }
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index d782de5..72e07dc 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -631,9 +631,9 @@
 
               dst_path =
                   ResourceUtils::BuildResourceFileName(doc->file, context_->GetNameMangler());
-              bool result = table->AddFileReferenceAllowMangled(doc->file.name, doc->file.config,
-                                                                doc->file.source, dst_path, nullptr,
-                                                                context_->GetDiagnostics());
+              bool result =
+                  table->AddFileReferenceMangled(doc->file.name, doc->file.config, doc->file.source,
+                                                 dst_path, nullptr, context_->GetDiagnostics());
               if (!result) {
                 return false;
               }
@@ -1343,9 +1343,9 @@
 
       std::unique_ptr<Id> id = util::make_unique<Id>();
       id->SetSource(source.WithLine(exported_symbol.line));
-      bool result = final_table_.AddResourceAllowMangled(
-          res_name, ConfigDescription::DefaultConfig(), std::string(), std::move(id),
-          context_->GetDiagnostics());
+      bool result =
+          final_table_.AddResourceMangled(res_name, ConfigDescription::DefaultConfig(),
+                                          std::string(), std::move(id), context_->GetDiagnostics());
       if (!result) {
         return false;
       }
@@ -2121,6 +2121,9 @@
                         &options.manifest_fixer_options.rename_instrumentation_target_package)
           .OptionalFlagList("-0", "File extensions not to compress.",
                             &options.extensions_to_not_compress)
+          .OptionalSwitch("--warn-manifest-validation",
+                          "Treat manifest validation errors as warnings.",
+                          &options.manifest_fixer_options.warn_validation)
           .OptionalFlagList("--split",
                             "Split resources matching a set of configs out to a Split APK.\n"
                             "Syntax: path/to/output.apk:<config>[,<config>[...]].\n"
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/format/binary/BinaryResourceParser.cpp b/tools/aapt2/format/binary/BinaryResourceParser.cpp
index 5078678..8d079ff 100644
--- a/tools/aapt2/format/binary/BinaryResourceParser.cpp
+++ b/tools/aapt2/format/binary/BinaryResourceParser.cpp
@@ -223,7 +223,7 @@
         break;
 
       case android::RES_TABLE_TYPE_SPEC_TYPE:
-        if (!ParseTypeSpec(parser.chunk())) {
+        if (!ParseTypeSpec(package, parser.chunk())) {
           return false;
         }
         break;
@@ -260,7 +260,8 @@
   return true;
 }
 
-bool BinaryResourceParser::ParseTypeSpec(const ResChunk_header* chunk) {
+bool BinaryResourceParser::ParseTypeSpec(const ResourceTablePackage* package,
+                                         const ResChunk_header* chunk) {
   if (type_pool_.getError() != NO_ERROR) {
     diag_->Error(DiagMessage(source_) << "missing type string pool");
     return false;
@@ -276,6 +277,34 @@
     diag_->Error(DiagMessage(source_) << "ResTable_typeSpec has invalid id: " << type_spec->id);
     return false;
   }
+
+  // The data portion of this chunk contains entry_count 32bit entries,
+  // each one representing a set of flags.
+  const size_t entry_count = dtohl(type_spec->entryCount);
+
+  // There can only be 2^16 entries in a type, because that is the ID
+  // space for entries (EEEE) in the resource ID 0xPPTTEEEE.
+  if (entry_count > std::numeric_limits<uint16_t>::max()) {
+    diag_->Error(DiagMessage(source_)
+                 << "ResTable_typeSpec has too many entries (" << entry_count << ")");
+    return false;
+  }
+
+  const size_t data_size = util::DeviceToHost32(type_spec->header.size) -
+                           util::DeviceToHost16(type_spec->header.headerSize);
+  if (entry_count * sizeof(uint32_t) > data_size) {
+    diag_->Error(DiagMessage(source_) << "ResTable_typeSpec too small to hold entries.");
+    return false;
+  }
+
+  // Record the type_spec_flags for later. We don't know resource names yet, and we need those
+  // to mark resources as overlayable.
+  const uint32_t* type_spec_flags = reinterpret_cast<const uint32_t*>(
+      reinterpret_cast<uintptr_t>(type_spec) + util::DeviceToHost16(type_spec->header.headerSize));
+  for (size_t i = 0; i < entry_count; i++) {
+    ResourceId id(package->id.value_or_default(0x0), type_spec->id, static_cast<size_t>(i));
+    entry_type_spec_flags_[id] = util::DeviceToHost32(type_spec_flags[i]);
+  }
   return true;
 }
 
@@ -346,18 +375,34 @@
       return false;
     }
 
-    if (!table_->AddResourceAllowMangled(name, res_id, config, {}, std::move(resource_value),
-                                         diag_)) {
+    if (!table_->AddResourceWithIdMangled(name, res_id, config, {}, std::move(resource_value),
+                                          diag_)) {
       return false;
     }
 
-    if ((entry->flags & ResTable_entry::FLAG_PUBLIC) != 0) {
-      Symbol symbol;
-      symbol.state = SymbolState::kPublic;
-      symbol.source = source_.WithLine(0);
-      if (!table_->SetSymbolStateAllowMangled(name, res_id, symbol, diag_)) {
-        return false;
+    const uint32_t type_spec_flags = entry_type_spec_flags_[res_id];
+    if ((entry->flags & ResTable_entry::FLAG_PUBLIC) != 0 ||
+        (type_spec_flags & ResTable_typeSpec::SPEC_OVERLAYABLE) != 0) {
+      if (entry->flags & ResTable_entry::FLAG_PUBLIC) {
+        Visibility visibility;
+        visibility.level = Visibility::Level::kPublic;
+        visibility.source = source_.WithLine(0);
+        if (!table_->SetVisibilityWithIdMangled(name, visibility, res_id, diag_)) {
+          return false;
+        }
       }
+
+      if (type_spec_flags & ResTable_typeSpec::SPEC_OVERLAYABLE) {
+        Overlayable overlayable;
+        overlayable.source = source_.WithLine(0);
+        if (!table_->SetOverlayableMangled(name, overlayable, diag_)) {
+          return false;
+        }
+      }
+
+      // Erase the ID from the map once processed, so that we don't mark the same symbol more than
+      // once.
+      entry_type_spec_flags_.erase(res_id);
     }
 
     // Add this resource name->id mapping to the index so
diff --git a/tools/aapt2/format/binary/BinaryResourceParser.h b/tools/aapt2/format/binary/BinaryResourceParser.h
index 052f806..a1f9f83 100644
--- a/tools/aapt2/format/binary/BinaryResourceParser.h
+++ b/tools/aapt2/format/binary/BinaryResourceParser.h
@@ -50,7 +50,7 @@
 
   bool ParseTable(const android::ResChunk_header* chunk);
   bool ParsePackage(const android::ResChunk_header* chunk);
-  bool ParseTypeSpec(const android::ResChunk_header* chunk);
+  bool ParseTypeSpec(const ResourceTablePackage* package, const android::ResChunk_header* chunk);
   bool ParseType(const ResourceTablePackage* package, const android::ResChunk_header* chunk);
   bool ParseLibrary(const android::ResChunk_header* chunk);
 
@@ -105,6 +105,9 @@
   // A mapping of resource ID to resource name. When we finish parsing
   // we use this to convert all resource IDs to symbolic references.
   std::map<ResourceId, ResourceName> id_index_;
+
+  // A mapping of resource ID to type spec flags.
+  std::unordered_map<ResourceId, uint32_t> entry_type_spec_flags_;
 };
 
 }  // namespace aapt
diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp
index a3034df..24a4112 100644
--- a/tools/aapt2/format/binary/TableFlattener.cpp
+++ b/tools/aapt2/format/binary/TableFlattener.cpp
@@ -283,7 +283,7 @@
 
     T* result = buffer->NextBlock<T>();
     ResTable_entry* out_entry = (ResTable_entry*)result;
-    if (entry->entry->symbol_status.state == SymbolState::kPublic) {
+    if (entry->entry->visibility.level == Visibility::Level::kPublic) {
       out_entry->flags |= ResTable_entry::FLAG_PUBLIC;
     }
 
@@ -443,10 +443,15 @@
 
       // Populate the config masks for this entry.
 
-      if (entry->symbol_status.state == SymbolState::kPublic) {
+      if (entry->visibility.level == Visibility::Level::kPublic) {
         config_masks[entry->id.value()] |= util::HostToDevice32(ResTable_typeSpec::SPEC_PUBLIC);
       }
 
+      if (entry->overlayable) {
+        config_masks[entry->id.value()] |=
+            util::HostToDevice32(ResTable_typeSpec::SPEC_OVERLAYABLE);
+      }
+
       const size_t config_count = entry->values.size();
       for (size_t i = 0; i < config_count; i++) {
         const ConfigDescription& config = entry->values[i]->config;
diff --git a/tools/aapt2/format/binary/TableFlattener_test.cpp b/tools/aapt2/format/binary/TableFlattener_test.cpp
index f0b80d2..51ccdc7 100644
--- a/tools/aapt2/format/binary/TableFlattener_test.cpp
+++ b/tools/aapt2/format/binary/TableFlattener_test.cpp
@@ -26,6 +26,7 @@
 
 using namespace android;
 
+using ::testing::Gt;
 using ::testing::IsNull;
 using ::testing::NotNull;
 
@@ -250,15 +251,15 @@
     const ResourceId resid(context->GetPackageId(), 0x02, static_cast<uint16_t>(i));
     const auto value =
         util::make_unique<BinaryPrimitive>(Res_value::TYPE_INT_DEC, static_cast<uint32_t>(i));
-    CHECK(table->AddResource(name, resid, ConfigDescription::DefaultConfig(), "",
-                             std::unique_ptr<Value>(value->Clone(nullptr)),
-                             context->GetDiagnostics()));
+    CHECK(table->AddResourceWithId(name, resid, ConfigDescription::DefaultConfig(), "",
+                                   std::unique_ptr<Value>(value->Clone(nullptr)),
+                                   context->GetDiagnostics()));
 
     // Every few entries, write out a sparse_config value. This will give us the desired load.
     if (i % stride == 0) {
-      CHECK(table->AddResource(name, resid, sparse_config, "",
-                               std::unique_ptr<Value>(value->Clone(nullptr)),
-                               context->GetDiagnostics()));
+      CHECK(table->AddResourceWithId(name, resid, sparse_config, "",
+                                     std::unique_ptr<Value>(value->Clone(nullptr)),
+                                     context->GetDiagnostics()));
     }
   }
   return table;
@@ -568,4 +569,25 @@
                      ResourceId(0x7f050000), {}, Res_value::TYPE_STRING, (uint32_t)idx, 0u));
 }
 
+TEST_F(TableFlattenerTest, FlattenOverlayable) {
+  std::unique_ptr<ResourceTable> table =
+      test::ResourceTableBuilder()
+          .SetPackageId("com.app.test", 0x7f)
+          .AddSimple("com.app.test:integer/overlayable", ResourceId(0x7f020000))
+          .Build();
+
+  ASSERT_TRUE(table->SetOverlayable(test::ParseNameOrDie("com.app.test:integer/overlayable"),
+                                    Overlayable{}, test::GetDiagnostics()));
+
+  ResTable res_table;
+  ASSERT_TRUE(Flatten(context_.get(), {}, table.get(), &res_table));
+
+  const StringPiece16 overlayable_name(u"com.app.test:integer/overlayable");
+  uint32_t spec_flags = 0u;
+  ASSERT_THAT(res_table.identifierForName(overlayable_name.data(), overlayable_name.size(), nullptr,
+                                          0u, nullptr, 0u, &spec_flags),
+              Gt(0u));
+  EXPECT_TRUE(spec_flags & android::ResTable_typeSpec::SPEC_OVERLAYABLE);
+}
+
 }  // namespace aapt
diff --git a/tools/aapt2/format/proto/ProtoDeserialize.cpp b/tools/aapt2/format/proto/ProtoDeserialize.cpp
index 0f0bce8..3d6975d 100644
--- a/tools/aapt2/format/proto/ProtoDeserialize.cpp
+++ b/tools/aapt2/format/proto/ProtoDeserialize.cpp
@@ -358,16 +358,16 @@
   out_source->line = static_cast<size_t>(pb_source.position().line_number());
 }
 
-static SymbolState DeserializeVisibilityFromPb(const pb::SymbolStatus_Visibility& pb_visibility) {
-  switch (pb_visibility) {
-    case pb::SymbolStatus_Visibility_PRIVATE:
-      return SymbolState::kPrivate;
-    case pb::SymbolStatus_Visibility_PUBLIC:
-      return SymbolState::kPublic;
+static Visibility::Level DeserializeVisibilityFromPb(const pb::Visibility::Level& pb_level) {
+  switch (pb_level) {
+    case pb::Visibility::PRIVATE:
+      return Visibility::Level::kPrivate;
+    case pb::Visibility::PUBLIC:
+      return Visibility::Level::kPublic;
     default:
       break;
   }
-  return SymbolState::kUndefined;
+  return Visibility::Level::kUndefined;
 }
 
 static bool DeserializePackageFromPb(const pb::Package& pb_package, const ResStringPool& src_pool,
@@ -402,28 +402,48 @@
       }
 
       // Deserialize the symbol status (public/private with source and comments).
-      if (pb_entry.has_symbol_status()) {
-        const pb::SymbolStatus& pb_status = pb_entry.symbol_status();
-        if (pb_status.has_source()) {
-          DeserializeSourceFromPb(pb_status.source(), src_pool, &entry->symbol_status.source);
+      if (pb_entry.has_visibility()) {
+        const pb::Visibility& pb_visibility = pb_entry.visibility();
+        if (pb_visibility.has_source()) {
+          DeserializeSourceFromPb(pb_visibility.source(), src_pool, &entry->visibility.source);
         }
+        entry->visibility.comment = pb_visibility.comment();
 
-        entry->symbol_status.comment = pb_status.comment();
-        entry->symbol_status.allow_new = pb_status.allow_new();
-
-        const SymbolState visibility = DeserializeVisibilityFromPb(pb_status.visibility());
-        entry->symbol_status.state = visibility;
-        if (visibility == SymbolState::kPublic) {
+        const Visibility::Level level = DeserializeVisibilityFromPb(pb_visibility.level());
+        entry->visibility.level = level;
+        if (level == Visibility::Level::kPublic) {
           // Propagate the public visibility up to the Type.
-          type->symbol_status.state = SymbolState::kPublic;
-        } else if (visibility == SymbolState::kPrivate) {
+          type->visibility_level = Visibility::Level::kPublic;
+        } else if (level == Visibility::Level::kPrivate) {
           // Only propagate if no previous state was assigned.
-          if (type->symbol_status.state == SymbolState::kUndefined) {
-            type->symbol_status.state = SymbolState::kPrivate;
+          if (type->visibility_level == Visibility::Level::kUndefined) {
+            type->visibility_level = Visibility::Level::kPrivate;
           }
         }
       }
 
+      if (pb_entry.has_allow_new()) {
+        const pb::AllowNew& pb_allow_new = pb_entry.allow_new();
+
+        AllowNew allow_new;
+        if (pb_allow_new.has_source()) {
+          DeserializeSourceFromPb(pb_allow_new.source(), src_pool, &allow_new.source);
+        }
+        allow_new.comment = pb_allow_new.comment();
+        entry->allow_new = std::move(allow_new);
+      }
+
+      if (pb_entry.has_overlayable()) {
+        const pb::Overlayable& pb_overlayable = pb_entry.overlayable();
+
+        Overlayable overlayable;
+        if (pb_overlayable.has_source()) {
+          DeserializeSourceFromPb(pb_overlayable.source(), src_pool, &overlayable.source);
+        }
+        overlayable.comment = pb_overlayable.comment();
+        entry->overlayable = std::move(overlayable);
+      }
+
       ResourceId resid(pb_package.package_id().id(), pb_type.type_id().id(),
                        pb_entry.entry_id().id());
       if (resid.is_valid()) {
diff --git a/tools/aapt2/format/proto/ProtoSerialize.cpp b/tools/aapt2/format/proto/ProtoSerialize.cpp
index 97ce01a..78f1281 100644
--- a/tools/aapt2/format/proto/ProtoSerialize.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize.cpp
@@ -43,16 +43,16 @@
   }
 }
 
-static pb::SymbolStatus_Visibility SerializeVisibilityToPb(SymbolState state) {
+static pb::Visibility::Level SerializeVisibilityToPb(Visibility::Level state) {
   switch (state) {
-    case SymbolState::kPrivate:
-      return pb::SymbolStatus_Visibility_PRIVATE;
-    case SymbolState::kPublic:
-      return pb::SymbolStatus_Visibility_PUBLIC;
+    case Visibility::Level::kPrivate:
+      return pb::Visibility::PRIVATE;
+    case Visibility::Level::kPublic:
+      return pb::Visibility::PUBLIC;
     default:
       break;
   }
-  return pb::SymbolStatus_Visibility_UNKNOWN;
+  return pb::Visibility::UNKNOWN;
 }
 
 void SerializeConfig(const ConfigDescription& config, pb::Configuration* out_pb_config) {
@@ -293,12 +293,26 @@
         }
         pb_entry->set_name(entry->name);
 
-        // Write the SymbolStatus struct.
-        pb::SymbolStatus* pb_status = pb_entry->mutable_symbol_status();
-        pb_status->set_visibility(SerializeVisibilityToPb(entry->symbol_status.state));
-        SerializeSourceToPb(entry->symbol_status.source, &source_pool, pb_status->mutable_source());
-        pb_status->set_comment(entry->symbol_status.comment);
-        pb_status->set_allow_new(entry->symbol_status.allow_new);
+        // Write the Visibility struct.
+        pb::Visibility* pb_visibility = pb_entry->mutable_visibility();
+        pb_visibility->set_level(SerializeVisibilityToPb(entry->visibility.level));
+        SerializeSourceToPb(entry->visibility.source, &source_pool,
+                            pb_visibility->mutable_source());
+        pb_visibility->set_comment(entry->visibility.comment);
+
+        if (entry->allow_new) {
+          pb::AllowNew* pb_allow_new = pb_entry->mutable_allow_new();
+          SerializeSourceToPb(entry->allow_new.value().source, &source_pool,
+                              pb_allow_new->mutable_source());
+          pb_allow_new->set_comment(entry->allow_new.value().comment);
+        }
+
+        if (entry->overlayable) {
+          pb::Overlayable* pb_overlayable = pb_entry->mutable_overlayable();
+          SerializeSourceToPb(entry->overlayable.value().source, &source_pool,
+                              pb_overlayable->mutable_source());
+          pb_overlayable->set_comment(entry->overlayable.value().comment);
+        }
 
         for (const std::unique_ptr<ResourceConfigValue>& config_value : entry->values) {
           pb::ConfigValue* pb_config_value = pb_entry->add_config_value();
diff --git a/tools/aapt2/format/proto/ProtoSerialize_test.cpp b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
index 9649a4d..d7f83fd 100644
--- a/tools/aapt2/format/proto/ProtoSerialize_test.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize_test.cpp
@@ -44,14 +44,15 @@
           .AddReference("com.app.a:layout/other", ResourceId(0x7f020001), "com.app.a:layout/main")
           .AddString("com.app.a:string/text", {}, "hi")
           .AddValue("com.app.a:id/foo", {}, util::make_unique<Id>())
-          .SetSymbolState("com.app.a:bool/foo", {}, SymbolState::kUndefined, true /*allow_new*/)
+          .SetSymbolState("com.app.a:bool/foo", {}, Visibility::Level::kUndefined,
+                          true /*allow_new*/)
           .Build();
 
-  Symbol public_symbol;
-  public_symbol.state = SymbolState::kPublic;
-  ASSERT_TRUE(table->SetSymbolState(test::ParseNameOrDie("com.app.a:layout/main"),
-                                    ResourceId(0x7f020000), public_symbol,
-                                    context->GetDiagnostics()));
+  Visibility public_symbol;
+  public_symbol.level = Visibility::Level::kPublic;
+  ASSERT_TRUE(table->SetVisibilityWithId(test::ParseNameOrDie("com.app.a:layout/main"),
+                                         public_symbol, ResourceId(0x7f020000),
+                                         context->GetDiagnostics()));
 
   Id* id = test::GetValue<Id>(table.get(), "com.app.a:id/foo");
   ASSERT_THAT(id, NotNull());
@@ -89,6 +90,10 @@
       test::ParseNameOrDie("com.app.a:layout/abc"), ConfigDescription::DefaultConfig(), {},
       util::make_unique<Reference>(expected_ref), context->GetDiagnostics()));
 
+  // Make an overlayable resource.
+  ASSERT_TRUE(table->SetOverlayable(test::ParseNameOrDie("com.app.a:integer/overlayable"),
+                                    Overlayable{}, test::GetDiagnostics()));
+
   pb::ResourceTable pb_table;
   SerializeTableToPb(*table, &pb_table);
 
@@ -110,13 +115,13 @@
       new_table.FindResource(test::ParseNameOrDie("com.app.a:layout/main"));
   ASSERT_TRUE(result);
 
-  EXPECT_THAT(result.value().type->symbol_status.state, Eq(SymbolState::kPublic));
-  EXPECT_THAT(result.value().entry->symbol_status.state, Eq(SymbolState::kPublic));
+  EXPECT_THAT(result.value().type->visibility_level, Eq(Visibility::Level::kPublic));
+  EXPECT_THAT(result.value().entry->visibility.level, Eq(Visibility::Level::kPublic));
 
   result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/foo"));
   ASSERT_TRUE(result);
-  EXPECT_THAT(result.value().entry->symbol_status.state, Eq(SymbolState::kUndefined));
-  EXPECT_TRUE(result.value().entry->symbol_status.allow_new);
+  EXPECT_THAT(result.value().entry->visibility.level, Eq(Visibility::Level::kUndefined));
+  EXPECT_TRUE(result.value().entry->allow_new);
 
   // Find the product-dependent values
   BinaryPrimitive* prim = test::GetValueForConfigAndProduct<BinaryPrimitive>(
@@ -148,6 +153,12 @@
   EXPECT_THAT(*actual_styled_str->value->spans[0].name, Eq("b"));
   EXPECT_THAT(actual_styled_str->value->spans[0].first_char, Eq(0u));
   EXPECT_THAT(actual_styled_str->value->spans[0].last_char, Eq(4u));
+
+  Maybe<ResourceTable::SearchResult> search_result =
+      new_table.FindResource(test::ParseNameOrDie("com.app.a:integer/overlayable"));
+  ASSERT_TRUE(search_result);
+  ASSERT_THAT(search_result.value().entry, NotNull());
+  EXPECT_TRUE(search_result.value().entry->overlayable);
 }
 
 TEST(ProtoSerializeTest, SerializeAndDeserializeXml) {
diff --git a/tools/aapt2/java/JavaClassGenerator.cpp b/tools/aapt2/java/JavaClassGenerator.cpp
index 8c8c254..6b07b1e 100644
--- a/tools/aapt2/java/JavaClassGenerator.cpp
+++ b/tools/aapt2/java/JavaClassGenerator.cpp
@@ -191,14 +191,14 @@
                                        const JavaClassGeneratorOptions& options)
     : context_(context), table_(table), options_(options) {}
 
-bool JavaClassGenerator::SkipSymbol(SymbolState state) {
+bool JavaClassGenerator::SkipSymbol(Visibility::Level level) {
   switch (options_.types) {
     case JavaClassGeneratorOptions::SymbolTypes::kAll:
       return false;
     case JavaClassGeneratorOptions::SymbolTypes::kPublicPrivate:
-      return state == SymbolState::kUndefined;
+      return level == Visibility::Level::kUndefined;
     case JavaClassGeneratorOptions::SymbolTypes::kPublic:
-      return state != SymbolState::kPublic;
+      return level != Visibility::Level::kPublic;
   }
   return true;
 }
@@ -444,8 +444,8 @@
     AnnotationProcessor* processor = resource_member->GetCommentBuilder();
 
     // Add the comments from any <public> tags.
-    if (entry.symbol_status.state != SymbolState::kUndefined) {
-      processor->AppendComment(entry.symbol_status.comment);
+    if (entry.visibility.level != Visibility::Level::kUndefined) {
+      processor->AppendComment(entry.visibility.comment);
     }
 
     // Add the comments from all configurations of this entry.
@@ -484,7 +484,7 @@
 Maybe<std::string> JavaClassGenerator::UnmangleResource(const StringPiece& package_name,
                                                         const StringPiece& package_name_to_generate,
                                                         const ResourceEntry& entry) {
-  if (SkipSymbol(entry.symbol_status.state)) {
+  if (SkipSymbol(entry.visibility.level)) {
     return {};
   }
 
diff --git a/tools/aapt2/java/JavaClassGenerator.h b/tools/aapt2/java/JavaClassGenerator.h
index 4992f07..853120b 100644
--- a/tools/aapt2/java/JavaClassGenerator.h
+++ b/tools/aapt2/java/JavaClassGenerator.h
@@ -82,7 +82,7 @@
   static std::string TransformToFieldName(const android::StringPiece& symbol);
 
  private:
-  bool SkipSymbol(SymbolState state);
+  bool SkipSymbol(Visibility::Level state);
   bool SkipSymbol(const Maybe<SymbolTable::Symbol>& symbol);
 
   // Returns the unmangled resource entry name if the unmangled package is the same as
diff --git a/tools/aapt2/java/JavaClassGenerator_test.cpp b/tools/aapt2/java/JavaClassGenerator_test.cpp
index 02f4cb1..5beb594 100644
--- a/tools/aapt2/java/JavaClassGenerator_test.cpp
+++ b/tools/aapt2/java/JavaClassGenerator_test.cpp
@@ -139,8 +139,8 @@
           .AddSimple("android:id/one", ResourceId(0x01020000))
           .AddSimple("android:id/two", ResourceId(0x01020001))
           .AddSimple("android:id/three", ResourceId(0x01020002))
-          .SetSymbolState("android:id/one", ResourceId(0x01020000), SymbolState::kPublic)
-          .SetSymbolState("android:id/two", ResourceId(0x01020001), SymbolState::kPrivate)
+          .SetSymbolState("android:id/one", ResourceId(0x01020000), Visibility::Level::kPublic)
+          .SetSymbolState("android:id/two", ResourceId(0x01020001), Visibility::Level::kPrivate)
           .Build();
 
   std::unique_ptr<IAaptContext> context =
diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp
index a68df1d..da05dc3 100644
--- a/tools/aapt2/link/ManifestFixer.cpp
+++ b/tools/aapt2/link/ManifestFixer.cpp
@@ -430,7 +430,10 @@
     return false;
   }
 
-  if (!executor.Execute(xml::XmlActionExecutorPolicy::kWhitelist, context->GetDiagnostics(), doc)) {
+  xml::XmlActionExecutorPolicy policy = options_.warn_validation
+                                            ? xml::XmlActionExecutorPolicy::kWhitelistWarning
+                                            : xml::XmlActionExecutorPolicy::kWhitelist;
+  if (!executor.Execute(policy, context->GetDiagnostics(), doc)) {
     return false;
   }
 
diff --git a/tools/aapt2/link/ManifestFixer.h b/tools/aapt2/link/ManifestFixer.h
index f5715f6..0caa52e 100644
--- a/tools/aapt2/link/ManifestFixer.h
+++ b/tools/aapt2/link/ManifestFixer.h
@@ -57,6 +57,11 @@
   // The version codename of the framework being compiled against to set for
   // 'android:compileSdkVersionCodename' in the <manifest> tag.
   Maybe<std::string> compile_sdk_version_codename;
+
+  // Wether validation errors should be treated only as warnings. If this is 'true', then an
+  // incorrect node will not result in an error, but only as a warning, and the parsing will
+  // continue.
+  bool warn_validation = false;
 };
 
 // Verifies that the manifest is correctly formed and inserts defaults where specified with
diff --git a/tools/aapt2/link/ManifestFixer_test.cpp b/tools/aapt2/link/ManifestFixer_test.cpp
index 1320dcd..c6f895b 100644
--- a/tools/aapt2/link/ManifestFixer_test.cpp
+++ b/tools/aapt2/link/ManifestFixer_test.cpp
@@ -112,7 +112,9 @@
 }
 
 TEST_F(ManifestFixerTest, UseDefaultSdkVersionsIfNonePresent) {
-  ManifestFixerOptions options = {std::string("8"), std::string("22")};
+  ManifestFixerOptions options;
+  options.min_sdk_version_default = std::string("8");
+  options.target_sdk_version_default = std::string("22");
 
   std::unique_ptr<xml::XmlResource> doc = VerifyWithOptions(R"EOF(
       <manifest xmlns:android="http://schemas.android.com/apk/res/android"
@@ -193,7 +195,9 @@
 }
 
 TEST_F(ManifestFixerTest, UsesSdkMustComeBeforeApplication) {
-  ManifestFixerOptions options = {std::string("8"), std::string("22")};
+  ManifestFixerOptions options;
+  options.min_sdk_version_default = std::string("8");
+  options.target_sdk_version_default = std::string("22");
   std::unique_ptr<xml::XmlResource> doc = VerifyWithOptions(R"EOF(
           <manifest xmlns:android="http://schemas.android.com/apk/res/android"
                     package="android">
@@ -467,4 +471,27 @@
   EXPECT_THAT(attr->value, StrEq("P"));
 }
 
+TEST_F(ManifestFixerTest, UnexpectedElementsInManifest) {
+  std::string input = R"(
+      <manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android">
+        <beep/>
+      </manifest>)";
+  ManifestFixerOptions options;
+  options.warn_validation = true;
+
+  // Unexpected element should result in a warning if the flag is set to 'true'.
+  std::unique_ptr<xml::XmlResource> manifest = VerifyWithOptions(input, options);
+  ASSERT_THAT(manifest, NotNull());
+
+  // Unexpected element should result in an error if the flag is set to 'false'.
+  options.warn_validation = false;
+  manifest = VerifyWithOptions(input, options);
+  ASSERT_THAT(manifest, IsNull());
+
+  // By default the flag should be set to 'false'.
+  manifest = Verify(input);
+  ASSERT_THAT(manifest, IsNull());
+}
+
 }  // namespace aapt
diff --git a/tools/aapt2/link/PrivateAttributeMover.cpp b/tools/aapt2/link/PrivateAttributeMover.cpp
index eee4b60..675b02a 100644
--- a/tools/aapt2/link/PrivateAttributeMover.cpp
+++ b/tools/aapt2/link/PrivateAttributeMover.cpp
@@ -62,7 +62,7 @@
       continue;
     }
 
-    if (type->symbol_status.state != SymbolState::kPublic) {
+    if (type->visibility_level != Visibility::Level::kPublic) {
       // No public attributes, so we can safely leave these private attributes
       // where they are.
       continue;
@@ -72,7 +72,7 @@
 
     move_if(type->entries, std::back_inserter(private_attr_entries),
             [](const std::unique_ptr<ResourceEntry>& entry) -> bool {
-              return entry->symbol_status.state != SymbolState::kPublic;
+              return entry->visibility.level != Visibility::Level::kPublic;
             });
 
     if (private_attr_entries.empty()) {
diff --git a/tools/aapt2/link/PrivateAttributeMover_test.cpp b/tools/aapt2/link/PrivateAttributeMover_test.cpp
index 7fcf6e7..168234b 100644
--- a/tools/aapt2/link/PrivateAttributeMover_test.cpp
+++ b/tools/aapt2/link/PrivateAttributeMover_test.cpp
@@ -30,9 +30,9 @@
           .AddSimple("android:attr/publicB")
           .AddSimple("android:attr/privateB")
           .SetSymbolState("android:attr/publicA", ResourceId(0x01010000),
-                          SymbolState::kPublic)
+                          Visibility::Level::kPublic)
           .SetSymbolState("android:attr/publicB", ResourceId(0x01010000),
-                          SymbolState::kPublic)
+                          Visibility::Level::kPublic)
           .Build();
 
   PrivateAttributeMover mover;
@@ -81,7 +81,7 @@
   std::unique_ptr<ResourceTable> table =
       test::ResourceTableBuilder()
           .AddSimple("android:attr/pub")
-          .SetSymbolState("android:attr/pub", ResourceId(0x01010000), SymbolState::kPublic)
+          .SetSymbolState("android:attr/pub", ResourceId(0x01010000), Visibility::Level::kPublic)
           .Build();
 
   ResourceTablePackage* package = table->FindPackage("android");
diff --git a/tools/aapt2/link/ReferenceLinker.cpp b/tools/aapt2/link/ReferenceLinker.cpp
index ad7d8b6..b8f8804 100644
--- a/tools/aapt2/link/ReferenceLinker.cpp
+++ b/tools/aapt2/link/ReferenceLinker.cpp
@@ -363,8 +363,8 @@
         NameMangler::Unmangle(&name.entry, &name.package);
 
         // Symbol state information may be lost if there is no value for the resource.
-        if (entry->symbol_status.state != SymbolState::kUndefined && entry->values.empty()) {
-          context->GetDiagnostics()->Error(DiagMessage(entry->symbol_status.source)
+        if (entry->visibility.level != Visibility::Level::kUndefined && entry->values.empty()) {
+          context->GetDiagnostics()->Error(DiagMessage(entry->visibility.source)
                                            << "no definition for declared symbol '" << name << "'");
           error = true;
         }
diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp
index 58d0607..e819f51 100644
--- a/tools/aapt2/link/TableMerger.cpp
+++ b/tools/aapt2/link/TableMerger.cpp
@@ -83,44 +83,58 @@
 
 static bool MergeType(IAaptContext* context, const Source& src, ResourceTableType* dst_type,
                       ResourceTableType* src_type) {
-  if (dst_type->symbol_status.state < src_type->symbol_status.state) {
+  if (src_type->visibility_level > dst_type->visibility_level) {
     // The incoming type's visibility is stronger, so we should override the visibility.
-    if (src_type->symbol_status.state == SymbolState::kPublic) {
+    if (src_type->visibility_level == Visibility::Level::kPublic) {
       // Only copy the ID if the source is public, or else the ID is meaningless.
       dst_type->id = src_type->id;
     }
-    dst_type->symbol_status = std::move(src_type->symbol_status);
-  } else if (dst_type->symbol_status.state == SymbolState::kPublic &&
-             src_type->symbol_status.state == SymbolState::kPublic &&
-             dst_type->id && src_type->id &&
-             dst_type->id.value() != src_type->id.value()) {
+    dst_type->visibility_level = src_type->visibility_level;
+  } else if (dst_type->visibility_level == Visibility::Level::kPublic &&
+             src_type->visibility_level == Visibility::Level::kPublic && dst_type->id &&
+             src_type->id && dst_type->id.value() != src_type->id.value()) {
     // Both types are public and have different IDs.
-    context->GetDiagnostics()->Error(DiagMessage(src)
-                                     << "cannot merge type '" << src_type->type
-                                     << "': conflicting public IDs");
+    context->GetDiagnostics()->Error(DiagMessage(src) << "cannot merge type '" << src_type->type
+                                                      << "': conflicting public IDs");
     return false;
   }
   return true;
 }
 
-static bool MergeEntry(IAaptContext* context, const Source& src, ResourceEntry* dst_entry,
-                       ResourceEntry* src_entry) {
-  if (dst_entry->symbol_status.state < src_entry->symbol_status.state) {
-    // The incoming type's visibility is stronger, so we should override the visibility.
-    if (src_entry->symbol_status.state == SymbolState::kPublic) {
-      // Only copy the ID if the source is public, or else the ID is meaningless.
+static bool MergeEntry(IAaptContext* context, const Source& src, bool overlay,
+                       ResourceEntry* dst_entry, ResourceEntry* src_entry) {
+  // Copy over the strongest visibility.
+  if (src_entry->visibility.level > dst_entry->visibility.level) {
+    // Only copy the ID if the source is public, or else the ID is meaningless.
+    if (src_entry->visibility.level == Visibility::Level::kPublic) {
       dst_entry->id = src_entry->id;
     }
-    dst_entry->symbol_status = std::move(src_entry->symbol_status);
-  } else if (src_entry->symbol_status.state == SymbolState::kPublic &&
-             dst_entry->symbol_status.state == SymbolState::kPublic &&
-             dst_entry->id && src_entry->id &&
-             dst_entry->id.value() != src_entry->id.value()) {
+    dst_entry->visibility = std::move(src_entry->visibility);
+  } else if (src_entry->visibility.level == Visibility::Level::kPublic &&
+             dst_entry->visibility.level == Visibility::Level::kPublic && dst_entry->id &&
+             src_entry->id && src_entry->id != dst_entry->id) {
     // Both entries are public and have different IDs.
     context->GetDiagnostics()->Error(DiagMessage(src) << "cannot merge entry '" << src_entry->name
                                                       << "': conflicting public IDs");
     return false;
   }
+
+  // Copy over the rest of the properties, if needed.
+  if (src_entry->allow_new) {
+    dst_entry->allow_new = std::move(src_entry->allow_new);
+  }
+
+  if (src_entry->overlayable) {
+    if (dst_entry->overlayable && !overlay) {
+      context->GetDiagnostics()->Error(DiagMessage(src_entry->overlayable.value().source)
+                                       << "duplicate overlayable declaration for resource '"
+                                       << src_entry->name << "'");
+      context->GetDiagnostics()->Error(DiagMessage(dst_entry->overlayable.value().source)
+                                       << "previous declaration here");
+      return false;
+    }
+    dst_entry->overlayable = std::move(src_entry->overlayable);
+  }
   return true;
 }
 
@@ -202,7 +216,7 @@
       }
 
       ResourceEntry* dst_entry;
-      if (allow_new_resources || src_entry->symbol_status.allow_new) {
+      if (allow_new_resources || src_entry->allow_new) {
         dst_entry = dst_type->FindOrCreateEntry(entry_name);
       } else {
         dst_entry = dst_type->FindEntry(entry_name);
@@ -220,7 +234,7 @@
         continue;
       }
 
-      if (!MergeEntry(context_, src, dst_entry, src_entry.get())) {
+      if (!MergeEntry(context_, src, overlay, dst_entry, src_entry.get())) {
         error = true;
         continue;
       }
diff --git a/tools/aapt2/link/TableMerger_test.cpp b/tools/aapt2/link/TableMerger_test.cpp
index 6aab8de..34461c6 100644
--- a/tools/aapt2/link/TableMerger_test.cpp
+++ b/tools/aapt2/link/TableMerger_test.cpp
@@ -182,14 +182,12 @@
   std::unique_ptr<ResourceTable> base =
       test::ResourceTableBuilder()
           .SetPackageId("", 0x7f)
-          .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001),
-                          SymbolState::kPublic)
+          .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001), Visibility::Level::kPublic)
           .Build();
   std::unique_ptr<ResourceTable> overlay =
       test::ResourceTableBuilder()
           .SetPackageId("", 0x7f)
-          .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001),
-                          SymbolState::kPublic)
+          .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001), Visibility::Level::kPublic)
           .Build();
 
   ResourceTable final_table;
@@ -205,14 +203,12 @@
   std::unique_ptr<ResourceTable> base =
       test::ResourceTableBuilder()
           .SetPackageId("", 0x7f)
-          .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001),
-                          SymbolState::kPublic)
+          .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001), Visibility::Level::kPublic)
           .Build();
   std::unique_ptr<ResourceTable> overlay =
       test::ResourceTableBuilder()
           .SetPackageId("", 0x7f)
-          .SetSymbolState("bool/foo", ResourceId(0x7f, 0x02, 0x0001),
-                          SymbolState::kPublic)
+          .SetSymbolState("bool/foo", ResourceId(0x7f, 0x02, 0x0001), Visibility::Level::kPublic)
           .Build();
 
   ResourceTable final_table;
@@ -228,14 +224,12 @@
   std::unique_ptr<ResourceTable> base =
       test::ResourceTableBuilder()
           .SetPackageId("", 0x7f)
-          .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001),
-                          SymbolState::kPublic)
+          .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0001), Visibility::Level::kPublic)
           .Build();
   std::unique_ptr<ResourceTable> overlay =
       test::ResourceTableBuilder()
           .SetPackageId("", 0x7f)
-          .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0002),
-                          SymbolState::kPublic)
+          .SetSymbolState("bool/foo", ResourceId(0x7f, 0x01, 0x0002), Visibility::Level::kPublic)
           .Build();
 
   ResourceTable final_table;
@@ -253,7 +247,7 @@
   std::unique_ptr<ResourceTable> table_b =
       test::ResourceTableBuilder()
           .SetPackageId("", 0x7f)
-          .SetSymbolState("bool/foo", {}, SymbolState::kUndefined, true /*allow new overlay*/)
+          .SetSymbolState("bool/foo", {}, Visibility::Level::kUndefined, true /*allow new overlay*/)
           .AddValue("bool/foo", ResourceUtils::TryParseBool("true"))
           .Build();
 
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/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp
index 0cfc0bd..3cae0e8 100644
--- a/tools/aapt2/process/SymbolTable.cpp
+++ b/tools/aapt2/process/SymbolTable.cpp
@@ -190,7 +190,7 @@
   ResourceTable::SearchResult sr = result.value();
 
   std::unique_ptr<SymbolTable::Symbol> symbol = util::make_unique<SymbolTable::Symbol>();
-  symbol->is_public = (sr.entry->symbol_status.state == SymbolState::kPublic);
+  symbol->is_public = (sr.entry->visibility.level == Visibility::Level::kPublic);
 
   if (sr.package->id && sr.type->id && sr.entry->id) {
     symbol->id = ResourceId(sr.package->id.value(), sr.type->id.value(), sr.entry->id.value());
diff --git a/tools/aapt2/split/TableSplitter.cpp b/tools/aapt2/split/TableSplitter.cpp
index 9d49ca6..e991743 100644
--- a/tools/aapt2/split/TableSplitter.cpp
+++ b/tools/aapt2/split/TableSplitter.cpp
@@ -233,13 +233,13 @@
             ResourceTableType* split_type = split_pkg->FindOrCreateType(type->type);
             if (!split_type->id) {
               split_type->id = type->id;
-              split_type->symbol_status = type->symbol_status;
+              split_type->visibility_level = type->visibility_level;
             }
 
             ResourceEntry* split_entry = split_type->FindOrCreateEntry(entry->name);
             if (!split_entry->id) {
               split_entry->id = entry->id;
-              split_entry->symbol_status = entry->symbol_status;
+              split_entry->visibility = entry->visibility;
             }
 
             // Copy the selected values into the new Split Entry.
diff --git a/tools/aapt2/test/Builders.cpp b/tools/aapt2/test/Builders.cpp
index 88897a8..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;
 
@@ -116,19 +117,20 @@
                                                      const ResourceId& id,
                                                      std::unique_ptr<Value> value) {
   ResourceName res_name = ParseNameOrDie(name);
-  CHECK(table_->AddResourceAllowMangled(res_name, id, config, {}, std::move(value),
-                                        GetDiagnostics()));
+  CHECK(table_->AddResourceWithIdMangled(res_name, id, config, {}, std::move(value),
+                                         GetDiagnostics()));
   return *this;
 }
 
 ResourceTableBuilder& ResourceTableBuilder::SetSymbolState(const StringPiece& name,
-                                                           const ResourceId& id, SymbolState state,
+                                                           const ResourceId& id,
+                                                           Visibility::Level level,
                                                            bool allow_new) {
   ResourceName res_name = ParseNameOrDie(name);
-  Symbol symbol;
-  symbol.state = state;
-  symbol.allow_new = allow_new;
-  CHECK(table_->SetSymbolStateAllowMangled(res_name, id, symbol, GetDiagnostics()));
+  Visibility visibility;
+  visibility.level = level;
+  CHECK(table_->SetVisibilityWithIdMangled(res_name, visibility, id, GetDiagnostics()));
+  CHECK(table_->SetAllowNewMangled(res_name, AllowNew{}, GetDiagnostics()));
   return *this;
 }
 
@@ -226,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;
@@ -250,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 2f83b78..0d7451b 100644
--- a/tools/aapt2/test/Builders.h
+++ b/tools/aapt2/test/Builders.h
@@ -68,7 +68,7 @@
   ResourceTableBuilder& AddValue(const android::StringPiece& name, const ConfigDescription& config,
                                  const ResourceId& id, std::unique_ptr<Value> value);
   ResourceTableBuilder& SetSymbolState(const android::StringPiece& name, const ResourceId& id,
-                                       SymbolState state, bool allow_new = false);
+                                       Visibility::Level level, bool allow_new = false);
 
   StringPool* string_pool();
   std::unique_ptr<ResourceTable> Build();
@@ -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
 
diff --git a/tools/aapt2/xml/XmlActionExecutor.cpp b/tools/aapt2/xml/XmlActionExecutor.cpp
index 602a902..cb844f0 100644
--- a/tools/aapt2/xml/XmlActionExecutor.cpp
+++ b/tools/aapt2/xml/XmlActionExecutor.cpp
@@ -66,7 +66,7 @@
         continue;
       }
 
-      if (policy == XmlActionExecutorPolicy::kWhitelist) {
+      if (policy != XmlActionExecutorPolicy::kNone) {
         DiagMessage error_msg(child_el->line_number);
         error_msg << "unexpected element ";
         PrintElementToDiagMessage(child_el, &error_msg);
@@ -74,8 +74,14 @@
         for (const StringPiece& element : *bread_crumb) {
           error_msg << "<" << element << ">";
         }
-        diag->Error(error_msg);
-        error = true;
+        if (policy == XmlActionExecutorPolicy::kWhitelistWarning) {
+          // Treat the error only as a warning.
+          diag->Warn(error_msg);
+        } else {
+          // Policy is XmlActionExecutorPolicy::kWhitelist, we should fail.
+          diag->Error(error_msg);
+          error = true;
+        }
       }
     }
   }
diff --git a/tools/aapt2/xml/XmlActionExecutor.h b/tools/aapt2/xml/XmlActionExecutor.h
index df70100..f689b2a 100644
--- a/tools/aapt2/xml/XmlActionExecutor.h
+++ b/tools/aapt2/xml/XmlActionExecutor.h
@@ -34,10 +34,15 @@
   // Actions are run if elements are matched, errors occur only when actions return false.
   kNone,
 
-  // The actions defined must match and run. If an element is found that does
-  // not match an action, an error occurs.
+  // The actions defined must match and run. If an element is found that does not match an action,
+  // an error occurs.
   // Note: namespaced elements are always ignored.
   kWhitelist,
+
+  // The actions defined should match and run. if an element is found that does not match an
+  // action, a warning is printed.
+  // Note: namespaced elements are always ignored.
+  kWhitelistWarning,
 };
 
 // Contains the actions to perform at this XML node. This is a recursive data structure that
diff --git a/tools/streaming_proto/Android.bp b/tools/streaming_proto/Android.bp
index 4e9391d..1121ead 100644
--- a/tools/streaming_proto/Android.bp
+++ b/tools/streaming_proto/Android.bp
@@ -32,26 +32,6 @@
     shared_libs: ["libprotoc"],
 }
 
-cc_library {
-    name: "streamingflags",
-    host_supported: true,
-    proto: {
-        export_proto_headers: true,
-        include_dirs: ["external/protobuf/src"],
-    },
-
-    target: {
-        host: {
-            proto: {
-                type: "full",
-            },
-            srcs: [
-                "stream.proto",
-            ],
-        },
-    },
-}
-
 cc_binary_host {
     name: "protoc-gen-javastream",
     srcs: [
@@ -68,5 +48,4 @@
     ],
 
     defaults: ["protoc-gen-stream-defaults"],
-    static_libs: ["streamingflags"],
 }
diff --git a/tools/streaming_proto/cpp/main.cpp b/tools/streaming_proto/cpp/main.cpp
index 745b3dc..d6b9d81 100644
--- a/tools/streaming_proto/cpp/main.cpp
+++ b/tools/streaming_proto/cpp/main.cpp
@@ -2,8 +2,6 @@
 #include "stream_proto_utils.h"
 #include "string_utils.h"
 
-#include <frameworks/base/tools/streaming_proto/stream.pb.h>
-
 #include <iomanip>
 #include <iostream>
 #include <sstream>
@@ -12,18 +10,14 @@
 using namespace google::protobuf::io;
 using namespace std;
 
+const bool GENERATE_MAPPING = true;
+
 static string
 make_filename(const FileDescriptorProto& file_descriptor)
 {
     return file_descriptor.name() + ".h";
 }
 
-static inline bool
-should_generate_enums_mapping(const EnumDescriptorProto& enu)
-{
-    return enu.options().GetExtension(stream_enum).enable_enums_mapping();
-}
-
 static void
 write_enum(stringstream& text, const EnumDescriptorProto& enu, const string& indent)
 {
@@ -36,7 +30,7 @@
                 << " = " << value.number() << ";" << endl;
     }
 
-    if (should_generate_enums_mapping(enu)) {
+    if (GENERATE_MAPPING) {
         string name = make_constant_name(enu.name());
         string prefix = name + "_";
         text << indent << "const int _ENUM_" << name << "_COUNT = " << N << ";" << endl;
@@ -79,23 +73,11 @@
     text << endl;
 }
 
-static inline bool
-should_generate_fields_mapping(const DescriptorProto& message)
-{
-    return message.options().GetExtension(stream_msg).enable_fields_mapping();
-}
-
-static inline bool
-should_generate_fields_mapping_recursively(const DescriptorProto& message) {
-    return message.options().GetExtension(stream_msg).enable_fields_mapping_recursively();
-}
-
 static void
-write_message(stringstream& text, const DescriptorProto& message, const string& indent, bool genMapping)
+write_message(stringstream& text, const DescriptorProto& message, const string& indent)
 {
     int N;
     const string indented = indent + INDENT;
-    genMapping |= should_generate_fields_mapping_recursively(message);
 
     text << indent << "// message " << message.name() << endl;
     text << indent << "namespace " << message.name() << " {" << endl;
@@ -109,7 +91,7 @@
     // Nested classes
     N = message.nested_type_size();
     for (int i=0; i<N; i++) {
-        write_message(text, message.nested_type(i), indented, genMapping);
+        write_message(text, message.nested_type(i), indented);
     }
 
     // Fields
@@ -118,7 +100,7 @@
         write_field(text, message.field(i), indented);
     }
 
-    if (genMapping | should_generate_fields_mapping(message)) {
+    if (GENERATE_MAPPING) {
         N = message.field_size();
         text << indented << "const int _FIELD_COUNT = " << N << ";" << endl;
         text << indented << "const char* _FIELD_NAMES[" << N << "] = {" << endl;
@@ -167,7 +149,7 @@
 
     N = file_descriptor.message_type_size();
     for (size_t i=0; i<N; i++) {
-        write_message(text, file_descriptor.message_type(i), "", false);
+        write_message(text, file_descriptor.message_type(i), "");
     }
 
     for (vector<string>::iterator it = namespaces.begin(); it != namespaces.end(); it++) {
diff --git a/tools/streaming_proto/stream.proto b/tools/streaming_proto/stream.proto
deleted file mode 100644
index e9b24a8..0000000
--- a/tools/streaming_proto/stream.proto
+++ /dev/null
@@ -1,45 +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.
- */
-
-syntax = "proto2";
-
-import "google/protobuf/descriptor.proto";
-
-package android.stream_proto;
-
-// This option tells streaming proto plugin to compile .proto files with extra features.
-message MessageOptions {
-  // creates a mapping of field names of the message to its field ids
-  optional bool enable_fields_mapping = 1;
-
-  // creates mapping between field names to its field ids and recursively for its submessages.
-  optional bool enable_fields_mapping_recursively = 2;
-}
-
-extend google.protobuf.MessageOptions {
-    // Flags used by streaming proto plugins
-    optional MessageOptions stream_msg = 126856794;
-}
-
-message EnumOptions {
-  // creates a mapping of enum names to its values, strip its prefix enum type for each value
-  optional bool enable_enums_mapping = 1;
-}
-
-extend google.protobuf.EnumOptions {
-    // Flags used by streaming proto plugins
-    optional EnumOptions stream_enum = 126856794;
-}