Merge "Track bugreports immediately after triggering a bugreport" into rvc-dev
diff --git a/Android.bp b/Android.bp
index 30d4409..874d76f 100644
--- a/Android.bp
+++ b/Android.bp
@@ -1159,7 +1159,7 @@
 // into wifi-service
 java_library {
     name: "framework-wifi-util-lib",
-    sdk_version: "core_current",
+    sdk_version: "module_current",
     srcs: [
         "core/java/android/content/pm/BaseParceledListSlice.java",
         "core/java/android/content/pm/ParceledListSlice.java",
@@ -1175,7 +1175,6 @@
     libs: [
         "framework-annotations-lib",
         "unsupportedappusage",
-        "android_system_stubs_current",
     ],
     visibility: ["//frameworks/base/wifi"],
 }
@@ -1279,8 +1278,7 @@
     aidl: {
         export_include_dirs: ["telephony/java"],
     },
-    sdk_version: "core_current",
-    libs: ["android_system_stubs_current"],
+    sdk_version: "module_current",
 }
 
 filegroup {
diff --git a/StubLibraries.bp b/StubLibraries.bp
index ccd87335..60f6174 100644
--- a/StubLibraries.bp
+++ b/StubLibraries.bp
@@ -95,6 +95,7 @@
     api_filename: "public_api.txt",
     private_api_filename: "private.txt",
     removed_api_filename: "removed.txt",
+    removed_dex_api_filename: "removed-dex.txt",
     arg_files: [
         "core/res/AndroidManifest.xml",
     ],
@@ -141,6 +142,7 @@
     private_api_filename: "system-private.txt",
     private_dex_api_filename: "system-private-dex.txt",
     removed_api_filename: "system-removed.txt",
+    removed_dex_api_filename: "system-removed-dex.txt",
     arg_files: [
         "core/res/AndroidManifest.xml",
     ],
@@ -340,44 +342,6 @@
 }
 
 /////////////////////////////////////////////////////////////////////
-// Stubs for hiddenapi processing.
-/////////////////////////////////////////////////////////////////////
-
-droidstubs {
-    name: "hiddenapi-lists-docs",
-    defaults: ["metalava-full-api-stubs-default"],
-    arg_files: [
-        "core/res/AndroidManifest.xml",
-    ],
-    dex_api_filename: "public-dex.txt",
-    private_dex_api_filename: "private-dex.txt",
-    removed_dex_api_filename: "removed-dex.txt",
-    args: metalava_framework_docs_args +
-        " --show-unannotated " +
-        priv_apps +
-        " --show-annotation android.annotation.TestApi ",
-}
-
-droidstubs {
-    name: "hiddenapi-mappings",
-    defaults: ["metalava-full-api-stubs-default"],
-    srcs: [
-        ":opt-telephony-common-srcs",
-    ],
-
-    arg_files: [
-        "core/res/AndroidManifest.xml",
-    ],
-    dex_mapping_filename: "dex-mapping.txt",
-    args: metalava_framework_docs_args +
-        " --hide ReferencesHidden " +
-        " --hide UnhiddenSystemApi " +
-        " --show-unannotated " +
-        priv_apps +
-        " --show-annotation android.annotation.TestApi ",
-}
-
-/////////////////////////////////////////////////////////////////////
 // api/*-current.txt files for use by modules in other directories
 // like the CTS test
 /////////////////////////////////////////////////////////////////////
diff --git a/aidl_api/libincremental_aidl/current/android/os/incremental/IncrementalFileSystemControlParcel.aidl b/aidl_api/libincremental_aidl/current/android/os/incremental/IncrementalFileSystemControlParcel.aidl
deleted file mode 100644
index d777e34..0000000
--- a/aidl_api/libincremental_aidl/current/android/os/incremental/IncrementalFileSystemControlParcel.aidl
+++ /dev/null
@@ -1,24 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////
-// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
-//
-// You must not make a backward incompatible changes to the AIDL files built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
-package android.os.incremental;
-/* @hide */
-parcelable IncrementalFileSystemControlParcel {
-  ParcelFileDescriptor cmd;
-  ParcelFileDescriptor pendingReads;
-  ParcelFileDescriptor log;
-}
diff --git a/aidl_api/libincremental_manager_aidl/current/android/os/incremental/IIncrementalService.aidl b/aidl_api/libincremental_manager_aidl/current/android/os/incremental/IIncrementalService.aidl
deleted file mode 100644
index 5e1f013..0000000
--- a/aidl_api/libincremental_manager_aidl/current/android/os/incremental/IIncrementalService.aidl
+++ /dev/null
@@ -1,44 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////
-// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
-//
-// You must not make a backward incompatible changes to the AIDL files built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
-package android.os.incremental;
-/* @hide */
-interface IIncrementalService {
-  int openStorage(in @utf8InCpp String path);
-  int createStorage(in @utf8InCpp String path, in android.content.pm.DataLoaderParamsParcel params, in android.content.pm.IDataLoaderStatusListener listener, int createMode);
-  int createLinkedStorage(in @utf8InCpp String path, int otherStorageId, int createMode);
-  int makeBindMount(int storageId, in @utf8InCpp String sourcePath, in @utf8InCpp String targetFullPath, int bindType);
-  int deleteBindMount(int storageId, in @utf8InCpp String targetFullPath);
-  int makeDirectory(int storageId, in @utf8InCpp String path);
-  int makeDirectories(int storageId, in @utf8InCpp String path);
-  int makeFile(int storageId, in @utf8InCpp String path, in android.os.incremental.IncrementalNewFileParams params);
-  int makeFileFromRange(int storageId, in @utf8InCpp String targetPath, in @utf8InCpp String sourcePath, long start, long end);
-  int makeLink(int sourceStorageId, in @utf8InCpp String sourcePath, int destStorageId, in @utf8InCpp String destPath);
-  int unlink(int storageId, in @utf8InCpp String path);
-  boolean isFileRangeLoaded(int storageId, in @utf8InCpp String path, long start, long end);
-  byte[] getMetadataByPath(int storageId, in @utf8InCpp String path);
-  byte[] getMetadataById(int storageId, in byte[] fileId);
-  boolean startLoading(int storageId);
-  void deleteStorage(int storageId);
-  boolean configureNativeBinaries(int storageId, in @utf8InCpp String apkFullPath, in @utf8InCpp String libDirRelativePath, in @utf8InCpp String abi);
-  const int CREATE_MODE_TEMPORARY_BIND = 1;
-  const int CREATE_MODE_PERMANENT_BIND = 2;
-  const int CREATE_MODE_CREATE = 4;
-  const int CREATE_MODE_OPEN_EXISTING = 8;
-  const int BIND_TEMPORARY = 0;
-  const int BIND_PERMANENT = 1;
-}
diff --git a/aidl_api/libincremental_manager_aidl/current/android/os/incremental/IncrementalNewFileParams.aidl b/aidl_api/libincremental_manager_aidl/current/android/os/incremental/IncrementalNewFileParams.aidl
deleted file mode 100644
index c737877..0000000
--- a/aidl_api/libincremental_manager_aidl/current/android/os/incremental/IncrementalNewFileParams.aidl
+++ /dev/null
@@ -1,25 +0,0 @@
-///////////////////////////////////////////////////////////////////////////////
-// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
-///////////////////////////////////////////////////////////////////////////////
-
-// This file is a snapshot of an AIDL interface (or parcelable). Do not try to
-// edit this file. It looks like you are doing that because you have modified
-// an AIDL interface in a backward-incompatible way, e.g., deleting a function
-// from an interface or a field from a parcelable and it broke the build. That
-// breakage is intended.
-//
-// You must not make a backward incompatible changes to the AIDL files built
-// with the aidl_interface module type with versions property set. The module
-// type is used to build AIDL files in a way that they can be used across
-// independently updatable components of the system. If a device is shipped
-// with such a backward incompatible change, it has a high risk of breaking
-// later when a module using the interface is updated, e.g., Mainline modules.
-
-package android.os.incremental;
-/* @hide */
-parcelable IncrementalNewFileParams {
-  long size;
-  byte[] fileId;
-  byte[] metadata;
-  @nullable byte[] signature;
-}
diff --git a/apex/statsd/framework/Android.bp b/apex/statsd/framework/Android.bp
index 7480ec8..6f29141 100644
--- a/apex/statsd/framework/Android.bp
+++ b/apex/statsd/framework/Android.bp
@@ -146,7 +146,8 @@
     visibility: [
         "//frameworks/base", // Framework
         "//frameworks/base/apex/statsd", // statsd apex
-        "//frameworks/opt/net/wifi/service" // wifi service
+        "//frameworks/opt/net/wifi/service", // wifi service
+        "//packages/providers/MediaProvider", // MediaProvider apk
     ],
 }
 
diff --git a/apex/statsd/framework/java/android/app/StatsManager.java b/apex/statsd/framework/java/android/app/StatsManager.java
index 62badb4..7fbfc43 100644
--- a/apex/statsd/framework/java/android/app/StatsManager.java
+++ b/apex/statsd/framework/java/android/app/StatsManager.java
@@ -467,10 +467,6 @@
         synchronized (sLock) {
             try {
                 IStatsManagerService service = getIStatsManagerServiceLocked();
-                if (service == null) {
-                    throw new StatsUnavailableException("Failed to find statsmanager when "
-                                                              + "getting experiment IDs");
-                }
                 return service.getRegisteredExperimentIds();
             } catch (RemoteException e) {
                 if (DEBUG) {
diff --git a/api/current.txt b/api/current.txt
index 1c1d142..ed4d9eb 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -30284,6 +30284,7 @@
     field public static final int NET_CAPABILITY_NOT_VPN = 15; // 0xf
     field public static final int NET_CAPABILITY_RCS = 8; // 0x8
     field public static final int NET_CAPABILITY_SUPL = 1; // 0x1
+    field public static final int NET_CAPABILITY_TEMPORARILY_NOT_METERED = 25; // 0x19
     field public static final int NET_CAPABILITY_TRUSTED = 14; // 0xe
     field public static final int NET_CAPABILITY_VALIDATED = 16; // 0x10
     field public static final int NET_CAPABILITY_WIFI_P2P = 6; // 0x6
diff --git a/api/test-current.txt b/api/test-current.txt
index 93cedf1..0ca8b2d 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -5158,3 +5158,75 @@
 
 }
 
+package android.window {
+
+  public class DisplayAreaOrganizer extends android.window.WindowOrganizer {
+    ctor public DisplayAreaOrganizer();
+    method public void onDisplayAreaAppeared(@NonNull android.window.WindowContainerToken);
+    method public void onDisplayAreaVanished(@NonNull android.window.WindowContainerToken);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void registerOrganizer(int);
+    field public static final int FEATURE_ROOT = 0; // 0x0
+    field public static final int FEATURE_SYSTEM_FIRST = 0; // 0x0
+    field public static final int FEATURE_SYSTEM_LAST = 10000; // 0x2710
+    field public static final int FEATURE_TASK_CONTAINER = 1; // 0x1
+    field public static final int FEATURE_UNDEFINED = -1; // 0xffffffff
+    field public static final int FEATURE_VENDOR_FIRST = 10001; // 0x2711
+    field public static final int FEATURE_WINDOW_TOKENS = 2; // 0x2
+  }
+
+  public class TaskOrganizer extends android.window.WindowOrganizer {
+    ctor public TaskOrganizer();
+    method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public static android.app.ActivityManager.RunningTaskInfo createRootTask(int, int);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public static boolean deleteRootTask(@NonNull android.window.WindowContainerToken);
+    method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public static java.util.List<android.app.ActivityManager.RunningTaskInfo> getChildTasks(@NonNull android.window.WindowContainerToken, @NonNull int[]);
+    method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public static android.window.WindowContainerToken getImeTarget(int);
+    method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public static java.util.List<android.app.ActivityManager.RunningTaskInfo> getRootTasks(int, @NonNull int[]);
+    method public void onBackPressedOnTaskRoot(@NonNull android.app.ActivityManager.RunningTaskInfo);
+    method public void onTaskAppeared(@NonNull android.app.ActivityManager.RunningTaskInfo);
+    method public void onTaskInfoChanged(@NonNull android.app.ActivityManager.RunningTaskInfo);
+    method public void onTaskVanished(@NonNull android.app.ActivityManager.RunningTaskInfo);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void registerOrganizer(int);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void setInterceptBackPressedOnTaskRoot(boolean);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public static void setLaunchRoot(int, @NonNull android.window.WindowContainerToken);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void unregisterOrganizer();
+  }
+
+  public final class WindowContainerToken implements android.os.Parcelable {
+    method public int describeContents();
+    method @Nullable public android.view.SurfaceControl getLeash();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.window.WindowContainerToken> CREATOR;
+  }
+
+  public final class WindowContainerTransaction implements android.os.Parcelable {
+    ctor public WindowContainerTransaction();
+    method public int describeContents();
+    method @NonNull public android.window.WindowContainerTransaction reorder(@NonNull android.window.WindowContainerToken, boolean);
+    method @NonNull public android.window.WindowContainerTransaction reparent(@NonNull android.window.WindowContainerToken, @Nullable android.window.WindowContainerToken, boolean);
+    method @NonNull public android.window.WindowContainerTransaction scheduleFinishEnterPip(@NonNull android.window.WindowContainerToken, @NonNull android.graphics.Rect);
+    method @NonNull public android.window.WindowContainerTransaction setActivityWindowingMode(@NonNull android.window.WindowContainerToken, int);
+    method @NonNull public android.window.WindowContainerTransaction setAppBounds(@NonNull android.window.WindowContainerToken, @NonNull android.graphics.Rect);
+    method @NonNull public android.window.WindowContainerTransaction setBounds(@NonNull android.window.WindowContainerToken, @NonNull android.graphics.Rect);
+    method @NonNull public android.window.WindowContainerTransaction setBoundsChangeTransaction(@NonNull android.window.WindowContainerToken, @NonNull android.view.SurfaceControl.Transaction);
+    method @NonNull public android.window.WindowContainerTransaction setFocusable(@NonNull android.window.WindowContainerToken, boolean);
+    method @NonNull public android.window.WindowContainerTransaction setHidden(@NonNull android.window.WindowContainerToken, boolean);
+    method @NonNull public android.window.WindowContainerTransaction setScreenSizeDp(@NonNull android.window.WindowContainerToken, int, int);
+    method @NonNull public android.window.WindowContainerTransaction setSmallestScreenWidthDp(@NonNull android.window.WindowContainerToken, int);
+    method @NonNull public android.window.WindowContainerTransaction setWindowingMode(@NonNull android.window.WindowContainerToken, int);
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.window.WindowContainerTransaction> CREATOR;
+  }
+
+  public abstract class WindowContainerTransactionCallback {
+    ctor public WindowContainerTransactionCallback();
+    method public abstract void onTransactionReady(int, @NonNull android.view.SurfaceControl.Transaction);
+  }
+
+  public class WindowOrganizer {
+    ctor public WindowOrganizer();
+    method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public int applySyncTransaction(@NonNull android.window.WindowContainerTransaction, @NonNull android.window.WindowContainerTransactionCallback);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public static void applyTransaction(@NonNull android.window.WindowContainerTransaction);
+  }
+
+}
+
diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp
index 776593d..20f9e76 100644
--- a/cmds/statsd/Android.bp
+++ b/cmds/statsd/Android.bp
@@ -78,6 +78,7 @@
         "src/matchers/EventMatcherWizard.cpp",
         "src/matchers/matcher_util.cpp",
         "src/matchers/SimpleLogMatchingTracker.cpp",
+        "src/metadata_util.cpp",
         "src/metrics/CountMetricProducer.cpp",
         "src/metrics/duration_helper/MaxDurationTracker.cpp",
         "src/metrics/duration_helper/OringDurationTracker.cpp",
@@ -336,10 +337,12 @@
         "tests/external/StatsPullerManager_test.cpp",
         "tests/FieldValue_test.cpp",
         "tests/guardrail/StatsdStats_test.cpp",
+        "tests/HashableDimensionKey_test.cpp",
         "tests/indexed_priority_queue_test.cpp",
         "tests/log_event/LogEventQueue_test.cpp",
         "tests/LogEntryMatcher_test.cpp",
         "tests/LogEvent_test.cpp",
+        "tests/metadata_util_test.cpp",
         "tests/metrics/CountMetricProducer_test.cpp",
         "tests/metrics/DurationMetricProducer_test.cpp",
         "tests/metrics/EventMetricProducer_test.cpp",
diff --git a/cmds/statsd/src/FieldValue.h b/cmds/statsd/src/FieldValue.h
index 967fd32..3536e5a 100644
--- a/cmds/statsd/src/FieldValue.h
+++ b/cmds/statsd/src/FieldValue.h
@@ -16,6 +16,7 @@
 #pragma once
 
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include "annotations.h"
 
 namespace android {
 namespace os {
@@ -357,6 +358,56 @@
     Value& operator=(const Value& that);
 };
 
+class Annotations {
+public:
+    Annotations() {}
+
+    // This enum stores where particular annotations can be found in the
+    // bitmask. Note that these pos do not correspond to annotation ids.
+    enum {
+        NESTED_POS = 0x0,
+        PRIMARY_POS = 0x1,
+        EXCLUSIVE_POS = 0x2
+    };
+
+    inline void setNested(bool nested) { setBitmaskAtPos(NESTED_POS, nested); }
+
+    inline void setPrimaryField(bool primary) { setBitmaskAtPos(PRIMARY_POS, primary); }
+
+    inline void setExclusiveState(bool exclusive) { setBitmaskAtPos(EXCLUSIVE_POS, exclusive); }
+
+    inline void setResetState(int resetState) { mResetState = resetState; }
+
+    // Default value = false
+    inline bool isNested() const { return getValueFromBitmask(NESTED_POS); }
+
+    // Default value = false
+    inline bool isPrimaryField() const { return getValueFromBitmask(PRIMARY_POS); }
+
+    // Default value = false
+    inline bool isExclusiveState() const { return getValueFromBitmask(EXCLUSIVE_POS); }
+
+    // If a reset state is not sent in the StatsEvent, returns -1. Note that a
+    // reset satate is only sent if and only if a reset should be triggered.
+    inline int getResetState() const { return mResetState; }
+
+private:
+    inline void setBitmaskAtPos(int pos, bool value) {
+        mBooleanBitmask &= ~(1 << pos); // clear
+        mBooleanBitmask |= (value << pos); // set
+    }
+
+    inline bool getValueFromBitmask(int pos) const {
+        return (mBooleanBitmask >> pos) & 0x1;
+    }
+
+    // This is a bitmask over all annotations stored in boolean form. Because
+    // there are only 3 booleans, just one byte is required.
+    uint8_t mBooleanBitmask = 0;
+
+    int mResetState = -1;
+};
+
 /**
  * Represents a log item, or a dimension item (They are essentially the same).
  */
@@ -384,6 +435,7 @@
 
     Field mField;
     Value mValue;
+    Annotations mAnnotations;
 };
 
 bool HasPositionANY(const FieldMatcher& matcher);
diff --git a/cmds/statsd/src/HashableDimensionKey.cpp b/cmds/statsd/src/HashableDimensionKey.cpp
index 23d8f59..29249f4 100644
--- a/cmds/statsd/src/HashableDimensionKey.cpp
+++ b/cmds/statsd/src/HashableDimensionKey.cpp
@@ -230,6 +230,47 @@
     }
 }
 
+bool containsLinkedStateValues(const HashableDimensionKey& whatKey,
+                               const HashableDimensionKey& primaryKey,
+                               const vector<Metric2State>& stateLinks, const int32_t stateAtomId) {
+    if (whatKey.getValues().size() < primaryKey.getValues().size()) {
+        ALOGE("Contains linked values false: whatKey is too small");
+        return false;
+    }
+
+    for (const auto& primaryValue : primaryKey.getValues()) {
+        bool found = false;
+        for (const auto& whatValue : whatKey.getValues()) {
+            if (linked(stateLinks, stateAtomId, primaryValue.mField, whatValue.mField) &&
+                primaryValue.mValue == whatValue.mValue) {
+                found = true;
+                break;
+            }
+        }
+        if (!found) {
+            return false;
+        }
+    }
+    return true;
+}
+
+bool linked(const vector<Metric2State>& stateLinks, const int32_t stateAtomId,
+            const Field& stateField, const Field& metricField) {
+    for (auto stateLink : stateLinks) {
+        if (stateLink.stateAtomId != stateAtomId) {
+            continue;
+        }
+
+        for (size_t i = 0; i < stateLink.stateFields.size(); i++) {
+            if (stateLink.stateFields[i].mMatcher == stateField &&
+                stateLink.metricFields[i].mMatcher == metricField) {
+                return true;
+            }
+        }
+    }
+    return false;
+}
+
 bool LessThan(const vector<FieldValue>& s1, const vector<FieldValue>& s2) {
     if (s1.size() != s2.size()) {
         return s1.size() < s2.size();
diff --git a/cmds/statsd/src/HashableDimensionKey.h b/cmds/statsd/src/HashableDimensionKey.h
index a766bba..33a5024 100644
--- a/cmds/statsd/src/HashableDimensionKey.h
+++ b/cmds/statsd/src/HashableDimensionKey.h
@@ -110,6 +110,10 @@
         return mStateValuesKey;
     }
 
+    inline HashableDimensionKey* getMutableStateValuesKey() {
+        return &mStateValuesKey;
+    }
+
     inline void setStateValuesKey(const HashableDimensionKey& key) {
         mStateValuesKey = key;
     }
@@ -169,6 +173,32 @@
 void getDimensionForState(const std::vector<FieldValue>& eventValues, const Metric2State& link,
                           HashableDimensionKey* statePrimaryKey);
 
+/**
+ * Returns true if the primaryKey values are a subset of the whatKey values.
+ * The values from the primaryKey come from the state atom, so we need to
+ * check that a link exists between the state atom field and what atom field.
+ *
+ * Example:
+ * whatKey = [Atom: 10, {uid: 1005, wakelock_name: "compose"}]
+ * statePrimaryKey = [Atom: 27, {uid: 1005}]
+ * Returns true IF one of the Metric2State links Atom 10's uid to Atom 27's uid
+ *
+ * Example:
+ * whatKey = [Atom: 10, {uid: 1005, wakelock_name: "compose"}]
+ * statePrimaryKey = [Atom: 59, {uid: 1005, package_name: "system"}]
+ * Returns false
+ */
+bool containsLinkedStateValues(const HashableDimensionKey& whatKey,
+                               const HashableDimensionKey& primaryKey,
+                               const std::vector<Metric2State>& stateLinks,
+                               const int32_t stateAtomId);
+
+/**
+ * Returns true if there is a Metric2State link that links the stateField and
+ * the metricField (they are equal fields from different atoms).
+ */
+bool linked(const std::vector<Metric2State>& stateLinks, const int32_t stateAtomId,
+            const Field& stateField, const Field& metricField);
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp
index 4966b2e..982a63e 100644
--- a/cmds/statsd/src/StatsLogProcessor.cpp
+++ b/cmds/statsd/src/StatsLogProcessor.cpp
@@ -79,6 +79,7 @@
 #define NS_PER_HOUR 3600 * NS_PER_SEC
 
 #define STATS_ACTIVE_METRIC_DIR "/data/misc/stats-active-metric"
+#define STATS_METADATA_DIR "/data/misc/stats-metadata"
 
 // Cool down period for writing data to disk to avoid overwriting files.
 #define WRITE_DATA_COOL_DOWN_SEC 5
@@ -852,6 +853,110 @@
     proto.flush(fd.get());
 }
 
+void StatsLogProcessor::SaveMetadataToDisk(int64_t currentWallClockTimeNs,
+                                           int64_t systemElapsedTimeNs) {
+    std::lock_guard<std::mutex> lock(mMetricsMutex);
+    // Do not write to disk if we already have in the last few seconds.
+    if (static_cast<unsigned long long> (systemElapsedTimeNs) <
+            mLastMetadataWriteNs + WRITE_DATA_COOL_DOWN_SEC * NS_PER_SEC) {
+        ALOGI("Statsd skipping writing metadata to disk. Already wrote data in last %d seconds",
+                WRITE_DATA_COOL_DOWN_SEC);
+        return;
+    }
+    mLastMetadataWriteNs = systemElapsedTimeNs;
+
+    metadata::StatsMetadataList metadataList;
+    WriteMetadataToProtoLocked(
+            currentWallClockTimeNs, systemElapsedTimeNs, &metadataList);
+
+    string file_name = StringPrintf("%s/metadata", STATS_METADATA_DIR);
+    StorageManager::deleteFile(file_name.c_str());
+
+    if (metadataList.stats_metadata_size() == 0) {
+        // Skip the write if we have nothing to write.
+        return;
+    }
+
+    std::string data;
+    metadataList.SerializeToString(&data);
+    StorageManager::writeFile(file_name.c_str(), data.c_str(), data.size());
+}
+
+void StatsLogProcessor::WriteMetadataToProto(int64_t currentWallClockTimeNs,
+                                             int64_t systemElapsedTimeNs,
+                                             metadata::StatsMetadataList* metadataList) {
+    std::lock_guard<std::mutex> lock(mMetricsMutex);
+    WriteMetadataToProtoLocked(currentWallClockTimeNs, systemElapsedTimeNs, metadataList);
+}
+
+void StatsLogProcessor::WriteMetadataToProtoLocked(int64_t currentWallClockTimeNs,
+                                                   int64_t systemElapsedTimeNs,
+                                                   metadata::StatsMetadataList* metadataList) {
+    for (const auto& pair : mMetricsManagers) {
+        const sp<MetricsManager>& metricsManager = pair.second;
+        metadata::StatsMetadata* statsMetadata = metadataList->add_stats_metadata();
+        bool metadataWritten = metricsManager->writeMetadataToProto(currentWallClockTimeNs,
+                systemElapsedTimeNs, statsMetadata);
+        if (!metadataWritten) {
+            metadataList->mutable_stats_metadata()->RemoveLast();
+        }
+    }
+}
+
+void StatsLogProcessor::LoadMetadataFromDisk(int64_t currentWallClockTimeNs,
+                                             int64_t systemElapsedTimeNs) {
+    std::lock_guard<std::mutex> lock(mMetricsMutex);
+    string file_name = StringPrintf("%s/metadata", STATS_METADATA_DIR);
+    int fd = open(file_name.c_str(), O_RDONLY | O_CLOEXEC);
+    if (-1 == fd) {
+        VLOG("Attempt to read %s but failed", file_name.c_str());
+        StorageManager::deleteFile(file_name.c_str());
+        return;
+    }
+    string content;
+    if (!android::base::ReadFdToString(fd, &content)) {
+        ALOGE("Attempt to read %s but failed", file_name.c_str());
+        close(fd);
+        StorageManager::deleteFile(file_name.c_str());
+        return;
+    }
+
+    close(fd);
+
+    metadata::StatsMetadataList statsMetadataList;
+    if (!statsMetadataList.ParseFromString(content)) {
+        ALOGE("Attempt to read %s but failed; failed to metadata", file_name.c_str());
+        StorageManager::deleteFile(file_name.c_str());
+        return;
+    }
+    SetMetadataStateLocked(statsMetadataList, currentWallClockTimeNs, systemElapsedTimeNs);
+    StorageManager::deleteFile(file_name.c_str());
+}
+
+void StatsLogProcessor::SetMetadataState(const metadata::StatsMetadataList& statsMetadataList,
+                                         int64_t currentWallClockTimeNs,
+                                         int64_t systemElapsedTimeNs) {
+    std::lock_guard<std::mutex> lock(mMetricsMutex);
+    SetMetadataStateLocked(statsMetadataList, currentWallClockTimeNs, systemElapsedTimeNs);
+}
+
+void StatsLogProcessor::SetMetadataStateLocked(
+        const metadata::StatsMetadataList& statsMetadataList,
+        int64_t currentWallClockTimeNs,
+        int64_t systemElapsedTimeNs) {
+    for (const metadata::StatsMetadata& metadata : statsMetadataList.stats_metadata()) {
+        ConfigKey key(metadata.config_key().uid(), metadata.config_key().config_id());
+        auto it = mMetricsManagers.find(key);
+        if (it == mMetricsManagers.end()) {
+            ALOGE("No config found for configKey %s", key.ToString().c_str());
+            continue;
+        }
+        VLOG("Setting metadata %s", key.ToString().c_str());
+        it->second->loadMetadata(metadata, currentWallClockTimeNs, systemElapsedTimeNs);
+    }
+    VLOG("Successfully loaded %d metadata.", statsMetadataList.stats_metadata_size());
+}
+
 void StatsLogProcessor::WriteActiveConfigsToProtoOutputStream(
         int64_t currentTimeNs, const DumpReportReason reason, ProtoOutputStream* proto) {
     std::lock_guard<std::mutex> lock(mMetricsMutex);
diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h
index 42e5676..97512ed 100644
--- a/cmds/statsd/src/StatsLogProcessor.h
+++ b/cmds/statsd/src/StatsLogProcessor.h
@@ -24,6 +24,7 @@
 #include "external/StatsPullerManager.h"
 
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include "frameworks/base/cmds/statsd/src/statsd_metadata.pb.h"
 
 #include <stdio.h>
 #include <unordered_map>
@@ -89,6 +90,23 @@
     /* Load configs containing metrics with active activations from disk. */
     void LoadActiveConfigsFromDisk();
 
+    /* Persist metadata for configs and metrics to disk. */
+    void SaveMetadataToDisk(int64_t currentWallClockTimeNs, int64_t systemElapsedTimeNs);
+
+    /* Writes the statsd metadata for all configs and metrics to StatsMetadataList. */
+    void WriteMetadataToProto(int64_t currentWallClockTimeNs,
+                              int64_t systemElapsedTimeNs,
+                              metadata::StatsMetadataList* metadataList);
+
+    /* Load stats metadata for configs and metrics from disk. */
+    void LoadMetadataFromDisk(int64_t currentWallClockTimeNs,
+                              int64_t systemElapsedTimeNs);
+
+    /* Sets the metadata for all configs and metrics */
+    void SetMetadataState(const metadata::StatsMetadataList& statsMetadataList,
+                          int64_t currentWallClockTimeNs,
+                          int64_t systemElapsedTimeNs);
+
     /* Sets the active status/ttl for all configs and metrics to the status in ActiveConfigList. */
     void SetConfigsActiveState(const ActiveConfigList& activeConfigList, int64_t currentTimeNs);
 
@@ -173,8 +191,17 @@
     void SetConfigsActiveStateLocked(const ActiveConfigList& activeConfigList,
                                      int64_t currentTimeNs);
 
+    void SetMetadataStateLocked(const metadata::StatsMetadataList& statsMetadataList,
+                                int64_t currentWallClockTimeNs,
+                                int64_t systemElapsedTimeNs);
+
+    void WriteMetadataToProtoLocked(int64_t currentWallClockTimeNs,
+                                    int64_t systemElapsedTimeNs,
+                                    metadata::StatsMetadataList* metadataList);
+
     void WriteDataToDiskLocked(const DumpReportReason dumpReportReason,
                                const DumpLatency dumpLatency);
+
     void WriteDataToDiskLocked(const ConfigKey& key, const int64_t timestampNs,
                                const DumpReportReason dumpReportReason,
                                const DumpLatency dumpLatency);
@@ -241,6 +268,9 @@
     // Last time we wrote active metrics to disk.
     int64_t mLastActiveMetricsWriteNs = 0;
 
+    //Last time we wrote metadata to disk.
+    int64_t mLastMetadataWriteNs = 0;
+
 #ifdef VERY_VERBOSE_PRINTING
     bool mPrintAllLogs = false;
 #endif
@@ -278,6 +308,9 @@
 
     FRIEND_TEST(AnomalyDetectionE2eTest, TestSlicedCountMetric_single_bucket);
     FRIEND_TEST(AnomalyDetectionE2eTest, TestSlicedCountMetric_multiple_buckets);
+    FRIEND_TEST(AnomalyDetectionE2eTest, TestCountMetric_save_refractory_to_disk_no_data_written);
+    FRIEND_TEST(AnomalyDetectionE2eTest, TestCountMetric_save_refractory_to_disk);
+    FRIEND_TEST(AnomalyDetectionE2eTest, TestCountMetric_load_refractory_from_disk);
     FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_single_bucket);
     FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_multiple_buckets);
     FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_long_refractory_period);
@@ -301,6 +334,11 @@
     FRIEND_TEST(DurationMetricE2eTest, TestWithCondition);
     FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedCondition);
     FRIEND_TEST(DurationMetricE2eTest, TestWithActivationAndSlicedCondition);
+    FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedState);
+    FRIEND_TEST(DurationMetricE2eTest, TestWithConditionAndSlicedState);
+    FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStateMapped);
+    FRIEND_TEST(DurationMetricE2eTest, TestSlicedStatePrimaryFieldsNotSubsetDimInWhat);
+    FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSubset);
 
     FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState);
     FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithDimensions);
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index 98879a0..9169eb17 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -1022,6 +1022,7 @@
     VLOG("StatsService::informDeviceShutdown");
     mProcessor->WriteDataToDisk(DEVICE_SHUTDOWN, FAST);
     mProcessor->SaveActiveConfigsToDisk(getElapsedRealtimeNs());
+    mProcessor->SaveMetadataToDisk(getWallClockNs(), getElapsedRealtimeNs());
     return Status::ok();
 }
 
@@ -1056,6 +1057,7 @@
 void StatsService::Startup() {
     mConfigManager->Startup();
     mProcessor->LoadActiveConfigsFromDisk();
+    mProcessor->LoadMetadataFromDisk(getWallClockNs(), getElapsedRealtimeNs());
 }
 
 void StatsService::Terminate() {
@@ -1063,6 +1065,7 @@
     if (mProcessor != nullptr) {
         mProcessor->WriteDataToDisk(TERMINATION_SIGNAL_RECEIVED, FAST);
         mProcessor->SaveActiveConfigsToDisk(getElapsedRealtimeNs());
+        mProcessor->SaveMetadataToDisk(getWallClockNs(), getElapsedRealtimeNs());
     }
 }
 
@@ -1295,20 +1298,23 @@
     if (mProcessor != nullptr) {
         ALOGW("Reset statsd upon system server restarts.");
         int64_t systemServerRestartNs = getElapsedRealtimeNs();
-        ProtoOutputStream proto;
+        ProtoOutputStream activeConfigsProto;
         mProcessor->WriteActiveConfigsToProtoOutputStream(systemServerRestartNs,
-                STATSCOMPANION_DIED, &proto);
-
+                STATSCOMPANION_DIED, &activeConfigsProto);
+        metadata::StatsMetadataList metadataList;
+        mProcessor->WriteMetadataToProto(getWallClockNs(),
+                systemServerRestartNs, &metadataList);
         mProcessor->WriteDataToDisk(STATSCOMPANION_DIED, FAST);
         mProcessor->resetConfigs();
 
         std::string serializedActiveConfigs;
-        if (proto.serializeToString(&serializedActiveConfigs)) {
+        if (activeConfigsProto.serializeToString(&serializedActiveConfigs)) {
             ActiveConfigList activeConfigs;
             if (activeConfigs.ParseFromString(serializedActiveConfigs)) {
                 mProcessor->SetConfigsActiveState(activeConfigs, systemServerRestartNs);
             }
         }
+        mProcessor->SetMetadataState(metadataList, getWallClockNs(), systemServerRestartNs);
     }
     mAnomalyAlarmMonitor->setStatsCompanionService(nullptr);
     mPeriodicAlarmMonitor->setStatsCompanionService(nullptr);
diff --git a/cmds/statsd/src/annotations.h b/cmds/statsd/src/annotations.h
new file mode 100644
index 0000000..1e9390e
--- /dev/null
+++ b/cmds/statsd/src/annotations.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+namespace android {
+namespace os {
+namespace statsd {
+
+const uint8_t ANNOTATION_ID_IS_UID = 1;
+const uint8_t ANNOTATION_ID_TRUNCATE_TIMESTAMP = 2;
+const uint8_t ANNOTATION_ID_STATE_OPTION = 3;
+const uint8_t ANNOTATION_ID_RESET_STATE = 5;
+const uint8_t ANNOTATION_ID_STATE_NESTED = 6;
+
+const int32_t STATE_OPTION_PRIMARY_FIELD = 1;
+const int32_t STATE_OPTION_EXCLUSIVE_STATE = 2;
+const int32_t STATE_OPTION_PRIMARY_FIELD_FIRST_UID = 3;
+
+} // namespace statsd
+} // namespace os
+} // namespace android
diff --git a/cmds/statsd/src/anomaly/AnomalyTracker.cpp b/cmds/statsd/src/anomaly/AnomalyTracker.cpp
index a21abbf..619752c 100644
--- a/cmds/statsd/src/anomaly/AnomalyTracker.cpp
+++ b/cmds/statsd/src/anomaly/AnomalyTracker.cpp
@@ -18,9 +18,11 @@
 #include "Log.h"
 
 #include "AnomalyTracker.h"
-#include "subscriber_util.h"
 #include "external/Perfetto.h"
 #include "guardrail/StatsdStats.h"
+#include "metadata_util.h"
+#include "stats_log_util.h"
+#include "subscriber_util.h"
 #include "subscriber/IncidentdReporter.h"
 #include "subscriber/SubscriberReporter.h"
 
@@ -262,6 +264,58 @@
     triggerSubscribers(mAlert.id(), metric_id, key, metricValue, mConfigKey, mSubscriptions);
 }
 
+bool AnomalyTracker::writeAlertMetadataToProto(int64_t currentWallClockTimeNs,
+                                               int64_t systemElapsedTimeNs,
+                                               metadata::AlertMetadata* alertMetadata) {
+    bool metadataWritten = false;
+
+    if (mRefractoryPeriodEndsSec.empty()) {
+        return false;
+    }
+
+    for (const auto& it: mRefractoryPeriodEndsSec) {
+        // Do not write the timestamp to disk if it has already expired
+        if (it.second < systemElapsedTimeNs / NS_PER_SEC) {
+            continue;
+        }
+
+        metadataWritten = true;
+        if (alertMetadata->alert_dim_keyed_data_size() == 0) {
+            alertMetadata->set_alert_id(mAlert.id());
+        }
+
+        metadata::AlertDimensionKeyedData* keyedData = alertMetadata->add_alert_dim_keyed_data();
+        // We convert and write the refractory_end_sec to wall clock time because we do not know
+        // when statsd will start again.
+        int32_t refractoryEndWallClockSec = (int32_t) ((currentWallClockTimeNs / NS_PER_SEC) +
+                (it.second - systemElapsedTimeNs / NS_PER_SEC));
+
+        keyedData->set_last_refractory_ends_sec(refractoryEndWallClockSec);
+        writeMetricDimensionKeyToMetadataDimensionKey(
+                it.first, keyedData->mutable_dimension_key());
+    }
+
+    return metadataWritten;
+}
+
+void AnomalyTracker::loadAlertMetadata(
+        const metadata::AlertMetadata& alertMetadata,
+        int64_t currentWallClockTimeNs,
+        int64_t systemElapsedTimeNs) {
+    for (const metadata::AlertDimensionKeyedData& keyedData :
+            alertMetadata.alert_dim_keyed_data()) {
+        if ((uint64_t) keyedData.last_refractory_ends_sec() < currentWallClockTimeNs / NS_PER_SEC) {
+            // Do not update the timestamp if it has already expired.
+            continue;
+        }
+        MetricDimensionKey metricKey = loadMetricDimensionKeyFromProto(
+                keyedData.dimension_key());
+        int32_t refractoryPeriodEndsSec = (int32_t) keyedData.last_refractory_ends_sec() -
+                currentWallClockTimeNs / NS_PER_SEC + systemElapsedTimeNs / NS_PER_SEC;
+        mRefractoryPeriodEndsSec[metricKey] = refractoryPeriodEndsSec;
+    }
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/statsd/src/anomaly/AnomalyTracker.h b/cmds/statsd/src/anomaly/AnomalyTracker.h
index 794ee98..bf36a3b 100644
--- a/cmds/statsd/src/anomaly/AnomalyTracker.h
+++ b/cmds/statsd/src/anomaly/AnomalyTracker.h
@@ -24,6 +24,7 @@
 #include "AlarmMonitor.h"
 #include "config/ConfigKey.h"
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"  // Alert
+#include "frameworks/base/cmds/statsd/src/statsd_metadata.pb.h"  // AlertMetadata
 #include "stats_util.h"  // HashableDimensionKey and DimToValMap
 
 namespace android {
@@ -112,6 +113,17 @@
         return; // The base AnomalyTracker class doesn't have alarms.
     }
 
+    // Writes metadata of the alert (refractory_period_end_sec) to AlertMetadata.
+    // Returns true if at least one element is written to alertMetadata.
+    bool writeAlertMetadataToProto(
+            int64_t currentWallClockTimeNs,
+            int64_t systemElapsedTimeNs, metadata::AlertMetadata* alertMetadata);
+
+    void loadAlertMetadata(
+            const metadata::AlertMetadata& alertMetadata,
+            int64_t currentWallClockTimeNs,
+            int64_t systemElapsedTimeNs);
+
 protected:
     // For testing only.
     // Returns the alarm timestamp in seconds for the query dimension if it exists. Otherwise
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index 6a4338f..5cd00c3 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -123,7 +123,8 @@
         BatteryLevelChanged battery_level_changed =
                 30 [(module) = "framework", (module) = "statsdtest"];
         ChargingStateChanged charging_state_changed = 31 [(module) = "framework"];
-        PluggedStateChanged plugged_state_changed = 32 [(module) = "framework"];
+        PluggedStateChanged plugged_state_changed = 32
+                [(module) = "framework", (module) = "statsdtest"];
         InteractiveStateChanged interactive_state_changed = 33 [(module) = "framework"];
         TouchEventReported touch_event_reported = 34;
         WakeupAlarmOccurred wakeup_alarm_occurred = 35 [(module) = "framework"];
@@ -5810,7 +5811,7 @@
  */
 message PackageNotificationPreferences {
     // Uid under which the package is installed.
-    optional int32 uid = 1;
+    optional int32 uid = 1 [(is_uid) = true];
     // Notification importance, which specifies when and how a notification is displayed.
     // Specified under core/java/android/app/NotificationManager.java.
     optional int32 importance = 2;
@@ -5827,7 +5828,7 @@
  */
 message PackageNotificationChannelPreferences {
     // Uid under which the package is installed.
-    optional int32 uid = 1;
+    optional int32 uid = 1 [(is_uid) = true];
     // Channel's ID. Should always be available.
     optional string channel_id = 2;
     // Channel's name. Should always be available.
@@ -5850,7 +5851,7 @@
  */
 message PackageNotificationChannelGroupPreferences {
     // Uid under which the package is installed.
-    optional int32 uid = 1;
+    optional int32 uid = 1 [(is_uid) = true];
     // Channel Group's ID. Should always be available.
     optional string group_id = 2;
     // Channel Group's name. Should always be available.
diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp
index a3701a7..79315eb 100644
--- a/cmds/statsd/src/external/StatsPullerManager.cpp
+++ b/cmds/statsd/src/external/StatsPullerManager.cpp
@@ -41,6 +41,40 @@
 namespace os {
 namespace statsd {
 
+// Stores the puller as a wp to avoid holding a reference in case it is unregistered and
+// pullAtomCallbackDied is never called.
+struct PullAtomCallbackDeathCookie {
+    PullAtomCallbackDeathCookie(sp<StatsPullerManager> pullerManager, const PullerKey& pullerKey,
+                                const wp<StatsPuller>& puller)
+        : mPullerManager(pullerManager), mPullerKey(pullerKey), mPuller(puller) {
+    }
+
+    sp<StatsPullerManager> mPullerManager;
+    PullerKey mPullerKey;
+    wp<StatsPuller> mPuller;
+};
+
+void StatsPullerManager::pullAtomCallbackDied(void* cookie) {
+    PullAtomCallbackDeathCookie* cookie_ = static_cast<PullAtomCallbackDeathCookie*>(cookie);
+    sp<StatsPullerManager>& thiz = cookie_->mPullerManager;
+    const PullerKey& pullerKey = cookie_->mPullerKey;
+    wp<StatsPuller> puller = cookie_->mPuller;
+
+    // Erase the mapping from the puller key to the puller if the mapping still exists.
+    // Note that we are removing the StatsPuller object, which internally holds the binder
+    // IPullAtomCallback. However, each new registration creates a new StatsPuller, so this works.
+    lock_guard<mutex> lock(thiz->mLock);
+    const auto& it = thiz->kAllPullAtomInfo.find(pullerKey);
+    if (it != thiz->kAllPullAtomInfo.end() && puller != nullptr && puller == it->second) {
+        StatsdStats::getInstance().notePullerCallbackRegistrationChanged(pullerKey.atomTag,
+                                                                         /*registered=*/false);
+        thiz->kAllPullAtomInfo.erase(pullerKey);
+    }
+    // The death recipient corresponding to this specific IPullAtomCallback can never
+    // be triggered again, so free up resources.
+    delete cookie_;
+}
+
 // Values smaller than this may require to update the alarm.
 const int64_t NO_ALARM_UPDATE = INT64_MAX;
 
@@ -49,7 +83,8 @@
               // TrainInfo.
               {{.atomTag = util::TRAIN_INFO, .uid = -1}, new TrainInfoPuller()},
       }),
-      mNextPullTimeNs(NO_ALARM_UPDATE) {
+      mNextPullTimeNs(NO_ALARM_UPDATE),
+      mPullAtomCallbackDeathRecipient(AIBinder_DeathRecipient_new(pullAtomCallbackDied)) {
 }
 
 bool StatsPullerManager::Pull(int tagId, const ConfigKey& configKey,
@@ -310,19 +345,28 @@
                                                   bool useUid) {
     std::lock_guard<std::mutex> _l(mLock);
     VLOG("RegisterPullerCallback: adding puller for tag %d", atomTag);
-    // TODO(b/146439412): linkToDeath with the callback so that we can remove it
-    // and delete the puller.
+
     StatsdStats::getInstance().notePullerCallbackRegistrationChanged(atomTag, /*registered=*/true);
     int64_t actualCoolDownNs = coolDownNs < kMinCoolDownNs ? kMinCoolDownNs : coolDownNs;
     int64_t actualTimeoutNs = timeoutNs > kMaxTimeoutNs ? kMaxTimeoutNs : timeoutNs;
-    kAllPullAtomInfo[{.atomTag = atomTag, .uid = useUid ? uid : -1}] = new StatsCallbackPuller(
-            atomTag, callback, actualCoolDownNs, actualTimeoutNs, additiveFields);
+
+    sp<StatsCallbackPuller> puller = new StatsCallbackPuller(atomTag, callback, actualCoolDownNs,
+                                                             actualTimeoutNs, additiveFields);
+    PullerKey key = {.atomTag = atomTag, .uid = useUid ? uid : -1};
+    AIBinder_linkToDeath(callback->asBinder().get(), mPullAtomCallbackDeathRecipient.get(),
+                         new PullAtomCallbackDeathCookie(this, key, puller));
+    kAllPullAtomInfo[key] = puller;
 }
 
-void StatsPullerManager::UnregisterPullAtomCallback(const int uid, const int32_t atomTag) {
+void StatsPullerManager::UnregisterPullAtomCallback(const int uid, const int32_t atomTag,
+                                                    bool useUids) {
     std::lock_guard<std::mutex> _l(mLock);
-    StatsdStats::getInstance().notePullerCallbackRegistrationChanged(atomTag, /*registered=*/false);
-    kAllPullAtomInfo.erase({.atomTag = atomTag});
+    PullerKey key = {.atomTag = atomTag, .uid = useUids ? uid : -1};
+    if (kAllPullAtomInfo.find(key) != kAllPullAtomInfo.end()) {
+        StatsdStats::getInstance().notePullerCallbackRegistrationChanged(atomTag,
+                                                                         /*registered=*/false);
+        kAllPullAtomInfo.erase(key);
+    }
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/external/StatsPullerManager.h b/cmds/statsd/src/external/StatsPullerManager.h
index c5824a8..e679837 100644
--- a/cmds/statsd/src/external/StatsPullerManager.h
+++ b/cmds/statsd/src/external/StatsPullerManager.h
@@ -120,7 +120,7 @@
                                   const shared_ptr<IPullAtomCallback>& callback,
                                   bool useUid = false);
 
-    void UnregisterPullAtomCallback(const int uid, const int32_t atomTag);
+    void UnregisterPullAtomCallback(const int uid, const int32_t atomTag, bool useUids = false);
 
     std::map<const PullerKey, sp<StatsPuller>> kAllPullAtomInfo;
 
@@ -164,6 +164,15 @@
 
     int64_t mNextPullTimeNs;
 
+    // Death recipient that is triggered when the process holding the IPullAtomCallback has died.
+    ::ndk::ScopedAIBinder_DeathRecipient mPullAtomCallbackDeathRecipient;
+
+    /**
+     * Death recipient callback that is called when a pull atom callback dies.
+     * The cookie is a pointer to a PullAtomCallbackDeathCookie.
+     */
+    static void pullAtomCallbackDied(void* cookie);
+
     FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEvents);
     FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEvent_LateAlarm);
     FRIEND_TEST(GaugeMetricE2eTest, TestRandomSamplePulledEventsWithActivation);
diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp
index 3b3d0b6..a6ae389 100644
--- a/cmds/statsd/src/logd/LogEvent.cpp
+++ b/cmds/statsd/src/logd/LogEvent.cpp
@@ -17,6 +17,7 @@
 #define DEBUG false  // STOPSHIP if true
 #include "logd/LogEvent.h"
 
+#include "annotations.h"
 #include "stats_log_util.h"
 #include "statslog_statsd.h"
 
@@ -447,6 +448,7 @@
 
 void LogEvent::parseAttributionChain(int32_t* pos, int32_t depth, bool* last,
                                      uint8_t numAnnotations) {
+    int firstUidInChainIndex = mValues.size();
     int32_t numNodes = readNextValue<uint8_t>();
     for (pos[1] = 1; pos[1] <= numNodes; pos[1]++) {
         last[1] = (pos[1] == numNodes);
@@ -461,26 +463,103 @@
         parseString(pos, /*depth=*/2, last, /*numAnnotations=*/0);
     }
 
-    parseAnnotations(numAnnotations);
+    parseAnnotations(numAnnotations, firstUidInChainIndex);
 
     pos[1] = pos[2] = 1;
     last[1] = last[2] = false;
 }
 
-// TODO(b/151109630): store annotation information within LogEvent
-void LogEvent::parseAnnotations(uint8_t numAnnotations) {
+void LogEvent::parseIsUidAnnotation(uint8_t annotationType) {
+    if (mValues.empty() || annotationType != BOOL_TYPE) {
+        mValid = false;
+        return;
+    }
+
+    bool isUid = readNextValue<uint8_t>();
+    if (isUid) mUidFieldIndex = mValues.size() - 1;
+}
+
+void LogEvent::parseTruncateTimestampAnnotation(uint8_t annotationType) {
+    if (!mValues.empty() || annotationType != BOOL_TYPE) {
+        mValid = false;
+        return;
+    }
+
+    mTruncateTimestamp = readNextValue<uint8_t>();
+}
+
+void LogEvent::parseStateOptionAnnotation(uint8_t annotationType, int firstUidInChainIndex) {
+    if (mValues.empty() || annotationType != INT32_TYPE) {
+        mValid = false;
+        return;
+    }
+
+    int32_t stateOption = readNextValue<int32_t>();
+    switch (stateOption) {
+        case STATE_OPTION_EXCLUSIVE_STATE:
+            mValues[mValues.size() - 1].mAnnotations.setExclusiveState(true);
+            break;
+        case STATE_OPTION_PRIMARY_FIELD:
+            mValues[mValues.size() - 1].mAnnotations.setPrimaryField(true);
+            break;
+        case STATE_OPTION_PRIMARY_FIELD_FIRST_UID:
+            if (firstUidInChainIndex == -1) {
+                mValid = false;
+            } else {
+                mValues[firstUidInChainIndex].mAnnotations.setPrimaryField(true);
+            }
+            break;
+        default:
+            mValid = false;
+    }
+}
+
+void LogEvent::parseResetStateAnnotation(uint8_t annotationType) {
+    if (mValues.empty() || annotationType != INT32_TYPE) {
+        mValid = false;
+        return;
+    }
+
+    int32_t resetState = readNextValue<int32_t>();
+    mValues[mValues.size() - 1].mAnnotations.setResetState(resetState);
+}
+
+void LogEvent::parseStateNestedAnnotation(uint8_t annotationType) {
+    if (mValues.empty() || annotationType != BOOL_TYPE) {
+        mValid = false;
+        return;
+    }
+
+    bool nested = readNextValue<uint8_t>();
+    mValues[mValues.size() - 1].mAnnotations.setNested(nested);
+}
+
+// firstUidInChainIndex is a default parameter that is only needed when parsing
+// annotations for attribution chains.
+void LogEvent::parseAnnotations(uint8_t numAnnotations, int firstUidInChainIndex) {
     for (uint8_t i = 0; i < numAnnotations; i++) {
-        /*uint8_t annotationId = */ readNextValue<uint8_t>();
+        uint8_t annotationId = readNextValue<uint8_t>();
         uint8_t annotationType = readNextValue<uint8_t>();
-        switch (annotationType) {
-            case BOOL_TYPE:
-                /*bool annotationValue = */ readNextValue<uint8_t>();
+
+        switch (annotationId) {
+            case ANNOTATION_ID_IS_UID:
+                parseIsUidAnnotation(annotationType);
                 break;
-            case INT32_TYPE:
-                /*int32_t annotationValue =*/ readNextValue<int32_t>();
+            case ANNOTATION_ID_TRUNCATE_TIMESTAMP:
+                parseTruncateTimestampAnnotation(annotationType);
+                break;
+            case ANNOTATION_ID_STATE_OPTION:
+                parseStateOptionAnnotation(annotationType, firstUidInChainIndex);
+                break;
+            case ANNOTATION_ID_RESET_STATE:
+                parseResetStateAnnotation(annotationType);
+                break;
+            case ANNOTATION_ID_STATE_NESTED:
+                parseStateNestedAnnotation(annotationType);
                 break;
             default:
                 mValid = false;
+                return;
         }
     }
 }
@@ -509,8 +588,8 @@
     typeInfo = readNextValue<uint8_t>();
     if (getTypeId(typeInfo) != INT32_TYPE) mValid = false;
     mTagId = readNextValue<int32_t>();
-    parseAnnotations(getNumAnnotations(typeInfo)); // atom-level annotations
     numElements--;
+    parseAnnotations(getNumAnnotations(typeInfo)); // atom-level annotations
 
 
     for (pos[0] = 1; pos[0] <= numElements && mValid; pos[0]++) {
@@ -544,6 +623,7 @@
                 break;
             case ATTRIBUTION_CHAIN_TYPE:
                 parseAttributionChain(pos, /*depth=*/0, last, getNumAnnotations(typeInfo));
+                if (mAttributionChainIndex == -1) mAttributionChainIndex = pos[0];
                 break;
             default:
                 mValid = false;
diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h
index 6537f13..0a89be4 100644
--- a/cmds/statsd/src/logd/LogEvent.h
+++ b/cmds/statsd/src/logd/LogEvent.h
@@ -206,6 +206,32 @@
         return &mValues;
     }
 
+    // Default value = false
+    inline bool shouldTruncateTimestamp() {
+        return mTruncateTimestamp;
+    }
+
+    // Returns the index of the uid field within the FieldValues vector if the
+    // uid exists. If there is no uid field, returns -1.
+    //
+    // If the index within the atom definition is desired, do the following:
+    //    int vectorIndex = LogEvent.getUidFieldIndex();
+    //    if (vectorIndex != -1) {
+    //        FieldValue& v = LogEvent.getValues()[vectorIndex];
+    //        int atomIndex = v.mField.getPosAtDepth(0);
+    //    }
+    // Note that atomIndex is 1-indexed.
+    inline int getUidFieldIndex() {
+        return mUidFieldIndex;
+    }
+
+    // Returns the index of (the first) attribution chain within the atom
+    // definition. Note that the value is 1-indexed. If there is no attribution
+    // chain, returns -1.
+    inline int getAttributionChainIndex() {
+        return mAttributionChainIndex;
+    }
+
     inline LogEvent makeCopy() {
         return LogEvent(*this);
     }
@@ -240,7 +266,13 @@
     void parseByteArray(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations);
     void parseKeyValuePairs(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations);
     void parseAttributionChain(int32_t* pos, int32_t depth, bool* last, uint8_t numAnnotations);
-    void parseAnnotations(uint8_t numAnnotations);
+
+    void parseAnnotations(uint8_t numAnnotations, int firstUidInChainIndex = -1);
+    void parseIsUidAnnotation(uint8_t annotationType);
+    void parseTruncateTimestampAnnotation(uint8_t annotationType);
+    void parseStateOptionAnnotation(uint8_t annotationType, int firstUidInChainIndex);
+    void parseResetStateAnnotation(uint8_t annotationType);
+    void parseStateNestedAnnotation(uint8_t annotationType);
 
     /**
      * The below three variables are only valid during the execution of
@@ -322,6 +354,11 @@
 
     // The pid of the logging client (defaults to -1).
     int32_t mLogPid = -1;
+
+    // Annotations
+    bool mTruncateTimestamp = false;
+    int mUidFieldIndex = -1;
+    int mAttributionChainIndex = -1;
 };
 
 void writeExperimentIdsToProto(const std::vector<int64_t>& experimentIds, std::vector<uint8_t>* protoOut);
diff --git a/cmds/statsd/src/metadata_util.cpp b/cmds/statsd/src/metadata_util.cpp
new file mode 100644
index 0000000..27ee59b
--- /dev/null
+++ b/cmds/statsd/src/metadata_util.cpp
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2020 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 "FieldValue.h"
+#include "metadata_util.h"
+
+namespace android {
+namespace os {
+namespace statsd {
+
+using google::protobuf::RepeatedPtrField;
+
+void writeValueToProto(metadata::FieldValue* metadataFieldValue, const Value& value) {
+    std::string storage_value;
+    switch (value.getType()) {
+        case INT:
+            metadataFieldValue->set_value_int(value.int_value);
+            break;
+        case LONG:
+            metadataFieldValue->set_value_long(value.long_value);
+            break;
+        case FLOAT:
+            metadataFieldValue->set_value_float(value.float_value);
+            break;
+        case DOUBLE:
+            metadataFieldValue->set_value_double(value.double_value);
+            break;
+        case STRING:
+            metadataFieldValue->set_value_str(value.str_value.c_str());
+            break;
+        case STORAGE: // byte array
+            storage_value = ((char*) value.storage_value.data());
+            metadataFieldValue->set_value_storage(storage_value);
+            break;
+        default:
+            break;
+    }
+}
+
+void writeMetricDimensionKeyToMetadataDimensionKey(
+        const MetricDimensionKey& metricKey,
+        metadata::MetricDimensionKey* metadataMetricKey) {
+    for (const FieldValue& fieldValue : metricKey.getDimensionKeyInWhat().getValues()) {
+        metadata::FieldValue* metadataFieldValue = metadataMetricKey->add_dimension_key_in_what();
+        metadata::Field* metadataField = metadataFieldValue->mutable_field();
+        metadataField->set_tag(fieldValue.mField.getTag());
+        metadataField->set_field(fieldValue.mField.getField());
+        writeValueToProto(metadataFieldValue, fieldValue.mValue);
+    }
+
+    for (const FieldValue& fieldValue : metricKey.getStateValuesKey().getValues()) {
+        metadata::FieldValue* metadataFieldValue = metadataMetricKey->add_state_values_key();
+        metadata::Field* metadataField = metadataFieldValue->mutable_field();
+        metadataField->set_tag(fieldValue.mField.getTag());
+        metadataField->set_field(fieldValue.mField.getField());
+        writeValueToProto(metadataFieldValue, fieldValue.mValue);
+    }
+}
+
+void writeFieldValuesFromMetadata(
+        const RepeatedPtrField<metadata::FieldValue>& repeatedFieldValueList,
+        std::vector<FieldValue>* fieldValues) {
+    for (const metadata::FieldValue& metadataFieldValue : repeatedFieldValueList) {
+        Field field(metadataFieldValue.field().tag(), metadataFieldValue.field().field());
+        Value value;
+        switch (metadataFieldValue.value_case()) {
+            case metadata::FieldValue::ValueCase::kValueInt:
+                value = Value(metadataFieldValue.value_int());
+                break;
+            case metadata::FieldValue::ValueCase::kValueLong:
+                value = Value(metadataFieldValue.value_long());
+                break;
+            case metadata::FieldValue::ValueCase::kValueFloat:
+                value = Value(metadataFieldValue.value_float());
+                break;
+            case metadata::FieldValue::ValueCase::kValueDouble:
+                value = Value(metadataFieldValue.value_double());
+                break;
+            case metadata::FieldValue::ValueCase::kValueStr:
+                value = Value(metadataFieldValue.value_str());
+                break;
+            case metadata::FieldValue::ValueCase::kValueStorage:
+                value = Value(metadataFieldValue.value_storage());
+                break;
+            default:
+                break;
+        }
+        FieldValue fieldValue(field, value);
+        fieldValues->emplace_back(field, value);
+    }
+}
+
+MetricDimensionKey loadMetricDimensionKeyFromProto(
+        const metadata::MetricDimensionKey& metricDimensionKey) {
+    std::vector<FieldValue> dimKeyInWhatFieldValues;
+    writeFieldValuesFromMetadata(metricDimensionKey.dimension_key_in_what(),
+            &dimKeyInWhatFieldValues);
+    std::vector<FieldValue> stateValuesFieldValues;
+    writeFieldValuesFromMetadata(metricDimensionKey.state_values_key(), &stateValuesFieldValues);
+
+    HashableDimensionKey dimKeyInWhat(dimKeyInWhatFieldValues);
+    HashableDimensionKey stateValues(stateValuesFieldValues);
+    MetricDimensionKey metricKey(dimKeyInWhat, stateValues);
+    return metricKey;
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/src/metadata_util.h b/cmds/statsd/src/metadata_util.h
new file mode 100644
index 0000000..84a39ff
--- /dev/null
+++ b/cmds/statsd/src/metadata_util.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2020 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 "HashableDimensionKey.h"
+
+#include "frameworks/base/cmds/statsd/src/statsd_metadata.pb.h"  // AlertMetadata
+
+namespace android {
+namespace os {
+namespace statsd {
+
+void writeMetricDimensionKeyToMetadataDimensionKey(const MetricDimensionKey& metricKey,
+                                                   metadata::MetricDimensionKey* metadataMetricKey);
+
+MetricDimensionKey loadMetricDimensionKeyFromProto(
+        const metadata::MetricDimensionKey& metricDimensionKey);
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
\ No newline at end of file
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.cpp b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
index e85b975..0de92f3 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.cpp
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.cpp
@@ -55,6 +55,7 @@
 const int FIELD_ID_DIMENSION_IN_WHAT = 1;
 const int FIELD_ID_BUCKET_INFO = 3;
 const int FIELD_ID_DIMENSION_LEAF_IN_WHAT = 4;
+const int FIELD_ID_SLICE_BY_STATE = 6;
 // for DurationBucketInfo
 const int FIELD_ID_DURATION = 3;
 const int FIELD_ID_BUCKET_NUM = 4;
@@ -115,6 +116,14 @@
     }
     mUnSlicedPartCondition = ConditionState::kUnknown;
 
+    for (const auto& stateLink : metric.state_link()) {
+        Metric2State ms;
+        ms.stateAtomId = stateLink.state_atom_id();
+        translateFieldMatcher(stateLink.fields_in_what(), &ms.metricFields);
+        translateFieldMatcher(stateLink.fields_in_state(), &ms.stateFields);
+        mMetric2StateLinks.push_back(ms);
+    }
+
     mUseWhatDimensionAsInternalDimension = equalDimensions(mDimensionsInWhat, mInternalDimensions);
     if (mWizard != nullptr && mConditionTrackerIndex >= 0 &&
             mMetric2ConditionLinks.size() == 1) {
@@ -150,21 +159,49 @@
     return anomalyTracker;
 }
 
+void DurationMetricProducer::onStateChanged(const int64_t eventTimeNs, const int32_t atomId,
+                                            const HashableDimensionKey& primaryKey,
+                                            const int32_t oldState, const int32_t newState) {
+    // Create a FieldValue object to hold the new state.
+    FieldValue value;
+    value.mValue.setInt(newState);
+    // Check if this metric has a StateMap. If so, map the new state value to
+    // the correct state group id.
+    mapStateValue(atomId, &value);
+
+    flushIfNeededLocked(eventTimeNs);
+
+    // Each duration tracker is mapped to a different whatKey (a set of values from the
+    // dimensionsInWhat fields). We notify all trackers iff the primaryKey field values from the
+    // state change event are a subset of the tracker's whatKey field values.
+    //
+    // Ex. For a duration metric dimensioned on uid and tag:
+    // DurationTracker1 whatKey = uid: 1001, tag: 1
+    // DurationTracker2 whatKey = uid: 1002, tag 1
+    //
+    // If the state change primaryKey = uid: 1001, we only notify DurationTracker1 of a state
+    // change.
+    for (auto& whatIt : mCurrentSlicedDurationTrackerMap) {
+        if (!containsLinkedStateValues(whatIt.first, primaryKey, mMetric2StateLinks, atomId)) {
+            continue;
+        }
+        whatIt.second->onStateChanged(eventTimeNs, atomId, value);
+    }
+}
+
 unique_ptr<DurationTracker> DurationMetricProducer::createDurationTracker(
         const MetricDimensionKey& eventKey) const {
     switch (mAggregationType) {
         case DurationMetric_AggregationType_SUM:
             return make_unique<OringDurationTracker>(
-                    mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex,
-                    mNested, mCurrentBucketStartTimeNs, mCurrentBucketNum,
-                    mTimeBaseNs, mBucketSizeNs, mConditionSliced,
-                    mHasLinksToAllConditionDimensionsInTracker, mAnomalyTrackers);
+                    mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex, mNested,
+                    mCurrentBucketStartTimeNs, mCurrentBucketNum, mTimeBaseNs, mBucketSizeNs,
+                    mConditionSliced, mHasLinksToAllConditionDimensionsInTracker, mAnomalyTrackers);
         case DurationMetric_AggregationType_MAX_SPARSE:
             return make_unique<MaxDurationTracker>(
-                    mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex,
-                    mNested, mCurrentBucketStartTimeNs, mCurrentBucketNum,
-                    mTimeBaseNs, mBucketSizeNs, mConditionSliced,
-                    mHasLinksToAllConditionDimensionsInTracker, mAnomalyTrackers);
+                    mConfigKey, mMetricId, eventKey, mWizard, mConditionTrackerIndex, mNested,
+                    mCurrentBucketStartTimeNs, mCurrentBucketNum, mTimeBaseNs, mBucketSizeNs,
+                    mConditionSliced, mHasLinksToAllConditionDimensionsInTracker, mAnomalyTrackers);
     }
 }
 
@@ -364,6 +401,13 @@
             writeDimensionLeafNodesToProto(dimensionKey.getDimensionKeyInWhat(),
                                            FIELD_ID_DIMENSION_LEAF_IN_WHAT, str_set, protoOutput);
         }
+        // Then fill slice_by_state.
+        for (auto state : dimensionKey.getStateValuesKey().getValues()) {
+            uint64_t stateToken = protoOutput->start(FIELD_TYPE_MESSAGE | FIELD_COUNT_REPEATED |
+                                                     FIELD_ID_SLICE_BY_STATE);
+            writeStateToProto(state, protoOutput);
+            protoOutput->end(stateToken);
+        }
         // Then fill bucket_info (DurationBucketInfo).
         for (const auto& bucket : pair.second) {
             uint64_t bucketInfoToken = protoOutput->start(
@@ -460,7 +504,6 @@
                                               const ConditionKey& conditionKeys,
                                               bool condition, const LogEvent& event) {
     const auto& whatKey = eventKey.getDimensionKeyInWhat();
-
     auto whatIt = mCurrentSlicedDurationTrackerMap.find(whatKey);
     if (whatIt == mCurrentSlicedDurationTrackerMap.end()) {
         if (hitGuardRailLocked(eventKey)) {
@@ -471,19 +514,18 @@
 
     auto it = mCurrentSlicedDurationTrackerMap.find(whatKey);
     if (mUseWhatDimensionAsInternalDimension) {
-        it->second->noteStart(whatKey, condition,
-                              event.GetElapsedTimestampNs(), conditionKeys);
+        it->second->noteStart(whatKey, condition, event.GetElapsedTimestampNs(), conditionKeys);
         return;
     }
 
     if (mInternalDimensions.empty()) {
-        it->second->noteStart(DEFAULT_DIMENSION_KEY, condition,
-                              event.GetElapsedTimestampNs(), conditionKeys);
+        it->second->noteStart(DEFAULT_DIMENSION_KEY, condition, event.GetElapsedTimestampNs(),
+                              conditionKeys);
     } else {
         HashableDimensionKey dimensionKey = DEFAULT_DIMENSION_KEY;
         filterValues(mInternalDimensions, event.getValues(), &dimensionKey);
-        it->second->noteStart(
-            dimensionKey, condition, event.GetElapsedTimestampNs(), conditionKeys);
+        it->second->noteStart(dimensionKey, condition, event.GetElapsedTimestampNs(),
+                              conditionKeys);
     }
 
 }
@@ -519,6 +561,41 @@
         filterValues(mDimensionsInWhat, event.getValues(), &dimensionInWhat);
     }
 
+    // Stores atom id to primary key pairs for each state atom that the metric is
+    // sliced by.
+    std::map<int, HashableDimensionKey> statePrimaryKeys;
+
+    // For states with primary fields, use MetricStateLinks to get the primary
+    // field values from the log event. These values will form a primary key
+    // that will be used to query StateTracker for the correct state value.
+    for (const auto& stateLink : mMetric2StateLinks) {
+        getDimensionForState(event.getValues(), stateLink,
+                             &statePrimaryKeys[stateLink.stateAtomId]);
+    }
+
+    // For each sliced state, query StateTracker for the state value using
+    // either the primary key from the previous step or the DEFAULT_DIMENSION_KEY.
+    //
+    // Expected functionality: for any case where the MetricStateLinks are
+    // initialized incorrectly (ex. # of state links != # of primary fields, no
+    // links are provided for a state with primary fields, links are provided
+    // in the wrong order, etc.), StateTracker will simply return kStateUnknown
+    // when queried using an incorrect key.
+    HashableDimensionKey stateValuesKey = DEFAULT_DIMENSION_KEY;
+    for (auto atomId : mSlicedStateAtoms) {
+        FieldValue value;
+        if (statePrimaryKeys.find(atomId) != statePrimaryKeys.end()) {
+            // found a primary key for this state, query using the key
+            queryStateValue(atomId, statePrimaryKeys[atomId], &value);
+        } else {
+            // if no MetricStateLinks exist for this state atom,
+            // query using the default dimension key (empty HashableDimensionKey)
+            queryStateValue(atomId, DEFAULT_DIMENSION_KEY, &value);
+        }
+        mapStateValue(atomId, &value);
+        stateValuesKey.addValue(value);
+    }
+
     // Handles Stop events.
     if (matcherIndex == mStopIndex) {
         if (mUseWhatDimensionAsInternalDimension) {
@@ -559,8 +636,8 @@
 
     condition = condition && mIsActive;
 
-    handleStartEvent(MetricDimensionKey(dimensionInWhat, DEFAULT_DIMENSION_KEY), conditionKey,
-                     condition, event);
+    handleStartEvent(MetricDimensionKey(dimensionInWhat, stateValuesKey), conditionKey, condition,
+                     event);
 }
 
 size_t DurationMetricProducer::byteSizeLocked() const {
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h
index 06da0f6..cc48f99 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.h
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.h
@@ -54,6 +54,10 @@
     sp<AnomalyTracker> addAnomalyTracker(const Alert &alert,
                                          const sp<AlarmMonitor>& anomalyAlarmMonitor) override;
 
+    void onStateChanged(const int64_t eventTimeNs, const int32_t atomId,
+                        const HashableDimensionKey& primaryKey, const int32_t oldState,
+                        const int32_t newState) override;
+
 protected:
     void onMatchedLogEventLocked(const size_t matcherIndex, const LogEvent& event) override;
 
@@ -137,7 +141,7 @@
 
     // Helper function to create a duration tracker given the metric aggregation type.
     std::unique_ptr<DurationTracker> createDurationTracker(
-        const MetricDimensionKey& eventKey) const;
+            const MetricDimensionKey& eventKey) const;
 
     // This hides the base class's std::vector<sp<AnomalyTracker>> mAnomalyTrackers
     std::vector<sp<DurationAnomalyTracker>> mAnomalyTrackers;
diff --git a/cmds/statsd/src/metrics/MetricProducer.cpp b/cmds/statsd/src/metrics/MetricProducer.cpp
index be754e2..2518d85 100644
--- a/cmds/statsd/src/metrics/MetricProducer.cpp
+++ b/cmds/statsd/src/metrics/MetricProducer.cpp
@@ -120,12 +120,13 @@
         FieldValue value;
         if (statePrimaryKeys.find(atomId) != statePrimaryKeys.end()) {
             // found a primary key for this state, query using the key
-            getMappedStateValue(atomId, statePrimaryKeys[atomId], &value);
+            queryStateValue(atomId, statePrimaryKeys[atomId], &value);
         } else {
             // if no MetricStateLinks exist for this state atom,
             // query using the default dimension key (empty HashableDimensionKey)
-            getMappedStateValue(atomId, DEFAULT_DIMENSION_KEY, &value);
+            queryStateValue(atomId, DEFAULT_DIMENSION_KEY, &value);
         }
+        mapStateValue(atomId, &value);
         stateValuesKey.addValue(value);
     }
 
@@ -264,15 +265,17 @@
     }
 }
 
-void MetricProducer::getMappedStateValue(const int32_t atomId, const HashableDimensionKey& queryKey,
-                                         FieldValue* value) {
+void MetricProducer::queryStateValue(const int32_t atomId, const HashableDimensionKey& queryKey,
+                                     FieldValue* value) {
     if (!StateManager::getInstance().getStateValue(atomId, queryKey, value)) {
         value->mValue = Value(StateTracker::kStateUnknown);
         value->mField.setTag(atomId);
         ALOGW("StateTracker not found for state atom %d", atomId);
         return;
     }
+}
 
+void MetricProducer::mapStateValue(const int32_t atomId, FieldValue* value) {
     // check if there is a state map for this atom
     auto atomIt = mStateGroupMap.find(atomId);
     if (atomIt == mStateGroupMap.end()) {
diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h
index 4c4cd89..4550e65 100644
--- a/cmds/statsd/src/metrics/MetricProducer.h
+++ b/cmds/statsd/src/metrics/MetricProducer.h
@@ -187,7 +187,8 @@
     };
 
     void onStateChanged(const int64_t eventTimeNs, const int32_t atomId,
-                        const HashableDimensionKey& primaryKey, int oldState, int newState){};
+                        const HashableDimensionKey& primaryKey, const int32_t oldState,
+                        const int32_t newState){};
 
     // Output the metrics data to [protoOutput]. All metrics reports end with the same timestamp.
     // This method clears all the past buckets.
@@ -379,11 +380,15 @@
         return (endNs - mTimeBaseNs) / mBucketSizeNs - 1;
     }
 
-    // Query StateManager for original state value.
-    // If no state map exists for this atom, return the original value.
-    // Otherwise, return the group_id mapped to the atom and original value.
-    void getMappedStateValue(const int32_t atomId, const HashableDimensionKey& queryKey,
-                             FieldValue* value);
+    // Query StateManager for original state value using the queryKey.
+    // The field and value are output.
+    void queryStateValue(const int32_t atomId, const HashableDimensionKey& queryKey,
+                         FieldValue* value);
+
+    // If a state map exists for the given atom, replace the original state
+    // value with the group id mapped to the value.
+    // If no state map exists, keep the original state value.
+    void mapStateValue(const int32_t atomId, FieldValue* value);
 
     DropEvent buildDropEvent(const int64_t dropTimeNs, const BucketDropReason reason);
 
@@ -467,6 +472,11 @@
     FRIEND_TEST(DurationMetricE2eTest, TestWithCondition);
     FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedCondition);
     FRIEND_TEST(DurationMetricE2eTest, TestWithActivationAndSlicedCondition);
+    FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedState);
+    FRIEND_TEST(DurationMetricE2eTest, TestWithConditionAndSlicedState);
+    FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStateMapped);
+    FRIEND_TEST(DurationMetricE2eTest, TestSlicedStatePrimaryFieldsNotSubsetDimInWhat);
+    FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSubset);
 
     FRIEND_TEST(MetricActivationE2eTest, TestCountMetric);
     FRIEND_TEST(MetricActivationE2eTest, TestCountMetricWithOneDeactivation);
diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp
index 8ed0cbc..d832ed8 100644
--- a/cmds/statsd/src/metrics/MetricsManager.cpp
+++ b/cmds/statsd/src/metrics/MetricsManager.cpp
@@ -642,8 +642,40 @@
     }
 }
 
+bool MetricsManager::writeMetadataToProto(int64_t currentWallClockTimeNs,
+                                          int64_t systemElapsedTimeNs,
+                                          metadata::StatsMetadata* statsMetadata) {
+    bool metadataWritten = false;
+    metadata::ConfigKey* configKey = statsMetadata->mutable_config_key();
+    configKey->set_config_id(mConfigKey.GetId());
+    configKey->set_uid(mConfigKey.GetUid());
+    for (const auto& anomalyTracker : mAllAnomalyTrackers) {
+        metadata::AlertMetadata* alertMetadata = statsMetadata->add_alert_metadata();
+        bool alertWritten = anomalyTracker->writeAlertMetadataToProto(currentWallClockTimeNs,
+                systemElapsedTimeNs, alertMetadata);
+        if (!alertWritten) {
+            statsMetadata->mutable_alert_metadata()->RemoveLast();
+        }
+        metadataWritten |= alertWritten;
+    }
+    return metadataWritten;
+}
 
-
+void MetricsManager::loadMetadata(const metadata::StatsMetadata& metadata,
+                                  int64_t currentWallClockTimeNs,
+                                  int64_t systemElapsedTimeNs) {
+    for (const metadata::AlertMetadata& alertMetadata : metadata.alert_metadata()) {
+        int64_t alertId = alertMetadata.alert_id();
+        auto it = mAlertTrackerMap.find(alertId);
+        if (it == mAlertTrackerMap.end()) {
+            ALOGE("No anomalyTracker found for alertId %lld", (long long) alertId);
+            continue;
+        }
+        mAllAnomalyTrackers[it->second]->loadAlertMetadata(alertMetadata,
+                                                           currentWallClockTimeNs,
+                                                           systemElapsedTimeNs);
+    }
+}
 
 }  // namespace statsd
 }  // namespace os
diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h
index 291f97b..1fd6572 100644
--- a/cmds/statsd/src/metrics/MetricsManager.h
+++ b/cmds/statsd/src/metrics/MetricsManager.h
@@ -23,6 +23,7 @@
 #include "config/ConfigKey.h"
 #include "external/StatsPullerManager.h"
 #include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include "frameworks/base/cmds/statsd/src/statsd_metadata.pb.h"
 #include "logd/LogEvent.h"
 #include "matchers/LogMatchingTracker.h"
 #include "metrics/MetricProducer.h"
@@ -143,6 +144,14 @@
     void writeActiveConfigToProtoOutputStream(
             int64_t currentTimeNs, const DumpReportReason reason, ProtoOutputStream* proto);
 
+    // Returns true if at least one piece of metadata is written.
+    bool writeMetadataToProto(int64_t currentWallClockTimeNs,
+                              int64_t systemElapsedTimeNs,
+                              metadata::StatsMetadata* statsMetadata);
+
+    void loadMetadata(const metadata::StatsMetadata& metadata,
+                      int64_t currentWallClockTimeNs,
+                      int64_t systemElapsedTimeNs);
 private:
     // For test only.
     inline int64_t getTtlEndNs() const { return mTtlEndNs; }
@@ -285,6 +294,9 @@
 
     FRIEND_TEST(AnomalyDetectionE2eTest, TestSlicedCountMetric_single_bucket);
     FRIEND_TEST(AnomalyDetectionE2eTest, TestSlicedCountMetric_multiple_buckets);
+    FRIEND_TEST(AnomalyDetectionE2eTest, TestCountMetric_save_refractory_to_disk_no_data_written);
+    FRIEND_TEST(AnomalyDetectionE2eTest, TestCountMetric_save_refractory_to_disk);
+    FRIEND_TEST(AnomalyDetectionE2eTest, TestCountMetric_load_refractory_from_disk);
     FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_single_bucket);
     FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_multiple_buckets);
     FRIEND_TEST(AnomalyDetectionE2eTest, TestDurationMetric_SUM_long_refractory_period);
@@ -317,6 +329,11 @@
     FRIEND_TEST(DurationMetricE2eTest, TestWithCondition);
     FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedCondition);
     FRIEND_TEST(DurationMetricE2eTest, TestWithActivationAndSlicedCondition);
+    FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedState);
+    FRIEND_TEST(DurationMetricE2eTest, TestWithConditionAndSlicedState);
+    FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStateMapped);
+    FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSuperset);
+    FRIEND_TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSubset);
 
     FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState);
     FRIEND_TEST(ValueMetricE2eTest, TestInitWithSlicedState_WithDimensions);
diff --git a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
index afe93d4..8d59d13 100644
--- a/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/DurationTracker.h
@@ -56,11 +56,19 @@
     int64_t mDuration;
 };
 
+struct DurationValues {
+    // Recorded duration for current partial bucket.
+    int64_t mDuration;
+
+    // Sum of past partial bucket durations in current full bucket.
+    // Used for anomaly detection.
+    int64_t mDurationFullBucket;
+};
+
 class DurationTracker {
 public:
     DurationTracker(const ConfigKey& key, const int64_t& id, const MetricDimensionKey& eventKey,
-                    sp<ConditionWizard> wizard, int conditionIndex,
-                    bool nesting,
+                    sp<ConditionWizard> wizard, int conditionIndex, bool nesting,
                     int64_t currentBucketStartNs, int64_t currentBucketNum, int64_t startTimeNs,
                     int64_t bucketSizeNs, bool conditionSliced, bool fullLink,
                     const std::vector<sp<DurationAnomalyTracker>>& anomalyTrackers)
@@ -73,7 +81,6 @@
           mNested(nesting),
           mCurrentBucketStartTimeNs(currentBucketStartNs),
           mDuration(0),
-          mDurationFullBucket(0),
           mCurrentBucketNum(currentBucketNum),
           mStartTimeNs(startTimeNs),
           mConditionSliced(conditionSliced),
@@ -82,8 +89,8 @@
 
     virtual ~DurationTracker(){};
 
-    virtual void noteStart(const HashableDimensionKey& key, bool condition,
-                           const int64_t eventTime, const ConditionKey& conditionKey) = 0;
+    virtual void noteStart(const HashableDimensionKey& key, bool condition, const int64_t eventTime,
+                           const ConditionKey& conditionKey) = 0;
     virtual void noteStop(const HashableDimensionKey& key, const int64_t eventTime,
                           const bool stopAll) = 0;
     virtual void noteStopAll(const int64_t eventTime) = 0;
@@ -91,6 +98,9 @@
     virtual void onSlicedConditionMayChange(bool overallCondition, const int64_t timestamp) = 0;
     virtual void onConditionChanged(bool condition, const int64_t timestamp) = 0;
 
+    virtual void onStateChanged(const int64_t timestamp, const int32_t atomId,
+                                const FieldValue& newState) = 0;
+
     // Flush stale buckets if needed, and return true if the tracker has no on-going duration
     // events, so that the owner can safely remove the tracker.
     virtual bool flushIfNeeded(
@@ -109,9 +119,12 @@
     // Dump internal states for debugging
     virtual void dumpStates(FILE* out, bool verbose) const = 0;
 
-    void setEventKey(const MetricDimensionKey& eventKey) {
-         mEventKey = eventKey;
-    }
+    virtual int64_t getCurrentStateKeyDuration() const = 0;
+
+    virtual int64_t getCurrentStateKeyFullBucketDuration() const = 0;
+
+    // Replace old value with new value for the given state atom.
+    virtual void updateCurrentStateKey(const int32_t atomId, const FieldValue& newState) = 0;
 
 protected:
     int64_t getCurrentBucketEndTimeNs() const {
@@ -140,10 +153,11 @@
         }
     }
 
-    void addPastBucketToAnomalyTrackers(const int64_t& bucketValue, const int64_t& bucketNum) {
+    void addPastBucketToAnomalyTrackers(const MetricDimensionKey eventKey,
+                                        const int64_t& bucketValue, const int64_t& bucketNum) {
         for (auto& anomalyTracker : mAnomalyTrackers) {
             if (anomalyTracker != nullptr) {
-                anomalyTracker->addPastBucket(mEventKey, bucketValue, bucketNum);
+                anomalyTracker->addPastBucket(eventKey, bucketValue, bucketNum);
             }
         }
     }
@@ -164,6 +178,10 @@
         return mStartTimeNs + (mCurrentBucketNum + 1) * mBucketSizeNs;
     }
 
+    void setEventKey(const MetricDimensionKey& eventKey) {
+        mEventKey = eventKey;
+    }
+
     // A reference to the DurationMetricProducer's config key.
     const ConfigKey& mConfigKey;
 
@@ -183,7 +201,8 @@
 
     int64_t mDuration;  // current recorded duration result (for partial bucket)
 
-    int64_t mDurationFullBucket;  // Sum of past partial buckets in current full bucket.
+    // Recorded duration results for each state key in the current partial bucket.
+    std::unordered_map<HashableDimensionKey, DurationValues> mStateKeyDurationMap;
 
     int64_t mCurrentBucketNum;
 
diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
index 2be5855..ee4e167 100644
--- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
+++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.cpp
@@ -26,15 +26,14 @@
 
 MaxDurationTracker::MaxDurationTracker(const ConfigKey& key, const int64_t& id,
                                        const MetricDimensionKey& eventKey,
-                                       sp<ConditionWizard> wizard, int conditionIndex,
-                                       bool nesting,
+                                       sp<ConditionWizard> wizard, int conditionIndex, bool nesting,
                                        int64_t currentBucketStartNs, int64_t currentBucketNum,
                                        int64_t startTimeNs, int64_t bucketSizeNs,
                                        bool conditionSliced, bool fullLink,
                                        const vector<sp<DurationAnomalyTracker>>& anomalyTrackers)
-    : DurationTracker(key, id, eventKey, wizard, conditionIndex, nesting,
-                      currentBucketStartNs, currentBucketNum, startTimeNs, bucketSizeNs,
-                      conditionSliced, fullLink, anomalyTrackers) {
+    : DurationTracker(key, id, eventKey, wizard, conditionIndex, nesting, currentBucketStartNs,
+                      currentBucketNum, startTimeNs, bucketSizeNs, conditionSliced, fullLink,
+                      anomalyTrackers) {
 }
 
 bool MaxDurationTracker::hitGuardRail(const HashableDimensionKey& newKey) {
@@ -91,7 +90,6 @@
     }
 }
 
-
 void MaxDurationTracker::noteStop(const HashableDimensionKey& key, const int64_t eventTime,
                                   bool forceStop) {
     VLOG("MaxDuration: key %s stop", key.toString().c_str());
@@ -240,6 +238,11 @@
     }
 }
 
+void MaxDurationTracker::onStateChanged(const int64_t timestamp, const int32_t atomId,
+                                        const FieldValue& newState) {
+    ALOGE("MaxDurationTracker does not handle sliced state changes.");
+}
+
 void MaxDurationTracker::onConditionChanged(bool condition, const int64_t timestamp) {
     for (auto& pair : mInfos) {
         noteConditionChanged(pair.first, condition, timestamp);
@@ -309,6 +312,20 @@
     fprintf(out, "\t\t current duration %lld\n", (long long)mDuration);
 }
 
+int64_t MaxDurationTracker::getCurrentStateKeyDuration() const {
+    ALOGE("MaxDurationTracker does not handle sliced state changes.");
+    return -1;
+}
+
+int64_t MaxDurationTracker::getCurrentStateKeyFullBucketDuration() const {
+    ALOGE("MaxDurationTracker does not handle sliced state changes.");
+    return -1;
+}
+
+void MaxDurationTracker::updateCurrentStateKey(const int32_t atomId, const FieldValue& newState) {
+    ALOGE("MaxDurationTracker does not handle sliced state changes.");
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
index efb8dc7..2891c6e 100644
--- a/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/MaxDurationTracker.h
@@ -54,10 +54,19 @@
     void onSlicedConditionMayChange(bool overallCondition, const int64_t timestamp) override;
     void onConditionChanged(bool condition, const int64_t timestamp) override;
 
+    void onStateChanged(const int64_t timestamp, const int32_t atomId,
+                        const FieldValue& newState) override;
+
     int64_t predictAnomalyTimestampNs(const DurationAnomalyTracker& anomalyTracker,
                                       const int64_t currentTimestamp) const override;
     void dumpStates(FILE* out, bool verbose) const override;
 
+    int64_t getCurrentStateKeyDuration() const override;
+
+    int64_t getCurrentStateKeyFullBucketDuration() const override;
+
+    void updateCurrentStateKey(const int32_t atomId, const FieldValue& newState);
+
 private:
     // Returns true if at least one of the mInfos is started.
     bool anyStarted();
diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
index 57f3965..19b2fe8 100644
--- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
+++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.cpp
@@ -26,13 +26,12 @@
 
 OringDurationTracker::OringDurationTracker(
         const ConfigKey& key, const int64_t& id, const MetricDimensionKey& eventKey,
-        sp<ConditionWizard> wizard, int conditionIndex,
-        bool nesting, int64_t currentBucketStartNs, int64_t currentBucketNum,
-        int64_t startTimeNs, int64_t bucketSizeNs, bool conditionSliced, bool fullLink,
-        const vector<sp<DurationAnomalyTracker>>& anomalyTrackers)
-    : DurationTracker(key, id, eventKey, wizard, conditionIndex, nesting,
-                      currentBucketStartNs, currentBucketNum, startTimeNs, bucketSizeNs,
-                      conditionSliced, fullLink, anomalyTrackers),
+        sp<ConditionWizard> wizard, int conditionIndex, bool nesting, int64_t currentBucketStartNs,
+        int64_t currentBucketNum, int64_t startTimeNs, int64_t bucketSizeNs, bool conditionSliced,
+        bool fullLink, const vector<sp<DurationAnomalyTracker>>& anomalyTrackers)
+    : DurationTracker(key, id, eventKey, wizard, conditionIndex, nesting, currentBucketStartNs,
+                      currentBucketNum, startTimeNs, bucketSizeNs, conditionSliced, fullLink,
+                      anomalyTrackers),
       mStarted(),
       mPaused() {
     mLastStartTime = 0;
@@ -90,10 +89,14 @@
             mConditionKeyMap.erase(key);
         }
         if (mStarted.empty()) {
-            mDuration += (timestamp - mLastStartTime);
-            detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration + mDurationFullBucket);
-            VLOG("record duration %lld, total %lld ", (long long)timestamp - mLastStartTime,
-                 (long long)mDuration);
+            mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration +=
+                    (timestamp - mLastStartTime);
+            detectAndDeclareAnomaly(
+                    timestamp, mCurrentBucketNum,
+                    getCurrentStateKeyDuration() + getCurrentStateKeyFullBucketDuration());
+            VLOG("record duration %lld, total duration %lld for state key %s",
+                 (long long)timestamp - mLastStartTime, (long long)getCurrentStateKeyDuration(),
+                 mEventKey.getStateValuesKey().toString().c_str());
         }
     }
 
@@ -112,10 +115,14 @@
 
 void OringDurationTracker::noteStopAll(const int64_t timestamp) {
     if (!mStarted.empty()) {
-        mDuration += (timestamp - mLastStartTime);
-        VLOG("Oring Stop all: record duration %lld %lld ", (long long)timestamp - mLastStartTime,
-             (long long)mDuration);
-        detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration + mDurationFullBucket);
+        mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration +=
+                (timestamp - mLastStartTime);
+        VLOG("Oring Stop all: record duration %lld, total duration %lld for state key %s",
+             (long long)timestamp - mLastStartTime, (long long)getCurrentStateKeyDuration(),
+             mEventKey.getStateValuesKey().toString().c_str());
+        detectAndDeclareAnomaly(
+                timestamp, mCurrentBucketNum,
+                getCurrentStateKeyDuration() + getCurrentStateKeyFullBucketDuration());
     }
 
     stopAnomalyAlarm(timestamp);
@@ -146,21 +153,36 @@
 
     // Process the current bucket.
     if (mStarted.size() > 0) {
-        mDuration += (currentBucketEndTimeNs - mLastStartTime);
+        // Calculate the duration for the current state key.
+        mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration +=
+                (currentBucketEndTimeNs - mLastStartTime);
     }
-    if (mDuration > 0) {
-        DurationBucket current_info;
-        current_info.mBucketStartNs = mCurrentBucketStartTimeNs;
-        current_info.mBucketEndNs = currentBucketEndTimeNs;
-        current_info.mDuration = mDuration;
-        (*output)[mEventKey].push_back(current_info);
-        mDurationFullBucket += mDuration;
-        VLOG("  duration: %lld", (long long)current_info.mDuration);
-    }
-    if (eventTimeNs > fullBucketEnd) {
-        // End of full bucket, can send to anomaly tracker now.
-        addPastBucketToAnomalyTrackers(mDurationFullBucket, mCurrentBucketNum);
-        mDurationFullBucket = 0;
+    // Store DurationBucket info for each whatKey, stateKey pair.
+    // Note: The whatKey stored in mEventKey is constant for each DurationTracker, while the
+    // stateKey stored in mEventKey is only the current stateKey. mStateKeyDurationMap is used to
+    // store durations for each stateKey, so we need to flush the bucket by creating a
+    // DurationBucket for each stateKey.
+    for (auto& durationIt : mStateKeyDurationMap) {
+        if (durationIt.second.mDuration > 0) {
+            DurationBucket current_info;
+            current_info.mBucketStartNs = mCurrentBucketStartTimeNs;
+            current_info.mBucketEndNs = currentBucketEndTimeNs;
+            current_info.mDuration = durationIt.second.mDuration;
+            (*output)[MetricDimensionKey(mEventKey.getDimensionKeyInWhat(), durationIt.first)]
+                    .push_back(current_info);
+
+            durationIt.second.mDurationFullBucket += durationIt.second.mDuration;
+            VLOG("  duration: %lld", (long long)current_info.mDuration);
+        }
+
+        if (eventTimeNs > fullBucketEnd) {
+            // End of full bucket, can send to anomaly tracker now.
+            addPastBucketToAnomalyTrackers(
+                    MetricDimensionKey(mEventKey.getDimensionKeyInWhat(), durationIt.first),
+                    getCurrentStateKeyFullBucketDuration(), mCurrentBucketNum);
+            durationIt.second.mDurationFullBucket = 0;
+        }
+        durationIt.second.mDuration = 0;
     }
 
     if (mStarted.size() > 0) {
@@ -169,20 +191,19 @@
             info.mBucketStartNs = fullBucketEnd + mBucketSizeNs * (i - 1);
             info.mBucketEndNs = info.mBucketStartNs + mBucketSizeNs;
             info.mDuration = mBucketSizeNs;
+            // Full duration buckets are attributed to the current stateKey.
             (*output)[mEventKey].push_back(info);
             // Safe to send these buckets to anomaly tracker since they must be full buckets.
             // If it's a partial bucket, numBucketsForward would be 0.
-            addPastBucketToAnomalyTrackers(info.mDuration, mCurrentBucketNum + i);
+            addPastBucketToAnomalyTrackers(mEventKey, info.mDuration, mCurrentBucketNum + i);
             VLOG("  add filling bucket with duration %lld", (long long)info.mDuration);
         }
     } else {
         if (numBucketsForward >= 2) {
-            addPastBucketToAnomalyTrackers(0, mCurrentBucketNum + numBucketsForward - 1);
+            addPastBucketToAnomalyTrackers(mEventKey, 0, mCurrentBucketNum + numBucketsForward - 1);
         }
     }
 
-    mDuration = 0;
-
     if (numBucketsForward > 0) {
         mCurrentBucketStartTimeNs = fullBucketEnd + (numBucketsForward - 1) * mBucketSizeNs;
         mCurrentBucketNum += numBucketsForward;
@@ -229,10 +250,14 @@
         }
 
         if (mStarted.empty()) {
-            mDuration += (timestamp - mLastStartTime);
-            VLOG("Duration add %lld , to %lld ", (long long)(timestamp - mLastStartTime),
-                 (long long)mDuration);
-            detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration + mDurationFullBucket);
+            mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration +=
+                    (timestamp - mLastStartTime);
+            VLOG("record duration %lld, total duration %lld for state key %s",
+                 (long long)(timestamp - mLastStartTime), (long long)getCurrentStateKeyDuration(),
+                 mEventKey.getStateValuesKey().toString().c_str());
+            detectAndDeclareAnomaly(
+                    timestamp, mCurrentBucketNum,
+                    getCurrentStateKeyDuration() + getCurrentStateKeyFullBucketDuration());
         }
     }
 
@@ -288,10 +313,13 @@
     } else {
         if (!mStarted.empty()) {
             VLOG("Condition false, all paused");
-            mDuration += (timestamp - mLastStartTime);
+            mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration +=
+                    (timestamp - mLastStartTime);
             mPaused.insert(mStarted.begin(), mStarted.end());
             mStarted.clear();
-            detectAndDeclareAnomaly(timestamp, mCurrentBucketNum, mDuration + mDurationFullBucket);
+            detectAndDeclareAnomaly(
+                    timestamp, mCurrentBucketNum,
+                    getCurrentStateKeyDuration() + getCurrentStateKeyFullBucketDuration());
         }
     }
     if (mStarted.empty()) {
@@ -299,6 +327,20 @@
     }
 }
 
+void OringDurationTracker::onStateChanged(const int64_t timestamp, const int32_t atomId,
+                                          const FieldValue& newState) {
+    // If no keys are being tracked, update the current state key and return.
+    if (mStarted.empty()) {
+        updateCurrentStateKey(atomId, newState);
+        return;
+    }
+    // Add the current duration length to the previous state key and then update
+    // the last start time and current state key.
+    mStateKeyDurationMap[mEventKey.getStateValuesKey()].mDuration += (timestamp - mLastStartTime);
+    mLastStartTime = timestamp;
+    updateCurrentStateKey(atomId, newState);
+}
+
 int64_t OringDurationTracker::predictAnomalyTimestampNs(
         const DurationAnomalyTracker& anomalyTracker, const int64_t eventTimestampNs) const {
 
@@ -308,12 +350,13 @@
     // The timestamp of the current bucket end.
     const int64_t currentBucketEndNs = getCurrentBucketEndTimeNs();
 
-    // The past duration ns for the current bucket.
-    int64_t currentBucketPastNs = mDuration + mDurationFullBucket;
+    // The past duration ns for the current bucket of the current stateKey.
+    int64_t currentStateBucketPastNs =
+            getCurrentStateKeyDuration() + getCurrentStateKeyFullBucketDuration();
 
     // As we move into the future, old buckets get overwritten (so their old data is erased).
     // Sum of past durations. Will change as we overwrite old buckets.
-    int64_t pastNs = currentBucketPastNs + anomalyTracker.getSumOverPastBuckets(mEventKey);
+    int64_t pastNs = currentStateBucketPastNs + anomalyTracker.getSumOverPastBuckets(mEventKey);
 
     // The refractory period end timestamp for dimension mEventKey.
     const int64_t refractoryPeriodEndNs =
@@ -372,7 +415,7 @@
                     mEventKey,
                     mCurrentBucketNum - anomalyTracker.getNumOfPastBuckets() + futureBucketIdx);
         } else if (futureBucketIdx == anomalyTracker.getNumOfPastBuckets()) {
-            pastNs -= (currentBucketPastNs + (currentBucketEndNs - eventTimestampNs));
+            pastNs -= (currentStateBucketPastNs + (currentBucketEndNs - eventTimestampNs));
         }
     }
 
@@ -382,7 +425,34 @@
 void OringDurationTracker::dumpStates(FILE* out, bool verbose) const {
     fprintf(out, "\t\t started count %lu\n", (unsigned long)mStarted.size());
     fprintf(out, "\t\t paused count %lu\n", (unsigned long)mPaused.size());
-    fprintf(out, "\t\t current duration %lld\n", (long long)mDuration);
+    fprintf(out, "\t\t current duration %lld\n", (long long)getCurrentStateKeyDuration());
+}
+
+int64_t OringDurationTracker::getCurrentStateKeyDuration() const {
+    auto it = mStateKeyDurationMap.find(mEventKey.getStateValuesKey());
+    if (it == mStateKeyDurationMap.end()) {
+        return 0;
+    } else {
+        return it->second.mDuration;
+    }
+}
+
+int64_t OringDurationTracker::getCurrentStateKeyFullBucketDuration() const {
+    auto it = mStateKeyDurationMap.find(mEventKey.getStateValuesKey());
+    if (it == mStateKeyDurationMap.end()) {
+        return 0;
+    } else {
+        return it->second.mDurationFullBucket;
+    }
+}
+
+void OringDurationTracker::updateCurrentStateKey(const int32_t atomId, const FieldValue& newState) {
+    HashableDimensionKey* stateValuesKey = mEventKey.getMutableStateValuesKey();
+    for (size_t i = 0; i < stateValuesKey->getValues().size(); i++) {
+        if (stateValuesKey->getValues()[i].mField.getTag() == atomId) {
+            stateValuesKey->mutableValue(i)->mValue = newState.mValue;
+        }
+    }
 }
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
index f44e327..bd8017a 100644
--- a/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
+++ b/cmds/statsd/src/metrics/duration_helper/OringDurationTracker.h
@@ -28,10 +28,9 @@
 public:
     OringDurationTracker(const ConfigKey& key, const int64_t& id,
                          const MetricDimensionKey& eventKey, sp<ConditionWizard> wizard,
-                         int conditionIndex,
-                         bool nesting, int64_t currentBucketStartNs, int64_t currentBucketNum,
-                         int64_t startTimeNs, int64_t bucketSizeNs, bool conditionSliced,
-                         bool fullLink,
+                         int conditionIndex, bool nesting, int64_t currentBucketStartNs,
+                         int64_t currentBucketNum, int64_t startTimeNs, int64_t bucketSizeNs,
+                         bool conditionSliced, bool fullLink,
                          const std::vector<sp<DurationAnomalyTracker>>& anomalyTrackers);
 
     OringDurationTracker(const OringDurationTracker& tracker) = default;
@@ -45,6 +44,9 @@
     void onSlicedConditionMayChange(bool overallCondition, const int64_t timestamp) override;
     void onConditionChanged(bool condition, const int64_t timestamp) override;
 
+    void onStateChanged(const int64_t timestamp, const int32_t atomId,
+                        const FieldValue& newState) override;
+
     bool flushCurrentBucket(
             const int64_t& eventTimeNs,
             std::unordered_map<MetricDimensionKey, std::vector<DurationBucket>>* output) override;
@@ -56,6 +58,12 @@
                                       const int64_t currentTimestamp) const override;
     void dumpStates(FILE* out, bool verbose) const override;
 
+    int64_t getCurrentStateKeyDuration() const override;
+
+    int64_t getCurrentStateKeyFullBucketDuration() const override;
+
+    void updateCurrentStateKey(const int32_t atomId, const FieldValue& newState);
+
 private:
     // We don't need to keep track of individual durations. The information that's needed is:
     // 1) which keys are started. We record the first start time.
diff --git a/cmds/statsd/src/metrics/metrics_manager_util.cpp b/cmds/statsd/src/metrics/metrics_manager_util.cpp
index 3810c48..0d0788e 100644
--- a/cmds/statsd/src/metrics/metrics_manager_util.cpp
+++ b/cmds/statsd/src/metrics/metrics_manager_util.cpp
@@ -564,6 +564,33 @@
             }
         }
 
+        std::vector<int> slicedStateAtoms;
+        unordered_map<int, unordered_map<int, int64_t>> stateGroupMap;
+        if (metric.slice_by_state_size() > 0) {
+            if (metric.aggregation_type() == DurationMetric::MAX_SPARSE) {
+                ALOGE("DurationMetric with aggregation type MAX_SPARSE cannot be sliced by state");
+                return false;
+            }
+            if (!handleMetricWithStates(config, metric.slice_by_state(), stateAtomIdMap,
+                                        allStateGroupMaps, slicedStateAtoms, stateGroupMap)) {
+                return false;
+            }
+        } else {
+            if (metric.state_link_size() > 0) {
+                ALOGW("DurationMetric has a MetricStateLink but doesn't have a sliced state");
+                return false;
+            }
+        }
+
+        // Check that all metric state links are a subset of dimensions_in_what fields.
+        std::vector<Matcher> dimensionsInWhat;
+        translateFieldMatcher(metric.dimensions_in_what(), &dimensionsInWhat);
+        for (const auto& stateLink : metric.state_link()) {
+            if (!handleMetricWithStateLink(stateLink.fields_in_what(), dimensionsInWhat)) {
+                return false;
+            }
+        }
+
         unordered_map<int, shared_ptr<Activation>> eventActivationMap;
         unordered_map<int, vector<shared_ptr<Activation>>> eventDeactivationMap;
         bool success = handleMetricActivation(config, metric.id(), metricIndex,
@@ -575,7 +602,8 @@
         sp<MetricProducer> durationMetric = new DurationMetricProducer(
                 key, metric, conditionIndex, trackerIndices[0], trackerIndices[1],
                 trackerIndices[2], nesting, wizard, internalDimensions, timeBaseTimeNs,
-                currentTimeNs, eventActivationMap, eventDeactivationMap);
+                currentTimeNs, eventActivationMap, eventDeactivationMap, slicedStateAtoms,
+                stateGroupMap);
 
         allMetricProducers.push_back(durationMetric);
     }
diff --git a/cmds/statsd/src/stats_log.proto b/cmds/statsd/src/stats_log.proto
index c45274e..ed98f50 100644
--- a/cmds/statsd/src/stats_log.proto
+++ b/cmds/statsd/src/stats_log.proto
@@ -103,12 +103,14 @@
 message DurationMetricData {
   optional DimensionsValue dimensions_in_what = 1;
 
-  optional DimensionsValue dimensions_in_condition = 2 [deprecated = true];
+  repeated StateValue slice_by_state = 6;
 
   repeated DurationBucketInfo bucket_info = 3;
 
   repeated DimensionsValue dimension_leaf_values_in_what = 4;
 
+  optional DimensionsValue dimensions_in_condition = 2 [deprecated = true];
+
   repeated DimensionsValue dimension_leaf_values_in_condition = 5 [deprecated = true];
 }
 
diff --git a/cmds/statsd/src/statsd_config.proto b/cmds/statsd/src/statsd_config.proto
index 83d9484..c7407bd 100644
--- a/cmds/statsd/src/statsd_config.proto
+++ b/cmds/statsd/src/statsd_config.proto
@@ -227,8 +227,12 @@
 
   optional int64 condition = 3;
 
+  repeated int64 slice_by_state = 9;
+
   repeated MetricConditionLink links = 4;
 
+  repeated MetricStateLink state_link = 10;
+
   enum AggregationType {
     SUM = 1;
 
@@ -238,9 +242,9 @@
 
   optional FieldMatcher dimensions_in_what = 6;
 
-  optional FieldMatcher dimensions_in_condition = 8 [deprecated = true];
-
   optional TimeUnit bucket = 7;
+
+  optional FieldMatcher dimensions_in_condition = 8 [deprecated = true];
 }
 
 message GaugeMetric {
diff --git a/cmds/statsd/src/statsd_metadata.proto b/cmds/statsd/src/statsd_metadata.proto
index e00fe336..200b392 100644
--- a/cmds/statsd/src/statsd_metadata.proto
+++ b/cmds/statsd/src/statsd_metadata.proto
@@ -45,11 +45,15 @@
   repeated FieldValue state_values_key = 2;
 }
 
+message AlertDimensionKeyedData {
+  // The earliest time the alert can be fired again in wall clock time.
+  optional int32 last_refractory_ends_sec = 1;
+  optional MetricDimensionKey dimension_key = 2;
+}
+
 message AlertMetadata {
   optional int64 alert_id = 1;
-  // The earliest time the alert can be fired again in wall clock time.
-  optional int32 last_refractory_ends_sec = 2;
-  optional MetricDimensionKey dimension_key = 3;
+  repeated AlertDimensionKeyedData alert_dim_keyed_data = 2;
 }
 
 // All metadata for a config in statsd
diff --git a/cmds/statsd/tests/HashableDimensionKey_test.cpp b/cmds/statsd/tests/HashableDimensionKey_test.cpp
new file mode 100644
index 0000000..29adcd0
--- /dev/null
+++ b/cmds/statsd/tests/HashableDimensionKey_test.cpp
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2020 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 "src/HashableDimensionKey.h"
+
+#include <gtest/gtest.h>
+
+#include "frameworks/base/cmds/statsd/src/statsd_config.pb.h"
+#include "statsd_test_util.h"
+
+#ifdef __ANDROID__
+
+using android::util::ProtoReader;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+/**
+ * Test that #containsLinkedStateValues returns false when the whatKey is
+ * smaller than the primaryKey.
+ */
+TEST(HashableDimensionKeyTest, TestContainsLinkedStateValues_WhatKeyTooSmall) {
+    std::vector<Metric2State> mMetric2StateLinks;
+
+    int32_t uid1 = 1000;
+    HashableDimensionKey whatKey = DEFAULT_DIMENSION_KEY;
+    HashableDimensionKey primaryKey;
+    getUidProcessKey(uid1, &primaryKey);
+
+    EXPECT_FALSE(containsLinkedStateValues(whatKey, primaryKey, mMetric2StateLinks,
+                                           UID_PROCESS_STATE_ATOM_ID));
+}
+
+/**
+ * Test that #containsLinkedStateValues returns false when the linked values
+ * are not equal.
+ */
+TEST(HashableDimensionKeyTest, TestContainsLinkedStateValues_UnequalLinkedValues) {
+    int stateAtomId = UID_PROCESS_STATE_ATOM_ID;
+
+    FieldMatcher whatMatcher;
+    whatMatcher.set_field(util::OVERLAY_STATE_CHANGED);
+    FieldMatcher* child11 = whatMatcher.add_child();
+    child11->set_field(1);
+
+    FieldMatcher stateMatcher;
+    stateMatcher.set_field(stateAtomId);
+    FieldMatcher* child21 = stateMatcher.add_child();
+    child21->set_field(1);
+
+    std::vector<Metric2State> mMetric2StateLinks;
+    Metric2State ms;
+    ms.stateAtomId = stateAtomId;
+    translateFieldMatcher(whatMatcher, &ms.metricFields);
+    translateFieldMatcher(stateMatcher, &ms.stateFields);
+    mMetric2StateLinks.push_back(ms);
+
+    int32_t uid1 = 1000;
+    int32_t uid2 = 1001;
+    HashableDimensionKey whatKey;
+    getOverlayKey(uid2, "package", &whatKey);
+    HashableDimensionKey primaryKey;
+    getUidProcessKey(uid1, &primaryKey);
+
+    EXPECT_FALSE(containsLinkedStateValues(whatKey, primaryKey, mMetric2StateLinks, stateAtomId));
+}
+
+/**
+ * Test that #containsLinkedStateValues returns false when there is no link
+ * between the key values.
+ */
+TEST(HashableDimensionKeyTest, TestContainsLinkedStateValues_MissingMetric2StateLinks) {
+    int stateAtomId = UID_PROCESS_STATE_ATOM_ID;
+
+    std::vector<Metric2State> mMetric2StateLinks;
+
+    int32_t uid1 = 1000;
+    HashableDimensionKey whatKey;
+    getOverlayKey(uid1, "package", &whatKey);
+    HashableDimensionKey primaryKey;
+    getUidProcessKey(uid1, &primaryKey);
+
+    EXPECT_FALSE(containsLinkedStateValues(whatKey, primaryKey, mMetric2StateLinks, stateAtomId));
+}
+
+/**
+ * Test that #containsLinkedStateValues returns true when the key values are
+ * linked and equal.
+ */
+TEST(HashableDimensionKeyTest, TestContainsLinkedStateValues_AllConditionsMet) {
+    int stateAtomId = UID_PROCESS_STATE_ATOM_ID;
+
+    FieldMatcher whatMatcher;
+    whatMatcher.set_field(util::OVERLAY_STATE_CHANGED);
+    FieldMatcher* child11 = whatMatcher.add_child();
+    child11->set_field(1);
+
+    FieldMatcher stateMatcher;
+    stateMatcher.set_field(stateAtomId);
+    FieldMatcher* child21 = stateMatcher.add_child();
+    child21->set_field(1);
+
+    std::vector<Metric2State> mMetric2StateLinks;
+    Metric2State ms;
+    ms.stateAtomId = stateAtomId;
+    translateFieldMatcher(whatMatcher, &ms.metricFields);
+    translateFieldMatcher(stateMatcher, &ms.stateFields);
+    mMetric2StateLinks.push_back(ms);
+
+    int32_t uid1 = 1000;
+    HashableDimensionKey whatKey;
+    getOverlayKey(uid1, "package", &whatKey);
+    HashableDimensionKey primaryKey;
+    getUidProcessKey(uid1, &primaryKey);
+
+    EXPECT_TRUE(containsLinkedStateValues(whatKey, primaryKey, mMetric2StateLinks, stateAtomId));
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
diff --git a/cmds/statsd/tests/LogEvent_test.cpp b/cmds/statsd/tests/LogEvent_test.cpp
index 7458cbf..41e21e4 100644
--- a/cmds/statsd/tests/LogEvent_test.cpp
+++ b/cmds/statsd/tests/LogEvent_test.cpp
@@ -12,12 +12,13 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-#include "src/logd/LogEvent.h"
 #include <gtest/gtest.h>
-#include <log/log_event_list.h>
+
 #include "frameworks/base/cmds/statsd/src/atoms.pb.h"
 #include "frameworks/base/core/proto/android/stats/launcher/launcher.pb.h"
-#include <stats_event.h>
+#include "log/log_event_list.h"
+#include "src/logd/LogEvent.h"
+#include "stats_event.h"
 
 
 #ifdef __ANDROID__
@@ -243,6 +244,117 @@
     AStatsEvent_release(event);
 }
 
+void createIntWithBoolAnnotationLogEvent(LogEvent* logEvent, uint8_t annotationId,
+                                         bool annotationValue) {
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, /*atomId=*/100);
+    AStatsEvent_writeInt32(statsEvent, 10);
+    AStatsEvent_addBoolAnnotation(statsEvent, annotationId, annotationValue);
+    AStatsEvent_build(statsEvent);
+
+    size_t size;
+    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
+    EXPECT_TRUE(logEvent->parseBuffer(buf, size));
+
+    AStatsEvent_release(statsEvent);
+}
+
+TEST(LogEventTest, TestAnnotationIdIsUid) {
+    LogEvent event(/*uid=*/0, /*pid=*/0);
+    createIntWithBoolAnnotationLogEvent(&event, ANNOTATION_ID_IS_UID, true);
+
+    const vector<FieldValue>& values = event.getValues();
+    EXPECT_EQ(values.size(), 1);
+    EXPECT_EQ(event.getUidFieldIndex(), 0);
+}
+
+TEST(LogEventTest, TestAnnotationIdStateNested) {
+    LogEvent event(/*uid=*/0, /*pid=*/0);
+    createIntWithBoolAnnotationLogEvent(&event, ANNOTATION_ID_STATE_NESTED, true);
+
+    const vector<FieldValue>& values = event.getValues();
+    EXPECT_EQ(values.size(), 1);
+    EXPECT_TRUE(values[0].mAnnotations.isNested());
+}
+
+void createIntWithIntAnnotationLogEvent(LogEvent* logEvent, uint8_t annotationId,
+                                        int annotationValue) {
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, /*atomId=*/100);
+    AStatsEvent_writeInt32(statsEvent, 10);
+    AStatsEvent_addInt32Annotation(statsEvent, annotationId, annotationValue);
+    AStatsEvent_build(statsEvent);
+
+    size_t size;
+    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
+    EXPECT_TRUE(logEvent->parseBuffer(buf, size));
+
+    AStatsEvent_release(statsEvent);
+}
+
+TEST(LogEventTest, TestPrimaryFieldAnnotation) {
+    LogEvent event(/*uid=*/0, /*pid=*/0);
+    createIntWithIntAnnotationLogEvent(&event, ANNOTATION_ID_STATE_OPTION,
+                                       STATE_OPTION_PRIMARY_FIELD);
+
+    const vector<FieldValue>& values = event.getValues();
+    EXPECT_EQ(values.size(), 1);
+    EXPECT_TRUE(values[0].mAnnotations.isPrimaryField());
+}
+
+TEST(LogEventTest, TestExclusiveStateAnnotation) {
+    LogEvent event(/*uid=*/0, /*pid=*/0);
+    createIntWithIntAnnotationLogEvent(&event, ANNOTATION_ID_STATE_OPTION,
+                                       STATE_OPTION_EXCLUSIVE_STATE);
+
+    const vector<FieldValue>& values = event.getValues();
+    EXPECT_EQ(values.size(), 1);
+    EXPECT_TRUE(values[0].mAnnotations.isExclusiveState());
+}
+
+TEST(LogEventTest, TestPrimaryFieldFirstUidAnnotation) {
+    // Event has 10 ints and then an attribution chain
+    int numInts = 10;
+    int firstUidInChainIndex = numInts;
+    string tag1 = "tag1";
+    string tag2 = "tag2";
+    uint32_t uids[] = {1001, 1002};
+    const char* tags[] = {tag1.c_str(), tag2.c_str()};
+
+    // Construct AStatsEvent
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, 100);
+    for (int i = 0; i < numInts; i++) {
+        AStatsEvent_writeInt32(statsEvent, 10);
+    }
+    AStatsEvent_writeAttributionChain(statsEvent, uids, tags, 2);
+    AStatsEvent_addInt32Annotation(statsEvent, ANNOTATION_ID_STATE_OPTION,
+                                   STATE_OPTION_PRIMARY_FIELD_FIRST_UID);
+    AStatsEvent_build(statsEvent);
+
+    // Construct LogEvent
+    size_t size;
+    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
+    LogEvent logEvent(/*uid=*/0, /*pid=*/0);
+    EXPECT_TRUE(logEvent.parseBuffer(buf, size));
+    AStatsEvent_release(statsEvent);
+
+    // Check annotation
+    const vector<FieldValue>& values = logEvent.getValues();
+    EXPECT_EQ(values.size(), numInts + 4);
+    EXPECT_TRUE(values[firstUidInChainIndex].mAnnotations.isPrimaryField());
+}
+
+TEST(LogEventTest, TestResetStateAnnotation) {
+    int32_t resetState = 10;
+    LogEvent event(/*uid=*/0, /*pid=*/0);
+    createIntWithIntAnnotationLogEvent(&event, ANNOTATION_ID_RESET_STATE, resetState);
+
+    const vector<FieldValue>& values = event.getValues();
+    EXPECT_EQ(values.size(), 1);
+    EXPECT_EQ(values[0].mAnnotations.getResetState(), resetState);
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/statsd/tests/e2e/Anomaly_count_e2e_test.cpp b/cmds/statsd/tests/e2e/Anomaly_count_e2e_test.cpp
index 9c6965d..c2d7043 100644
--- a/cmds/statsd/tests/e2e/Anomaly_count_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/Anomaly_count_e2e_test.cpp
@@ -14,6 +14,7 @@
 
 #include <gtest/gtest.h>
 
+#include "frameworks/base/cmds/statsd/src/statsd_metadata.pb.h"
 #include "src/StatsLogProcessor.h"
 #include "src/stats_log_util.h"
 #include "tests/statsd_test_util.h"
@@ -28,7 +29,7 @@
 
 namespace {
 
-StatsdConfig CreateStatsdConfig(int num_buckets, int threshold) {
+StatsdConfig CreateStatsdConfig(int num_buckets, int threshold, int refractory_period_sec) {
     StatsdConfig config;
     config.add_allowed_log_source("AID_ROOT"); // LogEvent defaults to UID of root.
     auto wakelockAcquireMatcher = CreateAcquireWakelockAtomMatcher();
@@ -46,7 +47,7 @@
     alert->set_id(StringToId("alert"));
     alert->set_metric_id(123456);
     alert->set_num_buckets(num_buckets);
-    alert->set_refractory_period_secs(10);
+    alert->set_refractory_period_secs(refractory_period_sec);
     alert->set_trigger_if_sum_gt(threshold);
     return config;
 }
@@ -56,9 +57,9 @@
 TEST(AnomalyDetectionE2eTest, TestSlicedCountMetric_single_bucket) {
     const int num_buckets = 1;
     const int threshold = 3;
-    auto config = CreateStatsdConfig(num_buckets, threshold);
+    const int refractory_period_sec = 10;
+    auto config = CreateStatsdConfig(num_buckets, threshold, refractory_period_sec);
     const uint64_t alert_id = config.alert(0).id();
-    const uint32_t refractory_period_sec = config.alert(0).refractory_period_secs();
 
     int64_t bucketStartTimeNs = 10000000000;
     int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000;
@@ -173,9 +174,9 @@
 TEST(AnomalyDetectionE2eTest, TestSlicedCountMetric_multiple_buckets) {
     const int num_buckets = 3;
     const int threshold = 3;
-    auto config = CreateStatsdConfig(num_buckets, threshold);
+    const int refractory_period_sec = 10;
+    auto config = CreateStatsdConfig(num_buckets, threshold, refractory_period_sec);
     const uint64_t alert_id = config.alert(0).id();
-    const uint32_t refractory_period_sec = config.alert(0).refractory_period_secs();
 
     int64_t bucketStartTimeNs = 10000000000;
     int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(config.count_metric(0).bucket()) * 1000000;
@@ -240,6 +241,146 @@
               anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1));
 }
 
+TEST(AnomalyDetectionE2eTest, TestCountMetric_save_refractory_to_disk_no_data_written) {
+    const int num_buckets = 1;
+    const int threshold = 0;
+    const int refractory_period_sec = 86400 * 365; // 1 year
+    auto config = CreateStatsdConfig(num_buckets, threshold, refractory_period_sec);
+    const int64_t alert_id = config.alert(0).id();
+
+    int64_t bucketStartTimeNs = 10000000000;
+
+    int configUid = 2000;
+    int64_t configId = 1000;
+    ConfigKey cfgKey(configUid, configId);
+    auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
+    EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+    EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
+    EXPECT_EQ(1u, processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers.size());
+
+    metadata::StatsMetadataList result;
+    int64_t mockWallClockNs = 1584991200 * NS_PER_SEC;
+    int64_t mockElapsedTimeNs = bucketStartTimeNs + 5000 * NS_PER_SEC;
+    processor->WriteMetadataToProto(mockWallClockNs, mockElapsedTimeNs, &result);
+
+    EXPECT_EQ(result.stats_metadata_size(), 0);
+}
+
+TEST(AnomalyDetectionE2eTest, TestCountMetric_save_refractory_to_disk) {
+    const int num_buckets = 1;
+    const int threshold = 0;
+    const int refractory_period_sec = 86400 * 365; // 1 year
+    auto config = CreateStatsdConfig(num_buckets, threshold, refractory_period_sec);
+    const int64_t alert_id = config.alert(0).id();
+
+    int64_t bucketStartTimeNs = 10000000000;
+
+    int configUid = 2000;
+    int64_t configId = 1000;
+    ConfigKey cfgKey(configUid, configId);
+    auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
+    EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+    EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
+    EXPECT_EQ(1u, processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers.size());
+
+    sp<AnomalyTracker> anomalyTracker =
+            processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers[0];
+
+    std::vector<int> attributionUids1 = {111};
+    std::vector<string> attributionTags1 = {"App1"};
+    std::vector<int> attributionUids2 = {111, 222};
+    std::vector<string> attributionTags2 = {"App1", "GMSCoreModule1"};
+
+    FieldValue fieldValue1(Field(util::WAKELOCK_STATE_CHANGED, (int32_t)0x02010101),
+                           Value((int32_t)111));
+    HashableDimensionKey whatKey1({fieldValue1});
+    MetricDimensionKey dimensionKey1(whatKey1, DEFAULT_DIMENSION_KEY);
+
+    auto event = CreateAcquireWakelockEvent(bucketStartTimeNs + 2, attributionUids1,
+                                            attributionTags1, "wl1");
+    processor->OnLogEvent(event.get());
+    EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + 2) / NS_PER_SEC + 1,
+              anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1));
+
+    metadata::StatsMetadataList result;
+    int64_t mockWallClockNs = 1584991200 * NS_PER_SEC;
+    int64_t mockElapsedTimeNs = bucketStartTimeNs + 5000 * NS_PER_SEC;
+    processor->WriteMetadataToProto(mockWallClockNs, mockElapsedTimeNs, &result);
+
+    metadata::StatsMetadata statsMetadata = result.stats_metadata(0);
+    EXPECT_EQ(result.stats_metadata_size(), 1);
+    EXPECT_EQ(statsMetadata.config_key().config_id(), configId);
+    EXPECT_EQ(statsMetadata.config_key().uid(), configUid);
+
+    metadata::AlertMetadata alertMetadata = statsMetadata.alert_metadata(0);
+    EXPECT_EQ(statsMetadata.alert_metadata_size(), 1);
+    EXPECT_EQ(alertMetadata.alert_id(), alert_id);
+    metadata::AlertDimensionKeyedData keyedData = alertMetadata.alert_dim_keyed_data(0);
+    EXPECT_EQ(alertMetadata.alert_dim_keyed_data_size(), 1);
+    EXPECT_EQ(keyedData.last_refractory_ends_sec(),
+              anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1) -
+              mockElapsedTimeNs / NS_PER_SEC +
+              mockWallClockNs / NS_PER_SEC);
+
+    metadata::MetricDimensionKey metadataDimKey = keyedData.dimension_key();
+    metadata::FieldValue dimKeyInWhat = metadataDimKey.dimension_key_in_what(0);
+    EXPECT_EQ(dimKeyInWhat.field().tag(), fieldValue1.mField.getTag());
+    EXPECT_EQ(dimKeyInWhat.field().field(), fieldValue1.mField.getField());
+    EXPECT_EQ(dimKeyInWhat.value_int(), fieldValue1.mValue.int_value);
+}
+
+TEST(AnomalyDetectionE2eTest, TestCountMetric_load_refractory_from_disk) {
+    const int num_buckets = 1;
+    const int threshold = 0;
+    const int refractory_period_sec = 86400 * 365; // 1 year
+    auto config = CreateStatsdConfig(num_buckets, threshold, refractory_period_sec);
+    const int64_t alert_id = config.alert(0).id();
+
+    int64_t bucketStartTimeNs = 10000000000;
+
+    int configUid = 2000;
+    int64_t configId = 1000;
+    ConfigKey cfgKey(configUid, configId);
+    auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
+    EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+    EXPECT_TRUE(processor->mMetricsManagers.begin()->second->isConfigValid());
+    EXPECT_EQ(1u, processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers.size());
+
+    sp<AnomalyTracker> anomalyTracker =
+            processor->mMetricsManagers.begin()->second->mAllAnomalyTrackers[0];
+
+    std::vector<int> attributionUids1 = {111};
+    std::vector<string> attributionTags1 = {"App1"};
+    std::vector<int> attributionUids2 = {111, 222};
+    std::vector<string> attributionTags2 = {"App1", "GMSCoreModule1"};
+
+    FieldValue fieldValue1(Field(util::WAKELOCK_STATE_CHANGED, (int32_t)0x02010101),
+                           Value((int32_t)111));
+    HashableDimensionKey whatKey1({fieldValue1});
+    MetricDimensionKey dimensionKey1(whatKey1, DEFAULT_DIMENSION_KEY);
+
+    auto event = CreateAcquireWakelockEvent(bucketStartTimeNs + 2, attributionUids1,
+                                            attributionTags1, "wl1");
+    processor->OnLogEvent(event.get());
+    EXPECT_EQ(refractory_period_sec + (bucketStartTimeNs + 2) / NS_PER_SEC + 1,
+              anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1));
+
+    int64_t mockWallClockNs = 1584991200 * NS_PER_SEC;
+    int64_t mockElapsedTimeNs = bucketStartTimeNs + 5000 * NS_PER_SEC;
+    processor->SaveMetadataToDisk(mockWallClockNs, mockElapsedTimeNs);
+
+    auto processor2 = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
+    int64_t mockElapsedTimeSinceBoot = 10 * NS_PER_SEC;
+    processor2->LoadMetadataFromDisk(mockWallClockNs, mockElapsedTimeSinceBoot);
+
+    sp<AnomalyTracker> anomalyTracker2 =
+                processor2->mMetricsManagers.begin()->second->mAllAnomalyTrackers[0];
+    EXPECT_EQ(anomalyTracker2->getRefractoryPeriodEndsSec(dimensionKey1) -
+              mockElapsedTimeSinceBoot / NS_PER_SEC,
+              anomalyTracker->getRefractoryPeriodEndsSec(dimensionKey1) -
+              mockElapsedTimeNs / NS_PER_SEC);
+}
+
 #else
 GTEST_LOG_(INFO) << "This test does nothing.\n";
 #endif
diff --git a/cmds/statsd/tests/e2e/DurationMetric_e2e_test.cpp b/cmds/statsd/tests/e2e/DurationMetric_e2e_test.cpp
index ae2a0f5..2659944 100644
--- a/cmds/statsd/tests/e2e/DurationMetric_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/DurationMetric_e2e_test.cpp
@@ -14,12 +14,13 @@
 
 #include <gtest/gtest.h>
 
+#include <vector>
+
 #include "src/StatsLogProcessor.h"
+#include "src/state/StateTracker.h"
 #include "src/stats_log_util.h"
 #include "tests/statsd_test_util.h"
 
-#include <vector>
-
 namespace android {
 namespace os {
 namespace statsd {
@@ -101,7 +102,7 @@
             reports.reports(0).metrics(0).duration_metrics();
     EXPECT_EQ(1, durationMetrics.data_size());
 
-    auto data = durationMetrics.data(0);
+    DurationMetricData data = durationMetrics.data(0);
     EXPECT_EQ(1, data.bucket_info_size());
     EXPECT_EQ(durationEndNs - durationStartNs, data.bucket_info(0).duration_nanos());
     EXPECT_EQ(configAddedTimeNs, data.bucket_info(0).start_bucket_elapsed_nanos());
@@ -183,7 +184,7 @@
             reports.reports(0).metrics(0).duration_metrics();
     EXPECT_EQ(1, durationMetrics.data_size());
 
-    auto data = durationMetrics.data(0);
+    DurationMetricData data = durationMetrics.data(0);
     EXPECT_EQ(1, data.bucket_info_size());
 
     auto bucketInfo = data.bucket_info(0);
@@ -353,7 +354,7 @@
             reports.reports(0).metrics(0).duration_metrics();
     EXPECT_EQ(1, durationMetrics.data_size());
 
-    auto data = durationMetrics.data(0);
+    DurationMetricData data = durationMetrics.data(0);
     EXPECT_EQ(1, data.bucket_info_size());
 
     auto bucketInfo = data.bucket_info(0);
@@ -434,7 +435,7 @@
     EXPECT_EQ(1, reports.reports(0).metrics_size());
     EXPECT_EQ(1, reports.reports(0).metrics(0).duration_metrics().data_size());
 
-    auto data = reports.reports(0).metrics(0).duration_metrics().data(0);
+    DurationMetricData data = reports.reports(0).metrics(0).duration_metrics().data(0);
 
     // Validate bucket info.
     EXPECT_EQ(1, data.bucket_info_size());
@@ -533,7 +534,7 @@
     EXPECT_EQ(1, reports.reports(0).metrics_size());
     EXPECT_EQ(1, reports.reports(0).metrics(0).duration_metrics().data_size());
 
-    auto data = reports.reports(0).metrics(0).duration_metrics().data(0);
+    DurationMetricData data = reports.reports(0).metrics(0).duration_metrics().data(0);
     // Validate dimension value.
     ValidateAttributionUidDimension(data.dimensions_in_what(),
                                     util::WAKELOCK_STATE_CHANGED, appUid);
@@ -691,7 +692,7 @@
     EXPECT_EQ(1, reports.reports(0).metrics_size());
     EXPECT_EQ(1, reports.reports(0).metrics(0).duration_metrics().data_size());
 
-    auto data = reports.reports(0).metrics(0).duration_metrics().data(0);
+    DurationMetricData data = reports.reports(0).metrics(0).duration_metrics().data(0);
     // Validate dimension value.
     ValidateAttributionUidDimension(data.dimensions_in_what(),
                                     util::WAKELOCK_STATE_CHANGED, appUid);
@@ -709,6 +710,734 @@
     EXPECT_EQ(bucketStartTimeNs + bucketSizeNs - duration2StartNs, bucketInfo.duration_nanos());
 }
 
+TEST(DurationMetricE2eTest, TestWithSlicedState) {
+    // Initialize config.
+    StatsdConfig config;
+    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
+
+    *config.add_atom_matcher() = CreateBatterySaverModeStartAtomMatcher();
+    *config.add_atom_matcher() = CreateBatterySaverModeStopAtomMatcher();
+
+    auto batterySaverModePredicate = CreateBatterySaverModePredicate();
+    *config.add_predicate() = batterySaverModePredicate;
+
+    auto screenState = CreateScreenState();
+    *config.add_state() = screenState;
+
+    // Create duration metric that slices by screen state.
+    auto durationMetric = config.add_duration_metric();
+    durationMetric->set_id(StringToId("DurationBatterySaverModeSliceScreen"));
+    durationMetric->set_what(batterySaverModePredicate.id());
+    durationMetric->add_slice_by_state(screenState.id());
+    durationMetric->set_aggregation_type(DurationMetric::SUM);
+    durationMetric->set_bucket(FIVE_MINUTES);
+
+    // Initialize StatsLogProcessor.
+    int uid = 12345;
+    int64_t cfgId = 98765;
+    ConfigKey cfgKey(uid, cfgId);
+    uint64_t bucketStartTimeNs = 10000000000;  // 0:10
+    uint64_t bucketSizeNs =
+            TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
+    auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
+
+    EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+    sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second;
+    EXPECT_TRUE(metricsManager->isConfigValid());
+    EXPECT_EQ(metricsManager->mAllMetricProducers.size(), 1);
+    EXPECT_TRUE(metricsManager->isActive());
+    sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0];
+    EXPECT_TRUE(metricProducer->mIsActive);
+    EXPECT_EQ(metricProducer->mSlicedStateAtoms.size(), 1);
+    EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(0), SCREEN_STATE_ATOM_ID);
+    EXPECT_EQ(metricProducer->mStateGroupMap.size(), 0);
+
+    // Check that StateTrackers were initialized correctly.
+    EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount());
+    EXPECT_EQ(1, StateManager::getInstance().getListenersCount(SCREEN_STATE_ATOM_ID));
+
+    /*
+               bucket #1                      bucket #2
+    |     1     2     3     4     5     6     7     8     9     10 (minutes)
+    |-----------------------------|-----------------------------|--
+        ON              OFF     ON                                  (BatterySaverMode)
+      |          |                   |                              (ScreenIsOnEvent)
+           |                  |                                     (ScreenIsOffEvent)
+              |                                                     (ScreenDozeEvent)
+    */
+    // Initialize log events.
+    std::vector<std::unique_ptr<LogEvent>> events;
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 10 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_ON));                       // 0:20
+    events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 20 * NS_PER_SEC));  // 0:30
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 50 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_OFF));  // 1:00
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 80 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_DOZE));  // 1:30
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 120 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_ON));                         // 2:10
+    events.push_back(CreateBatterySaverOffEvent(bucketStartTimeNs + 200 * NS_PER_SEC));  // 3:30
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 250 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_OFF));                       // 4:20
+    events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 280 * NS_PER_SEC));  // 4:50
+
+    // Bucket boundary.
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 310 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_ON));  // 5:20
+
+    // Send log events to StatsLogProcessor.
+    for (auto& event : events) {
+        processor->OnLogEvent(event.get());
+    }
+
+    // Check dump report.
+    vector<uint8_t> buffer;
+    ConfigMetricsReportList reports;
+    processor->onDumpReport(cfgKey, bucketStartTimeNs + 360 * NS_PER_SEC,
+                            true /* include current partial bucket */, true, ADB_DUMP, FAST,
+                            &buffer);  // 6:10
+    EXPECT_GT(buffer.size(), 0);
+    EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+    backfillDimensionPath(&reports);
+    backfillStringInReport(&reports);
+    backfillStartEndTimestamp(&reports);
+
+    EXPECT_EQ(1, reports.reports_size());
+    EXPECT_EQ(1, reports.reports(0).metrics_size());
+    EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics());
+    EXPECT_EQ(3, reports.reports(0).metrics(0).duration_metrics().data_size());
+
+    DurationMetricData data = reports.reports(0).metrics(0).duration_metrics().data(0);
+    EXPECT_EQ(1, data.slice_by_state_size());
+    EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_value());
+    EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_OFF, data.slice_by_state(0).value());
+    EXPECT_EQ(2, data.bucket_info_size());
+    EXPECT_EQ(50 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+    EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+    EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(1).duration_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos());
+    EXPECT_EQ(370 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos());
+
+    data = reports.reports(0).metrics(0).duration_metrics().data(1);
+    EXPECT_EQ(1, data.slice_by_state_size());
+    EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_value());
+    EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, data.slice_by_state(0).value());
+    EXPECT_EQ(2, data.bucket_info_size());
+    EXPECT_EQ(110 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+    EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+    EXPECT_EQ(50 * NS_PER_SEC, data.bucket_info(1).duration_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos());
+    EXPECT_EQ(370 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos());
+
+    data = reports.reports(0).metrics(0).duration_metrics().data(2);
+    EXPECT_EQ(1, data.slice_by_state_size());
+    EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_value());
+    EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_DOZE, data.slice_by_state(0).value());
+    EXPECT_EQ(1, data.bucket_info_size());
+    EXPECT_EQ(40 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+    EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+}
+
+TEST(DurationMetricE2eTest, TestWithConditionAndSlicedState) {
+    // Initialize config.
+    StatsdConfig config;
+    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
+
+    *config.add_atom_matcher() = CreateBatterySaverModeStartAtomMatcher();
+    *config.add_atom_matcher() = CreateBatterySaverModeStopAtomMatcher();
+    *config.add_atom_matcher() = CreateBatteryStateNoneMatcher();
+    *config.add_atom_matcher() = CreateBatteryStateUsbMatcher();
+
+    auto batterySaverModePredicate = CreateBatterySaverModePredicate();
+    *config.add_predicate() = batterySaverModePredicate;
+
+    auto deviceUnpluggedPredicate = CreateDeviceUnpluggedPredicate();
+    *config.add_predicate() = deviceUnpluggedPredicate;
+
+    auto screenState = CreateScreenState();
+    *config.add_state() = screenState;
+
+    // Create duration metric that has a condition and slices by screen state.
+    auto durationMetric = config.add_duration_metric();
+    durationMetric->set_id(StringToId("DurationBatterySaverModeOnBatterySliceScreen"));
+    durationMetric->set_what(batterySaverModePredicate.id());
+    durationMetric->set_condition(deviceUnpluggedPredicate.id());
+    durationMetric->add_slice_by_state(screenState.id());
+    durationMetric->set_aggregation_type(DurationMetric::SUM);
+    durationMetric->set_bucket(FIVE_MINUTES);
+
+    // Initialize StatsLogProcessor.
+    int uid = 12345;
+    int64_t cfgId = 98765;
+    ConfigKey cfgKey(uid, cfgId);
+    uint64_t bucketStartTimeNs = 10000000000;  // 0:10
+    uint64_t bucketSizeNs =
+            TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
+    auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
+
+    EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+    sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second;
+    EXPECT_TRUE(metricsManager->isConfigValid());
+    EXPECT_EQ(metricsManager->mAllMetricProducers.size(), 1);
+    EXPECT_TRUE(metricsManager->isActive());
+    sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0];
+    EXPECT_TRUE(metricProducer->mIsActive);
+    EXPECT_EQ(metricProducer->mSlicedStateAtoms.size(), 1);
+    EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(0), SCREEN_STATE_ATOM_ID);
+    EXPECT_EQ(metricProducer->mStateGroupMap.size(), 0);
+
+    // Check that StateTrackers were initialized correctly.
+    EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount());
+    EXPECT_EQ(1, StateManager::getInstance().getListenersCount(SCREEN_STATE_ATOM_ID));
+
+    /*
+               bucket #1                      bucket #2
+    |       1       2       3       4       5     6     7     8  (minutes)
+    |---------------------------------------|------------------
+         ON                              OFF    ON             (BatterySaverMode)
+                  T            F    T                          (DeviceUnpluggedPredicate)
+             |          |              |                       (ScreenIsOnEvent)
+                |           |                       |          (ScreenIsOffEvent)
+                                |                              (ScreenDozeEvent)
+    */
+    // Initialize log events.
+    std::vector<std::unique_ptr<LogEvent>> events;
+    events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 20 * NS_PER_SEC));  // 0:30
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 60 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_ON));  // 1:10
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 80 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_OFF));  // 1:30
+    events.push_back(
+            CreateBatteryStateChangedEvent(bucketStartTimeNs + 110 * NS_PER_SEC,
+                                           BatteryPluggedStateEnum::BATTERY_PLUGGED_NONE));  // 2:00
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 145 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_ON));  // 2:35
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 170 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_OFF));  // 3:00
+    events.push_back(
+            CreateBatteryStateChangedEvent(bucketStartTimeNs + 180 * NS_PER_SEC,
+                                           BatteryPluggedStateEnum::BATTERY_PLUGGED_USB));  // 3:10
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 200 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_DOZE));  // 3:30
+    events.push_back(
+            CreateBatteryStateChangedEvent(bucketStartTimeNs + 230 * NS_PER_SEC,
+                                           BatteryPluggedStateEnum::BATTERY_PLUGGED_NONE));  // 4:00
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 260 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_ON));                         // 4:30
+    events.push_back(CreateBatterySaverOffEvent(bucketStartTimeNs + 280 * NS_PER_SEC));  // 4:50
+
+    // Bucket boundary.
+    events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 320 * NS_PER_SEC));  // 5:30
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 380 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_OFF));  // 6:30
+
+    // Send log events to StatsLogProcessor.
+    for (auto& event : events) {
+        processor->OnLogEvent(event.get());
+    }
+
+    // Check dump report.
+    vector<uint8_t> buffer;
+    ConfigMetricsReportList reports;
+    processor->onDumpReport(cfgKey, bucketStartTimeNs + 410 * NS_PER_SEC,
+                            true /* include current partial bucket */, true, ADB_DUMP, FAST,
+                            &buffer);
+    EXPECT_GT(buffer.size(), 0);
+    EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+    backfillDimensionPath(&reports);
+    backfillStringInReport(&reports);
+    backfillStartEndTimestamp(&reports);
+
+    EXPECT_EQ(1, reports.reports_size());
+    EXPECT_EQ(1, reports.reports(0).metrics_size());
+    EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics());
+    EXPECT_EQ(3, reports.reports(0).metrics(0).duration_metrics().data_size());
+
+    DurationMetricData data = reports.reports(0).metrics(0).duration_metrics().data(0);
+    EXPECT_EQ(1, data.slice_by_state_size());
+    EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_value());
+    EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_OFF, data.slice_by_state(0).value());
+    EXPECT_EQ(2, data.bucket_info_size());
+    EXPECT_EQ(45 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+    EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+    EXPECT_EQ(30 * NS_PER_SEC, data.bucket_info(1).duration_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos());
+    EXPECT_EQ(420 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos());
+
+    data = reports.reports(0).metrics(0).duration_metrics().data(2);
+    EXPECT_EQ(1, data.slice_by_state_size());
+    EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_value());
+    EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_ON, data.slice_by_state(0).value());
+    EXPECT_EQ(2, data.bucket_info_size());
+    EXPECT_EQ(45 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+    EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+    EXPECT_EQ(60 * NS_PER_SEC, data.bucket_info(1).duration_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos());
+    EXPECT_EQ(420 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos());
+
+    data = reports.reports(0).metrics(0).duration_metrics().data(1);
+    EXPECT_EQ(1, data.slice_by_state_size());
+    EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_value());
+    EXPECT_EQ(android::view::DisplayStateEnum::DISPLAY_STATE_DOZE, data.slice_by_state(0).value());
+    EXPECT_EQ(1, data.bucket_info_size());
+    EXPECT_EQ(30 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+    EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+}
+
+TEST(DurationMetricE2eTest, TestWithSlicedStateMapped) {
+    // Initialize config.
+    StatsdConfig config;
+    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
+
+    *config.add_atom_matcher() = CreateBatterySaverModeStartAtomMatcher();
+    *config.add_atom_matcher() = CreateBatterySaverModeStopAtomMatcher();
+
+    auto batterySaverModePredicate = CreateBatterySaverModePredicate();
+    *config.add_predicate() = batterySaverModePredicate;
+
+    auto screenStateWithMap = CreateScreenStateWithOnOffMap();
+    *config.add_state() = screenStateWithMap;
+
+    // Create duration metric that slices by mapped screen state.
+    auto durationMetric = config.add_duration_metric();
+    durationMetric->set_id(StringToId("DurationBatterySaverModeSliceScreenMapped"));
+    durationMetric->set_what(batterySaverModePredicate.id());
+    durationMetric->add_slice_by_state(screenStateWithMap.id());
+    durationMetric->set_aggregation_type(DurationMetric::SUM);
+    durationMetric->set_bucket(FIVE_MINUTES);
+
+    // Initialize StatsLogProcessor.
+    int uid = 12345;
+    int64_t cfgId = 98765;
+    ConfigKey cfgKey(uid, cfgId);
+    uint64_t bucketStartTimeNs = 10000000000;  // 0:10
+    uint64_t bucketSizeNs =
+            TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
+    auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
+
+    EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+    sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second;
+    EXPECT_TRUE(metricsManager->isConfigValid());
+    EXPECT_EQ(metricsManager->mAllMetricProducers.size(), 1);
+    EXPECT_TRUE(metricsManager->isActive());
+    sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0];
+    EXPECT_TRUE(metricProducer->mIsActive);
+    EXPECT_EQ(metricProducer->mSlicedStateAtoms.size(), 1);
+    EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(0), SCREEN_STATE_ATOM_ID);
+    EXPECT_EQ(metricProducer->mStateGroupMap.size(), 1);
+
+    // Check that StateTrackers were initialized correctly.
+    EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount());
+    EXPECT_EQ(1, StateManager::getInstance().getListenersCount(SCREEN_STATE_ATOM_ID));
+
+    /*
+               bucket #1                      bucket #2
+    |     1     2     3     4     5     6     7     8     9     10 (minutes)
+    |-----------------------------|-----------------------------|--
+        ON              OFF     ON                                  (BatterySaverMode)
+     ---------------------------------------------------------SCREEN_OFF events
+           |                  |                                  (ScreenStateOffEvent = 1)
+              |                                                  (ScreenStateDozeEvent = 3)
+                                                |                (ScreenStateDozeSuspendEvent = 4)
+     ---------------------------------------------------------SCREEN_ON events
+      |          |                   |                           (ScreenStateOnEvent = 2)
+                      |                                          (ScreenStateVrEvent = 5)
+                                            |                    (ScreenStateOnSuspendEvent = 6)
+    */
+    // Initialize log events.
+    std::vector<std::unique_ptr<LogEvent>> events;
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 10 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_ON));                       // 0:20
+    events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 20 * NS_PER_SEC));  // 0:30
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 70 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_OFF));  // 1:20
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 100 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_DOZE));  // 1:50
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 120 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_ON));  // 2:10
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 170 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_VR));                         // 3:00
+    events.push_back(CreateBatterySaverOffEvent(bucketStartTimeNs + 200 * NS_PER_SEC));  // 3:30
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 250 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_OFF));                       // 4:20
+    events.push_back(CreateBatterySaverOnEvent(bucketStartTimeNs + 280 * NS_PER_SEC));  // 4:50
+
+    // Bucket boundary 5:10.
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 320 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_ON));  // 5:30
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 390 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_ON_SUSPEND));  // 6:40
+    events.push_back(CreateScreenStateChangedEvent(
+            bucketStartTimeNs + 430 * NS_PER_SEC,
+            android::view::DisplayStateEnum::DISPLAY_STATE_DOZE_SUSPEND));  // 7:20
+    // Send log events to StatsLogProcessor.
+    for (auto& event : events) {
+        processor->OnLogEvent(event.get());
+    }
+
+    // Check dump report.
+    vector<uint8_t> buffer;
+    ConfigMetricsReportList reports;
+    processor->onDumpReport(cfgKey, bucketStartTimeNs + 490 * NS_PER_SEC,
+                            true /* include current partial bucket */, true, ADB_DUMP, FAST,
+                            &buffer);
+    EXPECT_GT(buffer.size(), 0);
+    EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+    backfillDimensionPath(&reports);
+    backfillStringInReport(&reports);
+    backfillStartEndTimestamp(&reports);
+
+    EXPECT_EQ(1, reports.reports_size());
+    EXPECT_EQ(1, reports.reports(0).metrics_size());
+    EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics());
+    EXPECT_EQ(2, reports.reports(0).metrics(0).duration_metrics().data_size());
+
+    DurationMetricData data = reports.reports(0).metrics(0).duration_metrics().data(0);
+    EXPECT_EQ(1, data.slice_by_state_size());
+    EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_group_id());
+    EXPECT_EQ(StringToId("SCREEN_ON"), data.slice_by_state(0).group_id());
+    EXPECT_EQ(2, data.bucket_info_size());
+    EXPECT_EQ(130 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+    EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+    EXPECT_EQ(110 * NS_PER_SEC, data.bucket_info(1).duration_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos());
+    EXPECT_EQ(500 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos());
+
+    data = reports.reports(0).metrics(0).duration_metrics().data(1);
+    EXPECT_EQ(1, data.slice_by_state_size());
+    EXPECT_EQ(SCREEN_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_group_id());
+    EXPECT_EQ(StringToId("SCREEN_OFF"), data.slice_by_state(0).group_id());
+    EXPECT_EQ(2, data.bucket_info_size());
+    EXPECT_EQ(70 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+    EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+    EXPECT_EQ(80 * NS_PER_SEC, data.bucket_info(1).duration_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos());
+    EXPECT_EQ(500 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos());
+}
+
+TEST(DurationMetricE2eTest, TestSlicedStatePrimaryFieldsNotSubsetDimInWhat) {
+    // Initialize config.
+    StatsdConfig config;
+    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
+
+    *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher();
+    *config.add_atom_matcher() = CreateReleaseWakelockAtomMatcher();
+
+    auto holdingWakelockPredicate = CreateHoldingWakelockPredicate();
+    *config.add_predicate() = holdingWakelockPredicate;
+
+    auto uidProcessState = CreateUidProcessState();
+    *config.add_state() = uidProcessState;
+
+    // Create duration metric that slices by uid process state.
+    auto durationMetric = config.add_duration_metric();
+    durationMetric->set_id(StringToId("DurationHoldingWakelockSliceUidProcessState"));
+    durationMetric->set_what(holdingWakelockPredicate.id());
+    durationMetric->add_slice_by_state(uidProcessState.id());
+    durationMetric->set_aggregation_type(DurationMetric::SUM);
+    durationMetric->set_bucket(FIVE_MINUTES);
+
+    // The state has only one primary field (uid).
+    auto stateLink = durationMetric->add_state_link();
+    stateLink->set_state_atom_id(UID_PROCESS_STATE_ATOM_ID);
+    auto fieldsInWhat = stateLink->mutable_fields_in_what();
+    *fieldsInWhat = CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST});
+    auto fieldsInState = stateLink->mutable_fields_in_state();
+    *fieldsInState = CreateDimensions(UID_PROCESS_STATE_ATOM_ID, {1 /* uid */});
+
+    // Initialize StatsLogProcessor.
+    int uid = 12345;
+    int64_t cfgId = 98765;
+    ConfigKey cfgKey(uid, cfgId);
+    uint64_t bucketStartTimeNs = 10000000000;  // 0:10
+    uint64_t bucketSizeNs =
+            TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
+    auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
+
+    // This config is rejected because the dimension in what fields are not a superset of the sliced
+    // state primary fields.
+    EXPECT_EQ(processor->mMetricsManagers.size(), 0);
+}
+
+TEST(DurationMetricE2eTest, TestWithSlicedStatePrimaryFieldsSubset) {
+    // Initialize config.
+    StatsdConfig config;
+    config.add_allowed_log_source("AID_ROOT");  // LogEvent defaults to UID of root.
+
+    *config.add_atom_matcher() = CreateAcquireWakelockAtomMatcher();
+    *config.add_atom_matcher() = CreateReleaseWakelockAtomMatcher();
+
+    auto holdingWakelockPredicate = CreateHoldingWakelockPredicate();
+    *config.add_predicate() = holdingWakelockPredicate;
+
+    auto uidProcessState = CreateUidProcessState();
+    *config.add_state() = uidProcessState;
+
+    // Create duration metric that slices by uid process state.
+    auto durationMetric = config.add_duration_metric();
+    durationMetric->set_id(StringToId("DurationPartialWakelockPerTagUidSliceProcessState"));
+    durationMetric->set_what(holdingWakelockPredicate.id());
+    durationMetric->add_slice_by_state(uidProcessState.id());
+    durationMetric->set_aggregation_type(DurationMetric::SUM);
+    durationMetric->set_bucket(FIVE_MINUTES);
+
+    // The metric is dimensioning by first uid of attribution node and tag.
+    *durationMetric->mutable_dimensions_in_what() = CreateAttributionUidAndOtherDimensions(
+            util::WAKELOCK_STATE_CHANGED, {Position::FIRST}, {3 /* tag */});
+    // The state has only one primary field (uid).
+    auto stateLink = durationMetric->add_state_link();
+    stateLink->set_state_atom_id(UID_PROCESS_STATE_ATOM_ID);
+    auto fieldsInWhat = stateLink->mutable_fields_in_what();
+    *fieldsInWhat = CreateAttributionUidDimensions(util::WAKELOCK_STATE_CHANGED, {Position::FIRST});
+    auto fieldsInState = stateLink->mutable_fields_in_state();
+    *fieldsInState = CreateDimensions(UID_PROCESS_STATE_ATOM_ID, {1 /* uid */});
+
+    // Initialize StatsLogProcessor.
+    int uid = 12345;
+    int64_t cfgId = 98765;
+    ConfigKey cfgKey(uid, cfgId);
+    uint64_t bucketStartTimeNs = 10000000000;  // 0:10
+    uint64_t bucketSizeNs =
+            TimeUnitToBucketSizeInMillis(config.duration_metric(0).bucket()) * 1000000LL;
+    auto processor = CreateStatsLogProcessor(bucketStartTimeNs, bucketStartTimeNs, config, cfgKey);
+
+    EXPECT_EQ(processor->mMetricsManagers.size(), 1u);
+    sp<MetricsManager> metricsManager = processor->mMetricsManagers.begin()->second;
+    EXPECT_TRUE(metricsManager->isConfigValid());
+    EXPECT_EQ(metricsManager->mAllMetricProducers.size(), 1);
+    EXPECT_TRUE(metricsManager->isActive());
+    sp<MetricProducer> metricProducer = metricsManager->mAllMetricProducers[0];
+    EXPECT_TRUE(metricProducer->mIsActive);
+    EXPECT_EQ(metricProducer->mSlicedStateAtoms.size(), 1);
+    EXPECT_EQ(metricProducer->mSlicedStateAtoms.at(0), UID_PROCESS_STATE_ATOM_ID);
+    EXPECT_EQ(metricProducer->mStateGroupMap.size(), 0);
+
+    // Check that StateTrackers were initialized correctly.
+    EXPECT_EQ(1, StateManager::getInstance().getStateTrackersCount());
+    EXPECT_EQ(1, StateManager::getInstance().getListenersCount(UID_PROCESS_STATE_ATOM_ID));
+
+    // Initialize log events.
+    int appUid1 = 1001;
+    int appUid2 = 1002;
+    std::vector<int> attributionUids1 = {appUid1};
+    std::vector<string> attributionTags1 = {"App1"};
+
+    std::vector<int> attributionUids2 = {appUid2};
+    std::vector<string> attributionTags2 = {"App2"};
+
+    std::vector<std::unique_ptr<LogEvent>> events;
+    events.push_back(CreateUidProcessStateChangedEvent(
+            bucketStartTimeNs + 10 * NS_PER_SEC, appUid1,
+            android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND));  // 0:20
+    events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 20 * NS_PER_SEC,
+                                                attributionUids1, attributionTags1,
+                                                "wakelock1"));  // 0:30
+    events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 25 * NS_PER_SEC,
+                                                attributionUids1, attributionTags1,
+                                                "wakelock2"));  // 0:35
+    events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 30 * NS_PER_SEC,
+                                                attributionUids2, attributionTags2,
+                                                "wakelock1"));  // 0:40
+    events.push_back(CreateAcquireWakelockEvent(bucketStartTimeNs + 35 * NS_PER_SEC,
+                                                attributionUids2, attributionTags2,
+                                                "wakelock2"));  // 0:45
+    events.push_back(CreateUidProcessStateChangedEvent(
+            bucketStartTimeNs + 50 * NS_PER_SEC, appUid2,
+            android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND));  // 1:00
+    events.push_back(CreateUidProcessStateChangedEvent(
+            bucketStartTimeNs + 60 * NS_PER_SEC, appUid1,
+            android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND));  // 1:10
+    events.push_back(CreateReleaseWakelockEvent(bucketStartTimeNs + 100 * NS_PER_SEC,
+                                                attributionUids2, attributionTags2,
+                                                "wakelock1"));  // 1:50
+    events.push_back(CreateUidProcessStateChangedEvent(
+            bucketStartTimeNs + 120 * NS_PER_SEC, appUid2,
+            android::app::ProcessStateEnum::PROCESS_STATE_FOREGROUND_SERVICE));  // 2:10
+    events.push_back(CreateReleaseWakelockEvent(bucketStartTimeNs + 200 * NS_PER_SEC,
+                                                attributionUids1, attributionTags1,
+                                                "wakelock2"));  // 3:30
+
+    // Send log events to StatsLogProcessor.
+    for (auto& event : events) {
+        processor->OnLogEvent(event.get());
+    }
+
+    // Check dump report.
+    vector<uint8_t> buffer;
+    ConfigMetricsReportList reports;
+    processor->onDumpReport(cfgKey, bucketStartTimeNs + 320 * NS_PER_SEC,
+                            true /* include current partial bucket */, true, ADB_DUMP, FAST,
+                            &buffer);
+    EXPECT_GT(buffer.size(), 0);
+    EXPECT_TRUE(reports.ParseFromArray(&buffer[0], buffer.size()));
+    backfillDimensionPath(&reports);
+    backfillStringInReport(&reports);
+    backfillStartEndTimestamp(&reports);
+
+    EXPECT_EQ(1, reports.reports_size());
+    EXPECT_EQ(1, reports.reports(0).metrics_size());
+    EXPECT_TRUE(reports.reports(0).metrics(0).has_duration_metrics());
+    EXPECT_EQ(9, reports.reports(0).metrics(0).duration_metrics().data_size());
+
+    DurationMetricData data = reports.reports(0).metrics(0).duration_metrics().data(0);
+    ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid1,
+                                                  "wakelock2");
+    EXPECT_EQ(1, data.slice_by_state_size());
+    EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_value());
+    EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND,
+              data.slice_by_state(0).value());
+    EXPECT_EQ(1, data.bucket_info_size());
+    EXPECT_EQ(35 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+    EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+
+    data = reports.reports(0).metrics(0).duration_metrics().data(1);
+    ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid1,
+                                                  "wakelock2");
+    EXPECT_EQ(1, data.slice_by_state_size());
+    EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_value());
+    EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND,
+              data.slice_by_state(0).value());
+    EXPECT_EQ(1, data.bucket_info_size());
+    EXPECT_EQ(140 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+    EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+
+    data = reports.reports(0).metrics(0).duration_metrics().data(2);
+    ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid2,
+                                                  "wakelock1");
+    EXPECT_EQ(1, data.slice_by_state_size());
+    EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_value());
+    EXPECT_EQ(-1 /* StateTracker:: kStateUnknown */, data.slice_by_state(0).value());
+    EXPECT_EQ(1, data.bucket_info_size());
+    EXPECT_EQ(20 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+    EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+
+    data = reports.reports(0).metrics(0).duration_metrics().data(3);
+    ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid1,
+                                                  "wakelock1");
+    EXPECT_EQ(1, data.slice_by_state_size());
+    EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_value());
+    EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND,
+              data.slice_by_state(0).value());
+    EXPECT_EQ(2, data.bucket_info_size());
+    EXPECT_EQ(240 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+    EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+    EXPECT_EQ(20 * NS_PER_SEC, data.bucket_info(1).duration_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos());
+    EXPECT_EQ(330 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos());
+
+    data = reports.reports(0).metrics(0).duration_metrics().data(4);
+    ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid2,
+                                                  "wakelock1");
+    EXPECT_EQ(1, data.slice_by_state_size());
+    EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_value());
+    EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND,
+              data.slice_by_state(0).value());
+    EXPECT_EQ(1, data.bucket_info_size());
+    EXPECT_EQ(50 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+    EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+
+    data = reports.reports(0).metrics(0).duration_metrics().data(5);
+    ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid2,
+                                                  "wakelock2");
+    EXPECT_EQ(1, data.slice_by_state_size());
+    EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_value());
+    EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_FOREGROUND_SERVICE,
+              data.slice_by_state(0).value());
+    EXPECT_EQ(2, data.bucket_info_size());
+    EXPECT_EQ(180 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+    EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+    EXPECT_EQ(20 * NS_PER_SEC, data.bucket_info(1).duration_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(1).start_bucket_elapsed_nanos());
+    EXPECT_EQ(330 * NS_PER_SEC, data.bucket_info(1).end_bucket_elapsed_nanos());
+
+    data = reports.reports(0).metrics(0).duration_metrics().data(6);
+    ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid2,
+                                                  "wakelock2");
+    EXPECT_EQ(1, data.slice_by_state_size());
+    EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_value());
+    EXPECT_EQ(-1 /* StateTracker:: kStateUnknown */, data.slice_by_state(0).value());
+    EXPECT_EQ(1, data.bucket_info_size());
+    EXPECT_EQ(15 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+    EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+
+    data = reports.reports(0).metrics(0).duration_metrics().data(7);
+    ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid1,
+                                                  "wakelock1");
+    EXPECT_EQ(1, data.slice_by_state_size());
+    EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_value());
+    EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_FOREGROUND,
+              data.slice_by_state(0).value());
+    EXPECT_EQ(1, data.bucket_info_size());
+    EXPECT_EQ(40 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+    EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+
+    data = reports.reports(0).metrics(0).duration_metrics().data(8);
+    ValidateWakelockAttributionUidAndTagDimension(data.dimensions_in_what(), 10, appUid2,
+                                                  "wakelock2");
+    EXPECT_EQ(1, data.slice_by_state_size());
+    EXPECT_EQ(UID_PROCESS_STATE_ATOM_ID, data.slice_by_state(0).atom_id());
+    EXPECT_TRUE(data.slice_by_state(0).has_value());
+    EXPECT_EQ(android::app::ProcessStateEnum::PROCESS_STATE_IMPORTANT_BACKGROUND,
+              data.slice_by_state(0).value());
+    EXPECT_EQ(1, data.bucket_info_size());
+    EXPECT_EQ(70 * NS_PER_SEC, data.bucket_info(0).duration_nanos());
+    EXPECT_EQ(10 * NS_PER_SEC, data.bucket_info(0).start_bucket_elapsed_nanos());
+    EXPECT_EQ(310 * NS_PER_SEC, data.bucket_info(0).end_bucket_elapsed_nanos());
+}
+
 #else
 GTEST_LOG_(INFO) << "This test does nothing.\n";
 #endif
diff --git a/cmds/statsd/tests/metadata_util_test.cpp b/cmds/statsd/tests/metadata_util_test.cpp
new file mode 100644
index 0000000..7707890
--- /dev/null
+++ b/cmds/statsd/tests/metadata_util_test.cpp
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2020 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 <gtest/gtest.h>
+
+#include "metadata_util.h"
+#include "tests/statsd_test_util.h"
+
+#ifdef __ANDROID__
+
+namespace android {
+namespace os {
+namespace statsd {
+
+TEST(MetadataUtilTest, TestWriteAndReadMetricDimensionKey) {
+    HashableDimensionKey dim;
+    HashableDimensionKey dim2;
+    int pos1[] = {1, 1, 1};
+    int pos2[] = {1, 1, 2};
+    int pos3[] = {1, 1, 3};
+    int pos4[] = {2, 0, 0};
+    Field field1(10, pos1, 2);
+    Field field2(10, pos2, 2);
+    Field field3(10, pos3, 2);
+    Field field4(10, pos4, 0);
+
+    Value value1((int32_t)10025);
+    Value value2("tag");
+    Value value3((int32_t)987654);
+    Value value4((int32_t)99999);
+
+    dim.addValue(FieldValue(field1, value1));
+    dim.addValue(FieldValue(field2, value2));
+    dim.addValue(FieldValue(field3, value3));
+    dim.addValue(FieldValue(field4, value4));
+
+    dim2.addValue(FieldValue(field1, value1));
+    dim2.addValue(FieldValue(field2, value2));
+
+    MetricDimensionKey dimKey(dim, dim2);
+
+    metadata::MetricDimensionKey metadataDimKey;
+    writeMetricDimensionKeyToMetadataDimensionKey(dimKey, &metadataDimKey);
+
+    MetricDimensionKey loadedDimKey = loadMetricDimensionKeyFromProto(metadataDimKey);
+
+    ASSERT_EQ(loadedDimKey, dimKey);
+    ASSERT_EQ(std::hash<MetricDimensionKey>{}(loadedDimKey),
+            std::hash<MetricDimensionKey>{}(dimKey));
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
diff --git a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
index 100220b..d2f0f57 100644
--- a/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
+++ b/cmds/statsd/tests/metrics/MaxDurationTracker_test.cpp
@@ -62,9 +62,8 @@
     int64_t bucketNum = 0;
 
     int64_t metricId = 1;
-    MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1,
-                               false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
-                               false, false, {});
+    MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, false, bucketStartTimeNs,
+                               bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {});
 
     tracker.noteStart(key1, true, bucketStartTimeNs, ConditionKey());
     // Event starts again. This would not change anything as it already starts.
@@ -97,9 +96,8 @@
     int64_t bucketNum = 0;
 
     int64_t metricId = 1;
-    MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1,
-                               false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
-                               false, false, {});
+    MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, false, bucketStartTimeNs,
+                               bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {});
 
     tracker.noteStart(key1, true, bucketStartTimeNs + 1, ConditionKey());
 
@@ -132,9 +130,8 @@
     int64_t bucketNum = 0;
 
     int64_t metricId = 1;
-    MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1,
-                               false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
-                               false, false, {});
+    MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, false, bucketStartTimeNs,
+                               bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {});
 
     // The event starts.
     tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + 1, ConditionKey());
@@ -172,9 +169,8 @@
     int64_t bucketNum = 0;
 
     int64_t metricId = 1;
-    MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1,
-                               true, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
-                               false, false, {});
+    MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, -1, true, bucketStartTimeNs,
+                               bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {});
 
     // 2 starts
     tracker.noteStart(DEFAULT_DIMENSION_KEY, true, bucketStartTimeNs + 1, ConditionKey());
@@ -218,9 +214,8 @@
     int64_t eventStopTimeNs = conditionStops2 + 8 * NS_PER_SEC;
 
     int64_t metricId = 1;
-    MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1,
-                               false, bucketStartTimeNs, 0, bucketStartTimeNs, bucketSizeNs, true,
-                               false, {});
+    MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, bucketStartTimeNs,
+                               0, bucketStartTimeNs, bucketSizeNs, true, false, {});
     EXPECT_TRUE(tracker.mAnomalyTrackers.empty());
 
     tracker.noteStart(key1, false, eventStartTimeNs, conditionKey1);
@@ -267,9 +262,9 @@
     sp<AlarmMonitor> alarmMonitor;
     sp<DurationAnomalyTracker> anomalyTracker =
         new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor);
-    MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1,
-                               false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
-                               true, false, {anomalyTracker});
+    MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, bucketStartTimeNs,
+                               bucketNum, bucketStartTimeNs, bucketSizeNs, true, false,
+                               {anomalyTracker});
 
     tracker.noteStart(key1, true, eventStartTimeNs, conditionKey1);
     sp<const InternalAlarm> alarm = anomalyTracker->mAlarms.begin()->second;
@@ -326,9 +321,9 @@
     sp<AlarmMonitor> alarmMonitor;
     sp<DurationAnomalyTracker> anomalyTracker =
         new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor);
-    MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1,
-                               false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
-                               true, false, {anomalyTracker});
+    MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, bucketStartTimeNs,
+                               bucketNum, bucketStartTimeNs, bucketSizeNs, true, false,
+                               {anomalyTracker});
 
     tracker.noteStart(key1, false, eventStartTimeNs, conditionKey1);
     tracker.noteConditionChanged(key1, true, conditionStarts1);
@@ -408,9 +403,9 @@
     sp<AlarmMonitor> alarmMonitor;
     sp<DurationAnomalyTracker> anomalyTracker =
         new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor);
-    MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1,
-                               false, bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
-                               true, false, {anomalyTracker});
+    MaxDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false, bucketStartTimeNs,
+                               bucketNum, bucketStartTimeNs, bucketSizeNs, true, false,
+                               {anomalyTracker});
 
     tracker.noteStart(key1, true, eventStartTimeNs1, conditionKey1);
     tracker.noteStart(key2, true, eventStartTimeNs2, conditionKey2);
diff --git a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
index 1cd7bdb..39d3919 100644
--- a/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
+++ b/cmds/statsd/tests/metrics/OringDurationTracker_test.cpp
@@ -61,9 +61,9 @@
     int64_t eventStartTimeNs = bucketStartTimeNs + 1;
     int64_t durationTimeNs = 2 * 1000;
 
-    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1,
-                                 false, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
-                                 bucketSizeNs, false, false, {});
+    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false,
+                                 bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
+                                 false, false, {});
 
     tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
     EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime);
@@ -92,9 +92,8 @@
     int64_t bucketNum = 0;
     int64_t eventStartTimeNs = bucketStartTimeNs + 1;
 
-    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1,
-                                 true, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
-                                 bucketSizeNs, false, false, {});
+    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs,
+                                 bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {});
 
     tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
     tracker.noteStart(kEventKey1, true, eventStartTimeNs + 10, ConditionKey());  // overlapping wl
@@ -124,9 +123,8 @@
     int64_t bucketNum = 0;
     int64_t eventStartTimeNs = bucketStartTimeNs + 1;
 
-    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1,
-                                 true, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
-                                 bucketSizeNs, false, false, {});
+    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs,
+                                 bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {});
 
     tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
     tracker.noteStart(kEventKey2, true, eventStartTimeNs + 10, ConditionKey());  // overlapping wl
@@ -154,9 +152,8 @@
     int64_t eventStartTimeNs = bucketStartTimeNs + 1;
     int64_t durationTimeNs = 2 * 1000;
 
-    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1,
-                                 true, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
-                                 bucketSizeNs, false, false, {});
+    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs,
+                                 bucketNum, bucketStartTimeNs, bucketSizeNs, false, false, {});
 
     tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
     EXPECT_EQ((long long)eventStartTimeNs, tracker.mLastStartTime);
@@ -198,9 +195,9 @@
     int64_t eventStartTimeNs = bucketStartTimeNs + 1;
     int64_t durationTimeNs = 2 * 1000;
 
-    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1,
-                                 false, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
-                                 bucketSizeNs, true, false, {});
+    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false,
+                                 bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
+                                 true, false, {});
 
     tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
 
@@ -237,9 +234,9 @@
     int64_t eventStartTimeNs = bucketStartTimeNs + 1;
     int64_t durationTimeNs = 2 * 1000;
 
-    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1,
-                                 false, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
-                                 bucketSizeNs, true, false, {});
+    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, false,
+                                 bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
+                                 true, false, {});
 
     tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
     // condition to false; record duration 5n
@@ -275,9 +272,8 @@
     int64_t bucketNum = 0;
     int64_t eventStartTimeNs = bucketStartTimeNs + 1;
 
-    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1,
-                                 true, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
-                                 bucketSizeNs, true, false, {});
+    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs,
+                                 bucketNum, bucketStartTimeNs, bucketSizeNs, true, false, {});
 
     tracker.noteStart(kEventKey1, true, eventStartTimeNs, key1);
     tracker.noteStart(kEventKey1, true, eventStartTimeNs + 2, key1);
@@ -316,9 +312,9 @@
     sp<AlarmMonitor> alarmMonitor;
     sp<DurationAnomalyTracker> anomalyTracker =
         new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor);
-    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1,
-                                 true, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
-                                 bucketSizeNs, true, false, {anomalyTracker});
+    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true, bucketStartTimeNs,
+                                 bucketNum, bucketStartTimeNs, bucketSizeNs, true, false,
+                                 {anomalyTracker});
 
     // Nothing in the past bucket.
     tracker.noteStart(DEFAULT_DIMENSION_KEY, true, eventStartTimeNs, ConditionKey());
@@ -422,9 +418,8 @@
             sp<AlarmMonitor> alarmMonitor;
             sp<DurationAnomalyTracker> anomalyTracker =
                 new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor);
-            OringDurationTracker tracker(kConfigKey, metricId, DEFAULT_METRIC_DIMENSION_KEY,
-                                         wizard, 1,
-                                         true, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
+            OringDurationTracker tracker(kConfigKey, metricId, DEFAULT_METRIC_DIMENSION_KEY, wizard,
+                                         1, true, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
                                          bucketSizeNs, true, false, {anomalyTracker});
 
             int64_t eventStartTimeNs = bucketStartTimeNs + 9 * NS_PER_SEC;
@@ -481,15 +476,15 @@
     sp<AlarmMonitor> alarmMonitor;
     sp<DurationAnomalyTracker> anomalyTracker =
         new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor);
-    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1,
-                                 true /*nesting*/, bucketStartTimeNs, bucketNum, bucketStartTimeNs,
-                                 bucketSizeNs, false, false, {anomalyTracker});
+    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true /*nesting*/,
+                                 bucketStartTimeNs, bucketNum, bucketStartTimeNs, bucketSizeNs,
+                                 false, false, {anomalyTracker});
 
     tracker.noteStart(kEventKey1, true, eventStartTimeNs, ConditionKey());
     tracker.noteStop(kEventKey1, eventStartTimeNs + 10, false);
     EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U);
     EXPECT_TRUE(tracker.mStarted.empty());
-    EXPECT_EQ(10LL, tracker.mDuration); // 10ns
+    EXPECT_EQ(10LL, tracker.mStateKeyDurationMap[DEFAULT_DIMENSION_KEY].mDuration);  // 10ns
 
     EXPECT_EQ(0u, tracker.mStarted.size());
 
@@ -530,11 +525,11 @@
     sp<AlarmMonitor> alarmMonitor;
     sp<DurationAnomalyTracker> anomalyTracker =
         new DurationAnomalyTracker(alert, kConfigKey, alarmMonitor);
-    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1,
-                                 true /*nesting*/, bucketStartTimeNs, 0, bucketStartTimeNs,
-                                 bucketSizeNs, false, false, {anomalyTracker});
+    OringDurationTracker tracker(kConfigKey, metricId, eventKey, wizard, 1, true /*nesting*/,
+                                 bucketStartTimeNs, 0, bucketStartTimeNs, bucketSizeNs, false,
+                                 false, {anomalyTracker});
 
-    tracker.noteStart(kEventKey1, true, 15 * NS_PER_SEC, conkey); // start key1
+    tracker.noteStart(kEventKey1, true, 15 * NS_PER_SEC, conkey);  // start key1
     EXPECT_EQ(1u, anomalyTracker->mAlarms.size());
     sp<const InternalAlarm> alarm = anomalyTracker->mAlarms.begin()->second;
     EXPECT_EQ((long long)(55ULL * NS_PER_SEC), (long long)(alarm->timestampSec * NS_PER_SEC));
@@ -544,13 +539,13 @@
     EXPECT_EQ(0u, anomalyTracker->mAlarms.size());
     EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U);
 
-    tracker.noteStart(kEventKey1, true, 22 * NS_PER_SEC, conkey); // start key1 again
+    tracker.noteStart(kEventKey1, true, 22 * NS_PER_SEC, conkey);  // start key1 again
     EXPECT_EQ(1u, anomalyTracker->mAlarms.size());
     alarm = anomalyTracker->mAlarms.begin()->second;
     EXPECT_EQ((long long)(60ULL * NS_PER_SEC), (long long)(alarm->timestampSec * NS_PER_SEC));
     EXPECT_EQ(anomalyTracker->getRefractoryPeriodEndsSec(eventKey), 0U);
 
-    tracker.noteStart(kEventKey2, true, 32 * NS_PER_SEC, conkey); // start key2
+    tracker.noteStart(kEventKey2, true, 32 * NS_PER_SEC, conkey);  // start key2
     EXPECT_EQ(1u, anomalyTracker->mAlarms.size());
     alarm = anomalyTracker->mAlarms.begin()->second;
     EXPECT_EQ((long long)(60ULL * NS_PER_SEC), (long long)(alarm->timestampSec * NS_PER_SEC));
diff --git a/cmds/statsd/tests/state/StateTracker_test.cpp b/cmds/statsd/tests/state/StateTracker_test.cpp
index a0e0095..a5b8e1c 100644
--- a/cmds/statsd/tests/state/StateTracker_test.cpp
+++ b/cmds/statsd/tests/state/StateTracker_test.cpp
@@ -105,63 +105,6 @@
 }
 // END: build event functions.
 
-// START: get primary key functions
-void getUidProcessKey(int uid, HashableDimensionKey* key) {
-    int pos1[] = {1, 0, 0};
-    Field field1(27 /* atom id */, pos1, 0 /* depth */);
-    Value value1((int32_t)uid);
-
-    key->addValue(FieldValue(field1, value1));
-}
-
-void getOverlayKey(int uid, string packageName, HashableDimensionKey* key) {
-    int pos1[] = {1, 0, 0};
-    int pos2[] = {2, 0, 0};
-
-    Field field1(59 /* atom id */, pos1, 0 /* depth */);
-    Field field2(59 /* atom id */, pos2, 0 /* depth */);
-
-    Value value1((int32_t)uid);
-    Value value2(packageName);
-
-    key->addValue(FieldValue(field1, value1));
-    key->addValue(FieldValue(field2, value2));
-}
-
-void getPartialWakelockKey(int uid, const std::string& tag, HashableDimensionKey* key) {
-    int pos1[] = {1, 1, 1};
-    int pos3[] = {2, 0, 0};
-    int pos4[] = {3, 0, 0};
-
-    Field field1(10 /* atom id */, pos1, 2 /* depth */);
-
-    Field field3(10 /* atom id */, pos3, 0 /* depth */);
-    Field field4(10 /* atom id */, pos4, 0 /* depth */);
-
-    Value value1((int32_t)uid);
-    Value value3((int32_t)1 /*partial*/);
-    Value value4(tag);
-
-    key->addValue(FieldValue(field1, value1));
-    key->addValue(FieldValue(field3, value3));
-    key->addValue(FieldValue(field4, value4));
-}
-
-void getPartialWakelockKey(int uid, HashableDimensionKey* key) {
-    int pos1[] = {1, 1, 1};
-    int pos3[] = {2, 0, 0};
-
-    Field field1(10 /* atom id */, pos1, 2 /* depth */);
-    Field field3(10 /* atom id */, pos3, 0 /* depth */);
-
-    Value value1((int32_t)uid);
-    Value value3((int32_t)1 /*partial*/);
-
-    key->addValue(FieldValue(field1, value1));
-    key->addValue(FieldValue(field3, value3));
-}
-// END: get primary key functions
-
 TEST(StateListenerTest, TestStateListenerWeakPointer) {
     sp<TestStateListener> listener = new TestStateListener();
     wp<TestStateListener> wListener = listener;
diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp
index 8c8836b..2f81c2d 100644
--- a/cmds/statsd/tests/statsd_test_util.cpp
+++ b/cmds/statsd/tests/statsd_test_util.cpp
@@ -135,6 +135,27 @@
         "BatterySaverModeStop", BatterySaverModeStateChanged::OFF);
 }
 
+AtomMatcher CreateBatteryStateChangedAtomMatcher(const string& name,
+                                                 BatteryPluggedStateEnum state) {
+    AtomMatcher atom_matcher;
+    atom_matcher.set_id(StringToId(name));
+    auto simple_atom_matcher = atom_matcher.mutable_simple_atom_matcher();
+    simple_atom_matcher->set_atom_id(util::PLUGGED_STATE_CHANGED);
+    auto field_value_matcher = simple_atom_matcher->add_field_value_matcher();
+    field_value_matcher->set_field(1);  // State field.
+    field_value_matcher->set_eq_int(state);
+    return atom_matcher;
+}
+
+AtomMatcher CreateBatteryStateNoneMatcher() {
+    return CreateBatteryStateChangedAtomMatcher("BatteryPluggedNone",
+                                                BatteryPluggedStateEnum::BATTERY_PLUGGED_NONE);
+}
+
+AtomMatcher CreateBatteryStateUsbMatcher() {
+    return CreateBatteryStateChangedAtomMatcher("BatteryPluggedUsb",
+                                                BatteryPluggedStateEnum::BATTERY_PLUGGED_USB);
+}
 
 AtomMatcher CreateScreenStateChangedAtomMatcher(
     const string& name, android::view::DisplayStateEnum state) {
@@ -234,6 +255,14 @@
     return predicate;
 }
 
+Predicate CreateDeviceUnpluggedPredicate() {
+    Predicate predicate;
+    predicate.set_id(StringToId("DeviceUnplugged"));
+    predicate.mutable_simple_predicate()->set_start(StringToId("BatteryPluggedNone"));
+    predicate.mutable_simple_predicate()->set_stop(StringToId("BatteryPluggedUsb"));
+    return predicate;
+}
+
 Predicate CreateScreenIsOnPredicate() {
     Predicate predicate;
     predicate.set_id(StringToId("ScreenIsOn"));
@@ -410,6 +439,74 @@
     return dimensions;
 }
 
+FieldMatcher CreateAttributionUidAndOtherDimensions(const int atomId,
+                                                    const std::vector<Position>& positions,
+                                                    const std::vector<int>& fields) {
+    FieldMatcher dimensions = CreateAttributionUidDimensions(atomId, positions);
+
+    for (const int field : fields) {
+        dimensions.add_child()->set_field(field);
+    }
+    return dimensions;
+}
+
+// START: get primary key functions
+void getUidProcessKey(int uid, HashableDimensionKey* key) {
+    int pos1[] = {1, 0, 0};
+    Field field1(27 /* atom id */, pos1, 0 /* depth */);
+    Value value1((int32_t)uid);
+
+    key->addValue(FieldValue(field1, value1));
+}
+
+void getOverlayKey(int uid, string packageName, HashableDimensionKey* key) {
+    int pos1[] = {1, 0, 0};
+    int pos2[] = {2, 0, 0};
+
+    Field field1(59 /* atom id */, pos1, 0 /* depth */);
+    Field field2(59 /* atom id */, pos2, 0 /* depth */);
+
+    Value value1((int32_t)uid);
+    Value value2(packageName);
+
+    key->addValue(FieldValue(field1, value1));
+    key->addValue(FieldValue(field2, value2));
+}
+
+void getPartialWakelockKey(int uid, const std::string& tag, HashableDimensionKey* key) {
+    int pos1[] = {1, 1, 1};
+    int pos3[] = {2, 0, 0};
+    int pos4[] = {3, 0, 0};
+
+    Field field1(10 /* atom id */, pos1, 2 /* depth */);
+
+    Field field3(10 /* atom id */, pos3, 0 /* depth */);
+    Field field4(10 /* atom id */, pos4, 0 /* depth */);
+
+    Value value1((int32_t)uid);
+    Value value3((int32_t)1 /*partial*/);
+    Value value4(tag);
+
+    key->addValue(FieldValue(field1, value1));
+    key->addValue(FieldValue(field3, value3));
+    key->addValue(FieldValue(field4, value4));
+}
+
+void getPartialWakelockKey(int uid, HashableDimensionKey* key) {
+    int pos1[] = {1, 1, 1};
+    int pos3[] = {2, 0, 0};
+
+    Field field1(10 /* atom id */, pos1, 2 /* depth */);
+    Field field3(10 /* atom id */, pos3, 0 /* depth */);
+
+    Value value1((int32_t)uid);
+    Value value3((int32_t)1 /*partial*/);
+
+    key->addValue(FieldValue(field1, value1));
+    key->addValue(FieldValue(field3, value3));
+}
+// END: get primary key functions
+
 shared_ptr<LogEvent> CreateTwoValueLogEvent(int atomId, int64_t eventTimeNs, int32_t value1,
                                             int32_t value2) {
     AStatsEvent* statsEvent = AStatsEvent_obtain();
@@ -595,6 +692,23 @@
     return logEvent;
 }
 
+std::unique_ptr<LogEvent> CreateBatteryStateChangedEvent(const uint64_t timestampNs, const BatteryPluggedStateEnum state) {
+    AStatsEvent* statsEvent = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(statsEvent, util::PLUGGED_STATE_CHANGED);
+    AStatsEvent_overwriteTimestamp(statsEvent, timestampNs);
+
+    AStatsEvent_writeInt32(statsEvent, state);
+    AStatsEvent_build(statsEvent);
+
+    size_t size;
+    uint8_t* buf = AStatsEvent_getBuffer(statsEvent, &size);
+
+    std::unique_ptr<LogEvent> logEvent = std::make_unique<LogEvent>(/*uid=*/0, /*pid=*/0);
+    logEvent->parseBuffer(buf, size);
+    AStatsEvent_release(statsEvent);
+    return logEvent;
+}
+
 std::unique_ptr<LogEvent> CreateScreenBrightnessChangedEvent(uint64_t timestampNs, int level) {
     AStatsEvent* statsEvent = AStatsEvent_obtain();
     AStatsEvent_setAtomId(statsEvent, util::SCREEN_BRIGHTNESS_CHANGED);
@@ -964,6 +1078,22 @@
     return static_cast<int64_t>(std::hash<std::string>()(str));
 }
 
+void ValidateWakelockAttributionUidAndTagDimension(const DimensionsValue& value, const int atomId,
+                                                   const int uid, const string& tag) {
+    EXPECT_EQ(value.field(), atomId);
+    EXPECT_EQ(value.value_tuple().dimensions_value_size(), 2);
+    // Attribution field.
+    EXPECT_EQ(value.value_tuple().dimensions_value(0).field(), 1);
+    // Uid field.
+    EXPECT_EQ(value.value_tuple().dimensions_value(0).value_tuple().dimensions_value_size(), 1);
+    EXPECT_EQ(value.value_tuple().dimensions_value(0).value_tuple().dimensions_value(0).field(), 1);
+    EXPECT_EQ(value.value_tuple().dimensions_value(0).value_tuple().dimensions_value(0).value_int(),
+              uid);
+    // Tag field.
+    EXPECT_EQ(value.value_tuple().dimensions_value(1).field(), 3);
+    EXPECT_EQ(value.value_tuple().dimensions_value(1).value_str(), tag);
+}
+
 void ValidateAttributionUidDimension(const DimensionsValue& value, int atomId, int uid) {
     EXPECT_EQ(value.field(), atomId);
     EXPECT_EQ(value.value_tuple().dimensions_value_size(), 1);
diff --git a/cmds/statsd/tests/statsd_test_util.h b/cmds/statsd/tests/statsd_test_util.h
index 7c01755..715ba2b 100644
--- a/cmds/statsd/tests/statsd_test_util.h
+++ b/cmds/statsd/tests/statsd_test_util.h
@@ -68,6 +68,12 @@
 // Create AtomMatcher proto for stopping battery save mode.
 AtomMatcher CreateBatterySaverModeStopAtomMatcher();
 
+// Create AtomMatcher proto for battery state none mode.
+AtomMatcher CreateBatteryStateNoneMatcher();
+
+// Create AtomMatcher proto for battery state usb mode.
+AtomMatcher CreateBatteryStateUsbMatcher();
+
 // Create AtomMatcher proto for process state changed.
 AtomMatcher CreateUidProcessStateChangedAtomMatcher();
 
@@ -110,6 +116,9 @@
 // Create Predicate proto for battery saver mode.
 Predicate CreateBatterySaverModePredicate();
 
+// Create Predicate proto for device unplogged mode.
+Predicate CreateDeviceUnpluggedPredicate();
+
 // Create Predicate proto for holding wakelock.
 Predicate CreateHoldingWakelockPredicate();
 
@@ -164,6 +173,22 @@
 FieldMatcher CreateAttributionUidDimensions(const int atomId,
                                             const std::vector<Position>& positions);
 
+FieldMatcher CreateAttributionUidAndOtherDimensions(const int atomId,
+                                                    const std::vector<Position>& positions,
+                                                    const std::vector<int>& fields);
+
+// START: get primary key functions
+// These functions take in atom field information and create FieldValues which are stored in the
+// given HashableDimensionKey.
+void getUidProcessKey(int uid, HashableDimensionKey* key);
+
+void getOverlayKey(int uid, string packageName, HashableDimensionKey* key);
+
+void getPartialWakelockKey(int uid, const std::string& tag, HashableDimensionKey* key);
+
+void getPartialWakelockKey(int uid, HashableDimensionKey* key);
+// END: get primary key functions
+
 shared_ptr<LogEvent> CreateTwoValueLogEvent(int atomId, int64_t eventTimeNs, int32_t value1,
                                             int32_t value2);
 
@@ -213,6 +238,9 @@
 // Create log event when battery saver stops.
 std::unique_ptr<LogEvent> CreateBatterySaverOffEvent(uint64_t timestampNs);
 
+// Create log event when battery state changes.
+std::unique_ptr<LogEvent> CreateBatteryStateChangedEvent(const uint64_t timestampNs, const BatteryPluggedStateEnum state);
+
 // Create log event for app moving to background.
 std::unique_ptr<LogEvent> CreateMoveToBackgroundEvent(uint64_t timestampNs, const int uid);
 
@@ -277,6 +305,8 @@
 
 int64_t StringToId(const string& str);
 
+void ValidateWakelockAttributionUidAndTagDimension(const DimensionsValue& value, const int atomId,
+                                                   const int uid, const string& tag);
 void ValidateUidDimension(const DimensionsValue& value, int node_idx, int atomId, int uid);
 void ValidateAttributionUidDimension(const DimensionsValue& value, int atomId, int uid);
 void ValidateAttributionUidAndTagDimension(
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 556f841..b6d519a 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -73,8 +73,8 @@
 import android.util.DisplayMetrics;
 import android.util.Singleton;
 import android.util.Size;
-import android.window.IWindowContainer;
 import android.view.Surface;
+import android.window.WindowContainerToken;
 
 import com.android.internal.app.LocalePicker;
 import com.android.internal.app.procstats.ProcessStats;
@@ -2759,7 +2759,7 @@
         // Index of the stack in the display's stack list, can be used for comparison of stack order
         @UnsupportedAppUsage
         public int position;
-        public IWindowContainer stackToken;
+        public WindowContainerToken stackToken;
         /**
          * The full configuration the stack is currently running in.
          * @hide
@@ -2793,7 +2793,7 @@
             dest.writeInt(userId);
             dest.writeInt(visible ? 1 : 0);
             dest.writeInt(position);
-            dest.writeStrongInterface(stackToken);
+            stackToken.writeToParcel(dest, 0);
             if (topActivity != null) {
                 dest.writeInt(1);
                 topActivity.writeToParcel(dest, 0);
@@ -2825,7 +2825,7 @@
             userId = source.readInt();
             visible = source.readInt() > 0;
             position = source.readInt();
-            stackToken = IWindowContainer.Stub.asInterface(source.readStrongBinder());
+            stackToken = WindowContainerToken.CREATOR.createFromParcel(source);
             if (source.readInt() > 0) {
                 topActivity = ComponentName.readFromParcel(source);
             }
@@ -3632,7 +3632,8 @@
      * Set custom state data for this process. It will be included in the record of
      * {@link ApplicationExitInfo} on the death of the current calling process; the new process
      * of the app can retrieve this state data by calling
-     * {@link ApplicationExitInfo#getProcessStateSummary} on the record returned by
+     * {@link android.app.ApplicationExitInfo#getProcessStateSummary()
+     * ApplicationExitInfo.getProcessStateSummary()} on the record returned by
      * {@link #getHistoricalProcessExitReasons}.
      *
      * <p> This would be useful for the calling app to save its stateful data: if it's
@@ -3657,7 +3658,7 @@
         }
     }
 
-    /*
+    /**
      * @return Whether or not the low memory kill will be reported in
      * {@link #getHistoricalProcessExitReasons}.
      *
diff --git a/core/java/android/app/ActivityView.java b/core/java/android/app/ActivityView.java
index 073b8d0..ab7925c 100644
--- a/core/java/android/app/ActivityView.java
+++ b/core/java/android/app/ActivityView.java
@@ -42,6 +42,9 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewParent;
+import android.window.TaskEmbedder;
+import android.window.TaskOrganizerTaskEmbedder;
+import android.window.VirtualDisplayTaskEmbedder;
 
 import dalvik.system.CloseGuard;
 
@@ -52,11 +55,11 @@
  * @hide
  */
 @TestApi
-public class ActivityView extends ViewGroup implements TaskEmbedder.Host {
+public class ActivityView extends ViewGroup implements android.window.TaskEmbedder.Host {
 
     private static final String TAG = "ActivityView";
 
-    private TaskEmbedder mTaskEmbedder;
+    private android.window.TaskEmbedder mTaskEmbedder;
 
     private final SurfaceView mSurfaceView;
     private final SurfaceCallback mSurfaceCallback;
@@ -487,7 +490,7 @@
 
     /** @hide */
     @Override
-    public void onTaskBackgroundColorChanged(TaskEmbedder ts, int bgColor) {
+    public void onTaskBackgroundColorChanged(android.window.TaskEmbedder ts, int bgColor) {
         if (mSurfaceView != null) {
             mSurfaceView.setResizeBackgroundColor(bgColor);
         }
diff --git a/core/java/android/app/ApplicationExitInfo.java b/core/java/android/app/ApplicationExitInfo.java
index 0ecc003..cfe0aff 100644
--- a/core/java/android/app/ApplicationExitInfo.java
+++ b/core/java/android/app/ApplicationExitInfo.java
@@ -90,7 +90,8 @@
      * {@link #REASON_SIGNALED} and {@link #getStatus} will return
      * the value {@link android.system.OsConstants#SIGKILL}.
      *
-     * Application should use {@link ActivityManager#isLowMemoryKillReportSupported} to check
+     * Application should use {@link android.app.ActivityManager#isLowMemoryKillReportSupported()
+     * ActivityManager.isLowMemoryKillReportSupported()} to check
      * if the device supports reporting {@link #REASON_LOW_MEMORY} or not.
      * </p>
      */
@@ -523,7 +524,7 @@
         return mReason;
     }
 
-    /*
+    /**
      * The exit status argument of exit() if the application calls it, or the signal
      * number if the application is signaled.
      */
@@ -538,7 +539,7 @@
         return mImportance;
     }
 
-    /*
+    /**
      * Last proportional set size of the memory that the process had used in kB.
      *
      * <p class="note">Note: This is the value from last sampling on the process,
@@ -562,7 +563,7 @@
 
     /**
      * The timestamp of the process's death, in milliseconds since the epoch,
-     * as returned by {@link System#currentTimeMillis System.currentTimeMillis()}.
+     * as returned by {@link java.lang.System#currentTimeMillis() System.currentTimeMillis()}.
      */
     public @CurrentTimeMillisLong long getTimestamp() {
         return mTimestamp;
@@ -586,8 +587,9 @@
     }
 
     /**
-     * Return the state data set by calling {@link ActivityManager#setProcessStateSummary}
-     * from the process before its death.
+     * Return the state data set by calling
+     * {@link android.app.ActivityManager#setProcessStateSummary(byte[])
+     * ActivityManager.setProcessStateSummary(byte[])} from the process before its death.
      *
      * @return The process-customized data
      * @see ActivityManager#setProcessStateSummary(byte[])
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index a1ec27b..f883b60 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -1610,7 +1610,10 @@
 
     @Override
     public Drawable getUserBadgeForDensityNoBackground(UserHandle user, int density) {
-        Drawable badge = getProfileIconForDensity(user,
+        if (!hasUserBadge(user.getIdentifier())) {
+            return null;
+        }
+        Drawable badge = getDrawableForDensity(
                 getUserManager().getUserBadgeNoBackgroundResId(user.getIdentifier()), density);
         if (badge != null) {
             badge.setTint(getUserBadgeColor(user));
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 9b42fa2..f461a17 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -1232,6 +1232,10 @@
     /** @hide */
     public static final String EXTRA_CONVERSATION_ICON = "android.conversationIcon";
 
+    /** @hide */
+    public static final String EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT =
+            "android.conversationUnreadMessageCount";
+
     /**
      * {@link #extras} key: an array of {@link android.app.Notification.MessagingStyle.Message}
      * bundles provided by a
@@ -7102,6 +7106,7 @@
         List<Message> mHistoricMessages = new ArrayList<>();
         boolean mIsGroupConversation;
         @ConversationType int mConversationType = CONVERSATION_TYPE_LEGACY;
+        int mUnreadMessageCount;
 
         MessagingStyle() {
         }
@@ -7247,6 +7252,17 @@
             return mConversationType;
         }
 
+        /** @hide */
+        public int getUnreadMessageCount() {
+            return mUnreadMessageCount;
+        }
+
+        /** @hide */
+        public MessagingStyle setUnreadMessageCount(int unreadMessageCount) {
+            mUnreadMessageCount = unreadMessageCount;
+            return this;
+        }
+
         /**
          * Adds a message for display by this notification. Convenience call for a simple
          * {@link Message} in {@link #addMessage(Notification.MessagingStyle.Message)}.
@@ -7401,6 +7417,7 @@
             if (mShortcutIcon != null) {
                 extras.putParcelable(EXTRA_CONVERSATION_ICON, mShortcutIcon);
             }
+            extras.putInt(EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT, mUnreadMessageCount);
 
             fixTitleAndTextExtras(extras);
             extras.putBoolean(EXTRA_IS_GROUP_CONVERSATION, mIsGroupConversation);
@@ -7452,6 +7469,7 @@
             Parcelable[] histMessages = extras.getParcelableArray(EXTRA_HISTORIC_MESSAGES);
             mHistoricMessages = Message.getMessagesFromBundleArray(histMessages);
             mIsGroupConversation = extras.getBoolean(EXTRA_IS_GROUP_CONVERSATION);
+            mUnreadMessageCount = extras.getInt(EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT);
         }
 
         /**
@@ -7601,6 +7619,7 @@
                             : mBuilder.getMessagingLayoutResource(),
                     p,
                     bindResult);
+
             addExtras(mBuilder.mN.extras);
             if (!isConversationLayout) {
                 // also update the end margin if there is an image
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 054e5e0..91a8572 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -186,6 +186,7 @@
 import android.telephony.TelephonyRegistryManager;
 import android.util.ArrayMap;
 import android.util.Log;
+import android.util.Slog;
 import android.view.ContextThemeWrapper;
 import android.view.LayoutInflater;
 import android.view.WindowManager;
@@ -222,6 +223,9 @@
 public final class SystemServiceRegistry {
     private static final String TAG = "SystemServiceRegistry";
 
+    /** @hide */
+    public static boolean sEnableServiceNotFoundWtf = false;
+
     // Service registry information.
     // This information is never changed once static initialization has completed.
     private static final Map<Class<?>, String> SYSTEM_SERVICE_NAMES =
@@ -1364,8 +1368,30 @@
      * @hide
      */
     public static Object getSystemService(ContextImpl ctx, String name) {
-        ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
-        return fetcher != null ? fetcher.getService(ctx) : null;
+        if (name == null) {
+            return null;
+        }
+        final ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
+        if (fetcher == null) {
+            if (sEnableServiceNotFoundWtf) {
+                Slog.wtf(TAG, "Unknown manager requested: " + name);
+            }
+            return null;
+        }
+
+        final Object ret = fetcher.getService(ctx);
+        if (sEnableServiceNotFoundWtf && ret == null) {
+            // Some services do return null in certain situations, so don't do WTF for them.
+            switch (name) {
+                case Context.CONTENT_CAPTURE_MANAGER_SERVICE:
+                case Context.APP_PREDICTION_SERVICE:
+                case Context.INCREMENTAL_SERVICE:
+                    return null;
+            }
+            Slog.wtf(TAG, "Manager wrapper not available: " + name);
+            return null;
+        }
+        return ret;
     }
 
     /**
@@ -1373,7 +1399,15 @@
      * @hide
      */
     public static String getSystemServiceName(Class<?> serviceClass) {
-        return SYSTEM_SERVICE_NAMES.get(serviceClass);
+        if (serviceClass == null) {
+            return null;
+        }
+        final String serviceName = SYSTEM_SERVICE_NAMES.get(serviceClass);
+        if (sEnableServiceNotFoundWtf && serviceName == null) {
+            // This should be a caller bug.
+            Slog.wtf(TAG, "Unknown manager requested: " + serviceClass.getCanonicalName());
+        }
+        return serviceName;
     }
 
     /**
@@ -1683,7 +1717,9 @@
                         try {
                             cache.wait();
                         } catch (InterruptedException e) {
-                            Log.w(TAG, "getService() interrupted");
+                            // This shouldn't normally happen, but if someone interrupts the
+                            // thread, it will.
+                            Slog.wtf(TAG, "getService() interrupted");
                             Thread.currentThread().interrupt();
                             return null;
                         }
diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING
index 7b45b72..ab86860 100644
--- a/core/java/android/app/TEST_MAPPING
+++ b/core/java/android/app/TEST_MAPPING
@@ -44,7 +44,7 @@
             "name": "CtsWindowManagerDeviceTestCases",
             "options": [
                 {
-                    "include-filter": "android.server.wm.ToastTest"
+                    "include-filter": "android.server.wm.ToastWindowTest"
                 }
             ],
             "file_patterns": ["INotificationManager\\.aidl"]
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index 7c0fc42..0173731 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -28,7 +28,7 @@
 import android.os.Parcel;
 import android.os.RemoteException;
 import android.util.Log;
-import android.window.IWindowContainer;
+import android.window.WindowContainerToken;
 
 /**
  * Stores information about a particular Task.
@@ -147,7 +147,7 @@
      * @hide
      */
     @NonNull
-    public IWindowContainer token;
+    public WindowContainerToken token;
 
     /**
      * The PictureInPictureParams for the Task, if set.
@@ -222,7 +222,7 @@
         supportsSplitScreenMultiWindow = source.readBoolean();
         resizeMode = source.readInt();
         configuration.readFromParcel(source);
-        token = IWindowContainer.Stub.asInterface(source.readStrongBinder());
+        token = WindowContainerToken.CREATOR.createFromParcel(source);
         topActivityType = source.readInt();
         pictureInPictureParams = source.readInt() != 0
                 ? PictureInPictureParams.CREATOR.createFromParcel(source)
@@ -265,7 +265,7 @@
         dest.writeBoolean(supportsSplitScreenMultiWindow);
         dest.writeInt(resizeMode);
         configuration.writeToParcel(dest, flags);
-        dest.writeStrongInterface(token);
+        token.writeToParcel(dest, flags);
         dest.writeInt(topActivityType);
         if (pictureInPictureParams == null) {
             dest.writeInt(0);
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 5bad055..8bebaff 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -306,6 +306,35 @@
     void setHomeActivity(in ComponentName className, int userId);
 
     /**
+     * Overrides the label and icon of the component specified by the component name. The component
+     * must belong to the calling app.
+     *
+     * These changes will be reset on the next boot and whenever the package is updated.
+     *
+     * Only the app defined as com.android.internal.R.config_overrideComponentUiPackage is allowed
+     * to call this.
+     *
+     * @param componentName The component name to override the label/icon of.
+     * @param nonLocalizedLabel The label to be displayed.
+     * @param icon The icon to be displayed.
+     * @param userId The user id.
+     */
+    void overrideLabelAndIcon(in ComponentName componentName, String nonLocalizedLabel,
+            int icon, int userId);
+
+    /**
+     * Restores the label and icon of the activity specified by the component name if either has
+     * been overridden. The component must belong to the calling app.
+     *
+     * Only the app defined as com.android.internal.R.config_overrideComponentUiPackage is allowed
+     * to call this.
+     *
+     * @param componentName The component name.
+     * @param userId The user id.
+     */
+    void restoreLabelAndIcon(in ComponentName componentName, int userId);
+
+    /**
      * As per {@link android.content.pm.PackageManager#setComponentEnabledSetting}.
      */
     @UnsupportedAppUsage
diff --git a/core/java/android/content/pm/PackageUserState.java b/core/java/android/content/pm/PackageUserState.java
index 61b1553..327d1b8 100644
--- a/core/java/android/content/pm/PackageUserState.java
+++ b/core/java/android/content/pm/PackageUserState.java
@@ -27,18 +27,24 @@
 import static android.content.pm.PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS;
 import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ComponentName;
 import android.content.pm.parsing.component.ParsedMainComponent;
 import android.os.BaseBundle;
 import android.os.Debug;
 import android.os.PersistableBundle;
+import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.DebugUtils;
+import android.util.Pair;
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.CollectionUtils;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -84,6 +90,9 @@
     private ArrayMap<String, String[]> sharedLibraryOverlayPaths; // Lib name to overlay paths
     private String[] cachedOverlayPaths;
 
+    @Nullable
+    private ArrayMap<ComponentName, Pair<String, Integer>> componentLabelIconOverrideMap;
+
     @UnsupportedAppUsage
     public PackageUserState() {
         installed = true;
@@ -123,6 +132,9 @@
             sharedLibraryOverlayPaths = new ArrayMap<>(o.sharedLibraryOverlayPaths);
         }
         harmfulAppWarning = o.harmfulAppWarning;
+        if (o.componentLabelIconOverrideMap != null) {
+            this.componentLabelIconOverrideMap = new ArrayMap<>(o.componentLabelIconOverrideMap);
+        }
     }
 
     public String[] getOverlayPaths() {
@@ -147,6 +159,65 @@
     }
 
     /**
+     * Overrides the non-localized label and icon of a component.
+     *
+     * @return true if the label or icon was changed.
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public boolean overrideLabelAndIcon(@NonNull ComponentName component,
+            @Nullable String nonLocalizedLabel, @Nullable Integer icon) {
+        String existingLabel = null;
+        Integer existingIcon = null;
+
+        if (componentLabelIconOverrideMap != null) {
+            Pair<String, Integer> pair = componentLabelIconOverrideMap.get(component);
+            if (pair != null) {
+                existingLabel = pair.first;
+                existingIcon = pair.second;
+            }
+        }
+
+        boolean changed = !TextUtils.equals(existingLabel, nonLocalizedLabel)
+                || !Objects.equals(existingIcon, icon);
+
+        if (changed) {
+            if (nonLocalizedLabel == null && icon == null) {
+                componentLabelIconOverrideMap.remove(component);
+                if (componentLabelIconOverrideMap.isEmpty()) {
+                    componentLabelIconOverrideMap = null;
+                }
+            } else {
+                if (componentLabelIconOverrideMap == null) {
+                    componentLabelIconOverrideMap = new ArrayMap<>(1);
+                }
+
+                componentLabelIconOverrideMap.put(component, Pair.create(nonLocalizedLabel, icon));
+            }
+        }
+
+        return changed;
+    }
+
+    /**
+     * Clears all values previously set by {@link #overrideLabelAndIcon(ComponentName,
+     * String, Integer)}.
+     *
+     * This is done when the package is updated as the components and resource IDs may have changed.
+     */
+    public void resetOverrideComponentLabelIcon() {
+        componentLabelIconOverrideMap = null;
+    }
+
+    @Nullable
+    public Pair<String, Integer> getOverrideLabelIconForComponent(ComponentName componentName) {
+        if (ArrayUtils.isEmpty(componentLabelIconOverrideMap)) {
+            return null;
+        }
+
+        return componentLabelIconOverrideMap.get(componentName);
+    }
+
+    /**
      * Test if this package is installed.
      */
     public boolean isAvailable(int flags) {
diff --git a/core/java/android/content/pm/ShortcutServiceInternal.java b/core/java/android/content/pm/ShortcutServiceInternal.java
index 435c70a..eee91ce 100644
--- a/core/java/android/content/pm/ShortcutServiceInternal.java
+++ b/core/java/android/content/pm/ShortcutServiceInternal.java
@@ -109,4 +109,8 @@
      */
     public abstract String getShortcutIconUri(int launcherUserId, @NonNull String launcherPackage,
             @NonNull String packageName, @NonNull String shortcutId, int userId);
+
+    public abstract boolean isSharingShortcut(int callingUserId, @NonNull String callingPackage,
+            @NonNull String packageName, @NonNull String shortcutId, int userId,
+            @NonNull IntentFilter filter);
 }
diff --git a/core/java/android/content/pm/parsing/ParsingPackageImpl.java b/core/java/android/content/pm/parsing/ParsingPackageImpl.java
index 894ad55..be1817d 100644
--- a/core/java/android/content/pm/parsing/ParsingPackageImpl.java
+++ b/core/java/android/content/pm/parsing/ParsingPackageImpl.java
@@ -322,7 +322,12 @@
     private String className;
     private int compatibleWidthLimitDp;
     private int descriptionRes;
-    private boolean enabled;
+
+    // Usually there's code to set this to true during parsing, but it's possible to install an APK
+    // targeting <R that doesn't contain an <application> tag. That code would be skipped and never
+    // assign this, so initialize this to true for those cases.
+    private boolean enabled = true;
+
     private boolean crossProfile;
     private int fullBackupContent;
     private int iconRes;
diff --git a/core/java/android/database/DatabaseUtils.java b/core/java/android/database/DatabaseUtils.java
index 34cc856..9b809b8 100644
--- a/core/java/android/database/DatabaseUtils.java
+++ b/core/java/android/database/DatabaseUtils.java
@@ -43,6 +43,7 @@
 import java.io.FileNotFoundException;
 import java.io.PrintStream;
 import java.text.Collator;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Locale;
 import java.util.Map;
@@ -308,6 +309,34 @@
     }
 
     /**
+     * Make a deep copy of the given argument list, ensuring that the returned
+     * value is completely isolated from any changes to the original arguments.
+     *
+     * @hide
+     */
+    public static @Nullable Object[] deepCopyOf(@Nullable Object[] args) {
+        if (args == null) return null;
+
+        final Object[] res = new Object[args.length];
+        for (int i = 0; i < args.length; i++) {
+            final Object arg = args[i];
+
+            if ((arg == null) || (arg instanceof Number) || (arg instanceof String)) {
+                // When the argument is immutable, we can copy by reference
+                res[i] = arg;
+            } else if (arg instanceof byte[]) {
+                // Need to deep copy blobs
+                final byte[] castArg = (byte[]) arg;
+                res[i] = Arrays.copyOf(castArg, castArg.length);
+            } else {
+                // Convert everything else to string, making it immutable
+                res[i] = String.valueOf(arg);
+            }
+        }
+        return res;
+    }
+
+    /**
      * Returns data type of the given object's value.
      *<p>
      * Returned values are
diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java
index 24ac152..7c4692c 100644
--- a/core/java/android/database/sqlite/SQLiteDatabase.java
+++ b/core/java/android/database/sqlite/SQLiteDatabase.java
@@ -1066,6 +1066,10 @@
             throws SQLException {
         Objects.requireNonNull(sql);
 
+        // Copy arguments to ensure that the caller doesn't accidentally change
+        // the values used by future connections
+        bindArgs = DatabaseUtils.deepCopyOf(bindArgs);
+
         synchronized (mLock) {
             throwIfNotOpenLocked();
 
diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java
index b52b437..a298c85 100644
--- a/core/java/android/inputmethodservice/IInputMethodWrapper.java
+++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java
@@ -18,6 +18,7 @@
 
 import android.annotation.BinderThread;
 import android.annotation.MainThread;
+import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.pm.PackageManager;
@@ -37,6 +38,7 @@
 import android.view.inputmethod.InputMethodSubtype;
 
 import com.android.internal.inputmethod.IInputMethodPrivilegedOperations;
+import com.android.internal.inputmethod.CancellationGroup;
 import com.android.internal.os.HandlerCaller;
 import com.android.internal.os.SomeArgs;
 import com.android.internal.view.IInlineSuggestionsRequestCallback;
@@ -52,7 +54,6 @@
 import java.lang.ref.WeakReference;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * Implements the internal IInputMethod interface to convert incoming calls
@@ -90,12 +91,13 @@
      *
      * <p>This field must be set and cleared only from the binder thread(s), where the system
      * guarantees that {@link #bindInput(InputBinding)},
-     * {@link #startInput(IBinder, IInputContext, int, EditorInfo, boolean)}, and
+     * {@link #startInput(IBinder, IInputContext, int, EditorInfo, boolean, boolean)}, and
      * {@link #unbindInput()} are called with the same order as the original calls
      * in {@link com.android.server.inputmethod.InputMethodManagerService}.
      * See {@link IBinder#FLAG_ONEWAY} for detailed semantics.</p>
      */
-    AtomicBoolean mIsUnbindIssued = null;
+    @Nullable
+    CancellationGroup mCancellationGroup = null;
 
     // NOTE: we should have a cache of these.
     static final class InputMethodSessionCallbackWrapper implements InputMethod.SessionCallback {
@@ -187,11 +189,11 @@
                 final IBinder startInputToken = (IBinder) args.arg1;
                 final IInputContext inputContext = (IInputContext) args.arg2;
                 final EditorInfo info = (EditorInfo) args.arg3;
-                final AtomicBoolean isUnbindIssued = (AtomicBoolean) args.arg4;
+                final CancellationGroup cancellationGroup = (CancellationGroup) args.arg4;
                 SomeArgs moreArgs = (SomeArgs) args.arg5;
                 final InputConnection ic = inputContext != null
                         ? new InputConnectionWrapper(
-                                mTarget, inputContext, moreArgs.argi3, isUnbindIssued)
+                                mTarget, inputContext, moreArgs.argi3, cancellationGroup)
                         : null;
                 info.makeCompatible(mTargetSdkVersion);
                 inputMethod.dispatchStartInputWithToken(
@@ -295,15 +297,15 @@
     @BinderThread
     @Override
     public void bindInput(InputBinding binding) {
-        if (mIsUnbindIssued != null) {
+        if (mCancellationGroup != null) {
             Log.e(TAG, "bindInput must be paired with unbindInput.");
         }
-        mIsUnbindIssued = new AtomicBoolean();
+        mCancellationGroup = new CancellationGroup();
         // This IInputContext is guaranteed to implement all the methods.
         final int missingMethodFlags = 0;
         InputConnection ic = new InputConnectionWrapper(mTarget,
                 IInputContext.Stub.asInterface(binding.getConnectionToken()), missingMethodFlags,
-                mIsUnbindIssued);
+                mCancellationGroup);
         InputBinding nu = new InputBinding(ic, binding);
         mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_SET_INPUT_CONTEXT, nu));
     }
@@ -311,10 +313,10 @@
     @BinderThread
     @Override
     public void unbindInput() {
-        if (mIsUnbindIssued != null) {
+        if (mCancellationGroup != null) {
             // Signal the flag then forget it.
-            mIsUnbindIssued.set(true);
-            mIsUnbindIssued = null;
+            mCancellationGroup.cancelAll();
+            mCancellationGroup = null;
         } else {
             Log.e(TAG, "unbindInput must be paired with bindInput.");
         }
@@ -326,16 +328,16 @@
     public void startInput(IBinder startInputToken, IInputContext inputContext,
             @InputConnectionInspector.MissingMethodFlags final int missingMethods,
             EditorInfo attribute, boolean restarting, boolean shouldPreRenderIme) {
-        if (mIsUnbindIssued == null) {
+        if (mCancellationGroup == null) {
             Log.e(TAG, "startInput must be called after bindInput.");
-            mIsUnbindIssued = new AtomicBoolean();
+            mCancellationGroup = new CancellationGroup();
         }
         SomeArgs args = SomeArgs.obtain();
         args.argi1 = restarting ? 1 : 0;
         args.argi2 = shouldPreRenderIme ? 1 : 0;
         args.argi3 = missingMethods;
-        mCaller.executeOrSendMessage(mCaller.obtainMessageOOOOO(
-                DO_START_INPUT, startInputToken, inputContext, attribute, mIsUnbindIssued, args));
+        mCaller.executeOrSendMessage(mCaller.obtainMessageOOOOO(DO_START_INPUT, startInputToken,
+                inputContext, attribute, mCancellationGroup, args));
     }
 
     @BinderThread
diff --git a/core/java/android/inputmethodservice/MultiClientInputMethodClientCallbackAdaptor.java b/core/java/android/inputmethodservice/MultiClientInputMethodClientCallbackAdaptor.java
index ef138a0..dbb669b 100644
--- a/core/java/android/inputmethodservice/MultiClientInputMethodClientCallbackAdaptor.java
+++ b/core/java/android/inputmethodservice/MultiClientInputMethodClientCallbackAdaptor.java
@@ -39,6 +39,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.inputmethod.IMultiClientInputMethodSession;
+import com.android.internal.inputmethod.CancellationGroup;
 import com.android.internal.os.SomeArgs;
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.internal.view.IInputContext;
@@ -46,7 +47,6 @@
 import com.android.internal.view.InputConnectionWrapper;
 
 import java.lang.ref.WeakReference;
-import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
  * Re-dispatches all the incoming per-client events to the specified {@link Looper} thread.
@@ -80,19 +80,19 @@
     @Nullable
     InputEventReceiver mInputEventReceiver;
 
-    private final AtomicBoolean mFinished = new AtomicBoolean(false);
+    private final CancellationGroup mCancellationGroup = new CancellationGroup();
 
     IInputMethodSession.Stub createIInputMethodSession() {
         synchronized (mSessionLock) {
             return new InputMethodSessionImpl(
-                    mSessionLock, mCallbackImpl, mHandler, mFinished);
+                    mSessionLock, mCallbackImpl, mHandler, mCancellationGroup);
         }
     }
 
     IMultiClientInputMethodSession.Stub createIMultiClientInputMethodSession() {
         synchronized (mSessionLock) {
             return new MultiClientInputMethodSessionImpl(
-                    mSessionLock, mCallbackImpl, mHandler, mFinished);
+                    mSessionLock, mCallbackImpl, mHandler, mCancellationGroup);
         }
     }
 
@@ -105,7 +105,7 @@
             mHandler = new Handler(looper, null, true);
             mReadChannel = readChannel;
             mInputEventReceiver = new ImeInputEventReceiver(mReadChannel, mHandler.getLooper(),
-                    mFinished, mDispatcherState, mCallbackImpl.mOriginalCallback);
+                    mCancellationGroup, mDispatcherState, mCallbackImpl.mOriginalCallback);
         }
     }
 
@@ -139,16 +139,17 @@
     }
 
     private static final class ImeInputEventReceiver extends InputEventReceiver {
-        private final AtomicBoolean mFinished;
+        private final CancellationGroup mCancellationGroupOnFinishSession;
         private final KeyEvent.DispatcherState mDispatcherState;
         private final MultiClientInputMethodServiceDelegate.ClientCallback mClientCallback;
         private final KeyEventCallbackAdaptor mKeyEventCallbackAdaptor;
 
-        ImeInputEventReceiver(InputChannel readChannel, Looper looper, AtomicBoolean finished,
+        ImeInputEventReceiver(InputChannel readChannel, Looper looper,
+                CancellationGroup cancellationGroupOnFinishSession,
                 KeyEvent.DispatcherState dispatcherState,
                 MultiClientInputMethodServiceDelegate.ClientCallback callback) {
             super(readChannel, looper);
-            mFinished = finished;
+            mCancellationGroupOnFinishSession = cancellationGroupOnFinishSession;
             mDispatcherState = dispatcherState;
             mClientCallback = callback;
             mKeyEventCallbackAdaptor = new KeyEventCallbackAdaptor(callback);
@@ -156,7 +157,7 @@
 
         @Override
         public void onInputEvent(InputEvent event) {
-            if (mFinished.get()) {
+            if (mCancellationGroupOnFinishSession.isCanceled()) {
                 // The session has been finished.
                 finishInputEvent(event, false);
                 return;
@@ -187,14 +188,14 @@
         private CallbackImpl mCallbackImpl;
         @GuardedBy("mSessionLock")
         private Handler mHandler;
-        private final AtomicBoolean mSessionFinished;
+        private final CancellationGroup mCancellationGroupOnFinishSession;
 
         InputMethodSessionImpl(Object lock, CallbackImpl callback, Handler handler,
-                AtomicBoolean sessionFinished) {
+                CancellationGroup cancellationGroupOnFinishSession) {
             mSessionLock = lock;
             mCallbackImpl = callback;
             mHandler = handler;
-            mSessionFinished = sessionFinished;
+            mCancellationGroupOnFinishSession = cancellationGroupOnFinishSession;
         }
 
         @Override
@@ -272,7 +273,7 @@
                 if (mCallbackImpl == null || mHandler == null) {
                     return;
                 }
-                mSessionFinished.set(true);
+                mCancellationGroupOnFinishSession.cancelAll();
                 mHandler.sendMessage(PooledLambda.obtainMessage(
                         CallbackImpl::finishSession, mCallbackImpl));
                 mCallbackImpl = null;
@@ -311,14 +312,14 @@
         private CallbackImpl mCallbackImpl;
         @GuardedBy("mSessionLock")
         private Handler mHandler;
-        private final AtomicBoolean mSessionFinished;
+        private final CancellationGroup mCancellationGroupOnFinishSession;
 
         MultiClientInputMethodSessionImpl(Object lock, CallbackImpl callback,
-                Handler handler, AtomicBoolean sessionFinished) {
+                Handler handler, CancellationGroup cancellationGroupOnFinishSession) {
             mSessionLock = lock;
             mCallbackImpl = callback;
             mHandler = handler;
-            mSessionFinished = sessionFinished;
+            mCancellationGroupOnFinishSession = cancellationGroupOnFinishSession;
         }
 
         @Override
@@ -335,7 +336,7 @@
                         new WeakReference<>(null);
                 args.arg1 = (inputContext == null) ? null
                         : new InputConnectionWrapper(fakeIMS, inputContext, missingMethods,
-                                mSessionFinished);
+                                mCancellationGroupOnFinishSession);
                 args.arg2 = editorInfo;
                 args.argi1 = controlFlags;
                 args.argi2 = softInputMode;
diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java
index 9ff7ebe..73c6b3d 100644
--- a/core/java/android/net/NetworkCapabilities.java
+++ b/core/java/android/net/NetworkCapabilities.java
@@ -169,6 +169,7 @@
             NET_CAPABILITY_OEM_PAID,
             NET_CAPABILITY_MCX,
             NET_CAPABILITY_PARTIAL_CONNECTIVITY,
+            NET_CAPABILITY_TEMPORARILY_NOT_METERED,
     })
     public @interface NetCapability { }
 
@@ -336,8 +337,16 @@
     @SystemApi
     public static final int NET_CAPABILITY_PARTIAL_CONNECTIVITY = 24;
 
+    /**
+     * This capability will be set for networks that are generally metered, but are currently
+     * unmetered, e.g., because the user is in a particular area. This capability can be changed at
+     * any time. When it is removed, applications are responsible for stopping any data transfer
+     * that should not occur on a metered network.
+     */
+    public static final int NET_CAPABILITY_TEMPORARILY_NOT_METERED = 25;
+
     private static final int MIN_NET_CAPABILITY = NET_CAPABILITY_MMS;
-    private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_PARTIAL_CONNECTIVITY;
+    private static final int MAX_NET_CAPABILITY = NET_CAPABILITY_TEMPORARILY_NOT_METERED;
 
     /**
      * Network capabilities that are expected to be mutable, i.e., can change while a particular
@@ -353,7 +362,8 @@
             | (1 << NET_CAPABILITY_FOREGROUND)
             | (1 << NET_CAPABILITY_NOT_CONGESTED)
             | (1 << NET_CAPABILITY_NOT_SUSPENDED)
-            | (1 << NET_CAPABILITY_PARTIAL_CONNECTIVITY);
+            | (1 << NET_CAPABILITY_PARTIAL_CONNECTIVITY
+            | (1 << NET_CAPABILITY_TEMPORARILY_NOT_METERED));
 
     /**
      * Network capabilities that are not allowed in NetworkRequests. This exists because the
@@ -424,6 +434,7 @@
      */
     private static final long TEST_NETWORKS_ALLOWED_CAPABILITIES =
             (1 << NET_CAPABILITY_NOT_METERED)
+            | (1 << NET_CAPABILITY_TEMPORARILY_NOT_METERED)
             | (1 << NET_CAPABILITY_NOT_RESTRICTED)
             | (1 << NET_CAPABILITY_NOT_VPN)
             | (1 << NET_CAPABILITY_NOT_ROAMING)
@@ -1864,6 +1875,7 @@
             case NET_CAPABILITY_OEM_PAID:             return "OEM_PAID";
             case NET_CAPABILITY_MCX:                  return "MCX";
             case NET_CAPABILITY_PARTIAL_CONNECTIVITY: return "PARTIAL_CONNECTIVITY";
+            case NET_CAPABILITY_TEMPORARILY_NOT_METERED:    return "TEMPORARILY_NOT_METERED";
             default:                                  return Integer.toString(capability);
         }
     }
diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java
index cdc0019..b8e1aa8 100644
--- a/core/java/android/os/PowerManager.java
+++ b/core/java/android/os/PowerManager.java
@@ -32,6 +32,7 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.service.dreams.Sandman;
+import android.sysprop.InitProperties;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.util.proto.ProtoOutputStream;
@@ -1487,7 +1488,7 @@
      */
     // TODO(b/138605180): add link to documentation once it's ready.
     public boolean isRebootingUserspaceSupported() {
-        return SystemProperties.getBoolean("ro.init.userspace_reboot.is_supported", false);
+        return InitProperties.is_userspace_reboot_supported().orElse(false);
     }
 
     /**
diff --git a/core/java/android/os/incremental/IIncrementalService.aidl b/core/java/android/os/incremental/IIncrementalService.aidl
index 2dbaea8..d8308c7 100644
--- a/core/java/android/os/incremental/IIncrementalService.aidl
+++ b/core/java/android/os/incremental/IIncrementalService.aidl
@@ -38,6 +38,13 @@
     int createLinkedStorage(in @utf8InCpp String path, int otherStorageId, int createMode);
 
     /**
+     * Changes storage params. Returns 0 on success, and -errno on failure.
+     * Use enableReadLogs to switch pages read logs reporting on and off.
+     * Returns 0 on success, and - errno on failure: permission check or remount.
+     */
+    int setStorageParams(int storageId, boolean enableReadLogs);
+
+    /**
      * Bind-mounts a path under a storage to a full path. Can be permanent or temporary.
      */
     const int BIND_TEMPORARY = 0;
diff --git a/core/java/android/os/incremental/IncrementalManager.java b/core/java/android/os/incremental/IncrementalManager.java
index 35518db..5f01408 100644
--- a/core/java/android/os/incremental/IncrementalManager.java
+++ b/core/java/android/os/incremental/IncrementalManager.java
@@ -19,11 +19,13 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.SystemService;
 import android.content.Context;
 import android.content.pm.DataLoaderParams;
 import android.content.pm.IDataLoaderStatusListener;
 import android.os.RemoteException;
+import android.system.ErrnoException;
 import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
@@ -319,6 +321,23 @@
         return nativeUnsafeGetFileSignature(path);
     }
 
+    /**
+     * Sets storage parameters.
+     *
+     * @param enableReadLogs - enables or disables read logs. Caller has to have a permission.
+     */
+    @RequiresPermission(android.Manifest.permission.LOADER_USAGE_STATS)
+    public void setStorageParams(int storageId, boolean enableReadLogs) throws ErrnoException {
+        try {
+            int res = mService.setStorageParams(storageId, enableReadLogs);
+            if (res < 0) {
+                throw new ErrnoException("setStorageParams", -res);
+            }
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
     /* Native methods */
     private static native boolean nativeIsEnabled();
     private static native boolean nativeIsIncrementalPath(@NonNull String path);
diff --git a/core/java/android/provider/DocumentsProvider.java b/core/java/android/provider/DocumentsProvider.java
index 327bca2..2e00c0c 100644
--- a/core/java/android/provider/DocumentsProvider.java
+++ b/core/java/android/provider/DocumentsProvider.java
@@ -1274,6 +1274,8 @@
 
             out.putParcelable(DocumentsContract.EXTRA_RESULT, path);
         } else if (METHOD_GET_DOCUMENT_METADATA.equals(method)) {
+            enforceReadPermissionInner(documentUri, getCallingPackage(),
+                    getCallingAttributionTag(), null);
             return getDocumentMetadata(documentId);
         } else {
             throw new UnsupportedOperationException("Method not supported " + method);
diff --git a/core/java/android/service/dataloader/DataLoaderService.java b/core/java/android/service/dataloader/DataLoaderService.java
index c047dc0..05877a5 100644
--- a/core/java/android/service/dataloader/DataLoaderService.java
+++ b/core/java/android/service/dataloader/DataLoaderService.java
@@ -21,6 +21,7 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.app.Service;
+import android.content.Context;
 import android.content.Intent;
 import android.content.pm.DataLoaderParams;
 import android.content.pm.DataLoaderParamsParcel;
@@ -31,6 +32,8 @@
 import android.content.pm.InstallationFileParcel;
 import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
+import android.os.incremental.IncrementalManager;
+import android.system.ErrnoException;
 import android.util.ExceptionUtils;
 import android.util.Slog;
 
@@ -208,6 +211,25 @@
         private final long mNativeInstance;
     }
 
+    /* Used by native FileSystemConnector. */
+    private boolean setStorageParams(int storageId, boolean enableReadLogs) {
+        IncrementalManager incrementalManager = (IncrementalManager) getSystemService(
+                Context.INCREMENTAL_SERVICE);
+        if (incrementalManager == null) {
+            Slog.e(TAG, "Failed to obtain incrementalManager: " + storageId);
+            return false;
+        }
+        try {
+            // This has to be done directly in incrementalManager as the storage
+            // might be missing still.
+            incrementalManager.setStorageParams(storageId, enableReadLogs);
+        } catch (ErrnoException e) {
+            Slog.e(TAG, "Failed to set params for storage: " + storageId, e);
+            return false;
+        }
+        return true;
+    }
+
     /* Native methods */
     private native boolean nativeCreateDataLoader(int storageId,
             @NonNull FileSystemControlParcel control,
diff --git a/core/java/android/service/dreams/DreamManagerInternal.java b/core/java/android/service/dreams/DreamManagerInternal.java
index 41fdd0b..7bf5c38 100644
--- a/core/java/android/service/dreams/DreamManagerInternal.java
+++ b/core/java/android/service/dreams/DreamManagerInternal.java
@@ -49,6 +49,12 @@
      * Called by the ActivityTaskManagerService to verify that the startDreamActivity
      * request comes from the current active dream component.
      *
+     * This function and its call path should not acquire the DreamManagerService lock
+     * to avoid deadlock with the ActivityTaskManager lock.
+     *
+     * TODO: Make this interaction push-based - the DreamManager should inform the
+     * ActivityTaskManager whenever the active dream component changes.
+     *
      * @param doze If true returns the current active doze component. Otherwise, returns the
      *             active dream component.
      */
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 7734ffb..0db9771 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -1054,6 +1054,7 @@
         // DreamServiceWrapper.onActivityCreated.
         if (!mWindowless) {
             Intent i = new Intent(this, DreamActivity.class);
+            i.setPackage(getApplicationContext().getPackageName());
             i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
             i.putExtra(DreamActivity.EXTRA_CALLBACK, mDreamServiceWrapper);
 
diff --git a/core/java/android/util/TimeUtils.java b/core/java/android/util/TimeUtils.java
index 4b3afba..e8d3459 100644
--- a/core/java/android/util/TimeUtils.java
+++ b/core/java/android/util/TimeUtils.java
@@ -335,7 +335,17 @@
 
     /** @hide Just for debugging; not internationalized. */
     public static String formatUptime(long time) {
-        final long diff = time - SystemClock.uptimeMillis();
+        return formatTime(time, SystemClock.uptimeMillis());
+    }
+
+    /** @hide Just for debugging; not internationalized. */
+    public static String formatRealtime(long time) {
+        return formatTime(time, SystemClock.elapsedRealtime());
+    }
+
+    /** @hide Just for debugging; not internationalized. */
+    public static String formatTime(long time, long referenceTime) {
+        long diff = time - referenceTime;
         if (diff > 0) {
             return time + " (in " + diff + " ms)";
         }
diff --git a/core/java/android/view/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java
index 35286ba..2461e96 100644
--- a/core/java/android/view/ImeInsetsSourceConsumer.java
+++ b/core/java/android/view/ImeInsetsSourceConsumer.java
@@ -16,7 +16,6 @@
 
 package android.view;
 
-import static android.view.InsetsController.ANIMATION_TYPE_USER;
 import static android.view.InsetsController.AnimationType;
 import static android.view.InsetsState.ITYPE_IME;
 
@@ -104,13 +103,9 @@
     void hide(boolean animationFinished, @AnimationType int animationType) {
         super.hide();
 
-        if (!animationFinished) {
-            if (animationType == ANIMATION_TYPE_USER) {
-                // if controlWindowInsetsAnimation is hiding keyboard.
-                notifyHidden();
-            }
-        } else {
+        if (animationFinished) {
             // remove IME surface as IME has finished hide animation.
+            notifyHidden();
             removeSurface();
         }
     }
diff --git a/core/java/android/view/InsetsAnimationControlCallbacks.java b/core/java/android/view/InsetsAnimationControlCallbacks.java
index 4227f78..74c1869 100644
--- a/core/java/android/view/InsetsAnimationControlCallbacks.java
+++ b/core/java/android/view/InsetsAnimationControlCallbacks.java
@@ -40,8 +40,10 @@
 
     /**
      * Schedule the apply by posting the animation callback.
+     *
+     * @param runner The runner that requested applying insets
      */
-    void scheduleApplyChangeInsets();
+    void scheduleApplyChangeInsets(InsetsAnimationControlRunner runner);
 
     /**
      * Finish the final steps after the animation.
diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java
index 94ca550..05abc60 100644
--- a/core/java/android/view/InsetsAnimationControlImpl.java
+++ b/core/java/android/view/InsetsAnimationControlImpl.java
@@ -140,7 +140,12 @@
 
     @Override
     public void setInsetsAndAlpha(Insets insets, float alpha, float fraction) {
-        if (mFinished) {
+        setInsetsAndAlpha(insets, alpha, fraction, false /* allowWhenFinished */);
+    }
+
+    private void setInsetsAndAlpha(Insets insets, float alpha, float fraction,
+            boolean allowWhenFinished) {
+        if (!allowWhenFinished && mFinished) {
             throw new IllegalStateException(
                     "Can't change insets on an animation that is finished.");
         }
@@ -151,7 +156,7 @@
         mPendingFraction = sanitize(fraction);
         mPendingInsets = sanitize(insets);
         mPendingAlpha = sanitize(alpha);
-        mController.scheduleApplyChangeInsets();
+        mController.scheduleApplyChangeInsets(this);
     }
 
     @VisibleForTesting
@@ -201,8 +206,9 @@
             return;
         }
         mShownOnFinish = shown;
-        setInsetsAndAlpha(shown ? mShownInsets : mHiddenInsets, 1f /* alpha */, 1f /* fraction */);
         mFinished = true;
+        setInsetsAndAlpha(shown ? mShownInsets : mHiddenInsets, 1f /* alpha */, 1f /* fraction */,
+                true /* allowWhenFinished */);
         mListener.onFinished(this);
     }
 
diff --git a/core/java/android/view/InsetsAnimationThreadControlRunner.java b/core/java/android/view/InsetsAnimationThreadControlRunner.java
index 40ffa7ef..9dfdd06 100644
--- a/core/java/android/view/InsetsAnimationThreadControlRunner.java
+++ b/core/java/android/view/InsetsAnimationThreadControlRunner.java
@@ -54,7 +54,7 @@
         }
 
         @Override
-        public void scheduleApplyChangeInsets() {
+        public void scheduleApplyChangeInsets(InsetsAnimationControlRunner runner) {
             mControl.applyChangeInsets(mState);
         }
 
@@ -91,7 +91,7 @@
             @AnimationType int animationType, Handler mainThreadHandler) {
         mMainThreadHandler = mainThreadHandler;
         mOuterCallbacks = controller;
-        mControl = new InsetsAnimationControlImpl(copyControls(controls), frame, state, listener,
+        mControl = new InsetsAnimationControlImpl(controls, frame, state, listener,
                 types, mCallbacks, durationMs, interpolator, animationType);
         InsetsAnimationThread.getHandler().post(() -> listener.onReady(mControl, types));
     }
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 8eb9b5f..72ddaca 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -1076,8 +1076,8 @@
 
     @VisibleForTesting
     @Override
-    public void scheduleApplyChangeInsets() {
-        if (mStartingAnimation) {
+    public void scheduleApplyChangeInsets(InsetsAnimationControlRunner runner) {
+        if (mStartingAnimation || runner.getAnimationType() == ANIMATION_TYPE_USER) {
             mAnimCallback.run();
             mAnimCallbackScheduled = false;
             return;
diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java
index 83ff8fa..f74221d 100644
--- a/core/java/android/view/InsetsSourceConsumer.java
+++ b/core/java/android/view/InsetsSourceConsumer.java
@@ -117,7 +117,7 @@
             }
         }
         if (lastControl != null) {
-            lastControl.release(mController::releaseSurfaceControlFromRt);
+            lastControl.release(SurfaceControl::release);
         }
     }
 
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index c515466..9896aa4 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -172,6 +172,10 @@
         for (int type = FIRST_TYPE; type <= LAST_TYPE; type++) {
             InsetsSource source = mSources.get(type);
             if (source == null) {
+                int index = indexOf(toPublicType(type));
+                if (typeInsetsMap[index] == null) {
+                    typeInsetsMap[index] = Insets.NONE;
+                }
                 continue;
             }
 
diff --git a/core/java/android/view/NotificationHeaderView.java b/core/java/android/view/NotificationHeaderView.java
index 0359f3b4..a9f3e04 100644
--- a/core/java/android/view/NotificationHeaderView.java
+++ b/core/java/android/view/NotificationHeaderView.java
@@ -60,11 +60,7 @@
     private NotificationExpandButton mExpandButton;
     private CachingIconView mIcon;
     private View mProfileBadge;
-    private View mOverlayIcon;
-    private View mCameraIcon;
-    private View mMicIcon;
     private View mAppOps;
-    private View mAudiblyAlertedIcon;
     private boolean mExpanded;
     private boolean mShowExpandButtonAtEnd;
     private boolean mShowWorkBadgeAtEnd;
@@ -121,11 +117,7 @@
         mExpandButton = findViewById(com.android.internal.R.id.expand_button);
         mIcon = findViewById(com.android.internal.R.id.icon);
         mProfileBadge = findViewById(com.android.internal.R.id.profile_badge);
-        mCameraIcon = findViewById(com.android.internal.R.id.camera);
-        mMicIcon = findViewById(com.android.internal.R.id.mic);
-        mOverlayIcon = findViewById(com.android.internal.R.id.overlay);
         mAppOps = findViewById(com.android.internal.R.id.app_ops);
-        mAudiblyAlertedIcon = findViewById(com.android.internal.R.id.alerted_icon);
     }
 
     @Override
@@ -300,10 +292,6 @@
      */
     public void setAppOpsOnClickListener(OnClickListener l) {
         mAppOpsListener = l;
-        mAppOps.setOnClickListener(mAppOpsListener);
-        mCameraIcon.setOnClickListener(mAppOpsListener);
-        mMicIcon.setOnClickListener(mAppOpsListener);
-        mOverlayIcon.setOnClickListener(mAppOpsListener);
         updateTouchListener();
     }
 
@@ -328,27 +316,6 @@
         updateExpandButton();
     }
 
-    /**
-     * Shows or hides 'app op in use' icons based on app usage.
-     */
-    public void showAppOpsIcons(ArraySet<Integer> appOps) {
-        if (mOverlayIcon == null || mCameraIcon == null || mMicIcon == null || appOps == null) {
-            return;
-        }
-
-        mOverlayIcon.setVisibility(appOps.contains(AppOpsManager.OP_SYSTEM_ALERT_WINDOW)
-                ? View.VISIBLE : View.GONE);
-        mCameraIcon.setVisibility(appOps.contains(AppOpsManager.OP_CAMERA)
-                ? View.VISIBLE : View.GONE);
-        mMicIcon.setVisibility(appOps.contains(AppOpsManager.OP_RECORD_AUDIO)
-                ? View.VISIBLE : View.GONE);
-    }
-
-    /** Updates icon visibility based on the noisiness of the notification. */
-    public void setRecentlyAudiblyAlerted(boolean audiblyAlerted) {
-        mAudiblyAlertedIcon.setVisibility(audiblyAlerted ? View.VISIBLE : View.GONE);
-    }
-
     private void updateExpandButton() {
         int drawableId;
         int contentDescriptionId;
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 51304dc..35f955f 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -130,6 +130,7 @@
 import android.view.Window.OnContentApplyWindowInsetsListener;
 import android.view.WindowInsets.Type;
 import android.view.WindowInsets.Type.InsetsType;
+import android.view.WindowManager.LayoutParams;
 import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager;
@@ -1412,6 +1413,10 @@
                         | (oldSoftInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST);
             }
 
+            if ((changes & LayoutParams.SOFT_INPUT_MODE_CHANGED) != 0) {
+                requestFitSystemWindows();
+            }
+
             mWindowAttributesChanged = true;
             scheduleTraversals();
         }
diff --git a/core/java/android/view/WindowInsets.java b/core/java/android/view/WindowInsets.java
index 9b2a6cb..ca3dd04 100644
--- a/core/java/android/view/WindowInsets.java
+++ b/core/java/android/view/WindowInsets.java
@@ -818,12 +818,13 @@
      * @return A modified copy of this WindowInsets
      * @deprecated Consuming of different parts individually of a {@link WindowInsets} instance is
      * deprecated, since {@link WindowInsets} contains many different insets. Use {@link #CONSUMED}
-     * instead to stop dispatching insets.
+     * instead to stop dispatching insets. On {@link android.os.Build.VERSION_CODES#R R}, this
+     * method has no effect.
      */
     @Deprecated
     @NonNull
     public WindowInsets consumeStableInsets() {
-        return consumeSystemWindowInsets();
+        return this;
     }
 
     /**
@@ -835,12 +836,24 @@
 
     @Override
     public String toString() {
-        return "WindowInsets{systemWindowInsets=" + getSystemWindowInsets()
-                + " stableInsets=" + getStableInsets()
-                + " sysGestureInsets=" + getSystemGestureInsets()
-                + (mDisplayCutout != null ? " cutout=" + mDisplayCutout : "")
-                + (isRound() ? " round" : "")
-                + "}";
+        StringBuilder result = new StringBuilder("WindowInsets{\n    ");
+        for (int i = 0; i < SIZE; i++) {
+            Insets insets = mTypeInsetsMap[i];
+            Insets maxInsets = mTypeMaxInsetsMap[i];
+            boolean visible = mTypeVisibilityMap[i];
+            if (!Insets.NONE.equals(insets) || !Insets.NONE.equals(maxInsets) || visible) {
+                result.append(Type.toString(1 << i)).append("=").append(insets)
+                        .append(" max=").append(maxInsets)
+                        .append(" vis=").append(visible)
+                        .append("\n    ");
+            }
+        }
+
+        result.append(mDisplayCutout != null ? "cutout=" + mDisplayCutout : "");
+        result.append("\n    ");
+        result.append(isRound() ? "round" : "");
+        result.append("}");
+        return result.toString();
     }
 
     /**
@@ -1309,6 +1322,32 @@
             }
         }
 
+        static String toString(@InsetsType int type) {
+            switch (type) {
+                case STATUS_BARS:
+                    return "statusBars";
+                case NAVIGATION_BARS:
+                    return "navigationBars";
+                case CAPTION_BAR:
+                    return "captionBar";
+                case IME:
+                    return "ime";
+                case SYSTEM_GESTURES:
+                    return "systemGestures";
+                case MANDATORY_SYSTEM_GESTURES:
+                    return "mandatorySystemGestures";
+                case TAPPABLE_ELEMENT:
+                    return "tappableElement";
+                case DISPLAY_CUTOUT:
+                    return "displayCutout";
+                case WINDOW_DECOR:
+                    return "windowDecor";
+                default:
+                    throw new IllegalArgumentException("type needs to be >= FIRST and <= LAST,"
+                            + " type=" + type);
+            }
+        }
+
         private Type() {
         }
 
diff --git a/core/java/android/view/inputmethod/InlineSuggestionsRequest.java b/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
index 6aa288d..af896fc 100644
--- a/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
+++ b/core/java/android/view/inputmethod/InlineSuggestionsRequest.java
@@ -30,6 +30,7 @@
 
 import com.android.internal.util.DataClass;
 import com.android.internal.util.Preconditions;
+import com.android.internal.widget.InlinePresentationStyleUtils;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -113,6 +114,10 @@
         mHostInputToken = hostInputToken;
     }
 
+    private boolean extrasEquals(@NonNull Bundle extras) {
+        return InlinePresentationStyleUtils.bundleEquals(mExtras, extras);
+    }
+
     // TODO(b/149609075): remove once IBinder parcelling is natively supported
     private void parcelHostInputToken(@NonNull Parcel parcel, int flags) {
         parcel.writeStrongBinder(mHostInputToken);
@@ -331,7 +336,7 @@
                 && java.util.Objects.equals(mInlinePresentationSpecs, that.mInlinePresentationSpecs)
                 && java.util.Objects.equals(mHostPackageName, that.mHostPackageName)
                 && java.util.Objects.equals(mSupportedLocales, that.mSupportedLocales)
-                && java.util.Objects.equals(mExtras, that.mExtras)
+                && extrasEquals(that.mExtras)
                 && java.util.Objects.equals(mHostInputToken, that.mHostInputToken)
                 && mHostDisplayId == that.mHostDisplayId;
     }
@@ -603,10 +608,10 @@
     }
 
     @DataClass.Generated(
-            time = 1585691147541L,
+            time = 1585768018462L,
             codegenVersion = "1.0.15",
             sourceFile = "frameworks/base/core/java/android/view/inputmethod/InlineSuggestionsRequest.java",
-            inputSignatures = "public static final  int SUGGESTION_COUNT_UNLIMITED\nprivate final  int mMaxSuggestionCount\nprivate final @android.annotation.NonNull java.util.List<android.widget.inline.InlinePresentationSpec> mInlinePresentationSpecs\nprivate @android.annotation.NonNull java.lang.String mHostPackageName\nprivate @android.annotation.NonNull android.os.LocaleList mSupportedLocales\nprivate @android.annotation.NonNull android.os.Bundle mExtras\nprivate @android.annotation.Nullable android.os.IBinder mHostInputToken\nprivate  int mHostDisplayId\npublic @android.compat.annotation.UnsupportedAppUsage @android.annotation.NonNull java.util.List<android.view.inline.InlinePresentationSpec> getPresentationSpecs()\npublic  void setHostInputToken(android.os.IBinder)\nprivate  void parcelHostInputToken(android.os.Parcel,int)\nprivate @android.annotation.Nullable android.os.IBinder unparcelHostInputToken(android.os.Parcel)\npublic  void setHostDisplayId(int)\nprivate  void onConstructed()\nprivate static  int defaultMaxSuggestionCount()\nprivate static  java.lang.String defaultHostPackageName()\nprivate static  android.os.LocaleList defaultSupportedLocales()\nprivate static @android.annotation.Nullable android.os.IBinder defaultHostInputToken()\nprivate static @android.annotation.Nullable int defaultHostDisplayId()\nprivate static @android.annotation.NonNull android.os.Bundle defaultExtras()\nclass InlineSuggestionsRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\npublic @android.compat.annotation.UnsupportedAppUsage @android.annotation.NonNull android.view.inputmethod.InlineSuggestionsRequest.Builder addPresentationSpecs(android.view.inline.InlinePresentationSpec)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(java.util.List<android.widget.inline.InlinePresentationSpec>)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostInputToken(android.os.IBinder)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostDisplayId(int)\nclass BaseBuilder extends java.lang.Object implements []")
+            inputSignatures = "public static final  int SUGGESTION_COUNT_UNLIMITED\nprivate final  int mMaxSuggestionCount\nprivate final @android.annotation.NonNull java.util.List<android.widget.inline.InlinePresentationSpec> mInlinePresentationSpecs\nprivate @android.annotation.NonNull java.lang.String mHostPackageName\nprivate @android.annotation.NonNull android.os.LocaleList mSupportedLocales\nprivate @android.annotation.NonNull android.os.Bundle mExtras\nprivate @android.annotation.Nullable android.os.IBinder mHostInputToken\nprivate  int mHostDisplayId\npublic @android.compat.annotation.UnsupportedAppUsage @android.annotation.NonNull java.util.List<android.view.inline.InlinePresentationSpec> getPresentationSpecs()\npublic  void setHostInputToken(android.os.IBinder)\nprivate  boolean extrasEquals(android.os.Bundle)\nprivate  void parcelHostInputToken(android.os.Parcel,int)\nprivate @android.annotation.Nullable android.os.IBinder unparcelHostInputToken(android.os.Parcel)\npublic  void setHostDisplayId(int)\nprivate  void onConstructed()\nprivate static  int defaultMaxSuggestionCount()\nprivate static  java.lang.String defaultHostPackageName()\nprivate static  android.os.LocaleList defaultSupportedLocales()\nprivate static @android.annotation.Nullable android.os.IBinder defaultHostInputToken()\nprivate static @android.annotation.Nullable int defaultHostDisplayId()\nprivate static @android.annotation.NonNull android.os.Bundle defaultExtras()\nclass InlineSuggestionsRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\npublic @android.compat.annotation.UnsupportedAppUsage @android.annotation.NonNull android.view.inputmethod.InlineSuggestionsRequest.Builder addPresentationSpecs(android.view.inline.InlinePresentationSpec)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setInlinePresentationSpecs(java.util.List<android.widget.inline.InlinePresentationSpec>)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostPackageName(java.lang.String)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostInputToken(android.os.IBinder)\nabstract  android.view.inputmethod.InlineSuggestionsRequest.Builder setHostDisplayId(int)\nclass BaseBuilder extends java.lang.Object implements []")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/widget/TEST_MAPPING b/core/java/android/widget/TEST_MAPPING
index d0b8dbc..f089f48 100644
--- a/core/java/android/widget/TEST_MAPPING
+++ b/core/java/android/widget/TEST_MAPPING
@@ -13,7 +13,7 @@
       "name": "CtsWindowManagerDeviceTestCases",
       "options": [
         {
-          "include-filter": "android.server.wm.ToastTest"
+          "include-filter": "android.server.wm.ToastWindowTest"
         }
       ],
       "file_patterns": ["Toast\\.java"]
diff --git a/core/java/android/widget/inline/InlinePresentationSpec.java b/core/java/android/widget/inline/InlinePresentationSpec.java
index 00eb3ce..5635857 100644
--- a/core/java/android/widget/inline/InlinePresentationSpec.java
+++ b/core/java/android/widget/inline/InlinePresentationSpec.java
@@ -23,6 +23,7 @@
 import android.util.Size;
 
 import com.android.internal.util.DataClass;
+import com.android.internal.widget.InlinePresentationStyleUtils;
 
 /**
  * This class represents the presentation specification by which an inline suggestion
@@ -52,6 +53,10 @@
         return Bundle.EMPTY;
     }
 
+    private boolean styleEquals(@NonNull Bundle style) {
+        return InlinePresentationStyleUtils.bundleEquals(mStyle, style);
+    }
+
     /** @hide */
     @DataClass.Suppress({"setMaxSize", "setMinSize"})
     abstract static class BaseBuilder {
@@ -143,7 +148,7 @@
         return true
                 && java.util.Objects.equals(mMinSize, that.mMinSize)
                 && java.util.Objects.equals(mMaxSize, that.mMaxSize)
-                && java.util.Objects.equals(mStyle, that.mStyle);
+                && styleEquals(that.mStyle);
     }
 
     @Override
@@ -280,10 +285,10 @@
     }
 
     @DataClass.Generated(
-            time = 1585605466300L,
+            time = 1585768046898L,
             codegenVersion = "1.0.15",
             sourceFile = "frameworks/base/core/java/android/widget/inline/InlinePresentationSpec.java",
-            inputSignatures = "private final @android.annotation.NonNull android.util.Size mMinSize\nprivate final @android.annotation.NonNull android.util.Size mMaxSize\nprivate final @android.annotation.NonNull android.os.Bundle mStyle\nprivate static @android.annotation.NonNull android.os.Bundle defaultStyle()\nclass InlinePresentationSpec extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\nclass BaseBuilder extends java.lang.Object implements []")
+            inputSignatures = "private final @android.annotation.NonNull android.util.Size mMinSize\nprivate final @android.annotation.NonNull android.util.Size mMaxSize\nprivate final @android.annotation.NonNull android.os.Bundle mStyle\nprivate static @android.annotation.NonNull android.os.Bundle defaultStyle()\nprivate  boolean styleEquals(android.os.Bundle)\nclass InlinePresentationSpec extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true, genBuilder=true)\nclass BaseBuilder extends java.lang.Object implements []")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/window/DisplayAreaOrganizer.java b/core/java/android/window/DisplayAreaOrganizer.java
new file mode 100644
index 0000000..eee222b
--- /dev/null
+++ b/core/java/android/window/DisplayAreaOrganizer.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2020 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.window;
+
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.TestApi;
+import android.os.RemoteException;
+import android.util.Singleton;
+
+/**
+ * Interface for WindowManager to delegate control of display areas.
+ * @hide
+ */
+@TestApi
+public class DisplayAreaOrganizer extends WindowOrganizer {
+
+    public static final int FEATURE_UNDEFINED = -1;
+    public static final int FEATURE_SYSTEM_FIRST = 0;
+    // The Root display area on a display
+    public static final int FEATURE_ROOT = FEATURE_SYSTEM_FIRST;
+    // Display area hosting the task container.
+    public static final int FEATURE_TASK_CONTAINER = FEATURE_SYSTEM_FIRST + 1;
+    // Display area hosting non-activity window tokens.
+    public static final int FEATURE_WINDOW_TOKENS = FEATURE_SYSTEM_FIRST + 2;
+
+    public static final int FEATURE_SYSTEM_LAST = 10_000;
+
+    // Vendor specific display area definition can start with this value.
+    public static final int FEATURE_VENDOR_FIRST = FEATURE_SYSTEM_LAST + 1;
+
+    @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
+    public void registerOrganizer(int displayAreaFeature) {
+        try {
+            getController().registerOrganizer(mInterface, displayAreaFeature);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    public void onDisplayAreaAppeared(@NonNull WindowContainerToken displayArea) {}
+
+    public void onDisplayAreaVanished(@NonNull WindowContainerToken displayArea) {}
+
+
+    private final IDisplayAreaOrganizer mInterface = new IDisplayAreaOrganizer.Stub() {
+
+        @Override
+        public void onDisplayAreaAppeared(@NonNull WindowContainerToken displayArea) {
+            DisplayAreaOrganizer.this.onDisplayAreaAppeared(displayArea);
+        }
+
+        @Override
+        public void onDisplayAreaVanished(@NonNull WindowContainerToken displayArea) {
+            DisplayAreaOrganizer.this.onDisplayAreaVanished(displayArea);
+        }
+    };
+
+    private static IDisplayAreaOrganizerController getController() {
+        return IDisplayAreaOrganizerControllerSingleton.get();
+    }
+
+    private static final Singleton<IDisplayAreaOrganizerController>
+            IDisplayAreaOrganizerControllerSingleton =
+            new Singleton<IDisplayAreaOrganizerController>() {
+                @Override
+                protected IDisplayAreaOrganizerController create() {
+                    try {
+                        return getWindowOrganizerController()
+                                .getDisplayAreaOrganizerController();
+                    } catch (RemoteException e) {
+                        return null;
+                    }
+                }
+            };
+
+}
diff --git a/core/java/android/window/IDisplayAreaOrganizer.aidl b/core/java/android/window/IDisplayAreaOrganizer.aidl
index 1045ab2..9c72e60 100644
--- a/core/java/android/window/IDisplayAreaOrganizer.aidl
+++ b/core/java/android/window/IDisplayAreaOrganizer.aidl
@@ -16,13 +16,13 @@
 
 package android.window;
 
-import android.window.IWindowContainer;
+import android.window.WindowContainerToken;
 
 /**
  * Interface for WindowManager to delegate control of display areas.
  * {@hide}
  */
 oneway interface IDisplayAreaOrganizer {
-    void onDisplayAreaAppeared(in IWindowContainer displayArea);
-    void onDisplayAreaVanished(in IWindowContainer displayArea);
+    void onDisplayAreaAppeared(in WindowContainerToken displayArea);
+    void onDisplayAreaVanished(in WindowContainerToken displayArea);
 }
diff --git a/core/java/android/window/ITaskOrganizer.aidl b/core/java/android/window/ITaskOrganizer.aidl
index b038b0f..b4f0162 100644
--- a/core/java/android/window/ITaskOrganizer.aidl
+++ b/core/java/android/window/ITaskOrganizer.aidl
@@ -18,7 +18,7 @@
 
 import android.view.SurfaceControl;
 import android.app.ActivityManager;
-import android.window.IWindowContainer;
+import android.window.WindowContainerToken;
 
 /**
  * Interface for ActivityTaskManager/WindowManager to delegate control of tasks.
diff --git a/core/java/android/window/ITaskOrganizerController.aidl b/core/java/android/window/ITaskOrganizerController.aidl
index ba65915..1c03b2f 100644
--- a/core/java/android/window/ITaskOrganizerController.aidl
+++ b/core/java/android/window/ITaskOrganizerController.aidl
@@ -18,7 +18,7 @@
 
 import android.app.ActivityManager;
 import android.window.ITaskOrganizer;
-import android.window.IWindowContainer;
+import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 
 /** @hide */
@@ -40,23 +40,23 @@
     ActivityManager.RunningTaskInfo createRootTask(int displayId, int windowingMode);
 
     /** Deletes a persistent root task in WM */
-    boolean deleteRootTask(IWindowContainer task);
+    boolean deleteRootTask(in WindowContainerToken task);
 
     /** Gets direct child tasks (ordered from top-to-bottom) */
-    List<ActivityManager.RunningTaskInfo> getChildTasks(in IWindowContainer parent,
+    List<ActivityManager.RunningTaskInfo> getChildTasks(in WindowContainerToken parent,
             in int[] activityTypes);
 
     /** Gets all root tasks on a display (ordered from top-to-bottom) */
     List<ActivityManager.RunningTaskInfo> getRootTasks(int displayId, in int[] activityTypes);
 
     /** Get the root task which contains the current ime target */
-    IWindowContainer getImeTarget(int display);
+    WindowContainerToken getImeTarget(int display);
 
     /**
      * Set's the root task to launch new tasks into on a display. {@code null} means no launch root
      * and thus new tasks just end up directly on the display.
      */
-    void setLaunchRoot(int displayId, in IWindowContainer root);
+    void setLaunchRoot(int displayId, in WindowContainerToken root);
 
     /**
      * Requests that the given task organizer is notified when back is pressed on the root activity
diff --git a/core/java/android/window/IWindowContainer.aidl b/core/java/android/window/IWindowContainerToken.aidl
similarity index 96%
rename from core/java/android/window/IWindowContainer.aidl
rename to core/java/android/window/IWindowContainerToken.aidl
index f2960f6..57c7abf 100644
--- a/core/java/android/window/IWindowContainer.aidl
+++ b/core/java/android/window/IWindowContainerToken.aidl
@@ -23,7 +23,7 @@
  * token.
  * @hide
  */
-interface IWindowContainer {
+interface IWindowContainerToken {
 
     /**
      * Gets a persistent leash for this container or {@code null}.
diff --git a/core/java/android/window/IWindowContainerTransactionCallback.aidl b/core/java/android/window/IWindowContainerTransactionCallback.aidl
index 0579932..eb07965 100644
--- a/core/java/android/window/IWindowContainerTransactionCallback.aidl
+++ b/core/java/android/window/IWindowContainerTransactionCallback.aidl
@@ -24,5 +24,5 @@
  */
 oneway interface IWindowContainerTransactionCallback {
     /** Called upon completion of WindowOrganizer#applyTransaction */
-    void transactionReady(int id, in SurfaceControl.Transaction t);
+    void onTransactionReady(int id, in SurfaceControl.Transaction t);
 }
diff --git a/core/java/android/app/TaskEmbedder.java b/core/java/android/window/TaskEmbedder.java
similarity index 97%
rename from core/java/android/app/TaskEmbedder.java
rename to core/java/android/window/TaskEmbedder.java
index 10c11f2..45ab310 100644
--- a/core/java/android/app/TaskEmbedder.java
+++ b/core/java/android/window/TaskEmbedder.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,13 +14,19 @@
  * limitations under the License.
  */
 
-package android.app;
+package android.window;
 
 import static android.view.Display.INVALID_DISPLAY;
 
 import android.annotation.CallSuper;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.app.ActivityOptions;
+import android.app.ActivityTaskManager;
+import android.app.ActivityView;
+import android.app.IActivityTaskManager;
+import android.app.PendingIntent;
+import android.app.TaskStackListener;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
diff --git a/core/java/android/window/TaskOrganizer.java b/core/java/android/window/TaskOrganizer.java
new file mode 100644
index 0000000..5098b44
--- /dev/null
+++ b/core/java/android/window/TaskOrganizer.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2020 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.window;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.TestApi;
+import android.app.ActivityManager;
+import android.os.RemoteException;
+import android.util.Singleton;
+
+import java.util.List;
+
+/**
+ * Interface for ActivityTaskManager/WindowManager to delegate control of tasks.
+ * @hide
+ */
+@TestApi
+public class TaskOrganizer extends WindowOrganizer {
+
+    /**
+     * Register a TaskOrganizer to manage tasks as they enter the given windowing mode.
+     * If there was already a TaskOrganizer for this windowing mode it will be evicted
+     * and receive taskVanished callbacks in the process.
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
+    public void registerOrganizer(int windowingMode) {
+        try {
+            getController().registerTaskOrganizer(mInterface, windowingMode);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** Unregisters a previously registered task organizer. */
+    @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
+    public void unregisterOrganizer() {
+        try {
+            getController().unregisterTaskOrganizer(mInterface);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    public void onTaskAppeared(@NonNull ActivityManager.RunningTaskInfo taskInfo) {}
+
+    public void onTaskVanished(@NonNull ActivityManager.RunningTaskInfo taskInfo) {}
+
+    public void onTaskInfoChanged(@NonNull ActivityManager.RunningTaskInfo info) {}
+
+    public void onBackPressedOnTaskRoot(@NonNull ActivityManager.RunningTaskInfo info) {}
+
+    /** Creates a persistent root task in WM for a particular windowing-mode. */
+    @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
+    @Nullable
+    public static ActivityManager.RunningTaskInfo createRootTask(int displayId, int windowingMode) {
+        try {
+            return getController().createRootTask(displayId, windowingMode);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** Deletes a persistent root task in WM */
+    @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
+    public static boolean deleteRootTask(@NonNull WindowContainerToken task) {
+        try {
+            return getController().deleteRootTask(task);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** Gets direct child tasks (ordered from top-to-bottom) */
+    @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
+    @Nullable
+    public static List<ActivityManager.RunningTaskInfo> getChildTasks(
+            @NonNull WindowContainerToken parent, @NonNull int[] activityTypes) {
+        try {
+            return getController().getChildTasks(parent, activityTypes);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** Gets all root tasks on a display (ordered from top-to-bottom) */
+    @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
+    @Nullable
+    public static List<ActivityManager.RunningTaskInfo> getRootTasks(
+            int displayId, @NonNull int[] activityTypes) {
+        try {
+            return getController().getRootTasks(displayId, activityTypes);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** Get the root task which contains the current ime target */
+    @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
+    @Nullable
+    public static WindowContainerToken getImeTarget(int display) {
+        try {
+            return getController().getImeTarget(display);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Set's the root task to launch new tasks into on a display. {@code null} means no launch
+     * root and thus new tasks just end up directly on the display.
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
+    public static void setLaunchRoot(int displayId, @NonNull WindowContainerToken root) {
+        try {
+            getController().setLaunchRoot(displayId, root);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Requests that the given task organizer is notified when back is pressed on the root activity
+     * of one of its controlled tasks.
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
+    public void setInterceptBackPressedOnTaskRoot(boolean interceptBackPressed) {
+        try {
+            getController().setInterceptBackPressedOnTaskRoot(mInterface, interceptBackPressed);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    private final ITaskOrganizer mInterface = new ITaskOrganizer.Stub() {
+
+        @Override
+        public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo) {
+            TaskOrganizer.this.onTaskAppeared(taskInfo);
+        }
+
+        @Override
+        public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
+            TaskOrganizer.this.onTaskVanished(taskInfo);
+        }
+
+        @Override
+        public void onTaskInfoChanged(ActivityManager.RunningTaskInfo info) {
+            TaskOrganizer.this.onTaskInfoChanged(info);
+        }
+
+        @Override
+        public void onBackPressedOnTaskRoot(ActivityManager.RunningTaskInfo info) {
+            TaskOrganizer.this.onBackPressedOnTaskRoot(info);
+        }
+    };
+
+    private static ITaskOrganizerController getController() {
+        return ITaskOrganizerControllerSingleton.get();
+    }
+
+    private static final Singleton<ITaskOrganizerController> ITaskOrganizerControllerSingleton =
+            new Singleton<ITaskOrganizerController>() {
+                @Override
+                protected ITaskOrganizerController create() {
+                    try {
+                        return getWindowOrganizerController().getTaskOrganizerController();
+                    } catch (RemoteException e) {
+                        return null;
+                    }
+                }
+            };
+}
diff --git a/core/java/android/app/TaskOrganizerTaskEmbedder.java b/core/java/android/window/TaskOrganizerTaskEmbedder.java
similarity index 79%
rename from core/java/android/app/TaskOrganizerTaskEmbedder.java
rename to core/java/android/window/TaskOrganizerTaskEmbedder.java
index adc0792..2091c93 100644
--- a/core/java/android/app/TaskOrganizerTaskEmbedder.java
+++ b/core/java/android/window/TaskOrganizerTaskEmbedder.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,22 +14,20 @@
  * limitations under the License.
  */
 
-package android.app;
+package android.window;
 
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 
+import android.app.ActivityManager;
+import android.app.ActivityOptions;
+import android.app.ActivityView;
+import android.app.TaskStackListener;
 import android.content.Context;
 import android.graphics.Rect;
-import android.os.RemoteException;
 import android.util.Log;
 import android.view.KeyEvent;
 import android.view.SurfaceControl;
-import android.window.ITaskOrganizer;
-import android.window.IWindowContainer;
-import android.window.WindowContainerTransaction;
-import android.window.WindowOrganizer;
-import android.window.WindowOrganizer.TaskOrganizer;
 
 /**
  * A component which handles embedded display of tasks within another window. The embedded task can
@@ -41,9 +39,9 @@
     private static final String TAG = "TaskOrgTaskEmbedder";
     private static final boolean DEBUG = false;
 
-    private ITaskOrganizer.Stub mTaskOrganizer;
+    private TaskOrganizer mTaskOrganizer;
     private ActivityManager.RunningTaskInfo mTaskInfo;
-    private IWindowContainer mTaskToken;
+    private WindowContainerToken mTaskToken;
     private SurfaceControl mTaskLeash;
     private boolean mPendingNotifyBoundsChanged;
 
@@ -79,16 +77,11 @@
         }
         // Register the task organizer
         mTaskOrganizer =  new TaskOrganizerImpl();
-        try {
-            // TODO(wm-shell): This currently prevents other organizers from controlling MULT_WINDOW
-            // windowing mode tasks. Plan is to migrate this to a wm-shell front-end when that
-            // infrastructure is ready.
-            TaskOrganizer.registerOrganizer(mTaskOrganizer, WINDOWING_MODE_MULTI_WINDOW);
-            TaskOrganizer.setInterceptBackPressedOnTaskRoot(mTaskOrganizer, true);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Failed to initialize TaskEmbedder", e);
-            return false;
-        }
+        // TODO(wm-shell): This currently prevents other organizers from controlling MULT_WINDOW
+        // windowing mode tasks. Plan is to migrate this to a wm-shell front-end when that
+        // infrastructure is ready.
+        mTaskOrganizer.registerOrganizer(WINDOWING_MODE_MULTI_WINDOW);
+        mTaskOrganizer.setInterceptBackPressedOnTaskRoot(true);
         return true;
     }
 
@@ -100,11 +93,7 @@
         if (!isInitialized()) {
             return false;
         }
-        try {
-            TaskOrganizer.unregisterOrganizer(mTaskOrganizer);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Failed to remove task");
-        }
+        mTaskOrganizer.unregisterOrganizer();
         resetTaskInfo();
         return true;
     }
@@ -125,11 +114,7 @@
         }
         WindowContainerTransaction wct = new WindowContainerTransaction();
         wct.setHidden(mTaskToken, false /* hidden */);
-        try {
-            WindowOrganizer.applyTransaction(wct);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Failed to unset hidden in transaction");
-        }
+        WindowOrganizer.applyTransaction(wct);
         // TODO(b/151449487): Only call callback once we enable synchronization
         if (mListener != null) {
             mListener.onTaskVisibilityChanged(getTaskId(), true);
@@ -152,11 +137,7 @@
         }
         WindowContainerTransaction wct = new WindowContainerTransaction();
         wct.setHidden(mTaskToken, true /* hidden */);
-        try {
-            WindowOrganizer.applyTransaction(wct);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Failed to set hidden in transaction");
-        }
+        WindowOrganizer.applyTransaction(wct);
         // TODO(b/151449487): Only call callback once we enable synchronization
         if (mListener != null) {
             mListener.onTaskVisibilityChanged(getTaskId(), false);
@@ -186,12 +167,8 @@
 
         WindowContainerTransaction wct = new WindowContainerTransaction();
         wct.setBounds(mTaskToken, screenBounds);
-        try {
-            // TODO(b/151449487): Enable synchronization
-            WindowOrganizer.applyTransaction(wct);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Failed to set bounds in transaction");
-        }
+        // TODO(b/151449487): Enable synchronization
+        WindowOrganizer.applyTransaction(wct);
     }
 
     /**
@@ -253,8 +230,7 @@
     private class TaskStackListenerImpl extends TaskStackListener {
 
         @Override
-        public void onTaskDescriptionChanged(ActivityManager.RunningTaskInfo taskInfo)
-                throws RemoteException {
+        public void onTaskDescriptionChanged(ActivityManager.RunningTaskInfo taskInfo) {
             if (!isInitialized()) {
                 return;
             }
@@ -266,10 +242,9 @@
         }
     }
 
-    private class TaskOrganizerImpl extends ITaskOrganizer.Stub {
+    private class TaskOrganizerImpl extends TaskOrganizer {
         @Override
-        public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo)
-                throws RemoteException {
+        public void onTaskAppeared(ActivityManager.RunningTaskInfo taskInfo) {
             if (DEBUG) {
                 log("taskAppeared: " + taskInfo.taskId);
             }
@@ -293,8 +268,7 @@
         }
 
         @Override
-        public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo)
-                throws RemoteException {
+        public void onTaskVanished(ActivityManager.RunningTaskInfo taskInfo) {
             if (DEBUG) {
                 log("taskVanished: " + taskInfo.taskId);
             }
@@ -309,14 +283,7 @@
         }
 
         @Override
-        public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo)
-                throws RemoteException {
-            // Do nothing
-        }
-
-        @Override
-        public void onBackPressedOnTaskRoot(ActivityManager.RunningTaskInfo taskInfo)
-                throws RemoteException {
+        public void onBackPressedOnTaskRoot(ActivityManager.RunningTaskInfo taskInfo) {
             if (mListener != null) {
                 mListener.onBackPressedOnTaskRoot(taskInfo.taskId);
             }
diff --git a/core/java/android/app/VirtualDisplayTaskEmbedder.java b/core/java/android/window/VirtualDisplayTaskEmbedder.java
similarity index 97%
rename from core/java/android/app/VirtualDisplayTaskEmbedder.java
rename to core/java/android/window/VirtualDisplayTaskEmbedder.java
index 7ad8f22..1afbfeb 100644
--- a/core/java/android/app/VirtualDisplayTaskEmbedder.java
+++ b/core/java/android/window/VirtualDisplayTaskEmbedder.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.app;
+package android.window;
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL;
@@ -23,6 +23,11 @@
 import static android.view.Display.INVALID_DISPLAY;
 
 import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.app.ActivityOptions;
+import android.app.ActivityTaskManager;
+import android.app.ActivityView;
+import android.app.TaskStackListener;
 import android.content.ComponentName;
 import android.content.Context;
 import android.graphics.Insets;
@@ -72,7 +77,7 @@
      * @param singleTaskInstance whether to apply a single-task constraint to this container,
      *                           only applicable if virtual displays are used
      */
-    VirtualDisplayTaskEmbedder(Context context, VirtualDisplayTaskEmbedder.Host host,
+    public VirtualDisplayTaskEmbedder(Context context, VirtualDisplayTaskEmbedder.Host host,
             boolean singleTaskInstance) {
         super(context, host);
         mSingleTaskInstance = singleTaskInstance;
@@ -243,7 +248,6 @@
         options = super.prepareActivityOptions(options);
         options.setLaunchDisplayId(getDisplayId());
         options.setLaunchWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
-        options.setTaskAlwaysOnTop(true);
         return options;
     }
 
diff --git a/core/java/android/window/IWindowContainer.aidl b/core/java/android/window/WindowContainerToken.aidl
similarity index 62%
copy from core/java/android/window/IWindowContainer.aidl
copy to core/java/android/window/WindowContainerToken.aidl
index f2960f6..f22786b 100644
--- a/core/java/android/window/IWindowContainer.aidl
+++ b/core/java/android/window/WindowContainerToken.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,17 +16,4 @@
 
 package android.window;
 
-import android.view.SurfaceControl;
-
-/**
- * Interface for a window container to communicate with the window manager. This also acts as a
- * token.
- * @hide
- */
-interface IWindowContainer {
-
-    /**
-     * Gets a persistent leash for this container or {@code null}.
-     */
-    SurfaceControl getLeash();
-}
+parcelable WindowContainerToken;
diff --git a/core/java/android/window/WindowContainerToken.java b/core/java/android/window/WindowContainerToken.java
new file mode 100644
index 0000000..dde98da
--- /dev/null
+++ b/core/java/android/window/WindowContainerToken.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2020 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.window;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.view.SurfaceControl;
+import android.window.IWindowContainerToken;
+
+/**
+ * Interface for a window container to communicate with the window manager. This also acts as a
+ * token.
+ * @hide
+ */
+@TestApi
+public final class WindowContainerToken implements Parcelable {
+
+    private final IWindowContainerToken mRealToken;
+
+    /** @hide */
+    public WindowContainerToken(IWindowContainerToken realToken) {
+        mRealToken = realToken;
+    }
+
+    private WindowContainerToken(Parcel in) {
+        mRealToken = IWindowContainerToken.Stub.asInterface(in.readStrongBinder());
+    }
+
+    @Nullable
+    public SurfaceControl getLeash() {
+        try {
+            return mRealToken.getLeash();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** @hide */
+    public IBinder asBinder() {
+        return mRealToken.asBinder();
+    }
+
+    @Override
+    /** @hide */
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeStrongBinder(mRealToken.asBinder());
+    }
+
+    @NonNull
+    public static final Creator<WindowContainerToken> CREATOR =
+            new Creator<WindowContainerToken>() {
+                @Override
+                public WindowContainerToken createFromParcel(Parcel in) {
+                    return new WindowContainerToken(in);
+                }
+
+                @Override
+                public WindowContainerToken[] newArray(int size) {
+                    return new WindowContainerToken[size];
+                }
+            };
+
+    @Override
+    /** @hide */
+    public int describeContents() {
+        return 0;
+    }
+}
diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java
index 483dec6..231e024 100644
--- a/core/java/android/window/WindowContainerTransaction.java
+++ b/core/java/android/window/WindowContainerTransaction.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.TestApi;
 import android.app.WindowConfiguration;
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
@@ -26,7 +27,6 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.ArrayMap;
-import android.window.IWindowContainer;
 import android.view.SurfaceControl;
 
 import java.util.ArrayList;
@@ -39,7 +39,8 @@
  *
  * @hide
  */
-public class WindowContainerTransaction implements Parcelable {
+@TestApi
+public final class WindowContainerTransaction implements Parcelable {
     private final ArrayMap<IBinder, Change> mChanges = new ArrayMap<>();
 
     // Flat list because re-order operations are order-dependent
@@ -47,7 +48,7 @@
 
     public WindowContainerTransaction() {}
 
-    protected WindowContainerTransaction(Parcel in) {
+    private WindowContainerTransaction(Parcel in) {
         in.readMap(mChanges, null /* loader */);
         in.readList(mHierarchyOps, null /* loader */);
     }
@@ -64,7 +65,9 @@
     /**
      * Resize a container.
      */
-    public WindowContainerTransaction setBounds(IWindowContainer container, Rect bounds) {
+    @NonNull
+    public WindowContainerTransaction setBounds(
+            @NonNull WindowContainerToken container,@NonNull Rect bounds) {
         Change chg = getOrCreateChange(container.asBinder());
         chg.mConfiguration.windowConfiguration.setBounds(bounds);
         chg.mConfigSetMask |= ActivityInfo.CONFIG_WINDOW_CONFIGURATION;
@@ -77,7 +80,9 @@
      * app's DisplayInfo. It is derived by subtracting the overlapping portion of the navbar from
      * the full bounds.
      */
-    public WindowContainerTransaction setAppBounds(IWindowContainer container, Rect appBounds) {
+    @NonNull
+    public WindowContainerTransaction setAppBounds(
+            @NonNull WindowContainerToken container,@NonNull Rect appBounds) {
         Change chg = getOrCreateChange(container.asBinder());
         chg.mConfiguration.windowConfiguration.setAppBounds(appBounds);
         chg.mConfigSetMask |= ActivityInfo.CONFIG_WINDOW_CONFIGURATION;
@@ -91,7 +96,9 @@
      * derived by subtracting the overlapping portions of both the statusbar and the navbar from
      * the full bounds.
      */
-    public WindowContainerTransaction setScreenSizeDp(IWindowContainer container, int w, int h) {
+    @NonNull
+    public WindowContainerTransaction setScreenSizeDp(
+            @NonNull WindowContainerToken container, int w, int h) {
         Change chg = getOrCreateChange(container.asBinder());
         chg.mConfiguration.screenWidthDp = w;
         chg.mConfiguration.screenHeightDp = h;
@@ -100,11 +107,12 @@
     }
 
     /**
-     * Notify activies within the hiearchy of a container that they have entered picture-in-picture
+     * Notify activities within the hierarchy of a container that they have entered picture-in-picture
      * mode with the given bounds.
      */
-    public WindowContainerTransaction scheduleFinishEnterPip(IWindowContainer container,
-            Rect bounds) {
+    @NonNull
+    public WindowContainerTransaction scheduleFinishEnterPip(
+            @NonNull WindowContainerToken container,@NonNull Rect bounds) {
         Change chg = getOrCreateChange(container.asBinder());
         chg.mPinnedBounds = new Rect(bounds);
         chg.mChangeMask |= Change.CHANGE_PIP_CALLBACK;
@@ -123,8 +131,9 @@
      * that you can call this, apply the WindowContainer transaction, and then later call
      * dismissPip() to achieve synchronization.
      */
-    public WindowContainerTransaction setBoundsChangeTransaction(IWindowContainer container,
-            SurfaceControl.Transaction t) {
+    @NonNull
+    public WindowContainerTransaction setBoundsChangeTransaction(
+            @NonNull WindowContainerToken container,@NonNull SurfaceControl.Transaction t) {
         Change chg = getOrCreateChange(container.asBinder());
         chg.mBoundsChangeTransaction = t;
         chg.mChangeMask |= Change.CHANGE_BOUNDS_TRANSACTION;
@@ -139,8 +148,9 @@
      *
      * TODO(b/134365562): Can be removed once TaskOrg drives full-screen
      */
-    public WindowContainerTransaction setActivityWindowingMode(IWindowContainer container,
-            int windowingMode) {
+    @NonNull
+    public WindowContainerTransaction setActivityWindowingMode(
+            @NonNull WindowContainerToken container, int windowingMode) {
         Change chg = getOrCreateChange(container.asBinder());
         chg.mActivityWindowingMode = windowingMode;
         return this;
@@ -149,8 +159,9 @@
     /**
      * Sets the windowing mode of the given container.
      */
-    public WindowContainerTransaction setWindowingMode(IWindowContainer container,
-            int windowingMode) {
+    @NonNull
+    public WindowContainerTransaction setWindowingMode(
+            @NonNull WindowContainerToken container, int windowingMode) {
         Change chg = getOrCreateChange(container.asBinder());
         chg.mWindowingMode = windowingMode;
         return this;
@@ -161,7 +172,9 @@
      * child can be focused; however, when {@code true}, it is still possible for children to be
      * non-focusable due to WM policy.
      */
-    public WindowContainerTransaction setFocusable(IWindowContainer container, boolean focusable) {
+    @NonNull
+    public WindowContainerTransaction setFocusable(
+            @NonNull WindowContainerToken container, boolean focusable) {
         Change chg = getOrCreateChange(container.asBinder());
         chg.mFocusable = focusable;
         chg.mChangeMask |= Change.CHANGE_FOCUSABLE;
@@ -173,7 +186,9 @@
      * visibility of the container applies, but when {@code true} the container will be forced
      * to be hidden.
      */
-    public WindowContainerTransaction setHidden(IWindowContainer container, boolean hidden) {
+    @NonNull
+    public WindowContainerTransaction setHidden(
+            @NonNull WindowContainerToken container, boolean hidden) {
         Change chg = getOrCreateChange(container.asBinder());
         chg.mHidden = hidden;
         chg.mChangeMask |= Change.CHANGE_HIDDEN;
@@ -183,8 +198,9 @@
     /**
      * Set the smallestScreenWidth of a container.
      */
-    public WindowContainerTransaction setSmallestScreenWidthDp(IWindowContainer container,
-            int widthDp) {
+    @NonNull
+    public WindowContainerTransaction setSmallestScreenWidthDp(
+            @NonNull WindowContainerToken container, int widthDp) {
         Change cfg = getOrCreateChange(container.asBinder());
         cfg.mConfiguration.smallestScreenWidthDp = widthDp;
         cfg.mConfigSetMask |= ActivityInfo.CONFIG_SMALLEST_SCREEN_SIZE;
@@ -198,8 +214,9 @@
      * @param onTop When {@code true}, the child goes to the top of parent; otherwise it goes to
      *              the bottom.
      */
-    public WindowContainerTransaction reparent(@NonNull IWindowContainer child,
-            @Nullable IWindowContainer parent, boolean onTop) {
+    @NonNull
+    public WindowContainerTransaction reparent(@NonNull WindowContainerToken child,
+            @Nullable WindowContainerToken parent, boolean onTop) {
         mHierarchyOps.add(new HierarchyOp(child.asBinder(),
                 parent == null ? null : parent.asBinder(), onTop));
         return this;
@@ -211,36 +228,43 @@
      * @param onTop When {@code true}, the child goes to the top of parent; otherwise it goes to
      *              the bottom.
      */
-    public WindowContainerTransaction reorder(@NonNull IWindowContainer child, boolean onTop) {
+    @NonNull
+    public WindowContainerTransaction reorder(@NonNull WindowContainerToken child, boolean onTop) {
         mHierarchyOps.add(new HierarchyOp(child.asBinder(), onTop));
         return this;
     }
 
+    /** @hide */
     public Map<IBinder, Change> getChanges() {
         return mChanges;
     }
 
+    /** @hide */
     public List<HierarchyOp> getHierarchyOps() {
         return mHierarchyOps;
     }
 
     @Override
+    @NonNull
     public String toString() {
         return "WindowContainerTransaction { changes = " + mChanges + " hops = " + mHierarchyOps
                 + " }";
     }
 
     @Override
-    public void writeToParcel(Parcel dest, int flags) {
+    /** @hide */
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeMap(mChanges);
         dest.writeList(mHierarchyOps);
     }
 
     @Override
+    /** @hide */
     public int describeContents() {
         return 0;
     }
 
+    @NonNull
     public static final Creator<WindowContainerTransaction> CREATOR =
             new Creator<WindowContainerTransaction>() {
                 @Override
@@ -256,7 +280,6 @@
 
     /**
      * Holds changes on a single WindowContainer including Configuration changes.
-     *
      * @hide
      */
     public static class Change implements Parcelable {
@@ -430,6 +453,7 @@
     /**
      * Holds information about a reparent/reorder operation in the hierarchy. This is separate from
      * Changes because they must be executed in the same order that they are added.
+     * @hide
      */
     public static class HierarchyOp implements Parcelable {
         private final IBinder mContainer;
diff --git a/core/java/android/window/WindowContainerTransactionCallback.java b/core/java/android/window/WindowContainerTransactionCallback.java
new file mode 100644
index 0000000..67cb1e7
--- /dev/null
+++ b/core/java/android/window/WindowContainerTransactionCallback.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2020 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.window;
+
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.view.SurfaceControl;
+
+
+/**
+ * See WindowOrganizer#applyTransaction.
+ * {@hide}
+ */
+@TestApi
+public abstract class WindowContainerTransactionCallback {
+
+    public abstract void onTransactionReady(int id, @NonNull SurfaceControl.Transaction t);
+
+    /** @hide */
+    final IWindowContainerTransactionCallback mInterface =
+            new IWindowContainerTransactionCallback.Stub() {
+        @Override
+        public void onTransactionReady(int id, SurfaceControl.Transaction t) {
+            WindowContainerTransactionCallback.this.onTransactionReady(id, t);
+        }
+    };
+}
diff --git a/core/java/android/window/WindowOrganizer.java b/core/java/android/window/WindowOrganizer.java
index 5590e72..4578271 100644
--- a/core/java/android/window/WindowOrganizer.java
+++ b/core/java/android/window/WindowOrganizer.java
@@ -16,29 +16,32 @@
 
 package android.window;
 
+import android.annotation.NonNull;
 import android.annotation.RequiresPermission;
-import android.app.ActivityManager;
+import android.annotation.TestApi;
 import android.app.ActivityTaskManager;
 import android.os.RemoteException;
 import android.util.Singleton;
 
-import java.util.List;
-
 /**
- * Class for organizing specific types of windows like Tasks and DisplayAreas
+ * Base class for organizing specific types of windows like Tasks and DisplayAreas
  *
  * @hide
  */
+@TestApi
 public class WindowOrganizer {
 
     /**
      * Apply multiple WindowContainer operations at once.
      * @param t The transaction to apply.
-     * @hide
      */
     @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
-    public static void applyTransaction(WindowContainerTransaction t) throws RemoteException {
-        getWindowOrganizerController().applyTransaction(t);
+    public static void applyTransaction(@NonNull WindowContainerTransaction t) {
+        try {
+            getWindowOrganizerController().applyTransaction(t);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
     /**
@@ -49,17 +52,19 @@
      *        WindowContainer transaction will be passed to this callback when ready.
      * @return An ID for the sync operation which will later be passed to transactionReady callback.
      *         This lets the caller differentiate overlapping sync operations.
-     * @hide
      */
     @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
-    public static int applySyncTransaction(WindowContainerTransaction t,
-            IWindowContainerTransactionCallback callback) throws RemoteException {
-        return getWindowOrganizerController().applySyncTransaction(t, callback);
+    public int applySyncTransaction(@NonNull WindowContainerTransaction t,
+            @NonNull WindowContainerTransactionCallback callback) {
+        try {
+            return getWindowOrganizerController().applySyncTransaction(t, callback.mInterface);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
     }
 
-    /** @hide */
     @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
-    private static IWindowOrganizerController getWindowOrganizerController() {
+    static IWindowOrganizerController getWindowOrganizerController() {
         return IWindowOrganizerControllerSingleton.get();
     }
 
@@ -74,138 +79,4 @@
                     }
                 }
             };
-
-    public static class TaskOrganizer {
-
-        /**
-         * Register a TaskOrganizer to manage tasks as they enter the given windowing mode.
-         * If there was already a TaskOrganizer for this windowing mode it will be evicted
-         * and receive taskVanished callbacks in the process.
-         */
-        @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
-        public static void registerOrganizer(ITaskOrganizer organizer, int windowingMode)
-                throws RemoteException {
-            getController().registerTaskOrganizer(organizer, windowingMode);
-        }
-
-        /** Unregisters a previously registered task organizer. */
-        @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
-        public static void unregisterOrganizer(ITaskOrganizer organizer) throws RemoteException {
-            getController().unregisterTaskOrganizer(organizer);
-        }
-
-        /** Creates a persistent root task in WM for a particular windowing-mode. */
-        @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
-        public static ActivityManager.RunningTaskInfo createRootTask(
-                int displayId, int windowingMode) throws RemoteException {
-            return getController().createRootTask(displayId, windowingMode);
-        }
-
-        /** Deletes a persistent root task in WM */
-        @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
-        public static boolean deleteRootTask(IWindowContainer task) throws RemoteException {
-            return getController().deleteRootTask(task);
-        }
-
-        /** Gets direct child tasks (ordered from top-to-bottom) */
-        @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
-        public static List<ActivityManager.RunningTaskInfo> getChildTasks(IWindowContainer parent,
-                int[] activityTypes) throws RemoteException {
-            return getController().getChildTasks(parent, activityTypes);
-        }
-
-        /** Gets all root tasks on a display (ordered from top-to-bottom) */
-        @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
-        public static List<ActivityManager.RunningTaskInfo> getRootTasks(
-                int displayId, int[] activityTypes) throws RemoteException {
-            return getController().getRootTasks(displayId, activityTypes);
-        }
-
-        /** Get the root task which contains the current ime target */
-        @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
-        public static IWindowContainer getImeTarget(int display) throws RemoteException {
-            return getController().getImeTarget(display);
-        }
-
-        /**
-         * Set's the root task to launch new tasks into on a display. {@code null} means no launch
-         * root and thus new tasks just end up directly on the display.
-         */
-        @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
-        public static void setLaunchRoot(int displayId, IWindowContainer root)
-                throws RemoteException {
-            getController().setLaunchRoot(displayId, root);
-        }
-
-        /**
-         * Requests that the given task organizer is notified when back is pressed on the root
-         * activity of one of its controlled tasks.
-         */
-        @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
-        public static void setInterceptBackPressedOnTaskRoot(ITaskOrganizer organizer,
-                boolean interceptBackPressed) throws RemoteException {
-            getController().setInterceptBackPressedOnTaskRoot(organizer, interceptBackPressed);
-        }
-
-        private static ITaskOrganizerController getController() {
-            return ITaskOrganizerControllerSingleton.get();
-        }
-
-        private static final Singleton<ITaskOrganizerController> ITaskOrganizerControllerSingleton =
-                new Singleton<ITaskOrganizerController>() {
-                    @Override
-                    protected ITaskOrganizerController create() {
-                        try {
-                            return getWindowOrganizerController().getTaskOrganizerController();
-                        } catch (RemoteException e) {
-                            return null;
-                        }
-                    }
-                };
-    }
-
-    /** Class for organizing display areas. */
-    public static class DisplayAreaOrganizer {
-
-        public static final int FEATURE_UNDEFINED = -1;
-        public static final int FEATURE_SYSTEM_FIRST = 0;
-        // The Root display area on a display
-        public static final int FEATURE_ROOT = FEATURE_SYSTEM_FIRST;
-        // Display area hosting the task container.
-        public static final int FEATURE_TASK_CONTAINER = FEATURE_SYSTEM_FIRST + 1;
-        // Display area hosting non-activity window tokens.
-        public static final int FEATURE_WINDOW_TOKENS = FEATURE_SYSTEM_FIRST + 2;
-
-        public static final int FEATURE_SYSTEM_LAST = 10_000;
-
-        // Vendor specific display area definition can start with this value.
-        public static final int FEATURE_VENDOR_FIRST = FEATURE_SYSTEM_LAST + 1;
-
-        /** @hide */
-        @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
-        public static void registerOrganizer(
-                IDisplayAreaOrganizer organizer, int displayAreaFeature) throws RemoteException {
-            getController().registerOrganizer(organizer, displayAreaFeature);
-        }
-
-        /** @hide */
-        private static IDisplayAreaOrganizerController getController() {
-            return IDisplayAreaOrganizerControllerSingleton.get();
-        }
-
-        private static final Singleton<IDisplayAreaOrganizerController>
-                IDisplayAreaOrganizerControllerSingleton =
-                new Singleton<IDisplayAreaOrganizerController>() {
-                    @Override
-                    protected IDisplayAreaOrganizerController create() {
-                        try {
-                            return getWindowOrganizerController()
-                                    .getDisplayAreaOrganizerController();
-                        } catch (RemoteException e) {
-                            return null;
-                        }
-                    }
-                };
-
-    }
 }
diff --git a/core/java/com/android/internal/inputmethod/CancellationGroup.java b/core/java/com/android/internal/inputmethod/CancellationGroup.java
new file mode 100644
index 0000000..09c9d12
--- /dev/null
+++ b/core/java/com/android/internal/inputmethod/CancellationGroup.java
@@ -0,0 +1,348 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.inputmethod;
+
+import android.annotation.AnyThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.ArrayList;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A utility class, which works as both a factory class of completable objects and a cancellation
+ * signal to cancel all the completable objects created by this object.
+ */
+public final class CancellationGroup {
+    private final Object mLock = new Object();
+
+    /**
+     * List of {@link CountDownLatch}, which can be used to propagate {@link #cancelAll()} to
+     * completable objects.
+     *
+     * <p>This will be lazily instantiated to avoid unnecessary object allocations.</p>
+     */
+    @Nullable
+    @GuardedBy("mLock")
+    private ArrayList<CountDownLatch> mLatchList = null;
+
+    @GuardedBy("mLock")
+    private boolean mCanceled = false;
+
+    /**
+     * An inner class to consolidate completable object types supported by
+     * {@link CancellationGroup}.
+     */
+    public static final class Completable {
+
+        /**
+         * Not intended to be instantiated.
+         */
+        private Completable() {
+        }
+
+        /**
+         * Base class of all the completable types supported by {@link CancellationGroup}.
+         */
+        protected static class ValueBase {
+            /**
+             * {@link CountDownLatch} to be signaled to unblock {@link #await(int, TimeUnit)}.
+             */
+            private final CountDownLatch mLatch = new CountDownLatch(1);
+
+            /**
+             * {@link CancellationGroup} to which this completable object belongs.
+             */
+            @NonNull
+            private final CancellationGroup mParentGroup;
+
+            /**
+             * Lock {@link Object} to guard complete operations within this class.
+             */
+            protected final Object mValueLock = new Object();
+
+            /**
+             * {@code true} after {@link #onComplete()} gets called.
+             */
+            @GuardedBy("mValueLock")
+            protected boolean mHasValue = false;
+
+            /**
+             * Base constructor.
+             *
+             * @param parentGroup {@link CancellationGroup} to which this completable object
+             *                    belongs.
+             */
+            protected ValueBase(@NonNull CancellationGroup parentGroup) {
+                mParentGroup = parentGroup;
+            }
+
+            /**
+             * @return {@link true} if {@link #onComplete()} gets called already.
+             */
+            @AnyThread
+            public boolean hasValue() {
+                synchronized (mValueLock) {
+                    return mHasValue;
+                }
+            }
+
+            /**
+             * Called by subclasses to signale {@link #mLatch}.
+             */
+            @AnyThread
+            protected void onComplete() {
+                mLatch.countDown();
+            }
+
+            /**
+             * Blocks the calling thread until at least one of the following conditions is met.
+             *
+             * <p>
+             *     <ol>
+             *         <li>This object becomes ready to return the value.</li>
+             *         <li>{@link CancellationGroup#cancelAll()} gets called.</li>
+             *         <li>The given timeout period has passed.</li>
+             *     </ol>
+             * </p>
+             *
+             * <p>The caller can distinguish the case 1 and case 2 by calling {@link #hasValue()}.
+             * Note that the return value of {@link #hasValue()} can change from {@code false} to
+             * {@code true} at any time, even after this methods finishes with returning
+             * {@code true}.</p>
+             *
+             * @param timeout length of the timeout.
+             * @param timeUnit unit of {@code timeout}.
+             * @return {@code false} if and only if the given timeout period has passed. Otherwise
+             *         {@code true}.
+             */
+            @AnyThread
+            public boolean await(int timeout, @NonNull TimeUnit timeUnit) {
+                if (!mParentGroup.registerLatch(mLatch)) {
+                    // Already canceled when this method gets called.
+                    return false;
+                }
+                try {
+                    return mLatch.await(timeout, timeUnit);
+                } catch (InterruptedException e) {
+                    return true;
+                } finally {
+                    mParentGroup.unregisterLatch(mLatch);
+                }
+            }
+        }
+
+        /**
+         * Completable object of integer primitive.
+         */
+        public static final class Int extends ValueBase {
+            @GuardedBy("mValueLock")
+            private int mValue = 0;
+
+            private Int(@NonNull CancellationGroup factory) {
+                super(factory);
+            }
+
+            /**
+             * Notify when a value is set to this completable object.
+             *
+             * @param value value to be set.
+             */
+            @AnyThread
+            void onComplete(int value) {
+                synchronized (mValueLock) {
+                    if (mHasValue) {
+                        throw new UnsupportedOperationException(
+                                "onComplete() cannot be called multiple times");
+                    }
+                    mValue = value;
+                    mHasValue = true;
+                }
+                onComplete();
+            }
+
+            /**
+             * @return value associated with this object.
+             * @throws UnsupportedOperationException when called while {@link #hasValue()} returns
+             *                                       {@code false}.
+             */
+            @AnyThread
+            public int getValue() {
+                synchronized (mValueLock) {
+                    if (!mHasValue) {
+                        throw new UnsupportedOperationException(
+                                "getValue() is allowed only if hasValue() returns true");
+                    }
+                    return mValue;
+                }
+            }
+        }
+
+        /**
+         * Base class of completable object types.
+         *
+         * @param <T> type associated with this completable object.
+         */
+        public static class Values<T> extends ValueBase {
+            @GuardedBy("mValueLock")
+            @Nullable
+            private T mValue = null;
+
+            protected Values(@NonNull CancellationGroup factory) {
+                super(factory);
+            }
+
+            /**
+             * Notify when a value is set to this completable value object.
+             *
+             * @param value value to be set.
+             */
+            @AnyThread
+            void onComplete(@Nullable T value) {
+                synchronized (mValueLock) {
+                    if (mHasValue) {
+                        throw new UnsupportedOperationException(
+                                "onComplete() cannot be called multiple times");
+                    }
+                    mValue = value;
+                    mHasValue = true;
+                }
+                onComplete();
+            }
+
+            /**
+             * @return value associated with this object.
+             * @throws UnsupportedOperationException when called while {@link #hasValue()} returns
+             *                                       {@code false}.
+             */
+            @AnyThread
+            @Nullable
+            public T getValue() {
+                synchronized (mValueLock) {
+                    if (!mHasValue) {
+                        throw new UnsupportedOperationException(
+                                "getValue() is allowed only if hasValue() returns true");
+                    }
+                    return mValue;
+                }
+            }
+        }
+
+        /**
+         * Completable object of {@link java.lang.CharSequence}.
+         */
+        public static final class CharSequence extends Values<java.lang.CharSequence> {
+            private CharSequence(@NonNull CancellationGroup factory) {
+                super(factory);
+            }
+        }
+
+        /**
+         * Completable object of {@link android.view.inputmethod.ExtractedText}.
+         */
+        public static final class ExtractedText
+                extends Values<android.view.inputmethod.ExtractedText> {
+            private ExtractedText(@NonNull CancellationGroup factory) {
+                super(factory);
+            }
+        }
+    }
+
+    /**
+     * @return an instance of {@link Completable.Int} that is associated with this
+     *         {@link CancellationGroup}.
+     */
+    @AnyThread
+    public Completable.Int createCompletableInt() {
+        return new Completable.Int(this);
+    }
+
+    /**
+     * @return an instance of {@link Completable.CharSequence} that is associated with this
+     *         {@link CancellationGroup}.
+     */
+    @AnyThread
+    public Completable.CharSequence createCompletableCharSequence() {
+        return new Completable.CharSequence(this);
+    }
+
+    /**
+     * @return an instance of {@link Completable.ExtractedText} that is associated with this
+     *         {@link CancellationGroup}.
+     */
+    @AnyThread
+    public Completable.ExtractedText createCompletableExtractedText() {
+        return new Completable.ExtractedText(this);
+    }
+
+    @AnyThread
+    private boolean registerLatch(@NonNull CountDownLatch latch) {
+        synchronized (mLock) {
+            if (mCanceled) {
+                return false;
+            }
+            if (mLatchList == null) {
+                // Set the initial capacity to 1 with an assumption that usually there is up to 1
+                // on-going operation.
+                mLatchList = new ArrayList<>(1);
+            }
+            mLatchList.add(latch);
+            return true;
+        }
+    }
+
+    @AnyThread
+    private void unregisterLatch(@NonNull CountDownLatch latch) {
+        synchronized (mLock) {
+            if (mLatchList != null) {
+                mLatchList.remove(latch);
+            }
+        }
+    }
+
+    /**
+     * Cancel all the completable objects created from this {@link CancellationGroup}.
+     *
+     * <p>Secondary calls will be silently ignored.</p>
+     */
+    @AnyThread
+    public void cancelAll() {
+        synchronized (mLock) {
+            if (!mCanceled) {
+                mCanceled = true;
+                if (mLatchList != null) {
+                    mLatchList.forEach(CountDownLatch::countDown);
+                    mLatchList.clear();
+                    mLatchList = null;
+                }
+            }
+        }
+    }
+
+    /**
+     * @return {@code true} if {@link #cancelAll()} is already called. {@code false} otherwise.
+     */
+    @AnyThread
+    public boolean isCanceled() {
+        synchronized (mLock) {
+            return mCanceled;
+        }
+    }
+}
diff --git a/core/java/com/android/internal/inputmethod/ICharSequenceResultCallback.aidl b/core/java/com/android/internal/inputmethod/ICharSequenceResultCallback.aidl
new file mode 100644
index 0000000..da56fd0
--- /dev/null
+++ b/core/java/com/android/internal/inputmethod/ICharSequenceResultCallback.aidl
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.inputmethod;
+
+oneway interface ICharSequenceResultCallback {
+    void onResult(in CharSequence result);
+}
diff --git a/core/java/com/android/internal/inputmethod/IExtractedTextResultCallback.aidl b/core/java/com/android/internal/inputmethod/IExtractedTextResultCallback.aidl
new file mode 100644
index 0000000..b603f6a
--- /dev/null
+++ b/core/java/com/android/internal/inputmethod/IExtractedTextResultCallback.aidl
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.inputmethod;
+
+import android.view.inputmethod.ExtractedText;
+
+oneway interface IExtractedTextResultCallback {
+    void onResult(in ExtractedText result);
+}
diff --git a/core/java/com/android/internal/inputmethod/IIntResultCallback.aidl b/core/java/com/android/internal/inputmethod/IIntResultCallback.aidl
new file mode 100644
index 0000000..bc5ed0d
--- /dev/null
+++ b/core/java/com/android/internal/inputmethod/IIntResultCallback.aidl
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.inputmethod;
+
+oneway interface IIntResultCallback {
+    void onResult(int result);
+}
diff --git a/core/java/com/android/internal/inputmethod/ResultCallbacks.java b/core/java/com/android/internal/inputmethod/ResultCallbacks.java
new file mode 100644
index 0000000..44a8a83
--- /dev/null
+++ b/core/java/com/android/internal/inputmethod/ResultCallbacks.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.inputmethod;
+
+import android.annotation.AnyThread;
+import android.annotation.BinderThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import java.lang.ref.WeakReference;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Defines a set of factory methods to create {@link android.os.IBinder}-based callbacks that are
+ * associated with completable objects defined in {@link CancellationGroup.Completable}.
+ */
+public final class ResultCallbacks {
+
+    /**
+     * Not intended to be instantiated.
+     */
+    private ResultCallbacks() {
+    }
+
+    @AnyThread
+    @Nullable
+    private static <T> T unwrap(@NonNull AtomicReference<WeakReference<T>> atomicRef) {
+        final WeakReference<T> ref = atomicRef.getAndSet(null);
+        if (ref == null) {
+            // Double-call is guaranteed to be ignored here.
+            return null;
+        }
+        final T value = ref.get();
+        ref.clear();
+        return value;
+    }
+
+    /**
+     * Creates {@link IIntResultCallback.Stub} that is to set
+     * {@link CancellationGroup.Completable.Int} when receiving the result.
+     *
+     * @param value {@link CancellationGroup.Completable.Int} to be set when receiving the result.
+     * @return {@link IIntResultCallback.Stub} that can be passed as a binder IPC parameter.
+     */
+    @AnyThread
+    public static IIntResultCallback.Stub of(@NonNull CancellationGroup.Completable.Int value) {
+        final AtomicReference<WeakReference<CancellationGroup.Completable.Int>>
+                atomicRef = new AtomicReference<>(new WeakReference<>(value));
+
+        return new IIntResultCallback.Stub() {
+            @BinderThread
+            @Override
+            public void onResult(int result) {
+                final CancellationGroup.Completable.Int value = unwrap(atomicRef);
+                if (value == null) {
+                    return;
+                }
+                value.onComplete(result);
+            }
+        };
+    }
+
+    /**
+     * Creates {@link ICharSequenceResultCallback.Stub} that is to set
+     * {@link CancellationGroup.Completable.CharSequence} when receiving the result.
+     *
+     * @param value {@link CancellationGroup.Completable.CharSequence} to be set when receiving the
+     *              result.
+     * @return {@link ICharSequenceResultCallback.Stub} that can be passed as a binder IPC
+     *         parameter.
+     */
+    @AnyThread
+    public static ICharSequenceResultCallback.Stub of(
+            @NonNull CancellationGroup.Completable.CharSequence value) {
+        final AtomicReference<WeakReference<CancellationGroup.Completable.CharSequence>> atomicRef =
+                new AtomicReference<>(new WeakReference<>(value));
+
+        return new ICharSequenceResultCallback.Stub() {
+            @BinderThread
+            @Override
+            public void onResult(CharSequence result) {
+                final CancellationGroup.Completable.CharSequence value = unwrap(atomicRef);
+                if (value == null) {
+                    return;
+                }
+                value.onComplete(result);
+            }
+        };
+    }
+
+    /**
+     * Creates {@link IExtractedTextResultCallback.Stub} that is to set
+     * {@link CancellationGroup.Completable.ExtractedText} when receiving the result.
+     *
+     * @param value {@link CancellationGroup.Completable.ExtractedText} to be set when receiving the
+     *              result.
+     * @return {@link IExtractedTextResultCallback.Stub} that can be passed as a binder IPC
+     *         parameter.
+     */
+    @AnyThread
+    public static IExtractedTextResultCallback.Stub of(
+            @NonNull CancellationGroup.Completable.ExtractedText value) {
+        final AtomicReference<WeakReference<CancellationGroup.Completable.ExtractedText>>
+                atomicRef = new AtomicReference<>(new WeakReference<>(value));
+
+        return new IExtractedTextResultCallback.Stub() {
+            @BinderThread
+            @Override
+            public void onResult(android.view.inputmethod.ExtractedText result) {
+                final CancellationGroup.Completable.ExtractedText value = unwrap(atomicRef);
+                if (value == null) {
+                    return;
+                }
+                value.onComplete(result);
+            }
+        };
+    }
+}
diff --git a/core/java/com/android/internal/view/IInputConnectionWrapper.java b/core/java/com/android/internal/view/IInputConnectionWrapper.java
index 6278d4a3..9257c6d 100644
--- a/core/java/com/android/internal/view/IInputConnectionWrapper.java
+++ b/core/java/com/android/internal/view/IInputConnectionWrapper.java
@@ -29,6 +29,7 @@
 import android.view.KeyEvent;
 import android.view.inputmethod.CompletionInfo;
 import android.view.inputmethod.CorrectionInfo;
+import android.view.inputmethod.ExtractedText;
 import android.view.inputmethod.ExtractedTextRequest;
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputConnectionInspector;
@@ -36,6 +37,9 @@
 import android.view.inputmethod.InputContentInfo;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.inputmethod.ICharSequenceResultCallback;
+import com.android.internal.inputmethod.IExtractedTextResultCallback;
+import com.android.internal.inputmethod.IIntResultCallback;
 import com.android.internal.os.SomeArgs;
 
 public abstract class IInputConnectionWrapper extends IInputContext.Stub {
@@ -111,28 +115,31 @@
 
     abstract protected boolean isActive();
 
-    public void getTextAfterCursor(int length, int flags, int seq, IInputContextCallback callback) {
-        dispatchMessage(obtainMessageIISC(DO_GET_TEXT_AFTER_CURSOR, length, flags, seq, callback));
-    }
-    
-    public void getTextBeforeCursor(int length, int flags, int seq, IInputContextCallback callback) {
-        dispatchMessage(obtainMessageIISC(DO_GET_TEXT_BEFORE_CURSOR, length, flags, seq, callback));
+    public void getTextAfterCursor(int length, int flags, ICharSequenceResultCallback callback) {
+        dispatchMessage(mH.obtainMessage(DO_GET_TEXT_AFTER_CURSOR, length, flags, callback));
     }
 
-    public void getSelectedText(int flags, int seq, IInputContextCallback callback) {
-        dispatchMessage(obtainMessageISC(DO_GET_SELECTED_TEXT, flags, seq, callback));
+    public void getTextBeforeCursor(int length, int flags, ICharSequenceResultCallback callback) {
+        dispatchMessage(mH.obtainMessage(DO_GET_TEXT_BEFORE_CURSOR, length, flags, callback));
     }
 
-    public void getCursorCapsMode(int reqModes, int seq, IInputContextCallback callback) {
-        dispatchMessage(obtainMessageISC(DO_GET_CURSOR_CAPS_MODE, reqModes, seq, callback));
+    public void getSelectedText(int flags, ICharSequenceResultCallback callback) {
+        dispatchMessage(mH.obtainMessage(DO_GET_SELECTED_TEXT, flags, 0 /* unused */, callback));
     }
 
-    public void getExtractedText(ExtractedTextRequest request,
-            int flags, int seq, IInputContextCallback callback) {
-        dispatchMessage(obtainMessageIOSC(DO_GET_EXTRACTED_TEXT, flags,
-                request, seq, callback));
+    public void getCursorCapsMode(int reqModes, IIntResultCallback callback) {
+        dispatchMessage(
+                mH.obtainMessage(DO_GET_CURSOR_CAPS_MODE, reqModes, 0 /* unused */, callback));
     }
-    
+
+    public void getExtractedText(ExtractedTextRequest request, int flags,
+            IExtractedTextResultCallback callback) {
+        final SomeArgs args = SomeArgs.obtain();
+        args.arg1 = request;
+        args.arg2 = callback;
+        dispatchMessage(mH.obtainMessage(DO_GET_EXTRACTED_TEXT, flags, 0 /* unused */, args));
+    }
+
     public void commitText(CharSequence text, int newCursorPosition) {
         dispatchMessage(obtainMessageIO(DO_COMMIT_TEXT, newCursorPosition, text));
     }
@@ -199,10 +206,9 @@
         dispatchMessage(obtainMessageOO(DO_PERFORM_PRIVATE_COMMAND, action, data));
     }
 
-    public void requestUpdateCursorAnchorInfo(int cursorUpdateMode, int seq,
-            IInputContextCallback callback) {
-        dispatchMessage(obtainMessageISC(DO_REQUEST_UPDATE_CURSOR_ANCHOR_INFO, cursorUpdateMode,
-                seq, callback));
+    public void requestUpdateCursorAnchorInfo(int cursorUpdateMode, IIntResultCallback callback) {
+        dispatchMessage(mH.obtainMessage(DO_REQUEST_UPDATE_CURSOR_ANCHOR_INFO, cursorUpdateMode,
+                0 /* unused */, callback));
     }
 
     public void closeConnection() {
@@ -210,9 +216,12 @@
     }
 
     public void commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts,
-            int seq, IInputContextCallback callback) {
-        dispatchMessage(obtainMessageIOOSC(DO_COMMIT_CONTENT, flags, inputContentInfo, opts, seq,
-                callback));
+            IIntResultCallback callback) {
+        final SomeArgs args = SomeArgs.obtain();
+        args.arg1 = inputContentInfo;
+        args.arg2 = opts;
+        args.arg3 = callback;
+        dispatchMessage(mH.obtainMessage(DO_COMMIT_CONTENT, flags, 0 /* unused */, args));
     }
 
     void dispatchMessage(Message msg) {
@@ -231,100 +240,97 @@
     void executeMessage(Message msg) {
         switch (msg.what) {
             case DO_GET_TEXT_AFTER_CURSOR: {
-                SomeArgs args = (SomeArgs)msg.obj;
+                final ICharSequenceResultCallback callback = (ICharSequenceResultCallback) msg.obj;
+                final InputConnection ic = getInputConnection();
+                final CharSequence result;
+                if (ic == null || !isActive()) {
+                    Log.w(TAG, "getTextAfterCursor on inactive InputConnection");
+                    result = null;
+                } else {
+                    result = ic.getTextAfterCursor(msg.arg1, msg.arg2);
+                }
                 try {
-                    final IInputContextCallback callback = (IInputContextCallback) args.arg6;
-                    final int callbackSeq = args.argi6;
-                    InputConnection ic = getInputConnection();
-                    if (ic == null || !isActive()) {
-                        Log.w(TAG, "getTextAfterCursor on inactive InputConnection");
-                        callback.setTextAfterCursor(null, callbackSeq);
-                        return;
-                    }
-                    callback.setTextAfterCursor(ic.getTextAfterCursor(
-                            msg.arg1, msg.arg2), callbackSeq);
+                    callback.onResult(result);
                 } catch (RemoteException e) {
-                    Log.w(TAG, "Got RemoteException calling setTextAfterCursor", e);
-                } finally {
-                    args.recycle();
+                    Log.w(TAG, "Failed to return the result to getTextAfterCursor()."
+                            + " result=" + result, e);
                 }
                 return;
             }
             case DO_GET_TEXT_BEFORE_CURSOR: {
-                SomeArgs args = (SomeArgs)msg.obj;
+                final ICharSequenceResultCallback callback = (ICharSequenceResultCallback) msg.obj;
+                final InputConnection ic = getInputConnection();
+                final CharSequence result;
+                if (ic == null || !isActive()) {
+                    Log.w(TAG, "getTextBeforeCursor on inactive InputConnection");
+                    result = null;
+                } else {
+                    result = ic.getTextBeforeCursor(msg.arg1, msg.arg2);
+                }
                 try {
-                    final IInputContextCallback callback = (IInputContextCallback) args.arg6;
-                    final int callbackSeq = args.argi6;
-                    InputConnection ic = getInputConnection();
-                    if (ic == null || !isActive()) {
-                        Log.w(TAG, "getTextBeforeCursor on inactive InputConnection");
-                        callback.setTextBeforeCursor(null, callbackSeq);
-                        return;
-                    }
-                    callback.setTextBeforeCursor(ic.getTextBeforeCursor(
-                            msg.arg1, msg.arg2), callbackSeq);
+                    callback.onResult(result);
                 } catch (RemoteException e) {
-                    Log.w(TAG, "Got RemoteException calling setTextBeforeCursor", e);
-                } finally {
-                    args.recycle();
+                    Log.w(TAG, "Failed to return the result to getTextBeforeCursor()."
+                            + " result=" + result, e);
                 }
                 return;
             }
             case DO_GET_SELECTED_TEXT: {
-                SomeArgs args = (SomeArgs)msg.obj;
+                final ICharSequenceResultCallback callback = (ICharSequenceResultCallback) msg.obj;
+                final InputConnection ic = getInputConnection();
+                final CharSequence result;
+                if (ic == null || !isActive()) {
+                    Log.w(TAG, "getSelectedText on inactive InputConnection");
+                    result = null;
+                } else {
+                    result = ic.getSelectedText(msg.arg1);
+                }
                 try {
-                    final IInputContextCallback callback = (IInputContextCallback) args.arg6;
-                    final int callbackSeq = args.argi6;
-                    InputConnection ic = getInputConnection();
-                    if (ic == null || !isActive()) {
-                        Log.w(TAG, "getSelectedText on inactive InputConnection");
-                        callback.setSelectedText(null, callbackSeq);
-                        return;
-                    }
-                    callback.setSelectedText(ic.getSelectedText(
-                            msg.arg1), callbackSeq);
+                    callback.onResult(result);
                 } catch (RemoteException e) {
-                    Log.w(TAG, "Got RemoteException calling setSelectedText", e);
-                } finally {
-                    args.recycle();
+                    Log.w(TAG, "Failed to return the result to getSelectedText()."
+                            + " result=" + result, e);
                 }
                 return;
             }
             case DO_GET_CURSOR_CAPS_MODE: {
-                SomeArgs args = (SomeArgs)msg.obj;
+                final IIntResultCallback callback = (IIntResultCallback) msg.obj;
+                final InputConnection ic = getInputConnection();
+                final int result;
+                if (ic == null || !isActive()) {
+                    Log.w(TAG, "getCursorCapsMode on inactive InputConnection");
+                    result = 0;
+                } else {
+                    result = ic.getCursorCapsMode(msg.arg1);
+                }
                 try {
-                    final IInputContextCallback callback = (IInputContextCallback) args.arg6;
-                    final int callbackSeq = args.argi6;
-                    InputConnection ic = getInputConnection();
-                    if (ic == null || !isActive()) {
-                        Log.w(TAG, "getCursorCapsMode on inactive InputConnection");
-                        callback.setCursorCapsMode(0, callbackSeq);
-                        return;
-                    }
-                    callback.setCursorCapsMode(ic.getCursorCapsMode(msg.arg1),
-                            callbackSeq);
+                    callback.onResult(result);
                 } catch (RemoteException e) {
-                    Log.w(TAG, "Got RemoteException calling setCursorCapsMode", e);
-                } finally {
-                    args.recycle();
+                    Log.w(TAG, "Failed to return the result to getCursorCapsMode()."
+                            + " result=" + result, e);
                 }
                 return;
             }
             case DO_GET_EXTRACTED_TEXT: {
-                SomeArgs args = (SomeArgs)msg.obj;
+                final SomeArgs args = (SomeArgs) msg.obj;
                 try {
-                    final IInputContextCallback callback = (IInputContextCallback) args.arg6;
-                    final int callbackSeq = args.argi6;
-                    InputConnection ic = getInputConnection();
+                    final ExtractedTextRequest request = (ExtractedTextRequest) args.arg1;
+                    final IExtractedTextResultCallback callback =
+                            (IExtractedTextResultCallback) args.arg2;
+                    final InputConnection ic = getInputConnection();
+                    final ExtractedText result;
                     if (ic == null || !isActive()) {
                         Log.w(TAG, "getExtractedText on inactive InputConnection");
-                        callback.setExtractedText(null, callbackSeq);
-                        return;
+                        result = null;
+                    } else {
+                        result = ic.getExtractedText(request, msg.arg1);
                     }
-                    callback.setExtractedText(ic.getExtractedText(
-                            (ExtractedTextRequest)args.arg1, msg.arg1), callbackSeq);
-                } catch (RemoteException e) {
-                    Log.w(TAG, "Got RemoteException calling setExtractedText", e);
+                    try {
+                        callback.onResult(result);
+                    } catch (RemoteException e) {
+                        Log.w(TAG, "Failed to return the result to getExtractedText()."
+                                + " result=" + result, e);
+                    }
                 } finally {
                     args.recycle();
                 }
@@ -494,22 +500,20 @@
                 return;
             }
             case DO_REQUEST_UPDATE_CURSOR_ANCHOR_INFO: {
-                SomeArgs args = (SomeArgs)msg.obj;
+                final IIntResultCallback callback = (IIntResultCallback) msg.obj;
+                final InputConnection ic = getInputConnection();
+                final boolean result;
+                if (ic == null || !isActive()) {
+                    Log.w(TAG, "requestCursorAnchorInfo on inactive InputConnection");
+                    result = false;
+                } else {
+                    result = ic.requestCursorUpdates(msg.arg1);
+                }
                 try {
-                    final IInputContextCallback callback = (IInputContextCallback) args.arg6;
-                    final int callbackSeq = args.argi6;
-                    InputConnection ic = getInputConnection();
-                    if (ic == null || !isActive()) {
-                        Log.w(TAG, "requestCursorAnchorInfo on inactive InputConnection");
-                        callback.setRequestUpdateCursorAnchorInfoResult(false, callbackSeq);
-                        return;
-                    }
-                    callback.setRequestUpdateCursorAnchorInfoResult(
-                            ic.requestCursorUpdates(msg.arg1), callbackSeq);
+                    callback.onResult(result ? 1 : 0);
                 } catch (RemoteException e) {
-                    Log.w(TAG, "Got RemoteException calling requestCursorAnchorInfo", e);
-                } finally {
-                    args.recycle();
+                    Log.w(TAG, "Failed to return the result to requestCursorUpdates()."
+                            + " result=" + result, e);
                 }
                 return;
             }
@@ -547,26 +551,28 @@
                 final int flags = msg.arg1;
                 SomeArgs args = (SomeArgs) msg.obj;
                 try {
-                    final IInputContextCallback callback = (IInputContextCallback) args.arg6;
-                    final int callbackSeq = args.argi6;
-                    InputConnection ic = getInputConnection();
+                    final IIntResultCallback callback = (IIntResultCallback) args.arg3;
+                    final InputConnection ic = getInputConnection();
+                    final boolean result;
                     if (ic == null || !isActive()) {
                         Log.w(TAG, "commitContent on inactive InputConnection");
-                        callback.setCommitContentResult(false, callbackSeq);
-                        return;
+                        result = false;
+                    } else {
+                        final InputContentInfo inputContentInfo = (InputContentInfo) args.arg1;
+                        if (inputContentInfo == null || !inputContentInfo.validate()) {
+                            Log.w(TAG, "commitContent with invalid inputContentInfo="
+                                    + inputContentInfo);
+                            result = false;
+                        } else {
+                            result = ic.commitContent(inputContentInfo, flags, (Bundle) args.arg2);
+                        }
                     }
-                    final InputContentInfo inputContentInfo = (InputContentInfo) args.arg1;
-                    if (inputContentInfo == null || !inputContentInfo.validate()) {
-                        Log.w(TAG, "commitContent with invalid inputContentInfo="
-                                + inputContentInfo);
-                        callback.setCommitContentResult(false, callbackSeq);
-                        return;
+                    try {
+                        callback.onResult(result ? 1 : 0);
+                    } catch (RemoteException e) {
+                        Log.w(TAG, "Failed to return the result to commitContent()."
+                                + " result=" + result, e);
                     }
-                    final boolean result =
-                            ic.commitContent(inputContentInfo, flags, (Bundle) args.arg2);
-                    callback.setCommitContentResult(result, callbackSeq);
-                } catch (RemoteException e) {
-                    Log.w(TAG, "Got RemoteException calling commitContent", e);
                 } finally {
                     args.recycle();
                 }
@@ -588,40 +594,6 @@
         return mH.obtainMessage(what, 0, 0, arg1);
     }
 
-    Message obtainMessageISC(int what, int arg1, int callbackSeq, IInputContextCallback callback) {
-        final SomeArgs args = SomeArgs.obtain();
-        args.arg6 = callback;
-        args.argi6 = callbackSeq;
-        return mH.obtainMessage(what, arg1, 0, args);
-    }
-
-    Message obtainMessageIISC(int what, int arg1, int arg2, int callbackSeq,
-            IInputContextCallback callback) {
-        final SomeArgs args = SomeArgs.obtain();
-        args.arg6 = callback;
-        args.argi6 = callbackSeq;
-        return mH.obtainMessage(what, arg1, arg2, args);
-    }
-
-    Message obtainMessageIOOSC(int what, int arg1, Object objArg1, Object objArg2, int callbackSeq,
-            IInputContextCallback callback) {
-        final SomeArgs args = SomeArgs.obtain();
-        args.arg1 = objArg1;
-        args.arg2 = objArg2;
-        args.arg6 = callback;
-        args.argi6 = callbackSeq;
-        return mH.obtainMessage(what, arg1, 0, args);
-    }
-
-    Message obtainMessageIOSC(int what, int arg1, Object arg2, int callbackSeq,
-            IInputContextCallback callback) {
-        final SomeArgs args = SomeArgs.obtain();
-        args.arg1 = arg2;
-        args.arg6 = callback;
-        args.argi6 = callbackSeq;
-        return mH.obtainMessage(what, arg1, 0, args);
-    }
-
     Message obtainMessageIO(int what, int arg1, Object arg2) {
         return mH.obtainMessage(what, arg1, 0, arg2);
     }
diff --git a/core/java/com/android/internal/view/IInputContext.aidl b/core/java/com/android/internal/view/IInputContext.aidl
index c227991..86f1293 100644
--- a/core/java/com/android/internal/view/IInputContext.aidl
+++ b/core/java/com/android/internal/view/IInputContext.aidl
@@ -23,7 +23,9 @@
 import android.view.inputmethod.ExtractedTextRequest;
 import android.view.inputmethod.InputContentInfo;
 
-import com.android.internal.view.IInputContextCallback;
+import com.android.internal.inputmethod.ICharSequenceResultCallback;
+import com.android.internal.inputmethod.IExtractedTextResultCallback;
+import com.android.internal.inputmethod.IIntResultCallback;
 
 /**
  * Interface from an input method to the application, allowing it to perform
@@ -31,14 +33,14 @@
  * {@hide}
  */
  oneway interface IInputContext {
-    void getTextBeforeCursor(int length, int flags, int seq, IInputContextCallback callback); 
+    void getTextBeforeCursor(int length, int flags, ICharSequenceResultCallback callback);
 
-    void getTextAfterCursor(int length, int flags, int seq, IInputContextCallback callback);
-    
-    void getCursorCapsMode(int reqModes, int seq, IInputContextCallback callback);
-    
-    void getExtractedText(in ExtractedTextRequest request, int flags, int seq,
-            IInputContextCallback callback);
+    void getTextAfterCursor(int length, int flags, ICharSequenceResultCallback callback);
+
+    void getCursorCapsMode(int reqModes, IIntResultCallback callback);
+
+    void getExtractedText(in ExtractedTextRequest request, int flags,
+            IExtractedTextResultCallback callback);
 
     void deleteSurroundingText(int beforeLength, int afterLength);
     void deleteSurroundingTextInCodePoints(int beforeLength, int afterLength);
@@ -71,11 +73,10 @@
 
     void setComposingRegion(int start, int end);
 
-    void getSelectedText(int flags, int seq, IInputContextCallback callback);
+    void getSelectedText(int flags, ICharSequenceResultCallback callback);
 
-    void requestUpdateCursorAnchorInfo(int cursorUpdateMode, int seq,
-            IInputContextCallback callback);
+    void requestUpdateCursorAnchorInfo(int cursorUpdateMode, IIntResultCallback callback);
 
-    void commitContent(in InputContentInfo inputContentInfo, int flags, in Bundle opts, int sec,
-            IInputContextCallback callback);
+    void commitContent(in InputContentInfo inputContentInfo, int flags, in Bundle opts,
+            IIntResultCallback callback);
 }
diff --git a/core/java/com/android/internal/view/IInputContextCallback.aidl b/core/java/com/android/internal/view/IInputContextCallback.aidl
deleted file mode 100644
index 0f40a83..0000000
--- a/core/java/com/android/internal/view/IInputContextCallback.aidl
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * 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 com.android.internal.view;
-
-import android.view.inputmethod.ExtractedText;
-
-/**
- * {@hide}
- */
-oneway interface IInputContextCallback {
-    void setTextBeforeCursor(CharSequence textBeforeCursor, int seq);
-    void setTextAfterCursor(CharSequence textAfterCursor, int seq);
-    void setCursorCapsMode(int capsMode, int seq);
-    void setExtractedText(in ExtractedText extractedText, int seq);
-    void setSelectedText(CharSequence selectedText, int seq);
-    void setRequestUpdateCursorAnchorInfoResult(boolean result, int seq);
-    void setCommitContentResult(boolean result, int seq);
-}
diff --git a/core/java/com/android/internal/view/InputConnectionWrapper.java b/core/java/com/android/internal/view/InputConnectionWrapper.java
index a41048c..0bf5234 100644
--- a/core/java/com/android/internal/view/InputConnectionWrapper.java
+++ b/core/java/com/android/internal/view/InputConnectionWrapper.java
@@ -17,14 +17,12 @@
 package com.android.internal.view;
 
 import android.annotation.AnyThread;
-import android.annotation.BinderThread;
 import android.annotation.NonNull;
-import android.compat.annotation.UnsupportedAppUsage;
+import android.annotation.Nullable;
 import android.inputmethodservice.AbstractInputMethodService;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.RemoteException;
-import android.os.SystemClock;
 import android.util.Log;
 import android.view.KeyEvent;
 import android.view.inputmethod.CompletionInfo;
@@ -36,10 +34,15 @@
 import android.view.inputmethod.InputConnectionInspector.MissingMethodFlags;
 import android.view.inputmethod.InputContentInfo;
 
+import com.android.internal.inputmethod.CancellationGroup;
+import com.android.internal.inputmethod.ResultCallbacks;
+
 import java.lang.ref.WeakReference;
-import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.TimeUnit;
 
 public class InputConnectionWrapper implements InputConnection {
+    private static final String TAG = "InputConnectionWrapper";
+
     private static final int MAX_WAIT_TIME_MILLIS = 2000;
     private final IInputContext mIInputContext;
     @NonNull
@@ -49,257 +52,94 @@
     private final int mMissingMethods;
 
     /**
-     * {@code true} if the system already decided to take away IME focus from the target app. This
-     * can be signaled even when the corresponding signal is in the task queue and
-     * {@link InputMethodService#onUnbindInput()} is not yet called back on the UI thread.
+     * Signaled when the system decided to take away IME focus from the target app.
+     *
+     * <p>This is expected to be signaled immediately when the IME process receives
+     * {@link IInputMethod#unbindInput()}.</p>
      */
     @NonNull
-    private final AtomicBoolean mIsUnbindIssued;
-
-    static class InputContextCallback extends IInputContextCallback.Stub {
-        private static final String TAG = "InputConnectionWrapper.ICC";
-        public int mSeq;
-        public boolean mHaveValue;
-        public CharSequence mTextBeforeCursor;
-        public CharSequence mTextAfterCursor;
-        public CharSequence mSelectedText;
-        public ExtractedText mExtractedText;
-        public int mCursorCapsMode;
-        public boolean mRequestUpdateCursorAnchorInfoResult;
-        public boolean mCommitContentResult;
-
-        // A 'pool' of one InputContextCallback.  Each ICW request will attempt to gain
-        // exclusive access to this object.
-        private static InputContextCallback sInstance = new InputContextCallback();
-        private static int sSequenceNumber = 1;
-        
-        /**
-         * Returns an InputContextCallback object that is guaranteed not to be in use by
-         * any other thread.  The returned object's 'have value' flag is cleared and its expected
-         * sequence number is set to a new integer.  We use a sequence number so that replies that
-         * occur after a timeout has expired are not interpreted as replies to a later request.
-         */
-        @UnsupportedAppUsage
-        @AnyThread
-        private static InputContextCallback getInstance() {
-            synchronized (InputContextCallback.class) {
-                // Return sInstance if it's non-null, otherwise construct a new callback
-                InputContextCallback callback;
-                if (sInstance != null) {
-                    callback = sInstance;
-                    sInstance = null;
-                    
-                    // Reset the callback
-                    callback.mHaveValue = false;
-                } else {
-                    callback = new InputContextCallback();
-                }
-                
-                // Set the sequence number
-                callback.mSeq = sSequenceNumber++;
-                return callback;
-            }
-        }
-        
-        /**
-         * Makes the given InputContextCallback available for use in the future.
-         */
-        @UnsupportedAppUsage
-        @AnyThread
-        private void dispose() {
-            synchronized (InputContextCallback.class) {
-                // If sInstance is non-null, just let this object be garbage-collected
-                if (sInstance == null) {
-                    // Allow any objects being held to be gc'ed
-                    mTextAfterCursor = null;
-                    mTextBeforeCursor = null;
-                    mExtractedText = null;
-                    sInstance = this;
-                }
-            }
-        }
-
-        @BinderThread
-        public void setTextBeforeCursor(CharSequence textBeforeCursor, int seq) {
-            synchronized (this) {
-                if (seq == mSeq) {
-                    mTextBeforeCursor = textBeforeCursor;
-                    mHaveValue = true;
-                    notifyAll();
-                } else {
-                    Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
-                            + ") in setTextBeforeCursor, ignoring.");
-                }
-            }
-        }
-
-        @BinderThread
-        public void setTextAfterCursor(CharSequence textAfterCursor, int seq) {
-            synchronized (this) {
-                if (seq == mSeq) {
-                    mTextAfterCursor = textAfterCursor;
-                    mHaveValue = true;
-                    notifyAll();
-                } else {
-                    Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
-                            + ") in setTextAfterCursor, ignoring.");
-                }
-            }
-        }
-
-        @BinderThread
-        public void setSelectedText(CharSequence selectedText, int seq) {
-            synchronized (this) {
-                if (seq == mSeq) {
-                    mSelectedText = selectedText;
-                    mHaveValue = true;
-                    notifyAll();
-                } else {
-                    Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
-                            + ") in setSelectedText, ignoring.");
-                }
-            }
-        }
-
-        @BinderThread
-        public void setCursorCapsMode(int capsMode, int seq) {
-            synchronized (this) {
-                if (seq == mSeq) {
-                    mCursorCapsMode = capsMode; 
-                    mHaveValue = true;  
-                    notifyAll();
-                } else {
-                    Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
-                            + ") in setCursorCapsMode, ignoring.");
-                }
-            }
-        }
-
-        @BinderThread
-        public void setExtractedText(ExtractedText extractedText, int seq) {
-            synchronized (this) {
-                if (seq == mSeq) {
-                    mExtractedText = extractedText;
-                    mHaveValue = true;
-                    notifyAll();
-                } else {
-                    Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
-                            + ") in setExtractedText, ignoring.");
-                }
-            }
-        }
-
-        @BinderThread
-        public void setRequestUpdateCursorAnchorInfoResult(boolean result, int seq) {
-            synchronized (this) {
-                if (seq == mSeq) {
-                    mRequestUpdateCursorAnchorInfoResult = result;
-                    mHaveValue = true;
-                    notifyAll();
-                } else {
-                    Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
-                            + ") in setCursorAnchorInfoRequestResult, ignoring.");
-                }
-            }
-        }
-
-        @BinderThread
-        public void setCommitContentResult(boolean result, int seq) {
-            synchronized (this) {
-                if (seq == mSeq) {
-                    mCommitContentResult = result;
-                    mHaveValue = true;
-                    notifyAll();
-                } else {
-                    Log.i(TAG, "Got out-of-sequence callback " + seq + " (expected " + mSeq
-                            + ") in setCommitContentResult, ignoring.");
-                }
-            }
-        }
-
-        /**
-         * Waits for a result for up to {@link #MAX_WAIT_TIME_MILLIS} milliseconds.
-         * 
-         * <p>The caller must be synchronized on this callback object.
-         */
-        @AnyThread
-        void waitForResultLocked() {
-            long startTime = SystemClock.uptimeMillis();
-            long endTime = startTime + MAX_WAIT_TIME_MILLIS;
-
-            while (!mHaveValue) {
-                long remainingTime = endTime - SystemClock.uptimeMillis();
-                if (remainingTime <= 0) {
-                    Log.w(TAG, "Timed out waiting on IInputContextCallback");
-                    return;
-                }
-                try {
-                    wait(remainingTime);
-                } catch (InterruptedException e) {
-                }
-            }
-        }
-    }
+    private final CancellationGroup mCancellationGroup;
 
     public InputConnectionWrapper(
             @NonNull WeakReference<AbstractInputMethodService> inputMethodService,
-            IInputContext inputContext, @MissingMethodFlags final int missingMethods,
-            @NonNull AtomicBoolean isUnbindIssued) {
+            IInputContext inputContext, @MissingMethodFlags int missingMethods,
+            @NonNull CancellationGroup cancellationGroup) {
         mInputMethodService = inputMethodService;
         mIInputContext = inputContext;
         mMissingMethods = missingMethods;
-        mIsUnbindIssued = isUnbindIssued;
+        mCancellationGroup = cancellationGroup;
+    }
+
+    @AnyThread
+    private static void logInternal(@Nullable String methodName, boolean timedOut,
+            @Nullable Object defaultValue) {
+        if (timedOut) {
+            Log.w(TAG, methodName + " didn't respond in " + MAX_WAIT_TIME_MILLIS + " msec."
+                    + " Returning default: " + defaultValue);
+        } else {
+            Log.w(TAG, methodName + " was canceled before complete. Returning default: "
+                    + defaultValue);
+        }
+    }
+
+    @AnyThread
+    private static int getResultOrZero(@NonNull CancellationGroup.Completable.Int value,
+             @NonNull String methodName) {
+        final boolean timedOut = value.await(MAX_WAIT_TIME_MILLIS,  TimeUnit.MILLISECONDS);
+        if (value.hasValue()) {
+            return value.getValue();
+        }
+        logInternal(methodName, timedOut, 0);
+        return 0;
+    }
+
+    @AnyThread
+    @Nullable
+    private static <T> T getResultOrNull(@NonNull CancellationGroup.Completable.Values<T> value,
+            @NonNull String methodName) {
+        final boolean timedOut = value.await(MAX_WAIT_TIME_MILLIS,  TimeUnit.MILLISECONDS);
+        if (value.hasValue()) {
+            return value.getValue();
+        }
+        logInternal(methodName, timedOut, null);
+        return null;
     }
 
     @AnyThread
     public CharSequence getTextAfterCursor(int length, int flags) {
-        if (mIsUnbindIssued.get()) {
+        if (mCancellationGroup.isCanceled()) {
             return null;
         }
 
-        CharSequence value = null;
+        final CancellationGroup.Completable.CharSequence value =
+                mCancellationGroup.createCompletableCharSequence();
         try {
-            InputContextCallback callback = InputContextCallback.getInstance();
-            mIInputContext.getTextAfterCursor(length, flags, callback.mSeq, callback);
-            synchronized (callback) {
-                callback.waitForResultLocked();
-                if (callback.mHaveValue) {
-                    value = callback.mTextAfterCursor;
-                }
-            }
-            callback.dispose();
+            mIInputContext.getTextAfterCursor(length, flags, ResultCallbacks.of(value));
         } catch (RemoteException e) {
             return null;
         }
-        return value;
+        return getResultOrNull(value, "getTextAfterCursor()");
     }
 
     @AnyThread
     public CharSequence getTextBeforeCursor(int length, int flags) {
-        if (mIsUnbindIssued.get()) {
+        if (mCancellationGroup.isCanceled()) {
             return null;
         }
 
-        CharSequence value = null;
+        final CancellationGroup.Completable.CharSequence value =
+                mCancellationGroup.createCompletableCharSequence();
         try {
-            InputContextCallback callback = InputContextCallback.getInstance();
-            mIInputContext.getTextBeforeCursor(length, flags, callback.mSeq, callback);
-            synchronized (callback) {
-                callback.waitForResultLocked();
-                if (callback.mHaveValue) {
-                    value = callback.mTextBeforeCursor;
-                }
-            }
-            callback.dispose();
+            mIInputContext.getTextBeforeCursor(length, flags, ResultCallbacks.of(value));
         } catch (RemoteException e) {
             return null;
         }
-        return value;
+        return getResultOrNull(value, "getTextBeforeCursor()");
     }
 
     @AnyThread
     public CharSequence getSelectedText(int flags) {
-        if (mIsUnbindIssued.get()) {
+        if (mCancellationGroup.isCanceled()) {
             return null;
         }
 
@@ -307,67 +147,46 @@
             // This method is not implemented.
             return null;
         }
-        CharSequence value = null;
+        final CancellationGroup.Completable.CharSequence value =
+                mCancellationGroup.createCompletableCharSequence();
         try {
-            InputContextCallback callback = InputContextCallback.getInstance();
-            mIInputContext.getSelectedText(flags, callback.mSeq, callback);
-            synchronized (callback) {
-                callback.waitForResultLocked();
-                if (callback.mHaveValue) {
-                    value = callback.mSelectedText;
-                }
-            }
-            callback.dispose();
+            mIInputContext.getSelectedText(flags, ResultCallbacks.of(value));
         } catch (RemoteException e) {
             return null;
         }
-        return value;
+        return getResultOrNull(value, "getSelectedText()");
     }
 
     @AnyThread
     public int getCursorCapsMode(int reqModes) {
-        if (mIsUnbindIssued.get()) {
+        if (mCancellationGroup.isCanceled()) {
             return 0;
         }
 
-        int value = 0;
+        final CancellationGroup.Completable.Int value =
+                mCancellationGroup.createCompletableInt();
         try {
-            InputContextCallback callback = InputContextCallback.getInstance();
-            mIInputContext.getCursorCapsMode(reqModes, callback.mSeq, callback);
-            synchronized (callback) {
-                callback.waitForResultLocked();
-                if (callback.mHaveValue) {
-                    value = callback.mCursorCapsMode;
-                }
-            }
-            callback.dispose();
+            mIInputContext.getCursorCapsMode(reqModes, ResultCallbacks.of(value));
         } catch (RemoteException e) {
             return 0;
         }
-        return value;
+        return getResultOrZero(value, "getCursorCapsMode()");
     }
 
     @AnyThread
     public ExtractedText getExtractedText(ExtractedTextRequest request, int flags) {
-        if (mIsUnbindIssued.get()) {
+        if (mCancellationGroup.isCanceled()) {
             return null;
         }
 
-        ExtractedText value = null;
+        final CancellationGroup.Completable.ExtractedText value =
+                mCancellationGroup.createCompletableExtractedText();
         try {
-            InputContextCallback callback = InputContextCallback.getInstance();
-            mIInputContext.getExtractedText(request, flags, callback.mSeq, callback);
-            synchronized (callback) {
-                callback.waitForResultLocked();
-                if (callback.mHaveValue) {
-                    value = callback.mExtractedText;
-                }
-            }
-            callback.dispose();
+            mIInputContext.getExtractedText(request, flags, ResultCallbacks.of(value));
         } catch (RemoteException e) {
             return null;
         }
-        return value;
+        return getResultOrNull(value, "getExtractedText()");
     }
 
     @AnyThread
@@ -563,29 +382,22 @@
 
     @AnyThread
     public boolean requestCursorUpdates(int cursorUpdateMode) {
-        if (mIsUnbindIssued.get()) {
+        if (mCancellationGroup.isCanceled()) {
             return false;
         }
 
-        boolean result = false;
         if (isMethodMissing(MissingMethodFlags.REQUEST_CURSOR_UPDATES)) {
             // This method is not implemented.
             return false;
         }
+        final CancellationGroup.Completable.Int value = mCancellationGroup.createCompletableInt();
         try {
-            InputContextCallback callback = InputContextCallback.getInstance();
-            mIInputContext.requestUpdateCursorAnchorInfo(cursorUpdateMode, callback.mSeq, callback);
-            synchronized (callback) {
-                callback.waitForResultLocked();
-                if (callback.mHaveValue) {
-                    result = callback.mRequestUpdateCursorAnchorInfoResult;
-                }
-            }
-            callback.dispose();
+            mIInputContext.requestUpdateCursorAnchorInfo(cursorUpdateMode,
+                    ResultCallbacks.of(value));
         } catch (RemoteException e) {
             return false;
         }
-        return result;
+        return getResultOrZero(value, "requestUpdateCursorAnchorInfo()") != 0;
     }
 
     @AnyThread
@@ -601,38 +413,31 @@
 
     @AnyThread
     public boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) {
-        if (mIsUnbindIssued.get()) {
+        if (mCancellationGroup.isCanceled()) {
             return false;
         }
 
-        boolean result = false;
         if (isMethodMissing(MissingMethodFlags.COMMIT_CONTENT)) {
             // This method is not implemented.
             return false;
         }
-        try {
-            if ((flags & InputConnection.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0) {
-                final AbstractInputMethodService inputMethodService = mInputMethodService.get();
-                if (inputMethodService == null) {
-                    // This basically should not happen, because it's the the caller of this method.
-                    return false;
-                }
-                inputMethodService.exposeContent(inputContentInfo, this);
-            }
 
-            InputContextCallback callback = InputContextCallback.getInstance();
-            mIInputContext.commitContent(inputContentInfo, flags, opts, callback.mSeq, callback);
-            synchronized (callback) {
-                callback.waitForResultLocked();
-                if (callback.mHaveValue) {
-                    result = callback.mCommitContentResult;
-                }
+        if ((flags & InputConnection.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0) {
+            final AbstractInputMethodService inputMethodService = mInputMethodService.get();
+            if (inputMethodService == null) {
+                // This basically should not happen, because it's the caller of this method.
+                return false;
             }
-            callback.dispose();
+            inputMethodService.exposeContent(inputContentInfo, this);
+        }
+
+        final CancellationGroup.Completable.Int value = mCancellationGroup.createCompletableInt();
+        try {
+            mIInputContext.commitContent(inputContentInfo, flags, opts, ResultCallbacks.of(value));
         } catch (RemoteException e) {
             return false;
         }
-        return result;
+        return getResultOrZero(value, "commitContent()") != 0;
     }
 
     @AnyThread
diff --git a/core/java/com/android/internal/widget/ConversationLayout.java b/core/java/com/android/internal/widget/ConversationLayout.java
index 1336ec4..ab68c44 100644
--- a/core/java/com/android/internal/widget/ConversationLayout.java
+++ b/core/java/com/android/internal/widget/ConversationLayout.java
@@ -45,6 +45,7 @@
 import android.util.DisplayMetrics;
 import android.view.Gravity;
 import android.view.RemotableViewMethod;
+import android.view.TouchDelegate;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewTreeObserver;
@@ -62,6 +63,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Locale;
 import java.util.function.Consumer;
 import java.util.regex.Pattern;
 
@@ -151,6 +153,10 @@
     private int mFacePileProtectionWidth;
     private int mFacePileProtectionWidthExpanded;
     private boolean mImportantConversation;
+    private TextView mUnreadBadge;
+    private ViewGroup mAppOps;
+    private Rect mAppOpsTouchRect = new Rect();
+    private float mMinTouchSize;
 
     public ConversationLayout(@NonNull Context context) {
         super(context);
@@ -189,6 +195,8 @@
         mConversationIcon = findViewById(R.id.conversation_icon);
         mConversationIconContainer = findViewById(R.id.conversation_icon_container);
         mIcon = findViewById(R.id.icon);
+        mAppOps = findViewById(com.android.internal.R.id.app_ops);
+        mMinTouchSize = 48 * getResources().getDisplayMetrics().density;
         mImportanceRingView = findViewById(R.id.conversation_icon_badge_ring);
         mConversationIconBadge = findViewById(R.id.conversation_icon_badge);
         mConversationIconBadgeBg = findViewById(R.id.conversation_icon_badge_bg);
@@ -277,6 +285,7 @@
         mAppName.setOnVisibilityChangedListener((visibility) -> {
             onAppNameVisibilityChanged();
         });
+        mUnreadBadge = findViewById(R.id.conversation_unread_count);
         mConversationContentStart = getResources().getDimensionPixelSize(
                 R.dimen.conversation_content_start);
         mInternalButtonPadding
@@ -354,7 +363,6 @@
         // mUser now set (would be nice to avoid the side effect but WHATEVER)
         setUser(extras.getParcelable(Notification.EXTRA_MESSAGING_PERSON));
 
-
         // Append remote input history to newMessages (again, side effect is lame but WHATEVS)
         RemoteInputHistoryItem[] history = (RemoteInputHistoryItem[])
                 extras.getParcelableArray(Notification.EXTRA_REMOTE_INPUT_HISTORY_ITEMS);
@@ -362,9 +370,11 @@
 
         boolean showSpinner =
                 extras.getBoolean(Notification.EXTRA_SHOW_REMOTE_INPUT_SPINNER, false);
-
         // bind it, baby
         bind(newMessages, newHistoricMessages, showSpinner);
+
+        int unreadCount = extras.getInt(Notification.EXTRA_CONVERSATION_UNREAD_MESSAGE_COUNT);
+        setUnreadCount(unreadCount);
     }
 
     @Override
@@ -372,6 +382,18 @@
         mImageResolver = resolver;
     }
 
+    /** @hide */
+    public void setUnreadCount(int unreadCount) {
+        mUnreadBadge.setVisibility(mIsCollapsed && unreadCount > 1 ? VISIBLE : GONE);
+        CharSequence text = unreadCount >= 100
+                ? getResources().getString(R.string.unread_convo_overflow, 99)
+                : String.format(Locale.getDefault(), "%d", unreadCount);
+        mUnreadBadge.setText(text);
+        mUnreadBadge.setBackgroundTintList(ColorStateList.valueOf(mLayoutColor));
+        boolean needDarkText = ColorUtils.calculateLuminance(mLayoutColor) > 0.5f;
+        mUnreadBadge.setTextColor(needDarkText ? Color.BLACK : Color.WHITE);
+    }
+
     private void addRemoteInputHistoryToMessages(
             List<Notification.MessagingStyle.Message> newMessages,
             RemoteInputHistoryItem[] remoteInputHistory) {
@@ -855,6 +877,7 @@
     @RemotableViewMethod
     public void setSenderTextColor(int color) {
         mSenderTextColor = color;
+        mConversationText.setTextColor(color);
     }
 
     /**
@@ -1055,6 +1078,47 @@
                 }
             });
         }
+        if (mAppOps.getWidth() > 0) {
+
+            // Let's increase the touch size of the app ops view if it's here
+            mAppOpsTouchRect.set(
+                    mAppOps.getLeft(),
+                    mAppOps.getTop(),
+                    mAppOps.getRight(),
+                    mAppOps.getBottom());
+            for (int i = 0; i < mAppOps.getChildCount(); i++) {
+                View child = mAppOps.getChildAt(i);
+                if (child.getVisibility() == GONE) {
+                    continue;
+                }
+                // Make sure each child has at least a minTouchSize touch target around it
+                float childTouchLeft = child.getLeft() + child.getWidth() / 2.0f
+                        - mMinTouchSize / 2.0f;
+                float childTouchRight = childTouchLeft + mMinTouchSize;
+                mAppOpsTouchRect.left = (int) Math.min(mAppOpsTouchRect.left,
+                        mAppOps.getLeft() + childTouchLeft);
+                mAppOpsTouchRect.right = (int) Math.max(mAppOpsTouchRect.right,
+                        mAppOps.getLeft() + childTouchRight);
+            }
+
+            // Increase the height
+            int heightIncrease = 0;
+            if (mAppOpsTouchRect.height() < mMinTouchSize) {
+                heightIncrease = (int) Math.ceil((mMinTouchSize - mAppOpsTouchRect.height())
+                        / 2.0f);
+            }
+            mAppOpsTouchRect.inset(0, -heightIncrease);
+
+            // Let's adjust the hitrect since app ops isn't a direct child
+            ViewGroup viewGroup = (ViewGroup) mAppOps.getParent();
+            while (viewGroup != this) {
+                mAppOpsTouchRect.offset(viewGroup.getLeft(), viewGroup.getTop());
+                viewGroup = (ViewGroup) viewGroup.getParent();
+            }
+            //
+            // Extend the size of the app opps to be at least 48dp
+            setTouchDelegate(new TouchDelegate(mAppOpsTouchRect, mAppOps));
+        }
     }
 
     public MessagingLinearLayout getMessagingLinearLayout() {
diff --git a/core/java/com/android/internal/widget/InlinePresentationStyleUtils.java b/core/java/com/android/internal/widget/InlinePresentationStyleUtils.java
new file mode 100644
index 0000000..264c8bd
--- /dev/null
+++ b/core/java/com/android/internal/widget/InlinePresentationStyleUtils.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.widget;
+
+import android.annotation.NonNull;
+import android.os.Bundle;
+
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Utility methods relating to inline presentation UI.
+ */
+public final class InlinePresentationStyleUtils {
+
+    /**
+     * Returns true if the two bundles are deeply equal.
+     *
+     * Each input bundle may represent a UI style in the
+     * {@link android.widget.inline.InlinePresentationSpec} or the extra
+     * request info in the {@link android.view.inputmethod.InlineSuggestionsRequest}
+     *
+     * Note: this method should not be called in the framework process for security reasons.
+     */
+    public static boolean bundleEquals(@NonNull Bundle bundle1, @NonNull Bundle bundle2) {
+        if (bundle1 == bundle2) {
+            return true;
+        }
+        if (bundle1 == null || bundle2 == null) {
+            return false;
+        }
+        if (bundle1.size() != bundle2.size()) {
+            return false;
+        }
+        Set<String> keys = bundle1.keySet();
+        for (String key : keys) {
+            Object value1 = bundle1.get(key);
+            Object value2 = bundle2.get(key);
+            if (value1 instanceof Bundle && value2 instanceof Bundle
+                    && !bundleEquals((Bundle) value1, (Bundle) value2)) {
+                return false;
+            } else if (!Objects.equals(value1, value2)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Private ctor to avoid constructing the class.
+     */
+    private InlinePresentationStyleUtils() {
+    }
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index eae6145..451363f 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3788,10 +3788,9 @@
                 android:protectionLevel="signature|installer" />
 
     <!-- @SystemApi Allows an application to manage the holders of a role.
-         @hide
-         STOPSHIP b/145526313: Remove wellbeing protection flag from MANAGE_ROLE_HOLDERS. -->
+         @hide -->
     <permission android:name="android.permission.MANAGE_ROLE_HOLDERS"
-                android:protectionLevel="signature|installer|wellbeing" />
+                android:protectionLevel="signature|installer" />
 
     <!-- @SystemApi Allows an application to observe role holder changes.
          @hide -->
@@ -4713,12 +4712,6 @@
     <permission android:name="android.permission.MANAGE_SOUND_TRIGGER"
         android:protectionLevel="signature|privileged" />
 
-    <!-- Allows preempting sound trigger recognitions for the sake of capturing audio on
-         implementations which do not support running both concurrently.
-         @hide -->
-    <permission android:name="android.permission.PREEMPT_SOUND_TRIGGER"
-                android:protectionLevel="signature|privileged" />
-
     <!-- Must be required by system/priv apps implementing sound trigger detection services
          @hide
          @SystemApi -->
diff --git a/core/res/res/drawable/conversation_unread_bg.xml b/core/res/res/drawable/conversation_unread_bg.xml
new file mode 100644
index 0000000..d3e00cf
--- /dev/null
+++ b/core/res/res/drawable/conversation_unread_bg.xml
@@ -0,0 +1,19 @@
+<!--
+  ~ Copyright (C) 2020 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.
+  -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+    <corners android:radius="20sp" />
+    <solid android:color="@android:color/white" />
+</shape>
\ No newline at end of file
diff --git a/core/res/res/layout/notification_template_material_conversation.xml b/core/res/res/layout/notification_template_material_conversation.xml
index 46d3d13..b9ca292 100644
--- a/core/res/res/layout/notification_template_material_conversation.xml
+++ b/core/res/res/layout/notification_template_material_conversation.xml
@@ -136,6 +136,7 @@
                         android:textAppearance="@style/TextAppearance.DeviceDefault.Notification.Title"
                         android:textSize="16sp"
                         android:singleLine="true"
+                        android:layout_weight="1"
                         />
 
                     <TextView
@@ -166,6 +167,18 @@
                     />
 
                     <ImageView
+                        android:id="@+id/alerted_icon"
+                        android:layout_width="@dimen/notification_alerted_size"
+                        android:layout_height="@dimen/notification_alerted_size"
+                        android:layout_gravity="center"
+                        android:layout_marginStart="4dp"
+                        android:paddingTop="2dp"
+                        android:scaleType="fitCenter"
+                        android:visibility="gone"
+                        android:contentDescription="@string/notification_alerted_content_description"
+                        android:src="@drawable/ic_notifications_alerted"/>
+
+                    <ImageView
                         android:id="@+id/profile_badge"
                         android:layout_width="@dimen/notification_badge_size"
                         android:layout_height="@dimen/notification_badge_size"
@@ -176,6 +189,44 @@
                         android:visibility="gone"
                         android:contentDescription="@string/notification_work_profile_content_description"
                     />
+                    <LinearLayout
+                        android:id="@+id/app_ops"
+                        android:layout_height="wrap_content"
+                        android:layout_width="wrap_content"
+                        android:paddingTop="3dp"
+                        android:layout_marginStart="2dp"
+                        android:orientation="horizontal" >
+                        <ImageButton
+                            android:layout_marginStart="4dp"
+                            android:id="@+id/camera"
+                            android:layout_width="?attr/notificationHeaderIconSize"
+                            android:layout_height="?attr/notificationHeaderIconSize"
+                            android:src="@drawable/ic_camera"
+                            android:background="?android:selectableItemBackgroundBorderless"
+                            android:visibility="gone"
+                            android:contentDescription="@string/notification_appops_camera_active"
+                            />
+                        <ImageButton
+                            android:id="@+id/mic"
+                            android:layout_width="?attr/notificationHeaderIconSize"
+                            android:layout_height="?attr/notificationHeaderIconSize"
+                            android:src="@drawable/ic_mic"
+                            android:background="?android:selectableItemBackgroundBorderless"
+                            android:layout_marginStart="4dp"
+                            android:visibility="gone"
+                            android:contentDescription="@string/notification_appops_microphone_active"
+                            />
+                        <ImageButton
+                            android:id="@+id/overlay"
+                            android:layout_width="?attr/notificationHeaderIconSize"
+                            android:layout_height="?attr/notificationHeaderIconSize"
+                            android:src="@drawable/ic_alert_window_layer"
+                            android:background="?android:selectableItemBackgroundBorderless"
+                            android:layout_marginStart="4dp"
+                            android:visibility="gone"
+                            android:contentDescription="@string/notification_appops_overlay_active"
+                            />
+                    </LinearLayout>
                 </LinearLayout>
 
                 <!-- App Name -->
@@ -199,10 +250,8 @@
                     android:clipChildren="false"
                     />
             </com.android.internal.widget.RemeasuringLinearLayout>
-            <!-- Unread Count -->
-            <!-- <TextView /> -->
 
-            <!-- This is where the expand button will be placed when collapsed-->
+            <!-- This is where the expand button container will be placed when collapsed-->
         </com.android.internal.widget.RemeasuringLinearLayout>
 
         <include layout="@layout/notification_template_smart_reply_container"
@@ -238,6 +287,21 @@
             android:clipToPadding="false"
             android:clipChildren="false"
             />
+        <!-- Unread Count -->
+        <TextView
+            android:id="@+id/conversation_unread_count"
+            android:layout_width="33sp"
+            android:layout_height="wrap_content"
+            android:layout_marginEnd="11dp"
+            android:layout_gravity="center"
+            android:gravity="center"
+            android:padding="2dp"
+            android:visibility="gone"
+            android:textAppearance="@style/TextAppearance.DeviceDefault.Notification"
+            android:textColor="#FFFFFF"
+            android:textSize="12sp"
+            android:background="@drawable/conversation_unread_bg"
+        />
         <com.android.internal.widget.NotificationExpandButton
             android:id="@+id/expand_button"
             android:layout_width="@dimen/notification_header_expand_icon_size"
@@ -246,6 +310,6 @@
             android:drawable="@drawable/ic_expand_notification"
             android:clickable="false"
             android:importantForAccessibility="no"
-            />
+        />
     </LinearLayout>
 </com.android.internal.widget.ConversationLayout>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 4c4b7e6..b1bba53 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1910,6 +1910,9 @@
     <!-- The name of the package that will hold the system gallery role. -->
     <string name="config_systemGallery" translatable="false">com.android.gallery</string>
 
+    <!-- The name of the package that will be allowed to change its components' label/icon. -->
+    <string name="config_overrideComponentUiPackage" translatable="false"></string>
+
     <!-- Enable/disable default bluetooth profiles:
         HSP_AG, ObexObjectPush, Audio, NAP -->
     <bool name="config_bluetooth_default_profiles">true</bool>
@@ -4418,7 +4421,7 @@
     <string name="config_customSessionPolicyProvider"></string>
 
     <!-- The max scale for the wallpaper when it's zoomed in -->
-    <item name="config_wallpaperMaxScale" format="float" type="dimen">1.15</item>
+    <item name="config_wallpaperMaxScale" format="float" type="dimen">1.10</item>
 
     <!-- Package name that will receive an explicit manifest broadcast for
          android.os.action.POWER_SAVE_MODE_CHANGED. -->
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index e2e65dd..c9c498e 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5442,6 +5442,9 @@
     <!-- Conversation Title fallback if the there is no name provided in a group chat conversation [CHAR LIMIT=40]-->
     <string name="conversation_title_fallback_group_chat">Group Conversation</string>
 
+    <!-- Number of unread messages displayed on a conversation notification, when greater-than-or-equal-to 100 [CHAR LIMIT=3]-->
+    <string name="unread_convo_overflow"><xliff:g id="max_unread_count" example="99">%1$d</xliff:g>+</string>
+
     <!-- ResolverActivity - profile tabs -->
     <!-- Label of a tab on a screen. A user can tap this tap to switch to the 'Personal' view (that shows their personal content) if they have a work profile on their device. [CHAR LIMIT=NONE] -->
     <string name="resolver_personal_tab">Personal</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 0fea372..ec80582 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3898,6 +3898,8 @@
   <java-symbol type="dimen" name="button_padding_horizontal_material" />
   <java-symbol type="dimen" name="button_inset_horizontal_material" />
   <java-symbol type="layout" name="conversation_face_pile_layout" />
+  <java-symbol type="id" name="conversation_unread_count" />
+  <java-symbol type="string" name="unread_convo_overflow" />
 
   <!-- Intent resolver and share sheet -->
   <java-symbol type="string" name="resolver_personal_tab" />
@@ -3959,4 +3961,6 @@
 
   <!-- Set to true to make assistant show in front of the dream/screensaver. -->
   <java-symbol type="bool" name="config_assistantOnTopOfDream"/>
+
+  <java-symbol type="string" name="config_overrideComponentUiPackage" />
 </resources>
diff --git a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
index 6c01181..5f12bf0 100644
--- a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
+++ b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java
@@ -29,6 +29,7 @@
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
@@ -222,6 +223,22 @@
         verify(mMockListener, never()).onFinished(any());
     }
 
+    @Test
+    public void testFinish_immediately() {
+        when(mMockController.getState()).thenReturn(mInsetsState);
+        doAnswer(invocation -> {
+            mController.applyChangeInsets(mInsetsState);
+            return null;
+        }).when(mMockController).scheduleApplyChangeInsets(any());
+        mController.finish(true /* shown */);
+        assertEquals(Insets.of(0, 100, 100, 0), mController.getCurrentInsets());
+        verify(mMockController).notifyFinished(eq(mController), eq(true /* shown */));
+        assertFalse(mController.isReady());
+        assertTrue(mController.isFinished());
+        assertFalse(mController.isCancelled());
+        verify(mMockListener).onFinished(mController);
+    }
+
     private void assertPosition(Matrix m, Rect original, Rect transformed) {
         RectF rect = new RectF(original);
         rect.offsetTo(0, 0);
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index f63ec6b..6af887d 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -162,7 +162,6 @@
     <assign-permission name="android.permission.UPDATE_DEVICE_STATS" uid="audioserver" />
     <assign-permission name="android.permission.UPDATE_APP_OPS_STATS" uid="audioserver" />
     <assign-permission name="android.permission.PACKAGE_USAGE_STATS" uid="audioserver" />
-    <assign-permission name="android.permission.PREEMPT_SOUND_TRIGGER" uid="audioserver" />
 
     <assign-permission name="android.permission.MODIFY_AUDIO_SETTINGS" uid="cameraserver" />
     <assign-permission name="android.permission.ACCESS_SURFACE_FLINGER" uid="cameraserver" />
diff --git a/location/java/android/location/LocationRequest.java b/location/java/android/location/LocationRequest.java
index f3e4d81..4dd1a29 100644
--- a/location/java/android/location/LocationRequest.java
+++ b/location/java/android/location/LocationRequest.java
@@ -71,8 +71,7 @@
  * heavy-weight work after receiving an update - such as using the network.
  *
  * <p>Activities should strongly consider removing all location
- * request when entering the background
- * (for example at {@link android.app.Activity#onPause}), or
+ * request when entering the background, or
  * at least swap the request to a larger interval and lower quality.
  * Future version of the location manager may automatically perform background
  * throttling on behalf of applications.
@@ -146,38 +145,32 @@
      */
     public static final int POWER_HIGH = 203;
 
-    /**
-     * By default, mFastestInterval = FASTEST_INTERVAL_MULTIPLE * mInterval
-     */
+    private static final long DEFAULT_INTERVAL_MS = 60 * 60 * 1000; // 1 hour
     private static final double FASTEST_INTERVAL_FACTOR = 6.0;  // 6x
 
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
-    private int mQuality = POWER_LOW;
     @UnsupportedAppUsage
-    private long mInterval = 60 * 60 * 1000;   // 60 minutes
+    private String mProvider;
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
-    private long mFastestInterval = (long) (mInterval / FASTEST_INTERVAL_FACTOR);  // 10 minutes
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
-    private boolean mExplicitFastestInterval = false;
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
-    private long mExpireAt = Long.MAX_VALUE;  // no expiry
-    private long mExpireIn = Long.MAX_VALUE;  // no expiry
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
-    private int mNumUpdates = Integer.MAX_VALUE;  // no expiry
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
-    private float mSmallestDisplacement = 0.0f;    // meters
+    private int mQuality;
     @UnsupportedAppUsage
-    private WorkSource mWorkSource = null;
+    private long mInterval;
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+    private long mFastestInterval;
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+    private boolean mExplicitFastestInterval;
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+    private long mExpireAt;
+    private long mExpireIn;
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+    private int mNumUpdates;
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+    private float mSmallestDisplacement;
     @UnsupportedAppUsage
-    private boolean mHideFromAppOps = false; // True if this request shouldn't be counted by AppOps
-    private boolean mLocationSettingsIgnored = false;
-
+    private boolean mHideFromAppOps;
+    private boolean mLocationSettingsIgnored;
+    private boolean mLowPowerMode;
     @UnsupportedAppUsage
-    private String mProvider = LocationManager.FUSED_PROVIDER;
-            // for deprecated APIs that explicitly request a provider
-
-    /** If true, GNSS chipset will make strong tradeoffs to substantially restrict power use */
-    private boolean mLowPowerMode = false;
+    private @Nullable WorkSource mWorkSource;
 
     /**
      * Create a location request with default parameters.
@@ -260,23 +253,71 @@
 
     /** @hide */
     public LocationRequest() {
+        this(
+                /* provider= */ LocationManager.FUSED_PROVIDER,
+                /* quality= */ POWER_LOW,
+                /* interval= */ DEFAULT_INTERVAL_MS,
+                /* fastestInterval= */ (long) (DEFAULT_INTERVAL_MS / FASTEST_INTERVAL_FACTOR),
+                /* explicitFastestInterval= */ false,
+                /* expireAt= */ Long.MAX_VALUE,
+                /* expireIn= */ Long.MAX_VALUE,
+                /* numUpdates= */ Integer.MAX_VALUE,
+                /* smallestDisplacement= */ 0,
+                /* hideFromAppOps= */ false,
+                /* lowPowerMode= */ false,
+                /* locationSettingsIgnored= */ false,
+                /* workSource= */ null);
     }
 
     /** @hide */
     public LocationRequest(LocationRequest src) {
-        mQuality = src.mQuality;
-        mInterval = src.mInterval;
-        mFastestInterval = src.mFastestInterval;
-        mExplicitFastestInterval = src.mExplicitFastestInterval;
-        mExpireAt = src.mExpireAt;
-        mExpireIn = src.mExpireIn;
-        mNumUpdates = src.mNumUpdates;
-        mSmallestDisplacement = src.mSmallestDisplacement;
-        mProvider = src.mProvider;
-        mWorkSource = src.mWorkSource;
-        mHideFromAppOps = src.mHideFromAppOps;
-        mLowPowerMode = src.mLowPowerMode;
-        mLocationSettingsIgnored = src.mLocationSettingsIgnored;
+        this(
+                src.mProvider,
+                src.mQuality,
+                src.mInterval,
+                src.mFastestInterval,
+                src.mExplicitFastestInterval,
+                src.mExpireAt,
+                src.mExpireIn,
+                src.mNumUpdates,
+                src.mSmallestDisplacement,
+                src.mHideFromAppOps,
+                src.mLowPowerMode,
+                src.mLocationSettingsIgnored,
+                src.mWorkSource);
+    }
+
+    private LocationRequest(
+            @NonNull String provider,
+            int quality,
+            long intervalMs,
+            long fastestIntervalMs,
+            boolean explicitFastestInterval,
+            long expireAt,
+            long expireInMs,
+            int numUpdates,
+            float smallestDisplacementM,
+            boolean hideFromAppOps,
+            boolean locationSettingsIgnored,
+            boolean lowPowerMode,
+            WorkSource workSource) {
+        Preconditions.checkArgument(provider != null, "invalid provider: null");
+        checkQuality(quality);
+
+        mProvider = provider;
+        mQuality = quality;
+        mInterval = intervalMs;
+        mFastestInterval = fastestIntervalMs;
+        mExplicitFastestInterval = explicitFastestInterval;
+        mExpireAt = expireAt;
+        mExpireIn = expireInMs;
+        mNumUpdates = numUpdates;
+        mSmallestDisplacement = Preconditions.checkArgumentInRange(smallestDisplacementM, 0,
+                Float.MAX_VALUE, "smallestDisplacementM");
+        mHideFromAppOps = hideFromAppOps;
+        mLowPowerMode = lowPowerMode;
+        mLocationSettingsIgnored = locationSettingsIgnored;
+        mWorkSource = workSource;
     }
 
     /**
@@ -567,7 +608,7 @@
 
     /** Sets the provider to use for this location request. */
     public @NonNull LocationRequest setProvider(@NonNull String provider) {
-        checkProvider(provider);
+        Preconditions.checkArgument(provider != null, "invalid provider: null");
         mProvider = provider;
         return this;
     }
@@ -580,9 +621,9 @@
 
     /** @hide */
     @SystemApi
-    public @NonNull LocationRequest setSmallestDisplacement(float meters) {
-        checkDisplacement(meters);
-        mSmallestDisplacement = meters;
+    public @NonNull LocationRequest setSmallestDisplacement(float smallestDisplacementM) {
+        mSmallestDisplacement = Preconditions.checkArgumentInRange(smallestDisplacementM, 0,
+                Float.MAX_VALUE, "smallestDisplacementM");
         return this;
     }
 
@@ -653,40 +694,24 @@
         }
     }
 
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
-    private static void checkDisplacement(float meters) {
-        if (meters < 0.0f) {
-            throw new IllegalArgumentException("invalid displacement: " + meters);
-        }
-    }
-
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
-    private static void checkProvider(String name) {
-        if (name == null) {
-            throw new IllegalArgumentException("invalid provider: null");
-        }
-    }
-
-    public static final @android.annotation.NonNull Parcelable.Creator<LocationRequest> CREATOR =
+    public static final @NonNull Parcelable.Creator<LocationRequest> CREATOR =
             new Parcelable.Creator<LocationRequest>() {
                 @Override
                 public LocationRequest createFromParcel(Parcel in) {
-                    LocationRequest request = new LocationRequest();
-                    request.setQuality(in.readInt());
-                    request.setFastestInterval(in.readLong());
-                    request.setInterval(in.readLong());
-                    request.setExpireAt(in.readLong());
-                    request.setExpireIn(in.readLong());
-                    request.setNumUpdates(in.readInt());
-                    request.setSmallestDisplacement(in.readFloat());
-                    request.setHideFromAppOps(in.readInt() != 0);
-                    request.setLowPowerMode(in.readInt() != 0);
-                    request.setLocationSettingsIgnored(in.readInt() != 0);
-                    String provider = in.readString();
-                    if (provider != null) request.setProvider(provider);
-                    WorkSource workSource = in.readParcelable(null);
-                    if (workSource != null) request.setWorkSource(workSource);
-                    return request;
+                    return new LocationRequest(
+                            /* provider= */ in.readString(),
+                            /* quality= */ in.readInt(),
+                            /* interval= */ in.readLong(),
+                            /* fastestInterval= */ in.readLong(),
+                            /* explicitFastestInterval= */ in.readBoolean(),
+                            /* expireAt= */ in.readLong(),
+                            /* expireIn= */ in.readLong(),
+                            /* numUpdates= */ in.readInt(),
+                            /* smallestDisplacement= */ in.readFloat(),
+                            /* hideFromAppOps= */ in.readBoolean(),
+                            /* locationSettingsIgnored= */ in.readBoolean(),
+                            /* lowPowerMode= */ in.readBoolean(),
+                            /* workSource= */ in.readTypedObject(WorkSource.CREATOR));
                 }
 
                 @Override
@@ -702,18 +727,19 @@
 
     @Override
     public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeString(mProvider);
         parcel.writeInt(mQuality);
-        parcel.writeLong(mFastestInterval);
         parcel.writeLong(mInterval);
+        parcel.writeLong(mFastestInterval);
+        parcel.writeBoolean(mExplicitFastestInterval);
         parcel.writeLong(mExpireAt);
         parcel.writeLong(mExpireIn);
         parcel.writeInt(mNumUpdates);
         parcel.writeFloat(mSmallestDisplacement);
-        parcel.writeInt(mHideFromAppOps ? 1 : 0);
-        parcel.writeInt(mLowPowerMode ? 1 : 0);
-        parcel.writeInt(mLocationSettingsIgnored ? 1 : 0);
-        parcel.writeString(mProvider);
-        parcel.writeParcelable(mWorkSource, 0);
+        parcel.writeBoolean(mHideFromAppOps);
+        parcel.writeBoolean(mLocationSettingsIgnored);
+        parcel.writeBoolean(mLowPowerMode);
+        parcel.writeTypedObject(mWorkSource, 0);
     }
 
     /** @hide */
@@ -740,16 +766,19 @@
     @Override
     public String toString() {
         StringBuilder s = new StringBuilder();
-        s.append("Request[").append(qualityToString(mQuality));
-        if (mProvider != null) s.append(' ').append(mProvider);
+        s.append("Request[");
+        s.append(qualityToString(mQuality));
+        s.append(" ").append(mProvider);
         if (mQuality != POWER_NONE) {
-            s.append(" requested=");
+            s.append(" interval=");
             TimeUtils.formatDuration(mInterval, s);
+            if (mExplicitFastestInterval) {
+                s.append(" fastestInterval=");
+                TimeUtils.formatDuration(mFastestInterval, s);
+            }
         }
-        s.append(" fastest=");
-        TimeUtils.formatDuration(mFastestInterval, s);
         if (mExpireAt != Long.MAX_VALUE) {
-            s.append(" expireAt=").append(TimeUtils.formatUptime(mExpireAt));
+            s.append(" expireAt=").append(TimeUtils.formatRealtime(mExpireAt));
         }
         if (mExpireIn != Long.MAX_VALUE) {
             s.append(" expireIn=");
diff --git a/location/java/com/android/internal/location/ProviderRequest.java b/location/java/com/android/internal/location/ProviderRequest.java
index 572fbc3..a81ddfe 100644
--- a/location/java/com/android/internal/location/ProviderRequest.java
+++ b/location/java/com/android/internal/location/ProviderRequest.java
@@ -23,7 +23,6 @@
 import android.os.WorkSource;
 import android.util.TimeUtils;
 
-import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
@@ -83,18 +82,14 @@
             new Parcelable.Creator<ProviderRequest>() {
                 @Override
                 public ProviderRequest createFromParcel(Parcel in) {
-                    boolean reportLocation = in.readInt() == 1;
-                    long interval = in.readLong();
-                    boolean lowPowerMode = in.readBoolean();
-                    boolean locationSettingsIgnored = in.readBoolean();
-                    int count = in.readInt();
-                    ArrayList<LocationRequest> locationRequests = new ArrayList<>(count);
-                    for (int i = 0; i < count; i++) {
-                        locationRequests.add(LocationRequest.CREATOR.createFromParcel(in));
-                    }
-                    WorkSource workSource = in.readParcelable(null);
-                    return new ProviderRequest(reportLocation, interval, lowPowerMode,
-                            locationSettingsIgnored, locationRequests, workSource);
+                    return new ProviderRequest(
+                            /* reportLocation= */ in.readBoolean(),
+                            /* interval= */ in.readLong(),
+                            /* lowPowerMode= */ in.readBoolean(),
+                            /* locationSettingsIgnored= */ in.readBoolean(),
+                            /* locationRequests= */
+                            in.createTypedArrayList(LocationRequest.CREATOR),
+                            /* workSource= */ in.readTypedObject(WorkSource.CREATOR));
                 }
 
                 @Override
@@ -110,15 +105,12 @@
 
     @Override
     public void writeToParcel(Parcel parcel, int flags) {
-        parcel.writeInt(reportLocation ? 1 : 0);
+        parcel.writeBoolean(reportLocation);
         parcel.writeLong(interval);
         parcel.writeBoolean(lowPowerMode);
         parcel.writeBoolean(locationSettingsIgnored);
-        parcel.writeInt(locationRequests.size());
-        for (LocationRequest request : locationRequests) {
-            request.writeToParcel(parcel, flags);
-        }
-        parcel.writeParcelable(workSource, flags);
+        parcel.writeTypedList(locationRequests);
+        parcel.writeTypedObject(workSource, flags);
     }
 
     @Override
diff --git a/media/java/android/media/IMediaRouter2Manager.aidl b/media/java/android/media/IMediaRouter2Manager.aidl
index a2f9ee9..5925d38 100644
--- a/media/java/android/media/IMediaRouter2Manager.aidl
+++ b/media/java/android/media/IMediaRouter2Manager.aidl
@@ -24,8 +24,8 @@
  * {@hide}
  */
 oneway interface IMediaRouter2Manager {
-    void notifySessionCreated(in RoutingSessionInfo sessionInfo);
-    void notifySessionsUpdated();
+    void notifySessionCreated(int requestId, in RoutingSessionInfo sessionInfo);
+    void notifySessionUpdated(in RoutingSessionInfo sessionInfo);
     void notifyPreferredFeaturesChanged(String packageName, in List<String> preferredFeatures);
     void notifyRoutesAdded(in List<MediaRoute2Info> routes);
     void notifyRoutesRemoved(in List<MediaRoute2Info> routes);
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index 88bcd6a..b694fd0 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -75,6 +75,8 @@
     final ConcurrentMap<String, List<String>> mPreferredFeaturesMap = new ConcurrentHashMap<>();
 
     private final AtomicInteger mNextRequestId = new AtomicInteger(1);
+    private final CopyOnWriteArrayList<TransferRequest> mTransferRequests =
+            new CopyOnWriteArrayList<>();
 
     /**
      * Gets an instance of media router manager that controls media route of other applications.
@@ -328,6 +330,9 @@
         if (client != null) {
             try {
                 int requestId = mNextRequestId.getAndIncrement();
+                //TODO: Ensure that every request is eventually removed.
+                mTransferRequests.add(new TransferRequest(requestId, sessionInfo, route));
+
                 mMediaRouterService.requestCreateSessionWithManager(
                         client, requestId, sessionInfo.getClientPackageName(), route);
             } catch (RemoteException ex) {
@@ -446,6 +451,77 @@
         }
     }
 
+    void createSessionOnHandler(int requestId, RoutingSessionInfo sessionInfo) {
+        TransferRequest matchingRequest = null;
+        for (TransferRequest request : mTransferRequests) {
+            if (request.mRequestId == requestId) {
+                matchingRequest = request;
+                break;
+            }
+        }
+
+        if (matchingRequest == null) {
+            return;
+        }
+
+        mTransferRequests.remove(matchingRequest);
+
+        MediaRoute2Info requestedRoute = matchingRequest.mTargetRoute;
+
+        if (sessionInfo == null) {
+            notifyTransferFailed(matchingRequest.mOldSessionInfo, requestedRoute);
+            return;
+        } else if (!sessionInfo.getSelectedRoutes().contains(requestedRoute.getId())) {
+            Log.w(TAG, "The session does not contain the requested route. "
+                    + "(requestedRouteId=" + requestedRoute.getId()
+                    + ", actualRoutes=" + sessionInfo.getSelectedRoutes()
+                    + ")");
+            notifyTransferFailed(matchingRequest.mOldSessionInfo, requestedRoute);
+            return;
+        } else if (!TextUtils.equals(requestedRoute.getProviderId(),
+                sessionInfo.getProviderId())) {
+            Log.w(TAG, "The session's provider ID does not match the requested route's. "
+                    + "(requested route's providerId=" + requestedRoute.getProviderId()
+                    + ", actual providerId=" + sessionInfo.getProviderId()
+                    + ")");
+            notifyTransferFailed(matchingRequest.mOldSessionInfo, requestedRoute);
+            return;
+        }
+        notifyTransferred(matchingRequest.mOldSessionInfo, sessionInfo);
+    }
+
+    void handleFailureOnHandler(int requestId, int reason) {
+        TransferRequest matchingRequest = null;
+        for (TransferRequest request : mTransferRequests) {
+            if (request.mRequestId == requestId) {
+                matchingRequest = request;
+                break;
+            }
+        }
+
+        if (matchingRequest != null) {
+            mTransferRequests.remove(matchingRequest);
+            notifyTransferFailed(matchingRequest.mOldSessionInfo, matchingRequest.mTargetRoute);
+            return;
+        }
+        notifyRequestFailed(reason);
+    }
+
+    void handleSessionsUpdated(RoutingSessionInfo sessionInfo) {
+        for (TransferRequest request : mTransferRequests) {
+            String sessionId = request.mOldSessionInfo.getId();
+            if (!TextUtils.equals(sessionId, sessionInfo.getId())) {
+                continue;
+            }
+            if (sessionInfo.getSelectedRoutes().contains(request.mTargetRoute.getId())) {
+                notifyTransferred(request.mOldSessionInfo, sessionInfo);
+                mTransferRequests.remove(request);
+                break;
+            }
+        }
+        notifySessionUpdated(sessionInfo);
+    }
+
     private void notifyRoutesAdded(List<MediaRoute2Info> routes) {
         for (CallbackRecord record: mCallbackRecords) {
             record.mExecutor.execute(
@@ -467,16 +543,9 @@
         }
     }
 
-    void notifySessionCreated(RoutingSessionInfo sessionInfo) {
+    void notifySessionUpdated(RoutingSessionInfo sessionInfo) {
         for (CallbackRecord record : mCallbackRecords) {
-            record.mExecutor.execute(() -> record.mCallback.onSessionCreated(
-                    new RoutingController(sessionInfo)));
-        }
-    }
-
-    void notifySessionInfosChanged() {
-        for (CallbackRecord record : mCallbackRecords) {
-            record.mExecutor.execute(() -> record.mCallback.onSessionsUpdated());
+            record.mExecutor.execute(() -> record.mCallback.onSessionUpdated(sessionInfo));
         }
     }
 
@@ -569,7 +638,7 @@
      *
      * @see #getSelectedRoutes(RoutingSessionInfo)
      * @see #getSelectableRoutes(RoutingSessionInfo)
-     * @see Callback#onSessionsUpdated()
+     * @see Callback#onSessionUpdated(RoutingSessionInfo)
      */
     public void selectRoute(@NonNull RoutingSessionInfo sessionInfo,
             @NonNull MediaRoute2Info route) {
@@ -614,7 +683,7 @@
      *
      * @see #getSelectedRoutes(RoutingSessionInfo)
      * @see #getDeselectableRoutes(RoutingSessionInfo)
-     * @see Callback#onSessionsUpdated()
+     * @see Callback#onSessionUpdated(RoutingSessionInfo)
      */
     public void deselectRoute(@NonNull RoutingSessionInfo sessionInfo,
             @NonNull MediaRoute2Info route) {
@@ -667,13 +736,15 @@
             return;
         }
 
+        int requestId = mNextRequestId.getAndIncrement();
+        mTransferRequests.add(new TransferRequest(requestId, sessionInfo, route));
+
         Client client;
         synchronized (sLock) {
             client = mClient;
         }
         if (client != null) {
             try {
-                int requestId = mNextRequestId.getAndIncrement();
                 mMediaRouterService.transferToRouteWithManager(
                         mClient, requestId, sessionInfo.getId(), route);
             } catch (RemoteException ex) {
@@ -884,20 +955,12 @@
         public void onRoutesChanged(@NonNull List<MediaRoute2Info> routes) {}
 
         /**
-         * Called when a routing session is created.
-         *
-         * @param controller the controller to control the created session
+         * Called when a session is changed.
+         * @param sessionInfo the updated session
          */
-        public void onSessionCreated(@NonNull RoutingController controller) {}
+        public void onSessionUpdated(@NonNull RoutingSessionInfo sessionInfo) {}
 
         /**
-         * Called when at least one session info is changed.
-         * Call {@link #getActiveSessions()} to get current active session info.
-         */
-        public void onSessionsUpdated() {}
-
-        //TODO: Call this.
-        /**
          * Called when media is transferred.
          *
          * @param oldSession the previous session
@@ -906,7 +969,6 @@
         public void onTransferred(@NonNull RoutingSessionInfo oldSession,
                 @Nullable RoutingSessionInfo newSession) { }
 
-        //TODO: Call this.
         /**
          * Called when {@link #transfer(RoutingSessionInfo, MediaRoute2Info)} fails.
          */
@@ -971,25 +1033,37 @@
         }
     }
 
+    static final class TransferRequest {
+        public final int mRequestId;
+        public final RoutingSessionInfo mOldSessionInfo;
+        public final MediaRoute2Info mTargetRoute;
+
+        TransferRequest(int requestId, @NonNull RoutingSessionInfo oldSessionInfo,
+                @NonNull MediaRoute2Info targetRoute) {
+            mRequestId = requestId;
+            mOldSessionInfo = oldSessionInfo;
+            mTargetRoute = targetRoute;
+        }
+    }
+
     class Client extends IMediaRouter2Manager.Stub {
         @Override
-        public void notifySessionCreated(RoutingSessionInfo sessionInfo) {
-            mHandler.sendMessage(obtainMessage(MediaRouter2Manager::notifySessionCreated,
-                    MediaRouter2Manager.this, sessionInfo));
+        public void notifySessionCreated(int requestId, RoutingSessionInfo sessionInfo) {
+            mHandler.sendMessage(obtainMessage(MediaRouter2Manager::createSessionOnHandler,
+                    MediaRouter2Manager.this, requestId, sessionInfo));
         }
 
         @Override
-        public void notifySessionsUpdated() {
-            mHandler.sendMessage(obtainMessage(MediaRouter2Manager::notifySessionInfosChanged,
-                    MediaRouter2Manager.this));
-            // do nothing
+        public void notifySessionUpdated(RoutingSessionInfo sessionInfo) {
+            mHandler.sendMessage(obtainMessage(MediaRouter2Manager::handleSessionsUpdated,
+                    MediaRouter2Manager.this, sessionInfo));
         }
 
         @Override
         public void notifyRequestFailed(int requestId, int reason) {
             // Note: requestId is not used.
-            mHandler.sendMessage(obtainMessage(MediaRouter2Manager::notifyRequestFailed,
-                    MediaRouter2Manager.this, reason));
+            mHandler.sendMessage(obtainMessage(MediaRouter2Manager::handleFailureOnHandler,
+                    MediaRouter2Manager.this, requestId, reason));
         }
 
         @Override
diff --git a/media/java/android/media/RouteDiscoveryPreference.java b/media/java/android/media/RouteDiscoveryPreference.java
index 2e038e6..68f2964 100644
--- a/media/java/android/media/RouteDiscoveryPreference.java
+++ b/media/java/android/media/RouteDiscoveryPreference.java
@@ -21,6 +21,7 @@
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.text.TextUtils;
 
 import java.util.ArrayList;
 import java.util.Collection;
@@ -29,6 +30,7 @@
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 /**
  * A media route discovery preference describing the features of routes that media router
@@ -169,8 +171,9 @@
         Bundle mExtras;
 
         public Builder(@NonNull List<String> preferredFeatures, boolean activeScan) {
-            mPreferredFeatures = new ArrayList<>(Objects.requireNonNull(preferredFeatures,
-                    "preferredFeatures must not be null"));
+            Objects.requireNonNull(preferredFeatures, "preferredFeatures must not be null");
+            mPreferredFeatures = preferredFeatures.stream().filter(str -> !TextUtils.isEmpty(str))
+                    .collect(Collectors.toList());
             mActiveScan = activeScan;
         }
 
@@ -211,8 +214,9 @@
          */
         @NonNull
         public Builder setPreferredFeatures(@NonNull List<String> preferredFeatures) {
-            mPreferredFeatures = new ArrayList<>(Objects.requireNonNull(preferredFeatures,
-                            "preferredFeatures must not be null"));
+            Objects.requireNonNull(preferredFeatures, "preferredFeatures must not be null");
+            mPreferredFeatures = preferredFeatures.stream().filter(str -> !TextUtils.isEmpty(str))
+                    .collect(Collectors.toList());
             return this;
         }
 
diff --git a/media/java/android/media/soundtrigger_middleware/ISoundTriggerMiddlewareService.aidl b/media/java/android/media/soundtrigger_middleware/ISoundTriggerMiddlewareService.aidl
index 8033307..06c3907 100644
--- a/media/java/android/media/soundtrigger_middleware/ISoundTriggerMiddlewareService.aidl
+++ b/media/java/android/media/soundtrigger_middleware/ISoundTriggerMiddlewareService.aidl
@@ -39,10 +39,4 @@
      * one of the handles from the returned list.
      */
     ISoundTriggerModule attach(int handle, ISoundTriggerCallback callback);
-
-    /**
-     * Notify the service that external input capture is taking place. This may cause some of the
-     * active recognitions to be aborted.
-     */
-    void setExternalCaptureState(boolean active);
 }
\ No newline at end of file
diff --git a/media/java/android/media/tv/tuner/Tuner.java b/media/java/android/media/tv/tuner/Tuner.java
index d4494acb7e..cf1f1b5 100644
--- a/media/java/android/media/tv/tuner/Tuner.java
+++ b/media/java/android/media/tv/tuner/Tuner.java
@@ -602,6 +602,9 @@
      */
     @Nullable
     public FrontendStatus getFrontendStatus(@NonNull @FrontendStatusType int[] statusTypes) {
+        if (mFrontend == null) {
+            throw new IllegalStateException("frontend is not initialized");
+        }
         return nativeGetFrontendStatus(statusTypes);
     }
 
diff --git a/media/jni/android_media_tv_Tuner.cpp b/media/jni/android_media_tv_Tuner.cpp
index 4a7e8e1..312e5fe 100644
--- a/media/jni/android_media_tv_Tuner.cpp
+++ b/media/jni/android_media_tv_Tuner.cpp
@@ -124,7 +124,11 @@
 using ::android::hardware::tv::tuner::V1_0::FrontendIsdbtMode;
 using ::android::hardware::tv::tuner::V1_0::FrontendIsdbtModulation;
 using ::android::hardware::tv::tuner::V1_0::FrontendIsdbtSettings;
+using ::android::hardware::tv::tuner::V1_0::FrontendModulationStatus;
 using ::android::hardware::tv::tuner::V1_0::FrontendScanAtsc3PlpInfo;
+using ::android::hardware::tv::tuner::V1_0::FrontendStatus;
+using ::android::hardware::tv::tuner::V1_0::FrontendStatusAtsc3PlpInfo;
+using ::android::hardware::tv::tuner::V1_0::FrontendStatusType;
 using ::android::hardware::tv::tuner::V1_0::FrontendType;
 using ::android::hardware::tv::tuner::V1_0::ITuner;
 using ::android::hardware::tv::tuner::V1_0::LnbPosition;
@@ -1453,6 +1457,254 @@
             numBytesInSectionFilter, filterCaps, linkCaps, bTimeFilter);
 }
 
+jobject JTuner::getFrontendStatus(jintArray types) {
+    if (mFe == NULL) {
+        return NULL;
+    }
+    JNIEnv *env = AndroidRuntime::getJNIEnv();
+    jsize size = env->GetArrayLength(types);
+    std::vector<FrontendStatusType> v(size);
+    env->GetIntArrayRegion(types, 0, size, reinterpret_cast<jint*>(&v[0]));
+
+    Result res;
+    hidl_vec<FrontendStatus> status;
+    mFe->getStatus(v,
+            [&](Result r, const hidl_vec<FrontendStatus>& s) {
+                res = r;
+                status = s;
+            });
+    if (res != Result::SUCCESS) {
+        return NULL;
+    }
+
+    jclass clazz = env->FindClass("android/media/tv/tuner/frontend/FrontendStatus");
+    jmethodID init = env->GetMethodID(clazz, "<init>", "()V");
+    jobject statusObj = env->NewObject(clazz, init);
+
+    jclass intClazz = env->FindClass("java/lang/Integer");
+    jmethodID initInt = env->GetMethodID(intClazz, "<init>", "(I)V");
+    jclass booleanClazz = env->FindClass("java/lang/Boolean");
+    jmethodID initBoolean = env->GetMethodID(booleanClazz, "<init>", "(Z)V");
+
+    for (auto s : status) {
+        switch(s.getDiscriminator()) {
+            case FrontendStatus::hidl_discriminator::isDemodLocked: {
+                jfieldID field = env->GetFieldID(clazz, "mIsDemodLocked", "Ljava/lang/Boolean;");
+                jobject newBooleanObj = env->NewObject(
+                        booleanClazz, initBoolean, static_cast<jboolean>(s.isDemodLocked()));
+                env->SetObjectField(statusObj, field, newBooleanObj);
+                break;
+            }
+            case FrontendStatus::hidl_discriminator::snr: {
+                jfieldID field = env->GetFieldID(clazz, "mSnr", "Ljava/lang/Integer;");
+                jobject newIntegerObj = env->NewObject(
+                        intClazz, initInt, static_cast<jint>(s.snr()));
+                env->SetObjectField(statusObj, field, newIntegerObj);
+                break;
+            }
+            case FrontendStatus::hidl_discriminator::ber: {
+                jfieldID field = env->GetFieldID(clazz, "mBer", "Ljava/lang/Integer;");
+                jobject newIntegerObj = env->NewObject(
+                        intClazz, initInt, static_cast<jint>(s.ber()));
+                env->SetObjectField(statusObj, field, newIntegerObj);
+                break;
+            }
+            case FrontendStatus::hidl_discriminator::per: {
+                jfieldID field = env->GetFieldID(clazz, "mPer", "Ljava/lang/Integer;");
+                jobject newIntegerObj = env->NewObject(
+                        intClazz, initInt, static_cast<jint>(s.per()));
+                env->SetObjectField(statusObj, field, newIntegerObj);
+                break;
+            }
+            case FrontendStatus::hidl_discriminator::preBer: {
+                jfieldID field = env->GetFieldID(clazz, "mPerBer", "Ljava/lang/Integer;");
+                jobject newIntegerObj = env->NewObject(
+                        intClazz, initInt, static_cast<jint>(s.preBer()));
+                env->SetObjectField(statusObj, field, newIntegerObj);
+                break;
+            }
+            case FrontendStatus::hidl_discriminator::signalQuality: {
+                jfieldID field = env->GetFieldID(clazz, "mSignalQuality", "Ljava/lang/Integer;");
+                jobject newIntegerObj = env->NewObject(
+                        intClazz, initInt, static_cast<jint>(s.signalQuality()));
+                env->SetObjectField(statusObj, field, newIntegerObj);
+                break;
+            }
+            case FrontendStatus::hidl_discriminator::signalStrength: {
+                jfieldID field = env->GetFieldID(clazz, "mSignalStrength", "Ljava/lang/Integer;");
+                jobject newIntegerObj = env->NewObject(
+                        intClazz, initInt, static_cast<jint>(s.signalStrength()));
+                env->SetObjectField(statusObj, field, newIntegerObj);
+                break;
+            }
+            case FrontendStatus::hidl_discriminator::symbolRate: {
+                jfieldID field = env->GetFieldID(clazz, "mSymbolRate", "Ljava/lang/Integer;");
+                jobject newIntegerObj = env->NewObject(
+                        intClazz, initInt, static_cast<jint>(s.symbolRate()));
+                env->SetObjectField(statusObj, field, newIntegerObj);
+                break;
+            }
+            case FrontendStatus::hidl_discriminator::innerFec: {
+                jfieldID field = env->GetFieldID(clazz, "mInnerFec", "Ljava/lang/Long;");
+                jclass longClazz = env->FindClass("java/lang/Long");
+                jmethodID initLong = env->GetMethodID(longClazz, "<init>", "(J)V");
+                jobject newLongObj = env->NewObject(
+                        longClazz, initLong, static_cast<jlong>(s.innerFec()));
+                env->SetObjectField(statusObj, field, newLongObj);
+                break;
+            }
+            case FrontendStatus::hidl_discriminator::modulation: {
+                jfieldID field = env->GetFieldID(clazz, "mModulation", "Ljava/lang/Integer;");
+                FrontendModulationStatus modulation = s.modulation();
+                jint intModulation;
+                bool valid = true;
+                switch(modulation.getDiscriminator()) {
+                    case FrontendModulationStatus::hidl_discriminator::dvbc: {
+                        intModulation = static_cast<jint>(modulation.dvbc());
+                        break;
+                    }
+                    case FrontendModulationStatus::hidl_discriminator::dvbs: {
+                        intModulation = static_cast<jint>(modulation.dvbs());
+                        break;
+                    }
+                    case FrontendModulationStatus::hidl_discriminator::isdbs: {
+                        intModulation = static_cast<jint>(modulation.isdbs());
+                        break;
+                    }
+                    case FrontendModulationStatus::hidl_discriminator::isdbs3: {
+                        intModulation = static_cast<jint>(modulation.isdbs3());
+                        break;
+                    }
+                    case FrontendModulationStatus::hidl_discriminator::isdbt: {
+                        intModulation = static_cast<jint>(modulation.isdbt());
+                        break;
+                    }
+                    default: {
+                        valid = false;
+                        break;
+                    }
+                }
+                if (valid) {
+                    jobject newIntegerObj = env->NewObject(intClazz, initInt, intModulation);
+                    env->SetObjectField(statusObj, field, newIntegerObj);
+                }
+                break;
+            }
+            case FrontendStatus::hidl_discriminator::inversion: {
+                jfieldID field = env->GetFieldID(clazz, "mInversion", "Ljava/lang/Integer;");
+                jobject newIntegerObj = env->NewObject(
+                        intClazz, initInt, static_cast<jint>(s.inversion()));
+                env->SetObjectField(statusObj, field, newIntegerObj);
+                break;
+            }
+            case FrontendStatus::hidl_discriminator::lnbVoltage: {
+                jfieldID field = env->GetFieldID(clazz, "mLnbVoltage", "Ljava/lang/Integer;");
+                jobject newIntegerObj = env->NewObject(
+                        intClazz, initInt, static_cast<jint>(s.lnbVoltage()));
+                env->SetObjectField(statusObj, field, newIntegerObj);
+                break;
+            }
+            case FrontendStatus::hidl_discriminator::plpId: {
+                jfieldID field = env->GetFieldID(clazz, "mPlpId", "Ljava/lang/Integer;");
+                jobject newIntegerObj = env->NewObject(
+                        intClazz, initInt, static_cast<jint>(s.plpId()));
+                env->SetObjectField(statusObj, field, newIntegerObj);
+                break;
+            }
+            case FrontendStatus::hidl_discriminator::isEWBS: {
+                jfieldID field = env->GetFieldID(clazz, "mIsEwbs", "Ljava/lang/Boolean;");
+                jobject newBooleanObj = env->NewObject(
+                        booleanClazz, initBoolean, static_cast<jboolean>(s.isEWBS()));
+                env->SetObjectField(statusObj, field, newBooleanObj);
+                break;
+            }
+            case FrontendStatus::hidl_discriminator::agc: {
+                jfieldID field = env->GetFieldID(clazz, "mAgc", "Ljava/lang/Integer;");
+                jobject newIntegerObj = env->NewObject(
+                        intClazz, initInt, static_cast<jint>(s.agc()));
+                env->SetObjectField(statusObj, field, newIntegerObj);
+                break;
+            }
+            case FrontendStatus::hidl_discriminator::isLnaOn: {
+                jfieldID field = env->GetFieldID(clazz, "mIsLnaOn", "Ljava/lang/Boolean;");
+                jobject newBooleanObj = env->NewObject(
+                        booleanClazz, initBoolean, static_cast<jboolean>(s.isLnaOn()));
+                env->SetObjectField(statusObj, field, newBooleanObj);
+                break;
+            }
+            case FrontendStatus::hidl_discriminator::isLayerError: {
+                jfieldID field = env->GetFieldID(clazz, "mIsLayerErrors", "[Z");
+                hidl_vec<bool> layerErr = s.isLayerError();
+
+                jbooleanArray valObj = env->NewBooleanArray(layerErr.size());
+
+                for (size_t i = 0; i < layerErr.size(); i++) {
+                    jboolean x = layerErr[i];
+                    env->SetBooleanArrayRegion(valObj, i, 1, &x);
+                }
+                env->SetObjectField(statusObj, field, valObj);
+                break;
+            }
+            case FrontendStatus::hidl_discriminator::mer: {
+                jfieldID field = env->GetFieldID(clazz, "mMer", "Ljava/lang/Integer;");
+                jobject newIntegerObj = env->NewObject(
+                        intClazz, initInt, static_cast<jint>(s.mer()));
+                env->SetObjectField(statusObj, field, newIntegerObj);
+                break;
+            }
+            case FrontendStatus::hidl_discriminator::freqOffset: {
+                jfieldID field = env->GetFieldID(clazz, "mFreqOffset", "Ljava/lang/Integer;");
+                jobject newIntegerObj = env->NewObject(
+                        intClazz, initInt, static_cast<jint>(s.freqOffset()));
+                env->SetObjectField(statusObj, field, newIntegerObj);
+                break;
+            }
+            case FrontendStatus::hidl_discriminator::hierarchy: {
+                jfieldID field = env->GetFieldID(clazz, "mHierarchy", "Ljava/lang/Integer;");
+                jobject newIntegerObj = env->NewObject(
+                        intClazz, initInt, static_cast<jint>(s.hierarchy()));
+                env->SetObjectField(statusObj, field, newIntegerObj);
+                break;
+            }
+            case FrontendStatus::hidl_discriminator::isRfLocked: {
+                jfieldID field = env->GetFieldID(clazz, "mIsRfLocked", "Ljava/lang/Boolean;");
+                jobject newBooleanObj = env->NewObject(
+                        booleanClazz, initBoolean, static_cast<jboolean>(s.isRfLocked()));
+                env->SetObjectField(statusObj, field, newBooleanObj);
+                break;
+            }
+            case FrontendStatus::hidl_discriminator::plpInfo: {
+                jfieldID field = env->GetFieldID(clazz, "mPlpInfo",
+                        "[Landroid/media/tv/tuner/frontend/FrontendStatus$Atsc3PlpTuningInfo;");
+                jclass plpClazz = env->FindClass(
+                        "android/media/tv/tuner/frontend/FrontendStatus$Atsc3PlpTuningInfo");
+                jmethodID initPlp = env->GetMethodID(plpClazz, "<init>", "(IZI)V");
+
+                hidl_vec<FrontendStatusAtsc3PlpInfo> plpInfos = s.plpInfo();
+
+                jobjectArray valObj = env->NewObjectArray(plpInfos.size(), plpClazz, NULL);
+                for (int i = 0; i < plpInfos.size(); i++) {
+                    auto info = plpInfos[i];
+                    jint plpId = (jint) info.plpId;
+                    jboolean isLocked = (jboolean) info.isLocked;
+                    jint uec = (jint) info.uec;
+
+                    jobject plpObj = env->NewObject(plpClazz, initPlp, plpId, isLocked, uec);
+                    env->SetObjectArrayElement(valObj, i, plpObj);
+                }
+
+                env->SetObjectField(statusObj, field, valObj);
+                break;
+            }
+            default: {
+                break;
+            }
+        }
+    }
+
+    return statusObj;
+}
+
 }  // namespace android
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -2086,8 +2338,10 @@
     return tuner->setLna(enable);
 }
 
-static jobject android_media_tv_Tuner_get_frontend_status(JNIEnv, jobject, jintArray) {
-    return NULL;
+static jobject android_media_tv_Tuner_get_frontend_status(
+        JNIEnv* env, jobject thiz, jintArray types) {
+    sp<JTuner> tuner = getTuner(env, thiz);
+    return tuner->getFrontendStatus(types);
 }
 
 static jobject android_media_tv_Tuner_get_av_sync_hw_id(
diff --git a/media/jni/android_media_tv_Tuner.h b/media/jni/android_media_tv_Tuner.h
index 7e860b9..e6f10b2 100644
--- a/media/jni/android_media_tv_Tuner.h
+++ b/media/jni/android_media_tv_Tuner.h
@@ -187,6 +187,7 @@
     jobject openDescrambler();
     jobject openDvr(DvrType type, jlong bufferSize);
     jobject getDemuxCaps();
+    jobject getFrontendStatus(jintArray types);
 
 protected:
     Result openDemux();
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
index 77e8f97..6ca564f 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
@@ -231,9 +231,10 @@
         addRouterCallback(new RouteCallback() {});
         addManagerCallback(new MediaRouter2Manager.Callback() {
             @Override
-            public void onSessionCreated(MediaRouter2Manager.RoutingController controller) {
-                if (TextUtils.equals(mPackageName, controller.getClientPackageName())
-                        && createRouteMap(controller.getSelectedRoutes()).containsKey(ROUTE_ID1)) {
+            public void onTransferred(RoutingSessionInfo oldSessionInfo,
+                    RoutingSessionInfo newSessionInfo) {
+                if (TextUtils.equals(mPackageName, newSessionInfo.getClientPackageName())
+                        && newSessionInfo.getSelectedRoutes().contains(ROUTE_ID1)) {
                     latch.countDown();
                 }
             }
@@ -268,8 +269,9 @@
 
         addManagerCallback(new MediaRouter2Manager.Callback() {
             @Override
-            public void onSessionCreated(MediaRouter2Manager.RoutingController controller) {
-                assertNotNull(controller);
+            public void onTransferred(RoutingSessionInfo oldSessionInfo,
+                    RoutingSessionInfo newSessionInfo) {
+                assertNotNull(newSessionInfo);
                 onSessionCreatedLatch.countDown();
             }
         });
@@ -352,8 +354,9 @@
         // create a controller
         addManagerCallback(new MediaRouter2Manager.Callback() {
             @Override
-            public void onSessionCreated(MediaRouter2Manager.RoutingController controller) {
-                assertNotNull(controller);
+            public void onTransferred(RoutingSessionInfo oldSessionInfo,
+                    RoutingSessionInfo newSessionInfo) {
+                assertNotNull(newSessionInfo);
                 onSessionCreatedLatch.countDown();
             }
         });
@@ -383,13 +386,12 @@
 
         addManagerCallback(new MediaRouter2Manager.Callback() {
             @Override
-            public void onSessionsUpdated() {
-                List<RoutingSessionInfo> sessions = mManager.getRoutingSessions(mPackageName);
-                if (sessions.size() != 2) {
+            public void onSessionUpdated(RoutingSessionInfo updatedSessionInfo) {
+                if (!TextUtils.equals(sessionInfo.getId(), updatedSessionInfo.getId())) {
                     return;
                 }
 
-                if (sessions.get(1).getVolume() == targetVolume) {
+                if (updatedSessionInfo.getVolume() == targetVolume) {
                     volumeChangedLatch.countDown();
                 }
             }
diff --git a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java
index 3ee92bd7..df82753 100644
--- a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java
@@ -150,7 +150,7 @@
             ex.rethrowFromSystemServer();
         }
 
-        mAutoHideController.addAutoHideUiElement(new AutoHideUiElement() {
+        mAutoHideController.setNavigationBar(new AutoHideUiElement() {
             @Override
             public void synchronizeState() {
                 // No op.
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
index 02604d8..cd45fc9 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBar.java
@@ -65,6 +65,7 @@
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.NotificationShadeDepthController;
 import com.android.systemui.statusbar.NotificationViewHierarchyManager;
 import com.android.systemui.statusbar.PulseExpansionHandler;
 import com.android.systemui.statusbar.SuperStatusBarViewFactory;
@@ -225,6 +226,7 @@
             KeyguardIndicationController keyguardIndicationController,
             DismissCallbackRegistry dismissCallbackRegistry,
             StatusBarTouchableRegionManager statusBarTouchableRegionManager,
+            Lazy<NotificationShadeDepthController> depthControllerLazy,
             /* Car Settings injected components. */
             CarNavigationBarController carNavigationBarController) {
         super(
@@ -304,6 +306,7 @@
                 phoneStatusBarPolicy,
                 keyguardIndicationController,
                 dismissCallbackRegistry,
+                depthControllerLazy,
                 statusBarTouchableRegionManager);
         mUserSwitcherController = userSwitcherController;
         mScrimController = scrimController;
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java
index 1baa1f6..e163173 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java
+++ b/packages/CarSystemUI/src/com/android/systemui/statusbar/car/CarStatusBarModule.java
@@ -51,6 +51,7 @@
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.NotificationShadeDepthController;
 import com.android.systemui.statusbar.NotificationViewHierarchyManager;
 import com.android.systemui.statusbar.PulseExpansionHandler;
 import com.android.systemui.statusbar.SuperStatusBarViewFactory;
@@ -200,6 +201,7 @@
             KeyguardIndicationController keyguardIndicationController,
             DismissCallbackRegistry dismissCallbackRegistry,
             StatusBarTouchableRegionManager statusBarTouchableRegionManager,
+            Lazy<NotificationShadeDepthController> notificationShadeDepthControllerLazy,
             CarNavigationBarController carNavigationBarController) {
         return new CarStatusBar(
                 context,
@@ -278,6 +280,7 @@
                 keyguardIndicationController,
                 dismissCallbackRegistry,
                 statusBarTouchableRegionManager,
+                notificationShadeDepthControllerLazy,
                 carNavigationBarController);
     }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
index fc29f5c..2f10394 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardViewController.java
@@ -57,27 +57,27 @@
     /**
      * Called when the device started going to sleep.
      */
-    void onStartedGoingToSleep();
+    default void onStartedGoingToSleep() {};
 
     /**
      * Called when the device has finished going to sleep.
      */
-    void onFinishedGoingToSleep();
+    default void onFinishedGoingToSleep() {};
 
     /**
      * Called when the device started waking up.
      */
-    void onStartedWakingUp();
+    default void onStartedWakingUp() {};
 
     /**
      * Called when the device started turning on.
      */
-    void onScreenTurningOn();
+    default void onScreenTurningOn() {};
 
     /**
      * Called when the device has finished turning on.
      */
-    void onScreenTurnedOn();
+    default void onScreenTurnedOn() {};
 
     /**
      * Sets whether the Keyguard needs input.
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
index cc4ee89..f6368c4 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
@@ -109,7 +109,7 @@
     protected static final String CONSTRAINED_KEY = "should_constrain";
 
     public static final int INVOCATION_TYPE_GESTURE = 1;
-    public static final int INVOCATION_TYPE_ACTIVE_EDGE = 2;
+    public static final int INVOCATION_TYPE_OTHER = 2;
     public static final int INVOCATION_TYPE_VOICE = 3;
     public static final int INVOCATION_TYPE_QUICK_SEARCH_BAR = 4;
     public static final int INVOCATION_HOME_BUTTON_LONG_PRESS = 5;
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
index 5c66462..496456d 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
@@ -125,6 +125,7 @@
                     // Custom options so there is no activity transition animation
                     ActivityOptions options = ActivityOptions.makeCustomAnimation(getContext(),
                             0 /* enterResId */, 0 /* exitResId */);
+                    options.setTaskAlwaysOnTop(true);
                     // Post to keep the lifecycle normal
                     post(() -> {
                         if (DEBUG_BUBBLE_EXPANDED_VIEW) {
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index fee33dc..6a7b0da 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -1441,7 +1441,7 @@
 
     /** Expands the clicked bubble. */
     public void expandBubble(Bubble bubble) {
-        if (bubble.equals(mBubbleData.getSelectedBubble())) {
+        if (bubble != null && bubble.equals(mBubbleData.getSelectedBubble())) {
             // If the bubble we're supposed to expand is the selected bubble, that means the
             // overflow bubble is currently expanded. Don't tell BubbleData to set this bubble as
             // selected, since it already is. Just call the stack's setSelectedBubble to expand it.
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTaskView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTaskView.java
index 0d6d137..06205c5 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTaskView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleTaskView.java
@@ -23,8 +23,8 @@
 import android.annotation.Nullable;
 import android.app.ActivityOptions;
 import android.app.PendingIntent;
-import android.app.TaskEmbedder;
-import android.app.TaskOrganizerTaskEmbedder;
+import android.window.TaskEmbedder;
+import android.window.TaskOrganizerTaskEmbedder;
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ShortcutInfo;
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
index 3bed338..f7f9afd 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeMachine.java
@@ -16,6 +16,9 @@
 
 package com.android.systemui.doze;
 
+import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE;
+import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_WAKING;
+
 import android.annotation.MainThread;
 import android.hardware.display.AmbientDisplayConfiguration;
 import android.os.Trace;
@@ -368,8 +371,8 @@
             case DOZE_PULSE_DONE:
                 final State nextState;
                 @Wakefulness int wakefulness = mWakefulnessLifecycle.getWakefulness();
-                if (wakefulness == WakefulnessLifecycle.WAKEFULNESS_AWAKE
-                        || wakefulness == WakefulnessLifecycle.WAKEFULNESS_WAKING) {
+                if (state != State.INITIALIZED && (wakefulness == WAKEFULNESS_AWAKE
+                        || wakefulness == WAKEFULNESS_WAKING)) {
                     nextState = State.FINISH;
                 } else if (mDockManager.isDocked()) {
                     nextState = mDockManager.isHidden() ? State.DOZE : State.DOZE_AOD_DOCKED;
diff --git a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
index c40e9c0..15c9dba 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/PipTaskOrganizer.java
@@ -41,8 +41,8 @@
 import android.util.Log;
 import android.util.Size;
 import android.view.SurfaceControl;
-import android.window.ITaskOrganizer;
-import android.window.IWindowContainer;
+import android.window.TaskOrganizer;
+import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 import android.window.WindowOrganizer;
 
@@ -60,7 +60,7 @@
 /**
  * Manages PiP tasks such as resize and offset.
  *
- * This class listens on {@link ITaskOrganizer} callbacks for windowing mode change
+ * This class listens on {@link TaskOrganizer} callbacks for windowing mode change
  * both to and from PiP and issues corresponding animation if applicable.
  * Normally, we apply series of {@link SurfaceControl.Transaction} when the animator is running
  * and files a final {@link WindowContainerTransaction} at the end of the transition.
@@ -68,7 +68,7 @@
  * This class is also responsible for general resize/offset PiP operations within SysUI component,
  * see also {@link com.android.systemui.pip.phone.PipMotionHelper}.
  */
-public class PipTaskOrganizer extends ITaskOrganizer.Stub {
+public class PipTaskOrganizer extends TaskOrganizer {
     private static final String TAG = PipTaskOrganizer.class.getSimpleName();
 
     private static final int MSG_RESIZE_IMMEDIATE = 1;
@@ -182,7 +182,7 @@
     };
 
     private ActivityManager.RunningTaskInfo mTaskInfo;
-    private IWindowContainer mToken;
+    private WindowContainerToken mToken;
     private SurfaceControl mLeash;
     private boolean mInPip;
     private @PipAnimationController.AnimationType int mOneShotAnimationType = ANIM_TYPE_BOUNDS;
@@ -234,13 +234,9 @@
      * @param animationDurationMs duration in millisecond for the exiting PiP transition
      */
     public void dismissPip(int animationDurationMs) {
-        try {
-            final WindowContainerTransaction wct = new WindowContainerTransaction();
-            wct.setActivityWindowingMode(mToken, WINDOWING_MODE_FULLSCREEN);
-            WindowOrganizer.applyTransaction(wct);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Failed to apply container transaction", e);
-        }
+        final WindowContainerTransaction wct = new WindowContainerTransaction();
+        wct.setActivityWindowingMode(mToken, WINDOWING_MODE_FULLSCREEN);
+        WindowOrganizer.applyTransaction(wct);
         final Rect destinationBounds = mBoundsToRestore.remove(mToken.asBinder());
         scheduleAnimateResizePip(mLastReportedBounds, destinationBounds,
                 TRANSITION_DIRECTION_TO_FULLSCREEN, animationDurationMs,
@@ -258,11 +254,8 @@
         mTaskInfo = info;
         mToken = mTaskInfo.token;
         mInPip = true;
-        try {
-            mLeash = mToken.getLeash();
-        } catch (RemoteException e) {
-            throw new RuntimeException("Unable to get leash", e);
-        }
+        mLeash = mToken.getLeash();
+
         final Rect currentBounds = mTaskInfo.configuration.windowConfiguration.getBounds();
         mBoundsToRestore.put(mToken.asBinder(), currentBounds);
         if (mOneShotAnimationType == ANIM_TYPE_BOUNDS) {
@@ -290,8 +283,8 @@
      */
     @Override
     public void onTaskVanished(ActivityManager.RunningTaskInfo info) {
-        IWindowContainer token = info.token;
-        Objects.requireNonNull(token, "Requires valid IWindowContainer");
+        WindowContainerToken token = info.token;
+        Objects.requireNonNull(token, "Requires valid WindowContainerToken");
         if (token.asBinder() != mToken.asBinder()) {
             Log.wtf(TAG, "Unrecognized token: " + token);
             return;
@@ -502,30 +495,26 @@
                     + "directly");
         }
         mLastReportedBounds.set(destinationBounds);
-        try {
-            final WindowContainerTransaction wct = new WindowContainerTransaction();
-            final Rect taskBounds;
-            if (direction == TRANSITION_DIRECTION_TO_FULLSCREEN) {
-                // If we are animating to fullscreen, then we need to reset the override bounds
-                // on the task to ensure that the task "matches" the parent's bounds, this applies
-                // also to the final windowing mode, which should be reset to undefined rather than
-                // fullscreen.
-                taskBounds = null;
-                wct.setWindowingMode(mToken, WINDOWING_MODE_UNDEFINED)
-                        .setActivityWindowingMode(mToken, WINDOWING_MODE_UNDEFINED);
-            } else {
-                taskBounds = destinationBounds;
-            }
-            if (direction == TRANSITION_DIRECTION_TO_PIP) {
-                wct.scheduleFinishEnterPip(mToken, taskBounds);
-            } else {
-                wct.setBounds(mToken, taskBounds);
-            }
-            wct.setBoundsChangeTransaction(mToken, tx);
-            WindowOrganizer.applyTransaction(wct);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Failed to apply container transaction", e);
+        final WindowContainerTransaction wct = new WindowContainerTransaction();
+        final Rect taskBounds;
+        if (direction == TRANSITION_DIRECTION_TO_FULLSCREEN) {
+            // If we are animating to fullscreen, then we need to reset the override bounds
+            // on the task to ensure that the task "matches" the parent's bounds, this applies
+            // also to the final windowing mode, which should be reset to undefined rather than
+            // fullscreen.
+            taskBounds = null;
+            wct.setWindowingMode(mToken, WINDOWING_MODE_UNDEFINED)
+                    .setActivityWindowingMode(mToken, WINDOWING_MODE_UNDEFINED);
+        } else {
+            taskBounds = destinationBounds;
         }
+        if (direction == TRANSITION_DIRECTION_TO_PIP) {
+            wct.scheduleFinishEnterPip(mToken, taskBounds);
+        } else {
+            wct.setBounds(mToken, taskBounds);
+        }
+        wct.setBoundsChangeTransaction(mToken, tx);
+        WindowOrganizer.applyTransaction(wct);
     }
 
     private void animateResizePip(Rect currentBounds, Rect destinationBounds,
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
index 8a25f4d..99d6df5 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
@@ -18,7 +18,6 @@
 
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.window.WindowOrganizer.TaskOrganizer;
 
 import static com.android.systemui.pip.PipAnimationController.TRANSITION_DIRECTION_TO_FULLSCREEN;
 
@@ -234,7 +233,7 @@
         mPipBoundsHandler.onDisplayInfoChanged(displayInfo);
 
         try {
-            TaskOrganizer.registerOrganizer(mPipTaskOrganizer, WINDOWING_MODE_PINNED);
+            mPipTaskOrganizer.registerOrganizer(WINDOWING_MODE_PINNED);
             ActivityManager.StackInfo stackInfo = activityTaskManager.getStackInfo(
                     WINDOWING_MODE_PINNED, ACTIVITY_TYPE_UNDEFINED);
             if (stackInfo != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
index c6e6da1..52c8960 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/tv/PipManager.java
@@ -19,7 +19,6 @@
 import static android.app.ActivityTaskManager.INVALID_STACK_ID;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.window.WindowOrganizer.TaskOrganizer;
 
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.ActivityManager.StackInfo;
@@ -294,7 +293,7 @@
 
         try {
             WindowManagerWrapper.getInstance().addPinnedStackListener(mPinnedStackListener);
-            TaskOrganizer.registerOrganizer(mPipTaskOrganizer, WINDOWING_MODE_PINNED);
+            mPipTaskOrganizer.registerOrganizer(WINDOWING_MODE_PINNED);
         } catch (RemoteException | UnsupportedOperationException e) {
             Log.e(TAG, "Failed to register pinned stack listener", e);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
index 66e3211..b71c4eb 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/Divider.java
@@ -20,7 +20,6 @@
 import static android.content.res.Configuration.SCREEN_HEIGHT_DP_UNDEFINED;
 import static android.content.res.Configuration.SCREEN_WIDTH_DP_UNDEFINED;
 import static android.view.Display.DEFAULT_DISPLAY;
-import static android.window.WindowOrganizer.TaskOrganizer;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -33,11 +32,12 @@
 import android.os.RemoteException;
 import android.provider.Settings;
 import android.util.Slog;
+import android.window.TaskOrganizer;
+import android.window.WindowContainerToken;
 import android.view.LayoutInflater;
 import android.view.SurfaceControl;
 import android.view.SurfaceSession;
 import android.view.View;
-import android.window.IWindowContainer;
 import android.window.WindowContainerTransaction;
 import android.window.WindowOrganizer;
 
@@ -181,14 +181,9 @@
         private boolean mPausedTargetAdjusted = false;
 
         private boolean getSecondaryHasFocus(int displayId) {
-            try {
-                IWindowContainer imeSplit = TaskOrganizer.getImeTarget(displayId);
-                return imeSplit != null
-                        && (imeSplit.asBinder() == mSplits.mSecondary.token.asBinder());
-            } catch (RemoteException e) {
-                Slog.w(TAG, "Failed to get IME target", e);
-            }
-            return false;
+            WindowContainerToken imeSplit = TaskOrganizer.getImeTarget(displayId);
+            return imeSplit != null
+                    && (imeSplit.asBinder() == mSplits.mSecondary.token.asBinder());
         }
 
         private void updateDimTargets() {
@@ -270,10 +265,8 @@
                 wct.setScreenSizeDp(mSplits.mSecondary.token,
                         SCREEN_WIDTH_DP_UNDEFINED, SCREEN_HEIGHT_DP_UNDEFINED);
             }
-            try {
-                WindowOrganizer.applyTransaction(wct);
-            } catch (RemoteException e) {
-            }
+
+            WindowOrganizer.applyTransaction(wct);
 
             // Update all the adjusted-for-ime states
             if (!mPaused) {
@@ -506,12 +499,8 @@
             int midPos = mSplitLayout.getSnapAlgorithm().getMiddleTarget().position;
             final WindowContainerTransaction tct = new WindowContainerTransaction();
             mSplitLayout.resizeSplits(midPos, tct);
-            try {
-                WindowOrganizer.applyTransaction(tct);
-            } catch (RemoteException e) {
-            }
-        } else if (mRotateSplitLayout != null
-                && mSplitLayout.mDisplayLayout.rotation()
+            WindowOrganizer.applyTransaction(tct);
+        } else if (mSplitLayout.mDisplayLayout.rotation()
                         == mRotateSplitLayout.mDisplayLayout.rotation()) {
             mSplitLayout.mPrimary = new Rect(mRotateSplitLayout.mPrimary);
             mSplitLayout.mSecondary = new Rect(mRotateSplitLayout.mSecondary);
@@ -653,7 +642,7 @@
             }
         }
         updateTouchable();
-        WindowManagerProxy.applyContainerTransaction(wct);
+        WindowOrganizer.applyTransaction(wct);
     }
 
     void setAdjustedForIme(boolean adjustedForIme) {
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenTaskOrganizer.java b/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenTaskOrganizer.java
index a6f6741..91d638e 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenTaskOrganizer.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/SplitScreenTaskOrganizer.java
@@ -22,7 +22,6 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static android.view.Display.DEFAULT_DISPLAY;
-import static android.window.WindowOrganizer.TaskOrganizer;
 
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.WindowConfiguration;
@@ -32,11 +31,11 @@
 import android.view.Display;
 import android.view.SurfaceControl;
 import android.view.SurfaceSession;
-import android.window.ITaskOrganizer;
+import android.window.TaskOrganizer;
 
 import java.util.ArrayList;
 
-class SplitScreenTaskOrganizer extends ITaskOrganizer.Stub {
+class SplitScreenTaskOrganizer extends TaskOrganizer {
     private static final String TAG = "SplitScreenTaskOrganizer";
     private static final boolean DEBUG = Divider.DEBUG;
 
@@ -56,8 +55,8 @@
     }
 
     void init(SurfaceSession session) throws RemoteException {
-        TaskOrganizer.registerOrganizer(this, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
-        TaskOrganizer.registerOrganizer(this, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+        registerOrganizer(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+        registerOrganizer(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
         try {
             mPrimary = TaskOrganizer.createRootTask(Display.DEFAULT_DISPLAY,
                     WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
@@ -65,9 +64,9 @@
                     WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
             mPrimarySurface = mPrimary.token.getLeash();
             mSecondarySurface = mSecondary.token.getLeash();
-        } catch (RemoteException e) {
+        } catch (Exception e) {
             // teardown to prevent callbacks
-            TaskOrganizer.unregisterOrganizer(this);
+            unregisterOrganizer();
             throw e;
         }
         mSplitScreenSupported = true;
@@ -99,14 +98,6 @@
     }
 
     @Override
-    public void onTaskAppeared(RunningTaskInfo taskInfo) {
-    }
-
-    @Override
-    public void onTaskVanished(RunningTaskInfo taskInfo) {
-    }
-
-    @Override
     public void onTaskInfoChanged(RunningTaskInfo taskInfo) {
         if (taskInfo.displayId != DEFAULT_DISPLAY) {
             return;
diff --git a/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java b/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java
index 6ed7afe..85dcbb6 100644
--- a/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java
+++ b/packages/SystemUI/src/com/android/systemui/stackdivider/WindowManagerProxy.java
@@ -20,7 +20,6 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.view.Display.DEFAULT_DISPLAY;
-import static android.window.WindowOrganizer.TaskOrganizer;
 
 import android.annotation.NonNull;
 import android.app.ActivityManager;
@@ -30,7 +29,8 @@
 import android.util.Log;
 import android.view.Display;
 import android.view.WindowManagerGlobal;
-import android.window.IWindowContainer;
+import android.window.TaskOrganizer;
+import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 import android.window.WindowOrganizer;
 
@@ -112,27 +112,21 @@
     static void applyResizeSplits(int position, SplitDisplayLayout splitLayout) {
         WindowContainerTransaction t = new WindowContainerTransaction();
         splitLayout.resizeSplits(position, t);
-        try {
-            WindowOrganizer.applyTransaction(t);
-        } catch (RemoteException e) {
-        }
+        WindowOrganizer.applyTransaction(t);
     }
 
-    private static boolean getHomeAndRecentsTasks(List<IWindowContainer> out,
-            IWindowContainer parent) {
+    private static boolean getHomeAndRecentsTasks(List<WindowContainerToken> out,
+            WindowContainerToken parent) {
         boolean resizable = false;
-        try {
-            List<ActivityManager.RunningTaskInfo> rootTasks = parent == null
-                    ? TaskOrganizer.getRootTasks(Display.DEFAULT_DISPLAY, HOME_AND_RECENTS)
-                    : TaskOrganizer.getChildTasks(parent, HOME_AND_RECENTS);
-            for (int i = 0, n = rootTasks.size(); i < n; ++i) {
-                final ActivityManager.RunningTaskInfo ti = rootTasks.get(i);
-                out.add(ti.token);
-                if (ti.topActivityType == ACTIVITY_TYPE_HOME) {
-                    resizable = ti.isResizable();
-                }
+        List<ActivityManager.RunningTaskInfo> rootTasks = parent == null
+                ? TaskOrganizer.getRootTasks(Display.DEFAULT_DISPLAY, HOME_AND_RECENTS)
+                : TaskOrganizer.getChildTasks(parent, HOME_AND_RECENTS);
+        for (int i = 0, n = rootTasks.size(); i < n; ++i) {
+            final ActivityManager.RunningTaskInfo ti = rootTasks.get(i);
+            out.add(ti.token);
+            if (ti.topActivityType == ACTIVITY_TYPE_HOME) {
+                resizable = ti.isResizable();
             }
-        } catch (RemoteException e) {
         }
         return resizable;
     }
@@ -142,11 +136,11 @@
      * split is minimized. This actually "sticks out" of the secondary split area, but when in
      * minimized mode, the secondary split gets a 'negative' crop to expose it.
      */
-    static boolean applyHomeTasksMinimized(SplitDisplayLayout layout, IWindowContainer parent,
+    static boolean applyHomeTasksMinimized(SplitDisplayLayout layout, WindowContainerToken parent,
             @NonNull WindowContainerTransaction wct) {
         // Resize the home/recents stacks to the larger minimized-state size
         final Rect homeBounds;
-        final ArrayList<IWindowContainer> homeStacks = new ArrayList<>();
+        final ArrayList<WindowContainerToken> homeStacks = new ArrayList<>();
         boolean isHomeResizable = getHomeAndRecentsTasks(homeStacks, parent);
         if (isHomeResizable) {
             homeBounds = layout.calcMinimizedHomeStackBounds();
@@ -170,36 +164,31 @@
      * @return whether the home stack is resizable
      */
     static boolean applyEnterSplit(SplitScreenTaskOrganizer tiles, SplitDisplayLayout layout) {
-        try {
-            // Set launchtile first so that any stack created after
-            // getAllStackInfos and before reparent (even if unlikely) are placed
-            // correctly.
-            TaskOrganizer.setLaunchRoot(DEFAULT_DISPLAY, tiles.mSecondary.token);
-            List<ActivityManager.RunningTaskInfo> rootTasks =
-                    TaskOrganizer.getRootTasks(DEFAULT_DISPLAY, null /* activityTypes */);
-            WindowContainerTransaction wct = new WindowContainerTransaction();
-            if (rootTasks.isEmpty()) {
-                return false;
-            }
-            tiles.mHomeAndRecentsSurfaces.clear();
-            for (int i = rootTasks.size() - 1; i >= 0; --i) {
-                final ActivityManager.RunningTaskInfo rootTask = rootTasks.get(i);
-                if (isHomeOrRecentTask(rootTask)) {
-                    tiles.mHomeAndRecentsSurfaces.add(rootTask.token.getLeash());
-                }
-                if (rootTask.configuration.windowConfiguration.getWindowingMode()
-                        != WINDOWING_MODE_FULLSCREEN) {
-                    continue;
-                }
-                wct.reparent(rootTask.token, tiles.mSecondary.token, true /* onTop */);
-            }
-            boolean isHomeResizable = applyHomeTasksMinimized(layout, null /* parent */, wct);
-            WindowOrganizer.applyTransaction(wct);
-            return isHomeResizable;
-        } catch (RemoteException e) {
-            Log.w(TAG, "Error moving fullscreen tasks to secondary split: " + e);
+        // Set launchtile first so that any stack created after
+        // getAllStackInfos and before reparent (even if unlikely) are placed
+        // correctly.
+        TaskOrganizer.setLaunchRoot(DEFAULT_DISPLAY, tiles.mSecondary.token);
+        List<ActivityManager.RunningTaskInfo> rootTasks =
+                TaskOrganizer.getRootTasks(DEFAULT_DISPLAY, null /* activityTypes */);
+        WindowContainerTransaction wct = new WindowContainerTransaction();
+        if (rootTasks.isEmpty()) {
+            return false;
         }
-        return false;
+        tiles.mHomeAndRecentsSurfaces.clear();
+        for (int i = rootTasks.size() - 1; i >= 0; --i) {
+            final ActivityManager.RunningTaskInfo rootTask = rootTasks.get(i);
+            if (isHomeOrRecentTask(rootTask)) {
+                tiles.mHomeAndRecentsSurfaces.add(rootTask.token.getLeash());
+            }
+            if (rootTask.configuration.windowConfiguration.getWindowingMode()
+                    != WINDOWING_MODE_FULLSCREEN) {
+                continue;
+            }
+            wct.reparent(rootTask.token, tiles.mSecondary.token, true /* onTop */);
+        }
+        boolean isHomeResizable = applyHomeTasksMinimized(layout, null /* parent */, wct);
+        WindowOrganizer.applyTransaction(wct);
+        return isHomeResizable;
     }
 
     private static boolean isHomeOrRecentTask(ActivityManager.RunningTaskInfo ti) {
@@ -214,82 +203,70 @@
      *                          fullscreen. {@code false} resolves the other way.
      */
     static void applyDismissSplit(SplitScreenTaskOrganizer tiles, boolean dismissOrMaximize) {
-        try {
-            // Set launch root first so that any task created after getChildContainers and
-            // before reparent (pretty unlikely) are put into fullscreen.
-            TaskOrganizer.setLaunchRoot(Display.DEFAULT_DISPLAY, null);
-            tiles.mHomeAndRecentsSurfaces.clear();
-            // TODO(task-org): Once task-org is more complete, consider using Appeared/Vanished
-            //                 plus specific APIs to clean this up.
-            List<ActivityManager.RunningTaskInfo> primaryChildren =
-                    TaskOrganizer.getChildTasks(tiles.mPrimary.token, null /* activityTypes */);
-            List<ActivityManager.RunningTaskInfo> secondaryChildren =
-                    TaskOrganizer.getChildTasks(tiles.mSecondary.token, null /* activityTypes */);
-            // In some cases (eg. non-resizable is launched), system-server will leave split-screen.
-            // as a result, the above will not capture any tasks; yet, we need to clean-up the
-            // home task bounds.
-            List<ActivityManager.RunningTaskInfo> freeHomeAndRecents =
-                    TaskOrganizer.getRootTasks(Display.DEFAULT_DISPLAY, HOME_AND_RECENTS);
-            if (primaryChildren.isEmpty() && secondaryChildren.isEmpty()
-                    && freeHomeAndRecents.isEmpty()) {
-                return;
+        // Set launch root first so that any task created after getChildContainers and
+        // before reparent (pretty unlikely) are put into fullscreen.
+        TaskOrganizer.setLaunchRoot(Display.DEFAULT_DISPLAY, null);
+        tiles.mHomeAndRecentsSurfaces.clear();
+        // TODO(task-org): Once task-org is more complete, consider using Appeared/Vanished
+        //                 plus specific APIs to clean this up.
+        List<ActivityManager.RunningTaskInfo> primaryChildren =
+                TaskOrganizer.getChildTasks(tiles.mPrimary.token, null /* activityTypes */);
+        List<ActivityManager.RunningTaskInfo> secondaryChildren =
+                TaskOrganizer.getChildTasks(tiles.mSecondary.token, null /* activityTypes */);
+        // In some cases (eg. non-resizable is launched), system-server will leave split-screen.
+        // as a result, the above will not capture any tasks; yet, we need to clean-up the
+        // home task bounds.
+        List<ActivityManager.RunningTaskInfo> freeHomeAndRecents =
+                TaskOrganizer.getRootTasks(Display.DEFAULT_DISPLAY, HOME_AND_RECENTS);
+        if (primaryChildren.isEmpty() && secondaryChildren.isEmpty()
+                && freeHomeAndRecents.isEmpty()) {
+            return;
+        }
+        WindowContainerTransaction wct = new WindowContainerTransaction();
+        if (dismissOrMaximize) {
+            // Dismissing, so move all primary split tasks first
+            for (int i = primaryChildren.size() - 1; i >= 0; --i) {
+                wct.reparent(primaryChildren.get(i).token, null /* parent */,
+                        true /* onTop */);
             }
-            WindowContainerTransaction wct = new WindowContainerTransaction();
-            if (dismissOrMaximize) {
-                // Dismissing, so move all primary split tasks first
-                for (int i = primaryChildren.size() - 1; i >= 0; --i) {
-                    wct.reparent(primaryChildren.get(i).token, null /* parent */,
-                            true /* onTop */);
+            // Don't need to worry about home tasks because they are already in the "proper"
+            // order within the secondary split.
+            for (int i = secondaryChildren.size() - 1; i >= 0; --i) {
+                final ActivityManager.RunningTaskInfo ti = secondaryChildren.get(i);
+                wct.reparent(ti.token, null /* parent */, true /* onTop */);
+                if (isHomeOrRecentTask(ti)) {
+                    wct.setBounds(ti.token, null);
                 }
-                // Don't need to worry about home tasks because they are already in the "proper"
-                // order within the secondary split.
-                for (int i = secondaryChildren.size() - 1; i >= 0; --i) {
-                    final ActivityManager.RunningTaskInfo ti = secondaryChildren.get(i);
+            }
+        } else {
+            // Maximize, so move non-home secondary split first
+            for (int i = secondaryChildren.size() - 1; i >= 0; --i) {
+                if (isHomeOrRecentTask(secondaryChildren.get(i))) {
+                    continue;
+                }
+                wct.reparent(secondaryChildren.get(i).token, null /* parent */,
+                        true /* onTop */);
+            }
+            // Find and place home tasks in-between. This simulates the fact that there was
+            // nothing behind the primary split's tasks.
+            for (int i = secondaryChildren.size() - 1; i >= 0; --i) {
+                final ActivityManager.RunningTaskInfo ti = secondaryChildren.get(i);
+                if (isHomeOrRecentTask(ti)) {
                     wct.reparent(ti.token, null /* parent */, true /* onTop */);
-                    if (isHomeOrRecentTask(ti)) {
-                        wct.setBounds(ti.token, null);
-                    }
-                }
-            } else {
-                // Maximize, so move non-home secondary split first
-                for (int i = secondaryChildren.size() - 1; i >= 0; --i) {
-                    if (isHomeOrRecentTask(secondaryChildren.get(i))) {
-                        continue;
-                    }
-                    wct.reparent(secondaryChildren.get(i).token, null /* parent */,
-                            true /* onTop */);
-                }
-                // Find and place home tasks in-between. This simulates the fact that there was
-                // nothing behind the primary split's tasks.
-                for (int i = secondaryChildren.size() - 1; i >= 0; --i) {
-                    final ActivityManager.RunningTaskInfo ti = secondaryChildren.get(i);
-                    if (isHomeOrRecentTask(ti)) {
-                        wct.reparent(ti.token, null /* parent */, true /* onTop */);
-                        // reset bounds too
-                        wct.setBounds(ti.token, null);
-                    }
-                }
-                for (int i = primaryChildren.size() - 1; i >= 0; --i) {
-                    wct.reparent(primaryChildren.get(i).token, null /* parent */,
-                            true /* onTop */);
+                    // reset bounds too
+                    wct.setBounds(ti.token, null);
                 }
             }
-            for (int i = freeHomeAndRecents.size() - 1; i >= 0; --i) {
-                wct.setBounds(freeHomeAndRecents.get(i).token, null);
+            for (int i = primaryChildren.size() - 1; i >= 0; --i) {
+                wct.reparent(primaryChildren.get(i).token, null /* parent */,
+                        true /* onTop */);
             }
-            // Reset focusable to true
-            wct.setFocusable(tiles.mPrimary.token, true /* focusable */);
-            WindowOrganizer.applyTransaction(wct);
-        } catch (RemoteException e) {
-            Log.w(TAG, "Failed to remove stack: " + e);
         }
-    }
-
-    static void applyContainerTransaction(WindowContainerTransaction wct) {
-        try {
-            WindowOrganizer.applyTransaction(wct);
-        } catch (RemoteException e) {
-            Log.w(TAG, "Error setting focusability: " + e);
+        for (int i = freeHomeAndRecents.size() - 1; i >= 0; --i) {
+            wct.setBounds(freeHomeAndRecents.get(i).token, null);
         }
+        // Reset focusable to true
+        wct.setFocusable(tiles.mPrimary.token, true /* focusable */);
+        WindowOrganizer.applyTransaction(wct);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
index a978cad..fd44f04 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeDepthController.kt
@@ -32,6 +32,7 @@
 import com.android.systemui.Interpolators
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.notification.ActivityLaunchAnimator
 import com.android.systemui.statusbar.phone.BiometricUnlockController
 import com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK
 import com.android.systemui.statusbar.phone.NotificationShadeWindowController
@@ -39,7 +40,6 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import java.io.FileDescriptor
 import java.io.PrintWriter
-import java.lang.IllegalArgumentException
 import javax.inject.Inject
 import javax.inject.Singleton
 import kotlin.math.max
@@ -69,11 +69,41 @@
     private var notificationAnimator: Animator? = null
     private var updateScheduled: Boolean = false
     private var shadeExpansion = 0f
+    private var ignoreShadeBlurUntilHidden: Boolean = false
     @VisibleForTesting
     var shadeSpring = DepthAnimation()
     @VisibleForTesting
     var globalActionsSpring = DepthAnimation()
 
+    @VisibleForTesting
+    var brightnessMirrorSpring = DepthAnimation()
+    var brightnessMirrorVisible: Boolean = false
+        set(value) {
+            field = value
+            brightnessMirrorSpring.animateTo(if (value) blurUtils.blurRadiusOfRatio(1f)
+                else 0)
+        }
+
+    /**
+     * When launching an app from the shade, the animations progress should affect how blurry the
+     * shade is, overriding the expansion amount.
+     */
+    var notificationLaunchAnimationParams: ActivityLaunchAnimator.ExpandAnimationParameters? = null
+        set(value) {
+            field = value
+            if (value != null) {
+                scheduleUpdate()
+                return
+            }
+
+            if (shadeSpring.radius == 0) {
+                return
+            }
+            ignoreShadeBlurUntilHidden = true
+            shadeSpring.animateTo(0)
+            shadeSpring.finishIfRunning()
+        }
+
     /**
      * Blur radius of the wake-up animation on this frame.
      */
@@ -91,7 +121,19 @@
     val updateBlurCallback = Choreographer.FrameCallback {
         updateScheduled = false
 
-        val blur = max(max(shadeSpring.radius, wakeAndUnlockBlurRadius), globalActionsSpring.radius)
+        var shadeRadius = max(shadeSpring.radius, wakeAndUnlockBlurRadius).toFloat()
+        shadeRadius *= 1f - brightnessMirrorSpring.ratio
+        val launchProgress = notificationLaunchAnimationParams?.linearProgress ?: 0f
+        shadeRadius *= (1f - launchProgress) * (1f - launchProgress)
+
+        if (ignoreShadeBlurUntilHidden) {
+            if (shadeRadius == 0f) {
+                ignoreShadeBlurUntilHidden = false
+            } else {
+                shadeRadius = 0f
+            }
+        }
+        val blur = max(shadeRadius.toInt(), globalActionsSpring.radius)
         blurUtils.applyBlur(blurRoot?.viewRootImpl ?: root.viewRootImpl, blur)
         try {
             wallpaperManager.setWallpaperZoomOut(root.windowToken,
@@ -148,6 +190,7 @@
             if (isDozing) {
                 shadeSpring.finishIfRunning()
                 globalActionsSpring.finishIfRunning()
+                brightnessMirrorSpring.finishIfRunning()
             }
         }
     }
@@ -176,7 +219,6 @@
         if (statusBarStateController.state == StatusBarState.SHADE) {
             newBlur = blurUtils.blurRadiusOfRatio(shadeExpansion)
         }
-
         shadeSpring.animateTo(newBlur)
     }
 
@@ -199,7 +241,11 @@
             it.increaseIndent()
             it.println("shadeRadius: ${shadeSpring.radius}")
             it.println("globalActionsRadius: ${globalActionsSpring.radius}")
+            it.println("brightnessMirrorRadius: ${brightnessMirrorSpring.radius}")
             it.println("wakeAndUnlockBlur: $wakeAndUnlockBlurRadius")
+            it.println("notificationLaunchAnimationProgress: " +
+                    "${notificationLaunchAnimationParams?.linearProgress}")
+            it.println("ignoreShadeBlurUntilHidden: $ignoreShadeBlurUntilHidden")
         }
     }
 
@@ -212,7 +258,12 @@
          * Blur radius visible on the UI, in pixels.
          */
         var radius = 0
-            private set
+
+        /**
+         * Depth ratio of the current blur radius.
+         */
+        val ratio
+            get() = blurUtils.ratioOfBlurRadius(radius)
 
         /**
          * Radius that we're animating to.
@@ -239,7 +290,7 @@
         init {
             springAnimation.spring = SpringForce(0.0f)
             springAnimation.spring.dampingRatio = SpringForce.DAMPING_RATIO_NO_BOUNCY
-            springAnimation.spring.stiffness = SpringForce.STIFFNESS_MEDIUM
+            springAnimation.spring.stiffness = SpringForce.STIFFNESS_HIGH
             springAnimation.addEndListener { _, _, _, _ -> pendingRadius = -1 }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUiAdjustment.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUiAdjustment.java
index 564d8bc..3f74aaf 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUiAdjustment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationUiAdjustment.java
@@ -38,10 +38,12 @@
     public final String key;
     public final List<Notification.Action> smartActions;
     public final List<CharSequence> smartReplies;
+    public final boolean isConversation;
 
     @VisibleForTesting
     NotificationUiAdjustment(
-            String key, List<Notification.Action> smartActions, List<CharSequence> smartReplies) {
+            String key, List<Notification.Action> smartActions, List<CharSequence> smartReplies,
+            boolean isConversation) {
         this.key = key;
         this.smartActions = smartActions == null
                 ? Collections.emptyList()
@@ -49,12 +51,14 @@
         this.smartReplies = smartReplies == null
                 ? Collections.emptyList()
                 : smartReplies;
+        this.isConversation = isConversation;
     }
 
     public static NotificationUiAdjustment extractFromNotificationEntry(
             NotificationEntry entry) {
         return new NotificationUiAdjustment(
-                entry.getKey(), entry.getSmartActions(), entry.getSmartReplies());
+                entry.getKey(), entry.getSmartActions(), entry.getSmartReplies(),
+                entry.getRanking().isConversation());
     }
 
     public static boolean needReinflate(
@@ -63,6 +67,9 @@
         if (oldAdjustment == newAdjustment) {
             return false;
         }
+        if (oldAdjustment.isConversation != newAdjustment.isConversation) {
+            return true;
+        }
         if (areDifferent(oldAdjustment.smartActions, newAdjustment.smartActions)) {
             return true;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java
index 7c06157..6aef6b4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimator.java
@@ -34,6 +34,7 @@
 
 import com.android.internal.policy.ScreenDecorationsUtils;
 import com.android.systemui.Interpolators;
+import com.android.systemui.statusbar.NotificationShadeDepthController;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.phone.CollapsedStatusBarFragment;
@@ -57,6 +58,7 @@
     private final NotificationListContainer mNotificationContainer;
     private final float mWindowCornerRadius;
     private final NotificationShadeWindowViewController mNotificationShadeWindowViewController;
+    private final NotificationShadeDepthController mDepthController;
     private Callback mCallback;
     private final Runnable mTimeoutRunnable = () -> {
         setAnimationPending(false);
@@ -70,9 +72,11 @@
             NotificationShadeWindowViewController notificationShadeWindowViewController,
             Callback callback,
             NotificationPanelViewController notificationPanel,
+            NotificationShadeDepthController depthController,
             NotificationListContainer container) {
         mNotificationPanel = notificationPanel;
         mNotificationContainer = container;
+        mDepthController = depthController;
         mNotificationShadeWindowViewController = notificationShadeWindowViewController;
         mCallback = callback;
         mWindowCornerRadius = ScreenDecorationsUtils
@@ -212,7 +216,7 @@
                                 mWindowCornerRadius, progress);
                         applyParamsToWindow(primary);
                         applyParamsToNotification(mParams);
-                        applyParamsToNotificationList(mParams);
+                        applyParamsToNotificationShade(mParams);
                     }
                 });
                 anim.addListener(new AnimatorListenerAdapter() {
@@ -256,14 +260,15 @@
             if (!running) {
                 mCallback.onExpandAnimationFinished(mIsFullScreenLaunch);
                 applyParamsToNotification(null);
-                applyParamsToNotificationList(null);
+                applyParamsToNotificationShade(null);
             }
 
         }
 
-        private void applyParamsToNotificationList(ExpandAnimationParameters params) {
+        private void applyParamsToNotificationShade(ExpandAnimationParameters params) {
             mNotificationContainer.applyExpandAnimationParams(params);
             mNotificationPanel.applyExpandAnimationParams(params);
+            mDepthController.setNotificationLaunchAnimationParams(params);
         }
 
         private void applyParamsToNotification(ExpandAnimationParameters params) {
@@ -295,7 +300,7 @@
     };
 
     public static class ExpandAnimationParameters {
-        float linearProgress;
+        public float linearProgress;
         int[] startPosition;
         float startTranslationZ;
         int left;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotificationProcessor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotificationProcessor.kt
deleted file mode 100644
index 6be0fff..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotificationProcessor.kt
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification
-
-import android.app.Notification
-import android.content.pm.LauncherApps
-import com.android.systemui.statusbar.notification.collection.NotificationEntry
-import javax.inject.Inject
-
-class ConversationNotificationProcessor @Inject constructor(
-    private val launcherApps: LauncherApps
-) {
-    fun processNotification(entry: NotificationEntry, recoveredBuilder: Notification.Builder) {
-        val messagingStyle = recoveredBuilder.style as? Notification.MessagingStyle ?: return
-        messagingStyle.conversationType =
-                if (entry.ranking.channel.isImportantConversation)
-                    Notification.MessagingStyle.CONVERSATION_TYPE_IMPORTANT
-                else
-                    Notification.MessagingStyle.CONVERSATION_TYPE_NORMAL
-        entry.ranking.shortcutInfo?.let { shortcutInfo ->
-            messagingStyle.shortcutIcon = launcherApps.getShortcutIcon(shortcutInfo)
-            shortcutInfo.shortLabel?.let { shortLabel ->
-                messagingStyle.conversationTitle = shortLabel
-            }
-        }
-    }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
new file mode 100644
index 0000000..7ef1d0e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification
+
+import android.app.Notification
+import android.content.Context
+import android.content.pm.LauncherApps
+import android.service.notification.NotificationListenerService.Ranking
+import android.service.notification.NotificationListenerService.RankingMap
+import com.android.internal.statusbar.NotificationVisibility
+import com.android.internal.widget.ConversationLayout
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
+import com.android.systemui.statusbar.notification.row.NotificationContentView
+import java.util.concurrent.ConcurrentHashMap
+import javax.inject.Inject
+import javax.inject.Singleton
+
+/** Populates additional information in conversation notifications */
+class ConversationNotificationProcessor @Inject constructor(
+    private val launcherApps: LauncherApps,
+    private val conversationNotificationManager: ConversationNotificationManager
+) {
+    fun processNotification(entry: NotificationEntry, recoveredBuilder: Notification.Builder) {
+        val messagingStyle = recoveredBuilder.style as? Notification.MessagingStyle ?: return
+        messagingStyle.conversationType =
+                if (entry.ranking.channel.isImportantConversation)
+                    Notification.MessagingStyle.CONVERSATION_TYPE_IMPORTANT
+                else
+                    Notification.MessagingStyle.CONVERSATION_TYPE_NORMAL
+        entry.ranking.shortcutInfo?.let { shortcutInfo ->
+            messagingStyle.shortcutIcon = launcherApps.getShortcutIcon(shortcutInfo)
+            shortcutInfo.shortLabel?.let { shortLabel ->
+                messagingStyle.conversationTitle = shortLabel
+            }
+        }
+        messagingStyle.unreadMessageCount =
+                conversationNotificationManager.getUnreadCount(entry, recoveredBuilder)
+    }
+}
+
+/**
+ * Tracks state related to conversation notifications, and updates the UI of existing notifications
+ * when necessary.
+ */
+@Singleton
+class ConversationNotificationManager @Inject constructor(
+    private val notificationEntryManager: NotificationEntryManager,
+    private val context: Context
+) {
+    // Need this state to be thread safe, since it's accessed from the ui thread
+    // (NotificationEntryListener) and a bg thread (NotificationContentInflater)
+    private val states = ConcurrentHashMap<String, ConversationState>()
+
+    private var notifPanelCollapsed = true
+
+    init {
+        notificationEntryManager.addNotificationEntryListener(object : NotificationEntryListener {
+
+            override fun onNotificationRankingUpdated(rankingMap: RankingMap) {
+                fun getLayouts(view: NotificationContentView) =
+                        sequenceOf(view.contractedChild, view.expandedChild, view.headsUpChild)
+                val ranking = Ranking()
+                states.keys.asSequence()
+                        .mapNotNull { notificationEntryManager.getActiveNotificationUnfiltered(it) }
+                        .forEach { entry ->
+                            if (rankingMap.getRanking(entry.sbn.key, ranking) &&
+                                    ranking.isConversation) {
+                                val important = ranking.channel.isImportantConversation
+                                entry.row?.layouts?.asSequence()
+                                        ?.flatMap(::getLayouts)
+                                        ?.mapNotNull { it as? ConversationLayout }
+                                        ?.forEach { it.setIsImportantConversation(important) }
+                            }
+                        }
+            }
+
+            override fun onEntryInflated(entry: NotificationEntry) {
+                if (!entry.ranking.isConversation) return
+                fun updateCount(isExpanded: Boolean) {
+                    if (isExpanded && !notifPanelCollapsed) {
+                        resetCount(entry.key)
+                        entry.row?.let(::resetBadgeUi)
+                    }
+                }
+                entry.row?.setOnExpansionChangedListener(::updateCount)
+                updateCount(entry.row?.isExpanded == true)
+            }
+
+            override fun onEntryReinflated(entry: NotificationEntry) = onEntryInflated(entry)
+
+            override fun onEntryRemoved(
+                entry: NotificationEntry,
+                visibility: NotificationVisibility?,
+                removedByUser: Boolean,
+                reason: Int
+            ) = removeTrackedEntry(entry)
+        })
+    }
+
+    fun getUnreadCount(entry: NotificationEntry, recoveredBuilder: Notification.Builder): Int =
+            states.compute(entry.key) { _, state ->
+                val newCount = state?.run {
+                    val old = Notification.Builder.recoverBuilder(context, notification)
+                    val increment = Notification
+                            .areStyledNotificationsVisiblyDifferent(old, recoveredBuilder)
+                    if (increment) unreadCount + 1 else unreadCount
+                } ?: 1
+                ConversationState(newCount, entry.sbn.notification)
+            }!!.unreadCount
+
+    fun onNotificationPanelExpandStateChanged(isCollapsed: Boolean) {
+        notifPanelCollapsed = isCollapsed
+        if (isCollapsed) return
+
+        // When the notification panel is expanded, reset the counters of any expanded
+        // conversations
+        val expanded = states
+                .asSequence()
+                .mapNotNull { (key, _) ->
+                    notificationEntryManager.getActiveNotificationUnfiltered(key)
+                            ?.let { entry ->
+                                if (entry.row?.isExpanded == true) key to entry
+                                else null
+                            }
+                }
+                .toMap()
+        states.replaceAll { key, state ->
+            if (expanded.contains(key)) state.copy(unreadCount = 0)
+            else state
+        }
+        // Update UI separate from the replaceAll call, since ConcurrentHashMap may re-run the
+        // lambda if threads are in contention.
+        expanded.values.asSequence().mapNotNull { it.row }.forEach(::resetBadgeUi)
+    }
+
+    private fun resetCount(key: String) {
+        states.compute(key) { _, state -> state?.copy(unreadCount = 0) }
+    }
+
+    private fun removeTrackedEntry(entry: NotificationEntry) {
+        states.remove(entry.key)
+    }
+
+    private fun resetBadgeUi(row: ExpandableNotificationRow): Unit =
+            (row.layouts?.asSequence() ?: emptySequence())
+                    .mapNotNull { layout -> layout.contractedChild as? ConversationLayout }
+                    .forEach { convoLayout -> convoLayout.setUnreadCount(0) }
+
+    private data class ConversationState(val unreadCount: Int, val notification: Notification)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
index b90cfa8..c9cc670 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifCollection.java
@@ -249,6 +249,7 @@
                             stats.notificationVisibility);
                 } catch (RemoteException e) {
                     // system process is dead if we're here.
+                    mLogger.logRemoteExceptionOnNotificationClear(entry.getKey(), e);
                 }
             }
         }
@@ -277,6 +278,7 @@
             mStatusBarService.onClearAllNotifications(userId);
         } catch (RemoteException e) {
             // system process is dead if we're here.
+            mLogger.logRemoteExceptionOnClearAllNotifications(e);
         }
 
         final List<NotificationEntry> entries = new ArrayList<>(getAllNotifs());
@@ -743,6 +745,6 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface CancellationReason {}
 
-    public static final int REASON_NOT_CANCELED = -1;
+    static final int REASON_NOT_CANCELED = -1;
     public static final int REASON_UNKNOWN = 0;
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
index 8675cca..ef302f6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/NotifCollectionLogger.kt
@@ -16,11 +16,13 @@
 
 package com.android.systemui.statusbar.notification.collection.notifcollection
 
+import android.os.RemoteException
 import android.service.notification.NotificationListenerService.RankingMap
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.LogLevel.DEBUG
 import com.android.systemui.log.LogLevel.INFO
 import com.android.systemui.log.LogLevel.WARNING
+import com.android.systemui.log.LogLevel.WTF
 import com.android.systemui.log.dagger.NotificationLog
 import javax.inject.Inject
 
@@ -92,6 +94,23 @@
             buffer.log(TAG, DEBUG, { str1 = entry }, { "  $str1" })
         }
     }
+
+    fun logRemoteExceptionOnNotificationClear(key: String, e: RemoteException) {
+        buffer.log(TAG, WTF, {
+            str1 = key
+            str2 = e.toString()
+        }, {
+            "RemoteException while attempting to clear $str1:\n$str2"
+        })
+    }
+
+    fun logRemoteExceptionOnClearAllNotifications(e: RemoteException) {
+        buffer.log(TAG, WTF, {
+            str1 = e.toString()
+        }, {
+            "RemoteException while attempting to clear all notifications:\n$str1"
+        })
+    }
 }
 
-private const val TAG = "NotifCollection"
\ No newline at end of file
+private const val TAG = "NotifCollection"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 7deabf7..19b5f5c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -107,6 +107,7 @@
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
 import java.util.function.BooleanSupplier;
@@ -136,7 +137,11 @@
      */
     public interface LayoutListener {
         void onLayout();
+    }
 
+    /** Listens for changes to the expansion state of this row. */
+    public interface OnExpansionChangedListener {
+        void onExpansionChanged(boolean isExpanded);
     }
 
     private StatusBarStateController mStatusbarStateController;
@@ -323,6 +328,7 @@
     private boolean mWasChildInGroupWhenRemoved;
     private NotificationInlineImageResolver mImageResolver;
     private NotificationMediaManager mMediaManager;
+    @Nullable private OnExpansionChangedListener mExpansionChangedListener;
 
     private SystemNotificationAsyncTask mSystemNotificationAsyncTask =
             new SystemNotificationAsyncTask();
@@ -351,6 +357,10 @@
         return isSystemNotification;
     }
 
+    public NotificationContentView[] getLayouts() {
+        return Arrays.copyOf(mLayouts, mLayouts.length);
+    }
+
     @Override
     public boolean isGroupExpansionChanging() {
         if (isChildInGroup()) {
@@ -1659,8 +1669,8 @@
     }
 
     public void showAppOpsIcons(ArraySet<Integer> activeOps) {
-        if (mIsSummaryWithChildren && mChildrenContainer.getHeaderView() != null) {
-            mChildrenContainer.getHeaderView().showAppOpsIcons(activeOps);
+        if (mIsSummaryWithChildren) {
+            mChildrenContainer.showAppOpsIcons(activeOps);
         }
         mPrivateLayout.showAppOpsIcons(activeOps);
         mPublicLayout.showAppOpsIcons(activeOps);
@@ -1687,8 +1697,8 @@
     private final Runnable mExpireRecentlyAlertedFlag = () -> applyAudiblyAlertedRecently(false);
 
     private void applyAudiblyAlertedRecently(boolean audiblyAlertedRecently) {
-        if (mIsSummaryWithChildren && mChildrenContainer.getHeaderView() != null) {
-            mChildrenContainer.getHeaderView().setRecentlyAudiblyAlerted(audiblyAlertedRecently);
+        if (mIsSummaryWithChildren) {
+            mChildrenContainer.setRecentlyAudiblyAlerted(audiblyAlertedRecently);
         }
         mPrivateLayout.setRecentlyAudiblyAlerted(audiblyAlertedRecently);
         mPublicLayout.setRecentlyAudiblyAlerted(audiblyAlertedRecently);
@@ -2911,9 +2921,16 @@
             if (mIsSummaryWithChildren) {
                 mChildrenContainer.onExpansionChanged();
             }
+            if (mExpansionChangedListener != null) {
+                mExpansionChangedListener.onExpansionChanged(nowExpanded);
+            }
         }
     }
 
+    public void setOnExpansionChangedListener(@Nullable OnExpansionChangedListener listener) {
+        mExpansionChangedListener = listener;
+    }
+
     @Override
     public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
         super.onInitializeAccessibilityNodeInfoInternal(info);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
index 9b9225e..8efdc1b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java
@@ -1468,27 +1468,27 @@
     }
 
     public void showAppOpsIcons(ArraySet<Integer> activeOps) {
-        if (mContractedChild != null && mContractedWrapper.getNotificationHeader() != null) {
-            mContractedWrapper.getNotificationHeader().showAppOpsIcons(activeOps);
+        if (mContractedChild != null) {
+            mContractedWrapper.showAppOpsIcons(activeOps);
         }
-        if (mExpandedChild != null && mExpandedWrapper.getNotificationHeader() != null) {
-            mExpandedWrapper.getNotificationHeader().showAppOpsIcons(activeOps);
+        if (mExpandedChild != null) {
+            mExpandedWrapper.showAppOpsIcons(activeOps);
         }
-        if (mHeadsUpChild != null && mHeadsUpWrapper.getNotificationHeader() != null) {
-            mHeadsUpWrapper.getNotificationHeader().showAppOpsIcons(activeOps);
+        if (mHeadsUpChild != null) {
+            mHeadsUpWrapper.showAppOpsIcons(activeOps);
         }
     }
 
     /** Sets whether the notification being displayed audibly alerted the user. */
     public void setRecentlyAudiblyAlerted(boolean audiblyAlerted) {
-        if (mContractedChild != null && mContractedWrapper.getNotificationHeader() != null) {
-            mContractedWrapper.getNotificationHeader().setRecentlyAudiblyAlerted(audiblyAlerted);
+        if (mContractedChild != null) {
+            mContractedWrapper.setRecentlyAudiblyAlerted(audiblyAlerted);
         }
-        if (mExpandedChild != null && mExpandedWrapper.getNotificationHeader() != null) {
-            mExpandedWrapper.getNotificationHeader().setRecentlyAudiblyAlerted(audiblyAlerted);
+        if (mExpandedChild != null) {
+            mExpandedWrapper.setRecentlyAudiblyAlerted(audiblyAlerted);
         }
-        if (mHeadsUpChild != null && mHeadsUpWrapper.getNotificationHeader() != null) {
-            mHeadsUpWrapper.getNotificationHeader().setRecentlyAudiblyAlerted(audiblyAlerted);
+        if (mHeadsUpChild != null) {
+            mHeadsUpWrapper.setRecentlyAudiblyAlerted(audiblyAlerted);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
index 82e5f0a..8d675f8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/StackScrollerDecorView.java
@@ -47,6 +47,18 @@
         }
     };
 
+    private boolean mSecondaryAnimating = false;
+    private final Runnable mSecondaryVisibilityEndRunnable = () -> {
+        mSecondaryAnimating = false;
+        // If we were on screen, become GONE to avoid touches
+        if (mSecondaryView == null) return;
+        if (getVisibility() != View.GONE
+                && mSecondaryView.getVisibility() != View.GONE
+                && !mIsSecondaryVisible) {
+            mSecondaryView.setVisibility(View.GONE);
+        }
+    };
+
     public StackScrollerDecorView(Context context, AttributeSet attrs) {
         super(context, attrs);
         setClipChildren(false);
@@ -88,9 +100,11 @@
     private void setContentVisible(boolean contentVisible, boolean animate) {
         if (mContentVisible != contentVisible) {
             mContentAnimating = animate;
-            setViewVisible(mContent, contentVisible, animate, mContentVisibilityEndRunnable);
             mContentVisible = contentVisible;
-        } if (!mContentAnimating) {
+            setViewVisible(mContent, contentVisible, animate, mContentVisibilityEndRunnable);
+        }
+
+        if (!mContentAnimating) {
             mContentVisibilityEndRunnable.run();
         }
     }
@@ -136,8 +150,13 @@
      */
     public void setSecondaryVisible(boolean nowVisible, boolean animate) {
         if (mIsSecondaryVisible != nowVisible) {
-            setViewVisible(mSecondaryView, nowVisible, animate, null /* endRunnable */);
+            mSecondaryAnimating = animate;
             mIsSecondaryVisible = nowVisible;
+            setViewVisible(mSecondaryView, nowVisible, animate, mSecondaryVisibilityEndRunnable);
+        }
+
+        if (!mSecondaryAnimating) {
+            mSecondaryVisibilityEndRunnable.run();
         }
     }
 
@@ -170,6 +189,12 @@
         if (view == null) {
             return;
         }
+
+        // Make sure we're visible so animations work
+        if (view.getVisibility() != View.VISIBLE) {
+            view.setVisibility(View.VISIBLE);
+        }
+
         // cancel any previous animations
         view.animate().cancel();
         float endValue = nowVisible ? 1.0f : 0.0f;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
index 7808a4b..0c311b40 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationHeaderViewWrapper.java
@@ -18,6 +18,8 @@
 
 import static com.android.systemui.statusbar.notification.TransformState.TRANSFORM_Y;
 
+import android.annotation.NonNull;
+import android.app.AppOpsManager;
 import android.app.Notification;
 import android.content.Context;
 import android.util.ArraySet;
@@ -60,6 +62,11 @@
     protected NotificationHeaderView mNotificationHeader;
     private TextView mHeaderText;
     private ImageView mWorkProfileImage;
+    private View mCameraIcon;
+    private View mMicIcon;
+    private View mOverlayIcon;
+    private View mAppOps;
+    private View mAudiblyAlertedIcon;
 
     private boolean mIsLowPriority;
     private boolean mTransformLowPriorityTitle;
@@ -107,6 +114,11 @@
         mExpandButton = mView.findViewById(com.android.internal.R.id.expand_button);
         mWorkProfileImage = mView.findViewById(com.android.internal.R.id.profile_badge);
         mNotificationHeader = mView.findViewById(com.android.internal.R.id.notification_header);
+        mCameraIcon = mView.findViewById(com.android.internal.R.id.camera);
+        mMicIcon = mView.findViewById(com.android.internal.R.id.mic);
+        mOverlayIcon = mView.findViewById(com.android.internal.R.id.overlay);
+        mAppOps = mView.findViewById(com.android.internal.R.id.app_ops);
+        mAudiblyAlertedIcon = mView.findViewById(com.android.internal.R.id.alerted_icon);
         if (mNotificationHeader != null) {
             mNotificationHeader.setShowExpandButtonAtEnd(mShowExpandButtonAtEnd);
             mColor = mNotificationHeader.getOriginalIconColor();
@@ -114,8 +126,35 @@
     }
 
     private void addAppOpsOnClickListener(ExpandableNotificationRow row) {
+        View.OnClickListener listener = row.getAppOpsOnClickListener();
         if (mNotificationHeader != null) {
-            mNotificationHeader.setAppOpsOnClickListener(row.getAppOpsOnClickListener());
+            mNotificationHeader.setAppOpsOnClickListener(listener);
+        }
+        mAppOps.setOnClickListener(listener);
+        mCameraIcon.setOnClickListener(listener);
+        mMicIcon.setOnClickListener(listener);
+        mOverlayIcon.setOnClickListener(listener);
+    }
+
+    /**
+     * Shows or hides 'app op in use' icons based on app usage.
+     */
+    @Override
+    public void showAppOpsIcons(ArraySet<Integer> appOps) {
+        if (appOps == null) {
+            return;
+        }
+        if (mOverlayIcon != null) {
+            mOverlayIcon.setVisibility(appOps.contains(AppOpsManager.OP_SYSTEM_ALERT_WINDOW)
+                    ? View.VISIBLE : View.GONE);
+        }
+        if (mCameraIcon != null) {
+            mCameraIcon.setVisibility(appOps.contains(AppOpsManager.OP_CAMERA)
+                    ? View.VISIBLE : View.GONE);
+        }
+        if (mMicIcon != null) {
+            mMicIcon.setVisibility(appOps.contains(AppOpsManager.OP_RECORD_AUDIO)
+                    ? View.VISIBLE : View.GONE);
         }
     }
 
@@ -184,6 +223,18 @@
             mTransformationHelper.addTransformedView(TransformableView.TRANSFORMING_VIEW_TITLE,
                     mHeaderText);
         }
+        if (mCameraIcon != null) {
+            mTransformationHelper.addViewTransformingToSimilar(mCameraIcon);
+        }
+        if (mMicIcon != null) {
+            mTransformationHelper.addViewTransformingToSimilar(mMicIcon);
+        }
+        if (mOverlayIcon != null) {
+            mTransformationHelper.addViewTransformingToSimilar(mOverlayIcon);
+        }
+        if (mAudiblyAlertedIcon != null) {
+            mTransformationHelper.addViewTransformingToSimilar(mAudiblyAlertedIcon);
+        }
     }
 
     @Override
@@ -195,6 +246,13 @@
     }
 
     @Override
+    public void setRecentlyAudiblyAlerted(boolean audiblyAlerted) {
+        if (mAudiblyAlertedIcon != null) {
+            mAudiblyAlertedIcon.setVisibility(audiblyAlerted ? View.VISIBLE : View.GONE);
+        }
+    }
+
+    @Override
     public NotificationHeaderView getNotificationHeader() {
         return mNotificationHeader;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
index e4fb2f7..fa7f282 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapper.java
@@ -29,6 +29,7 @@
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
+import android.util.ArraySet;
 import android.view.NotificationHeaderView;
 import android.view.View;
 import android.view.ViewGroup;
@@ -95,6 +96,14 @@
     public void onContentUpdated(ExpandableNotificationRow row) {
     }
 
+    /**
+     * Show a set of app opp icons in the layout.
+     *
+     * @param appOps which app ops to show
+     */
+    public void showAppOpsIcons(ArraySet<Integer> appOps) {
+    }
+
     public void onReinflated() {
         if (shouldClearBackgroundOnReapply()) {
             mBackgroundColor = 0;
@@ -362,4 +371,10 @@
     public int getExtraMeasureHeight() {
         return 0;
     }
+
+    /**
+     * Set the view to have recently visibly alerted.
+     */
+    public void setRecentlyAudiblyAlerted(boolean audiblyAlerted) {
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index 3d0bf3f..400e794 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
@@ -22,6 +22,7 @@
 import android.content.res.Resources;
 import android.graphics.drawable.ColorDrawable;
 import android.service.notification.StatusBarNotification;
+import android.util.ArraySet;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.NotificationHeaderView;
@@ -1265,4 +1266,27 @@
         mHeaderVisibleAmount = headerVisibleAmount;
         mCurrentHeaderTranslation = (int) ((1.0f - headerVisibleAmount) * mTranslationForHeader);
     }
+
+    /**
+     * Show a set of app opp icons in the layout.
+     *
+     * @param appOps which app ops to show
+     */
+    public void showAppOpsIcons(ArraySet<Integer> appOps) {
+        if (mNotificationHeaderWrapper != null) {
+            mNotificationHeaderWrapper.showAppOpsIcons(appOps);
+        }
+        if (mNotificationHeaderWrapperLowPriority != null) {
+            mNotificationHeaderWrapperLowPriority.showAppOpsIcons(appOps);
+        }
+    }
+
+    public void setRecentlyAudiblyAlerted(boolean audiblyAlertedRecently) {
+        if (mNotificationHeaderWrapper != null) {
+            mNotificationHeaderWrapper.setRecentlyAudiblyAlerted(audiblyAlertedRecently);
+        }
+        if (mNotificationHeaderWrapperLowPriority != null) {
+            mNotificationHeaderWrapperLowPriority.setRecentlyAudiblyAlerted(audiblyAlertedRecently);
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java
index c05119d..d6039af 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoHideController.java
@@ -19,7 +19,6 @@
 import android.content.Context;
 import android.os.Handler;
 import android.os.RemoteException;
-import android.util.ArraySet;
 import android.util.Log;
 import android.view.IWindowManager;
 import android.view.MotionEvent;
@@ -27,8 +26,6 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.statusbar.AutoHideUiElement;
 
-import java.util.Set;
-
 import javax.inject.Inject;
 
 /** A controller to control all auto-hide things. Also see {@link AutoHideUiElement}. */
@@ -38,8 +35,9 @@
 
     private final IWindowManager mWindowManagerService;
     private final Handler mHandler;
-    private final Set<AutoHideUiElement> mElements;
 
+    private AutoHideUiElement mStatusBar;
+    private AutoHideUiElement mNavigationBar;
     private int mDisplayId;
 
     private boolean mAutoHideSuspended;
@@ -55,28 +53,24 @@
             IWindowManager iWindowManager) {
         mHandler = handler;
         mWindowManagerService = iWindowManager;
-        mElements = new ArraySet<>();
 
         mDisplayId = context.getDisplayId();
     }
 
     /**
-     * Adds an {@link AutoHideUiElement} whose behavior should be controlled by the
+     * Sets a {@link AutoHideUiElement} status bar that should be controlled by the
      * {@link AutoHideController}.
      */
-    public void addAutoHideUiElement(AutoHideUiElement element) {
-        if (element != null) {
-            mElements.add(element);
-        }
+    public void setStatusBar(AutoHideUiElement element) {
+        mStatusBar = element;
     }
 
     /**
-     * Remove an {@link AutoHideUiElement} that was previously added.
+     * Sets a {@link AutoHideUiElement} navigation bar that should be controlled by the
+     * {@link AutoHideController}.
      */
-    public void removeAutoHideUiElement(AutoHideUiElement element) {
-        if (element != null) {
-            mElements.remove(element);
-        }
+    public void setNavigationBar(AutoHideUiElement element) {
+        mNavigationBar = element;
     }
 
     private void hideTransientBars() {
@@ -86,8 +80,12 @@
             Log.w(TAG, "Cannot get WindowManager");
         }
 
-        for (AutoHideUiElement element : mElements) {
-            element.hide();
+        if (mStatusBar != null) {
+            mStatusBar.hide();
+        }
+
+        if (mNavigationBar != null) {
+            mNavigationBar.hide();
         }
     }
 
@@ -121,15 +119,13 @@
     }
 
     private Runnable getCheckBarModesRunnable() {
-        if (mElements.isEmpty()) {
+        if (mStatusBar != null) {
+            return () -> mStatusBar.synchronizeState();
+        } else if (mNavigationBar != null) {
+            return () -> mNavigationBar.synchronizeState();
+        } else {
             return null;
         }
-
-        return () -> {
-            for (AutoHideUiElement element : mElements) {
-                element.synchronizeState();
-            }
-        };
     }
 
     private void cancelAutoHide() {
@@ -147,8 +143,11 @@
                 && event.getAction() == MotionEvent.ACTION_OUTSIDE // touch outside the source bar.
                 && event.getX() == 0 && event.getY() == 0;
 
-        for (AutoHideUiElement element : mElements) {
-            shouldHide &= element.shouldHideOnTouch();
+        if (mStatusBar != null) {
+            shouldHide &= mStatusBar.shouldHideOnTouch();
+        }
+        if (mNavigationBar != null) {
+            shouldHide &= mNavigationBar.shouldHideOnTouch();
         }
 
         if (shouldHide) {
@@ -162,11 +161,14 @@
     }
 
     private boolean isAnyTransientBarShown() {
-        for (AutoHideUiElement element : mElements) {
-            if (element.isVisible()) {
-                return true;
-            }
+        if (mStatusBar != null && mStatusBar.isVisible()) {
+            return true;
         }
+
+        if (mNavigationBar != null && mNavigationBar.isVisible()) {
+            return true;
+        }
+
         return false;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
index 31266db..6fd3bb2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarFragment.java
@@ -1075,12 +1075,9 @@
 
     /** Sets {@link AutoHideController} to the navigation bar. */
     public void setAutoHideController(AutoHideController autoHideController) {
-        if (mAutoHideController != null) {
-            mAutoHideController.removeAutoHideUiElement(mAutoHideUiElement);
-        }
         mAutoHideController = autoHideController;
         if (mAutoHideController != null) {
-            mAutoHideController.addAutoHideUiElement(mAutoHideUiElement);
+            mAutoHideController.setNavigationBar(mAutoHideUiElement);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
index 98ba6e5..31797d1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java
@@ -19,6 +19,8 @@
 import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters;
 import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL;
 
+import static java.lang.Float.isNaN;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
@@ -86,6 +88,7 @@
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
 import com.android.systemui.statusbar.notification.AnimatableProperty;
+import com.android.systemui.statusbar.notification.ConversationNotificationManager;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
@@ -238,6 +241,7 @@
     private final PulseExpansionHandler mPulseExpansionHandler;
     private final KeyguardBypassController mKeyguardBypassController;
     private final KeyguardUpdateMonitor mUpdateMonitor;
+    private final ConversationNotificationManager mConversationNotificationManager;
 
     private KeyguardAffordanceHelper mAffordanceHelper;
     private KeyguardUserSwitcher mKeyguardUserSwitcher;
@@ -451,7 +455,8 @@
             ActivityManager activityManager, ZenModeController zenModeController,
             ConfigurationController configurationController,
             FlingAnimationUtils.Builder flingAnimationUtilsBuilder,
-            StatusBarTouchableRegionManager statusBarTouchableRegionManager) {
+            StatusBarTouchableRegionManager statusBarTouchableRegionManager,
+            ConversationNotificationManager conversationNotificationManager) {
         super(view, falsingManager, dozeLog, keyguardStateController,
                 (SysuiStatusBarStateController) statusBarStateController, vibratorHelper,
                 latencyTracker, flingAnimationUtilsBuilder, statusBarTouchableRegionManager);
@@ -509,6 +514,7 @@
         mShadeController = shadeController;
         mLockscreenUserManager = notificationLockscreenUserManager;
         mEntryManager = notificationEntryManager;
+        mConversationNotificationManager = conversationNotificationManager;
 
         mView.setBackgroundColor(Color.TRANSPARENT);
         OnAttachStateChangeListener onAttachStateChangeListener = new OnAttachStateChangeListener();
@@ -2005,7 +2011,12 @@
 
     @Override
     protected float getOverExpansionAmount() {
-        return mNotificationStackScroller.getCurrentOverScrollAmount(true /* top */);
+        float result = mNotificationStackScroller.getCurrentOverScrollAmount(true /* top */);
+        if (isNaN(result)) {
+            Log.wtf(TAG, "OverExpansionAmount is NaN!");
+        }
+
+        return result;
     }
 
     @Override
@@ -2143,6 +2154,7 @@
         super.onExpandingFinished();
         mNotificationStackScroller.onExpansionStopped();
         mHeadsUpManager.onExpandingFinished();
+        mConversationNotificationManager.onNotificationPanelExpandStateChanged(isFullyCollapsed());
         mIsExpanding = false;
         if (isFullyCollapsed()) {
             DejankUtils.postAfterTraversal(new Runnable() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
index 83cc4e3..f7d403f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PanelViewController.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static java.lang.Float.isNaN;
+
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
@@ -638,6 +640,9 @@
     }
 
     public void setExpandedHeightInternal(float h) {
+        if (isNaN(h)) {
+            Log.wtf(TAG, "ExpandedHeight set to NaN");
+        }
         if (mExpandLatencyTracking && h != 0f) {
             DejankUtils.postAfterTraversal(
                     () -> mLatencyTracker.onActionEnd(LatencyTracker.ACTION_EXPAND_PANEL));
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
index d343090..fa55b74 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java
@@ -176,6 +176,7 @@
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.NotificationShadeDepthController;
 import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.NotificationViewHierarchyManager;
 import com.android.systemui.statusbar.PulseExpansionHandler;
@@ -589,6 +590,7 @@
     private ActivityLaunchAnimator mActivityLaunchAnimator;
     protected StatusBarNotificationPresenter mPresenter;
     private NotificationActivityStarter mNotificationActivityStarter;
+    private Lazy<NotificationShadeDepthController> mNotificationShadeDepthControllerLazy;
     private final BubbleController mBubbleController;
     private final BubbleController.BubbleExpandListener mBubbleExpandListener;
 
@@ -679,6 +681,7 @@
             PhoneStatusBarPolicy phoneStatusBarPolicy,
             KeyguardIndicationController keyguardIndicationController,
             DismissCallbackRegistry dismissCallbackRegistry,
+            Lazy<NotificationShadeDepthController> notificationShadeDepthControllerLazy,
             StatusBarTouchableRegionManager statusBarTouchableRegionManager) {
         super(context);
         mNotificationsController = notificationsController;
@@ -735,6 +738,7 @@
         mScreenPinningRequest = screenPinningRequest;
         mDozeScrimController = dozeScrimController;
         mBiometricUnlockControllerLazy = biometricUnlockControllerLazy;
+        mNotificationShadeDepthControllerLazy = notificationShadeDepthControllerLazy;
         mVolumeComponent = volumeComponent;
         mCommandQueue = commandQueue;
         mRecentsOptional = recentsOptional;
@@ -1073,7 +1077,7 @@
             }
         });
 
-        mAutoHideController.addAutoHideUiElement(new AutoHideUiElement() {
+        mAutoHideController.setStatusBar(new AutoHideUiElement() {
             @Override
             public void synchronizeState() {
                 checkBarModes();
@@ -1135,6 +1139,7 @@
             mBrightnessMirrorController = new BrightnessMirrorController(
                     mNotificationShadeWindowView,
                     mNotificationPanelViewController,
+                    mNotificationShadeDepthControllerLazy.get(),
                     (visible) -> {
                         mBrightnessMirrorVisible = visible;
                         updateScrimController();
@@ -1230,6 +1235,7 @@
         // Set up the initial notification state.
         mActivityLaunchAnimator = new ActivityLaunchAnimator(
                 mNotificationShadeWindowViewController, this, mNotificationPanelViewController,
+                mNotificationShadeDepthControllerLazy.get(),
                 (NotificationListContainer) mStackScroller);
 
         // TODO: inject this.
@@ -3333,12 +3339,12 @@
         Trace.traceCounter(Trace.TRACE_TAG_APP, "dozing", mDozing ? 1 : 0);
         Trace.beginSection("StatusBar#updateDozingState");
 
-        boolean sleepingFromKeyguard =
-                mStatusBarKeyguardViewManager.isGoingToSleepVisibleNotOccluded();
+        boolean visibleNotOccluded = mStatusBarKeyguardViewManager.isShowing()
+                && !mStatusBarKeyguardViewManager.isOccluded();
         boolean wakeAndUnlock = mBiometricUnlockController.getMode()
                 == BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
         boolean animate = (!mDozing && mDozeServiceHost.shouldAnimateWakeup() && !wakeAndUnlock)
-                || (mDozing && mDozeServiceHost.shouldAnimateScreenOff() && sleepingFromKeyguard);
+                || (mDozing && mDozeServiceHost.shouldAnimateScreenOff() && visibleNotOccluded);
 
         mNotificationPanelViewController.setDozing(mDozing, animate, mWakeUpTouchLocation);
         updateQsExpansionEnabled();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 31db8eb..45719c7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -168,7 +168,6 @@
     private boolean mLastIsDocked;
     private boolean mLastPulsing;
     private int mLastBiometricMode;
-    private boolean mGoingToSleepVisibleNotOccluded;
     private boolean mLastLockVisible;
 
     private OnDismissAction mAfterKeyguardGoneAction;
@@ -450,37 +449,12 @@
         }
     }
 
-    public boolean isGoingToSleepVisibleNotOccluded() {
-        return mGoingToSleepVisibleNotOccluded;
-    }
-
-    @Override
-    public void onStartedGoingToSleep() {
-        mGoingToSleepVisibleNotOccluded = isShowing() && !isOccluded();
-    }
-
     @Override
     public void onFinishedGoingToSleep() {
-        mGoingToSleepVisibleNotOccluded = false;
         mBouncer.onScreenTurnedOff();
     }
 
     @Override
-    public void onStartedWakingUp() {
-        // TODO: remove
-    }
-
-    @Override
-    public void onScreenTurningOn() {
-        // TODO: remove
-    }
-
-    @Override
-    public void onScreenTurnedOn() {
-        // TODO: remove
-    }
-
-    @Override
     public void onRemoteInputActive(boolean active) {
         mRemoteInputActive = active;
         updateStates();
@@ -999,7 +973,6 @@
         pw.println("  mOccluded: " + mOccluded);
         pw.println("  mRemoteInputActive: " + mRemoteInputActive);
         pw.println("  mDozing: " + mDozing);
-        pw.println("  mGoingToSleepVisibleNotOccluded: " + mGoingToSleepVisibleNotOccluded);
         pw.println("  mAfterKeyguardGoneAction: " + mAfterKeyguardGoneAction);
         pw.println("  mAfterKeyguardGoneRunnables: " + mAfterKeyguardGoneRunnables);
         pw.println("  mPendingWakeupAction: " + mPendingWakeupAction);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
index bbc7e7a..b81a519 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
@@ -51,6 +51,7 @@
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.NotificationShadeDepthController;
 import com.android.systemui.statusbar.NotificationViewHierarchyManager;
 import com.android.systemui.statusbar.PulseExpansionHandler;
 import com.android.systemui.statusbar.SuperStatusBarViewFactory;
@@ -197,6 +198,7 @@
             UserInfoControllerImpl userInfoControllerImpl,
             PhoneStatusBarPolicy phoneStatusBarPolicy,
             KeyguardIndicationController keyguardIndicationController,
+            Lazy<NotificationShadeDepthController> notificationShadeDepthController,
             DismissCallbackRegistry dismissCallbackRegistry,
             StatusBarTouchableRegionManager statusBarTouchableRegionManager) {
         return new StatusBar(
@@ -276,6 +278,7 @@
                 phoneStatusBarPolicy,
                 keyguardIndicationController,
                 dismissCallbackRegistry,
+                notificationShadeDepthController,
                 statusBarTouchableRegionManager);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
index d62da10..78111fb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BrightnessMirrorController.java
@@ -24,6 +24,7 @@
 import android.widget.FrameLayout;
 
 import com.android.systemui.R;
+import com.android.systemui.statusbar.NotificationShadeDepthController;
 import com.android.systemui.statusbar.phone.NotificationPanelViewController;
 import com.android.systemui.statusbar.phone.NotificationShadeWindowView;
 
@@ -39,16 +40,19 @@
     private final NotificationShadeWindowView mStatusBarWindow;
     private final Consumer<Boolean> mVisibilityCallback;
     private final NotificationPanelViewController mNotificationPanel;
+    private final NotificationShadeDepthController mDepthController;
     private final ArraySet<BrightnessMirrorListener> mBrightnessMirrorListeners = new ArraySet<>();
     private final int[] mInt2Cache = new int[2];
     private View mBrightnessMirror;
 
     public BrightnessMirrorController(NotificationShadeWindowView statusBarWindow,
             NotificationPanelViewController notificationPanelViewController,
+            NotificationShadeDepthController notificationShadeDepthController,
             @NonNull Consumer<Boolean> visibilityCallback) {
         mStatusBarWindow = statusBarWindow;
         mBrightnessMirror = statusBarWindow.findViewById(R.id.brightness_mirror);
         mNotificationPanel = notificationPanelViewController;
+        mDepthController = notificationShadeDepthController;
         mNotificationPanel.setPanelAlphaEndAction(() -> {
             mBrightnessMirror.setVisibility(View.INVISIBLE);
         });
@@ -59,11 +63,13 @@
         mBrightnessMirror.setVisibility(View.VISIBLE);
         mVisibilityCallback.accept(true);
         mNotificationPanel.setPanelAlpha(0, true /* animate */);
+        mDepthController.setBrightnessMirrorVisible(true);
     }
 
     public void hideMirror() {
         mVisibilityCallback.accept(false);
         mNotificationPanel.setPanelAlpha(255, true /* animate */);
+        mDepthController.setBrightnessMirrorVisible(false);
     }
 
     public void setLocation(View original) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
index 956bfd0..6b7a3bf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationShadeDepthControllerTest.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.notification.ActivityLaunchAnimator
 import com.android.systemui.statusbar.phone.BiometricUnlockController
 import com.android.systemui.statusbar.phone.NotificationShadeWindowController
 import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -43,9 +44,9 @@
 import org.mockito.Mockito.anyString
 import org.mockito.Mockito.clearInvocations
 import org.mockito.Mockito.doThrow
+import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 import org.mockito.junit.MockitoJUnit
-import java.lang.IllegalArgumentException
 
 @RunWith(AndroidTestingRunner::class)
 @RunWithLooper
@@ -64,6 +65,7 @@
     @Mock private lateinit var viewRootImpl: ViewRootImpl
     @Mock private lateinit var shadeSpring: NotificationShadeDepthController.DepthAnimation
     @Mock private lateinit var globalActionsSpring: NotificationShadeDepthController.DepthAnimation
+    @Mock private lateinit var brightnessSpring: NotificationShadeDepthController.DepthAnimation
     @JvmField @Rule val mockitoRule = MockitoJUnit.rule()
 
     private lateinit var statusBarStateListener: StatusBarStateController.StateListener
@@ -83,6 +85,7 @@
                 keyguardStateController, choreographer, wallpaperManager,
                 notificationShadeWindowController, dumpManager)
         notificationShadeDepthController.shadeSpring = shadeSpring
+        notificationShadeDepthController.brightnessMirrorSpring = brightnessSpring
         notificationShadeDepthController.globalActionsSpring = globalActionsSpring
         notificationShadeDepthController.root = root
 
@@ -127,6 +130,23 @@
     }
 
     @Test
+    fun updateBlurCallback_setsBlur_whenExpanded() {
+        `when`(shadeSpring.radius).thenReturn(maxBlur)
+        notificationShadeDepthController.updateBlurCallback.doFrame(0)
+        verify(blurUtils).applyBlur(any(), eq(maxBlur))
+    }
+
+    @Test
+    fun updateBlurCallback_appLaunchAnimation_overridesZoom() {
+        `when`(shadeSpring.radius).thenReturn(maxBlur)
+        val animProgress = ActivityLaunchAnimator.ExpandAnimationParameters()
+        animProgress.linearProgress = 1f
+        notificationShadeDepthController.notificationLaunchAnimationParams = animProgress
+        notificationShadeDepthController.updateBlurCallback.doFrame(0)
+        verify(blurUtils).applyBlur(any(), eq(0))
+    }
+
+    @Test
     fun updateBlurCallback_invalidWindow() {
         doThrow(IllegalArgumentException("test exception")).`when`(wallpaperManager)
                 .setWallpaperZoomOut(any(), anyFloat())
@@ -134,6 +154,48 @@
         verify(wallpaperManager).setWallpaperZoomOut(any(), anyFloat())
     }
 
+    @Test
+    fun brightnessMirrorVisible_whenVisible() {
+        notificationShadeDepthController.brightnessMirrorVisible = true
+        verify(brightnessSpring).animateTo(eq(maxBlur), any())
+    }
+
+    @Test
+    fun brightnessMirrorVisible_whenHidden() {
+        notificationShadeDepthController.brightnessMirrorVisible = false
+        verify(brightnessSpring).animateTo(eq(0), any())
+    }
+
+    @Test
+    fun brightnessMirror_hidesShadeBlur() {
+        // Brightness mirror is fully visible
+        `when`(brightnessSpring.ratio).thenReturn(1f)
+        // And shade is blurred
+        `when`(shadeSpring.radius).thenReturn(maxBlur)
+
+        notificationShadeDepthController.updateBlurCallback.doFrame(0)
+        verify(notificationShadeWindowController).setBackgroundBlurRadius(0)
+        verify(blurUtils).applyBlur(safeEq(viewRootImpl), eq(0))
+    }
+
+    @Test
+    fun setNotificationLaunchAnimationParams_schedulesFrame() {
+        val animProgress = ActivityLaunchAnimator.ExpandAnimationParameters()
+        animProgress.linearProgress = 0.5f
+        notificationShadeDepthController.notificationLaunchAnimationParams = animProgress
+        verify(choreographer).postFrameCallback(
+                eq(notificationShadeDepthController.updateBlurCallback))
+    }
+
+    @Test
+    fun setNotificationLaunchAnimationParams_whennNull_ignoresIfShadeHasNoBlur() {
+        val animProgress = ActivityLaunchAnimator.ExpandAnimationParameters()
+        animProgress.linearProgress = 0.5f
+        `when`(shadeSpring.radius).thenReturn(0)
+        notificationShadeDepthController.notificationLaunchAnimationParams = animProgress
+        verify(shadeSpring, never()).animateTo(anyInt(), any())
+    }
+
     private fun <T : Any> safeEq(value: T): T {
         return eq(value) ?: value
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationUiAdjustmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationUiAdjustmentTest.java
index d00be56..3c9c9cc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationUiAdjustmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationUiAdjustmentTest.java
@@ -188,6 +188,30 @@
                 .isFalse();
     }
 
+    @Test
+    public void needReinflate_bothConversation() {
+        assertThat(NotificationUiAdjustment.needReinflate(
+                createUiAdjustmentForConversation("first", true),
+                createUiAdjustmentForConversation("first", true)))
+                .isFalse();
+    }
+
+    @Test
+    public void needReinflate_neitherConversation() {
+        assertThat(NotificationUiAdjustment.needReinflate(
+                createUiAdjustmentForConversation("first", false),
+                createUiAdjustmentForConversation("first", false)))
+                .isFalse();
+    }
+
+    @Test
+    public void needReinflate_differentIsConversation() {
+        assertThat(NotificationUiAdjustment.needReinflate(
+                createUiAdjustmentForConversation("first", false),
+                createUiAdjustmentForConversation("first", true)))
+                .isTrue();
+    }
+
     private Notification.Action.Builder createActionBuilder(
             String title, int drawableRes, PendingIntent pendingIntent) {
         return new Notification.Action.Builder(
@@ -200,11 +224,16 @@
 
     private NotificationUiAdjustment createUiAdjustmentFromSmartActions(
             String key, List<Notification.Action> actions) {
-        return new NotificationUiAdjustment(key, actions, null);
+        return new NotificationUiAdjustment(key, actions, null, false);
     }
 
     private NotificationUiAdjustment createUiAdjustmentFromSmartReplies(
             String key, CharSequence[] replies) {
-        return new NotificationUiAdjustment(key, null, Arrays.asList(replies));
+        return new NotificationUiAdjustment(key, null, Arrays.asList(replies), false);
+    }
+
+    private NotificationUiAdjustment createUiAdjustmentForConversation(
+            String key, boolean isConversation) {
+        return new NotificationUiAdjustment(key, null, null, isConversation);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimatorTest.java
index a07cfc3..cdef49d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/ActivityLaunchAnimatorTest.java
@@ -19,7 +19,6 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -31,6 +30,7 @@
 import android.view.View;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.NotificationShadeDepthController;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.phone.NotificationPanelViewController;
@@ -39,8 +39,12 @@
 
 import org.junit.Assert;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
@@ -48,14 +52,22 @@
 public class ActivityLaunchAnimatorTest extends SysuiTestCase {
 
     private ActivityLaunchAnimator mLaunchAnimator;
-    private ActivityLaunchAnimator.Callback mCallback = mock(ActivityLaunchAnimator.Callback.class);
-    private NotificationShadeWindowViewController mNotificationShadeWindowViewController = mock(
-            NotificationShadeWindowViewController.class);
-    private NotificationShadeWindowView mNotificationShadeWindowView = mock(
-            NotificationShadeWindowView.class);
-    private NotificationListContainer mNotificationContainer
-            = mock(NotificationListContainer.class);
-    private ExpandableNotificationRow mRow = mock(ExpandableNotificationRow.class);
+    @Mock
+    private ActivityLaunchAnimator.Callback mCallback;
+    @Mock
+    private NotificationShadeWindowViewController mNotificationShadeWindowViewController;
+    @Mock
+    private NotificationShadeWindowView mNotificationShadeWindowView;
+    @Mock
+    private NotificationListContainer mNotificationContainer;
+    @Mock
+    private ExpandableNotificationRow mRow;
+    @Mock
+    private NotificationShadeDepthController mNotificationShadeDepthController;
+    @Mock
+    private NotificationPanelViewController mNotificationPanelViewController;
+    @Rule
+    public MockitoRule rule = MockitoJUnit.rule();
 
     @Before
     public void setUp() throws Exception {
@@ -66,7 +78,8 @@
         mLaunchAnimator = new ActivityLaunchAnimator(
                 mNotificationShadeWindowViewController,
                 mCallback,
-                mock(NotificationPanelViewController.class),
+                mNotificationPanelViewController,
+                mNotificationShadeDepthController,
                 mNotificationContainer);
 
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index c356e0d..cb37920 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -229,22 +229,19 @@
 
     @Test
     public void testShowAppOpsIcons_header() {
-        NotificationHeaderView mockHeader = mock(NotificationHeaderView.class);
-
         NotificationContentView publicLayout = mock(NotificationContentView.class);
         mGroupRow.setPublicLayout(publicLayout);
         NotificationContentView privateLayout = mock(NotificationContentView.class);
         mGroupRow.setPrivateLayout(privateLayout);
         NotificationChildrenContainer mockContainer = mock(NotificationChildrenContainer.class);
         when(mockContainer.getNotificationChildCount()).thenReturn(1);
-        when(mockContainer.getHeaderView()).thenReturn(mockHeader);
         mGroupRow.setChildrenContainer(mockContainer);
 
         ArraySet<Integer> ops = new ArraySet<>();
         ops.add(AppOpsManager.OP_ANSWER_PHONE_CALLS);
         mGroupRow.showAppOpsIcons(ops);
 
-        verify(mockHeader, times(1)).showAppOpsIcons(ops);
+        verify(mockContainer, times(1)).showAppOpsIcons(ops);
         verify(privateLayout, times(1)).showAppOpsIcons(ops);
         verify(publicLayout, times(1)).showAppOpsIcons(ops);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java
index 84c6513..0f26898 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.java
@@ -76,14 +76,14 @@
     @Test
     @UiThreadTest
     public void testShowAppOpsIcons() {
-        NotificationHeaderView mockContracted = mock(NotificationHeaderView.class);
-        when(mockContracted.findViewById(com.android.internal.R.id.notification_header))
+        View mockContracted = mock(View.class);
+        when(mockContracted.findViewById(com.android.internal.R.id.mic))
                 .thenReturn(mockContracted);
-        NotificationHeaderView mockExpanded = mock(NotificationHeaderView.class);
-        when(mockExpanded.findViewById(com.android.internal.R.id.notification_header))
+        View mockExpanded = mock(View.class);
+        when(mockExpanded.findViewById(com.android.internal.R.id.mic))
                 .thenReturn(mockExpanded);
-        NotificationHeaderView mockHeadsUp = mock(NotificationHeaderView.class);
-        when(mockHeadsUp.findViewById(com.android.internal.R.id.notification_header))
+        View mockHeadsUp = mock(View.class);
+        when(mockHeadsUp.findViewById(com.android.internal.R.id.mic))
                 .thenReturn(mockHeadsUp);
 
         mView.setContractedChild(mockContracted);
@@ -91,11 +91,11 @@
         mView.setHeadsUpChild(mockHeadsUp);
 
         ArraySet<Integer> ops = new ArraySet<>();
-        ops.add(AppOpsManager.OP_ANSWER_PHONE_CALLS);
+        ops.add(AppOpsManager.OP_RECORD_AUDIO);
         mView.showAppOpsIcons(ops);
 
-        verify(mockContracted, times(1)).showAppOpsIcons(ops);
-        verify(mockExpanded, times(1)).showAppOpsIcons(ops);
-        verify(mockHeadsUp, times(1)).showAppOpsIcons(any());
+        verify(mockContracted, times(1)).setVisibility(View.VISIBLE);
+        verify(mockExpanded, times(1)).setVisibility(View.VISIBLE);
+        verify(mockHeadsUp, times(1)).setVisibility(View.VISIBLE);
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
index 13bf38c..4b09aa6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewTest.java
@@ -64,6 +64,7 @@
 import com.android.systemui.statusbar.StatusBarStateControllerImpl;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.VibratorHelper;
+import com.android.systemui.statusbar.notification.ConversationNotificationManager;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
@@ -169,6 +170,8 @@
     private ZenModeController mZenModeController;
     @Mock
     private ConfigurationController mConfigurationController;
+    @Mock
+    private ConversationNotificationManager mConversationNotificationManager;
     private FlingAnimationUtils.Builder mFlingAnimationUtilsBuilder;
 
     private NotificationPanelViewController mNotificationPanelViewController;
@@ -223,7 +226,8 @@
                 mDozeParameters, mCommandQueue, mVibratorHelper,
                 mLatencyTracker, mPowerManager, mAccessibilityManager, 0, mUpdateMonitor,
                 mMetricsLogger, mActivityManager, mZenModeController, mConfigurationController,
-                mFlingAnimationUtilsBuilder, mStatusBarTouchableRegionManager);
+                mFlingAnimationUtilsBuilder, mStatusBarTouchableRegionManager,
+                mConversationNotificationManager);
         mNotificationPanelViewController.initDependencies(mStatusBar, mGroupManager,
                 mNotificationShelf, mNotificationAreaController, mScrimController);
         mNotificationPanelViewController.setHeadsUpManager(mHeadsUpManager);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
index 679ac22..b905bdd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarTest.java
@@ -102,6 +102,7 @@
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.NotificationShadeDepthController;
 import com.android.systemui.statusbar.NotificationViewHierarchyManager;
 import com.android.systemui.statusbar.PulseExpansionHandler;
 import com.android.systemui.statusbar.RemoteInputController;
@@ -249,6 +250,7 @@
     @Mock private ExtensionController mExtensionController;
     @Mock private UserInfoControllerImpl mUserInfoControllerImpl;
     @Mock private PhoneStatusBarPolicy mPhoneStatusBarPolicy;
+    @Mock private Lazy<NotificationShadeDepthController> mNotificationShadeDepthControllerLazy;
     private ShadeController mShadeController;
     private FakeExecutor mUiBgExecutor = new FakeExecutor(new FakeSystemClock());
     private InitController mInitController = new InitController();
@@ -404,6 +406,7 @@
                 mPhoneStatusBarPolicy,
                 mKeyguardIndicationController,
                 mDismissCallbackRegistry,
+                mNotificationShadeDepthControllerLazy,
                 mStatusBarTouchableRegionManager);
 
         when(mNotificationShadeWindowView.findViewById(R.id.lock_icon_container)).thenReturn(
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 052026c..5faed43 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -126,6 +126,7 @@
         "android.hardware.rebootescrow-java",
         "android.hardware.soundtrigger-V2.3-java",
         "android.hidl.manager-V1.2-java",
+        "capture_state_listener-aidl-java",
         "dnsresolver_aidl_interface-V2-java",
         "netd_event_listener_interface-java",
         "overlayable_policy_aidl-java",
diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
index 808d322..bfcde97 100644
--- a/services/core/java/com/android/server/RescueParty.java
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -98,8 +98,8 @@
 
     private static final String PROP_DISABLE_RESCUE = "persist.sys.disable_rescue";
     private static final String PROP_VIRTUAL_DEVICE = "ro.hardware.virtual_device";
-
-    private static final String DEVICE_CONFIG_DISABLE_FLAG = "disable_rescue_party";
+    private static final String PROP_DEVICE_CONFIG_DISABLE_FLAG =
+            "persist.device_config.configuration.disable_rescue_party";
 
     private static final int PERSISTENT_MASK = ApplicationInfo.FLAG_PERSISTENT
             | ApplicationInfo.FLAG_SYSTEM;
@@ -118,8 +118,7 @@
 
         // We're disabled if the DeviceConfig disable flag is set to true.
         // This is in case that an emergency rollback of the feature is needed.
-        if (DeviceConfig.getBoolean(
-                DeviceConfig.NAMESPACE_CONFIGURATION, DEVICE_CONFIG_DISABLE_FLAG, false)) {
+        if (SystemProperties.getBoolean(PROP_DEVICE_CONFIG_DISABLE_FLAG, false)) {
             Slog.v(TAG, "Disabled because of DeviceConfig flag");
             return true;
         }
diff --git a/services/core/java/com/android/server/TEST_MAPPING b/services/core/java/com/android/server/TEST_MAPPING
index 059eb6a..df16058 100644
--- a/services/core/java/com/android/server/TEST_MAPPING
+++ b/services/core/java/com/android/server/TEST_MAPPING
@@ -32,7 +32,7 @@
             "name": "CtsWindowManagerDeviceTestCases",
             "options": [
                 {
-                    "include-filter": "android.server.wm.ToastTest"
+                    "include-filter": "android.server.wm.ToastWindowTest"
                 }
             ],
             "file_patterns": ["NotificationManagerService\\.java"]
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 689f64d0..85d28831 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -17698,7 +17698,7 @@
 
             proc.setReportedForegroundServiceTypes(fgServiceTypes);
             ProcessChangeItem item = enqueueProcessChangeItemLocked(proc.pid, proc.info.uid);
-            item.changes = ProcessChangeItem.CHANGE_FOREGROUND_SERVICES;
+            item.changes |= ProcessChangeItem.CHANGE_FOREGROUND_SERVICES;
             item.foregroundServiceTypes = fgServiceTypes;
         }
         if (oomAdj) {
diff --git a/services/core/java/com/android/server/am/CarUserSwitchingDialog.java b/services/core/java/com/android/server/am/CarUserSwitchingDialog.java
index a6811e3..ea607cb 100644
--- a/services/core/java/com/android/server/am/CarUserSwitchingDialog.java
+++ b/services/core/java/com/android/server/am/CarUserSwitchingDialog.java
@@ -35,6 +35,8 @@
 import android.provider.Settings;
 import android.view.LayoutInflater;
 import android.view.View;
+import android.view.WindowInsets;
+import android.view.WindowManager;
 import android.widget.ImageView;
 import android.widget.TextView;
 
@@ -50,12 +52,19 @@
 final class CarUserSwitchingDialog extends UserSwitchingDialog {
 
     private static final String TAG = "ActivityManagerCarUserSwitchingDialog";
+    private View mView;
 
     public CarUserSwitchingDialog(ActivityManagerService service, Context context, UserInfo oldUser,
             UserInfo newUser, boolean aboveSystem, String switchingFromSystemUserMessage,
             String switchingToSystemUserMessage) {
         super(service, context, oldUser, newUser, aboveSystem, switchingFromSystemUserMessage,
                 switchingToSystemUserMessage);
+
+        // {@link UserSwitchingDialog} uses {@link WindowManager.LayoutParams.TYPE_SYSTEM_ERROR}
+        // when trying to show dialog above system. That window type has been deprecated and since
+        // this is a system dialog, hence, it makes sense to put this in System Dialog Window.
+        // This window also automatically shows status bar.
+        getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
     }
 
     @Override
@@ -65,7 +74,7 @@
         Resources res = getContext().getResources();
         // Custom view due to alignment and font size requirements
         getContext().setTheme(R.style.Theme_DeviceDefault_Light_Dialog_Alert_UserSwitchingDialog);
-        View view = LayoutInflater.from(getContext()).inflate(
+        mView = LayoutInflater.from(getContext()).inflate(
                 R.layout.car_user_switching_dialog,
                 null);
 
@@ -75,11 +84,11 @@
         if (bitmap != null) {
             CircleFramedDrawable drawable = CircleFramedDrawable.getInstance(bitmap,
                     res.getDimension(R.dimen.car_fullscreen_user_pod_image_avatar_height));
-            ((ImageView) view.findViewById(R.id.user_loading_avatar))
+            ((ImageView) mView.findViewById(R.id.user_loading_avatar))
                     .setImageDrawable(drawable);
         }
 
-        TextView msgView = view.findViewById(R.id.user_loading);
+        TextView msgView = mView.findViewById(R.id.user_loading);
 
         // TODO(b/145132885): use constant from CarSettings
         boolean showInfo = "true".equals(Settings.Global.getString(
@@ -92,7 +101,17 @@
         } else {
             msgView.setText(res.getString(R.string.car_loading_profile));
         }
-        setView(view);
+        setView(mView);
+    }
+
+    @Override
+    public void show() {
+        super.show();
+        hideNavigationBar();
+    }
+
+    private void hideNavigationBar() {
+        mView.getWindowInsetsController().hide(WindowInsets.Type.navigationBars());
     }
 
     /**
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 1412112..dbcb3da 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -2385,7 +2385,7 @@
                     "Changes in " + app + ": " + changes);
             ActivityManagerService.ProcessChangeItem item =
                     mService.enqueueProcessChangeItemLocked(app.pid, app.info.uid);
-            item.changes = changes;
+            item.changes |= changes;
             item.foregroundActivities = app.repForegroundActivities;
             item.capability = app.setCapability;
             if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG_PROCESS_OBSERVERS,
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index b546120..c2c79d3 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -324,7 +324,7 @@
     }
 
     /*package*/ void setBluetoothScoOn(boolean on, String eventSource) {
-        //Log.i(TAG, "setBluetoothScoOnInt: " + on + " " + eventSource);
+        //Log.i(TAG, "setBluetoothScoOn: " + on + " " + eventSource);
         synchronized (mDeviceStateLock) {
             if (on) {
                 // do not accept SCO ON if SCO audio is not connected
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index 36332c0..93d1bed 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -58,6 +58,7 @@
     }
 
     // List of clients having issued a SCO start request
+    @GuardedBy("BtHelper.this")
     private final @NonNull ArrayList<ScoClient> mScoClients = new ArrayList<ScoClient>();
 
     // BluetoothHeadset API to control SCO connection
@@ -356,9 +357,8 @@
         // client is created.
         final long ident = Binder.clearCallingIdentity();
         try {
-            eventSource += " client count before=" + client.getCount();
             AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(eventSource));
-            client.incCount(scoAudioMode);
+            client.requestScoState(BluetoothHeadset.STATE_AUDIO_CONNECTED, scoAudioMode);
         } catch (NullPointerException e) {
             Log.e(TAG, "Null ScoClient", e);
         }
@@ -375,9 +375,15 @@
         // and this must be done on behalf of system server to make sure permissions are granted.
         final long ident = Binder.clearCallingIdentity();
         if (client != null) {
-            eventSource += " client count before=" + client.getCount();
             AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(eventSource));
-            client.decCount();
+            client.requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED,
+                    SCO_MODE_VIRTUAL_CALL);
+            // If a disconnection is pending, the client will be removed whne clearAllScoClients()
+            // is called form receiveBtEvent()
+            if (mScoAudioState != SCO_STATE_DEACTIVATE_REQ
+                    && mScoAudioState != SCO_STATE_DEACTIVATING) {
+                client.remove(false /*stop */, true /*unregister*/);
+            }
         }
         Binder.restoreCallingIdentity(ident);
     }
@@ -657,25 +663,33 @@
     @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
     /*package*/ synchronized void scoClientDied(Object obj) {
         final ScoClient client = (ScoClient) obj;
+        client.remove(true /*stop*/, false /*unregister*/);
         Log.w(TAG, "SCO client died");
-        int index = mScoClients.indexOf(client);
-        if (index < 0) {
-            Log.w(TAG, "unregistered SCO client died");
-        } else {
-            client.clearCount(true);
-            mScoClients.remove(client);
-        }
     }
 
     private class ScoClient implements IBinder.DeathRecipient {
         private IBinder mCb; // To be notified of client's death
         private int mCreatorPid;
-        private int mStartcount; // number of SCO connections started by this client
 
         ScoClient(IBinder cb) {
             mCb = cb;
             mCreatorPid = Binder.getCallingPid();
-            mStartcount = 0;
+        }
+
+        public void registerDeathRecipient() {
+            try {
+                mCb.linkToDeath(this, 0);
+            } catch (RemoteException e) {
+                Log.w(TAG, "ScoClient could not link to " + mCb + " binder death");
+            }
+        }
+
+        public void unregisterDeathRecipient() {
+            try {
+                mCb.unlinkToDeath(this, 0);
+            } catch (NoSuchElementException e) {
+                Log.w(TAG, "ScoClient could not not unregistered to binder");
+            }
         }
 
         @Override
@@ -685,70 +699,6 @@
             mDeviceBroker.postScoClientDied(this);
         }
 
-        // @GuardedBy("AudioDeviceBroker.mSetModeLock")
-        // @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
-        @GuardedBy("BtHelper.this")
-        void incCount(int scoAudioMode) {
-            if (!requestScoState(BluetoothHeadset.STATE_AUDIO_CONNECTED, scoAudioMode)) {
-                Log.e(TAG, "Request sco connected with scoAudioMode("
-                        + scoAudioMode + ") failed");
-                return;
-            }
-            if (mStartcount == 0) {
-                try {
-                    mCb.linkToDeath(this, 0);
-                } catch (RemoteException e) {
-                    // client has already died!
-                    Log.w(TAG, "ScoClient  incCount() could not link to "
-                            + mCb + " binder death");
-                }
-            }
-            mStartcount++;
-        }
-
-        // @GuardedBy("AudioDeviceBroker.mSetModeLock")
-        // @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
-        @GuardedBy("BtHelper.this")
-        void decCount() {
-            if (mStartcount == 0) {
-                Log.w(TAG, "ScoClient.decCount() already 0");
-            } else {
-                mStartcount--;
-                if (mStartcount == 0) {
-                    try {
-                        mCb.unlinkToDeath(this, 0);
-                    } catch (NoSuchElementException e) {
-                        Log.w(TAG, "decCount() going to 0 but not registered to binder");
-                    }
-                }
-                if (!requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, 0)) {
-                    Log.w(TAG, "Request sco disconnected with scoAudioMode(0) failed");
-                }
-            }
-        }
-
-        // @GuardedBy("AudioDeviceBroker.mSetModeLock")
-        // @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
-        @GuardedBy("BtHelper.this")
-        void clearCount(boolean stopSco) {
-            if (mStartcount != 0) {
-                try {
-                    mCb.unlinkToDeath(this, 0);
-                } catch (NoSuchElementException e) {
-                    Log.w(TAG, "clearCount() mStartcount: "
-                            + mStartcount + " != 0 but not registered to binder");
-                }
-            }
-            mStartcount = 0;
-            if (stopSco) {
-                requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, 0);
-            }
-        }
-
-        int getCount() {
-            return mStartcount;
-        }
-
         IBinder getBinder() {
             return mCb;
         }
@@ -757,23 +707,14 @@
             return mCreatorPid;
         }
 
-        private int totalCount() {
-            int count = 0;
-            for (ScoClient mScoClient : mScoClients) {
-                count += mScoClient.getCount();
-            }
-            return count;
-        }
-
         // @GuardedBy("AudioDeviceBroker.mSetModeLock")
         //@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
         @GuardedBy("BtHelper.this")
         private boolean requestScoState(int state, int scoAudioMode) {
             checkScoAudioState();
-            int clientCount = totalCount();
-            if (clientCount != 0) {
+            if (mScoClients.size() != 1) {
                 Log.i(TAG, "requestScoState: state=" + state + ", scoAudioMode=" + scoAudioMode
-                        + ", clientCount=" + clientCount);
+                        + ", num SCO clients=" + mScoClients.size());
                 return true;
             }
             if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) {
@@ -842,12 +783,14 @@
                         mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
                         broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTED);
                         break;
+                    case SCO_STATE_ACTIVE_INTERNAL:
+                        Log.w(TAG, "requestScoState: already in ACTIVE mode, simply return");
+                        break;
                     default:
                         Log.w(TAG, "requestScoState: failed to connect in state "
                                 + mScoAudioState + ", scoAudioMode=" + scoAudioMode);
                         broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
                         return false;
-
                 }
             } else if (state == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
                 switch (mScoAudioState) {
@@ -893,6 +836,18 @@
             }
             return true;
         }
+
+        @GuardedBy("BtHelper.this")
+        void remove(boolean stop, boolean unregister) {
+            if (unregister) {
+                unregisterDeathRecipient();
+            }
+            if (stop) {
+                requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED,
+                        SCO_MODE_VIRTUAL_CALL);
+            }
+            mScoClients.remove(this);
+        }
     }
 
     //-----------------------------------------------------
@@ -946,6 +901,7 @@
     }
 
 
+    @GuardedBy("BtHelper.this")
     private ScoClient getScoClient(IBinder cb, boolean create) {
         for (ScoClient existingClient : mScoClients) {
             if (existingClient.getBinder() == cb) {
@@ -954,6 +910,7 @@
         }
         if (create) {
             ScoClient newClient = new ScoClient(cb);
+            newClient.registerDeathRecipient();
             mScoClients.add(newClient);
             return newClient;
         }
@@ -964,18 +921,16 @@
     //@GuardedBy("AudioDeviceBroker.mDeviceStateLock")
     @GuardedBy("BtHelper.this")
     private void clearAllScoClients(int exceptPid, boolean stopSco) {
-        ScoClient savedClient = null;
+        final ArrayList<ScoClient> clients = new ArrayList<ScoClient>();
         for (ScoClient cl : mScoClients) {
             if (cl.getPid() != exceptPid) {
-                cl.clearCount(stopSco);
-            } else {
-                savedClient = cl;
+                clients.add(cl);
             }
         }
-        mScoClients.clear();
-        if (savedClient != null) {
-            mScoClients.add(savedClient);
+        for (ScoClient cl : clients) {
+            cl.remove(stopSco, true /*unregister*/);
         }
+
     }
 
     private boolean getBluetoothHeadset() {
diff --git a/services/core/java/com/android/server/audio/RecordingActivityMonitor.java b/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
index 65f2218..32c6cc3 100644
--- a/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
@@ -150,14 +150,14 @@
         final AudioRecordingConfiguration config = createRecordingConfiguration(
                 uid, session, source, recordingInfo,
                 portId, silenced, activeSource, clientEffects, effects);
-        if (source == MediaRecorder.AudioSource.REMOTE_SUBMIX) {
+        if (source == MediaRecorder.AudioSource.REMOTE_SUBMIX
+                && (event == AudioManager.RECORD_CONFIG_EVENT_START
+                        || event == AudioManager.RECORD_CONFIG_EVENT_UPDATE)) {
             final AudioDeviceInfo device = config.getAudioDevice();
-            if (AudioSystem.LEGACY_REMOTE_SUBMIX_ADDRESS.equals(device.getAddress())) {
+            if (device != null
+                    && AudioSystem.LEGACY_REMOTE_SUBMIX_ADDRESS.equals(device.getAddress())) {
                 mLegacyRemoteSubmixRiid.set(riid);
-                if (event == AudioManager.RECORD_CONFIG_EVENT_START
-                        || event == AudioManager.RECORD_CONFIG_EVENT_UPDATE) {
-                    mLegacyRemoteSubmixActive.set(true);
-                }
+                mLegacyRemoteSubmixActive.set(true);
             }
         }
         if (MediaRecorder.isSystemOnlyAudioSource(source)) {
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index eb0257e..bcf262d 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -51,6 +51,7 @@
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
+import com.android.server.wm.ActivityTaskManagerInternal;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -75,6 +76,7 @@
     private final PowerManager mPowerManager;
     private final PowerManagerInternal mPowerManagerInternal;
     private final PowerManager.WakeLock mDozeWakeLock;
+    private final ActivityTaskManagerInternal mAtmInternal;
 
     private Binder mCurrentDreamToken;
     private ComponentName mCurrentDreamName;
@@ -97,6 +99,7 @@
 
         mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
         mPowerManagerInternal = getLocalService(PowerManagerInternal.class);
+        mAtmInternal = getLocalService(ActivityTaskManagerInternal.class);
         mDozeWakeLock = mPowerManager.newWakeLock(PowerManager.DOZE_WAKE_LOCK, TAG);
         mDozeConfig = new AmbientDisplayConfiguration(mContext);
     }
@@ -383,8 +386,10 @@
 
         PowerManager.WakeLock wakeLock = mPowerManager
                 .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "startDream");
-        mHandler.post(wakeLock.wrap(
-                () -> mController.startDream(newToken, name, isTest, canDoze, userId, wakeLock)));
+        mHandler.post(wakeLock.wrap(() -> {
+            mAtmInternal.notifyDreamStateChanged(true);
+            mController.startDream(newToken, name, isTest, canDoze, userId, wakeLock);
+        }));
     }
 
     private void stopDreamLocked(final boolean immediate) {
@@ -422,6 +427,7 @@
         }
         mCurrentDreamDozeScreenState = Display.STATE_UNKNOWN;
         mCurrentDreamDozeScreenBrightness = PowerManager.BRIGHTNESS_DEFAULT;
+        mAtmInternal.notifyDreamStateChanged(false);
     }
 
     private void checkPermission(String permission) {
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 0bba172..4896ba8 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -70,10 +70,8 @@
     private static final String TAG = "MR2ServiceImpl";
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
-    /**
-     * TODO: Change this with the real request ID from MediaRouter2 when
-     * MediaRouter2 needs to get notified for the failures.
-     */
+    // TODO: (In Android S or later) if we add callback methods for generic failures
+    //       in MediaRouter2, remove this constant and replace the usages with the real request IDs.
     private static final long DUMMY_REQUEST_ID = -1;
 
     private final Context mContext;
@@ -493,7 +491,7 @@
         }
     }
 
-    //TODO: Review this is handling multi-user properly.
+    //TODO(b/136703681): Review this is handling multi-user properly.
     void switchUser() {
         synchronized (mLock) {
             int userId = ActivityManager.getCurrentUser();
@@ -568,7 +566,9 @@
 
         UserRecord userRecord = routerRecord.mUserRecord;
         userRecord.mRouterRecords.remove(routerRecord);
-        //TODO: update discovery request
+        userRecord.mHandler.sendMessage(
+                obtainMessage(UserHandler::updateDiscoveryPreferenceOnHandler,
+                        userRecord.mHandler));
         routerRecord.dispose();
         disposeUserIfNeededLocked(userRecord); // since router removed from user
     }
@@ -793,7 +793,7 @@
         }
 
         long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId);
-        //TODO: Use MediaRouter2's OnCreateSessionListener to send proper session hints.
+        //TODO(b/152851868): Use MediaRouter2's OnCreateSessionListener to send session hints.
         routerRecord.mUserRecord.mHandler.sendMessage(
                 obtainMessage(UserHandler::requestCreateSessionOnHandler,
                         routerRecord.mUserRecord.mHandler,
@@ -1146,7 +1146,6 @@
             return mSessionToRouterMap.get(uniqueSessionId);
         }
 
-        //TODO: notify session info updates
         private void onProviderStateChangedOnHandler(@NonNull MediaRoute2Provider provider) {
             int providerInfoIndex = getLastProviderInfoIndex(provider.getUniqueId());
             MediaRoute2ProviderInfo providerInfo = provider.getProviderInfo();
@@ -1323,7 +1322,7 @@
                 return true;
             }
 
-            //TODO: Handle RCN case.
+            //TODO(b/152950479): Handle RCN case.
             if (routerRecord == null) {
                 Slog.w(TAG, "Ignoring " + description + " route from unknown router.");
                 return false;
@@ -1403,7 +1402,8 @@
 
         private void onSessionCreatedOnHandler(@NonNull MediaRoute2Provider provider,
                 long uniqueRequestId, @NonNull RoutingSessionInfo sessionInfo) {
-            notifySessionCreatedToManagers(getManagers(), sessionInfo);
+            notifySessionCreatedToManagers(getManagers(),
+                    toOriginalRequestId(uniqueRequestId), sessionInfo);
 
             if (uniqueRequestId == REQUEST_ID_NONE) {
                 // The session is created without any matching request.
@@ -1457,7 +1457,7 @@
         private void onSessionInfoChangedOnHandler(@NonNull MediaRoute2Provider provider,
                 @NonNull RoutingSessionInfo sessionInfo) {
             List<IMediaRouter2Manager> managers = getManagers();
-            notifySessionInfosChangedToManagers(managers);
+            notifySessionInfoChangedToManagers(managers, sessionInfo);
 
             // For system provider, notify all routers.
             if (provider == mSystemProvider) {
@@ -1480,7 +1480,7 @@
         private void onSessionReleasedOnHandler(@NonNull MediaRoute2Provider provider,
                 @NonNull RoutingSessionInfo sessionInfo) {
             List<IMediaRouter2Manager> managers = getManagers();
-            notifySessionInfosChangedToManagers(managers);
+            notifySessionInfoChangedToManagers(managers, sessionInfo);
 
             RouterRecord routerRecord = mSessionToRouterMap.get(sessionInfo.getId());
             if (routerRecord == null) {
@@ -1558,7 +1558,8 @@
         private void notifySessionCreationFailedToRouter(@NonNull RouterRecord routerRecord,
                 int requestId) {
             try {
-                routerRecord.mRouter.notifySessionCreated(requestId, /* sessionInfo= */ null);
+                routerRecord.mRouter.notifySessionCreated(requestId,
+                        /* sessionInfo= */ null);
             } catch (RemoteException ex) {
                 Slog.w(TAG, "Failed to notify router of the session creation failure."
                         + " Router probably died.", ex);
@@ -1731,10 +1732,10 @@
         }
 
         private void notifySessionCreatedToManagers(@NonNull List<IMediaRouter2Manager> managers,
-                @NonNull RoutingSessionInfo sessionInfo) {
+                int requestId, @NonNull RoutingSessionInfo sessionInfo) {
             for (IMediaRouter2Manager manager : managers) {
                 try {
-                    manager.notifySessionCreated(sessionInfo);
+                    manager.notifySessionCreated(requestId, sessionInfo);
                 } catch (RemoteException ex) {
                     Slog.w(TAG, "notifySessionCreatedToManagers: "
                             + "failed to notify. Manager probably died.", ex);
@@ -1742,11 +1743,12 @@
             }
         }
 
-        private void notifySessionInfosChangedToManagers(
-                @NonNull List<IMediaRouter2Manager> managers) {
+        private void notifySessionInfoChangedToManagers(
+                @NonNull List<IMediaRouter2Manager> managers,
+                @NonNull RoutingSessionInfo sessionInfo) {
             for (IMediaRouter2Manager manager : managers) {
                 try {
-                    manager.notifySessionsUpdated();
+                    manager.notifySessionUpdated(sessionInfo);
                 } catch (RemoteException ex) {
                     Slog.w(TAG, "notifySessionInfosChangedToManagers: "
                             + "failed to notify. Manager probably died.", ex);
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 4aeddc8..e8d8ed7 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -107,7 +107,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
-import android.annotation.UserIdInt;
 import android.annotation.WorkerThread;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
@@ -155,6 +154,7 @@
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.ParceledListSlice;
+import android.content.pm.ShortcutInfo;
 import android.content.pm.UserInfo;
 import android.content.res.Resources;
 import android.database.ContentObserver;
@@ -1730,6 +1730,11 @@
     }
 
     @VisibleForTesting
+    void setShortcutHelper(ShortcutHelper helper) {
+        mShortcutHelper = helper;
+    }
+
+    @VisibleForTesting
     void setHints(int hints) {
         mListenerHints = hints;
     }
@@ -3459,10 +3464,14 @@
             ArrayList<ConversationChannelWrapper> conversations =
                     mPreferencesHelper.getConversations(onlyImportant);
             for (ConversationChannelWrapper conversation : conversations) {
-                conversation.setShortcutInfo(mShortcutHelper.getValidShortcutInfo(
-                        conversation.getNotificationChannel().getConversationId(),
-                        conversation.getPkg(),
-                        UserHandle.of(UserHandle.getUserId(conversation.getUid()))));
+                if (mShortcutHelper == null) {
+                    conversation.setShortcutInfo(null);
+                } else {
+                    conversation.setShortcutInfo(mShortcutHelper.getValidShortcutInfo(
+                            conversation.getNotificationChannel().getConversationId(),
+                            conversation.getPkg(),
+                            UserHandle.of(UserHandle.getUserId(conversation.getUid()))));
+                }
             }
             return new ParceledListSlice<>(conversations);
         }
@@ -3482,10 +3491,14 @@
             ArrayList<ConversationChannelWrapper> conversations =
                     mPreferencesHelper.getConversations(pkg, uid);
             for (ConversationChannelWrapper conversation : conversations) {
-                conversation.setShortcutInfo(mShortcutHelper.getValidShortcutInfo(
-                        conversation.getNotificationChannel().getConversationId(),
-                        pkg,
-                        UserHandle.of(UserHandle.getUserId(uid))));
+                if (mShortcutHelper == null) {
+                    conversation.setShortcutInfo(null);
+                } else {
+                    conversation.setShortcutInfo(mShortcutHelper.getValidShortcutInfo(
+                            conversation.getNotificationChannel().getConversationId(),
+                            pkg,
+                            UserHandle.of(UserHandle.getUserId(uid))));
+                }
             }
             return new ParceledListSlice<>(conversations);
         }
@@ -5680,8 +5693,10 @@
             }
         }
 
-        r.setShortcutInfo(mShortcutHelper.getValidShortcutInfo(
-                notification.getShortcutId(), pkg, user));
+        ShortcutInfo info = mShortcutHelper != null
+                ? mShortcutHelper.getValidShortcutInfo(notification.getShortcutId(), pkg, user)
+                : null;
+        r.setShortcutInfo(info);
 
         if (!checkDisqualifyingFeatures(userId, notificationUid, id, tag, r,
                 r.getSbn().getOverrideGroupKey() != null)) {
@@ -6214,8 +6229,11 @@
                     cancelGroupChildrenLocked(r, mCallingUid, mCallingPid, listenerName,
                             mSendDelete, childrenFlagChecker);
                     updateLightsLocked();
-                    mShortcutHelper.maybeListenForShortcutChangesForBubbles(r, true /* isRemoved */,
-                            mHandler);
+                    if (mShortcutHelper != null) {
+                        mShortcutHelper.maybeListenForShortcutChangesForBubbles(r,
+                                true /* isRemoved */,
+                                mHandler);
+                    }
                 } else {
                     // No notification was found, assume that it is snoozed and cancel it.
                     if (mReason != REASON_SNOOZED) {
@@ -6453,9 +6471,11 @@
                                 + n.getPackageName());
                     }
 
-                    mShortcutHelper.maybeListenForShortcutChangesForBubbles(r,
-                            false /* isRemoved */,
-                            mHandler);
+                    if (mShortcutHelper != null) {
+                        mShortcutHelper.maybeListenForShortcutChangesForBubbles(r,
+                                false /* isRemoved */,
+                                mHandler);
+                    }
 
                     maybeRecordInterruptionLocked(r);
 
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index 32cfaf6..dbb246e 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -20,6 +20,7 @@
 import static android.app.NotificationManager.IMPORTANCE_NONE;
 import static android.app.NotificationManager.IMPORTANCE_UNSPECIFIED;
 
+import static com.android.internal.util.FrameworkStatsLog.ANNOTATION_ID_IS_UID;
 import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES;
 import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES;
 import static com.android.internal.util.FrameworkStatsLog.PACKAGE_NOTIFICATION_PREFERENCES;
@@ -1798,6 +1799,7 @@
                         .setAtomId(PACKAGE_NOTIFICATION_PREFERENCES);
                 final PackagePreferences r = mPackagePreferences.valueAt(i);
                 event.writeInt(r.uid);
+                event.addBooleanAnnotation(ANNOTATION_ID_IS_UID, true);
                 event.writeInt(r.importance);
                 event.writeInt(r.visibility);
                 event.writeInt(r.lockedAppFields);
@@ -1825,6 +1827,7 @@
                     StatsEvent.Builder event = StatsEvent.newBuilder()
                             .setAtomId(PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES);
                     event.writeInt(r.uid);
+                    event.addBooleanAnnotation(ANNOTATION_ID_IS_UID, true);
                     event.writeString(channel.getId());
                     event.writeString(channel.getName().toString());
                     event.writeString(channel.getDescription());
@@ -1856,6 +1859,7 @@
                     StatsEvent.Builder event = StatsEvent.newBuilder()
                             .setAtomId(PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES);
                     event.writeInt(r.uid);
+                    event.addBooleanAnnotation(ANNOTATION_ID_IS_UID, true);
                     event.writeString(groupChannel.getId());
                     event.writeString(groupChannel.getName().toString());
                     event.writeString(groupChannel.getDescription());
diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java
index 805d918..09b782d 100644
--- a/services/core/java/com/android/server/pm/AppsFilter.java
+++ b/services/core/java/com/android/server/pm/AppsFilter.java
@@ -674,7 +674,8 @@
             Trace.endSection();
 
             if (callingPkgSetting != null) {
-                if (!mFeatureConfig.packageIsEnabled(callingPkgSetting.pkg)) {
+                if (callingPkgSetting.pkg != null
+                        && !mFeatureConfig.packageIsEnabled(callingPkgSetting.pkg)) {
                     if (DEBUG_LOGGING) {
                         log(callingSetting, targetPkgSetting, "DISABLED");
                     }
@@ -682,7 +683,8 @@
                 }
             } else {
                 for (int i = callingSharedPkgSettings.size() - 1; i >= 0; i--) {
-                    if (!mFeatureConfig.packageIsEnabled(callingSharedPkgSettings.valueAt(i).pkg)) {
+                    final AndroidPackage pkg = callingSharedPkgSettings.valueAt(i).pkg;
+                    if (pkg != null && !mFeatureConfig.packageIsEnabled(pkg)) {
                         if (DEBUG_LOGGING) {
                             log(callingSetting, targetPkgSetting, "DISABLED");
                         }
diff --git a/services/core/java/com/android/server/pm/ComponentResolver.java b/services/core/java/com/android/server/pm/ComponentResolver.java
index f497f11..f1e1433 100644
--- a/services/core/java/com/android/server/pm/ComponentResolver.java
+++ b/services/core/java/com/android/server/pm/ComponentResolver.java
@@ -54,6 +54,7 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
 import com.android.server.IntentResolver;
 import com.android.server.pm.parsing.PackageInfoUtils;
@@ -207,33 +208,57 @@
     }
 
     /** Returns the given activity */
-    ParsedActivity getActivity(ComponentName component) {
+    @Nullable
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public ParsedActivity getActivity(@NonNull ComponentName component) {
         synchronized (mLock) {
             return mActivities.mActivities.get(component);
         }
     }
 
     /** Returns the given provider */
-    ParsedProvider getProvider(ComponentName component) {
+    @Nullable
+    ParsedProvider getProvider(@NonNull ComponentName component) {
         synchronized (mLock) {
             return mProviders.mProviders.get(component);
         }
     }
 
     /** Returns the given receiver */
-    ParsedActivity getReceiver(ComponentName component) {
+    @Nullable
+    ParsedActivity getReceiver(@NonNull ComponentName component) {
         synchronized (mLock) {
             return mReceivers.mActivities.get(component);
         }
     }
 
     /** Returns the given service */
-    ParsedService getService(ComponentName component) {
+    @Nullable
+    ParsedService getService(@NonNull ComponentName component) {
         synchronized (mLock) {
             return mServices.mServices.get(component);
         }
     }
 
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public boolean componentExists(@NonNull ComponentName componentName) {
+        synchronized (mLock) {
+            ParsedMainComponent component = mActivities.mActivities.get(componentName);
+            if (component != null) {
+                return true;
+            }
+            component = mReceivers.mActivities.get(componentName);
+            if (component != null) {
+                return true;
+            }
+            component = mServices.mServices.get(componentName);
+            if (component != null) {
+                return true;
+            }
+            return mProviders.mProviders.get(componentName) != null;
+        }
+    }
+
     @Nullable
     List<ResolveInfo> queryActivities(Intent intent, String resolvedType, int flags,
             @PrivateResolveFlags int privateResolveFlags, int userId) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 8850f29..f96ab1d 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -674,7 +674,7 @@
 
     final ServiceThread mHandlerThread;
 
-    final PackageHandler mHandler;
+    final Handler mHandler;
 
     private final ProcessLoggingHandler mProcessLoggingHandler;
 
@@ -1033,6 +1033,62 @@
         }
     }
 
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public static class TestParams {
+        public ApexManager apexManager;
+        public @Nullable String appPredictionServicePackage;
+        public ArtManagerService artManagerService;
+        public @Nullable String configuratorPackage;
+        public int defParseFlags;
+        public DexManager dexManager;
+        public List<ScanPartition> dirsToScanAsSystem;
+        public @Nullable String documenterPackage;
+        public boolean factoryTest;
+        public ArrayMap<String, FeatureInfo> availableFeatures;
+        public Handler handler;
+        public ServiceThread handlerThread;
+        public @Nullable String incidentReportApproverPackage;
+        public IncrementalManager incrementalManager;
+        public PackageInstallerService installerService;
+        public InstantAppRegistry instantAppRegistry;
+        public InstantAppResolverConnection instantAppResolverConnection;
+        public ComponentName instantAppResolverSettingsComponent;
+        public @Nullable IntentFilterVerifier<ParsedIntentInfo> intentFilterVerifier;
+        public @Nullable ComponentName intentFilterVerifierComponent;
+        public boolean isPreNmr1Upgrade;
+        public boolean isPreNupgrade;
+        public boolean isPreQupgrade;
+        public boolean isUpgrade;
+        public DisplayMetrics Metrics;
+        public ModuleInfoProvider moduleInfoProvider;
+        public MoveCallbacks moveCallbacks;
+        public boolean onlyCore;
+        public OverlayConfig overlayConfig;
+        public PackageDexOptimizer packageDexOptimizer;
+        public PackageParser2.Callback packageParserCallback;
+        public IPermissionManager permissionManagerService;
+        public PendingPackageBroadcasts pendingPackageBroadcasts;
+        public PackageManagerInternal pmInternal;
+        public ProcessLoggingHandler processLoggingHandler;
+        public ProtectedPackages protectedPackages;
+        public @NonNull String requiredInstallerPackage;
+        public @NonNull String requiredPermissionControllerPackage;
+        public @NonNull String requiredUninstallerPackage;
+        public @Nullable String requiredVerifierPackage;
+        public String[] separateProcesses;
+        public @NonNull String servicesExtensionPackageName;
+        public @Nullable String setupWizardPackage;
+        public @NonNull String sharedSystemSharedLibraryPackageName;
+        public @Nullable String storageManagerPackage;
+        public @Nullable String defaultTextClassifierPackage;
+        public @Nullable String systemTextClassifierPackage;
+        public ViewCompiler viewCompiler;
+        public @Nullable String wellbeingPackage;
+        public @Nullable String retailDemoPackage;
+        public ComponentName resolveComponentName;
+        public ArrayMap<String, AndroidPackage> packages;
+    }
+
     private final AppsFilter mAppsFilter;
 
     final PackageParser2.Callback mPackageParserCallback;
@@ -1396,7 +1452,8 @@
     }
 
     // Set of pending broadcasts for aggregating enable/disable of components.
-    static class PendingPackageBroadcasts {
+    @VisibleForTesting(visibility = Visibility.PACKAGE)
+    public static class PendingPackageBroadcasts {
         // for each user id, a map of <package name -> components within that package>
         final SparseArray<ArrayMap<String, ArrayList<String>>> mUidMap;
 
@@ -1459,7 +1516,7 @@
             return map;
         }
     }
-    final PendingPackageBroadcasts mPendingBroadcasts = new PendingPackageBroadcasts();
+    final PendingPackageBroadcasts mPendingBroadcasts;
 
     static final int SEND_PENDING_BROADCAST = 1;
     static final int INIT_COPY = 5;
@@ -2676,6 +2733,82 @@
         }
     }
 
+    /**
+     * A extremely minimal constructor designed to start up a PackageManagerService instance for
+     * testing.
+     *
+     * It is assumed that all methods under test will mock the internal fields and thus
+     * none of the initialization is needed.
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    public PackageManagerService(@NonNull Injector injector, @NonNull TestParams testParams) {
+        mInjector = injector;
+        mInjector.bootstrap(this);
+        mAppsFilter = injector.getAppsFilter();
+        mComponentResolver = injector.getComponentResolver();
+        mContext = injector.getContext();
+        mInstaller = injector.getInstaller();
+        mInstallLock = injector.getInstallLock();
+        mLock = injector.getLock();
+        mPermissionManager = injector.getPermissionManagerServiceInternal();
+        mSettings = injector.getSettings();
+        mUserManager = injector.getUserManagerService();
+
+        mApexManager = testParams.apexManager;
+        mArtManagerService = testParams.artManagerService;
+        mAvailableFeatures = testParams.availableFeatures;
+        mDefParseFlags = testParams.defParseFlags;
+        mDexManager = testParams.dexManager;
+        mDirsToScanAsSystem = testParams.dirsToScanAsSystem;
+        mFactoryTest = testParams.factoryTest;
+        mHandler = testParams.handler;
+        mHandlerThread = testParams.handlerThread;
+        mIncrementalManager = testParams.incrementalManager;
+        mInstallerService = testParams.installerService;
+        mInstantAppRegistry = testParams.instantAppRegistry;
+        mInstantAppResolverConnection = testParams.instantAppResolverConnection;
+        mInstantAppResolverSettingsComponent = testParams.instantAppResolverSettingsComponent;
+        mIntentFilterVerifier = testParams.intentFilterVerifier;
+        mIntentFilterVerifierComponent = testParams.intentFilterVerifierComponent;
+        mIsPreNMR1Upgrade = testParams.isPreNmr1Upgrade;
+        mIsPreNUpgrade = testParams.isPreNupgrade;
+        mIsPreQUpgrade = testParams.isPreQupgrade;
+        mIsUpgrade = testParams.isUpgrade;
+        mMetrics = testParams.Metrics;
+        mModuleInfoProvider = testParams.moduleInfoProvider;
+        mMoveCallbacks = testParams.moveCallbacks;
+        mOnlyCore = testParams.onlyCore;
+        mOverlayConfig = testParams.overlayConfig;
+        mPackageDexOptimizer = testParams.packageDexOptimizer;
+        mPackageParserCallback = testParams.packageParserCallback;
+        mPendingBroadcasts = testParams.pendingPackageBroadcasts;
+        mPermissionManagerService = testParams.permissionManagerService;
+        mPmInternal = testParams.pmInternal;
+        mProcessLoggingHandler = testParams.processLoggingHandler;
+        mProtectedPackages = testParams.protectedPackages;
+        mSeparateProcesses = testParams.separateProcesses;
+        mViewCompiler = testParams.viewCompiler;
+        mRequiredVerifierPackage = testParams.requiredVerifierPackage;
+        mRequiredInstallerPackage = testParams.requiredInstallerPackage;
+        mRequiredUninstallerPackage = testParams.requiredUninstallerPackage;
+        mRequiredPermissionControllerPackage = testParams.requiredPermissionControllerPackage;
+        mSetupWizardPackage = testParams.setupWizardPackage;
+        mStorageManagerPackage = testParams.storageManagerPackage;
+        mDefaultTextClassifierPackage = testParams.defaultTextClassifierPackage;
+        mSystemTextClassifierPackageName = testParams.systemTextClassifierPackage;
+        mWellbeingPackage = testParams.wellbeingPackage;
+        mRetailDemoPackage = testParams.retailDemoPackage;
+        mDocumenterPackage = testParams.documenterPackage;
+        mConfiguratorPackage = testParams.configuratorPackage;
+        mAppPredictionServicePackage = testParams.appPredictionServicePackage;
+        mIncidentReportApproverPackage = testParams.incidentReportApproverPackage;
+        mServicesExtensionPackageName = testParams.servicesExtensionPackageName;
+        mSharedSystemSharedLibraryPackageName = testParams.sharedSystemSharedLibraryPackageName;
+
+        mResolveComponentName = testParams.resolveComponentName;
+        mPackages.putAll(testParams.packages);
+    }
+
     public PackageManagerService(Injector injector, boolean onlyCore, boolean factoryTest) {
         PackageManager.invalidatePackageInfoCache();
         PackageManager.disableApplicationInfoCache();
@@ -2683,6 +2816,8 @@
 
         final TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG + "Timing",
                 Trace.TRACE_TAG_PACKAGE_MANAGER);
+        mPendingBroadcasts = new PendingPackageBroadcasts();
+
         mInjector = injector;
         mInjector.bootstrap(this);
         mLock = injector.getLock();
@@ -5168,7 +5303,7 @@
 
             AndroidPackage pkg = a == null ? null : mPackages.get(a.getPackageName());
             if (pkg != null && mSettings.isEnabledAndMatchLPr(pkg, a, flags, userId)) {
-                PackageSetting ps = mSettings.mPackages.get(component.getPackageName());
+                PackageSetting ps = mSettings.getPackageLPr(component.getPackageName());
                 if (ps == null) return null;
                 if (shouldFilterApplicationLocked(
                         ps, filterCallingUid, component, TYPE_ACTIVITY, userId)) {
@@ -15609,6 +15744,12 @@
                         // these install state changes will be persisted in the
                         // upcoming call to mSettings.writeLPr().
                     }
+
+                    if (allUsers != null) {
+                        for (int currentUserId : allUsers) {
+                            ps.resetOverrideComponentLabelIcon(currentUserId);
+                        }
+                    }
                 }
 
                 // Retrieve the overlays for shared libraries of the package.
@@ -20177,6 +20318,86 @@
     }
 
     @Override
+    public void overrideLabelAndIcon(@NonNull ComponentName componentName,
+            @NonNull String nonLocalizedLabel, int icon, int userId) {
+        if (TextUtils.isEmpty(nonLocalizedLabel)) {
+            throw new IllegalArgumentException("Override label should be a valid String");
+        }
+        updateComponentLabelIcon(componentName, nonLocalizedLabel, icon, userId);
+    }
+
+    @Override
+    public void restoreLabelAndIcon(@NonNull ComponentName componentName, int userId) {
+        updateComponentLabelIcon(componentName, null, null, userId);
+    }
+
+    @VisibleForTesting(visibility = Visibility.PRIVATE)
+    public void updateComponentLabelIcon(/*@NonNull*/ ComponentName componentName,
+            @Nullable String nonLocalizedLabel, @Nullable Integer icon, int userId) {
+        if (componentName == null) {
+            throw new IllegalArgumentException("Must specify a component");
+        }
+
+        boolean componentExists = mComponentResolver.componentExists(componentName);
+        if (!componentExists) {
+            throw new IllegalArgumentException("Component " + componentName + " not found");
+        }
+
+        int callingUid = Binder.getCallingUid();
+
+        String componentPkgName = componentName.getPackageName();
+        int componentUid = getPackageUid(componentPkgName, 0, userId);
+        if (!UserHandle.isSameApp(callingUid, componentUid)) {
+            throw new SecurityException("The calling UID (" + callingUid + ")"
+                    + " does not match the target UID");
+        }
+
+        String allowedCallerPkg = mContext.getString(R.string.config_overrideComponentUiPackage);
+        if (TextUtils.isEmpty(allowedCallerPkg)) {
+            throw new SecurityException(
+                    "There is no package defined as allowed to change a component's label or icon");
+        }
+
+        int allowedCallerUid = getPackageUid(allowedCallerPkg, PackageManager.MATCH_SYSTEM_ONLY,
+                userId);
+        if (allowedCallerUid == -1 || !UserHandle.isSameApp(callingUid, allowedCallerUid)) {
+            throw new SecurityException("The calling UID (" + callingUid + ")"
+                    + " is not allowed to change a component's label or icon");
+        }
+
+        synchronized (mLock) {
+            AndroidPackage pkg = mPackages.get(componentPkgName);
+            PackageSetting pkgSetting = getPackageSetting(componentPkgName);
+            if (pkg == null || pkgSetting == null
+                    || (!pkg.isSystem() && !pkgSetting.getPkgState().isUpdatedSystemApp())) {
+                throw new SecurityException(
+                        "Changing the label is not allowed for " + componentName);
+            }
+
+            if (!pkgSetting.overrideNonLocalizedLabelAndIcon(componentName, nonLocalizedLabel,
+                    icon, userId)) {
+                // Nothing changed
+                return;
+            }
+        }
+
+        ArrayList<String> components = mPendingBroadcasts.get(userId, componentPkgName);
+        if (components == null) {
+            components = new ArrayList<>();
+            mPendingBroadcasts.put(userId, componentPkgName, components);
+        }
+
+        String className = componentName.getClassName();
+        if (!components.contains(className)) {
+            components.add(className);
+        }
+
+        if (!mHandler.hasMessages(SEND_PENDING_BROADCAST)) {
+            mHandler.sendEmptyMessageDelayed(SEND_PENDING_BROADCAST, BROADCAST_DELAY);
+        }
+    }
+
+    @Override
     public void setComponentEnabledSetting(ComponentName componentName,
             int newState, int flags, int userId) {
         if (!mUserManager.exists(userId)) return;
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 9a8692d..432d7f3 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -26,6 +26,7 @@
 import android.util.ArraySet;
 import android.util.proto.ProtoOutputStream;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.pm.parsing.pkg.AndroidPackage;
 import com.android.server.pm.permission.PermissionsState;
 import com.android.server.pm.pkg.PackageStateUnserialized;
@@ -43,6 +44,7 @@
 public class PackageSetting extends PackageSettingBase {
     int appId;
 
+    @Nullable
     public AndroidPackage pkg;
     /**
      * WARNING. The object reference is important. We perform integer equality and NOT
@@ -68,7 +70,8 @@
     @NonNull
     private PackageStateUnserialized pkgState = new PackageStateUnserialized();
 
-    PackageSetting(String name, String realName, File codePath, File resourcePath,
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public PackageSetting(String name, String realName, File codePath, File resourcePath,
             String legacyNativeLibraryPathString, String primaryCpuAbiString,
             String secondaryCpuAbiString, String cpuAbiOverrideString,
             long pVersionCode, int pkgFlags, int privateFlags,
diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java
index 7cb3df5..00a5fe7 100644
--- a/services/core/java/com/android/server/pm/PackageSettingBase.java
+++ b/services/core/java/com/android/server/pm/PackageSettingBase.java
@@ -21,6 +21,9 @@
 import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.ComponentName;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IntentFilterVerificationInfo;
 import android.content.pm.PackageManager;
@@ -697,6 +700,26 @@
         return userState.harmfulAppWarning;
     }
 
+    /**
+     * @see PackageUserState#overrideLabelAndIcon(ComponentName, String, Integer)
+     *
+     * @param userId the specific user to change the label/icon for
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public boolean overrideNonLocalizedLabelAndIcon(@NonNull ComponentName component,
+            @Nullable String label, @Nullable Integer icon, @UserIdInt int userId) {
+        return modifyUserState(userId).overrideLabelAndIcon(component, label, icon);
+    }
+
+    /**
+     * @see PackageUserState#resetOverrideComponentLabelIcon()
+     *
+     * @param userId the specific user to reset
+     */
+    public void resetOverrideComponentLabelIcon(@UserIdInt int userId) {
+        modifyUserState(userId).resetOverrideComponentLabelIcon();
+    }
+
     protected PackageSettingBase updateFrom(PackageSettingBase other) {
         super.copyFrom(other);
         this.codePath = other.codePath;
diff --git a/services/core/java/com/android/server/pm/SettingBase.java b/services/core/java/com/android/server/pm/SettingBase.java
index ec9746d..3e2ab05 100644
--- a/services/core/java/com/android/server/pm/SettingBase.java
+++ b/services/core/java/com/android/server/pm/SettingBase.java
@@ -18,9 +18,11 @@
 
 import android.content.pm.ApplicationInfo;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.pm.permission.PermissionsState;
 
-abstract class SettingBase {
+@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+public abstract class SettingBase {
     int pkgFlags;
     int pkgPrivateFlags;
 
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index f6ca87d..091535d 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -91,6 +91,7 @@
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.CollectionUtils;
@@ -418,6 +419,21 @@
     /** Settings and other information about permissions */
     final PermissionSettings mPermissions;
 
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
+    public Settings(Map<String, PackageSetting> pkgSettings) {
+        mLock = new Object();
+        mPackages.putAll(pkgSettings);
+        mSystemDir = null;
+        mPermissions = null;
+        mRuntimePermissionsPersistence = null;
+        mSettingsFilename = null;
+        mBackupSettingsFilename = null;
+        mPackageListFilename = null;
+        mStoppedPackagesFilename = null;
+        mBackupStoppedPackagesFilename = null;
+        mKernelMappingFilename = null;
+    }
+
     Settings(File dataDir, PermissionSettings permission,
             Object lock) {
         mLock = lock;
@@ -4328,8 +4344,9 @@
         return userState.isMatch(componentInfo, flags);
     }
 
-    boolean isEnabledAndMatchLPr(AndroidPackage pkg, ParsedMainComponent component, int flags,
-            int userId) {
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+    public boolean isEnabledAndMatchLPr(AndroidPackage pkg, ParsedMainComponent component,
+            int flags, int userId) {
         final PackageSetting ps = mPackages.get(component.getPackageName());
         if (ps == null) return false;
 
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index 8768ab0..8d53d15 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -2385,6 +2385,30 @@
         }
     }
 
+    public boolean isSharingShortcut(int callingUserId, @NonNull String callingPackage,
+            @NonNull String packageName, @NonNull String shortcutId, int userId,
+            @NonNull IntentFilter filter) {
+        verifyCaller(callingPackage, callingUserId);
+        enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_APP_PREDICTIONS,
+                "isSharingShortcut");
+
+        synchronized (mLock) {
+            throwIfUserLockedL(userId);
+            throwIfUserLockedL(callingUserId);
+
+            final List<ShortcutManager.ShareShortcutInfo> matchedTargets =
+                    getPackageShortcutsLocked(packageName, userId)
+                            .getMatchingShareTargets(filter);
+            final int matchedSize = matchedTargets.size();
+            for (int i = 0; i < matchedSize; i++) {
+                if (matchedTargets.get(i).getShortcutInfo().getId().equals(shortcutId)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
     @GuardedBy("mLock")
     private ParceledListSlice<ShortcutInfo> getShortcutsWithQueryLocked(@NonNull String packageName,
             @UserIdInt int userId, int cloneFlags, @NonNull Predicate<ShortcutInfo> query) {
@@ -2969,6 +2993,18 @@
                     callingPackage, intentFilter, userId).getList();
         }
 
+        @Override
+        public boolean isSharingShortcut(int callingUserId, @NonNull String callingPackage,
+                @NonNull String packageName, @NonNull String shortcutId, int userId,
+                @NonNull IntentFilter filter) {
+            Preconditions.checkStringNotEmpty(callingPackage, "callingPackage");
+            Preconditions.checkStringNotEmpty(packageName, "packageName");
+            Preconditions.checkStringNotEmpty(shortcutId, "shortcutId");
+
+            return ShortcutService.this.isSharingShortcut(callingUserId, callingPackage,
+                    packageName, shortcutId, userId, filter);
+        }
+
         private void updateCachedShortcutsInternal(int launcherUserId,
                 @NonNull String callingPackage, @NonNull String packageName,
                 @NonNull List<String> shortcutIds, int userId, boolean doCache) {
diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index 5a1e8e2..137e0aa 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -48,6 +48,7 @@
 import android.os.UserHandle;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.Pair;
 import android.util.Slog;
 
 import com.android.internal.util.ArrayUtils;
@@ -271,7 +272,7 @@
 
         ActivityInfo info =
                 PackageInfoWithoutStateUtils.generateActivityInfoUnchecked(a, applicationInfo);
-        assignSharedFieldsForComponentInfo(info, a, pkgSetting);
+        assignSharedFieldsForComponentInfo(info, a, pkgSetting, userId);
         return info;
     }
 
@@ -306,7 +307,7 @@
 
         ServiceInfo info =
                 PackageInfoWithoutStateUtils.generateServiceInfoUnchecked(s, applicationInfo);
-        assignSharedFieldsForComponentInfo(info, s, pkgSetting);
+        assignSharedFieldsForComponentInfo(info, s, pkgSetting, userId);
         return info;
     }
 
@@ -333,7 +334,7 @@
         }
         ProviderInfo info = PackageInfoWithoutStateUtils.generateProviderInfoUnchecked(p, flags,
                 applicationInfo);
-        assignSharedFieldsForComponentInfo(info, p, pkgSetting);
+        assignSharedFieldsForComponentInfo(info, p, pkgSetting, userId);
         return info;
     }
 
@@ -358,7 +359,7 @@
         info.nativeLibraryDir = pkg.getNativeLibraryDir();
         info.secondaryNativeLibraryDir = pkg.getSecondaryNativeLibraryDir();
 
-        assignStateFieldsForPackageItemInfo(info, i, pkgSetting);
+        assignStateFieldsForPackageItemInfo(info, i, pkgSetting, userId);
 
         return info;
     }
@@ -426,8 +427,9 @@
     }
 
     private static void assignSharedFieldsForComponentInfo(@NonNull ComponentInfo componentInfo,
-            @NonNull ParsedMainComponent mainComponent, @Nullable PackageSetting pkgSetting) {
-        assignStateFieldsForPackageItemInfo(componentInfo, mainComponent, pkgSetting);
+            @NonNull ParsedMainComponent mainComponent, @Nullable PackageSetting pkgSetting,
+            int userId) {
+        assignStateFieldsForPackageItemInfo(componentInfo, mainComponent, pkgSetting, userId);
         componentInfo.descriptionRes = mainComponent.getDescriptionRes();
         componentInfo.directBootAware = mainComponent.isDirectBootAware();
         componentInfo.enabled = mainComponent.isEnabled();
@@ -436,8 +438,12 @@
 
     private static void assignStateFieldsForPackageItemInfo(
             @NonNull PackageItemInfo packageItemInfo, @NonNull ParsedComponent component,
-            @Nullable PackageSetting pkgSetting) {
-        // TODO(b/135203078): Add setting related state
+            @Nullable PackageSetting pkgSetting, int userId) {
+        Pair<CharSequence, Integer> labelAndIcon =
+                ParsedComponentStateUtils.getNonLocalizedLabelAndIcon(component, pkgSetting,
+                        userId);
+        packageItemInfo.nonLocalizedLabel = labelAndIcon.first;
+        packageItemInfo.icon = labelAndIcon.second;
     }
 
     @CheckResult
diff --git a/services/core/java/com/android/server/pm/parsing/ParsedComponentStateUtils.java b/services/core/java/com/android/server/pm/parsing/ParsedComponentStateUtils.java
new file mode 100644
index 0000000..54466ac
--- /dev/null
+++ b/services/core/java/com/android/server/pm/parsing/ParsedComponentStateUtils.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.parsing;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.parsing.component.ParsedComponent;
+import android.util.Pair;
+
+import com.android.server.pm.PackageSetting;
+
+/**
+ * For exposing internal fields to the rest of the server, enforcing that any overridden state from
+ * a {@link com.android.server.pm.PackageSetting} is applied.
+ *
+ * TODO(chiuwinson): The fields on ParsedComponent are not actually hidden. Will need to find a
+ *   way to enforce the mechanism now that they exist in core instead of server. Can't rely on
+ *   package-private.
+ *
+ * @hide
+ */
+public class ParsedComponentStateUtils {
+
+    @NonNull
+    public static Pair<CharSequence, Integer> getNonLocalizedLabelAndIcon(ParsedComponent component,
+            @Nullable PackageSetting pkgSetting, int userId) {
+        CharSequence label = component.getNonLocalizedLabel();
+        int icon = component.getIcon();
+
+        Pair<String, Integer> overrideLabelIcon = pkgSetting == null ? null :
+                pkgSetting.readUserState(userId)
+                        .getOverrideLabelIconForComponent(component.getComponentName());
+        if (overrideLabelIcon != null) {
+            if (overrideLabelIcon.first != null) {
+                label = overrideLabelIcon.first;
+            }
+            if (overrideLabelIcon.second != null) {
+                icon = overrideLabelIcon.second;
+            }
+        }
+
+        return Pair.create(label, icon);
+    }
+}
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 82c02a4..b7c9ecb 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -2497,10 +2497,24 @@
         synchronized (mLock) {
             ArraySet<String> newImplicitPermissions = new ArraySet<>();
 
+            // TODO ntmyren: Remove once propagated to droidfood
+            int flagMask = PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED
+                    | PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED;
+            int user = UserHandle.getUserId(pkg.getUid());
+
             final int N = pkg.getRequestedPermissions().size();
             for (int i = 0; i < N; i++) {
                 final String permName = pkg.getRequestedPermissions().get(i);
                 final BasePermission bp = mSettings.getPermissionLocked(permName);
+
+                // TODO ntmyren: Remove once propagated to droidfood
+                if (bp != null && !bp.isRuntime()) {
+                    PermissionState permState = permissionsState.getInstallPermissionState(bp.name);
+                    if (permState == null || (permState.getFlags() & flagMask) != 0) {
+                        permissionsState.updatePermissionFlags(bp, user, flagMask, 0);
+                    }
+                }
+
                 final boolean appSupportsRuntimePermissions =
                         pkg.getTargetSdkVersion() >= Build.VERSION_CODES.M;
                 String upgradedActivityRecognitionPermission = null;
diff --git a/services/core/java/com/android/server/policy/PermissionPolicyService.java b/services/core/java/com/android/server/policy/PermissionPolicyService.java
index 27288d8..161f304 100644
--- a/services/core/java/com/android/server/policy/PermissionPolicyService.java
+++ b/services/core/java/com/android/server/policy/PermissionPolicyService.java
@@ -30,7 +30,6 @@
 import android.annotation.UserIdInt;
 import android.app.AppOpsManager;
 import android.app.AppOpsManagerInternal;
-import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -48,7 +47,6 @@
 import android.os.UserHandle;
 import android.os.UserManagerInternal;
 import android.permission.PermissionControllerManager;
-import android.provider.Settings;
 import android.provider.Telephony;
 import android.telecom.TelecomManager;
 import android.util.ArrayMap;
@@ -72,9 +70,7 @@
 import com.android.server.policy.PermissionPolicyInternal.OnInitializedCallback;
 
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.List;
-import java.util.Map;
 import java.util.concurrent.ExecutionException;
 
 /**
@@ -184,6 +180,8 @@
         intentFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
         intentFilter.addDataScheme("package");
 
+
+        /* TODO ntmyren: enable receiver when test flakes are fixed
         getContext().registerReceiverAsUser(new BroadcastReceiver() {
             final List<Integer> mUserSetupUids = new ArrayList<>(200);
             final Map<UserHandle, PermissionControllerManager> mPermControllerManagers =
@@ -234,6 +232,7 @@
                 manager.updateUserSensitiveForApp(uid);
             }
         }, UserHandle.ALL, intentFilter, null, null);
+         */
     }
 
     /**
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ExternalCaptureStateTracker.java b/services/core/java/com/android/server/soundtrigger_middleware/ExternalCaptureStateTracker.java
new file mode 100644
index 0000000..7977e93
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/ExternalCaptureStateTracker.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2020 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.soundtrigger_middleware;
+
+import android.media.ICaptureStateListener;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+import java.util.concurrent.Semaphore;
+import java.util.function.Consumer;
+
+/**
+ * This is a never-give-up listener for sound trigger external capture state notifications, as
+ * published by the audio policy service.
+ *
+ * This class will constantly try to connect to the service over a background thread and tolerate
+ * its death. The client will be notified by a single provided function that is called in a
+ * synchronized manner.
+ * For simplicity, there is currently no way to stop the tracker. This is possible to add if the
+ * need ever arises.
+ */
+class ExternalCaptureStateTracker {
+    private static final String TAG = "CaptureStateTracker";
+    /** Our client's listener. */
+    private final Consumer<Boolean> mListener;
+    /** This semaphore will get a permit every time we need to reconnect. */
+    private final Semaphore mNeedToConnect = new Semaphore(1);
+
+    /**
+     * Constructor. Will start a background thread to do the work.
+     *
+     * @param listener A client provided listener that will be called on state
+     *                 changes. May be
+     *                 called multiple consecutive times with the same value. Never
+     *                 called
+     *                 concurrently.
+     */
+    ExternalCaptureStateTracker(Consumer<Boolean> listener) {
+        mListener = listener;
+        new Thread(this::run).start();
+    }
+
+    /**
+     * Routine for the background thread. Keeps trying to reconnect.
+     */
+    private void run() {
+        while (true) {
+            mNeedToConnect.acquireUninterruptibly();
+            connect();
+        }
+    }
+
+    /**
+     * Connect to the service, install listener and death notifier.
+     */
+    private native void connect();
+
+    /**
+     * Called by native code to invoke the client listener.
+     *
+     * @param active true when external capture is active.
+     */
+    private void setCaptureState(boolean active) {
+        mListener.accept(active);
+    }
+
+    /**
+     * Called by native code when the remote service died.
+     */
+    private void binderDied() {
+        Log.w(TAG, "Audio policy service died");
+        mNeedToConnect.release();
+    }
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerMiddlewareInternal.java b/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerMiddlewareInternal.java
new file mode 100644
index 0000000..5def762
--- /dev/null
+++ b/services/core/java/com/android/server/soundtrigger_middleware/ISoundTriggerMiddlewareInternal.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2020 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.soundtrigger_middleware;
+
+import android.media.ICaptureStateListener;
+import android.media.soundtrigger_middleware.ISoundTriggerMiddlewareService;
+
+/**
+ * This interface unifies ISoundTriggerMiddlewareService with ICaptureStateListener.
+ */
+public interface ISoundTriggerMiddlewareInternal extends ISoundTriggerMiddlewareService,
+        ICaptureStateListener {
+}
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java
index 9f4b09a..d76b1bf 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImpl.java
@@ -50,7 +50,7 @@
  *
  * @hide
  */
-public class SoundTriggerMiddlewareImpl implements ISoundTriggerMiddlewareService {
+public class SoundTriggerMiddlewareImpl implements ISoundTriggerMiddlewareInternal {
     static private final String TAG = "SoundTriggerMiddlewareImpl";
     private final SoundTriggerModule[] mModules;
 
@@ -124,7 +124,7 @@
     }
 
     @Override
-    public void setExternalCaptureState(boolean active) {
+    public void setCaptureState(boolean active) {
         for (SoundTriggerModule module : mModules) {
             module.setExternalCaptureState(active);
         }
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java
index fa78cb0..04ba6bf 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareLogging.java
@@ -62,11 +62,11 @@
  * String, Object, Object[])}, {@link #logVoidReturnWithObject(Object, String, Object[])} and {@link
  * #logExceptionWithObject(Object, String, Exception, Object[])}.
  */
-public class SoundTriggerMiddlewareLogging implements ISoundTriggerMiddlewareService, Dumpable {
+public class SoundTriggerMiddlewareLogging implements ISoundTriggerMiddlewareInternal, Dumpable {
     private static final String TAG = "SoundTriggerMiddlewareLogging";
-    private final @NonNull ISoundTriggerMiddlewareService mDelegate;
+    private final @NonNull ISoundTriggerMiddlewareInternal mDelegate;
 
-    public SoundTriggerMiddlewareLogging(@NonNull ISoundTriggerMiddlewareService delegate) {
+    public SoundTriggerMiddlewareLogging(@NonNull ISoundTriggerMiddlewareInternal delegate) {
         mDelegate = delegate;
     }
 
@@ -96,12 +96,12 @@
     }
 
     @Override
-    public void setExternalCaptureState(boolean active) throws RemoteException {
+    public void setCaptureState(boolean active) throws RemoteException {
         try {
-            mDelegate.setExternalCaptureState(active);
-            logVoidReturn("setExternalCaptureState", active);
+            mDelegate.setCaptureState(active);
+            logVoidReturn("setCaptureState", active);
         } catch (Exception e) {
-            logException("setExternalCaptureState", e, active);
+            logException("setCaptureState", e, active);
             throw e;
         }
     }
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java
index 0d8fc76..929d92f 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java
@@ -63,14 +63,21 @@
     static private final String TAG = "SoundTriggerMiddlewareService";
 
     @NonNull
-    private final ISoundTriggerMiddlewareService mDelegate;
+    private final ISoundTriggerMiddlewareInternal mDelegate;
 
     /**
      * Constructor for internal use only. Could be exposed for testing purposes in the future.
      * Users should access this class via {@link Lifecycle}.
      */
-    private SoundTriggerMiddlewareService(@NonNull ISoundTriggerMiddlewareService delegate) {
+    private SoundTriggerMiddlewareService(@NonNull ISoundTriggerMiddlewareInternal delegate) {
         mDelegate = Objects.requireNonNull(delegate);
+        new ExternalCaptureStateTracker(active -> {
+            try {
+                mDelegate.setCaptureState(active);
+            } catch (RemoteException e) {
+                throw e.rethrowAsRuntimeException();
+            }
+        });
     }
 
     @Override
@@ -86,11 +93,6 @@
         return new ModuleService(mDelegate.attach(handle, callback));
     }
 
-    @Override
-    public void setExternalCaptureState(boolean active) throws RemoteException {
-        mDelegate.setExternalCaptureState(active);
-    }
-
     @Override protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
         if (mDelegate instanceof Dumpable) {
             ((Dumpable) mDelegate).dump(fout);
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java
index 06f2d65..008933f 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareValidation.java
@@ -105,7 +105,7 @@
  *
  * {@hide}
  */
-public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddlewareService, Dumpable {
+public class SoundTriggerMiddlewareValidation implements ISoundTriggerMiddlewareInternal, Dumpable {
     private static final String TAG = "SoundTriggerMiddlewareValidation";
 
     private enum ModuleState {
@@ -114,12 +114,12 @@
         DEAD
     };
 
-    private final @NonNull ISoundTriggerMiddlewareService mDelegate;
+    private final @NonNull ISoundTriggerMiddlewareInternal mDelegate;
     private final @NonNull Context mContext;
     private Map<Integer, Set<ModuleService>> mModules;
 
     public SoundTriggerMiddlewareValidation(
-            @NonNull ISoundTriggerMiddlewareService delegate, @NonNull Context context) {
+            @NonNull ISoundTriggerMiddlewareInternal delegate, @NonNull Context context) {
         mDelegate = delegate;
         mContext = context;
     }
@@ -213,21 +213,15 @@
     }
 
     @Override
-    public void setExternalCaptureState(boolean active) {
-        // Permission check.
-        checkPreemptPermissions();
-        // Input validation (always valid).
-
-        // State validation (always valid).
-
+    public void setCaptureState(boolean active) {
+        // This is an internal call. No permissions needed.
+        //
         // Normally, we would acquire a lock here. However, we do not access any state here so it
         // is safe to not lock. This call is typically done from a different context than all the
         // other calls and may result in a deadlock if we lock here (between the audio server and
         // the system server).
-
-        // From here on, every exception isn't client's fault.
         try {
-            mDelegate.setExternalCaptureState(active);
+            mDelegate.setCaptureState(active);
         } catch (Exception e) {
             throw handleException(e);
         }
@@ -252,16 +246,6 @@
     /**
      * Throws a {@link SecurityException} if caller permanently doesn't have the given permission,
      * or a {@link ServiceSpecificException} with a {@link Status#TEMPORARY_PERMISSION_DENIED} if
-     * caller temporarily doesn't have the right permissions to preempt active sound trigger
-     * sessions.
-     */
-    void checkPreemptPermissions() {
-        enforcePermission(Manifest.permission.PREEMPT_SOUND_TRIGGER);
-    }
-
-    /**
-     * Throws a {@link SecurityException} if caller permanently doesn't have the given permission,
-     * or a {@link ServiceSpecificException} with a {@link Status#TEMPORARY_PERMISSION_DENIED} if
      * caller temporarily doesn't have the given permission.
      *
      * @param permission The permission to check.
@@ -806,4 +790,4 @@
             }
         }
     }
-}
\ No newline at end of file
+}
diff --git a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
index 155b2e0..24ab89b 100644
--- a/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
+++ b/services/core/java/com/android/server/stats/pull/StatsPullAtomService.java
@@ -29,6 +29,7 @@
 import static android.util.MathUtils.abs;
 import static android.util.MathUtils.constrain;
 
+import static com.android.internal.util.FrameworkStatsLog.ANNOTATION_ID_IS_UID;
 import static com.android.server.am.MemoryStatUtil.readMemoryStatFromFilesystem;
 import static com.android.server.stats.pull.IonMemoryUtil.readProcessSystemIonHeapSizesFromDebugfs;
 import static com.android.server.stats.pull.IonMemoryUtil.readSystemIonHeapSizeFromDebugfs;
@@ -750,6 +751,7 @@
             StatsEvent.Builder e = StatsEvent.newBuilder();
             e.setAtomId(atomTag);
             e.writeInt(entry.uid);
+            e.addBooleanAnnotation(ANNOTATION_ID_IS_UID, true);
             if (withFgbg) {
                 e.writeInt(entry.set);
             }
@@ -920,6 +922,7 @@
             StatsEvent e = StatsEvent.newBuilder()
                     .setAtomId(atomTag)
                     .writeInt(traffic.getUid())
+                    .addBooleanAnnotation(ANNOTATION_ID_IS_UID, true)
                     .writeLong(traffic.getRxBytes())
                     .writeLong(traffic.getTxBytes())
                     .build();
@@ -1006,6 +1009,7 @@
             StatsEvent e = StatsEvent.newBuilder()
                     .setAtomId(atomTag)
                     .writeInt(uid)
+                    .addBooleanAnnotation(ANNOTATION_ID_IS_UID, true)
                     .writeLong(userTimeUs)
                     .writeLong(systemTimeUs)
                     .build();
@@ -1036,6 +1040,7 @@
                     StatsEvent e = StatsEvent.newBuilder()
                             .setAtomId(atomTag)
                             .writeInt(uid)
+                            .addBooleanAnnotation(ANNOTATION_ID_IS_UID, true)
                             .writeInt(freqIndex)
                             .writeLong(cpuFreqTimeMs[freqIndex])
                             .build();
@@ -1066,6 +1071,7 @@
             StatsEvent e = StatsEvent.newBuilder()
                     .setAtomId(atomTag)
                     .writeInt(uid)
+                    .addBooleanAnnotation(ANNOTATION_ID_IS_UID, true)
                     .writeLong(cpuActiveTimesMs)
                     .build();
             pulledData.add(e);
@@ -1094,6 +1100,7 @@
                 StatsEvent e = StatsEvent.newBuilder()
                         .setAtomId(atomTag)
                         .writeInt(uid)
+                        .addBooleanAnnotation(ANNOTATION_ID_IS_UID, true)
                         .writeInt(i)
                         .writeLong(cpuClusterTimesMs[i])
                         .build();
@@ -1289,6 +1296,7 @@
             StatsEvent e = StatsEvent.newBuilder()
                     .setAtomId(atomTag)
                     .writeInt(processMemoryState.uid)
+                    .addBooleanAnnotation(ANNOTATION_ID_IS_UID, true)
                     .writeString(processMemoryState.processName)
                     .writeInt(processMemoryState.oomScore)
                     .writeLong(memoryStat.pgfault)
@@ -1331,6 +1339,7 @@
             StatsEvent e = StatsEvent.newBuilder()
                     .setAtomId(atomTag)
                     .writeInt(managedProcess.uid)
+                    .addBooleanAnnotation(ANNOTATION_ID_IS_UID, true)
                     .writeString(managedProcess.processName)
                     // RSS high-water mark in bytes.
                     .writeLong(snapshot.rssHighWaterMarkInKilobytes * 1024L)
@@ -1350,6 +1359,7 @@
             StatsEvent e = StatsEvent.newBuilder()
                     .setAtomId(atomTag)
                     .writeInt(snapshot.uid)
+                    .addBooleanAnnotation(ANNOTATION_ID_IS_UID, true)
                     .writeString(processCmdlines.valueAt(i))
                     // RSS high-water mark in bytes.
                     .writeLong(snapshot.rssHighWaterMarkInKilobytes * 1024L)
@@ -1384,6 +1394,7 @@
             StatsEvent e = StatsEvent.newBuilder()
                     .setAtomId(atomTag)
                     .writeInt(managedProcess.uid)
+                    .addBooleanAnnotation(ANNOTATION_ID_IS_UID, true)
                     .writeString(managedProcess.processName)
                     .writeInt(managedProcess.pid)
                     .writeInt(managedProcess.oomScore)
@@ -1409,6 +1420,7 @@
             StatsEvent e = StatsEvent.newBuilder()
                     .setAtomId(atomTag)
                     .writeInt(snapshot.uid)
+                    .addBooleanAnnotation(ANNOTATION_ID_IS_UID, true)
                     .writeString(processCmdlines.valueAt(i))
                     .writeInt(pid)
                     .writeInt(-1001)  // Placeholder for native processes, OOM_SCORE_ADJ_MIN - 1.
@@ -1481,6 +1493,7 @@
             StatsEvent e = StatsEvent.newBuilder()
                     .setAtomId(atomTag)
                     .writeInt(getUidForPid(allocations.pid))
+                    .addBooleanAnnotation(ANNOTATION_ID_IS_UID, true)
                     .writeString(readCmdlineFromProcfs(allocations.pid))
                     .writeInt((int) (allocations.totalSizeInBytes / 1024))
                     .writeInt(allocations.count)
@@ -1593,6 +1606,7 @@
             StatsEvent e = StatsEvent.newBuilder()
                     .setAtomId(atomTag)
                     .writeInt(callStat.workSourceUid)
+                    .addBooleanAnnotation(ANNOTATION_ID_IS_UID, true)
                     .writeString(callStat.className)
                     .writeString(callStat.methodName)
                     .writeLong(callStat.callCount)
@@ -1669,6 +1683,7 @@
             StatsEvent e = StatsEvent.newBuilder()
                     .setAtomId(atomTag)
                     .writeInt(entry.workSourceUid)
+                    .addBooleanAnnotation(ANNOTATION_ID_IS_UID, true)
                     .writeString(entry.handlerClassName)
                     .writeString(entry.threadName)
                     .writeString(entry.messageName)
@@ -2112,6 +2127,7 @@
             StatsEvent e = StatsEvent.newBuilder()
                     .setAtomId(atomTag)
                     .writeInt(uid)
+                    .addBooleanAnnotation(ANNOTATION_ID_IS_UID, true)
                     .writeLong(fgCharsRead)
                     .writeLong(fgCharsWrite)
                     .writeLong(fgBytesRead)
@@ -2177,6 +2193,7 @@
                 StatsEvent e = StatsEvent.newBuilder()
                         .setAtomId(atomTag)
                         .writeInt(st.uid)
+                        .addBooleanAnnotation(ANNOTATION_ID_IS_UID, true)
                         .writeString(st.name)
                         .writeLong(st.base_utime)
                         .writeLong(st.base_stime)
@@ -2235,6 +2252,7 @@
                 StatsEvent.Builder e = StatsEvent.newBuilder();
                 e.setAtomId(atomTag);
                 e.writeInt(processCpuUsage.uid);
+                e.addBooleanAnnotation(ANNOTATION_ID_IS_UID, true);
                 e.writeInt(processCpuUsage.processId);
                 e.writeInt(threadCpuUsage.threadId);
                 e.writeString(processCpuUsage.processName);
@@ -2326,6 +2344,7 @@
             StatsEvent e = StatsEvent.newBuilder()
                     .setAtomId(atomTag)
                     .writeInt(bs.uidObj.getUid())
+                    .addBooleanAnnotation(ANNOTATION_ID_IS_UID, true)
                     .writeLong(milliAmpHrsToNanoAmpSecs(bs.totalPowerMah))
                     .build();
             pulledData.add(e);
@@ -2530,6 +2549,7 @@
                         StatsEvent e = StatsEvent.newBuilder()
                                 .setAtomId(atomTag)
                                 .writeInt(pkg.applicationInfo.uid)
+                                .addBooleanAnnotation(ANNOTATION_ID_IS_UID, true)
                                 .writeString(holderName)
                                 .writeString(roleName)
                                 .build();
@@ -2613,6 +2633,7 @@
                         e.setAtomId(atomTag);
                         e.writeString(permName);
                         e.writeInt(pkg.applicationInfo.uid);
+                        e.addBooleanAnnotation(ANNOTATION_ID_IS_UID, true);
                         if (atomTag == FrameworkStatsLog.DANGEROUS_PERMISSION_STATE) {
                             e.writeString("");
                         }
@@ -2967,6 +2988,7 @@
         StatsEvent.Builder e = StatsEvent.newBuilder();
         e.setAtomId(atomTag);
         e.writeInt(uid);
+        e.addBooleanAnnotation(ANNOTATION_ID_IS_UID, true);
         e.writeString(packageName);
         if (atomTag == FrameworkStatsLog.ATTRIBUTED_APP_OPS) {
             e.writeString(attributionTag);
@@ -3015,6 +3037,7 @@
             StatsEvent.Builder e = StatsEvent.newBuilder();
             e.setAtomId(atomTag);
             e.writeInt(message.getUid());
+            e.addBooleanAnnotation(ANNOTATION_ID_IS_UID, true);
             e.writeString(message.getPackageName());
             e.writeString(message.getOp());
             if (message.getAttributionTag() == null) {
diff --git a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
index 0d16fcc..9a5b020 100644
--- a/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
+++ b/services/core/java/com/android/server/textclassifier/TextClassificationManagerService.java
@@ -772,6 +772,8 @@
         @NonNull
         final TextClassifierServiceConnection mConnection;
         final boolean mIsTrusted;
+        @Context.BindServiceFlags
+        final int mBindServiceFlags;
         @NonNull
         @GuardedBy("mLock")
         final Queue<PendingRequest> mPendingRequests = new ArrayDeque<>();
@@ -786,11 +788,22 @@
         @GuardedBy("mLock")
         int mBoundServiceUid = Process.INVALID_UID;
 
-        private ServiceState(@UserIdInt int userId, String packageName, boolean isTrusted) {
+        private ServiceState(
+                @UserIdInt int userId, @NonNull String packageName, boolean isTrusted) {
             mUserId = userId;
             mPackageName = packageName;
             mConnection = new TextClassifierServiceConnection(mUserId);
             mIsTrusted = isTrusted;
+            mBindServiceFlags = createBindServiceFlags(packageName);
+        }
+
+        @Context.BindServiceFlags
+        private int createBindServiceFlags(@NonNull String packageName) {
+            int flags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE;
+            if (!packageName.equals(mDefaultTextClassifierPackage)) {
+                flags |= Context.BIND_RESTRICT_ASSOCIATIONS;
+            }
+            return flags;
         }
 
         @GuardedBy("mLock")
@@ -858,10 +871,7 @@
                         .setComponent(componentName);
                 Slog.d(LOG_TAG, "Binding to " + serviceIntent.getComponent());
                 willBind = mContext.bindServiceAsUser(
-                        serviceIntent, mConnection,
-                        Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
-                                | Context.BIND_RESTRICT_ASSOCIATIONS,
-                        UserHandle.of(mUserId));
+                        serviceIntent, mConnection, mBindServiceFlags, UserHandle.of(mUserId));
                 mBinding = willBind;
             } finally {
                 Binder.restoreCallingIdentity(identity);
@@ -884,6 +894,7 @@
                 pw.printPair("packageName", mPackageName);
                 pw.printPair("boundComponentName", mBoundComponentName);
                 pw.printPair("isTrusted", mIsTrusted);
+                pw.printPair("bindServiceFlags", mBindServiceFlags);
                 pw.printPair("boundServiceUid", mBoundServiceUid);
                 pw.printPair("binding", mBinding);
                 pw.printPair("numberRequests", mPendingRequests.size());
diff --git a/services/core/java/com/android/server/timezonedetector/ReferenceWithHistory.java b/services/core/java/com/android/server/timezonedetector/ReferenceWithHistory.java
index 8bd1035..165419a 100644
--- a/services/core/java/com/android/server/timezonedetector/ReferenceWithHistory.java
+++ b/services/core/java/com/android/server/timezonedetector/ReferenceWithHistory.java
@@ -22,7 +22,7 @@
 
 import com.android.internal.util.IndentingPrintWriter;
 
-import java.util.LinkedList;
+import java.util.ArrayDeque;
 
 /**
  * A class that behaves like the following definition, except it stores the history of values set
@@ -50,11 +50,18 @@
  */
 public final class ReferenceWithHistory<V> {
 
-    /** The size the history linked list is allowed to grow to. */
+    private static final Object NULL_MARKER = "{null marker}";
+
+    /** The maximum number of references to store. */
     private final int mMaxHistorySize;
 
+    /**
+     * The history storage. Note that ArrayDeque doesn't support {@code null} so this stores Object
+     * and not V. Use {@link #packNullIfRequired(Object)} and {@link #unpackNullIfRequired(Object)}
+     * to convert to / from the storage object.
+     */
     @Nullable
-    private LinkedList<V> mValues;
+    private ArrayDeque<Object> mValues;
 
     /**
      * Creates an instance that records, at most, the specified number of values.
@@ -69,22 +76,31 @@
     /** Returns the current value, or {@code null} if it has never been set. */
     @Nullable
     public V get() {
-        return (mValues == null || mValues.isEmpty()) ? null : mValues.getFirst();
+        if (mValues == null || mValues.isEmpty()) {
+            return null;
+        }
+        Object value = mValues.getFirst();
+        return unpackNullIfRequired(value);
     }
 
-    /** Sets the current value. Returns the previous value, or {@code null}. */
+    /**
+     * Sets the current value. Returns the previous value, which can be {@code null} if the
+     * reference has never been set, or if the reference has been set to {@code null}.
+     */
     @Nullable
     public V set(@Nullable V newValue) {
         if (mValues == null) {
-            mValues = new LinkedList<>();
+            mValues = new ArrayDeque<>(mMaxHistorySize);
+        }
+
+        if (mValues.size() >= mMaxHistorySize) {
+            mValues.removeLast();
         }
 
         V previous = get();
 
-        mValues.addFirst(newValue);
-        if (mValues.size() > mMaxHistorySize) {
-            mValues.removeLast();
-        }
+        Object nullSafeValue = packNullIfRequired(newValue);
+        mValues.addFirst(nullSafeValue);
         return previous;
     }
 
@@ -96,8 +112,8 @@
             ipw.println("{Empty}");
         } else {
             int i = 0;
-            for (V value : mValues) {
-                ipw.println(i + ": " + value);
+            for (Object value : mValues) {
+                ipw.println(i + ": " + unpackNullIfRequired(value));
                 i++;
             }
         }
@@ -115,4 +131,23 @@
     public String toString() {
         return String.valueOf(get());
     }
+
+    /**
+     * Turns a non-nullable Object into a nullable value. See also
+     * {@link #packNullIfRequired(Object)}.
+     */
+    @SuppressWarnings("unchecked")
+    @Nullable
+    private V unpackNullIfRequired(@NonNull Object value) {
+        return value == NULL_MARKER ? null : (V) value;
+    }
+
+    /**
+     * Turns a nullable value into a non-nullable Object. See also
+     * {@link #unpackNullIfRequired(Object)}.
+     */
+    @NonNull
+    private Object packNullIfRequired(@Nullable V value) {
+        return value == null ? NULL_MARKER : value;
+    }
 }
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index ecafa69..c1c8440 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -36,6 +36,7 @@
 import static android.app.AppOpsManager.OP_PICTURE_IN_PICTURE;
 import static android.app.WaitResult.INVALID_DELAY;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_DREAM;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
@@ -269,6 +270,8 @@
 import android.os.Trace;
 import android.os.UserHandle;
 import android.os.storage.StorageManager;
+import android.service.dreams.DreamActivity;
+import android.service.dreams.DreamManagerInternal;
 import android.service.voice.IVoiceInteractionSession;
 import android.util.ArraySet;
 import android.util.EventLog;
@@ -2035,6 +2038,26 @@
         return false;
     }
 
+    static boolean canLaunchDreamActivity(String packageName) {
+        final DreamManagerInternal dreamManager =
+                LocalServices.getService(DreamManagerInternal.class);
+
+        // Verify that the package is the current active dream. The getActiveDreamComponent()
+        // call path does not acquire the DreamManager lock and thus is safe to use.
+        final ComponentName activeDream = dreamManager.getActiveDreamComponent(false /* doze */);
+        if (activeDream == null || activeDream.getPackageName() == null
+                || !activeDream.getPackageName().equals(packageName)) {
+            return false;
+        }
+
+        // Verify that the device is dreaming.
+        if (!LocalServices.getService(ActivityTaskManagerInternal.class).isDreaming()) {
+            return false;
+        }
+
+        return true;
+    }
+
     private void setActivityType(boolean componentSpecified, int launchedFromUid, Intent intent,
             ActivityOptions options, ActivityRecord sourceRecord) {
         int activityType = ACTIVITY_TYPE_UNDEFINED;
@@ -2054,6 +2077,10 @@
         } else if (options != null && options.getLaunchActivityType() == ACTIVITY_TYPE_ASSISTANT
                 && canLaunchAssistActivity(launchedFromPackage)) {
             activityType = ACTIVITY_TYPE_ASSISTANT;
+        } else if (options != null && options.getLaunchActivityType() == ACTIVITY_TYPE_DREAM
+                && canLaunchDreamActivity(launchedFromPackage)
+                && DreamActivity.class.getName() == info.name) {
+            activityType = ACTIVITY_TYPE_DREAM;
         }
         setActivityType(activityType);
     }
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 0ba1866..7a6da67 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -341,7 +341,6 @@
         int filterCallingUid;
         PendingIntentRecord originatingPendingIntent;
         boolean allowBackgroundActivityStart;
-        boolean isDream;
 
         /**
          * If set to {@code true}, allows this activity start to look into
@@ -393,7 +392,6 @@
             filterCallingUid = UserHandle.USER_NULL;
             originatingPendingIntent = null;
             allowBackgroundActivityStart = false;
-            isDream = false;
         }
 
         /**
@@ -434,7 +432,6 @@
             filterCallingUid = request.filterCallingUid;
             originatingPendingIntent = request.originatingPendingIntent;
             allowBackgroundActivityStart = request.allowBackgroundActivityStart;
-            isDream = request.isDream;
         }
 
         /**
@@ -985,7 +982,7 @@
                 restrictedBgActivity = shouldAbortBackgroundActivityStart(callingUid,
                         callingPid, callingPackage, realCallingUid, realCallingPid, callerApp,
                         request.originatingPendingIntent, request.allowBackgroundActivityStart,
-                        request.isDream, intent);
+                        intent);
             } finally {
                 Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
             }
@@ -1195,7 +1192,7 @@
     boolean shouldAbortBackgroundActivityStart(int callingUid, int callingPid,
             final String callingPackage, int realCallingUid, int realCallingPid,
             WindowProcessController callerApp, PendingIntentRecord originatingPendingIntent,
-            boolean allowBackgroundActivityStart, boolean isDream, Intent intent) {
+            boolean allowBackgroundActivityStart, Intent intent) {
         // don't abort for the most important UIDs
         final int callingAppId = UserHandle.getAppId(callingUid);
         if (callingUid == Process.ROOT_UID || callingAppId == Process.SYSTEM_UID
@@ -1203,10 +1200,6 @@
             return false;
         }
 
-        // don't abort if this is the dream activity
-        if (isDream) {
-            return false;
-        }
         // don't abort if the callingUid has a visible window or is a persistent system process
         final int callingUidProcState = mService.getUidState(callingUid);
         final boolean callingUidHasAnyVisibleWindow =
@@ -2717,11 +2710,6 @@
         return this;
     }
 
-    ActivityStarter setIsDream(boolean isDream) {
-        mRequest.isDream = isDream;
-        return this;
-    }
-
     void dump(PrintWriter pw, String prefix) {
         prefix = prefix + "  ";
         pw.print(prefix);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index 2263795..d48df9f 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -290,6 +290,11 @@
     public abstract void notifyActiveVoiceInteractionServiceChanged(ComponentName component);
 
     /**
+     * Called when the device changes its dreaming state.
+     */
+    public abstract void notifyDreamStateChanged(boolean dreaming);
+
+    /**
      * Set a uid that is allowed to bypass stopped app switches, launching an app
      * whenever it wants.
      *
@@ -318,6 +323,7 @@
     public abstract void clearHeavyWeightProcessIfEquals(WindowProcessController proc);
     public abstract void finishHeavyWeightApp();
 
+    public abstract boolean isDreaming();
     public abstract boolean isSleeping();
     public abstract boolean isShuttingDown();
     public abstract boolean shuttingDown(boolean booted, int timeout);
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 4fff860..682e991 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -212,7 +212,6 @@
 import android.os.storage.StorageManager;
 import android.provider.Settings;
 import android.service.dreams.DreamActivity;
-import android.service.dreams.DreamManagerInternal;
 import android.service.voice.IVoiceInteractionSession;
 import android.service.voice.VoiceInteractionManagerInternal;
 import android.sysprop.DisplayProperties;
@@ -599,6 +598,13 @@
     private boolean mSleeping = false;
 
     /**
+     * The mDreaming state is set by the {@link DreamManagerService} when it receives a request to
+     * start/stop the dream. It is set to true shortly  before the {@link DreamService} is started.
+     * It is set to false after the {@link DreamService} is stopped.
+     */
+    private boolean mDreaming = false;
+
+    /**
      * The process state used for processes that are running the top activities.
      * This changes between TOP and TOP_SLEEPING to following mSleeping.
      */
@@ -1238,36 +1244,29 @@
         }
     }
 
-    @Override
-    public boolean startDreamActivity(Intent intent) {
-        final WindowProcessController process = mProcessMap.getProcess(Binder.getCallingPid());
+    private void enforceCallerIsDream(String callerPackageName) {
         final long origId = Binder.clearCallingIdentity();
-
-        // The dream activity is only called for non-doze dreams.
-        final ComponentName currentDream = LocalServices.getService(DreamManagerInternal.class)
-                .getActiveDreamComponent(/* doze= */ false);
-
-        if (currentDream == null || currentDream.getPackageName() == null
-                || !currentDream.getPackageName().equals(process.mInfo.packageName)) {
-            Slog.e(TAG, "Calling package is not the current dream package. "
-                    + "Aborting startDreamActivity...");
-            return false;
+        try {
+            if (!ActivityRecord.canLaunchDreamActivity(callerPackageName)) {
+                throw new SecurityException("The dream activity can be started only when the device"
+                        + " is dreaming and only by the active dream package.");
+            }
+        } finally {
+            Binder.restoreCallingIdentity(origId);
         }
+    }
+
+    @Override
+    public boolean startDreamActivity(@NonNull Intent intent) {
+        assertPackageMatchesCallingUid(intent.getPackage());
+        enforceCallerIsDream(intent.getPackage());
 
         final ActivityInfo a = new ActivityInfo();
         a.theme = com.android.internal.R.style.Theme_Dream;
         a.exported = true;
         a.name = DreamActivity.class.getName();
-
-
-        a.packageName = process.mInfo.packageName;
-        a.applicationInfo = process.mInfo;
-        a.processName = process.mInfo.processName;
-        a.uiOptions = process.mInfo.uiOptions;
-        a.taskAffinity = "android:" + a.packageName + "/dream";
         a.enabled = true;
         a.launchMode = ActivityInfo.LAUNCH_SINGLE_INSTANCE;
-
         a.persistableMode = ActivityInfo.PERSIST_NEVER;
         a.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
         a.colorMode = ActivityInfo.COLOR_MODE_DEFAULT;
@@ -1276,15 +1275,34 @@
         final ActivityOptions options = ActivityOptions.makeBasic();
         options.setLaunchActivityType(ACTIVITY_TYPE_DREAM);
 
-        try {
-            getActivityStartController().obtainStarter(intent, "dream")
-                    .setActivityInfo(a)
-                    .setActivityOptions(options.toBundle())
-                    .setIsDream(true)
-                    .execute();
-            return true;
-        } finally {
-            Binder.restoreCallingIdentity(origId);
+        synchronized (mGlobalLock) {
+            final WindowProcessController process = mProcessMap.getProcess(Binder.getCallingPid());
+
+            a.packageName = process.mInfo.packageName;
+            a.applicationInfo = process.mInfo;
+            a.processName = process.mInfo.processName;
+            a.uiOptions = process.mInfo.uiOptions;
+            a.taskAffinity = "android:" + a.packageName + "/dream";
+
+            final int callingUid = Binder.getCallingUid();
+            final int callingPid = Binder.getCallingPid();
+
+            final long origId = Binder.clearCallingIdentity();
+            try {
+                getActivityStartController().obtainStarter(intent, "dream")
+                        .setCallingUid(callingUid)
+                        .setCallingPid(callingPid)
+                        .setActivityInfo(a)
+                        .setActivityOptions(options.toBundle())
+                        // To start the dream from background, we need to start it from a persistent
+                        // system process. Here we set the real calling uid to the system server uid
+                        .setRealCallingUid(Binder.getCallingUid())
+                        .setAllowBackgroundActivityStart(true)
+                        .execute();
+                return true;
+            } finally {
+                Binder.restoreCallingIdentity(origId);
+            }
         }
     }
 
@@ -2476,7 +2494,7 @@
         final ActivityStarter starter = getActivityStartController().obtainStarter(
                 null /* intent */, "moveTaskToFront");
         if (starter.shouldAbortBackgroundActivityStart(callingUid, callingPid, callingPackage, -1,
-                -1, callerApp, null, false, false, null)) {
+                -1, callerApp, null, false, null)) {
             if (!isBackgroundActivityStartsEnabled()) {
                 return;
             }
@@ -2784,7 +2802,8 @@
                     false /* includingParents */);
         }
         WindowContainerTransaction wct = new WindowContainerTransaction();
-        wct.reparent(task.getStack().mRemoteToken, primarySplitTask.mRemoteToken, toTop);
+        wct.reparent(task.getStack().mRemoteToken.toWindowContainerToken(),
+                primarySplitTask.mRemoteToken.toWindowContainerToken(), toTop);
         mWindowOrganizerController.applyTransaction(wct);
     }
 
@@ -4273,7 +4292,7 @@
                         tempDockedTaskInsetBounds != null ? tempDockedTaskInsetBounds
                             : (tempDockedTaskBounds != null ? tempDockedTaskBounds
                                     : dockedBounds);
-                wct.setBounds(primary.mRemoteToken, primaryRect);
+                wct.setBounds(primary.mRemoteToken.toWindowContainerToken(), primaryRect);
                 Rect otherRect = tempOtherTaskInsetBounds != null ? tempOtherTaskInsetBounds
                         : tempOtherTaskBounds;
                 if (otherRect == null) {
@@ -4285,7 +4304,7 @@
                         otherRect.top = primaryRect.bottom + 6;
                     }
                 }
-                wct.setBounds(secondary.mRemoteToken, otherRect);
+                wct.setBounds(secondary.mRemoteToken.toWindowContainerToken(), otherRect);
                 mWindowOrganizerController.applyTransaction(wct);
             }
         } finally {
@@ -6337,6 +6356,13 @@
         }
 
         @Override
+        public void notifyDreamStateChanged(boolean dreaming) {
+            synchronized (mGlobalLock) {
+                mDreaming = dreaming;
+            }
+        }
+
+        @Override
         public void setAllowAppSwitches(@NonNull String type, int uid, int userId) {
             if (!mAmInternal.isUserRunning(userId, ActivityManager.FLAG_OR_STOPPED)) {
                 return;
@@ -6438,6 +6464,13 @@
             }
         }
 
+        @Override
+        public boolean isDreaming() {
+            synchronized (mGlobalLock) {
+                return mDreaming;
+            }
+        }
+
         @HotPath(caller = HotPath.OOM_ADJUSTMENT)
         @Override
         public boolean isSleeping() {
diff --git a/services/core/java/com/android/server/wm/AppTaskImpl.java b/services/core/java/com/android/server/wm/AppTaskImpl.java
index 4cce212..8fa8119 100644
--- a/services/core/java/com/android/server/wm/AppTaskImpl.java
+++ b/services/core/java/com/android/server/wm/AppTaskImpl.java
@@ -111,7 +111,7 @@
                 final ActivityStarter starter = mService.getActivityStartController().obtainStarter(
                         null /* intent */, "moveToFront");
                 if (starter.shouldAbortBackgroundActivityStart(callingUid, callingPid,
-                        callingPackage, -1, -1, callerApp, null, false, false, null)) {
+                        callingPackage, -1, -1, callerApp, null, false, null)) {
                     if (!mService.isBackgroundActivityStartsEnabled()) {
                         return;
                     }
diff --git a/services/core/java/com/android/server/wm/BLASTSyncEngine.java b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
index 5b20023..efcb558 100644
--- a/services/core/java/com/android/server/wm/BLASTSyncEngine.java
+++ b/services/core/java/com/android/server/wm/BLASTSyncEngine.java
@@ -16,10 +16,6 @@
 
 package com.android.server.wm;
 
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Slog;
-import android.window.ITaskOrganizer;
 import android.view.SurfaceControl;
 
 import java.util.HashMap;
@@ -50,7 +46,7 @@
     private static final String TAG = "BLASTSyncEngine";
 
     interface TransactionReadyListener {
-        void transactionReady(int mSyncId, SurfaceControl.Transaction mergedTransaction);
+        void onTransactionReady(int mSyncId, SurfaceControl.Transaction mergedTransaction);
     };
 
     // Holds state associated with a single synchronous set of operations.
@@ -63,12 +59,12 @@
 
         private void tryFinish() {
             if (mRemainingTransactions == 0 && mReady) {
-                mListener.transactionReady(mSyncId, mMergedTransaction);
+                mListener.onTransactionReady(mSyncId, mMergedTransaction);
                 mPendingSyncs.remove(mSyncId);
             }
         }
 
-        public void transactionReady(int mSyncId, SurfaceControl.Transaction mergedTransaction) {
+        public void onTransactionReady(int mSyncId, SurfaceControl.Transaction mergedTransaction) {
             mRemainingTransactions--;
             mMergedTransaction.merge(mergedTransaction);
             tryFinish();
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index fd70971..90fdf19 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -21,9 +21,9 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 import static android.view.WindowManager.TRANSIT_KEYGUARD_UNOCCLUDE;
 import static android.view.WindowManagerPolicyConstants.APPLICATION_LAYER;
-import static android.window.WindowOrganizer.DisplayAreaOrganizer.FEATURE_ROOT;
-import static android.window.WindowOrganizer.DisplayAreaOrganizer.FEATURE_UNDEFINED;
-import static android.window.WindowOrganizer.DisplayAreaOrganizer.FEATURE_WINDOW_TOKENS;
+import static android.window.DisplayAreaOrganizer.FEATURE_ROOT;
+import static android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED;
+import static android.window.DisplayAreaOrganizer.FEATURE_WINDOW_TOKENS;
 
 import static com.android.internal.util.Preconditions.checkState;
 import static com.android.server.wm.DisplayAreaProto.NAME;
diff --git a/services/core/java/com/android/server/wm/DisplayAreaOrganizerController.java b/services/core/java/com/android/server/wm/DisplayAreaOrganizerController.java
index 464b127..f05783b 100644
--- a/services/core/java/com/android/server/wm/DisplayAreaOrganizerController.java
+++ b/services/core/java/com/android/server/wm/DisplayAreaOrganizerController.java
@@ -94,7 +94,7 @@
 
     void onDisplayAreaAppeared(IDisplayAreaOrganizer organizer, DisplayArea da) {
         try {
-            organizer.onDisplayAreaAppeared(da.mRemoteToken);
+            organizer.onDisplayAreaAppeared(da.mRemoteToken.toWindowContainerToken());
         } catch (RemoteException e) {
             // Oh well...
         }
@@ -102,7 +102,7 @@
 
     void onDisplayAreaVanished(IDisplayAreaOrganizer organizer, DisplayArea da) {
         try {
-            organizer.onDisplayAreaVanished(da.mRemoteToken);
+            organizer.onDisplayAreaVanished(da.mRemoteToken.toWindowContainerToken());
         } catch (RemoteException e) {
             // Oh well...
         }
diff --git a/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java b/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java
index 950df6f..682a142 100644
--- a/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java
+++ b/services/core/java/com/android/server/wm/DisplayAreaPolicyBuilder.java
@@ -87,8 +87,8 @@
          *
          * Must be unique among the features added to a {@link DisplayAreaPolicyBuilder}.
          *
-         * @see android.window.WindowOrganizer.DisplayAreaOrganizer#FEATURE_SYSTEM_FIRST
-         * @see android.window.WindowOrganizer.DisplayAreaOrganizer#FEATURE_VENDOR_FIRST
+         * @see android.window.DisplayAreaOrganizer#FEATURE_SYSTEM_FIRST
+         * @see android.window.DisplayAreaOrganizer#FEATURE_VENDOR_FIRST
          */
         public int getId() {
             return mId;
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 0108140..ce7e797 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -503,6 +503,9 @@
     /** Windows removed since {@link #mCurrentFocus} was set to null. Used for ANR blaming. */
     final ArrayList<WindowState> mWinRemovedSinceNullFocus = new ArrayList<>();
 
+    /** Windows whose client's insets states are not up-to-date. */
+    final ArrayList<WindowState> mWinInsetsChanged = new ArrayList<>();
+
     private ScreenRotationAnimation mScreenRotationAnimation;
 
     /**
@@ -708,7 +711,10 @@
         }
 
         // Sets mBehindIme for each window. Windows behind IME can get IME insets.
-        w.mBehindIme = mTmpWindowsBehindIme;
+        if (w.mBehindIme != mTmpWindowsBehindIme) {
+            w.mBehindIme = mTmpWindowsBehindIme;
+            mWinInsetsChanged.add(w);
+        }
         if (w == mInputMethodWindow) {
             mTmpWindowsBehindIme = true;
         }
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index b2f5988..007af24 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -367,7 +367,6 @@
         @Override
         protected void onAnimationFinish() {
             super.onAnimationFinish();
-            mControlCallbacks.mAnimationControl.finish(mAnimatingShown);
             DisplayThread.getHandler().post(mFinishCallback);
         }
 
@@ -399,7 +398,7 @@
 
             /** Called on SurfaceAnimationThread without global WM lock held. */
             @Override
-            public void scheduleApplyChangeInsets() {
+            public void scheduleApplyChangeInsets(InsetsAnimationControlRunner runner) {
                 InsetsState state = getState();
                 if (mAnimationControl.applyChangeInsets(state)) {
                     mAnimationControl.finish(mAnimatingShown);
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index 04454a5..ba14d48 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -227,10 +227,19 @@
         for (int i = mProviders.size() - 1; i >= 0; i--) {
             mProviders.valueAt(i).onPostLayout();
         }
+        final ArrayList<WindowState> winInsetsChanged = mDisplayContent.mWinInsetsChanged;
         if (!mLastState.equals(mState)) {
             mLastState.set(mState, true /* copySources */);
             notifyInsetsChanged();
+        } else {
+            // The global insets state has not changed but there might be windows whose conditions
+            // (e.g., z-order) have changed. They can affect the insets states that we dispatch to
+            // the clients.
+            for (int i = winInsetsChanged.size() - 1; i >= 0; i--) {
+                winInsetsChanged.get(i).notifyInsetsChanged();
+            }
         }
+        winInsetsChanged.clear();
     }
 
     void onInsetsModified(InsetsControlTarget windowState, InsetsState state) {
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index af783c5..2764b12 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -2346,7 +2346,7 @@
         stack.getBounds(info.bounds);
         info.displayId = taskDisplayArea != null ? taskDisplayArea.getDisplayId() : INVALID_DISPLAY;
         info.stackId = stack.mTaskId;
-        info.stackToken = stack.mRemoteToken;
+        info.stackToken = stack.mRemoteToken.toWindowContainerToken();
         info.userId = stack.mCurrentUser;
         info.visible = stack.shouldBeVisible(null);
         // A stack might be not attached to a display.
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 9f5126e..0151b82 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -478,7 +478,7 @@
 
     /**
      * The TaskOrganizer which is delegated presentation of this task. If set the Task will
-     * emit an IWindowContainer (allowing access to it's SurfaceControl leash) to the organizers
+     * emit an WindowContainerToken (allowing access to it's SurfaceControl leash) to the organizers
      * taskAppeared callback, and emit a taskRemoved callback when the Task is vanished.
      */
     ITaskOrganizer mTaskOrganizer;
@@ -3435,7 +3435,7 @@
         info.taskDescription = new ActivityManager.TaskDescription(getTaskDescription());
         info.supportsSplitScreenMultiWindow = supportsSplitScreenWindowingMode();
         info.configuration.setTo(getConfiguration());
-        info.token = mRemoteToken;
+        info.token = mRemoteToken.toWindowContainerToken();
 
         //TODO (AM refactor): Just use local once updateEffectiveIntent is run during all child
         //                    order changes.
diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java
index a5ae721..9356ec2 100644
--- a/services/core/java/com/android/server/wm/TaskDisplayArea.java
+++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java
@@ -31,7 +31,7 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_BEHIND;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
-import static android.window.WindowOrganizer.DisplayAreaOrganizer.FEATURE_TASK_CONTAINER;
+import static android.window.DisplayAreaOrganizer.FEATURE_TASK_CONTAINER;
 
 import static com.android.server.wm.ActivityStack.ActivityState.RESUMED;
 import static com.android.server.wm.ActivityStack.STACK_VISIBILITY_VISIBLE;
@@ -1183,7 +1183,8 @@
         for (int i = mTmpTasks.size() - 1; i >= 0; i--) {
             final Task root = mTmpTasks.get(i);
             for (int j = 0; j < root.getChildCount(); j++) {
-                wct.reparent(root.getChildAt(j).mRemoteToken, null, true /* toTop */);
+                wct.reparent(root.getChildAt(j).mRemoteToken.toWindowContainerToken(),
+                        null, true /* toTop */);
             }
         }
         mAtmService.mWindowOrganizerController.applyTransaction(wct);
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 2dec655..872f254 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -36,7 +36,7 @@
 import android.util.SparseArray;
 import android.window.ITaskOrganizer;
 import android.window.ITaskOrganizerController;
-import android.window.IWindowContainer;
+import android.window.WindowContainerToken;
 
 import com.android.internal.util.ArrayUtils;
 
@@ -290,7 +290,7 @@
     }
 
     @Override
-    public boolean deleteRootTask(IWindowContainer token) {
+    public boolean deleteRootTask(WindowContainerToken token) {
         enforceStackPermission("deleteRootTask()");
         final long origId = Binder.clearCallingIdentity();
         try {
@@ -367,7 +367,7 @@
     }
 
     @Override
-    public IWindowContainer getImeTarget(int displayId) {
+    public WindowContainerToken getImeTarget(int displayId) {
         enforceStackPermission("getImeTarget()");
         final long origId = Binder.clearCallingIdentity();
         try {
@@ -382,7 +382,7 @@
                 if (task == null) {
                     return null;
                 }
-                return task.getRootTask().mRemoteToken;
+                return task.getRootTask().mRemoteToken.toWindowContainerToken();
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
@@ -390,7 +390,7 @@
     }
 
     @Override
-    public void setLaunchRoot(int displayId, @Nullable IWindowContainer token) {
+    public void setLaunchRoot(int displayId, @Nullable WindowContainerToken token) {
         enforceStackPermission("setLaunchRoot()");
         final long origId = Binder.clearCallingIdentity();
         try {
@@ -422,7 +422,7 @@
     }
 
     @Override
-    public List<RunningTaskInfo> getChildTasks(IWindowContainer parent,
+    public List<RunningTaskInfo> getChildTasks(WindowContainerToken parent,
             @Nullable int[] activityTypes) {
         enforceStackPermission("getChildTasks()");
         final long ident = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/wm/WallpaperController.java b/services/core/java/com/android/server/wm/WallpaperController.java
index 29a2e18..e43f4b4 100644
--- a/services/core/java/com/android/server/wm/WallpaperController.java
+++ b/services/core/java/com/android/server/wm/WallpaperController.java
@@ -304,7 +304,11 @@
         }
     }
 
-    boolean updateWallpaperOffset(WindowState wallpaperWin, int dw, int dh, boolean sync) {
+    boolean updateWallpaperOffset(WindowState wallpaperWin, boolean sync) {
+        final DisplayInfo displayInfo = wallpaperWin.getDisplayInfo();
+        final int dw = displayInfo.logicalWidth;
+        final int dh = displayInfo.logicalHeight;
+
         int xOffset = 0;
         int yOffset = 0;
         boolean rawChanged = false;
@@ -444,10 +448,6 @@
     }
 
     private void updateWallpaperOffsetLocked(WindowState changingTarget, boolean sync) {
-        final DisplayInfo displayInfo = mDisplayContent.getDisplayInfo();
-        final int dw = displayInfo.logicalWidth;
-        final int dh = displayInfo.logicalHeight;
-
         WindowState target = mWallpaperTarget;
         if (target != null) {
             if (target.mWallpaperX >= 0) {
@@ -484,7 +484,7 @@
         }
 
         for (int curTokenNdx = mWallpaperTokens.size() - 1; curTokenNdx >= 0; curTokenNdx--) {
-            mWallpaperTokens.get(curTokenNdx).updateWallpaperOffset(dw, dh, sync);
+            mWallpaperTokens.get(curTokenNdx).updateWallpaperOffset(sync);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index e29580b..203ca25 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -73,11 +73,11 @@
         }
     }
 
-    void updateWallpaperOffset(int dw, int dh, boolean sync) {
+    void updateWallpaperOffset(boolean sync) {
         final WallpaperController wallpaperController = mDisplayContent.mWallpaperController;
         for (int wallpaperNdx = mChildren.size() - 1; wallpaperNdx >= 0; wallpaperNdx--) {
             final WindowState wallpaper = mChildren.get(wallpaperNdx);
-            if (wallpaperController.updateWallpaperOffset(wallpaper, dw, dh, sync)) {
+            if (wallpaperController.updateWallpaperOffset(wallpaper, sync)) {
                 // We only want to be synchronous with one wallpaper.
                 sync = false;
             }
@@ -85,10 +85,6 @@
     }
 
     void updateWallpaperVisibility(boolean visible) {
-        final DisplayInfo displayInfo = mDisplayContent.getDisplayInfo();
-        final int dw = displayInfo.logicalWidth;
-        final int dh = displayInfo.logicalHeight;
-
         if (isVisible() != visible) {
             // Need to do a layout to ensure the wallpaper now has the correct size.
             mDisplayContent.setLayoutNeeded();
@@ -98,7 +94,7 @@
         for (int wallpaperNdx = mChildren.size() - 1; wallpaperNdx >= 0; wallpaperNdx--) {
             final WindowState wallpaper = mChildren.get(wallpaperNdx);
             if (visible) {
-                wallpaperController.updateWallpaperOffset(wallpaper, dw, dh, false);
+                wallpaperController.updateWallpaperOffset(wallpaper, false /* sync */);
             }
 
             wallpaper.dispatchWallpaperVisibility(visible);
@@ -145,19 +141,11 @@
             }
         }
 
-        DisplayInfo displayInfo = getFixedRotationTransformDisplayInfo();
-        if (displayInfo == null) {
-            displayInfo = mDisplayContent.getDisplayInfo();
-        }
-
-        final int dw = displayInfo.logicalWidth;
-        final int dh = displayInfo.logicalHeight;
-
         for (int wallpaperNdx = mChildren.size() - 1; wallpaperNdx >= 0; wallpaperNdx--) {
             final WindowState wallpaper = mChildren.get(wallpaperNdx);
 
             if (visible) {
-                wallpaperController.updateWallpaperOffset(wallpaper, dw, dh, false);
+                wallpaperController.updateWallpaperOffset(wallpaper, false /* sync */);
             }
 
             // First, make sure the client has the current visibility state.
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 98585a9..58119c2 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -76,7 +76,8 @@
 import android.view.SurfaceSession;
 import android.view.WindowManager;
 import android.view.animation.Animation;
-import android.window.IWindowContainer;
+import android.window.IWindowContainerToken;
+import android.window.WindowContainerToken;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ToBooleanFunction;
@@ -2449,8 +2450,10 @@
         return RemoteToken.fromBinder(binder).getContainer();
     }
 
-    static class RemoteToken extends IWindowContainer.Stub {
+    static class RemoteToken extends IWindowContainerToken.Stub {
+
         final WeakReference<WindowContainer> mWeakRef;
+        private WindowContainerToken mWindowContainerToken;
 
         RemoteToken(WindowContainer container) {
             mWeakRef = new WeakReference<>(container);
@@ -2476,6 +2479,13 @@
             return sc;
         }
 
+        WindowContainerToken toWindowContainerToken() {
+            if (mWindowContainerToken == null) {
+                mWindowContainerToken = new WindowContainerToken(this);
+            }
+            return mWindowContainerToken;
+        }
+
         @Override
         public String toString() {
             StringBuilder sb = new StringBuilder(128);
@@ -2489,11 +2499,11 @@
     }
 
     @Override
-    public void transactionReady(int mSyncId, SurfaceControl.Transaction mergedTransaction) {
+    public void onTransactionReady(int mSyncId, SurfaceControl.Transaction mergedTransaction) {
         mergedTransaction.merge(mBLASTSyncTransaction);
         mUsingBLASTSyncTransaction = false;
 
-        mWaitingListener.transactionReady(mWaitingSyncId, mergedTransaction);
+        mWaitingListener.onTransactionReady(mWaitingSyncId, mergedTransaction);
 
         mWaitingListener = null;
         mWaitingSyncId = -1;
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 8e45752..ae668f1 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -2332,9 +2332,7 @@
             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
 
             if (toBeDisplayed && win.mIsWallpaper) {
-                DisplayInfo displayInfo = displayContent.getDisplayInfo();
-                displayContent.mWallpaperController.updateWallpaperOffset(
-                        win, displayInfo.logicalWidth, displayInfo.logicalHeight, false);
+                displayContent.mWallpaperController.updateWallpaperOffset(win, false /* sync */);
             }
             if (win.mActivityRecord != null) {
                 win.mActivityRecord.updateReportedVisibilityLocked();
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 14c9429..a332b69 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -340,12 +340,12 @@
     }
 
     @Override
-    public void transactionReady(int mSyncId, SurfaceControl.Transaction mergedTransaction) {
+    public void onTransactionReady(int mSyncId, SurfaceControl.Transaction mergedTransaction) {
         final IWindowContainerTransactionCallback callback =
                 mTransactionCallbacksByPendingSyncId.get(mSyncId);
 
         try {
-            callback.transactionReady(mSyncId, mergedTransaction);
+            callback.onTransactionReady(mSyncId, mergedTransaction);
         } catch (RemoteException e) {
         }
 
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index ad60d31..b87d181 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1101,7 +1101,6 @@
                 }
             }
 
-            final ActivityStack stack = getRootTask();
             layoutDisplayFrame = new Rect(windowFrames.mDisplayFrame);
             windowFrames.mDisplayFrame.set(windowFrames.mContainingFrame);
             layoutXDiff = mInsetFrame.left - windowFrames.mContainingFrame.left;
@@ -1205,8 +1204,7 @@
 
         if (mIsWallpaper && (fw != windowFrames.mFrame.width()
                 || fh != windowFrames.mFrame.height())) {
-            dc.mWallpaperController.updateWallpaperOffset(this,
-                    displayInfo.logicalWidth, displayInfo.logicalHeight, false /* sync */);
+            dc.mWallpaperController.updateWallpaperOffset(this, false /* sync */);
         }
 
         // Calculate relative frame
@@ -5723,7 +5721,7 @@
         if (mLocalSyncId >= 0) {
             mBLASTSyncEngine.setReady(mLocalSyncId);
         } else {
-            mWaitingListener.transactionReady(mWaitingSyncId, mBLASTSyncTransaction);
+            mWaitingListener.onTransactionReady(mWaitingSyncId, mBLASTSyncTransaction);
         }
 
         mUsingBLASTSyncTransaction = false;
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 563710b..b25383b 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -1381,7 +1381,8 @@
             return true;
         }
 
-        if (isEntrance && mWin.mAttrs.type == TYPE_INPUT_METHOD) {
+        final boolean isImeWindow = mWin.mAttrs.type == TYPE_INPUT_METHOD;
+        if (isEntrance && isImeWindow) {
             mWin.getDisplayContent().adjustForImeIfNeeded();
             mWin.setDisplayLayoutNeeded();
             mService.mWindowPlacerLocked.requestTraversal();
@@ -1435,11 +1436,11 @@
                 Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
                 mAnimationIsEntrance = isEntrance;
             }
-        } else {
+        } else if (!isImeWindow) {
             mWin.cancelAnimation();
         }
 
-        if (!isEntrance && mWin.mAttrs.type == TYPE_INPUT_METHOD) {
+        if (!isEntrance && isImeWindow) {
             mWin.getDisplayContent().adjustForImeIfNeeded();
         }
 
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 74982c6..4c3f73d 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -40,6 +40,7 @@
         "com_android_server_security_VerityUtils.cpp",
         "com_android_server_SerialService.cpp",
         "com_android_server_soundtrigger_middleware_AudioSessionProviderImpl.cpp",
+        "com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker.cpp",
         "com_android_server_stats_pull_StatsPullAtomService.cpp",
         "com_android_server_storage_AppFuseBridge.cpp",
         "com_android_server_SystemServer.cpp",
diff --git a/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp b/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp
index e9a5e58..853eba7 100644
--- a/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp
+++ b/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp
@@ -392,6 +392,7 @@
         mArgs = params.arguments();
         mIfs = ifs;
         mStatusListener = statusListener;
+        mIfs->setParams({.readLogsEnabled = true});
         return true;
     }
     bool onStart() final { return true; }
@@ -438,7 +439,7 @@
             }
 
             const auto fileId = IncFs_FileIdFromMetadata(file.metadata);
-            const auto incfsFd(mIfs->openWrite(fileId));
+            const base::unique_fd incfsFd(mIfs->openForSpecialOps(fileId).release());
             if (incfsFd < 0) {
                 ALOGE("Failed to open an IncFS file for metadata: %.*s, final file name is: %s. "
                       "Error %d",
@@ -716,7 +717,7 @@
 
                 auto& writeFd = writeFds[fileIdx];
                 if (writeFd < 0) {
-                    writeFd.reset(this->mIfs->openWrite(fileId));
+                    writeFd.reset(this->mIfs->openForSpecialOps(fileId).release());
                     if (writeFd < 0) {
                         ALOGE("Failed to open file %d for writing (%d). Aborting.", header.fileIdx,
                               -writeFd);
diff --git a/services/core/jni/com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker.cpp b/services/core/jni/com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker.cpp
new file mode 100644
index 0000000..ae6cb18
--- /dev/null
+++ b/services/core/jni/com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker.cpp
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2020 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 <sstream>
+
+#define LOG_TAG "ExternalCaptureStateTracker"
+
+#include "core_jni_helpers.h"
+#include <log/log.h>
+#include <media/AudioSystem.h>
+
+namespace android {
+namespace {
+
+#define PACKAGE "com/android/server/soundtrigger_middleware"
+#define CLASSNAME PACKAGE "/ExternalCaptureStateTracker"
+
+jclass gExternalCaptureStateTrackerClassId;
+jmethodID gSetCaptureStateMethodId;
+jmethodID gBinderDiedMethodId;
+
+void PopulateIds(JNIEnv* env) {
+    gExternalCaptureStateTrackerClassId =
+        (jclass) env->NewGlobalRef(FindClassOrDie(env, CLASSNAME));
+    gSetCaptureStateMethodId = GetMethodIDOrDie(env,
+                                                gExternalCaptureStateTrackerClassId,
+                                                "setCaptureState",
+                                                "(Z)V");
+    gBinderDiedMethodId = GetMethodIDOrDie(env,
+                                           gExternalCaptureStateTrackerClassId,
+                                           "binderDied",
+                                           "()V");
+}
+
+class Listener : public AudioSystem::CaptureStateListener {
+public:
+    Listener(JNIEnv* env, jobject obj) : mObj(env->NewGlobalRef(obj)) {}
+
+    ~Listener() {
+        JNIEnv* env = AndroidRuntime::getJNIEnv();
+        env->DeleteGlobalRef(mObj);
+    }
+
+    void onStateChanged(bool active) override {
+        JNIEnv* env = AndroidRuntime::getJNIEnv();
+        env->CallVoidMethod(mObj, gSetCaptureStateMethodId, active);
+    }
+
+    void onServiceDied() override {
+        JNIEnv* env = AndroidRuntime::getJNIEnv();
+        env->CallVoidMethod(mObj, gBinderDiedMethodId);
+    }
+
+private:
+    jobject mObj;
+};
+
+void connect(JNIEnv* env, jobject obj) {
+    sp<AudioSystem::CaptureStateListener> listener(new Listener(env, obj));
+    status_t status =
+        AudioSystem::registerSoundTriggerCaptureStateListener(listener);
+    LOG_ALWAYS_FATAL_IF(status != NO_ERROR);
+}
+
+const JNINativeMethod gMethods[] = {
+    {"connect", "()V", reinterpret_cast<void*>(connect)},
+};
+
+}  // namespace
+
+int register_com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker(
+    JNIEnv* env) {
+    PopulateIds(env);
+    return RegisterMethodsOrDie(env,
+                                CLASSNAME,
+                                gMethods,
+                                NELEM(gMethods));
+}
+
+} // namespace android
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index eb486fe..b988bd4 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -58,6 +58,8 @@
 int register_android_server_am_LowMemDetector(JNIEnv* env);
 int register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(
         JNIEnv* env);
+int register_com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker(
+    JNIEnv* env);
 int register_android_server_com_android_server_pm_PackageManagerShellCommandDataLoader(JNIEnv* env);
 int register_android_server_stats_pull_StatsPullAtomService(JNIEnv* env);
 int register_android_server_AdbDebuggingManager(JNIEnv* env);
@@ -112,6 +114,8 @@
     register_android_server_am_LowMemDetector(env);
     register_com_android_server_soundtrigger_middleware_AudioSessionProviderImpl(
             env);
+    register_com_android_server_soundtrigger_middleware_ExternalCaptureStateTracker(
+        env);
     register_android_server_com_android_server_pm_PackageManagerShellCommandDataLoader(env);
     register_android_server_stats_pull_StatsPullAtomService(env);
     register_android_server_AdbDebuggingManager(env);
diff --git a/services/incremental/BinderIncrementalService.cpp b/services/incremental/BinderIncrementalService.cpp
index 2dbbc5a..97de1800 100644
--- a/services/incremental/BinderIncrementalService.cpp
+++ b/services/incremental/BinderIncrementalService.cpp
@@ -155,6 +155,11 @@
     return ok();
 }
 
+binder::Status BinderIncrementalService::setStorageParams(int32_t storage, bool enableReadLogs, int32_t* _aidl_return) {
+    *_aidl_return = mImpl.setStorageParams(storage, enableReadLogs);
+    return ok();
+}
+
 binder::Status BinderIncrementalService::makeDirectory(int32_t storageId, const std::string& path,
                                                        int32_t* _aidl_return) {
     *_aidl_return = mImpl.makeDir(storageId, path);
diff --git a/services/incremental/BinderIncrementalService.h b/services/incremental/BinderIncrementalService.h
index 28613e1..d0357d9 100644
--- a/services/incremental/BinderIncrementalService.h
+++ b/services/incremental/BinderIncrementalService.h
@@ -71,6 +71,7 @@
     binder::Status configureNativeBinaries(int32_t storageId, const std::string& apkFullPath,
                                            const std::string& libDirRelativePath,
                                            const std::string& abi, bool* _aidl_return) final;
+    binder::Status setStorageParams(int32_t storage, bool enableReadLogs, int32_t* _aidl_return) final;
 
 private:
     android::incremental::IncrementalService mImpl;
diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp
index 25da8fe..5e3c337d 100644
--- a/services/incremental/IncrementalService.cpp
+++ b/services/incremental/IncrementalService.cpp
@@ -563,6 +563,36 @@
     return it->second->second.storage;
 }
 
+int IncrementalService::setStorageParams(StorageId storageId, bool enableReadLogs) {
+    const auto ifs = getIfs(storageId);
+    if (!ifs) {
+        return -EINVAL;
+    }
+
+    using unique_fd = ::android::base::unique_fd;
+    ::android::os::incremental::IncrementalFileSystemControlParcel control;
+    control.cmd.reset(unique_fd(dup(ifs->control.cmd())));
+    control.pendingReads.reset(unique_fd(dup(ifs->control.pendingReads())));
+    auto logsFd = ifs->control.logs();
+    if (logsFd >= 0) {
+        control.log.reset(unique_fd(dup(logsFd)));
+    }
+
+    std::lock_guard l(mMountOperationLock);
+    const auto status = mVold->setIncFsMountOptions(control, enableReadLogs);
+    if (!status.isOk()) {
+        LOG(ERROR) << "Calling Vold::setIncFsMountOptions() failed: " << status.toString8();
+        return status.exceptionCode() == binder::Status::EX_SERVICE_SPECIFIC
+                ? status.serviceSpecificErrorCode() > 0 ? -status.serviceSpecificErrorCode()
+                                                        : status.serviceSpecificErrorCode() == 0
+                                ? -EFAULT
+                                : status.serviceSpecificErrorCode()
+                : -EIO;
+    }
+
+    return 0;
+}
+
 void IncrementalService::deleteStorage(StorageId storageId) {
     const auto ifs = getIfs(storageId);
     if (!ifs) {
@@ -737,10 +767,12 @@
     if (auto ifs = getIfs(storage)) {
         std::string normPath = normalizePathToStorage(ifs, storage, path);
         if (normPath.empty()) {
+            LOG(ERROR) << "Internal error: storageId " << storage << " failed to normalize: " << path;
             return -EINVAL;
         }
         auto err = mIncFs->makeFile(ifs->control, normPath, mode, id, params);
         if (err) {
+            LOG(ERROR) << "Internal error: storageId " << storage << " failed to makeFile: " << err;
             return err;
         }
         std::vector<uint8_t> metadataBytes;
@@ -1182,8 +1214,8 @@
             success = false;
             break;
         }
-        android::base::unique_fd writeFd(mIncFs->openWrite(ifs->control, libFileId));
-        if (writeFd < 0) {
+        const auto writeFd = mIncFs->openForSpecialOps(ifs->control, libFileId);
+        if (!writeFd.ok()) {
             LOG(ERROR) << "Failed to open write fd for: " << targetLibPath << " errno: " << writeFd;
             success = false;
             break;
diff --git a/services/incremental/IncrementalService.h b/services/incremental/IncrementalService.h
index 406b32e..90d58a7 100644
--- a/services/incremental/IncrementalService.h
+++ b/services/incremental/IncrementalService.h
@@ -111,6 +111,8 @@
     int unbind(StorageId storage, std::string_view target);
     void deleteStorage(StorageId storage);
 
+    int setStorageParams(StorageId storage, bool enableReadLogs);
+
     int makeFile(StorageId storage, std::string_view path, int mode, FileId id,
                  incfs::NewFileParams params);
     int makeDir(StorageId storage, std::string_view path, int mode = 0755);
diff --git a/services/incremental/ServiceWrappers.h b/services/incremental/ServiceWrappers.h
index c70a47d..c330030 100644
--- a/services/incremental/ServiceWrappers.h
+++ b/services/incremental/ServiceWrappers.h
@@ -49,6 +49,7 @@
     virtual binder::Status unmountIncFs(const std::string& dir) const = 0;
     virtual binder::Status bindMount(const std::string& sourceDir,
                                      const std::string& targetDir) const = 0;
+    virtual binder::Status setIncFsMountOptions(const ::android::os::incremental::IncrementalFileSystemControlParcel& control, bool enableReadLogs) const = 0;
 };
 
 class DataLoaderManagerWrapper {
@@ -76,7 +77,7 @@
     virtual ErrorCode link(const Control& control, std::string_view from,
                            std::string_view to) const = 0;
     virtual ErrorCode unlink(const Control& control, std::string_view path) const = 0;
-    virtual base::unique_fd openWrite(const Control& control, FileId id) const = 0;
+    virtual base::unique_fd openForSpecialOps(const Control& control, FileId id) const = 0;
     virtual ErrorCode writeBlocks(Span<const DataBlock> blocks) const = 0;
 };
 
@@ -106,6 +107,9 @@
                              const std::string& targetDir) const override {
         return mInterface->bindMount(sourceDir, targetDir);
     }
+    binder::Status setIncFsMountOptions(const ::android::os::incremental::IncrementalFileSystemControlParcel& control, bool enableReadLogs) const override {
+        return mInterface->setIncFsMountOptions(control, enableReadLogs);
+    }
 
 private:
     sp<os::IVold> mInterface;
@@ -177,8 +181,8 @@
     ErrorCode unlink(const Control& control, std::string_view path) const override {
         return incfs::unlink(control, path);
     }
-    base::unique_fd openWrite(const Control& control, FileId id) const override {
-        return base::unique_fd{incfs::openWrite(control, id)};
+    base::unique_fd openForSpecialOps(const Control& control, FileId id) const override {
+        return base::unique_fd{incfs::openForSpecialOps(control, id).release()};
     }
     ErrorCode writeBlocks(Span<const DataBlock> blocks) const override {
         return incfs::writeBlocks(blocks);
diff --git a/services/incremental/test/IncrementalServiceTest.cpp b/services/incremental/test/IncrementalServiceTest.cpp
index c4b4d17..cde38fb 100644
--- a/services/incremental/test/IncrementalServiceTest.cpp
+++ b/services/incremental/test/IncrementalServiceTest.cpp
@@ -52,6 +52,8 @@
     MOCK_CONST_METHOD1(unmountIncFs, binder::Status(const std::string& dir));
     MOCK_CONST_METHOD2(bindMount,
                        binder::Status(const std::string& sourceDir, const std::string& argetDir));
+    MOCK_CONST_METHOD2(setIncFsMountOptions,
+                       binder::Status(const ::android::os::incremental::IncrementalFileSystemControlParcel&, bool));
 
     void mountIncFsFails() {
         ON_CALL(*this, mountIncFs(_, _, _, _))
@@ -74,6 +76,14 @@
     void bindMountSuccess() {
         ON_CALL(*this, bindMount(_, _)).WillByDefault(Return(binder::Status::ok()));
     }
+    void setIncFsMountOptionsFails() const {
+        ON_CALL(*this, setIncFsMountOptions(_, _))
+                .WillByDefault(
+                        Return(binder::Status::fromExceptionCode(1, String8("failed to set options"))));
+    }
+    void setIncFsMountOptionsSuccess() {
+        ON_CALL(*this, setIncFsMountOptions(_, _)).WillByDefault(Return(binder::Status::ok()));
+    }
     binder::Status getInvalidControlParcel(const std::string& imagePath,
                                            const std::string& targetDir, int32_t flags,
                                            IncrementalFileSystemControlParcel* _aidl_return) {
@@ -175,7 +185,7 @@
     MOCK_CONST_METHOD3(link,
                        ErrorCode(const Control& control, std::string_view from, std::string_view to));
     MOCK_CONST_METHOD2(unlink, ErrorCode(const Control& control, std::string_view path));
-    MOCK_CONST_METHOD2(openWrite, base::unique_fd(const Control& control, FileId id));
+    MOCK_CONST_METHOD2(openForSpecialOps, base::unique_fd(const Control& control, FileId id));
     MOCK_CONST_METHOD1(writeBlocks, ErrorCode(Span<const DataBlock> blocks));
 
     void makeFileFails() { ON_CALL(*this, makeFile(_, _, _, _, _)).WillByDefault(Return(-1)); }
@@ -390,6 +400,42 @@
     ASSERT_TRUE(mIncrementalService->startLoading(storageId));
 }
 
+TEST_F(IncrementalServiceTest, testSetIncFsMountOptionsSuccess) {
+    mVold->mountIncFsSuccess();
+    mIncFs->makeFileSuccess();
+    mVold->bindMountSuccess();
+    mVold->setIncFsMountOptionsSuccess();
+    mDataLoaderManager->initializeDataLoaderSuccess();
+    mDataLoaderManager->getDataLoaderSuccess();
+    EXPECT_CALL(*mDataLoaderManager, destroyDataLoader(_));
+    EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
+    EXPECT_CALL(*mVold, setIncFsMountOptions(_, _));
+    TemporaryDir tempDir;
+    int storageId =
+            mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), {},
+                                               IncrementalService::CreateOptions::CreateNew);
+    ASSERT_GE(storageId, 0);
+    ASSERT_GE(mIncrementalService->setStorageParams(storageId, true), 0);
+}
+
+TEST_F(IncrementalServiceTest, testSetIncFsMountOptionsFails) {
+    mVold->mountIncFsSuccess();
+    mIncFs->makeFileSuccess();
+    mVold->bindMountSuccess();
+    mVold->setIncFsMountOptionsFails();
+    mDataLoaderManager->initializeDataLoaderSuccess();
+    mDataLoaderManager->getDataLoaderSuccess();
+    EXPECT_CALL(*mDataLoaderManager, destroyDataLoader(_));
+    EXPECT_CALL(*mVold, unmountIncFs(_)).Times(2);
+    EXPECT_CALL(*mVold, setIncFsMountOptions(_, _));
+    TemporaryDir tempDir;
+    int storageId =
+            mIncrementalService->createStorage(tempDir.path, std::move(mDataLoaderParcel), {},
+                                               IncrementalService::CreateOptions::CreateNew);
+    ASSERT_GE(storageId, 0);
+    ASSERT_LT(mIncrementalService->setStorageParams(storageId, true), 0);
+}
+
 TEST_F(IncrementalServiceTest, testMakeDirectory) {
     mVold->mountIncFsSuccess();
     mIncFs->makeFileSuccess();
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 1939313..2a914ec 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -30,6 +30,7 @@
 import android.app.ActivityThread;
 import android.app.AppCompatCallbacks;
 import android.app.INotificationManager;
+import android.app.SystemServiceRegistry;
 import android.app.usage.UsageStatsManagerInternal;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -513,6 +514,8 @@
             Looper.getMainLooper().setSlowLogThresholdMs(
                     SLOW_DISPATCH_THRESHOLD_MS, SLOW_DELIVERY_THRESHOLD_MS);
 
+            SystemServiceRegistry.sEnableServiceNotFoundWtf = true;
+
             // Initialize native services.
             System.loadLibrary("android_servers");
 
diff --git a/services/net/java/android/net/ip/IpClientManager.java b/services/net/java/android/net/ip/IpClientManager.java
index 09e333e..db464e7 100644
--- a/services/net/java/android/net/ip/IpClientManager.java
+++ b/services/net/java/android/net/ip/IpClientManager.java
@@ -21,6 +21,7 @@
 import android.net.NattKeepalivePacketData;
 import android.net.ProxyInfo;
 import android.net.TcpKeepalivePacketData;
+import android.net.shared.Layer2Information;
 import android.net.shared.ProvisioningConfiguration;
 import android.net.util.KeepalivePacketDataUtil;
 import android.os.Binder;
@@ -292,4 +293,20 @@
             Binder.restoreCallingIdentity(token);
         }
     }
+
+    /**
+     * Update the bssid, L2 key and group hint layer2 information.
+     */
+    public boolean updateLayer2Information(Layer2Information info) {
+        final long token = Binder.clearCallingIdentity();
+        try {
+            mIpClient.updateLayer2Information(info.toStableParcelable());
+            return true;
+        } catch (RemoteException e) {
+            log("Error updating layer2 information", e);
+            return false;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
 }
diff --git a/services/tests/PackageManagerComponentOverrideTests/Android.bp b/services/tests/PackageManagerComponentOverrideTests/Android.bp
new file mode 100644
index 0000000..a2668a1
--- /dev/null
+++ b/services/tests/PackageManagerComponentOverrideTests/Android.bp
@@ -0,0 +1,40 @@
+//
+// Copyright (C) 2019 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.
+//
+
+// NOTE: This test is separate from service tests since it relies on same vs different calling UID,
+// and this is more representative of a real caller. It also uses Mockito extended, and this
+// prevents converting the entire services test module.
+android_test {
+    name: "PackageManagerComponentOverrideTests",
+    srcs: [
+        "src/**/*.kt"
+    ],
+    static_libs: [
+        "androidx.test.runner",
+        "mockito-target-extended-minus-junit4",
+        "services.core",
+        "servicestests-utils-mockito-extended",
+        "testng", // TODO: remove once Android migrates to JUnit 4.12, which provides assertThrows
+        "truth-prebuilt",
+    ],
+
+    jni_libs: [
+        "libdexmakerjvmtiagent",
+        "libstaticjvmtiagent",
+    ],
+    test_suites: ["device-tests"],
+    platform_apis: true,
+}
diff --git a/services/tests/PackageManagerComponentOverrideTests/AndroidManifest.xml b/services/tests/PackageManagerComponentOverrideTests/AndroidManifest.xml
new file mode 100644
index 0000000..c25e112
--- /dev/null
+++ b/services/tests/PackageManagerComponentOverrideTests/AndroidManifest.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.server.pm.test.override">
+
+    <uses-sdk
+        android:minSdkVersion="1"
+        android:targetSdkVersion="28" />
+
+    <application android:debuggable="true">
+        <uses-library android:name="android.test.mock" android:required="true" />
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:label="PackageManagerComponentOverrideTests"
+        android:targetPackage="com.android.server.pm.test.override" />
+
+</manifest>
diff --git a/services/tests/PackageManagerComponentOverrideTests/AndroidTest.xml b/services/tests/PackageManagerComponentOverrideTests/AndroidTest.xml
new file mode 100644
index 0000000..b83b1a8
--- /dev/null
+++ b/services/tests/PackageManagerComponentOverrideTests/AndroidTest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2020 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.
+  -->
+
+<configuration description="Test module config for PackageManagerComponentOverrideTests">
+    <option name="test-tag" value="PackageManagerComponentOverrideTests" />
+
+    <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="PackageManagerComponentOverrideTests.apk" />
+    </target_preparer>
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+        <option name="package" value="com.android.server.pm.test.override" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+    </test>
+</configuration>
diff --git a/services/tests/PackageManagerComponentOverrideTests/res/drawable/black16x16.png b/services/tests/PackageManagerComponentOverrideTests/res/drawable/black16x16.png
new file mode 100644
index 0000000..86b12fc
--- /dev/null
+++ b/services/tests/PackageManagerComponentOverrideTests/res/drawable/black16x16.png
Binary files differ
diff --git a/services/tests/PackageManagerComponentOverrideTests/res/drawable/white16x16.png b/services/tests/PackageManagerComponentOverrideTests/res/drawable/white16x16.png
new file mode 100644
index 0000000..49dbb4f
--- /dev/null
+++ b/services/tests/PackageManagerComponentOverrideTests/res/drawable/white16x16.png
Binary files differ
diff --git a/services/tests/PackageManagerComponentOverrideTests/res/values/values.xml b/services/tests/PackageManagerComponentOverrideTests/res/values/values.xml
new file mode 100644
index 0000000..73d1128
--- /dev/null
+++ b/services/tests/PackageManagerComponentOverrideTests/res/values/values.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<resources>
+    <public name="black16x16" type="drawable" id="0x7f080001"/>
+    <public name="white16x16" type="drawable" id="0x7f080002"/>
+</resources>
diff --git a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
new file mode 100644
index 0000000..ecdb30f
--- /dev/null
+++ b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
@@ -0,0 +1,358 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.test.override
+
+import android.content.ComponentName
+import android.content.Context
+import android.content.pm.parsing.component.ParsedActivity
+import android.os.Binder
+import android.os.UserHandle
+import android.util.ArrayMap
+import com.android.server.pm.AppsFilter
+import com.android.server.pm.ComponentResolver
+import com.android.server.pm.PackageManagerService
+import com.android.server.pm.PackageSetting
+import com.android.server.pm.Settings
+import com.android.server.pm.UserManagerService
+import com.android.server.pm.parsing.pkg.AndroidPackage
+import com.android.server.pm.parsing.pkg.PackageImpl
+import com.android.server.pm.parsing.pkg.ParsedPackage
+import com.android.server.pm.permission.PermissionManagerServiceInternal
+import com.android.server.pm.test.override.PackageManagerComponentLabelIconOverrideTest.Companion.Params.AppType
+import com.android.server.pm.test.override.R
+import com.android.server.testutils.TestHandler
+import com.android.server.testutils.mock
+import com.android.server.testutils.mockThrowOnUnmocked
+import com.android.server.testutils.spy
+import com.android.server.testutils.whenever
+import com.android.server.wm.ActivityTaskManagerInternal
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import org.mockito.Mockito.any
+import org.mockito.Mockito.anyBoolean
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.anyString
+import org.mockito.Mockito.clearInvocations
+import org.mockito.Mockito.intThat
+import org.mockito.Mockito.never
+import org.mockito.Mockito.same
+import org.mockito.Mockito.verify
+import org.testng.Assert.assertThrows
+import java.io.File
+
+@RunWith(Parameterized::class)
+class PackageManagerComponentLabelIconOverrideTest {
+
+    companion object {
+        private const val VALID_PKG = "com.android.server.pm.test.override"
+        private const val SHARED_PKG = "com.android.server.pm.test.override.shared"
+        private const val INVALID_PKG = "com.android.server.pm.test.override.invalid"
+
+        private const val SEND_PENDING_BROADCAST = 1 // PackageManagerService.SEND_PENDING_BROADCAST
+
+        private const val DEFAULT_LABEL = "DefaultLabel"
+        private const val TEST_LABEL = "TestLabel"
+
+        private const val DEFAULT_ICON = R.drawable.black16x16
+        private const val TEST_ICON = R.drawable.white16x16
+
+        private const val COMPONENT_CLASS_NAME = ".TestComponent"
+
+        sealed class Result {
+            // Component label/icon changed, message sent to send broadcast
+            object Changed : Result()
+
+            // Component label/icon changed, message was pending, not re-sent
+            object ChangedWithoutNotify : Result()
+
+            // Component label/icon did not changed, was already equivalent
+            object NotChanged : Result()
+
+            // Updating label/icon encountered a specific exception
+            data class Exception(val type: Class<out java.lang.Exception>) : Result()
+        }
+
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun parameters() = arrayOf(
+                // Start with an array of the simplest known inputs and expected outputs
+                Params(VALID_PKG, AppType.SYSTEM_APP, Result.Changed),
+                Params(SHARED_PKG, AppType.SYSTEM_APP, Result.Changed),
+                Params(INVALID_PKG, AppType.SYSTEM_APP, SecurityException::class.java)
+        )
+                .flatMap { param ->
+                    mutableListOf(param).apply {
+                        if (param.result is Result.Changed) {
+                            // For each param that would've succeeded, also verify that if a change
+                            // happened, but a message was pending, another is not re-queued/reset
+                            this += param.copy(result = Result.ChangedWithoutNotify)
+                            // Also verify that when the component is already configured, no change
+                            // is propagated
+                            this += param.copy(result = Result.NotChanged)
+                        }
+                        // For all params, verify that an invalid component will cause an
+                        // IllegalArgumentException, instead of result initially specified
+                        this += param.copy(componentName = null,
+                                result = Result.Exception(IllegalArgumentException::class.java))
+                        // Also verify an updated system app variant, which should have the same
+                        // result as a vanilla system app
+                        this += param.copy(appType = AppType.UPDATED_SYSTEM_APP)
+                        // Also verify a non-system app will cause a failure, since normal apps
+                        // are not allowed to edit their label/icon
+                        this += param.copy(appType = AppType.NORMAL_APP,
+                                result = Result.Exception(SecurityException::class.java))
+                    }
+                }
+
+        data class Params(
+            val pkgName: String,
+            private val appType: AppType,
+            val result: Result,
+            val componentName: ComponentName? = ComponentName(pkgName, COMPONENT_CLASS_NAME)
+        ) {
+            constructor(pkgName: String, appType: AppType, exception: Class<out Exception>)
+                    : this(pkgName, appType, Result.Exception(exception))
+
+            val expectedLabel = when (result) {
+                Result.Changed, Result.ChangedWithoutNotify, Result.NotChanged -> TEST_LABEL
+                is Result.Exception -> DEFAULT_LABEL
+            }
+
+            val expectedIcon = when (result) {
+                Result.Changed, Result.ChangedWithoutNotify, Result.NotChanged -> TEST_ICON
+                is Result.Exception -> DEFAULT_ICON
+            }
+
+            val isUpdatedSystemApp = appType == AppType.UPDATED_SYSTEM_APP
+            val isSystem = appType == AppType.SYSTEM_APP || isUpdatedSystemApp
+
+            override fun toString(): String {
+                val resultString = when (result) {
+                    Result.Changed -> "Changed"
+                    Result.ChangedWithoutNotify -> "ChangedWithoutNotify"
+                    Result.NotChanged -> "NotChanged"
+                    is Result.Exception -> result.type.simpleName
+                }
+
+                // Nicer formatting for the test method suffix
+                return "pkg=$pkgName, type=$appType, component=$componentName, result=$resultString"
+            }
+
+            enum class AppType { SYSTEM_APP, UPDATED_SYSTEM_APP, NORMAL_APP }
+        }
+    }
+
+    @Parameterized.Parameter(0)
+    lateinit var params: Params
+
+    private lateinit var testHandler: TestHandler
+    private lateinit var mockPendingBroadcasts: PackageManagerService.PendingPackageBroadcasts
+    private lateinit var mockPkg: AndroidPackage
+    private lateinit var mockPkgSetting: PackageSetting
+    private lateinit var service: PackageManagerService
+
+    private val userId = UserHandle.getCallingUserId()
+    private val userIdDifferent = userId + 1
+
+    @Before
+    fun setUpMocks() {
+        makeTestData()
+
+        testHandler = TestHandler(null)
+        if (params.result is Result.ChangedWithoutNotify) {
+            // Case where the handler already has a message and so another should not be sent.
+            // This case will verify that only 1 message exists, which is the one added here.
+            testHandler.sendEmptyMessage(SEND_PENDING_BROADCAST)
+        }
+
+        mockPendingBroadcasts = PackageManagerService.PendingPackageBroadcasts()
+
+        service = mockService()
+    }
+
+    @Test
+    fun updateComponentLabelIcon() {
+        fun runUpdate() {
+            service.updateComponentLabelIcon(params.componentName, TEST_LABEL, TEST_ICON, userId)
+        }
+
+        when (val result = params.result) {
+            Result.Changed, Result.ChangedWithoutNotify, Result.NotChanged -> {
+                runUpdate()
+                verify(mockPkgSetting).overrideNonLocalizedLabelAndIcon(params.componentName!!,
+                        TEST_LABEL, TEST_ICON, userId)
+            }
+            is Result.Exception -> {
+                assertThrows(result.type) { runUpdate() }
+                verify(mockPkgSetting, never()).overrideNonLocalizedLabelAndIcon(
+                        any<ComponentName>(), any(), anyInt(), anyInt())
+            }
+        }
+    }
+
+    @After
+    fun verifyExpectedResult() {
+        if (params.componentName != null) {
+            val activityInfo = service.getActivityInfo(params.componentName, 0, userId)
+            assertThat(activityInfo.nonLocalizedLabel).isEqualTo(params.expectedLabel)
+            assertThat(activityInfo.icon).isEqualTo(params.expectedIcon)
+        }
+    }
+
+    @After
+    fun verifyDifferentUserUnchanged() {
+        when (params.result) {
+            Result.Changed, Result.ChangedWithoutNotify -> {
+                val activityInfo = service.getActivityInfo(params.componentName, 0, userIdDifferent)
+                assertThat(activityInfo.nonLocalizedLabel).isEqualTo(DEFAULT_LABEL)
+                assertThat(activityInfo.icon).isEqualTo(DEFAULT_ICON)
+            }
+            Result.NotChanged, is Result.Exception -> {}
+        }.run { /*exhaust*/ }
+    }
+
+    @After
+    fun verifyHandlerHasMessage() {
+        when (params.result) {
+            is Result.Changed, is Result.ChangedWithoutNotify -> {
+                assertThat(testHandler.pendingMessages).hasSize(1)
+                assertThat(testHandler.pendingMessages.first().message.what)
+                        .isEqualTo(SEND_PENDING_BROADCAST)
+            }
+            is Result.NotChanged, is Result.Exception -> {
+                assertThat(testHandler.pendingMessages).hasSize(0)
+            }
+        }.run { /*exhaust*/ }
+    }
+
+    @After
+    fun verifyPendingBroadcast() {
+        when (params.result) {
+            is Result.Changed, Result.ChangedWithoutNotify -> {
+                assertThat(mockPendingBroadcasts.get(userId, params.pkgName))
+                        .containsExactly(params.componentName!!.className)
+                        .inOrder()
+            }
+            is Result.NotChanged, is Result.Exception -> {
+                assertThat(mockPendingBroadcasts.get(userId, params.pkgName)).isNull()
+            }
+        }.run { /*exhaust*/ }
+    }
+
+    private fun makePkg(pkgName: String, block: ParsedPackage.() -> Unit = {}) =
+            PackageImpl.forTesting(pkgName)
+                    .setEnabled(true)
+                    .let { it.hideAsParsed() as ParsedPackage }
+                    .setSystem(params.isSystem)
+                    .apply(block)
+                    .hideAsFinal()
+
+    private fun makePkgSetting(pkgName: String) = spy(PackageSetting(pkgName, null, File("/test"),
+            File("/test"), null, null, null, null, 0, 0, 0, 0, null, null, null)) {
+        this.pkgState.isUpdatedSystemApp = params.isUpdatedSystemApp
+    }
+
+    private fun makeTestData() {
+        mockPkg = makePkg(params.pkgName)
+        mockPkgSetting = makePkgSetting(params.pkgName)
+
+        if (params.result is Result.NotChanged) {
+            // If verifying no-op behavior, set the current setting to the test values
+            mockPkgSetting.overrideNonLocalizedLabelAndIcon(params.componentName!!, TEST_LABEL,
+                    TEST_ICON, userId)
+            // Then clear the mock because the line above just incremented it
+            clearInvocations(mockPkgSetting)
+        }
+    }
+
+    private fun mockService(): PackageManagerService {
+        val mockedPkgs = mapOf(
+                // Must use the test app's UID so that PMS can match them when querying, since
+                // the static Binder.getCallingUid can't mocked as it's marked final
+                VALID_PKG to makePkg(VALID_PKG) { uid = Binder.getCallingUid() },
+                SHARED_PKG to makePkg(SHARED_PKG) { uid = Binder.getCallingUid() },
+                INVALID_PKG to makePkg(INVALID_PKG) { uid = Binder.getCallingUid() + 1 }
+        )
+        val mockedPkgSettings = mapOf(
+                VALID_PKG to makePkgSetting(VALID_PKG),
+                SHARED_PKG to makePkgSetting(SHARED_PKG),
+                INVALID_PKG to makePkgSetting(INVALID_PKG)
+        )
+                // Add pkgSetting under test so its attributes override the defaults added above
+                .plus(params.pkgName to mockPkgSetting)
+
+        val mockActivity: ParsedActivity = mock {
+            whenever(this.packageName) { params.pkgName }
+            whenever(this.nonLocalizedLabel) { DEFAULT_LABEL }
+            whenever(this.icon) { DEFAULT_ICON }
+            whenever(this.componentName) { params.componentName }
+            whenever(this.name) { params.componentName?.className }
+            whenever(this.isEnabled) { true }
+            whenever(this.isDirectBootAware) { params.isSystem }
+        }
+
+        val mockSettings = Settings(mockedPkgSettings)
+        val mockComponentResolver: ComponentResolver = mockThrowOnUnmocked {
+            params.componentName?.let {
+                whenever(this.componentExists(same(it))) { true }
+                whenever(this.getActivity(same(it))) { mockActivity }
+            }
+        }
+        val mockUserManagerService: UserManagerService = mockThrowOnUnmocked {
+            val matcher: (Int) -> Boolean = { it == userId || it == userIdDifferent }
+            whenever(this.exists(intThat(matcher))) { true }
+            whenever(this.isUserUnlockingOrUnlocked(intThat(matcher))) { true }
+        }
+        val mockPermissionManagerService: PermissionManagerServiceInternal = mockThrowOnUnmocked {
+            whenever(this.enforceCrossUserPermission(anyInt(), anyInt(), anyBoolean(), anyBoolean(),
+                    anyString())) { }
+        }
+        val mockActivityTaskManager: ActivityTaskManagerInternal = mockThrowOnUnmocked {
+            whenever(this.isCallerRecents(anyInt())) { false }
+        }
+        val mockAppsFilter: AppsFilter = mockThrowOnUnmocked {
+            whenever(this.shouldFilterApplication(anyInt(), any<PackageSetting>(),
+                    any<PackageSetting>(), anyInt())) { false }
+        }
+        val mockContext: Context = mockThrowOnUnmocked {
+            whenever(this.getString(
+                    com.android.internal.R.string.config_overrideComponentUiPackage)) { VALID_PKG }
+        }
+        val mockInjector: PackageManagerService.Injector = mock {
+            whenever(this.lock) { Object() }
+            whenever(this.componentResolver) { mockComponentResolver }
+            whenever(this.userManagerService) { mockUserManagerService }
+            whenever(this.permissionManagerServiceInternal) { mockPermissionManagerService }
+            whenever(this.settings) { mockSettings }
+            whenever(this.activityTaskManagerInternal) { mockActivityTaskManager }
+            whenever(this.appsFilter) { mockAppsFilter }
+            whenever(this.context) { mockContext }
+        }
+        val testParams = PackageManagerService.TestParams().apply {
+            this.handler = testHandler
+            this.pendingPackageBroadcasts = mockPendingBroadcasts
+            this.resolveComponentName = ComponentName("android", ".Test")
+            this.packages = ArrayMap<String, AndroidPackage>().apply { putAll(mockedPkgs) }
+        }
+
+        return PackageManagerService(mockInjector, testParams)
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
index 5c82200..736a7be 100644
--- a/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java
@@ -79,7 +79,8 @@
     private static final String CALLING_PACKAGE2 = "com.package.name2";
     private static final String NAMESPACE1 = "namespace1";
     private static final String NAMESPACE2 = "namespace2";
-    private static final String DISABLE_RESCUE_PARTY_FLAG = "disable_rescue_party";
+    private static final String PROP_DEVICE_CONFIG_DISABLE_FLAG =
+            "persist.device_config.configuration.disable_rescue_party";
 
     private MockitoSession mSession;
     private HashMap<String, String> mSystemSettingsMap;
@@ -172,6 +173,7 @@
                 Integer.toString(RescueParty.LEVEL_NONE));
         SystemProperties.set(RescueParty.PROP_RESCUE_BOOT_COUNT, Integer.toString(0));
         SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(true));
+        SystemProperties.set(PROP_DEVICE_CONFIG_DISABLE_FLAG, Boolean.toString(false));
     }
 
     @After
@@ -317,13 +319,6 @@
 
     @Test
     public void testExplicitlyEnablingAndDisablingRescue() {
-        // mock the DeviceConfig get call to avoid hitting
-        // android.permission.READ_DEVICE_CONFIG when calling real DeviceConfig.
-        doReturn(true)
-                .when(() -> DeviceConfig.getBoolean(
-                    eq(DeviceConfig.NAMESPACE_CONFIGURATION),
-                    eq(DISABLE_RESCUE_PARTY_FLAG),
-                    eq(false)));
         SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(false));
         SystemProperties.set(PROP_DISABLE_RESCUE, Boolean.toString(true));
         assertEquals(RescuePartyObserver.getInstance(mMockContext).execute(sFailingPackage,
@@ -336,18 +331,15 @@
 
     @Test
     public void testDisablingRescueByDeviceConfigFlag() {
-        doReturn(true)
-                .when(() -> DeviceConfig.getBoolean(
-                    eq(DeviceConfig.NAMESPACE_CONFIGURATION),
-                    eq(DISABLE_RESCUE_PARTY_FLAG),
-                    eq(false)));
         SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(false));
+        SystemProperties.set(PROP_DEVICE_CONFIG_DISABLE_FLAG, Boolean.toString(true));
 
         assertEquals(RescuePartyObserver.getInstance(mMockContext).execute(sFailingPackage,
                 PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING), false);
 
         // Restore the property value initalized in SetUp()
         SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(true));
+        SystemProperties.set(PROP_DEVICE_CONFIG_DISABLE_FLAG, Boolean.toString(false));
     }
 
     @Test
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index e58e911..b457856 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -107,6 +107,8 @@
     name: "servicestests-utils",
     srcs: [
         "utils/**/*.java",
+        "utils/**/*.kt",
+        "utils-mockito/**/*.kt",
     ],
     static_libs: [
         "junit",
@@ -117,6 +119,22 @@
     ],
 }
 
+java_library {
+    name: "servicestests-utils-mockito-extended",
+    srcs: [
+        "utils/**/*.java",
+        "utils/**/*.kt",
+        "utils-mockito/**/*.kt",
+    ],
+    static_libs: [
+        "junit",
+        "mockito-target-extended-minus-junit4",
+    ],
+    libs: [
+        "android.test.runner",
+    ],
+}
+
 filegroup {
     name: "servicestests-SuspendTestApp-files",
     srcs: [
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt b/services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt
index 063cd5da..78c7080 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayReferenceMapperTests.kt
@@ -18,6 +18,8 @@
 
 import android.net.Uri
 import com.android.server.pm.parsing.pkg.AndroidPackage
+import com.android.server.testutils.mockThrowOnUnmocked
+import com.android.server.testutils.whenever
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
index 2cbb6d5..06b344b 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -8595,6 +8595,56 @@
         }
     }
 
+    public void testIsSharingShortcut() throws IntentFilter.MalformedMimeTypeException {
+        addManifestShortcutResource(
+                new ComponentName(CALLING_PACKAGE_1, ShortcutActivity.class.getName()),
+                R.xml.shortcut_share_targets);
+        updatePackageVersion(CALLING_PACKAGE_1, 1);
+        mService.mPackageMonitor.onReceive(getTestContext(),
+                genPackageAddIntent(CALLING_PACKAGE_1, USER_0));
+
+        setCaller(CALLING_PACKAGE_1, USER_0);
+
+        final ShortcutInfo s1 = makeShortcutWithCategory("s1",
+                set("com.test.category.CATEGORY1", "com.test.category.CATEGORY2"));
+        final ShortcutInfo s2 = makeShortcutWithCategory("s2",
+                set("com.test.category.CATEGORY5", "com.test.category.CATEGORY6"));
+        final ShortcutInfo s3 = makeShortcut("s3");
+
+        assertTrue(mManager.setDynamicShortcuts(list(s1, s2, s3)));
+        assertShortcutIds(assertAllNotKeyFieldsOnly(mManager.getDynamicShortcuts()),
+                "s1", "s2", "s3");
+
+        IntentFilter filter_cat1 = new IntentFilter();
+        filter_cat1.addDataType("text/plain");
+        IntentFilter filter_cat5 = new IntentFilter();
+        filter_cat5.addDataType("video/*");
+        IntentFilter filter_any = new IntentFilter();
+        filter_any.addDataType("*/*");
+
+        setCaller(LAUNCHER_1, USER_0);
+        mCallerPermissions.add(permission.MANAGE_APP_PREDICTIONS);
+
+        assertTrue(mInternal.isSharingShortcut(USER_0, LAUNCHER_1, CALLING_PACKAGE_1, "s1", USER_0,
+                filter_cat1));
+        assertFalse(mInternal.isSharingShortcut(USER_0, LAUNCHER_1, CALLING_PACKAGE_1, "s1", USER_0,
+                filter_cat5));
+        assertTrue(mInternal.isSharingShortcut(USER_0, LAUNCHER_1, CALLING_PACKAGE_1, "s1", USER_0,
+                filter_any));
+
+        assertFalse(mInternal.isSharingShortcut(USER_0, LAUNCHER_1, CALLING_PACKAGE_1, "s2", USER_0,
+                filter_cat1));
+        assertTrue(mInternal.isSharingShortcut(USER_0, LAUNCHER_1, CALLING_PACKAGE_1, "s2", USER_0,
+                filter_cat5));
+        assertTrue(mInternal.isSharingShortcut(USER_0, LAUNCHER_1, CALLING_PACKAGE_1, "s2", USER_0,
+                filter_any));
+
+        assertFalse(mInternal.isSharingShortcut(USER_0, LAUNCHER_1, CALLING_PACKAGE_1, "s3", USER_0,
+                filter_any));
+        assertFalse(mInternal.isSharingShortcut(USER_0, LAUNCHER_1, CALLING_PACKAGE_1, "s4", USER_0,
+                filter_any));
+    }
+
     private Uri getFileUriFromResource(String fileName, int resId) throws IOException {
         File file = new File(getTestContext().getFilesDir(), fileName);
         // Make sure we are not leaving phantom files behind.
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingEquivalenceTest.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingEquivalenceTest.kt
index 191c038..5412bb5 100644
--- a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingEquivalenceTest.kt
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingEquivalenceTest.kt
@@ -18,7 +18,9 @@
 
 import android.content.pm.PackageManager
 import android.platform.test.annotations.Presubmit
+import com.google.common.truth.Expect
 import com.google.common.truth.Truth.assertWithMessage
+import org.junit.Rule
 import org.junit.Test
 
 /**
@@ -28,6 +30,9 @@
 @Presubmit
 class AndroidPackageParsingEquivalenceTest : AndroidPackageParsingTestBase() {
 
+    @get:Rule
+    val expect = Expect.create()
+
     @Test
     fun applicationInfoEquality() {
         val flags = PackageManager.GET_META_DATA or PackageManager.GET_SHARED_LIBRARY_FILES
@@ -41,7 +46,8 @@
             } else {
                 "$firstName | $secondName"
             }
-            assertWithMessage(packageName).that(it.first?.dumpToString())
+            expect.withMessage("${it.first?.sourceDir} $packageName")
+                    .that(it.first?.dumpToString())
                     .isEqualTo(it.second?.dumpToString())
         }
     }
@@ -71,7 +77,8 @@
             } else {
                 "$firstName | $secondName"
             }
-            assertWithMessage(packageName).that(it.first?.dumpToString())
+            expect.withMessage("${it.first?.applicationInfo?.sourceDir} $packageName")
+                    .that(it.first?.dumpToString())
                     .isEqualTo(it.second?.dumpToString())
         }
     }
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt
index f532dd8..7b1b2d2 100644
--- a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingTestBase.kt
@@ -29,14 +29,17 @@
 import android.content.pm.ProviderInfo
 import android.os.Debug
 import android.os.Environment
+import android.os.ServiceManager
 import android.util.SparseArray
 import androidx.test.platform.app.InstrumentationRegistry
-import com.android.server.om.mockThrowOnUnmocked
-import com.android.server.om.whenever
+import com.android.internal.compat.IPlatformCompat
 import com.android.server.pm.PackageManagerService
 import com.android.server.pm.PackageSetting
 import com.android.server.pm.parsing.pkg.AndroidPackage
 import com.android.server.pm.pkg.PackageStateUnserialized
+import com.android.server.testutils.mockThrowOnUnmocked
+import com.android.server.testutils.whenever
+import org.junit.After
 import org.junit.BeforeClass
 import org.mockito.Mockito
 import org.mockito.Mockito.anyInt
@@ -59,7 +62,27 @@
             setCallback { false /* hasFeature */ }
         }
 
-        protected val packageParser2 = TestPackageParser2()
+        private val platformCompat = IPlatformCompat.Stub
+                .asInterface(ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE))
+
+        protected val packageParser2 = PackageParser2(null /* separateProcesses */,
+                false /* onlyCoreApps */, context.resources.displayMetrics, null /* cacheDir */,
+                object : PackageParser2.Callback() {
+                    override fun isChangeEnabled(
+                        changeId: Long,
+                        appInfo: ApplicationInfo
+                    ): Boolean {
+                        // This test queries PlatformCompat because prebuilts in the tree
+                        // may not be updated to be compliant with the latest enforcement checks.
+                        return platformCompat.isChangeEnabled(changeId, appInfo)
+                    }
+
+                    // Assume the device doesn't support anything. This will affect permission
+                    // parsing and will force <uses-permission/> declarations to include all
+                    // requiredNotFeature permissions and exclude all requiredFeature permissions.
+                    // This mirrors the old behavior.
+                    override fun hasFeature(feature: String) = false
+                })
 
         /**
          * It would be difficult to mock all possibilities, so just use the APKs on device.
@@ -91,22 +114,29 @@
 
         lateinit var newPackages: List<AndroidPackage>
 
+        private val thrownInSetUp = mutableListOf<Throwable>()
+
         @Suppress("ConstantConditionIf")
         @JvmStatic
         @BeforeClass
         fun setUpPackages() {
-            this.oldPackages = apks.map {
-                packageParser.parsePackage(it, PackageParser.PARSE_IS_SYSTEM_DIR, false)
+            this.oldPackages = apks.mapNotNull {
+                tryOrNull {
+                    packageParser.parsePackage(it, PackageParser.PARSE_IS_SYSTEM_DIR, false)
+                }
             }
 
-            this.newPackages = apks.map {
-                packageParser2.parsePackage(it, PackageParser.PARSE_IS_SYSTEM_DIR, false)
+            this.newPackages = apks.mapNotNull {
+                tryOrNull {
+                    packageParser2.parsePackage(it, PackageParser.PARSE_IS_SYSTEM_DIR, false)
+                }
             }
 
             if (DUMP_HPROF_TO_EXTERNAL) {
                 System.gc()
                 Environment.getExternalStorageDirectory()
-                        .resolve("${AndroidPackageParsingTestBase::class.java.simpleName}.hprof")
+                        .resolve(
+                                "${AndroidPackageParsingTestBase::class.java.simpleName}.hprof")
                         .absolutePath
                         .run(Debug::dumpHprofData)
             }
@@ -135,6 +165,36 @@
             this.pkg = aPkg
             whenever(pkgState) { PackageStateUnserialized() }
         }
+
+        private fun <T> tryOrNull(block: () -> T) = try {
+            block()
+        } catch (t: Throwable) {
+            thrownInSetUp.add(t)
+            null
+        }
+    }
+
+    @After
+    fun verifySetUpPackages() {
+        if (thrownInSetUp.isEmpty()) return
+        val exception = AssertionError("setUpPackages failed with ${thrownInSetUp.size} errors:\n" +
+                thrownInSetUp.joinToString(separator = "\n") { it.message.orEmpty() })
+
+        /*
+            Testing infrastructure doesn't currently support errors thrown in @AfterClass,
+            so instead it's thrown here. But to avoid throwing a massive repeated stack for every
+            test method, only throw on the first method run in the class, clearing the list so that
+            subsequent methods can run without failing. Doing this in @After lets true method
+            failures propagate, as those should throw before this does.
+
+            This will cause the failure to be attached to a different method depending on run order,
+            which could make comparisons difficult. So if a failure points here, it's worth
+            checking failures for all methods in all subclasses.
+
+            TODO: When infrastructure supports @AfterClass errors, move this
+        */
+        thrownInSetUp.clear()
+        throw exception
     }
 
     // The following methods dump an exact set of fields from the object to compare, because
diff --git a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
index 06b5fe4..ebcf10d 100644
--- a/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareImplTest.java
@@ -1106,7 +1106,7 @@
     public void testAbortRecognition() throws Exception {
         // Make sure the HAL doesn't support concurrent capture.
         initService(false);
-        mService.setExternalCaptureState(false);
+        mService.setCaptureState(false);
 
         ISoundTriggerCallback callback = createCallbackMock();
         ISoundTriggerModule module = mService.attach(0, callback);
@@ -1120,7 +1120,7 @@
         startRecognition(module, handle, hwHandle);
 
         // Abort.
-        mService.setExternalCaptureState(true);
+        mService.setCaptureState(true);
 
         ArgumentCaptor<RecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
                 RecognitionEvent.class);
@@ -1142,7 +1142,7 @@
         verifyNotStartRecognition();
 
         // Now enable it and make sure we are notified.
-        mService.setExternalCaptureState(false);
+        mService.setCaptureState(false);
         verify(callback).onRecognitionAvailabilityChange(true);
 
         // Unload the model.
@@ -1154,7 +1154,7 @@
     public void testAbortPhraseRecognition() throws Exception {
         // Make sure the HAL doesn't support concurrent capture.
         initService(false);
-        mService.setExternalCaptureState(false);
+        mService.setCaptureState(false);
 
         ISoundTriggerCallback callback = createCallbackMock();
         ISoundTriggerModule module = mService.attach(0, callback);
@@ -1168,7 +1168,7 @@
         startRecognition(module, handle, hwHandle);
 
         // Abort.
-        mService.setExternalCaptureState(true);
+        mService.setCaptureState(true);
 
         ArgumentCaptor<PhraseRecognitionEvent> eventCaptor = ArgumentCaptor.forClass(
                 PhraseRecognitionEvent.class);
@@ -1190,7 +1190,7 @@
         verifyNotStartRecognition();
 
         // Now enable it and make sure we are notified.
-        mService.setExternalCaptureState(false);
+        mService.setCaptureState(false);
         verify(callback).onRecognitionAvailabilityChange(true);
 
         // Unload the model.
@@ -1216,7 +1216,7 @@
         startRecognition(module, handle, hwHandle);
 
         // Signal concurrent capture. Shouldn't abort.
-        mService.setExternalCaptureState(true);
+        mService.setCaptureState(true);
         verify(callback, never()).onRecognition(anyInt(), any());
         verify(callback, never()).onRecognitionAvailabilityChange(anyBoolean());
 
@@ -1252,7 +1252,7 @@
         startRecognition(module, handle, hwHandle);
 
         // Signal concurrent capture. Shouldn't abort.
-        mService.setExternalCaptureState(true);
+        mService.setCaptureState(true);
         verify(callback, never()).onPhraseRecognition(anyInt(), any());
         verify(callback, never()).onRecognitionAvailabilityChange(anyBoolean());
 
diff --git a/services/tests/servicestests/src/com/android/server/om/MockitoUtils.kt b/services/tests/servicestests/utils-mockito/com/android/server/testutils/MockitoUtils.kt
similarity index 74%
rename from services/tests/servicestests/src/com/android/server/om/MockitoUtils.kt
rename to services/tests/servicestests/utils-mockito/com/android/server/testutils/MockitoUtils.kt
index 0f915db..056fa88 100644
--- a/services/tests/servicestests/src/com/android/server/om/MockitoUtils.kt
+++ b/services/tests/servicestests/utils-mockito/com/android/server/testutils/MockitoUtils.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.om
+package com.android.server.testutils
 
 import org.mockito.Answers
 import org.mockito.Mockito
@@ -31,6 +31,13 @@
             else -> {
                 val arguments = it.arguments
                         ?.takeUnless { it.isEmpty() }
+                        ?.mapIndexed { index, arg ->
+                            try {
+                                arg?.toString()
+                            } catch (e: Exception) {
+                                "toString[$index] threw ${e.message}"
+                            }
+                        }
                         ?.joinToString()
                         ?.let {
                             "with $it"
@@ -46,6 +53,8 @@
 
 inline fun <reified T> mock(block: T.() -> Unit = {}) = Mockito.mock(T::class.java).apply(block)
 
+fun <T> spy(value: T, block: T.() -> Unit = {}) = Mockito.spy(value).apply(block)
+
 fun <Type> Stubber.whenever(mock: Type) = Mockito.`when`(mock)
 fun <Type : Any?> whenever(mock: Type) = Mockito.`when`(mock)
 
@@ -55,7 +64,7 @@
 
 fun whenever(mock: Unit) = Mockito.`when`(mock).thenAnswer { }
 
-inline fun <reified T> mockThrowOnUnmocked(block: T.() -> Unit): T {
+inline fun <reified T> spyThrowOnUnmocked(value: T?, block: T.() -> Unit): T {
     val swappingAnswer = object : Answer<Any?> {
         var delegate: Answer<*> = Answers.RETURNS_DEFAULTS
 
@@ -64,9 +73,12 @@
         }
     }
 
-    return Mockito.mock(T::class.java, swappingAnswer).apply(block)
+    return Mockito.mock(T::class.java, Mockito.withSettings().spiedInstance(value)
+            .defaultAnswer(swappingAnswer)).apply(block)
             .also {
                 // To allow when() usage inside block, only swap to throwing afterwards
                 swappingAnswer.delegate = MockitoUtils.ANSWER_THROWS
             }
 }
+
+inline fun <reified T> mockThrowOnUnmocked(block: T.() -> Unit) = spyThrowOnUnmocked<T>(null, block)
diff --git a/services/tests/servicestests/utils/com/android/server/testutils/TestHandler.java b/services/tests/servicestests/utils/com/android/server/testutils/TestHandler.java
index 69db384..355e7f3 100644
--- a/services/tests/servicestests/utils/com/android/server/testutils/TestHandler.java
+++ b/services/tests/servicestests/utils/com/android/server/testutils/TestHandler.java
@@ -139,7 +139,7 @@
         }
     }
 
-    private class MsgInfo implements Comparable<MsgInfo> {
+    public class MsgInfo implements Comparable<MsgInfo> {
         public final Message message;
         public final long sendTime;
         public final RuntimeException postPoint;
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index f083f0e..f9596b5 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -6504,4 +6504,19 @@
         assertNull(conversations.get(0).getShortcutInfo());
         assertNull(conversations.get(1).getShortcutInfo());
     }
+
+    @Test
+    public void testShortcutHelperNull_doesntCrashEnqueue() throws RemoteException {
+        mService.setShortcutHelper(null);
+        NotificationRecord nr =
+                generateMessageBubbleNotifRecord(mTestNotificationChannel,
+                        "testShortcutHelperNull_doesntCrashEnqueue");
+        try {
+            mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(),
+                    nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId());
+            waitForIdle();
+        } catch (Exception e) {
+            fail(e.getMessage());
+        }
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index 319e9ac..1d952bf 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -83,7 +83,7 @@
 import android.service.voice.IVoiceInteractionSession;
 import android.view.Gravity;
 import android.window.ITaskOrganizer;
-import android.window.IWindowContainer;
+import android.window.WindowContainerToken;
 
 import androidx.test.filters.SmallTest;
 
@@ -1014,10 +1014,10 @@
                     WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
             mService.mTaskOrganizerController.registerTaskOrganizer(this,
                     WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
-            IWindowContainer primary = mService.mTaskOrganizerController.createRootTask(
+            WindowContainerToken primary = mService.mTaskOrganizerController.createRootTask(
                     displayId, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY).token;
             mPrimary = WindowContainer.fromBinder(primary.asBinder()).asTask();
-            IWindowContainer secondary = mService.mTaskOrganizerController.createRootTask(
+            WindowContainerToken secondary = mService.mTaskOrganizerController.createRootTask(
                     displayId, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY).token;
             mSecondary = WindowContainer.fromBinder(secondary.asBinder()).asTask();
         }
@@ -1037,7 +1037,7 @@
                         == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
                     mInSplit = true;
                     mService.mTaskOrganizerController.setLaunchRoot(mDisplayId,
-                            mSecondary.mRemoteToken);
+                            mSecondary.mRemoteToken.toWindowContainerToken());
                     // move everything to secondary because test expects this but usually sysui
                     // does it.
                     DisplayContent dc = mService.mRootWindowContainer.getDisplayContent(mDisplayId);
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
index 08e492a..8b91c7e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
@@ -73,7 +73,6 @@
     }
 
     @Test
-    @FlakyTest(bugId = 131005232)
     public void testKeyguardOverride() {
         mWm.prepareAppTransition(TRANSIT_ACTIVITY_OPEN, false /* alwaysKeepCurrent */);
         mWm.prepareAppTransition(TRANSIT_KEYGUARD_GOING_AWAY, false /* alwaysKeepCurrent */);
@@ -81,7 +80,6 @@
     }
 
     @Test
-    @FlakyTest(bugId = 131005232)
     public void testKeyguardKeep() {
         mWm.prepareAppTransition(TRANSIT_KEYGUARD_GOING_AWAY, false /* alwaysKeepCurrent */);
         mWm.prepareAppTransition(TRANSIT_ACTIVITY_OPEN, false /* alwaysKeepCurrent */);
@@ -89,7 +87,6 @@
     }
 
     @Test
-    @FlakyTest(bugId = 131005232)
     public void testForceOverride() {
         mWm.prepareAppTransition(TRANSIT_KEYGUARD_UNOCCLUDE, false /* alwaysKeepCurrent */);
         mDc.prepareAppTransition(TRANSIT_ACTIVITY_OPEN,
@@ -105,7 +102,6 @@
     }
 
     @Test
-    @FlakyTest(bugId = 131005232)
     public void testKeepKeyguard_withCrashing() {
         mWm.prepareAppTransition(TRANSIT_KEYGUARD_GOING_AWAY, false /* alwaysKeepCurrent */);
         mWm.prepareAppTransition(TRANSIT_CRASHING_ACTIVITY_CLOSE, false /* alwaysKeepCurrent */);
@@ -113,7 +109,6 @@
     }
 
     @Test
-    @FlakyTest(bugId = 131005232)
     public void testAppTransitionStateForMultiDisplay() {
         // Create 2 displays & presume both display the state is ON for ready to display & animate.
         final DisplayContent dc1 = createNewDisplay(Display.STATE_ON);
@@ -182,7 +177,6 @@
     }
 
     @Test
-    @FlakyTest(bugId = 131005232)
     public void testLoadAnimationSafely() {
         DisplayContent dc = createNewDisplay(Display.STATE_ON);
         assertNull(dc.mAppTransition.loadAnimationSafely(
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenAnimationTests.java b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenAnimationTests.java
index 6e78a27..b93a8fc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenAnimationTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppWindowTokenAnimationTests.java
@@ -67,7 +67,6 @@
     }
 
     @Test
-    @FlakyTest(bugId = 131005232)
     public void clipAfterAnim_boundsLayerIsCreated() {
         mActivity.mNeedsAnimationBoundsLayer = true;
 
@@ -91,7 +90,6 @@
     }
 
     @Test
-    @FlakyTest(bugId = 131005232)
     public void clipAfterAnim_boundsLayerIsDestroyed() {
         mActivity.mNeedsAnimationBoundsLayer = true;
         mActivity.mSurfaceAnimator.startAnimation(mTransaction, mSpec, true /* hidden */,
@@ -126,7 +124,6 @@
     }
 
     @Test
-    @FlakyTest(bugId = 131005232)
     public void clipNoneAnim_boundsLayerIsNotCreated() {
         mActivity.mNeedsAnimationBoundsLayer = false;
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 9cfee34..38b3d76 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -1039,6 +1039,13 @@
         assertEquals(config90.orientation, app.getConfiguration().orientation);
         assertEquals(config90.windowConfiguration.getBounds(), app.getBounds());
 
+        // Force the negative offset to verify it can be updated.
+        mWallpaperWindow.mWinAnimator.mXOffset = mWallpaperWindow.mWinAnimator.mYOffset = -1;
+        assertTrue(mDisplayContent.mWallpaperController.updateWallpaperOffset(mWallpaperWindow,
+                false /* sync */));
+        assertThat(mWallpaperWindow.mWinAnimator.mXOffset).isGreaterThan(-1);
+        assertThat(mWallpaperWindow.mWinAnimator.mYOffset).isGreaterThan(-1);
+
         mDisplayContent.mAppTransition.notifyAppTransitionFinishedLocked(app.token);
 
         // The animation in old rotation should be cancelled.
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
index 7928e76..28ae36a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyLayoutTests.java
@@ -677,7 +677,6 @@
     }
 
     @Test
-    @FlakyTest(bugId = 149760800)
     public void layoutWindowLw_withLongEdgeDisplayCutout() {
         addLongEdgeDisplayCutout();
 
@@ -698,7 +697,6 @@
     }
 
     @Test
-    @FlakyTest(bugId = 149760800)
     public void layoutWindowLw_withLongEdgeDisplayCutout_never() {
         addLongEdgeDisplayCutout();
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
index c370d6c..d0fd50d 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java
@@ -277,7 +277,6 @@
     }
 
     @Test
-    @FlakyTest(bugId = 131005232)
     public void testOverlappingWithNavBar() {
         final WindowState targetWin = createApplicationWindow();
         final WindowFrames winFrame = targetWin.getWindowFrames();
diff --git a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
index 956c200..0eee3ca 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
@@ -147,19 +147,16 @@
     }
 
     @Test
-    @FlakyTest(bugId = 131005232)
     public void testDragFlow() {
         dragFlow(0, ClipData.newPlainText("label", "Test"), 0, 0);
     }
 
     @Test
-    @FlakyTest(bugId = 131005232)
     public void testPerformDrag_NullDataWithGrantUri() {
         dragFlow(View.DRAG_FLAG_GLOBAL | View.DRAG_FLAG_GLOBAL_URI_READ, null, 0, 0);
     }
 
     @Test
-    @FlakyTest(bugId = 131005232)
     public void testPerformDrag_NullDataToOtherUser() {
         final WindowState otherUsersWindow =
                 createDropTargetWindow("Other user's window", 1 * UserHandle.PER_USER_RANGE);
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
index b21ea79..89bc65b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
@@ -58,7 +58,6 @@
 import org.junit.runner.RunWith;
 
 @SmallTest
-@FlakyTest(detail = "Promote to pre-submit once confirmed stable.")
 @Presubmit
 @RunWith(WindowTestRunner.class)
 public class InsetsPolicyTest extends WindowTestsBase {
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
index db7bce4..61b74b0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
@@ -32,6 +32,10 @@
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
 
 import android.graphics.Rect;
 import android.platform.test.annotations.Presubmit;
@@ -49,7 +53,6 @@
 import org.junit.runner.RunWith;
 
 @SmallTest
-@FlakyTest(detail = "Promote to pre-submit once confirmed stable.")
 @Presubmit
 @RunWith(WindowTestRunner.class)
 public class InsetsStateControllerTest extends WindowTestsBase {
@@ -68,7 +71,6 @@
     }
 
     @Test
-    @FlakyTest(bugId = 131005232)
     public void testStripForDispatch_notOwn() {
         final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
         final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
@@ -102,7 +104,8 @@
         getController().getSourceProvider(ITYPE_STATUS_BAR).setWindow(statusBar, null, null);
         getController().getSourceProvider(ITYPE_NAVIGATION_BAR).setWindow(navBar, null, null);
         getController().getSourceProvider(ITYPE_IME).setWindow(ime, null, null);
-        assertEquals(0, getController().getInsetsForDispatch(navBar).getSourcesCount());
+        assertNull(getController().getInsetsForDispatch(navBar).peekSource(ITYPE_IME));
+        assertNull(getController().getInsetsForDispatch(navBar).peekSource(ITYPE_STATUS_BAR));
     }
 
     @Test
@@ -169,6 +172,45 @@
     }
 
     @Test
+    public void testStripForDispatch_imeOrderChanged() {
+        getController().getSourceProvider(ITYPE_IME).setWindow(mImeWindow, null, null);
+
+        // This window can be the IME target while app cannot be the IME target.
+        createWindow(null, TYPE_APPLICATION, "base");
+
+        // Send our spy window (app) into the system so that we can detect the invocation.
+        final WindowState win = createWindow(null, TYPE_APPLICATION, "app");
+        final WindowToken parent = win.mToken;
+        parent.removeChild(win);
+        final WindowState app = spy(win);
+        parent.addWindow(app);
+
+        // Adding FLAG_NOT_FOCUSABLE makes app above IME.
+        app.mAttrs.flags |= FLAG_NOT_FOCUSABLE;
+        mDisplayContent.computeImeTarget(true);
+        mDisplayContent.setLayoutNeeded();
+        mDisplayContent.applySurfaceChangesTransaction();
+
+        // app won't get IME insets while above IME.
+        assertNull(getController().getInsetsForDispatch(app).peekSource(ITYPE_IME));
+
+        // Reset invocation counter.
+        clearInvocations(app);
+
+        // Removing FLAG_NOT_FOCUSABLE makes app below IME.
+        app.mAttrs.flags &= ~FLAG_NOT_FOCUSABLE;
+        mDisplayContent.computeImeTarget(true);
+        mDisplayContent.setLayoutNeeded();
+        mDisplayContent.applySurfaceChangesTransaction();
+
+        // Make sure app got notified.
+        verify(app, atLeast(1)).notifyInsetsChanged();
+
+        // app will get IME insets while below IME.
+        assertNotNull(getController().getInsetsForDispatch(app).peekSource(ITYPE_IME));
+    }
+
+    @Test
     public void testStripForDispatch_childWindow_altFocusable() {
         final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
 
@@ -247,7 +289,6 @@
         assertNull(getController().getControlsForDispatch(app));
     }
 
-    @FlakyTest(bugId = 124088319)
     @Test
     public void testControlRevoked_animation() {
         final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar");
diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
index 34ac835..67aab7e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java
@@ -146,7 +146,6 @@
     }
 
     @Test
-    @FlakyTest(bugId = 133372977)
     public void testTimeout() throws Exception {
         final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
         final AnimationAdapter adapter = mController.createRemoteAnimationRecord(win.mActivityRecord,
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
index f05acce..6ef714e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
@@ -26,6 +26,8 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.content.res.Configuration.SCREEN_HEIGHT_DP_UNDEFINED;
+import static android.content.res.Configuration.SCREEN_WIDTH_DP_UNDEFINED;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
@@ -282,7 +284,7 @@
         final Task task = stack.getTopMostTask();
         WindowContainerTransaction t = new WindowContainerTransaction();
         Rect newBounds = new Rect(10, 10, 100, 100);
-        t.setBounds(task.mRemoteToken, new Rect(10, 10, 100, 100));
+        t.setBounds(task.mRemoteToken.toWindowContainerToken(), new Rect(10, 10, 100, 100));
         mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
         assertEquals(newBounds, task.getBounds());
     }
@@ -295,7 +297,7 @@
         StackInfo info =
                 mWm.mAtmService.getStackInfo(WINDOWING_MODE_FREEFORM, ACTIVITY_TYPE_STANDARD);
         WindowContainerTransaction t = new WindowContainerTransaction();
-        assertEquals(stack.mRemoteToken, info.stackToken);
+        assertEquals(stack.mRemoteToken.toWindowContainerToken(), info.stackToken);
         Rect newBounds = new Rect(10, 10, 100, 100);
         t.setBounds(info.stackToken, new Rect(10, 10, 100, 100));
         mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
@@ -308,7 +310,7 @@
             .setWindowingMode(WINDOWING_MODE_FREEFORM).build();
         final WindowContainerTransaction t = new WindowContainerTransaction();
 
-        t.setWindowingMode(stack.mRemoteToken, WINDOWING_MODE_FULLSCREEN);
+        t.setWindowingMode(stack.mRemoteToken.toWindowContainerToken(), WINDOWING_MODE_FULLSCREEN);
         mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
 
         assertEquals(WINDOWING_MODE_FULLSCREEN, stack.getWindowingMode());
@@ -320,8 +322,9 @@
         final ActivityStack stack = record.getStack();
         final WindowContainerTransaction t = new WindowContainerTransaction();
 
-        t.setWindowingMode(stack.mRemoteToken, WINDOWING_MODE_PINNED);
-        t.setActivityWindowingMode(stack.mRemoteToken, WINDOWING_MODE_FULLSCREEN);
+        t.setWindowingMode(stack.mRemoteToken.toWindowContainerToken(), WINDOWING_MODE_PINNED);
+        t.setActivityWindowingMode(
+                stack.mRemoteToken.toWindowContainerToken(), WINDOWING_MODE_FULLSCREEN);
         mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
 
         assertEquals(WINDOWING_MODE_FULLSCREEN, record.getWindowingMode());
@@ -336,10 +339,10 @@
         final Task task = stack.getTopMostTask();
         WindowContainerTransaction t = new WindowContainerTransaction();
         assertTrue(task.isFocusable());
-        t.setFocusable(stack.mRemoteToken, false);
+        t.setFocusable(stack.mRemoteToken.toWindowContainerToken(), false);
         mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
         assertFalse(task.isFocusable());
-        t.setFocusable(stack.mRemoteToken, true);
+        t.setFocusable(stack.mRemoteToken.toWindowContainerToken(), true);
         mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
         assertTrue(task.isFocusable());
     }
@@ -351,10 +354,10 @@
                 .setWindowingMode(WINDOWING_MODE_FREEFORM).build();
         WindowContainerTransaction t = new WindowContainerTransaction();
         assertTrue(stack.shouldBeVisible(null));
-        t.setHidden(stack.mRemoteToken, true);
+        t.setHidden(stack.mRemoteToken.toWindowContainerToken(), true);
         mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
         assertFalse(stack.shouldBeVisible(null));
-        t.setHidden(stack.mRemoteToken, false);
+        t.setHidden(stack.mRemoteToken.toWindowContainerToken(), false);
         mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
         assertTrue(stack.shouldBeVisible(null));
     }
@@ -366,19 +369,19 @@
                 .setWindowingMode(WINDOWING_MODE_FREEFORM).build();
         final Task task = stack.getTopMostTask();
         WindowContainerTransaction t = new WindowContainerTransaction();
-        t.setBounds(task.mRemoteToken, new Rect(10, 10, 100, 100));
+        t.setBounds(task.mRemoteToken.toWindowContainerToken(), new Rect(10, 10, 100, 100));
         mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
         final int origScreenWDp = task.getConfiguration().screenHeightDp;
         final int origScreenHDp = task.getConfiguration().screenHeightDp;
         t = new WindowContainerTransaction();
         // verify that setting config overrides on parent restricts children.
-        t.setScreenSizeDp(stack.mRemoteToken, origScreenWDp, origScreenHDp);
-        t.setBounds(task.mRemoteToken, new Rect(10, 10, 150, 200));
+        t.setScreenSizeDp(stack.mRemoteToken.toWindowContainerToken(), origScreenWDp, origScreenHDp);
+        t.setBounds(task.mRemoteToken.toWindowContainerToken(), new Rect(10, 10, 150, 200));
         mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
         assertEquals(origScreenHDp, task.getConfiguration().screenHeightDp);
         t = new WindowContainerTransaction();
-        t.setScreenSizeDp(stack.mRemoteToken, Configuration.SCREEN_WIDTH_DP_UNDEFINED,
-                Configuration.SCREEN_HEIGHT_DP_UNDEFINED);
+        t.setScreenSizeDp(stack.mRemoteToken.toWindowContainerToken(), SCREEN_WIDTH_DP_UNDEFINED,
+                SCREEN_HEIGHT_DP_UNDEFINED);
         mWm.mAtmService.mWindowOrganizerController.applyTransaction(t);
         assertNotEquals(origScreenHDp, task.getConfiguration().screenHeightDp);
     }
@@ -435,7 +438,7 @@
                 WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, mDisplayContent);
         assertEquals(mDisplayContent.getWindowingMode(), stack.getWindowingMode());
         WindowContainerTransaction wct = new WindowContainerTransaction();
-        wct.reparent(stack.mRemoteToken, info1.token, true /* onTop */);
+        wct.reparent(stack.mRemoteToken.toWindowContainerToken(), info1.token, true /* onTop */);
         mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
         assertEquals(info1.configuration.windowConfiguration.getWindowingMode(),
                 stack.getWindowingMode());
@@ -455,7 +458,7 @@
         assertEquals(newSize, stack.getBounds());
 
         wct = new WindowContainerTransaction();
-        wct.reparent(stack.mRemoteToken, null, true /* onTop */);
+        wct.reparent(stack.mRemoteToken.toWindowContainerToken(), null, true /* onTop */);
         mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
         assertEquals(mDisplayContent.getWindowingMode(), stack.getWindowingMode());
         infos = getTasksCreatedByOrganizer(mDisplayContent);
@@ -495,7 +498,7 @@
                 WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_STANDARD, mDisplayContent);
         Task task1 = WindowContainer.fromBinder(info1.token.asBinder()).asTask();
         WindowContainerTransaction wct = new WindowContainerTransaction();
-        wct.reparent(stack.mRemoteToken, info1.token, true /* onTop */);
+        wct.reparent(stack.mRemoteToken.toWindowContainerToken(), info1.token, true /* onTop */);
         mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
         assertTrue(called[0]);
         assertEquals(ACTIVITY_TYPE_STANDARD, lastReportedTiles.get(0).topActivityType);
@@ -505,7 +508,7 @@
         final ActivityStack stack2 = createTaskStackOnDisplay(
                 WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME, mDisplayContent);
         wct = new WindowContainerTransaction();
-        wct.reparent(stack2.mRemoteToken, info1.token, true /* onTop */);
+        wct.reparent(stack2.mRemoteToken.toWindowContainerToken(), info1.token, true /* onTop */);
         mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
         assertTrue(called[0]);
         assertEquals(ACTIVITY_TYPE_HOME, lastReportedTiles.get(0).topActivityType);
@@ -519,8 +522,8 @@
         lastReportedTiles.clear();
         called[0] = false;
         wct = new WindowContainerTransaction();
-        wct.reparent(stack.mRemoteToken, null, true /* onTop */);
-        wct.reparent(stack2.mRemoteToken, null, true /* onTop */);
+        wct.reparent(stack.mRemoteToken.toWindowContainerToken(), null, true /* onTop */);
+        wct.reparent(stack2.mRemoteToken.toWindowContainerToken(), null, true /* onTop */);
         mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
         assertTrue(called[0]);
         assertEquals(ACTIVITY_TYPE_UNDEFINED, lastReportedTiles.get(0).topActivityType);
@@ -569,8 +572,8 @@
 
         lastReportedTiles.clear();
         WindowContainerTransaction wct = new WindowContainerTransaction();
-        wct.reparent(stack.mRemoteToken, info1.token, true /* onTop */);
-        wct.reparent(stack2.mRemoteToken, info2.token, true /* onTop */);
+        wct.reparent(stack.mRemoteToken.toWindowContainerToken(), info1.token, true /* onTop */);
+        wct.reparent(stack2.mRemoteToken.toWindowContainerToken(), info2.token, true /* onTop */);
         mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
         assertFalse(lastReportedTiles.isEmpty());
         assertEquals(ACTIVITY_TYPE_STANDARD,
@@ -580,7 +583,7 @@
 
         lastReportedTiles.clear();
         wct = new WindowContainerTransaction();
-        wct.reparent(stack2.mRemoteToken, info1.token, false /* onTop */);
+        wct.reparent(stack2.mRemoteToken.toWindowContainerToken(), info1.token, false /* onTop */);
         mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
         assertFalse(lastReportedTiles.isEmpty());
         // Standard should still be on top of tile 1, so no change there
@@ -605,7 +608,7 @@
 
         lastReportedTiles.clear();
         wct = new WindowContainerTransaction();
-        wct.reorder(stack2.mRemoteToken, true /* onTop */);
+        wct.reorder(stack2.mRemoteToken.toWindowContainerToken(), true /* onTop */);
         mWm.mAtmService.mWindowOrganizerController.applyTransaction(wct);
         // Home should now be on top. No change occurs in second tile, so not reported
         assertEquals(1, lastReportedTiles.size());
@@ -641,7 +644,7 @@
         bse.setReady(id);
         // Since this task has no windows the sync is trivial and completes immediately.
         verify(transactionListener)
-            .transactionReady(anyInt(), any());
+            .onTransactionReady(anyInt(), any());
     }
 
     @Test
@@ -691,10 +694,10 @@
         bse.setReady(id);
         // Since we have a window we have to wait for it to draw to finish sync.
         verify(transactionListener, never())
-            .transactionReady(anyInt(), any());
+            .onTransactionReady(anyInt(), any());
         w.finishDrawing(null);
         verify(transactionListener)
-            .transactionReady(anyInt(), any());
+            .onTransactionReady(anyInt(), any());
     }
 
     @Test
@@ -716,7 +719,7 @@
         // Since the window was invisible, the Task had no visible leaves and the sync should
         // complete as soon as we call setReady.
         verify(transactionListener)
-            .transactionReady(anyInt(), any());
+            .onTransactionReady(anyInt(), any());
     }
 
     @Test
@@ -742,13 +745,13 @@
 
         // Since we have a child window we still shouldn't be done.
         verify(transactionListener, never())
-            .transactionReady(anyInt(), any());
+            .onTransactionReady(anyInt(), any());
         reset(transactionListener);
 
         child.finishDrawing(null);
         // Ah finally! Done
         verify(transactionListener)
-                .transactionReady(anyInt(), any());
+                .onTransactionReady(anyInt(), any());
     }
 
     class StubOrganizer extends ITaskOrganizer.Stub {
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskPersisterTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskPersisterTest.java
index 9fc1602..12ed3c2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskPersisterTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskPersisterTest.java
@@ -74,7 +74,6 @@
     }
 
     @Test
-    @FlakyTest(bugId = 131005232)
     public void testTaskIdsPersistence() {
         SparseBooleanArray taskIdsOnFile = new SparseBooleanArray();
         for (int i = 0; i < 100; i++) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java
index ea52d7d..93dcc91 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskPositionerTests.java
@@ -518,7 +518,6 @@
         assertEquals(expected, actual);
     }
 
-    @FlakyTest(bugId = 129492888)
     @Test
     public void testFinishingMovingWhenBinderDied() {
         spyOn(mWm.mTaskPositioningController);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java
index ca84932..75226b7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskPositioningControllerTests.java
@@ -90,7 +90,6 @@
         assertNull(mTarget.getDragWindowHandleLocked());
     }
 
-    @FlakyTest(bugId = 129507487)
     @Test
     public void testFinishPositioningWhenAppRequested() {
         assertFalse(mTarget.isPositioningLocked());
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
index 31d68a4..f76809b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskRecordTests.java
@@ -184,7 +184,6 @@
 
     /** Ensures that bounds on freeform stacks are not clipped. */
     @Test
-    @FlakyTest(bugId = 137879065)
     public void testAppBounds_FreeFormBounds() {
         final Rect freeFormBounds = new Rect(mParentBounds);
         freeFormBounds.offset(10, 10);
@@ -194,7 +193,6 @@
 
     /** Ensures that fully contained bounds are not clipped. */
     @Test
-    @FlakyTest(bugId = 137879065)
     public void testAppBounds_ContainedBounds() {
         final Rect insetBounds = new Rect(mParentBounds);
         insetBounds.inset(5, 5, 5, 5);
@@ -203,7 +201,6 @@
     }
 
     @Test
-    @FlakyTest(bugId = 137879065)
     public void testFitWithinBounds() {
         final Rect parentBounds = new Rect(10, 10, 200, 200);
         DisplayContent display = mService.mRootWindowContainer.getDefaultDisplay();
@@ -243,7 +240,6 @@
 
     /** Tests that the task bounds adjust properly to changes between FULLSCREEN and FREEFORM */
     @Test
-    @FlakyTest(bugId = 137879065)
     public void testBoundsOnModeChangeFreeformToFullscreen() {
         DisplayContent display = mService.mRootWindowContainer.getDefaultDisplay();
         ActivityStack stack = new StackBuilder(mRootWindowContainer).setDisplay(display)
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index e95ccab..820d381 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -449,7 +449,6 @@
     }
 
     @Test
-    @FlakyTest(bugId = 74078662)
     public void testLayoutSeqResetOnReparent() {
         final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
         app.mLayoutSeq = 1;
@@ -508,7 +507,6 @@
     }
 
     @Test
-    @FlakyTest(bugId = 74078662)
     public void testDisplayCutoutIsCalculatedRelativeToFrame() {
         final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
         WindowFrames wf = app.getWindowFrames();
diff --git a/startop/iorap/src/com/google/android/startop/iorap/EventSequenceValidator.java b/startop/iorap/src/com/google/android/startop/iorap/EventSequenceValidator.java
index 47bf148..6b19455 100644
--- a/startop/iorap/src/com/google/android/startop/iorap/EventSequenceValidator.java
+++ b/startop/iorap/src/com/google/android/startop/iorap/EventSequenceValidator.java
@@ -103,8 +103,7 @@
   @Override
   public void onIntentStarted(@NonNull Intent intent, long timestampNs) {
     if (state == State.UNKNOWN) {
-      logWarningWithStackTrace(
-          String.format("IntentStarted during UNKNOWN. " + intent));
+      logWarningWithStackTrace("IntentStarted during UNKNOWN. " + intent);
       incAccIntentStartedEvents();
       return;
     }
@@ -128,7 +127,7 @@
   @Override
   public void onIntentFailed() {
     if (state == State.UNKNOWN) {
-      logWarningWithStackTrace(String.format("onIntentFailed during UNKNOWN."));
+      logWarningWithStackTrace("onIntentFailed during UNKNOWN.");
       decAccIntentStartedEvents();
       return;
     }
@@ -147,8 +146,7 @@
   public void onActivityLaunched(@NonNull @ActivityRecordProto byte[] activity,
       @Temperature int temperature) {
     if (state == State.UNKNOWN) {
-      logWarningWithStackTrace(
-          String.format("onActivityLaunched during UNKNOWN."));
+      logWarningWithStackTrace("onActivityLaunched during UNKNOWN.");
       return;
     }
     if (state != State.INTENT_STARTED) {
@@ -165,8 +163,7 @@
   @Override
   public void onActivityLaunchCancelled(@Nullable @ActivityRecordProto byte[] activity) {
     if (state == State.UNKNOWN) {
-      logWarningWithStackTrace(
-          String.format("onActivityLaunchCancelled during UNKNOWN."));
+      logWarningWithStackTrace("onActivityLaunchCancelled during UNKNOWN.");
       decAccIntentStartedEvents();
       return;
     }
@@ -185,8 +182,7 @@
   public void onActivityLaunchFinished(@NonNull @ActivityRecordProto byte[] activity,
       long timestampNs) {
     if (state == State.UNKNOWN) {
-      logWarningWithStackTrace(
-          String.format("onActivityLaunchFinished during UNKNOWN."));
+      logWarningWithStackTrace("onActivityLaunchFinished during UNKNOWN.");
       decAccIntentStartedEvents();
       return;
     }
@@ -206,8 +202,7 @@
   public void onReportFullyDrawn(@NonNull @ActivityRecordProto byte[] activity,
       long timestampNs) {
     if (state == State.UNKNOWN) {
-      logWarningWithStackTrace(
-          String.format("onReportFullyDrawn during UNKNOWN."));
+      logWarningWithStackTrace("onReportFullyDrawn during UNKNOWN.");
       return;
     }
     if (state == State.INIT) {
@@ -237,8 +232,7 @@
 
   private void incAccIntentStartedEvents() {
     if (accIntentStartedEvents < 0) {
-      throw new AssertionError(
-          String.format("The number of unknowns cannot be negative"));
+      throw new AssertionError("The number of unknowns cannot be negative");
     }
     if (accIntentStartedEvents == 0) {
       state = State.UNKNOWN;
@@ -250,8 +244,7 @@
 
   private void decAccIntentStartedEvents() {
     if (accIntentStartedEvents <= 0) {
-      throw new AssertionError(
-          String.format("The number of unknowns cannot be negative"));
+      throw new AssertionError("The number of unknowns cannot be negative");
     }
     if(accIntentStartedEvents == 1) {
       state = State.INIT;
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 4e5be5c..9ae905d 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -12344,6 +12344,9 @@
     @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
     @IsMultiSimSupportedResult
     public int isMultiSimSupported() {
+        if (getSupportedModemCount() < 2) {
+            return TelephonyManager.MULTISIM_NOT_SUPPORTED_BY_HARDWARE;
+        }
         try {
             ITelephony service = getITelephony();
             if (service != null) {
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index 6fdc13e..d524299 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -493,6 +493,7 @@
     int RIL_REQUEST_GET_UICC_APPLICATIONS_ENABLEMENT = 209;
     int RIL_REQUEST_SET_SYSTEM_SELECTION_CHANNELS = 210;
     int RIL_REQUEST_GET_BARRING_INFO = 211;
+    int RIL_REQUEST_ENTER_SIM_DEPERSONALIZATION = 212;
 
     /* Responses begin */
     int RIL_RESPONSE_ACKNOWLEDGEMENT = 800;
diff --git a/tests/RollbackTest/Android.bp b/tests/RollbackTest/Android.bp
index 24623fb..2be4ae6 100644
--- a/tests/RollbackTest/Android.bp
+++ b/tests/RollbackTest/Android.bp
@@ -39,7 +39,7 @@
     name: "NetworkStagedRollbackTest",
     srcs: ["NetworkStagedRollbackTest/src/**/*.java"],
     libs: ["tradefed"],
-    static_libs: ["testng", "RollbackTestLib"],
+    static_libs: ["RollbackTestLib"],
     test_suites: ["general-tests"],
     test_config: "NetworkStagedRollbackTest.xml",
 }
diff --git a/tests/RollbackTest/NetworkStagedRollbackTest/src/com/android/tests/rollback/host/NetworkStagedRollbackTest.java b/tests/RollbackTest/NetworkStagedRollbackTest/src/com/android/tests/rollback/host/NetworkStagedRollbackTest.java
index f6dcff4..57c52f9 100644
--- a/tests/RollbackTest/NetworkStagedRollbackTest/src/com/android/tests/rollback/host/NetworkStagedRollbackTest.java
+++ b/tests/RollbackTest/NetworkStagedRollbackTest/src/com/android/tests/rollback/host/NetworkStagedRollbackTest.java
@@ -20,7 +20,6 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
-import static org.testng.Assert.assertThrows;
 
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
@@ -31,6 +30,7 @@
 import org.junit.runner.RunWith;
 
 import java.util.List;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Runs the network rollback tests.
@@ -83,11 +83,12 @@
 
             // Verify rollback was enabled
             runPhase("testNetworkFailedRollback_Phase2");
-            assertThrows(AssertionError.class, () -> runPhase("testNetworkFailedRollback_Phase3"));
-
+            // Wait for reboot to happen
+            assertTrue(getDevice().waitForDeviceNotAvailable(TimeUnit.MINUTES.toMillis(5)));
+            // Wait for reboot to complete and device to become available
             getDevice().waitForDeviceAvailable();
             // Verify rollback was executed after health check deadline
-            runPhase("testNetworkFailedRollback_Phase4");
+            runPhase("testNetworkFailedRollback_Phase3");
 
             List<String> watchdogEvents = mLogger.getWatchdogLoggingEvents();
             assertTrue(watchdogEventOccurred(watchdogEvents, ROLLBACK_INITIATE, null,
diff --git a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/NetworkStagedRollbackTest.java b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/NetworkStagedRollbackTest.java
index 35bc65a..7977b9a 100644
--- a/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/NetworkStagedRollbackTest.java
+++ b/tests/RollbackTest/RollbackTest/src/com/android/tests/rollback/NetworkStagedRollbackTest.java
@@ -119,15 +119,6 @@
 
     @Test
     public void testNetworkFailedRollback_Phase3() throws Exception {
-        // Sleep for > health check deadline (120s to trigger rollback + 120s to reboot)
-        // The device is expected to reboot during sleeping. This device method will fail and
-        // the host will catch the assertion. If reboot doesn't happen, the host will fail the
-        // assertion.
-        Thread.sleep(TimeUnit.SECONDS.toMillis(240));
-    }
-
-    @Test
-    public void testNetworkFailedRollback_Phase4() throws Exception {
         RollbackManager rm = RollbackUtils.getRollbackManager();
         assertThat(getUniqueRollbackInfoForPackage(rm.getRecentlyCommittedRollbacks(),
                 getNetworkStackPackageName())).isNotNull();
diff --git a/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerMultiWindowTest.java b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerMultiWindowTest.java
index d468076..b40d022 100644
--- a/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerMultiWindowTest.java
+++ b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerMultiWindowTest.java
@@ -17,10 +17,9 @@
 package com.android.test.taskembed;
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
-import static android.window.WindowOrganizer.TaskOrganizer;
 
-import android.app.Activity;
 import android.app.ActivityManager;
+import android.app.Activity;
 import android.app.ActivityOptions;
 import android.content.Context;
 import android.content.Intent;
@@ -35,10 +34,12 @@
 import android.view.SurfaceHolder;
 import android.view.View;
 import android.view.ViewGroup;
-import android.widget.LinearLayout;
 import android.window.ITaskOrganizer;
 import android.window.IWindowContainerTransactionCallback;
+import android.window.TaskOrganizer;
 import android.window.WindowContainerTransaction;
+import android.widget.LinearLayout;
+import android.window.WindowContainerTransactionCallback;
 import android.window.WindowOrganizer;
 
 public class TaskOrganizerMultiWindowTest extends Activity {
@@ -97,7 +98,7 @@
     class ResizingTaskView extends TaskView {
         final Intent mIntent;
         boolean launched = false;
-        ResizingTaskView(Context c, ITaskOrganizer o, int windowingMode, Intent i) {
+        ResizingTaskView(Context c, TaskOrganizer o, int windowingMode, Intent i) {
             super(c, o, windowingMode);
             mIntent = i;
         }
@@ -116,7 +117,7 @@
             final WindowContainerTransaction wct = new WindowContainerTransaction();
             wct.setBounds(mWc, new Rect(0, 0, width, height));
             try {
-                WindowOrganizer.applySyncTransaction(wct, mOrganizer.mTransactionCallback);
+                mOrganizer.applySyncTransaction(wct, mOrganizer.mTransactionCallback);
             } catch (Exception e) {
                 // Oh well
             }
@@ -127,14 +128,13 @@
     TaskView mTaskView2;
     boolean gotFirstTask = false;
 
-    class Organizer extends ITaskOrganizer.Stub {
+    class Organizer extends TaskOrganizer {
         private int receivedTransactions = 0;
         SurfaceControl.Transaction mergedTransaction = new SurfaceControl.Transaction();
-        IWindowContainerTransactionCallback mTransactionCallback =
-                new IWindowContainerTransactionCallback() {
+        WindowContainerTransactionCallback mTransactionCallback =
+                new WindowContainerTransactionCallback() {
             @Override
-            public void transactionReady(int id, SurfaceControl.Transaction t)
-                    throws RemoteException {
+            public void onTransactionReady(int id, SurfaceControl.Transaction t) {
                 mergedTransaction.merge(t);
                 receivedTransactions++;
                 if (receivedTransactions == 2) {
@@ -142,11 +142,6 @@
                     receivedTransactions = 0;
                 }
             }
-
-            @Override
-            public IBinder asBinder() {
-                return null;
-            }
         };
 
         @Override
@@ -158,14 +153,6 @@
                 mTaskView2.reparentTask(ti.token);
             }
         }
-        public void onTaskVanished(ActivityManager.RunningTaskInfo ti) {
-        }
-        @Override
-        public void onTaskInfoChanged(ActivityManager.RunningTaskInfo info) {
-        }
-        @Override
-        public void onBackPressedOnTaskRoot(ActivityManager.RunningTaskInfo taskInfo) {
-        }
     }
 
     Organizer mOrganizer = new Organizer();
@@ -174,10 +161,7 @@
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
 
-        try {
-            TaskOrganizer.registerOrganizer(mOrganizer, WINDOWING_MODE_MULTI_WINDOW);
-        } catch (Exception e) {
-        }
+        mOrganizer.registerOrganizer(WINDOWING_MODE_MULTI_WINDOW);
 
         mTaskView1 = new ResizingTaskView(this, mOrganizer, WINDOWING_MODE_MULTI_WINDOW,
                 makeSettingsIntent());
diff --git a/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java
index a589d95..2a1aa2e 100644
--- a/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java
+++ b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskOrganizerPipTest.java
@@ -17,7 +17,6 @@
 package com.android.test.taskembed;
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.window.WindowOrganizer.TaskOrganizer;
 
 import android.app.ActivityManager;
 import android.app.Service;
@@ -25,11 +24,10 @@
 import android.graphics.Rect;
 import android.os.IBinder;
 import android.view.ViewGroup;
+import android.window.TaskOrganizer;
+import android.window.WindowContainerTransaction;
 import android.view.WindowManager;
 import android.widget.FrameLayout;
-import android.window.ITaskOrganizer;
-import android.window.WindowContainerTransaction;
-import android.window.WindowOrganizer;
 
 public class TaskOrganizerPipTest extends Service {
     static final int PIP_WIDTH  = 640;
@@ -37,23 +35,13 @@
 
     TaskView mTaskView;
 
-    class Organizer extends ITaskOrganizer.Stub {
+    class Organizer extends TaskOrganizer {
         public void onTaskAppeared(ActivityManager.RunningTaskInfo ti) {
             mTaskView.reparentTask(ti.token);
 
             final WindowContainerTransaction wct = new WindowContainerTransaction();
             wct.scheduleFinishEnterPip(ti.token, new Rect(0, 0, PIP_WIDTH, PIP_HEIGHT));
-            try {
-                WindowOrganizer.applyTransaction(wct);
-            } catch (Exception e) {
-            }
-        }
-        public void onTaskVanished(ActivityManager.RunningTaskInfo ti) {
-        }
-        public void onTaskInfoChanged(ActivityManager.RunningTaskInfo info) {
-        }
-        @Override
-        public void onBackPressedOnTaskRoot(ActivityManager.RunningTaskInfo taskInfo) {
+            applyTransaction(wct);
         }
     }
 
@@ -68,10 +56,7 @@
     public void onCreate() {
         super.onCreate();
 
-        try {
-            TaskOrganizer.registerOrganizer(mOrganizer, WINDOWING_MODE_PINNED);
-        } catch (Exception e) {
-        }
+        mOrganizer.registerOrganizer(WINDOWING_MODE_PINNED);
 
         final WindowManager.LayoutParams wlp = new WindowManager.LayoutParams();
         wlp.setTitle("TaskOrganizerPipTest");
diff --git a/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskView.java b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskView.java
index 438a062..03615f3 100644
--- a/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskView.java
+++ b/tests/TaskOrganizerTest/src/com/android/test/taskembed/TaskView.java
@@ -18,7 +18,8 @@
 
 import android.app.ActivityTaskManager;
 import android.content.Context;
-import android.window.IWindowContainer;
+import android.window.TaskOrganizer;
+import android.window.WindowContainerToken;
 import android.view.SurfaceControl;
 import android.view.SurfaceHolder;
 import android.view.SurfaceView;
@@ -29,14 +30,14 @@
  * after it's Surface is ready.
  */
 class TaskView extends SurfaceView implements SurfaceHolder.Callback {
-    final ITaskOrganizer mTaskOrganizer;
+    final TaskOrganizer mTaskOrganizer;
     final int mWindowingMode;
-    IWindowContainer mWc;
+    WindowContainerToken mWc;
 
     boolean mSurfaceCreated = false;
     boolean mNeedsReparent;
 
-    TaskView(Context c, ITaskOrganizer o, int windowingMode) {
+    TaskView(Context c, TaskOrganizer o, int windowingMode) {
         super(c);
         getHolder().addCallback(this);
         setZOrderOnTop(true);
@@ -62,7 +63,7 @@
     public void surfaceDestroyed(SurfaceHolder holder) {
     }
 
-    void reparentTask(IWindowContainer wc) {
+    void reparentTask(WindowContainerToken wc) {
         mWc = wc;
         if (mSurfaceCreated == false) {
             mNeedsReparent = true;
diff --git a/tests/net/common/java/android/net/LinkPropertiesTest.java b/tests/net/common/java/android/net/LinkPropertiesTest.java
index 2b5720a..8de27e8 100644
--- a/tests/net/common/java/android/net/LinkPropertiesTest.java
+++ b/tests/net/common/java/android/net/LinkPropertiesTest.java
@@ -445,14 +445,20 @@
         // Check comparisons work.
         LinkProperties lp2 = new LinkProperties(lp);
         assertAllRoutesHaveInterface("wlan0", lp2);
-        assertEquals(0, lp.compareAllRoutes(lp2).added.size());
-        assertEquals(0, lp.compareAllRoutes(lp2).removed.size());
+        // LinkProperties#compareAllRoutes exists both in R and before R, but the return type
+        // changed in R, so a test compiled with the R version of LinkProperties cannot run on Q.
+        if (isAtLeastR()) {
+            assertEquals(0, lp.compareAllRoutes(lp2).added.size());
+            assertEquals(0, lp.compareAllRoutes(lp2).removed.size());
+        }
 
         lp2.setInterfaceName("p2p0");
         assertAllRoutesHaveInterface("p2p0", lp2);
         assertAllRoutesNotHaveInterface("wlan0", lp2);
-        assertEquals(3, lp.compareAllRoutes(lp2).added.size());
-        assertEquals(3, lp.compareAllRoutes(lp2).removed.size());
+        if (isAtLeastR()) {
+            assertEquals(3, lp.compareAllRoutes(lp2).added.size());
+            assertEquals(3, lp.compareAllRoutes(lp2).removed.size());
+        }
 
         // Remove route with incorrect interface, no route removed.
         lp.removeRoute(new RouteInfo(prefix2, null, null));
@@ -480,6 +486,8 @@
         assertEquals(1, rmnet0.getLinkAddresses().size());
         assertEquals(1, rmnet0.getAllAddresses().size());
         assertEquals(1, rmnet0.getAllLinkAddresses().size());
+        assertEquals(1, rmnet0.getAllInterfaceNames().size());
+        assertEquals("rmnet0", rmnet0.getAllInterfaceNames().get(0));
 
         rmnet0.addStackedLink(clat4);
         assertEquals(1, rmnet0.getStackedLinks().size());
@@ -487,6 +495,9 @@
         assertEquals(1, rmnet0.getLinkAddresses().size());
         assertEquals(2, rmnet0.getAllAddresses().size());
         assertEquals(2, rmnet0.getAllLinkAddresses().size());
+        assertEquals(2, rmnet0.getAllInterfaceNames().size());
+        assertEquals("rmnet0", rmnet0.getAllInterfaceNames().get(0));
+        assertEquals("clat4", rmnet0.getAllInterfaceNames().get(1));
 
         rmnet0.addStackedLink(clat4);
         assertEquals(1, rmnet0.getStackedLinks().size());
@@ -494,6 +505,9 @@
         assertEquals(1, rmnet0.getLinkAddresses().size());
         assertEquals(2, rmnet0.getAllAddresses().size());
         assertEquals(2, rmnet0.getAllLinkAddresses().size());
+        assertEquals(2, rmnet0.getAllInterfaceNames().size());
+        assertEquals("rmnet0", rmnet0.getAllInterfaceNames().get(0));
+        assertEquals("clat4", rmnet0.getAllInterfaceNames().get(1));
 
         assertEquals(0, clat4.getStackedLinks().size());
 
@@ -513,6 +527,8 @@
         assertEquals(1, rmnet0.getLinkAddresses().size());
         assertEquals(1, rmnet0.getAllAddresses().size());
         assertEquals(1, rmnet0.getAllLinkAddresses().size());
+        assertEquals(1, rmnet0.getAllInterfaceNames().size());
+        assertEquals("rmnet0", rmnet0.getAllInterfaceNames().get(0));
 
         assertFalse(rmnet0.removeStackedLink("clat4"));
     }
@@ -936,7 +952,7 @@
 
     }
 
-    @Test
+    @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
     public void testCompareResult() {
         // Either adding or removing items
         compareResult(Arrays.asList(1, 2, 3, 4), Arrays.asList(1),
@@ -1197,4 +1213,48 @@
         lp.clear();
         assertNull(lp.getCaptivePortalData());
     }
+
+    private LinkProperties makeIpv4LinkProperties() {
+        final LinkProperties linkProperties = new LinkProperties();
+        linkProperties.setInterfaceName(NAME);
+        linkProperties.addLinkAddress(LINKADDRV4);
+        linkProperties.addDnsServer(DNS1);
+        linkProperties.addRoute(new RouteInfo(GATEWAY1));
+        linkProperties.addRoute(new RouteInfo(GATEWAY2));
+        return linkProperties;
+    }
+
+    private LinkProperties makeIpv6LinkProperties() {
+        final LinkProperties linkProperties = new LinkProperties();
+        linkProperties.setInterfaceName(NAME);
+        linkProperties.addLinkAddress(LINKADDRV6);
+        linkProperties.addDnsServer(DNS6);
+        linkProperties.addRoute(new RouteInfo(GATEWAY61));
+        linkProperties.addRoute(new RouteInfo(GATEWAY62));
+        return linkProperties;
+    }
+
+    @Test
+    public void testHasIpv4DefaultRoute() {
+        final LinkProperties Ipv4 = makeIpv4LinkProperties();
+        assertTrue(Ipv4.hasIpv4DefaultRoute());
+        final LinkProperties Ipv6 = makeIpv6LinkProperties();
+        assertFalse(Ipv6.hasIpv4DefaultRoute());
+    }
+
+    @Test
+    public void testHasIpv4DnsServer() {
+        final LinkProperties Ipv4 = makeIpv4LinkProperties();
+        assertTrue(Ipv4.hasIpv4DnsServer());
+        final LinkProperties Ipv6 = makeIpv6LinkProperties();
+        assertFalse(Ipv6.hasIpv4DnsServer());
+    }
+
+    @Test
+    public void testHasIpv6DnsServer() {
+        final LinkProperties Ipv4 = makeIpv4LinkProperties();
+        assertFalse(Ipv4.hasIpv6DnsServer());
+        final LinkProperties Ipv6 = makeIpv6LinkProperties();
+        assertTrue(Ipv6.hasIpv6DnsServer());
+    }
 }
diff --git a/tools/stats_log_api_gen/java_writer.cpp b/tools/stats_log_api_gen/java_writer.cpp
index 3eabb14..54c5b90 100644
--- a/tools/stats_log_api_gen/java_writer.cpp
+++ b/tools/stats_log_api_gen/java_writer.cpp
@@ -39,6 +39,15 @@
     return 0;
 }
 
+static void write_java_annotation_constants(FILE* out) {
+    fprintf(out, "    // Annotation constants.\n");
+
+    for (const auto& [id, name] : ANNOTATION_ID_CONSTANTS) {
+        fprintf(out, "    public static final byte %s = %hhu;\n", name.c_str(), id);
+    }
+    fprintf(out, "\n");
+}
+
 static void write_annotations(FILE* out, int argIndex,
                               const FieldNumberToAtomDeclSet& fieldNumberToAtomDeclSet) {
     FieldNumberToAtomDeclSet::const_iterator fieldNumberToAtomDeclSetIt =
@@ -48,32 +57,28 @@
     }
     const AtomDeclSet& atomDeclSet = fieldNumberToAtomDeclSetIt->second;
     for (const shared_ptr<AtomDecl>& atomDecl : atomDeclSet) {
-        fprintf(out, "        if (code == %d) {\n", atomDecl->code);
+        const string atomConstant = make_constant_name(atomDecl->name);
+        fprintf(out, "        if (%s == code) {\n", atomConstant.c_str());
         const AnnotationSet& annotations = atomDecl->fieldNumberToAnnotations.at(argIndex);
         int resetState = -1;
         int defaultState = -1;
         for (const shared_ptr<Annotation>& annotation : annotations) {
-            // TODO(b/151786433): Write atom constant name instead of atom id literal.
+            const string& annotationConstant =
+                    ANNOTATION_ID_CONSTANTS.at(annotation->annotationId);
             switch (annotation->type) {
-                // TODO(b/151776731): Check for reset state annotation and only include
-                // reset state when field value == default state annotation value.
                 case ANNOTATION_TYPE_INT:
-                    // TODO(b/151786433): Write annotation constant name instead of
-                    // annotation id literal.
                     if (ANNOTATION_ID_RESET_STATE == annotation->annotationId) {
                         resetState = annotation->value.intValue;
                     } else if (ANNOTATION_ID_DEFAULT_STATE == annotation->annotationId) {
                         defaultState = annotation->value.intValue;
                     } else {
-                        fprintf(out, "            builder.addIntAnnotation((byte) %d, %d);\n",
-                                annotation->annotationId, annotation->value.intValue);
+                        fprintf(out, "            builder.addIntAnnotation(%s, %d);\n",
+                                annotationConstant.c_str(), annotation->value.intValue);
                     }
                     break;
                 case ANNOTATION_TYPE_BOOL:
-                    // TODO(b/151786433): Write annotation constant name instead of
-                    // annotation id literal.
-                    fprintf(out, "            builder.addBooleanAnnotation((byte) %d, %s);\n",
-                            annotation->annotationId,
+                    fprintf(out, "            builder.addBooleanAnnotation(%s, %s);\n",
+                            annotationConstant.c_str(),
                             annotation->value.boolValue ? "true" : "false");
                     break;
                 default:
@@ -81,9 +86,11 @@
             }
         }
         if (defaultState != -1 && resetState != -1) {
+            const string& annotationConstant =
+                    ANNOTATION_ID_CONSTANTS.at(ANNOTATION_ID_RESET_STATE);
             fprintf(out, "            if (arg%d == %d) {\n", argIndex, resetState);
-            fprintf(out, "                builder.addIntAnnotation((byte) %d, %d);\n",
-                    ANNOTATION_ID_RESET_STATE, defaultState);
+            fprintf(out, "                builder.addIntAnnotation(%s, %d);\n",
+                    annotationConstant.c_str(), defaultState);
             fprintf(out, "            }\n");
         }
         fprintf(out, "        }\n");
@@ -311,6 +318,7 @@
 
     write_java_atom_codes(out, atoms);
     write_java_enum_values(out, atoms);
+    write_java_annotation_constants(out);
 
     int errors = 0;
 
diff --git a/tools/stats_log_api_gen/native_writer.cpp b/tools/stats_log_api_gen/native_writer.cpp
index c0d73fa..d8db620 100644
--- a/tools/stats_log_api_gen/native_writer.cpp
+++ b/tools/stats_log_api_gen/native_writer.cpp
@@ -21,6 +21,16 @@
 namespace android {
 namespace stats_log_api_gen {
 
+static void write_native_annotation_constants(FILE* out) {
+    fprintf(out, "// Annotation constants.\n");
+
+    for (const auto& [id, name] : ANNOTATION_ID_CONSTANTS) {
+        fprintf(out, "const uint8_t %s = %hhu;\n", name.c_str(), id);
+    }
+    fprintf(out, "\n");
+}
+
+
 static void write_annotations(FILE* out, int argIndex,
                               const FieldNumberToAtomDeclSet& fieldNumberToAtomDeclSet,
                               const string& methodPrefix, const string& methodSuffix) {
@@ -31,33 +41,31 @@
     }
     const AtomDeclSet& atomDeclSet = fieldNumberToAtomDeclSetIt->second;
     for (const shared_ptr<AtomDecl>& atomDecl : atomDeclSet) {
-        fprintf(out, "    if (code == %d) {\n", atomDecl->code);
+        const string atomConstant = make_constant_name(atomDecl->name);
+        fprintf(out, "    if (%s == code) {\n", atomConstant.c_str());
         const AnnotationSet& annotations = atomDecl->fieldNumberToAnnotations.at(argIndex);
         int resetState = -1;
         int defaultState = -1;
         for (const shared_ptr<Annotation>& annotation : annotations) {
-            // TODO(b/151786433): Write atom constant name instead of atom id literal.
+            const string& annotationConstant =
+                    ANNOTATION_ID_CONSTANTS.at(annotation->annotationId);
             switch (annotation->type) {
-                // TODO(b/151776731): Check for reset state annotation and only include
-                // reset state when field value == default state annotation value.
                 case ANNOTATION_TYPE_INT:
-                    // TODO(b/151786433): Write annotation constant name instead of
-                    // annotation id literal.
                     if (ANNOTATION_ID_RESET_STATE == annotation->annotationId) {
                         resetState = annotation->value.intValue;
                     } else if (ANNOTATION_ID_DEFAULT_STATE == annotation->annotationId) {
                         defaultState = annotation->value.intValue;
                     } else {
-                        fprintf(out, "        %saddInt32Annotation(%s%d, %d);\n",
+                        fprintf(out, "        %saddInt32Annotation(%s%s, %d);\n",
                                 methodPrefix.c_str(), methodSuffix.c_str(),
-                                annotation->annotationId, annotation->value.intValue);
+                                annotationConstant.c_str(), annotation->value.intValue);
                     }
                     break;
                 case ANNOTATION_TYPE_BOOL:
                     // TODO(b/151786433): Write annotation constant name instead of
                     // annotation id literal.
-                    fprintf(out, "        %saddBoolAnnotation(%s%d, %s);\n", methodPrefix.c_str(),
-                            methodSuffix.c_str(), annotation->annotationId,
+                    fprintf(out, "        %saddBoolAnnotation(%s%s, %s);\n", methodPrefix.c_str(),
+                            methodSuffix.c_str(), annotationConstant.c_str(),
                             annotation->value.boolValue ? "true" : "false");
                     break;
                 default:
@@ -65,9 +73,11 @@
             }
         }
         if (defaultState != -1 && resetState != -1) {
+            const string& annotationConstant =
+                    ANNOTATION_ID_CONSTANTS.at(ANNOTATION_ID_RESET_STATE);
             fprintf(out, "        if (arg%d == %d) {\n", argIndex, resetState);
-            fprintf(out, "            %saddInt32Annotation(%s%d, %d);\n", methodPrefix.c_str(),
-                    methodSuffix.c_str(), ANNOTATION_ID_RESET_STATE, defaultState);
+            fprintf(out, "            %saddInt32Annotation(%s%s, %d);\n", methodPrefix.c_str(),
+                    methodSuffix.c_str(), annotationConstant.c_str(), defaultState);
             fprintf(out, "        }\n");
         }
         fprintf(out, "    }\n");
@@ -314,6 +324,8 @@
         }
     }
 
+    write_native_annotation_constants(out);
+
     fprintf(out, "struct BytesField {\n");
     fprintf(out,
             "  BytesField(char const* array, size_t len) : arg(array), "
diff --git a/tools/stats_log_api_gen/utils.h b/tools/stats_log_api_gen/utils.h
index 57b6f62..1f64442 100644
--- a/tools/stats_log_api_gen/utils.h
+++ b/tools/stats_log_api_gen/utils.h
@@ -38,6 +38,14 @@
 const int JAVA_MODULE_REQUIRES_ATTRIBUTION = 0x02;
 const int JAVA_MODULE_REQUIRES_KEY_VALUE_PAIRS = 0x04;
 
+const map<unsigned char, string> ANNOTATION_ID_CONSTANTS = {
+    { ANNOTATION_ID_IS_UID, "ANNOTATION_ID_IS_UID" },
+    { ANNOTATION_ID_TRUNCATE_TIMESTAMP, "ANNOTATION_ID_TRUNCATE_TIMESTAMP" },
+    { ANNOTATION_ID_STATE_OPTION, "ANNOTATION_ID_STATE_OPTION" },
+    { ANNOTATION_ID_RESET_STATE, "ANNOTATION_ID_RESET_STATE" },
+    { ANNOTATION_ID_STATE_NESTED, "ANNOTATION_ID_STATE_NESTED" }
+};
+
 string make_constant_name(const string& str);
 
 const char* cpp_type_name(java_type_t type);