Merge "Set min height for BiometricPrompt password field"
diff --git a/Android.bp b/Android.bp
index 5796fb1..12bc906 100644
--- a/Android.bp
+++ b/Android.bp
@@ -653,6 +653,33 @@
     output_extension: "srcjar",
 }
 
+gensrcs {
+    name: "framework-cppstream-protos",
+    depfile: true,
+
+    tools: [
+        "aprotoc",
+        "protoc-gen-cppstream",
+    ],
+
+    cmd: "mkdir -p $(genDir) " +
+        "&& $(location aprotoc) " +
+        "  --plugin=$(location protoc-gen-cppstream) " +
+        "  --dependency_out=$(depfile) " +
+        "  --cppstream_out=$(genDir) " +
+        "  -Iexternal/protobuf/src " +
+        "  -I . " +
+        "  $(in)",
+
+    srcs: [
+        ":ipconnectivity-proto-src",
+        "core/proto/**/*.proto",
+        "libs/incident/**/*.proto",
+    ],
+
+    output_extension: "proto.h",
+}
+
 filegroup {
     name: "framework-annotations",
     srcs: [
@@ -1010,43 +1037,6 @@
     },
 }
 
-gensrcs {
-    name: "gen-platform-proto-constants",
-    depfile: true,
-
-    tools: [
-        "aprotoc",
-        "protoc-gen-cppstream",
-    ],
-
-    srcs: [
-        "core/proto/android/os/backtrace.proto",
-        "core/proto/android/os/batterytype.proto",
-        "core/proto/android/os/cpufreq.proto",
-        "core/proto/android/os/cpuinfo.proto",
-        "core/proto/android/os/data.proto",
-        "core/proto/android/os/kernelwake.proto",
-        "core/proto/android/os/pagetypeinfo.proto",
-        "core/proto/android/os/procrank.proto",
-        "core/proto/android/os/ps.proto",
-        "core/proto/android/os/system_properties.proto",
-        "core/proto/android/util/event_log_tags.proto",
-        "core/proto/android/util/log.proto",
-    ],
-
-    // Append protoc-gen-cppstream tool's PATH otherwise aprotoc can't find the plugin tool
-    cmd: "mkdir -p $(genDir) " +
-        "&& $(location aprotoc) " +
-        "  --plugin=$(location protoc-gen-cppstream) " +
-        "  --dependency_out=$(depfile) " +
-        "  --cppstream_out=$(genDir) " +
-        "  -Iexternal/protobuf/src " +
-        "  -I . " +
-        "  $(in)",
-
-    output_extension: "proto.h",
-}
-
 
 subdirs = [
     "cmds/*",
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
index 0bb07ca..088cadb 100644
--- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
+++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java
@@ -1287,7 +1287,7 @@
          * {@link #setPeriodic(long)} or {@link #setPersisted(boolean)}.  To continually monitor
          * for content changes, you need to schedule a new JobInfo observing the same URIs
          * before you finish execution of the JobService handling the most recent changes.
-         * Following this pattern will ensure you do not lost any content changes: while your
+         * Following this pattern will ensure you do not lose any content changes: while your
          * job is running, the system will continue monitoring for content changes, and propagate
          * any it sees over to the next job you schedule.</p>
          *
diff --git a/apex/sdkextensions/testing/Android.bp b/apex/sdkextensions/testing/Android.bp
index e6451cc..f2f5b32 100644
--- a/apex/sdkextensions/testing/Android.bp
+++ b/apex/sdkextensions/testing/Android.bp
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-apex {
+apex_test {
     name: "test_com.android.sdkext",
     visibility: [ "//system/apex/tests" ],
     defaults: ["com.android.sdkext-defaults"],
diff --git a/apex/statsd/framework/java/android/app/StatsManager.java b/apex/statsd/framework/java/android/app/StatsManager.java
index a1de330..411482b 100644
--- a/apex/statsd/framework/java/android/app/StatsManager.java
+++ b/apex/statsd/framework/java/android/app/StatsManager.java
@@ -476,7 +476,7 @@
     /**
      * Registers a callback for an atom when that atom is to be pulled. The stats service will
      * invoke pullData in the callback when the stats service determines that this atom needs to be
-     * pulled.
+     * pulled. This method should not be called by third-party apps.
      *
      * @param atomTag           The tag of the atom for this puller callback.
      * @param metadata          Optional metadata specifying the timeout, cool down time, and
@@ -485,6 +485,7 @@
      * @param executor          The executor in which to run the callback.
      *
      */
+    @RequiresPermission(android.Manifest.permission.REGISTER_STATS_PULL_ATOM)
     public void registerPullAtomCallback(int atomTag, @Nullable PullAtomMetadata metadata,
             @NonNull @CallbackExecutor Executor executor,
             @NonNull StatsPullAtomCallback callback) {
@@ -510,11 +511,12 @@
 
     /**
      * Unregisters a callback for an atom when that atom is to be pulled. Note that any ongoing
-     * pulls will still occur.
+     * pulls will still occur. This method should not be called by third-party apps.
      *
      * @param atomTag           The tag of the atom of which to unregister
      *
      */
+    @RequiresPermission(android.Manifest.permission.REGISTER_STATS_PULL_ATOM)
     public void unregisterPullAtomCallback(int atomTag) {
         synchronized (sLock) {
             try {
diff --git a/core/java/android/util/StatsLog.java b/apex/statsd/framework/java/android/util/StatsLog.java
similarity index 100%
rename from core/java/android/util/StatsLog.java
rename to apex/statsd/framework/java/android/util/StatsLog.java
diff --git a/apex/statsd/service/java/com/android/server/stats/StatsCompanion.java b/apex/statsd/service/java/com/android/server/stats/StatsCompanion.java
index 4383b50..4495dc9 100644
--- a/apex/statsd/service/java/com/android/server/stats/StatsCompanion.java
+++ b/apex/statsd/service/java/com/android/server/stats/StatsCompanion.java
@@ -38,11 +38,15 @@
     private static final String TAG = "StatsCompanion";
     private static final boolean DEBUG = false;
 
-    static void enforceStatsCompanionPermission(Context context) {
+    private static final int AID_STATSD = 1066;
+
+    static void enforceStatsdCallingUid() {
         if (Binder.getCallingPid() == Process.myPid()) {
             return;
         }
-        context.enforceCallingPermission(android.Manifest.permission.STATSCOMPANION, null);
+        if (Binder.getCallingUid() != AID_STATSD) {
+            throw new SecurityException("Not allowed to access StatsCompanion");
+        }
     }
 
     /**
@@ -114,7 +118,7 @@
 
         @Override
         public void sendDataBroadcast(long lastReportTimeNs) {
-            enforceStatsCompanionPermission(mContext);
+            enforceStatsdCallingUid();
             Intent intent = new Intent();
             intent.putExtra(EXTRA_LAST_REPORT_TIME, lastReportTimeNs);
             try {
@@ -126,7 +130,7 @@
 
         @Override
         public void sendActiveConfigsChangedBroadcast(long[] configIds) {
-            enforceStatsCompanionPermission(mContext);
+            enforceStatsdCallingUid();
             Intent intent = new Intent();
             intent.putExtra(StatsManager.EXTRA_STATS_ACTIVE_CONFIG_KEYS, configIds);
             try {
@@ -142,7 +146,7 @@
         @Override
         public void sendSubscriberBroadcast(long configUid, long configId, long subscriptionId,
                 long subscriptionRuleId, String[] cookies, StatsDimensionsValue dimensionsValue) {
-            enforceStatsCompanionPermission(mContext);
+            enforceStatsdCallingUid();
             Intent intent =
                     new Intent()
                             .putExtra(StatsManager.EXTRA_STATS_CONFIG_UID, configUid)
diff --git a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java
index 3e9a488..a735cb8 100644
--- a/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java
+++ b/apex/statsd/service/java/com/android/server/stats/StatsCompanionService.java
@@ -398,7 +398,7 @@
 
     @Override // Binder call
     public void setAnomalyAlarm(long timestampMs) {
-        StatsCompanion.enforceStatsCompanionPermission(mContext);
+        StatsCompanion.enforceStatsdCallingUid();
         if (DEBUG) Slog.d(TAG, "Setting anomaly alarm for " + timestampMs);
         final long callingToken = Binder.clearCallingIdentity();
         try {
@@ -414,7 +414,7 @@
 
     @Override // Binder call
     public void cancelAnomalyAlarm() {
-        StatsCompanion.enforceStatsCompanionPermission(mContext);
+        StatsCompanion.enforceStatsdCallingUid();
         if (DEBUG) Slog.d(TAG, "Cancelling anomaly alarm");
         final long callingToken = Binder.clearCallingIdentity();
         try {
@@ -426,7 +426,7 @@
 
     @Override // Binder call
     public void setAlarmForSubscriberTriggering(long timestampMs) {
-        StatsCompanion.enforceStatsCompanionPermission(mContext);
+        StatsCompanion.enforceStatsdCallingUid();
         if (DEBUG) {
             Slog.d(TAG,
                     "Setting periodic alarm in about " + (timestampMs
@@ -445,7 +445,7 @@
 
     @Override // Binder call
     public void cancelAlarmForSubscriberTriggering() {
-        StatsCompanion.enforceStatsCompanionPermission(mContext);
+        StatsCompanion.enforceStatsdCallingUid();
         if (DEBUG) {
             Slog.d(TAG, "Cancelling periodic alarm");
         }
@@ -459,7 +459,7 @@
 
     @Override // Binder call
     public void setPullingAlarm(long nextPullTimeMs) {
-        StatsCompanion.enforceStatsCompanionPermission(mContext);
+        StatsCompanion.enforceStatsdCallingUid();
         if (DEBUG) {
             Slog.d(TAG, "Setting pulling alarm in about "
                     + (nextPullTimeMs - SystemClock.elapsedRealtime()));
@@ -477,7 +477,7 @@
 
     @Override // Binder call
     public void cancelPullingAlarm() {
-        StatsCompanion.enforceStatsCompanionPermission(mContext);
+        StatsCompanion.enforceStatsdCallingUid();
         if (DEBUG) {
             Slog.d(TAG, "Cancelling pulling alarm");
         }
@@ -491,7 +491,7 @@
 
     @Override // Binder call
     public void statsdReady() {
-        StatsCompanion.enforceStatsCompanionPermission(mContext);
+        StatsCompanion.enforceStatsdCallingUid();
         if (DEBUG) {
             Slog.d(TAG, "learned that statsdReady");
         }
@@ -503,7 +503,7 @@
 
     @Override
     public void triggerUidSnapshot() {
-        StatsCompanion.enforceStatsCompanionPermission(mContext);
+        StatsCompanion.enforceStatsdCallingUid();
         synchronized (sStatsdLock) {
             final long token = Binder.clearCallingIdentity();
             try {
@@ -518,7 +518,7 @@
 
     @Override // Binder call
     public boolean checkPermission(String permission, int pid, int uid) {
-        StatsCompanion.enforceStatsCompanionPermission(mContext);
+        StatsCompanion.enforceStatsdCallingUid();
         return mContext.checkPermission(permission, pid, uid) == PackageManager.PERMISSION_GRANTED;
     }
 
diff --git a/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java b/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java
index 04d8b00..c1dc584 100644
--- a/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java
+++ b/apex/statsd/service/java/com/android/server/stats/StatsManagerService.java
@@ -171,8 +171,8 @@
     @Override
     public void registerPullAtomCallback(int atomTag, long coolDownNs, long timeoutNs,
             int[] additiveFields, IPullAtomCallback pullerCallback) {
+        enforceRegisterStatsPullAtomPermission();
         int callingUid = Binder.getCallingUid();
-        final long token = Binder.clearCallingIdentity();
         PullerKey key = new PullerKey(callingUid, atomTag);
         PullerValue val = new PullerValue(coolDownNs, timeoutNs, additiveFields, pullerCallback);
 
@@ -187,6 +187,7 @@
             return;
         }
 
+        final long token = Binder.clearCallingIdentity();
         try {
             statsd.registerPullAtomCallback(
                     callingUid, atomTag, coolDownNs, timeoutNs, additiveFields, pullerCallback);
@@ -199,8 +200,8 @@
 
     @Override
     public void unregisterPullAtomCallback(int atomTag) {
+        enforceRegisterStatsPullAtomPermission();
         int callingUid = Binder.getCallingUid();
-        final long token = Binder.clearCallingIdentity();
         PullerKey key = new PullerKey(callingUid, atomTag);
 
         // Always remove the puller from StatsManagerService even if statsd is down. When statsd
@@ -214,6 +215,7 @@
             return;
         }
 
+        final long token = Binder.clearCallingIdentity();
         try {
             statsd.unregisterPullAtomCallback(callingUid, atomTag);
         } catch (RemoteException e) {
@@ -502,6 +504,13 @@
         }
     }
 
+    private void enforceRegisterStatsPullAtomPermission() {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.REGISTER_STATS_PULL_ATOM,
+                "Need REGISTER_STATS_PULL_ATOM permission.");
+    }
+
+
     /**
      * Clients should call this if blocking until statsd to be ready is desired
      *
diff --git a/api/current.txt b/api/current.txt
index 87a5cd7..6e3a656 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -3850,7 +3850,7 @@
     method public void onPerformDirectAction(@NonNull String, @NonNull android.os.Bundle, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<android.os.Bundle>);
     method public void onPictureInPictureModeChanged(boolean, android.content.res.Configuration);
     method @Deprecated public void onPictureInPictureModeChanged(boolean);
-    method public void onPictureInPictureRequested();
+    method public boolean onPictureInPictureRequested();
     method @CallSuper protected void onPostCreate(@Nullable android.os.Bundle);
     method public void onPostCreate(@Nullable android.os.Bundle, @Nullable android.os.PersistableBundle);
     method @CallSuper protected void onPostResume();
@@ -28772,6 +28772,7 @@
     field public static final String COLUMN_DESCRIPTION = "description";
     field public static final String COLUMN_DISPLAY_NAME = "display_name";
     field public static final String COLUMN_DISPLAY_NUMBER = "display_number";
+    field public static final String COLUMN_GLOBAL_CONTENT_ID = "global_content_id";
     field public static final String COLUMN_INPUT_ID = "input_id";
     field public static final String COLUMN_INTERNAL_PROVIDER_DATA = "internal_provider_data";
     field public static final String COLUMN_INTERNAL_PROVIDER_FLAG1 = "internal_provider_flag1";
@@ -28797,6 +28798,7 @@
     field public static final String SERVICE_TYPE_AUDIO_VIDEO = "SERVICE_TYPE_AUDIO_VIDEO";
     field public static final String SERVICE_TYPE_OTHER = "SERVICE_TYPE_OTHER";
     field public static final String TYPE_1SEG = "TYPE_1SEG";
+    field public static final String TYPE_ATSC3_T = "TYPE_ATSC3_T";
     field public static final String TYPE_ATSC_C = "TYPE_ATSC_C";
     field public static final String TYPE_ATSC_M_H = "TYPE_ATSC_M_H";
     field public static final String TYPE_ATSC_T = "TYPE_ATSC_T";
@@ -28890,6 +28892,7 @@
     field public static final String COLUMN_SEASON_TITLE = "season_title";
     field public static final String COLUMN_SERIES_ID = "series_id";
     field public static final String COLUMN_SHORT_DESCRIPTION = "short_description";
+    field public static final String COLUMN_SPLIT_ID = "split_id";
     field public static final String COLUMN_STARTING_PRICE = "starting_price";
     field public static final String COLUMN_THUMBNAIL_ASPECT_RATIO = "poster_thumbnail_aspect_ratio";
     field public static final String COLUMN_THUMBNAIL_URI = "thumbnail_uri";
@@ -28937,6 +28940,8 @@
     field public static final String COLUMN_EPISODE_DISPLAY_NUMBER = "episode_display_number";
     field @Deprecated public static final String COLUMN_EPISODE_NUMBER = "episode_number";
     field public static final String COLUMN_EPISODE_TITLE = "episode_title";
+    field public static final String COLUMN_EVENT_ID = "event_id";
+    field public static final String COLUMN_GLOBAL_CONTENT_ID = "global_content_id";
     field public static final String COLUMN_INTERNAL_PROVIDER_DATA = "internal_provider_data";
     field public static final String COLUMN_INTERNAL_PROVIDER_FLAG1 = "internal_provider_flag1";
     field public static final String COLUMN_INTERNAL_PROVIDER_FLAG2 = "internal_provider_flag2";
@@ -28953,6 +28958,7 @@
     field public static final String COLUMN_SEASON_TITLE = "season_title";
     field public static final String COLUMN_SERIES_ID = "series_id";
     field public static final String COLUMN_SHORT_DESCRIPTION = "short_description";
+    field public static final String COLUMN_SPLIT_ID = "split_id";
     field public static final String COLUMN_START_TIME_UTC_MILLIS = "start_time_utc_millis";
     field public static final String COLUMN_THUMBNAIL_URI = "thumbnail_uri";
     field public static final String COLUMN_TITLE = "title";
@@ -29018,6 +29024,7 @@
     field public static final String COLUMN_SEASON_TITLE = "season_title";
     field public static final String COLUMN_SERIES_ID = "series_id";
     field public static final String COLUMN_SHORT_DESCRIPTION = "short_description";
+    field public static final String COLUMN_SPLIT_ID = "split_id";
     field public static final String COLUMN_START_TIME_UTC_MILLIS = "start_time_utc_millis";
     field public static final String COLUMN_THUMBNAIL_URI = "thumbnail_uri";
     field public static final String COLUMN_TITLE = "title";
@@ -29078,6 +29085,7 @@
     field public static final String COLUMN_SEASON_TITLE = "season_title";
     field public static final String COLUMN_SERIES_ID = "series_id";
     field public static final String COLUMN_SHORT_DESCRIPTION = "short_description";
+    field public static final String COLUMN_SPLIT_ID = "split_id";
     field public static final String COLUMN_STARTING_PRICE = "starting_price";
     field public static final String COLUMN_THUMBNAIL_ASPECT_RATIO = "poster_thumbnail_aspect_ratio";
     field public static final String COLUMN_THUMBNAIL_URI = "thumbnail_uri";
diff --git a/api/system-current.txt b/api/system-current.txt
index 253d6b6..97914dd 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -188,6 +188,7 @@
     field public static final String REGISTER_CALL_PROVIDER = "android.permission.REGISTER_CALL_PROVIDER";
     field public static final String REGISTER_CONNECTION_MANAGER = "android.permission.REGISTER_CONNECTION_MANAGER";
     field public static final String REGISTER_SIM_SUBSCRIPTION = "android.permission.REGISTER_SIM_SUBSCRIPTION";
+    field public static final String REGISTER_STATS_PULL_ATOM = "android.permission.REGISTER_STATS_PULL_ATOM";
     field public static final String REMOTE_DISPLAY_PROVIDER = "android.permission.REMOTE_DISPLAY_PROVIDER";
     field public static final String REMOVE_DRM_CERTIFICATES = "android.permission.REMOVE_DRM_CERTIFICATES";
     field public static final String REMOVE_TASKS = "android.permission.REMOVE_TASKS";
@@ -692,7 +693,7 @@
     method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public long[] getRegisteredExperimentIds() throws android.app.StatsManager.StatsUnavailableException;
     method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public byte[] getReports(long) throws android.app.StatsManager.StatsUnavailableException;
     method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public byte[] getStatsMetadata() throws android.app.StatsManager.StatsUnavailableException;
-    method public void registerPullAtomCallback(int, @Nullable android.app.StatsManager.PullAtomMetadata, @NonNull java.util.concurrent.Executor, @NonNull android.app.StatsManager.StatsPullAtomCallback);
+    method @RequiresPermission(android.Manifest.permission.REGISTER_STATS_PULL_ATOM) public void registerPullAtomCallback(int, @Nullable android.app.StatsManager.PullAtomMetadata, @NonNull java.util.concurrent.Executor, @NonNull android.app.StatsManager.StatsPullAtomCallback);
     method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public void removeConfig(long) throws android.app.StatsManager.StatsUnavailableException;
     method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public boolean removeConfiguration(long);
     method @NonNull @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public long[] setActiveConfigsChangedOperation(@Nullable android.app.PendingIntent) throws android.app.StatsManager.StatsUnavailableException;
@@ -700,7 +701,7 @@
     method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public boolean setBroadcastSubscriber(long, long, android.app.PendingIntent);
     method @Deprecated @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public boolean setDataFetchOperation(long, android.app.PendingIntent);
     method @RequiresPermission(allOf={android.Manifest.permission.DUMP, android.Manifest.permission.PACKAGE_USAGE_STATS}) public void setFetchReportsOperation(android.app.PendingIntent, long) throws android.app.StatsManager.StatsUnavailableException;
-    method public void unregisterPullAtomCallback(int);
+    method @RequiresPermission(android.Manifest.permission.REGISTER_STATS_PULL_ATOM) public void unregisterPullAtomCallback(int);
     field public static final String ACTION_STATSD_STARTED = "android.app.action.STATSD_STARTED";
     field public static final String EXTRA_STATS_ACTIVE_CONFIG_KEYS = "android.app.extra.STATS_ACTIVE_CONFIG_KEYS";
     field public static final String EXTRA_STATS_BROADCAST_SUBSCRIBER_COOKIES = "android.app.extra.STATS_BROADCAST_SUBSCRIBER_COOKIES";
@@ -2098,6 +2099,10 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.LauncherApps.AppUsageLimit> CREATOR;
   }
 
+  public static class LauncherApps.ShortcutQuery {
+    method @NonNull public android.content.pm.LauncherApps.ShortcutQuery setLocusIds(@Nullable java.util.List<android.content.LocusId>);
+  }
+
   public class PackageInstaller {
     method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void setPermissionsResult(int, boolean);
     field public static final int DATA_LOADER_TYPE_INCREMENTAL = 2; // 0x2
@@ -8197,7 +8202,7 @@
     ctor public NativeScanResult();
     method public int describeContents();
     method @NonNull public byte[] getBssid();
-    method @NonNull public java.util.BitSet getCapabilities();
+    method @NonNull public int getCapabilities();
     method public int getFrequencyMhz();
     method @NonNull public byte[] getInformationElements();
     method @NonNull public java.util.List<android.net.wifi.wificond.RadioChainInfo> getRadioChainInfos();
@@ -8233,12 +8238,12 @@
   public final class PnoSettings implements android.os.Parcelable {
     ctor public PnoSettings();
     method public int describeContents();
-    method public int getIntervalMillis();
+    method public long getIntervalMillis();
     method public int getMin2gRssiDbm();
     method public int getMin5gRssiDbm();
     method public int getMin6gRssiDbm();
     method @NonNull public java.util.List<android.net.wifi.wificond.PnoNetwork> getPnoNetworks();
-    method public void setIntervalMillis(int);
+    method public void setIntervalMillis(long);
     method public void setMin2gRssiDbm(int);
     method public void setMin5gRssiDbm(int);
     method public void setMin6gRssiDbm(int);
@@ -8263,10 +8268,10 @@
     method @Nullable public android.net.wifi.wificond.DeviceWiphyCapabilities getDeviceWiphyCapabilities(@NonNull String);
     method @NonNull public java.util.List<android.net.wifi.wificond.NativeScanResult> getScanResults(@NonNull String, int);
     method @Nullable public android.net.wifi.wificond.WifiCondManager.TxPacketCounters getTxPacketCounters(@NonNull String);
-    method public boolean initialize(@NonNull Runnable);
     method @Nullable public static android.net.wifi.wificond.WifiCondManager.OemSecurityType parseOemSecurityTypeElement(int, int, @NonNull byte[]);
     method public boolean registerApCallback(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.wificond.WifiCondManager.SoftApCallback);
     method public void sendMgmtFrame(@NonNull String, @NonNull byte[], int, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.wificond.WifiCondManager.SendMgmtFrameCallback);
+    method public void setOnServiceDeadCallback(@NonNull Runnable);
     method public boolean setupInterfaceForClientMode(@NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.net.wifi.wificond.WifiCondManager.ScanEventCallback, @NonNull android.net.wifi.wificond.WifiCondManager.ScanEventCallback);
     method public boolean setupInterfaceForSoftApMode(@NonNull String);
     method @Nullable public android.net.wifi.wificond.WifiCondManager.SignalPollResult signalPoll(@NonNull String);
diff --git a/api/system-lint-baseline.txt b/api/system-lint-baseline.txt
index a57e178..0caee6b 100644
--- a/api/system-lint-baseline.txt
+++ b/api/system-lint-baseline.txt
@@ -64,10 +64,6 @@
     
 
 
-HeavyBitSet: android.net.wifi.wificond.NativeScanResult#getCapabilities():
-    
-
-
 IntentBuilderName: android.content.Context#registerReceiverForAllUsers(android.content.BroadcastReceiver, android.content.IntentFilter, String, android.os.Handler):
     Methods creating an Intent should be named `create<Foo>Intent()`, was `registerReceiverForAllUsers`
 
diff --git a/cmds/incident/Android.bp b/cmds/incident/Android.bp
index 9e9dac1..94855aa 100644
--- a/cmds/incident/Android.bp
+++ b/cmds/incident/Android.bp
@@ -26,7 +26,7 @@
         "libcutils",
         "liblog",
         "libutils",
-        "libincident",
+        "libincidentpriv",
     ],
 
     static_libs: [
diff --git a/cmds/incident_helper/Android.bp b/cmds/incident_helper/Android.bp
index 64f4c66..f07743e 100644
--- a/cmds/incident_helper/Android.bp
+++ b/cmds/incident_helper/Android.bp
@@ -44,7 +44,7 @@
         "src/ih_util.cpp",
     ],
 
-    generated_headers: ["gen-platform-proto-constants"],
+    generated_headers: ["framework-cppstream-protos"],
 
     shared_libs: [
         "libbase",
diff --git a/cmds/incidentd/Android.bp b/cmds/incidentd/Android.bp
index 25e0328..c47526a 100644
--- a/cmds/incidentd/Android.bp
+++ b/cmds/incidentd/Android.bp
@@ -43,7 +43,7 @@
     ],
 
     local_include_dirs: ["src"],
-    generated_headers: ["gen-platform-proto-constants"],
+    generated_headers: ["framework-cppstream-protos"],
 
     proto: {
         type: "lite",
@@ -54,7 +54,7 @@
         "libbinder",
         "libdebuggerd_client",
         "libdumputils",
-        "libincident",
+        "libincidentpriv",
         "liblog",
         "libprotoutil",
         "libservices",
@@ -98,7 +98,7 @@
     ],
 
     local_include_dirs: ["src"],
-    generated_headers: ["gen-platform-proto-constants"],
+    generated_headers: ["framework-cppstream-protos"],
 
     srcs: [
         "tests/**/*.cpp",
@@ -128,7 +128,7 @@
         "libbinder",
         "libdebuggerd_client",
         "libdumputils",
-        "libincident",
+        "libincidentpriv",
         "liblog",
         "libprotobuf-cpp-full",
         "libprotoutil",
diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp
index e6cc1da..73befec 100644
--- a/cmds/statsd/Android.bp
+++ b/cmds/statsd/Android.bp
@@ -122,9 +122,9 @@
         "android.frameworks.stats@1.0",
         "libbase",
         "libcutils",
-        "liblog",
         "libprotoutil",
         "libstatslog",
+        "libstatsmetadata",
         "libstatssocket",
         "libsysutils",
     ],
@@ -133,8 +133,8 @@
         "libgraphicsenv",
         "libhidlbase",
         "libincident",
+        "liblog",
         "libservices",
-        "libstatsmetadata",
         "libutils",
     ],
 }
@@ -161,7 +161,7 @@
     ],
 }
 
-cc_library_shared {
+cc_library_static {
     name: "libstatsmetadata",
     host_supported: true,
     generated_sources: [
@@ -279,7 +279,6 @@
         "tests/e2e/ValueMetric_pull_e2e_test.cpp",
         "tests/e2e/WakelockDuration_e2e_test.cpp",
         "tests/external/GpuStatsPuller_test.cpp",
-        "tests/external/IncidentReportArgs_test.cpp",
         "tests/external/puller_util_test.cpp",
         "tests/external/StatsCallbackPuller_test.cpp",
         "tests/external/StatsPuller_test.cpp",
diff --git a/cmds/statsd/src/subscriber/IncidentdReporter.cpp b/cmds/statsd/src/subscriber/IncidentdReporter.cpp
index d86e291..30c90b1 100644
--- a/cmds/statsd/src/subscriber/IncidentdReporter.cpp
+++ b/cmds/statsd/src/subscriber/IncidentdReporter.cpp
@@ -21,10 +21,8 @@
 #include "packages/UidMap.h"
 #include "stats_log_util.h"
 
-#include <android/os/IIncidentManager.h>
-#include <android/os/IncidentReportArgs.h>
 #include <android/util/ProtoOutputStream.h>
-#include <binder/IServiceManager.h>
+#include <incident/incident_report.h>
 
 #include <vector>
 
@@ -132,7 +130,7 @@
         return false;
     }
 
-    IncidentReportArgs incidentReport;
+    android::os::IncidentReportRequest incidentReport;
 
     vector<uint8_t> protoData;
     getProtoData(rule_id, metricId, dimensionKey, metricValue, configKey,
@@ -146,30 +144,21 @@
     uint8_t dest;
     switch (config.dest()) {
         case IncidentdDetails_Destination_AUTOMATIC:
-            dest = android::os::PRIVACY_POLICY_AUTOMATIC;
+            dest = INCIDENT_REPORT_PRIVACY_POLICY_AUTOMATIC;
             break;
         case IncidentdDetails_Destination_EXPLICIT:
-            dest = android::os::PRIVACY_POLICY_EXPLICIT;
+            dest = INCIDENT_REPORT_PRIVACY_POLICY_EXPLICIT;
             break;
         default:
-            dest = android::os::PRIVACY_POLICY_AUTOMATIC;
+            dest = INCIDENT_REPORT_PRIVACY_POLICY_AUTOMATIC;
     }
     incidentReport.setPrivacyPolicy(dest);
 
-    incidentReport.setReceiverPkg(config.receiver_pkg());
+    incidentReport.setReceiverPackage(config.receiver_pkg());
 
-    incidentReport.setReceiverCls(config.receiver_cls());
+    incidentReport.setReceiverClass(config.receiver_cls());
 
-    sp<IIncidentManager> service = interface_cast<IIncidentManager>(
-            defaultServiceManager()->getService(android::String16("incident")));
-    if (service == nullptr) {
-        ALOGW("Failed to fetch incident service.");
-        return false;
-    }
-    VLOG("Calling incidentd %p", service.get());
-    binder::Status s = service->reportIncident(incidentReport);
-    VLOG("Report incident status: %s", s.toString8().string());
-    return s.isOk();
+    return incidentReport.takeReport() == NO_ERROR;
 }
 
 }  // namespace statsd
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index f31c614..642f51b 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -2880,13 +2880,14 @@
      * {@link #enterPictureInPictureMode(PictureInPictureParams)} at this time. For example, the
      * system will call this method when the activity is being put into the background, so the app
      * developer might want to switch an activity into PIP mode instead.</p>
+     *
+     * @return {@code true} if the activity received this callback regardless of if it acts on it
+     * or not. If {@code false}, the framework will assume the app hasn't been updated to leverage
+     * this callback and will in turn send a legacy callback of {@link #onUserLeaveHint()} for the
+     * app to enter picture-in-picture mode.
      */
-    public void onPictureInPictureRequested() {
-        // Previous recommendation was for apps to enter picture-in-picture in onUserLeaveHint()
-        // which is sent after onPause(). This new method allows the system to request the app to
-        // go into picture-in-picture decoupling it from life cycle events. For backwards
-        // compatibility we schedule the life cycle events if the app didn't override this method.
-        mMainThread.schedulePauseAndReturnToCurrentState(mToken);
+    public boolean onPictureInPictureRequested() {
+        return false;
     }
 
     void dispatchMovedToDisplay(int displayId, Configuration config) {
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 206c771..db9aa18 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -1472,7 +1472,7 @@
                 dest.writeInt(1);
                 dest.writeString(mLabel);
             }
-            if (mIcon == null) {
+            if (mIcon == null || mIcon.isRecycled()) {
                 dest.writeInt(0);
             } else {
                 dest.writeInt(1);
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index c901d2a..1921567 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -3772,7 +3772,15 @@
             return;
         }
 
-        r.activity.onPictureInPictureRequested();
+        final boolean receivedByApp = r.activity.onPictureInPictureRequested();
+        if (!receivedByApp) {
+            // Previous recommendation was for apps to enter picture-in-picture in
+            // onUserLeavingHint() for cases such as the app being put into the background. For
+            // backwards compatibility with apps that are not using the newer
+            // onPictureInPictureRequested() callback, we schedule the life cycle events needed to
+            // trigger onUserLeavingHint(), then we return the activity to its previous state.
+            schedulePauseWithUserLeaveHintAndReturnToCurrentState(r);
+        }
     }
 
     /**
@@ -3780,18 +3788,7 @@
      * return to its previous state. This allows activities that rely on onUserLeaveHint instead of
      * onPictureInPictureRequested to enter picture-in-picture.
      */
-    public void schedulePauseAndReturnToCurrentState(IBinder token) {
-        final ActivityClientRecord r = mActivities.get(token);
-        if (r == null) {
-            Log.w(TAG, "Activity to request pause with user leaving hint to no longer exists");
-            return;
-        }
-
-        if (r.mIsUserLeaving) {
-            // The activity is about to perform user leaving, so there's no need to cycle ourselves.
-            return;
-        }
-
+    private void schedulePauseWithUserLeaveHintAndReturnToCurrentState(ActivityClientRecord r) {
         final int prevState = r.getLifecycleState();
         if (prevState != ON_RESUME && prevState != ON_PAUSE) {
             return;
@@ -4544,7 +4541,6 @@
         if (r != null) {
             if (userLeaving) {
                 performUserLeavingActivity(r);
-                r.mIsUserLeaving = false;
             }
 
             r.activity.mConfigChangeFlags |= configChanges;
@@ -4559,7 +4555,6 @@
     }
 
     final void performUserLeavingActivity(ActivityClientRecord r) {
-        r.mIsUserLeaving = true;
         mInstrumentation.callActivityOnPictureInPictureRequested(r.activity);
         mInstrumentation.callActivityOnUserLeaving(r.activity);
     }
diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl
index 50bb3c7..0492359 100644
--- a/core/java/android/content/pm/ILauncherApps.aidl
+++ b/core/java/android/content/pm/ILauncherApps.aidl
@@ -20,11 +20,13 @@
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.IntentSender;
+import android.content.LocusId;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IOnAppsChangedListener;
 import android.content.pm.LauncherApps;
 import android.content.pm.IPackageInstallerCallback;
+import android.content.pm.IShortcutChangeCallback;
 import android.content.pm.PackageInstaller;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.ResolveInfo;
@@ -66,7 +68,8 @@
             in UserHandle user);
 
     ParceledListSlice getShortcuts(String callingPackage, long changedSince, String packageName,
-            in List shortcutIds, in ComponentName componentName, int flags, in UserHandle user);
+            in List shortcutIds, in List<LocusId> locusIds, in ComponentName componentName,
+            int flags, in UserHandle user);
     void pinShortcuts(String callingPackage, String packageName, in List<String> shortcutIds,
             in UserHandle user);
     boolean startShortcut(String callingPackage, String packageName, String id,
@@ -89,4 +92,10 @@
     void registerPackageInstallerCallback(String callingPackage,
             in IPackageInstallerCallback callback);
     ParceledListSlice getAllSessions(String callingPackage);
+
+    void registerShortcutChangeCallback(String callingPackage, long changedSince,
+            String packageName, in List shortcutIds, in List<LocusId> locusIds,
+            in ComponentName componentName, int flags, in IShortcutChangeCallback callback,
+            int callbackId);
+    void unregisterShortcutChangeCallback(String callingPackage, int callbackId);
 }
diff --git a/core/java/android/content/pm/IShortcutChangeCallback.aidl b/core/java/android/content/pm/IShortcutChangeCallback.aidl
new file mode 100644
index 0000000..fed4e4a
--- /dev/null
+++ b/core/java/android/content/pm/IShortcutChangeCallback.aidl
@@ -0,0 +1,37 @@
+/**
+ * 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.content.pm;
+
+import android.content.pm.ParceledListSlice;
+import android.content.pm.ShortcutInfo;
+import android.os.UserHandle;
+
+import java.util.List;
+
+/**
+ * Interface for LauncherApps#ShortcutChangeCallbackProxy.
+ *
+ * @hide
+ */
+oneway interface IShortcutChangeCallback
+{
+    void onShortcutsAddedOrUpdated(String packageName, in List<ShortcutInfo> shortcuts,
+            in UserHandle user);
+
+    void onShortcutsRemoved(String packageName, in List<ShortcutInfo> shortcuts,
+            in UserHandle user);
+}
\ No newline at end of file
diff --git a/core/java/android/content/pm/IShortcutService.aidl b/core/java/android/content/pm/IShortcutService.aidl
index 747e929..9e85fc3 100644
--- a/core/java/android/content/pm/IShortcutService.aidl
+++ b/core/java/android/content/pm/IShortcutService.aidl
@@ -29,10 +29,6 @@
     boolean setDynamicShortcuts(String packageName, in ParceledListSlice shortcutInfoList,
             int userId);
 
-    ParceledListSlice getDynamicShortcuts(String packageName, int userId);
-
-    ParceledListSlice getManifestShortcuts(String packageName, int userId);
-
     boolean addDynamicShortcuts(String packageName, in ParceledListSlice shortcutInfoList,
             int userId);
 
@@ -40,8 +36,6 @@
 
     void removeAllDynamicShortcuts(String packageName, int userId);
 
-    ParceledListSlice getPinnedShortcuts(String packageName, int userId);
-
     boolean updateShortcuts(String packageName, in ParceledListSlice shortcuts, int userId);
 
     boolean requestPinShortcut(String packageName, in ShortcutInfo shortcut,
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index cea0b6b..73c9e4d 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -34,6 +34,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentSender;
+import android.content.LocusId;
 import android.content.pm.PackageInstaller.SessionCallback;
 import android.content.pm.PackageInstaller.SessionCallbackDelegate;
 import android.content.pm.PackageInstaller.SessionInfo;
@@ -61,15 +62,21 @@
 import android.os.UserManager;
 import android.util.DisplayMetrics;
 import android.util.Log;
+import android.util.Pair;
+
+import com.android.internal.util.function.pooled.PooledLambda;
 
 import java.io.IOException;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.Executor;
 
@@ -152,6 +159,9 @@
     private final List<CallbackMessageHandler> mCallbacks = new ArrayList<>();
     private final List<SessionCallbackDelegate> mDelegates = new ArrayList<>();
 
+    private final Map<Integer, Pair<Executor, ShortcutChangeCallback>>
+            mShortcutChangeCallbacks = new HashMap<>();
+
     /**
      * Callbacks for package changes to this and related managed profiles.
      */
@@ -406,6 +416,9 @@
         List<String> mShortcutIds;
 
         @Nullable
+        List<LocusId> mLocusIds;
+
+        @Nullable
         ComponentName mActivity;
 
         @QueryFlags
@@ -442,6 +455,19 @@
         }
 
         /**
+         * If non-null, return only the specified shortcuts by locus ID.  When setting this field,
+         * a package name must also be set with {@link #setPackage}.
+         *
+         * @hide
+         */
+        @SystemApi
+        @NonNull
+        public ShortcutQuery setLocusIds(@Nullable List<LocusId> locusIds) {
+            mLocusIds = locusIds;
+            return this;
+        }
+
+        /**
          * If non-null, returns only shortcuts associated with the activity; i.e.
          * {@link ShortcutInfo}s whose {@link ShortcutInfo#getActivity()} are equal
          * to {@code activity}.
@@ -469,6 +495,95 @@
         }
     }
 
+    /**
+     * Callbacks for shortcut changes to this and related managed profiles.
+     *
+     * @hide
+     */
+    public interface ShortcutChangeCallback {
+        /**
+         * Indicates that one or more shortcuts, that match the {@link ShortcutQuery} used to
+         * register this callback, have been added or updated.
+         * @see LauncherApps#registerShortcutChangeCallback(ShortcutChangeCallback, ShortcutQuery)
+         *
+         * <p>Only the applications that are allowed to access the shortcut information,
+         * as defined in {@link #hasShortcutHostPermission()}, will receive it.
+         *
+         * @param packageName The name of the package that has the shortcuts.
+         * @param shortcuts Shortcuts from the package that have updated or added. Only "key"
+         *    information will be provided, as defined in {@link ShortcutInfo#hasKeyFieldsOnly()}.
+         * @param user The UserHandle of the profile that generated the change.
+         *
+         * @see ShortcutManager
+         */
+        default void onShortcutsAddedOrUpdated(@NonNull String packageName,
+                @NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user) {}
+
+        /**
+         * Indicates that one or more shortcuts, that match the {@link ShortcutQuery} used to
+         * register this callback, have been removed.
+         * @see LauncherApps#registerShortcutChangeCallback(ShortcutChangeCallback, ShortcutQuery)
+         *
+         * <p>Only the applications that are allowed to access the shortcut information,
+         * as defined in {@link #hasShortcutHostPermission()}, will receive it.
+         *
+         * @param packageName The name of the package that has the shortcuts.
+         * @param shortcuts Shortcuts from the package that have been removed. Only "key"
+         *    information will be provided, as defined in {@link ShortcutInfo#hasKeyFieldsOnly()}.
+         * @param user The UserHandle of the profile that generated the change.
+         *
+         * @see ShortcutManager
+         */
+        default void onShortcutsRemoved(@NonNull String packageName,
+                @NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user) {}
+    }
+
+    /**
+     * Callback proxy class for {@link ShortcutChangeCallback}
+     *
+     * @hide
+     */
+    private static class ShortcutChangeCallbackProxy extends
+            android.content.pm.IShortcutChangeCallback.Stub {
+        private final WeakReference<Pair<Executor, ShortcutChangeCallback>> mRemoteReferences;
+
+        ShortcutChangeCallbackProxy(Pair<Executor, ShortcutChangeCallback> remoteReferences) {
+            mRemoteReferences = new WeakReference<>(remoteReferences);
+        }
+
+        @Override
+        public void onShortcutsAddedOrUpdated(@NonNull String packageName,
+                @NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user) {
+            Pair<Executor, ShortcutChangeCallback> remoteReferences = mRemoteReferences.get();
+            if (remoteReferences == null) {
+                // Binder is dead.
+                return;
+            }
+
+            final Executor executor = remoteReferences.first;
+            final ShortcutChangeCallback callback = remoteReferences.second;
+            executor.execute(
+                    PooledLambda.obtainRunnable(ShortcutChangeCallback::onShortcutsAddedOrUpdated,
+                            callback, packageName, shortcuts, user).recycleOnUse());
+        }
+
+        @Override
+        public void onShortcutsRemoved(@NonNull String packageName,
+                @NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user) {
+            Pair<Executor, ShortcutChangeCallback> remoteReferences = mRemoteReferences.get();
+            if (remoteReferences == null) {
+                // Binder is dead.
+                return;
+            }
+
+            final Executor executor = remoteReferences.first;
+            final ShortcutChangeCallback callback = remoteReferences.second;
+            executor.execute(
+                    PooledLambda.obtainRunnable(ShortcutChangeCallback::onShortcutsRemoved,
+                            callback, packageName, shortcuts, user).recycleOnUse());
+        }
+    }
+
     /** @hide */
     public LauncherApps(Context context, ILauncherApps service) {
         mContext = context;
@@ -924,8 +1039,8 @@
             // changed callback, but that only returns shortcuts with the "key" information, so
             // that won't return disabled message.
             return maybeUpdateDisabledMessage(mService.getShortcuts(mContext.getPackageName(),
-                    query.mChangedSince, query.mPackage, query.mShortcutIds, query.mActivity,
-                    query.mQueryFlags, user)
+                    query.mChangedSince, query.mPackage, query.mShortcutIds, query.mLocusIds,
+                    query.mActivity, query.mQueryFlags, user)
                     .getList());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -1560,6 +1675,63 @@
     }
 
     /**
+     * Register a callback to watch for shortcut change events in this user and managed profiles.
+     *
+     * @param callback The callback to register.
+     * @param query {@link ShortcutQuery} to match and filter the shortcut events. Only matching
+     * shortcuts will be returned by the callback.
+     * @param executor {@link Executor} to handle the callbacks. To dispatch callbacks to the main
+     * thread of your application, you can use {@link android.content.Context#getMainExecutor()}.
+     *
+     * @hide
+     */
+    public void registerShortcutChangeCallback(@NonNull ShortcutChangeCallback callback,
+            @NonNull ShortcutQuery query, @NonNull @CallbackExecutor Executor executor) {
+        Objects.requireNonNull(callback, "Callback cannot be null");
+        Objects.requireNonNull(query, "Query cannot be null");
+        Objects.requireNonNull(executor, "Executor cannot be null");
+
+        synchronized (mShortcutChangeCallbacks) {
+            final int callbackId = callback.hashCode();
+            final Pair<Executor, ShortcutChangeCallback> state = new Pair<>(executor, callback);
+            mShortcutChangeCallbacks.put(callbackId, state);
+            try {
+                mService.registerShortcutChangeCallback(mContext.getPackageName(),
+                        query.mChangedSince, query.mPackage, query.mShortcutIds, query.mLocusIds,
+                        query.mActivity, query.mQueryFlags, new ShortcutChangeCallbackProxy(state),
+                        callbackId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Unregisters a callback that was previously registered.
+     * @see #registerShortcutChangeCallback(ShortcutChangeCallback, ShortcutQuery, Executor)
+     *
+     * @param callback Callback to be unregistered.
+     *
+     * @hide
+     */
+    public void unregisterShortcutChangeCallback(@NonNull ShortcutChangeCallback callback) {
+        Objects.requireNonNull(callback, "Callback cannot be null");
+
+        synchronized (mShortcutChangeCallbacks) {
+            final int callbackId = callback.hashCode();
+            if (mShortcutChangeCallbacks.containsKey(callbackId)) {
+                mShortcutChangeCallbacks.remove(callbackId);
+                try {
+                    mService.unregisterShortcutChangeCallback(mContext.getPackageName(),
+                            callbackId);
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+            }
+        }
+    }
+
+    /**
      * A helper method to extract a {@link PinItemRequest} set to
      * the {@link #EXTRA_PIN_ITEM_REQUEST} extra.
      */
diff --git a/core/java/android/content/pm/ShortcutServiceInternal.java b/core/java/android/content/pm/ShortcutServiceInternal.java
index e6f682d..a11a1dd 100644
--- a/core/java/android/content/pm/ShortcutServiceInternal.java
+++ b/core/java/android/content/pm/ShortcutServiceInternal.java
@@ -23,6 +23,7 @@
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.IntentSender;
+import android.content.LocusId;
 import android.content.pm.LauncherApps.ShortcutQuery;
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
@@ -45,8 +46,8 @@
             getShortcuts(int launcherUserId,
             @NonNull String callingPackage, long changedSince,
             @Nullable String packageName, @Nullable List<String> shortcutIds,
-            @Nullable ComponentName componentName, @ShortcutQuery.QueryFlags int flags,
-            int userId, int callingPid, int callingUid);
+            @Nullable List<LocusId> locusIds, @Nullable ComponentName componentName,
+            @ShortcutQuery.QueryFlags int flags, int userId, int callingPid, int callingUid);
 
     public abstract boolean
             isPinnedByCaller(int launcherUserId, @NonNull String callingPackage,
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 22d6f37..54de1bb 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -190,9 +190,7 @@
                     onAnimationFinish();
                 }
             });
-            setStartingAnimation(true);
             mAnimator.start();
-            setStartingAnimation(false);
         }
 
         @Override
@@ -203,9 +201,6 @@
             }
         }
 
-        protected void setStartingAnimation(boolean startingAnimation) {
-        }
-
         protected void onAnimationFinish() {
             mController.finish(mShow);
         }
@@ -239,16 +234,6 @@
         final @AnimationType int type;
     }
 
-    private class DefaultAnimationControlListener extends InternalAnimationControlListener {
-        DefaultAnimationControlListener(boolean show) {
-            super(show);
-        }
-        
-        @Override
-        protected void setStartingAnimation(boolean startingAnimation) {
-            mStartingAnimation = startingAnimation;
-        }
-    }
     /**
      * Represents a control request that we had to defer because we are waiting for the IME to
      * process our show request.
@@ -822,7 +807,8 @@
             return;
         }
 
-        final DefaultAnimationControlListener listener = new DefaultAnimationControlListener(show);
+        final InternalAnimationControlListener listener =
+                new InternalAnimationControlListener(show);
         // Show/hide animations always need to be relative to the display frame, in order that shown
         // and hidden state insets are correct.
         controlAnimationUnchecked(
@@ -878,7 +864,9 @@
                     return true;
                 }
                 mViewRoot.mView.dispatchWindowInsetsAnimationStart(animation, bounds);
+                mStartingAnimation = true;
                 listener.onReady(controller, types);
+                mStartingAnimation = false;
                 return true;
             }
         });
diff --git a/core/java/com/android/internal/net/VpnConfig.java b/core/java/com/android/internal/net/VpnConfig.java
index f5a19fe..6d2d735 100644
--- a/core/java/com/android/internal/net/VpnConfig.java
+++ b/core/java/com/android/internal/net/VpnConfig.java
@@ -52,6 +52,7 @@
 
     public static final String DIALOGS_PACKAGE = "com.android.vpndialogs";
 
+    // TODO: Rename this to something that encompasses Settings-based Platform VPNs as well.
     public static final String LEGACY_VPN = "[Legacy VPN]";
 
     public static Intent getIntentForConfirmation() {
diff --git a/core/proto/android/os/incident.proto b/core/proto/android/os/incident.proto
index 8adcc9e..bf4cdee 100644
--- a/core/proto/android/os/incident.proto
+++ b/core/proto/android/os/incident.proto
@@ -52,6 +52,7 @@
 import "frameworks/base/core/proto/android/service/print.proto";
 import "frameworks/base/core/proto/android/service/procstats.proto";
 import "frameworks/base/core/proto/android/service/restricted_image.proto";
+import "frameworks/base/core/proto/android/service/sensor_service.proto";
 import "frameworks/base/core/proto/android/service/usb.proto";
 import "frameworks/base/core/proto/android/util/event_log_tags.proto";
 import "frameworks/base/core/proto/android/util/log.proto";
@@ -492,6 +493,11 @@
         (section).args = "contexthub --proto"
     ];
 
+    optional android.service.SensorServiceProto sensor_service = 3053 [
+        (section).type = SECTION_DUMPSYS,
+        (section).args = "sensorservice --proto"
+    ];
+
     // Reserved for OEMs.
     extensions 50000 to 100000;
 }
diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto
index 0a2fd70..2d2ead4 100644
--- a/core/proto/android/server/activitymanagerservice.proto
+++ b/core/proto/android/server/activitymanagerservice.proto
@@ -27,7 +27,6 @@
 import "frameworks/base/core/proto/android/content/configuration.proto";
 import "frameworks/base/core/proto/android/content/intent.proto";
 import "frameworks/base/core/proto/android/content/package_item_info.proto";
-import "frameworks/base/core/proto/android/graphics/rect.proto";
 import "frameworks/base/core/proto/android/internal/processstats.proto";
 import "frameworks/base/core/proto/android/os/bundle.proto";
 import "frameworks/base/core/proto/android/os/looper.proto";
diff --git a/core/proto/android/server/notificationhistory.proto b/core/proto/android/server/notificationhistory.proto
index 1e6ee3f..6749719 100644
--- a/core/proto/android/server/notificationhistory.proto
+++ b/core/proto/android/server/notificationhistory.proto
@@ -17,8 +17,6 @@
 syntax = "proto2";
 package com.android.server.notification;
 
-import "frameworks/base/core/proto/android/server/enums.proto";
-
 option java_multiple_files = true;
 
 // On disk data store for historical notifications
diff --git a/core/proto/android/service/sensor_service.proto b/core/proto/android/service/sensor_service.proto
new file mode 100644
index 0000000..8598f86
--- /dev/null
+++ b/core/proto/android/service/sensor_service.proto
@@ -0,0 +1,246 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+package android.service;
+
+import "frameworks/base/core/proto/android/privacy.proto";
+
+option java_multiple_files = true;
+
+/*
+ * Notes:
+ * 1. When using ProtoOutputStream to write this proto message, must call
+ *    token = ProtoOutputStream#start(fieldId) before and ProtoOutputStream#end(token) after
+ *    writing a nested message.
+ * 2. Never reuse a proto field number. When removing a field, mark it as reserved.
+ */
+
+// Proto dump of android::SensorService. dumpsys sensorservice --proto
+message SensorServiceProto {
+    option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+    enum OperatingModeEnum {
+        OP_MODE_UNKNOWN = 0;
+        OP_MODE_NORMAL = 1;
+        OP_MODE_RESTRICTED = 2;
+        OP_MODE_DATA_INJECTION = 3;
+    }
+
+    optional int64 current_time_ms = 1;
+    optional SensorDeviceProto sensor_device = 2;
+    optional SensorListProto sensors = 3;
+    optional SensorFusionProto fusion_state = 4;
+    optional SensorEventsProto sensor_events = 5;
+    repeated ActiveSensorProto active_sensors = 6;
+    optional int32 socket_buffer_size = 7;
+    optional int32 socket_buffer_size_in_events = 8;
+    optional bool wake_lock_acquired = 9;
+    optional OperatingModeEnum operating_mode = 10;
+    // Non-empty only if operating_mode is RESTRICTED or DATA_INJECTION.
+    optional string whitelisted_package = 11;
+    optional bool sensor_privacy = 12;
+    repeated SensorEventConnectionProto active_connections = 13;
+    repeated SensorDirectConnectionProto direct_connections = 14;
+    repeated SensorRegistrationInfoProto previous_registrations = 15;
+}
+
+// Proto dump of android::SensorDevice
+message SensorDeviceProto {
+    option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+    optional bool initialized = 1;
+    optional int32 total_sensors = 2;
+    optional int32 active_sensors = 3;
+
+    message SensorProto {
+        option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+        optional int32 handle = 1;
+        optional int32 active_count = 2;
+        repeated float sampling_period_ms = 3;
+        optional float sampling_period_selected = 4;
+        repeated float batching_period_ms = 5;
+        optional float batching_period_selected = 6;
+    }
+    repeated SensorProto sensors = 4;
+}
+
+// Proto dump of android::SensorServiceUtil::SensorList
+message SensorListProto {
+    option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+    enum ReportingModeEnum {
+        RM_UNKNOWN = 0;
+        RM_CONTINUOUS = 1;
+        RM_ON_CHANGE = 2;
+        RM_ONE_SHOT = 3;
+        RM_SPECIAL_TRIGGER = 4;
+    }
+
+    message SensorProto {
+        option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+        optional int32 handle = 1;
+        optional string name = 2;
+        optional string vendor = 3;
+        optional int32 version = 4;
+        optional string string_type = 5;
+        optional int32 type = 6;
+        optional string required_permission = 7;
+        optional int32 flags = 8;
+        optional ReportingModeEnum reporting_mode = 9;
+        optional int32 max_delay_us = 10;
+        optional int32 min_delay_us = 11;
+        optional int32 fifo_max_event_count = 12;
+        optional int32 fifo_reserved_event_count = 13;
+        optional bool is_wakeup = 14;
+        optional bool data_injection_supported = 15;
+        optional bool is_dynamic = 16;
+        optional bool has_additional_info = 17;
+        optional int32 highest_rate_level = 18;
+        optional bool ashmem = 19;
+        optional bool gralloc = 20;
+        optional float min_value = 21;
+        optional float max_value = 22;
+        optional float resolution = 23;
+        optional float power_usage = 24;
+    }
+    repeated SensorProto sensors = 1;
+}
+
+
+// Proto dump of android::SensorFusion
+message SensorFusionProto {
+    option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+    message FusionProto {
+        option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+        optional bool enabled = 1;
+        optional int32 num_clients = 2;
+        optional float estimated_gyro_rate = 3;
+        optional float attitude_x = 4;
+        optional float attitude_y = 5;
+        optional float attitude_z = 6;
+        optional float attitude_w = 7;
+        optional float attitude_length = 8;
+        optional float bias_x = 9;
+        optional float bias_y = 10;
+        optional float bias_z = 11;
+    }
+    optional FusionProto fusion_9axis = 1;
+    optional FusionProto fusion_nomag = 2;
+    optional FusionProto fusion_nogyro = 3;
+}
+
+// Proto dump of android::SensorServiceUtil::RecentEventLogger
+message SensorEventsProto {
+    option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+    message Event {
+        option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+        optional float timestamp_sec = 1;
+        optional int64 wall_timestamp_ms = 2;
+        optional bool masked = 3;
+        optional int64 int64_data = 4;
+        repeated float float_array = 5;
+    }
+
+    message RecentEventsLog {
+        option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+        optional string name = 1;
+        optional int32 recent_events_count = 2;
+        repeated Event events = 3;
+    }
+    repeated RecentEventsLog recent_events_logs = 1;
+}
+
+message ActiveSensorProto {
+    option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+    optional string name = 1;
+    optional int32 handle = 2;
+    optional int32 num_connections = 3;
+}
+
+// Proto dump of android::SensorService::SensorDirectConnection
+message SensorDirectConnectionProto {
+    option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+    message SensorProto {
+        option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+        optional int32 sensor = 1;
+        optional int32 rate = 2;
+    }
+
+    optional string package_name = 1;
+    optional int32 hal_channel_handle = 2;
+    optional int32 num_sensor_activated = 3;
+    repeated SensorProto sensors = 4;
+}
+
+// Proto dump of android::SensorService::SensorEventConnection
+message SensorEventConnectionProto {
+    option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+    message FlushInfoProto {
+        option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+        optional string sensor_name = 1;
+        optional int32 sensor_handle = 2;
+        optional bool first_flush_pending = 3;
+        optional int32 pending_flush_events_to_send = 4;
+    }
+
+    enum OperatingModeEnum {
+        OP_MODE_UNKNOWN = 0;
+        OP_MODE_NORMAL = 1;
+        OP_MODE_RESTRICTED = 2;
+        OP_MODE_DATA_INJECTION = 3;
+    }
+
+    optional OperatingModeEnum operating_mode = 1;
+    optional string package_name = 2;
+    optional int32 wake_lock_ref_count = 3;
+    optional int32 uid = 4;
+    optional int32 cache_size = 5;
+    optional int32 max_cache_size = 6;
+    repeated FlushInfoProto flush_infos = 7;
+    optional int32 events_received = 8;
+    optional int32 events_sent = 9;
+    optional int32 events_cache = 10;
+    optional int32 events_dropped = 11;
+    optional int32 total_acks_needed = 12;
+    optional int32 total_acks_received = 13;
+}
+
+// Proto dump of android::SensorService::SensorRegistrationInfo
+message SensorRegistrationInfoProto {
+    option (android.msg_privacy).dest = DEST_AUTOMATIC;
+
+    optional int64 timestamp_sec = 1;
+    optional int32 sensor_handle = 2;
+    optional string package_name = 3;
+    optional int32 pid = 4;
+    optional int32 uid = 5;
+    optional int64 sampling_rate_us = 6;
+    optional int64 max_report_latency_us = 7;
+    optional bool activated = 8;
+}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 7a30256..efa7d59 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -3448,6 +3448,14 @@
     <permission android:name="android.permission.TUNER_RESOURCE_ACCESS"
          android:protectionLevel="signature|privileged" />
 
+    <!-- This permission is required by Media Resource Manager Service when
+         accessing its overridePid Api.
+         <p>Protection level: signature|privileged
+         <p>Not for use by third-party applications.
+         @hide -->
+    <permission android:name="android.permission.MEDIA_RESOURCE_OVERRIDE_PID"
+         android:protectionLevel="signature|privileged" />
+
     <!-- Must be required by a {@link android.media.routing.MediaRouteService}
          to ensure that only the system can interact with it.
          @hide -->
@@ -4057,6 +4065,11 @@
     <permission android:name="android.permission.STATSCOMPANION"
         android:protectionLevel="signature" />
 
+    <!--@SystemApi @hide Allows an application to register stats pull atom callbacks.
+    <p>Not for use by third-party applications.-->
+    <permission android:name="android.permission.REGISTER_STATS_PULL_ATOM"
+                android:protectionLevel="signature|privileged" />
+
     <!-- @SystemApi Allows an application to control the backup and restore process.
     <p>Not for use by third-party applications.
          @hide pending API council -->
diff --git a/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java b/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java
index 6c5d548..02be557 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityManagerTest.java
@@ -24,6 +24,9 @@
 import android.content.Context;
 import android.content.pm.ConfigurationInfo;
 import android.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.os.Parcel;
+import android.os.Parcelable;
 import android.test.AndroidTestCase;
 
 import androidx.test.filters.SmallTest;
@@ -137,21 +140,7 @@
         // Must overwrite all the fields
         td2.copyFrom(td1);
 
-        assertEquals(td1.getLabel(), td2.getLabel());
-        assertEquals(td1.getInMemoryIcon(), td2.getInMemoryIcon());
-        assertEquals(td1.getIconFilename(), td2.getIconFilename());
-        assertEquals(td1.getIconResource(), td2.getIconResource());
-        assertEquals(td1.getPrimaryColor(), td2.getPrimaryColor());
-        assertEquals(td1.getBackgroundColor(), td2.getBackgroundColor());
-        assertEquals(td1.getStatusBarColor(), td2.getStatusBarColor());
-        assertEquals(td1.getNavigationBarColor(), td2.getNavigationBarColor());
-        assertEquals(td1.getEnsureStatusBarContrastWhenTransparent(),
-                td2.getEnsureStatusBarContrastWhenTransparent());
-        assertEquals(td1.getEnsureNavigationBarContrastWhenTransparent(),
-                td2.getEnsureNavigationBarContrastWhenTransparent());
-        assertEquals(td1.getResizeMode(), td2.getResizeMode());
-        assertEquals(td1.getMinWidth(), td2.getMinWidth());
-        assertEquals(td1.getMinHeight(), td2.getMinHeight());
+        assertTaskDescriptionEqual(td1, td2, true, true);
     }
 
     @SmallTest
@@ -191,44 +180,101 @@
         // Must overwrite all public and hidden fields, since other has all fields set.
         td2.copyFromPreserveHiddenFields(td1);
 
-        assertEquals(td1.getLabel(), td2.getLabel());
-        assertEquals(td1.getInMemoryIcon(), td2.getInMemoryIcon());
-        assertEquals(td1.getIconFilename(), td2.getIconFilename());
-        assertEquals(td1.getIconResource(), td2.getIconResource());
-        assertEquals(td1.getPrimaryColor(), td2.getPrimaryColor());
-        assertEquals(td1.getBackgroundColor(), td2.getBackgroundColor());
-        assertEquals(td1.getStatusBarColor(), td2.getStatusBarColor());
-        assertEquals(td1.getNavigationBarColor(), td2.getNavigationBarColor());
-        assertEquals(td1.getEnsureStatusBarContrastWhenTransparent(),
-                td2.getEnsureStatusBarContrastWhenTransparent());
-        assertEquals(td1.getEnsureNavigationBarContrastWhenTransparent(),
-                td2.getEnsureNavigationBarContrastWhenTransparent());
-        assertEquals(td1.getResizeMode(), td2.getResizeMode());
-        assertEquals(td1.getMinWidth(), td2.getMinWidth());
-        assertEquals(td1.getMinHeight(), td2.getMinHeight());
+        assertTaskDescriptionEqual(td1, td2, true, true);
 
         TaskDescription td3 = new TaskDescription();
         // Must overwrite only public fields, and preserve hidden fields.
         td2.copyFromPreserveHiddenFields(td3);
 
-        // Overwritten fields
-        assertEquals(td3.getLabel(), td2.getLabel());
-        assertEquals(td3.getInMemoryIcon(), td2.getInMemoryIcon());
-        assertEquals(td3.getIconFilename(), td2.getIconFilename());
-        assertEquals(td3.getIconResource(), td2.getIconResource());
-        assertEquals(td3.getPrimaryColor(), td2.getPrimaryColor());
-        assertEquals(td3.getEnsureStatusBarContrastWhenTransparent(),
-                td2.getEnsureStatusBarContrastWhenTransparent());
-        assertEquals(td3.getEnsureNavigationBarContrastWhenTransparent(),
-                td2.getEnsureNavigationBarContrastWhenTransparent());
+        assertTaskDescriptionEqual(td3, td2, true, false);
+        assertTaskDescriptionEqual(td1, td2, false, true);
+    }
 
-        // Preserved fields
-        assertEquals(td1.getBackgroundColor(), td2.getBackgroundColor());
-        assertEquals(td1.getStatusBarColor(), td2.getStatusBarColor());
-        assertEquals(td1.getNavigationBarColor(), td2.getNavigationBarColor());
-        assertEquals(td1.getResizeMode(), td2.getResizeMode());
-        assertEquals(td1.getMinWidth(), td2.getMinWidth());
-        assertEquals(td1.getMinHeight(), td2.getMinHeight());
+    @SmallTest
+    public void testTaskDescriptionParceling() throws Exception {
+        TaskDescription tdBitmapNull = new TaskDescription(
+                "test label",              // label
+                null,                      // bitmap
+                21,                        // iconRes
+                "dummy file",              // iconFilename
+                0x111111,                  // colorPrimary
+                0x222222,                  // colorBackground
+                0x333333,                  // statusBarColor
+                0x444444,                  // navigationBarColor
+                false,                     // ensureStatusBarContrastWhenTransparent
+                false,                     // ensureNavigationBarContrastWhenTransparent
+                RESIZE_MODE_UNRESIZEABLE,  // resizeMode
+                10,                        // minWidth
+                20                         // minHeight
+        );
+
+        // Normal parceling should keep everything the same.
+        TaskDescription tdParcelled = new TaskDescription(parcelingRoundTrip(tdBitmapNull));
+        assertTaskDescriptionEqual(tdBitmapNull, tdParcelled, true, true);
+
+        Bitmap recycledBitmap = Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_8888);
+        recycledBitmap.recycle();
+        assertTrue(recycledBitmap.isRecycled());
+        TaskDescription tdBitmapRecycled = new TaskDescription(
+                "test label",              // label
+                recycledBitmap,                      // bitmap
+                21,                        // iconRes
+                "dummy file",              // iconFilename
+                0x111111,                  // colorPrimary
+                0x222222,                  // colorBackground
+                0x333333,                  // statusBarColor
+                0x444444,                  // navigationBarColor
+                false,                     // ensureStatusBarContrastWhenTransparent
+                false,                     // ensureNavigationBarContrastWhenTransparent
+                RESIZE_MODE_UNRESIZEABLE,  // resizeMode
+                10,                        // minWidth
+                20                         // minHeight
+        );
+        // Recycled bitmap will be ignored while parceling.
+        tdParcelled = new TaskDescription(parcelingRoundTrip(tdBitmapRecycled));
+        assertTaskDescriptionEqual(tdBitmapNull, tdParcelled, true, true);
+
+    }
+
+    private void assertTaskDescriptionEqual(TaskDescription td1, TaskDescription td2,
+            boolean checkOverwrittenFields, boolean checkPreservedFields) {
+        if (checkOverwrittenFields) {
+            assertEquals(td1.getLabel(), td2.getLabel());
+            assertEquals(td1.getInMemoryIcon(), td2.getInMemoryIcon());
+            assertEquals(td1.getIconFilename(), td2.getIconFilename());
+            assertEquals(td1.getIconResource(), td2.getIconResource());
+            assertEquals(td1.getPrimaryColor(), td2.getPrimaryColor());
+            assertEquals(td1.getEnsureStatusBarContrastWhenTransparent(),
+                    td2.getEnsureStatusBarContrastWhenTransparent());
+            assertEquals(td1.getEnsureNavigationBarContrastWhenTransparent(),
+                    td2.getEnsureNavigationBarContrastWhenTransparent());
+        }
+        if (checkPreservedFields) {
+            assertEquals(td1.getBackgroundColor(), td2.getBackgroundColor());
+            assertEquals(td1.getStatusBarColor(), td2.getStatusBarColor());
+            assertEquals(td1.getNavigationBarColor(), td2.getNavigationBarColor());
+            assertEquals(td1.getResizeMode(), td2.getResizeMode());
+            assertEquals(td1.getMinWidth(), td2.getMinWidth());
+            assertEquals(td1.getMinHeight(), td2.getMinHeight());
+        }
+    }
+
+    private <T extends Parcelable> T parcelingRoundTrip(final T in) throws Exception {
+        final Parcel p = Parcel.obtain();
+        in.writeToParcel(p, /* flags */ 0);
+        p.setDataPosition(0);
+        final byte[] marshalledData = p.marshall();
+        p.recycle();
+
+        final Parcel q = Parcel.obtain();
+        q.unmarshall(marshalledData, 0, marshalledData.length);
+        q.setDataPosition(0);
+
+        final Parcelable.Creator<T> creator = (Parcelable.Creator<T>)
+                in.getClass().getField("CREATOR").get(null); // static object, so null receiver
+        final T unmarshalled = (T) creator.createFromParcel(q);
+        q.recycle();
+        return unmarshalled;
     }
 
     // If any entries in appear in the list, sanity check them against all running applications
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index c986db8..c328d72 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -504,15 +504,17 @@
         }
 
         @Override
-        public void onPictureInPictureRequested() {
+        public boolean onPictureInPictureRequested() {
             mPipRequested = true;
             if (getIntent().getBooleanExtra(PIP_REQUESTED_OVERRIDE_ENTER, false)) {
                 enterPictureInPictureMode(new PictureInPictureParams.Builder().build());
                 mPipEntered = true;
+                return true;
             } else if (getIntent().getBooleanExtra(PIP_REQUESTED_OVERRIDE_SKIP, false)) {
                 mPipEnterSkipped = true;
+                return false;
             }
-            super.onPictureInPictureRequested();
+            return super.onPictureInPictureRequested();
         }
 
         boolean pipRequested() {
diff --git a/data/etc/platform.xml b/data/etc/platform.xml
index da50550..6929d0d 100644
--- a/data/etc/platform.xml
+++ b/data/etc/platform.xml
@@ -72,6 +72,11 @@
         <group gid="net_admin" />
     </permission>
 
+    <permission name="android.permission.MAINLINE_NETWORK_STACK" >
+        <group gid="net_admin" />
+        <group gid="net_raw" />
+    </permission>
+
     <!-- The group that /cache belongs to, linked to the permission
          set on the applications that can access /cache -->
     <permission name="android.permission.ACCESS_CACHE_FILESYSTEM" >
diff --git a/libs/incident/Android.bp b/libs/incident/Android.bp
index 150f6dc..512b8c4 100644
--- a/libs/incident/Android.bp
+++ b/libs/incident/Android.bp
@@ -12,8 +12,9 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-cc_library_shared {
-    name: "libincident",
+
+cc_defaults {
+    name: "libincidentpriv_defaults",
 
     cflags: [
         "-Wall",
@@ -50,6 +51,70 @@
         ":libincident_aidl",
         "src/IncidentReportArgs.cpp",
     ],
+}
+
+cc_library_shared {
+    name: "libincidentpriv",
+    defaults: ["libincidentpriv_defaults"],
+    export_include_dirs: ["include_priv"],
+}
+
+cc_library_shared {
+    name: "libincident",
+
+    cflags: [
+        "-Wall",
+        "-Werror",
+        "-Wno-missing-field-initializers",
+        "-Wno-unused-variable",
+        "-Wunused-parameter",
+    ],
+
+    shared_libs: [
+        "libbinder",
+        "liblog",
+        "libutils",
+        "libincidentpriv",
+    ],
+
+    srcs: [
+        "src/incident_report.cpp",
+    ],
 
     export_include_dirs: ["include"],
+
+    stubs: {
+        symbol_file: "libincident.map.txt",
+        versions: [
+            "30",
+        ],
+    },
 }
+
+cc_test {
+    name: "libincident_test",
+    defaults: ["libincidentpriv_defaults"],
+    test_suites: ["device-tests"],
+
+    include_dirs: [
+        "frameworks/base/libs/incident/include",
+        "frameworks/base/libs/incident/include_priv",
+    ],
+
+    srcs: [
+        "tests/IncidentReportArgs_test.cpp",
+        "tests/IncidentReportRequest_test.cpp",
+        "tests/c_api_compile_test.c",
+    ],
+
+    shared_libs: [
+        "libincident",
+    ],
+
+    static_libs: [
+        "libgmock",
+    ],
+}
+
+
+
diff --git a/libs/incident/include/incident/incident_report.h b/libs/incident/include/incident/incident_report.h
new file mode 100644
index 0000000..49fe5b9
--- /dev/null
+++ b/libs/incident/include/incident/incident_report.h
@@ -0,0 +1,192 @@
+/**
+ * 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.
+ */
+
+/**
+ * @file incident_report.h
+ */
+
+#ifndef ANDROID_INCIDENT_INCIDENT_REPORT_H
+#define ANDROID_INCIDENT_INCIDENT_REPORT_H
+
+#include <stdbool.h>
+
+#if __cplusplus
+#include <set>
+#include <string>
+#include <vector>
+
+extern "C" {
+#endif // __cplusplus
+
+struct AIncidentReportArgs;
+/**
+ * Opaque class to represent the arguments to an incident report request.
+ * Incident reports contain debugging data about the device at runtime.
+ * For more information see the android.os.IncidentManager java class.
+ */
+typedef struct AIncidentReportArgs AIncidentReportArgs;
+
+// Privacy policy enum value, sync with frameworks/base/core/proto/android/privacy.proto,
+// IncidentReportArgs.h and IncidentReportArgs.java.
+enum {
+    /**
+     * Flag marking fields and incident reports than can be taken
+     * off the device only via adb.
+     */
+    INCIDENT_REPORT_PRIVACY_POLICY_LOCAL = 0,
+
+    /**
+     * Flag marking fields and incident reports than can be taken
+     * off the device with contemporary consent.
+     */
+    INCIDENT_REPORT_PRIVACY_POLICY_EXPLICIT = 100,
+
+    /**
+     * Flag marking fields and incident reports than can be taken
+     * off the device with prior consent.
+     */
+    INCIDENT_REPORT_PRIVACY_POLICY_AUTOMATIC = 200,
+
+    /**
+     * Flag to indicate that a given field has not been marked
+     * with a privacy policy.
+     */
+    INCIDENT_REPORT_PRIVACY_POLICY_UNSET = 255
+};
+
+/**
+ * Allocate and initialize an AIncidentReportArgs object.
+ */
+AIncidentReportArgs* AIncidentReportArgs_init();
+
+/**
+ * Duplicate an existing AIncidentReportArgs object.
+ */
+AIncidentReportArgs* AIncidentReportArgs_clone(AIncidentReportArgs* that);
+
+/**
+ * Clean up and delete an AIncidentReportArgs object.
+ */
+void AIncidentReportArgs_delete(AIncidentReportArgs* args);
+
+/**
+ * Set this incident report to include all sections.
+ */
+void AIncidentReportArgs_setAll(AIncidentReportArgs* args, bool all);
+
+/**
+ * Set this incident report privacy policy spec.
+ */
+void AIncidentReportArgs_setPrivacyPolicy(AIncidentReportArgs* args, int privacyPolicy);
+
+/**
+ * Add this section to the incident report. The section IDs are the field numbers
+ * from the android.os.IncidentProto protobuf message.
+ */
+void AIncidentReportArgs_addSection(AIncidentReportArgs* args, int section);
+
+/**
+ * Set the apk package name that will be sent a broadcast when the incident
+ * report completes.  Must be called in conjunction with AIncidentReportArgs_setReceiverClass.
+ */
+void AIncidentReportArgs_setReceiverPackage(AIncidentReportArgs* args, char const* pkg);
+
+/**
+ * Set the fully qualified class name of the java BroadcastReceiver class that will be
+ * sent a broadcast when the report completes.  Must be called in conjunction with
+ * AIncidentReportArgs_setReceiverPackage.
+ */
+void AIncidentReportArgs_setReceiverClass(AIncidentReportArgs* args, char const* cls);
+
+/**
+ * Add protobuf data as a header to the incident report. The buffer should be a serialized
+ * android.os.IncidentHeaderProto object.
+ */
+void AIncidentReportArgs_addHeader(AIncidentReportArgs* args, uint8_t const* buf, size_t size);
+
+/**
+ * Initiate taking the report described in the args object.  Returns 0 on success,
+ * and non-zero otherwise.
+ */
+int AIncidentReportArgs_takeReport(AIncidentReportArgs* args);
+
+#if __cplusplus
+} // extern "C"
+
+namespace android {
+namespace os {
+
+class IncidentReportRequest {
+public:
+    inline IncidentReportRequest() {
+        mImpl = AIncidentReportArgs_init();
+    }
+
+    inline IncidentReportRequest(const IncidentReportRequest& that) {
+        mImpl = AIncidentReportArgs_clone(that.mImpl);
+    }
+
+    inline ~IncidentReportRequest() {
+        AIncidentReportArgs_delete(mImpl);
+    }
+
+    inline AIncidentReportArgs* getImpl() {
+        return mImpl;
+    }
+
+    inline void setAll(bool all) {
+        AIncidentReportArgs_setAll(mImpl, all);
+    }
+
+    inline void setPrivacyPolicy(int privacyPolicy) {
+        AIncidentReportArgs_setPrivacyPolicy(mImpl, privacyPolicy);
+    }
+
+    inline void addSection(int section) {
+        AIncidentReportArgs_addSection(mImpl, section);
+    }
+
+    inline void setReceiverPackage(const std::string& pkg) {
+        AIncidentReportArgs_setReceiverPackage(mImpl, pkg.c_str());
+    };
+
+    inline void setReceiverClass(const std::string& cls) {
+        AIncidentReportArgs_setReceiverClass(mImpl, cls.c_str());
+    };
+
+    inline void addHeader(const std::vector<uint8_t>& headerProto) {
+        AIncidentReportArgs_addHeader(mImpl, headerProto.data(), headerProto.size());
+    };
+
+    inline void addHeader(const uint8_t* buf, size_t size) {
+        AIncidentReportArgs_addHeader(mImpl, buf, size);
+    };
+
+    // returns a status_t
+    inline int takeReport() {
+        return AIncidentReportArgs_takeReport(mImpl);
+    }
+
+private:
+    AIncidentReportArgs* mImpl;
+};
+
+} // namespace os
+} // namespace android
+
+#endif // __cplusplus
+
+#endif // ANDROID_INCIDENT_INCIDENT_REPORT_H
diff --git a/libs/incident/include/android/os/IncidentReportArgs.h b/libs/incident/include_priv/android/os/IncidentReportArgs.h
similarity index 89%
rename from libs/incident/include/android/os/IncidentReportArgs.h
rename to libs/incident/include_priv/android/os/IncidentReportArgs.h
index 94b4ad6..0e61590 100644
--- a/libs/incident/include/android/os/IncidentReportArgs.h
+++ b/libs/incident/include_priv/android/os/IncidentReportArgs.h
@@ -14,9 +14,10 @@
  * limitations under the License.
  */
 
-#ifndef ANDROID_OS_DUMPSTATE_ARGS_H_
-#define ANDROID_OS_DUMPSTATE_ARGS_H_
+#ifndef ANDROID_OS_INCIDENT_REPORT_ARGS_H
+#define ANDROID_OS_INCIDENT_REPORT_ARGS_H
 
+#include <binder/IServiceManager.h>
 #include <binder/Parcel.h>
 #include <binder/Parcelable.h>
 #include <utils/String16.h>
@@ -29,7 +30,8 @@
 
 using namespace std;
 
-// DESTINATION enum value, sync with frameworks/base/core/proto/android/privacy.proto
+// DESTINATION enum value, sync with frameworks/base/core/proto/android/privacy.proto,
+// incident/incident_report.h and IncidentReportArgs.java
 const uint8_t PRIVACY_POLICY_LOCAL = 0;
 const uint8_t PRIVACY_POLICY_EXPLICIT = 100;
 const uint8_t PRIVACY_POLICY_AUTOMATIC = 200;
@@ -74,4 +76,4 @@
 }
 }
 
-#endif // ANDROID_OS_DUMPSTATE_ARGS_H_
+#endif // ANDROID_OS_INCIDENT_REPORT_ARGS_H
diff --git a/libs/incident/libincident.map.txt b/libs/incident/libincident.map.txt
new file mode 100644
index 0000000..f157763
--- /dev/null
+++ b/libs/incident/libincident.map.txt
@@ -0,0 +1,15 @@
+LIBINCIDENT {
+    global:
+        AIncidentReportArgs_init; # apex # introduced=30
+        AIncidentReportArgs_clone; # apex # introduced=30
+        AIncidentReportArgs_delete; # apex # introduced=30
+        AIncidentReportArgs_setAll; # apex # introduced=30
+        AIncidentReportArgs_setPrivacyPolicy; # apex # introduced=30
+        AIncidentReportArgs_addSection; # apex # introduced=30
+        AIncidentReportArgs_setReceiverPackage; # apex # introduced=30
+        AIncidentReportArgs_setReceiverClass; # apex # introduced=30
+        AIncidentReportArgs_addHeader; # apex # introduced=30
+        AIncidentReportArgs_takeReport; # apex # introduced=30
+    local:
+        *;
+};
diff --git a/libs/incident/src/incident_report.cpp b/libs/incident/src/incident_report.cpp
new file mode 100644
index 0000000..7897ddf
--- /dev/null
+++ b/libs/incident/src/incident_report.cpp
@@ -0,0 +1,83 @@
+/**
+ * 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.
+ */
+
+#define LOG_TAG "libincident"
+
+#include <incident/incident_report.h>
+
+#include <android/os/IIncidentManager.h>
+#include <android/os/IncidentReportArgs.h>
+#include <binder/IServiceManager.h>
+#include <binder/Status.h>
+#include <log/log.h>
+
+using android::sp;
+using android::binder::Status;
+using android::os::IncidentReportArgs;
+using android::os::IIncidentManager;
+using std::string;
+using std::vector;
+
+AIncidentReportArgs* AIncidentReportArgs_init() {
+    return reinterpret_cast<AIncidentReportArgs*>(new IncidentReportArgs());
+}
+
+AIncidentReportArgs* AIncidentReportArgs_clone(AIncidentReportArgs* that) {
+    return reinterpret_cast<AIncidentReportArgs*>(
+            new IncidentReportArgs(*reinterpret_cast<IncidentReportArgs*>(that)));
+}
+
+void AIncidentReportArgs_delete(AIncidentReportArgs* args) {
+    delete reinterpret_cast<IncidentReportArgs*>(args);
+}
+
+void AIncidentReportArgs_setAll(AIncidentReportArgs* args, bool all) {
+    reinterpret_cast<IncidentReportArgs*>(args)->setAll(all);
+}
+
+void AIncidentReportArgs_setPrivacyPolicy(AIncidentReportArgs* args, int privacyPolicy) {
+    reinterpret_cast<IncidentReportArgs*>(args)->setPrivacyPolicy(privacyPolicy);
+}
+
+void AIncidentReportArgs_addSection(AIncidentReportArgs* args, int section) {
+    reinterpret_cast<IncidentReportArgs*>(args)->addSection(section);
+}
+
+void AIncidentReportArgs_setReceiverPackage(AIncidentReportArgs* args, char const* pkg) {
+    reinterpret_cast<IncidentReportArgs*>(args)->setReceiverPkg(string(pkg));
+}
+
+void AIncidentReportArgs_setReceiverClass(AIncidentReportArgs* args, char const* cls) {
+    reinterpret_cast<IncidentReportArgs*>(args)->setReceiverCls(string(cls));
+}
+
+void AIncidentReportArgs_addHeader(AIncidentReportArgs* args, uint8_t const* buf, size_t size) {
+    vector<uint8_t> vec(buf, buf+size);
+    reinterpret_cast<IncidentReportArgs*>(args)->addHeader(vec);
+}
+
+int AIncidentReportArgs_takeReport(AIncidentReportArgs* argp) {
+    IncidentReportArgs* args = reinterpret_cast<IncidentReportArgs*>(argp);
+
+    sp<IIncidentManager> service = android::interface_cast<IIncidentManager>(
+            android::defaultServiceManager()->getService(android::String16("incident")));
+    if (service == nullptr) {
+        ALOGW("Failed to fetch incident service.");
+        return false;
+    }
+    Status s = service->reportIncident(*args);
+    return s.transactionError();
+}
diff --git a/cmds/statsd/tests/external/IncidentReportArgs_test.cpp b/libs/incident/tests/IncidentReportArgs_test.cpp
similarity index 93%
rename from cmds/statsd/tests/external/IncidentReportArgs_test.cpp
rename to libs/incident/tests/IncidentReportArgs_test.cpp
index 38bc194..224b343 100644
--- a/cmds/statsd/tests/external/IncidentReportArgs_test.cpp
+++ b/libs/incident/tests/IncidentReportArgs_test.cpp
@@ -20,6 +20,8 @@
 namespace os {
 namespace statsd {
 
+// Checks that all of the inline methods on IncidentReportRequest and the real C functions
+// result in a working IncidentReportArgs.
 TEST(IncidentReportArgsTest, testSerialization) {
     IncidentReportArgs args;
     args.setAll(0);
diff --git a/libs/incident/tests/IncidentReportRequest_test.cpp b/libs/incident/tests/IncidentReportRequest_test.cpp
new file mode 100644
index 0000000..6d218b6
--- /dev/null
+++ b/libs/incident/tests/IncidentReportRequest_test.cpp
@@ -0,0 +1,65 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <android/os/IncidentReportArgs.h>
+#include <incident/incident_report.h>
+
+#include <gtest/gtest.h>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+TEST(IncidentReportRequestTest, testWrite) {
+    IncidentReportRequest request;
+    request.setAll(0);
+    request.addSection(1000);
+    request.addSection(1001);
+
+    vector<uint8_t> header1;
+    header1.push_back(0x1);
+    header1.push_back(0x2);
+    vector<uint8_t> header2;
+    header1.push_back(0x22);
+    header1.push_back(0x33);
+
+    request.addHeader(header1);
+    request.addHeader(header2);
+
+    request.setPrivacyPolicy(1);
+
+    request.setReceiverPackage("com.android.os");
+    request.setReceiverClass("com.android.os.Receiver");
+
+    IncidentReportArgs* args = reinterpret_cast<IncidentReportArgs*>(request.getImpl());
+
+    EXPECT_EQ(0, args->all());
+    set<int> sections;
+    sections.insert(1000);
+    sections.insert(1001);
+    EXPECT_EQ(sections, args->sections());
+    EXPECT_EQ(1, args->getPrivacyPolicy());
+
+    EXPECT_EQ(string("com.android.os"), args->receiverPkg());
+    EXPECT_EQ(string("com.android.os.Receiver"), args->receiverCls());
+
+    vector<vector<uint8_t>> headers;
+    headers.push_back(header1);
+    headers.push_back(header2);
+    EXPECT_EQ(headers, args->headers());
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/libs/incident/tests/c_api_compile_test.c b/libs/incident/tests/c_api_compile_test.c
new file mode 100644
index 0000000..e1620df
--- /dev/null
+++ b/libs/incident/tests/c_api_compile_test.c
@@ -0,0 +1,11 @@
+#include <stdio.h>
+#include <incident/incident_report.h>
+
+/*
+ * This file ensures that incident/incident_report.h actually compiles with C,
+ * since there is no other place in the tree that actually uses it from C.
+ */
+int not_called() {
+    return 0;
+}
+
diff --git a/media/java/android/media/tv/TvContract.java b/media/java/android/media/tv/TvContract.java
index 09b7559..433c622 100644
--- a/media/java/android/media/tv/TvContract.java
+++ b/media/java/android/media/tv/TvContract.java
@@ -1109,6 +1109,24 @@
          * <p>Type: TEXT
          */
         String COLUMN_SERIES_ID = "series_id";
+
+        /**
+         * The split ID of this TV program for multi-part content, as a URI.
+         *
+         * <p>A content may consist of multiple programs within the same channel or over several
+         * channels. For example, a film might be divided into two parts interrupted by a news in
+         * the middle or a longer sport event might be split into several parts over several
+         * channels. The split ID is used to identify all the programs in the same multi-part
+         * content. Suitable URIs include
+         * <ul>
+         * <li>{@code crid://<CRIDauthority>/<data>#<IMI>} from ETSI TS 102 323
+         * </ul>
+         *
+         * <p>Can be empty.
+         *
+         * <p>Type: TEXT
+         */
+        String COLUMN_SPLIT_ID = "split_id";
     }
 
     /**
@@ -1677,6 +1695,7 @@
                 TYPE_ATSC_T,
                 TYPE_ATSC_C,
                 TYPE_ATSC_M_H,
+                TYPE_ATSC3_T,
                 TYPE_ISDB_T,
                 TYPE_ISDB_TB,
                 TYPE_ISDB_S,
@@ -1801,6 +1820,13 @@
         public static final String TYPE_ATSC_M_H = "TYPE_ATSC_M_H";
 
         /**
+         * The channel type for ATSC3.0 (terrestrial).
+         *
+         * @see #COLUMN_TYPE
+         */
+        public static final String TYPE_ATSC3_T = "TYPE_ATSC3_T";
+
+        /**
          * The channel type for ISDB-T (terrestrial).
          *
          * @see #COLUMN_TYPE
@@ -2022,6 +2048,7 @@
          * {@link #TYPE_ATSC_C},
          * {@link #TYPE_ATSC_M_H},
          * {@link #TYPE_ATSC_T},
+         * {@link #TYPE_ATSC3_T},
          * {@link #TYPE_CMMB},
          * {@link #TYPE_DTMB},
          * {@link #TYPE_DVB_C},
@@ -2407,6 +2434,22 @@
          */
         public static final String COLUMN_TRANSIENT = "transient";
 
+        /**
+         * The global content ID of this TV channel, as a URI.
+         *
+         * <p>A globally unique URI that identifies this TV channel, if applicable. Suitable URIs
+         * include
+         * <ul>
+         * <li>{@code globalServiceId} from ATSC A/331. ex {@code https://doi.org/10.5239/7E4E-B472}
+         * <li>Other broadcast ID provider. ex {@code http://example.com/tv_channel/1234}
+         * </ul>
+         *
+         * <p>Can be empty.
+         *
+         * <p>Type: TEXT
+         */
+        public static final String COLUMN_GLOBAL_CONTENT_ID = "global_content_id";
+
         private Channels() {}
 
         /**
@@ -2562,6 +2605,37 @@
          */
         public static final String COLUMN_RECORDING_PROHIBITED = "recording_prohibited";
 
+        /**
+         * The event ID of this TV program.
+         *
+         * <p>It is used to identify the current TV program in the same channel, if applicable.
+         * Use the same coding for {@code event_id} in the underlying broadcast standard if it
+         * is defined there (e.g. ATSC A/65, ETSI EN 300 468 and ARIB STD-B10).
+         *
+         * <p>This is a required field only if the underlying broadcast standard defines the same
+         * name field. Otherwise, leave empty.
+         *
+         * <p>Type: INTEGER
+         */
+        public static final String COLUMN_EVENT_ID = "event_id";
+
+        /**
+         * The global content ID of this TV program, as a URI.
+         *
+         * <p>A globally unique ID that identifies this TV program, if applicable. Suitable URIs
+         * include
+         * <ul>
+         * <li>{@code crid://<CRIDauthority>/<data>} from ETSI TS 102 323
+         * <li>{@code globalContentId} from ATSC A/332
+         * <li>Other broadcast ID provider. ex {@code http://example.com/tv_program/1234}
+         * </ul>
+         *
+         * <p>Can be empty.
+         *
+         * <p>Type: TEXT
+         */
+        public static final String COLUMN_GLOBAL_CONTENT_ID = "global_content_id";
+
         private Programs() {}
 
         /** Canonical genres for TV programs. */
diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp
index 392c9f6..ba793e8 100644
--- a/native/android/surface_control.cpp
+++ b/native/android/surface_control.cpp
@@ -294,7 +294,7 @@
 
         auto& aSurfaceControlStats = aSurfaceTransactionStats.aSurfaceControlStats;
 
-        for (const auto& [surfaceControl, acquireTime, previousReleaseFence, transformHint] : surfaceControlStats) {
+        for (const auto& [surfaceControl, latchTime, acquireTime, presentFence, previousReleaseFence, transformHint, frameEvents] : surfaceControlStats) {
             ASurfaceControl* aSurfaceControl = reinterpret_cast<ASurfaceControl*>(surfaceControl.get());
             aSurfaceControlStats[aSurfaceControl].acquireTime = acquireTime;
             aSurfaceControlStats[aSurfaceControl].previousReleaseFence = previousReleaseFence;
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
index ddb7341..1ebe917 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpProfile.java
@@ -153,21 +153,6 @@
         return mService.getDevicesMatchingConnectionStates(states);
     }
 
-    public boolean connect(BluetoothDevice device) {
-        if (mService == null) {
-            return false;
-        }
-        return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
-    }
-
-    public boolean disconnect(BluetoothDevice device) {
-        if (mService == null) {
-            return false;
-        }
-
-        return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
-    }
-
     public int getConnectionStatus(BluetoothDevice device) {
         if (mService == null) {
             return BluetoothProfile.STATE_DISCONNECTED;
@@ -187,31 +172,37 @@
         return mService.getActiveDevice();
     }
 
-    public boolean isPreferred(BluetoothDevice device) {
+    @Override
+    public boolean isEnabled(BluetoothDevice device) {
         if (mService == null) {
             return false;
         }
         return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
     }
 
-    public int getPreferred(BluetoothDevice device) {
+    @Override
+    public int getConnectionPolicy(BluetoothDevice device) {
         if (mService == null) {
             return CONNECTION_POLICY_FORBIDDEN;
         }
         return mService.getConnectionPolicy(device);
     }
 
-    public void setPreferred(BluetoothDevice device, boolean preferred) {
+    @Override
+    public boolean setEnabled(BluetoothDevice device, boolean enabled) {
+        boolean isEnabled = false;
         if (mService == null) {
-            return;
+            return false;
         }
-        if (preferred) {
+        if (enabled) {
             if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
-                mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
+                isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
+            isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
+
+        return isEnabled;
     }
     boolean isA2dpPlaying() {
         if (mService == null) return false;
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java
index 8ca5a74..c7a5bd8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/A2dpSinkProfile.java
@@ -115,21 +115,6 @@
                          BluetoothProfile.STATE_DISCONNECTING});
     }
 
-    public boolean connect(BluetoothDevice device) {
-        if (mService == null) {
-            return false;
-        }
-        return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
-    }
-
-    public boolean disconnect(BluetoothDevice device) {
-        if (mService == null) {
-            return false;
-        }
-
-        return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
-    }
-
     public int getConnectionStatus(BluetoothDevice device) {
         if (mService == null) {
             return BluetoothProfile.STATE_DISCONNECTED;
@@ -137,31 +122,37 @@
         return mService.getConnectionState(device);
     }
 
-    public boolean isPreferred(BluetoothDevice device) {
+    @Override
+    public boolean isEnabled(BluetoothDevice device) {
         if (mService == null) {
             return false;
         }
         return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
     }
 
-    public int getPreferred(BluetoothDevice device) {
+    @Override
+    public int getConnectionPolicy(BluetoothDevice device) {
         if (mService == null) {
             return CONNECTION_POLICY_FORBIDDEN;
         }
         return mService.getConnectionPolicy(device);
     }
 
-    public void setPreferred(BluetoothDevice device, boolean preferred) {
+    @Override
+    public boolean setEnabled(BluetoothDevice device, boolean enabled) {
+        boolean isEnabled = false;
         if (mService == null) {
-            return;
+            return false;
         }
-        if (preferred) {
+        if (enabled) {
             if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
-                mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
+                isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
+            isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
+
+        return isEnabled;
     }
 
     boolean isAudioPlaying() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 50d3a5d..3aa35cb 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -195,7 +195,7 @@
 
             if (newProfileState == BluetoothProfile.STATE_CONNECTED) {
                 if (profile instanceof MapProfile) {
-                    profile.setPreferred(mDevice, true);
+                    profile.setEnabled(mDevice, true);
                 }
                 if (!mProfiles.contains(profile)) {
                     mRemovedProfiles.remove(profile);
@@ -208,7 +208,7 @@
                 }
             } else if (profile instanceof MapProfile
                     && newProfileState == BluetoothProfile.STATE_DISCONNECTED) {
-                profile.setPreferred(mDevice, false);
+                profile.setEnabled(mDevice, false);
             } else if (mLocalNapRoleConnected && profile instanceof PanProfile
                     && ((PanProfile) profile).isLocalRoleNap(mDevice)
                     && newProfileState == BluetoothProfile.STATE_DISCONNECTED) {
@@ -250,12 +250,12 @@
         PbapServerProfile PbapProfile = mProfileManager.getPbapProfile();
         if (PbapProfile != null && isConnectedProfile(PbapProfile))
         {
-            PbapProfile.disconnect(mDevice);
+            PbapProfile.setEnabled(mDevice, false);
         }
     }
 
     public void disconnect(LocalBluetoothProfile profile) {
-        if (profile.disconnect(mDevice)) {
+        if (profile.setEnabled(mDevice, false)) {
             if (BluetoothUtils.D) {
                 Log.d(TAG, "Command sent successfully:DISCONNECT " + describe(profile));
             }
@@ -342,7 +342,7 @@
         if (!ensurePaired()) {
             return;
         }
-        if (profile.connect(mDevice)) {
+        if (profile.setEnabled(mDevice, true)) {
             if (BluetoothUtils.D) {
                 Log.d(TAG, "Command sent successfully:CONNECT " + describe(profile));
             }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java
index 218d0b2..9dfc4d9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HeadsetProfile.java
@@ -114,21 +114,6 @@
         return true;
     }
 
-    public boolean connect(BluetoothDevice device) {
-        if (mService == null) {
-            return false;
-        }
-        return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
-    }
-
-    public boolean disconnect(BluetoothDevice device) {
-        if (mService == null) {
-            return false;
-        }
-
-        return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
-    }
-
     public int getConnectionStatus(BluetoothDevice device) {
         if (mService == null) {
             return BluetoothProfile.STATE_DISCONNECTED;
@@ -164,31 +149,37 @@
         return mService.getAudioState(device);
     }
 
-    public boolean isPreferred(BluetoothDevice device) {
+    @Override
+    public boolean isEnabled(BluetoothDevice device) {
         if (mService == null) {
             return false;
         }
         return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
     }
 
-    public int getPreferred(BluetoothDevice device) {
+    @Override
+    public int getConnectionPolicy(BluetoothDevice device) {
         if (mService == null) {
             return CONNECTION_POLICY_FORBIDDEN;
         }
         return mService.getConnectionPolicy(device);
     }
 
-    public void setPreferred(BluetoothDevice device, boolean preferred) {
+    @Override
+    public boolean setEnabled(BluetoothDevice device, boolean enabled) {
+        boolean isEnabled = false;
         if (mService == null) {
-            return;
+            return false;
         }
-        if (preferred) {
+        if (enabled) {
             if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
-                mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
+                isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
+            isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
+
+        return isEnabled;
     }
 
     public List<BluetoothDevice> getConnectedDevices() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
index b82fb37..a3b68b4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidProfile.java
@@ -151,21 +151,6 @@
         return mService.getDevicesMatchingConnectionStates(states);
     }
 
-    public boolean connect(BluetoothDevice device) {
-        if (mService == null) {
-            return false;
-        }
-        return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
-    }
-
-    public boolean disconnect(BluetoothDevice device) {
-        if (mService == null) {
-            return false;
-        }
-
-        return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
-    }
-
     public int getConnectionStatus(BluetoothDevice device) {
         if (mService == null) {
             return BluetoothProfile.STATE_DISCONNECTED;
@@ -185,31 +170,37 @@
         return mService.getActiveDevices();
     }
 
-    public boolean isPreferred(BluetoothDevice device) {
+    @Override
+    public boolean isEnabled(BluetoothDevice device) {
         if (mService == null) {
             return false;
         }
         return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
     }
 
-    public int getPreferred(BluetoothDevice device) {
+    @Override
+    public int getConnectionPolicy(BluetoothDevice device) {
         if (mService == null) {
             return CONNECTION_POLICY_FORBIDDEN;
         }
         return mService.getConnectionPolicy(device);
     }
 
-    public void setPreferred(BluetoothDevice device, boolean preferred) {
+    @Override
+    public boolean setEnabled(BluetoothDevice device, boolean enabled) {
+        boolean isEnabled = false;
         if (mService == null) {
-            return;
+            return false;
         }
-        if (preferred) {
+        if (enabled) {
             if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
-                mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
+                isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
+            isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
+
+        return isEnabled;
     }
 
     public void setVolume(int volume) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java
index 678f2e3..66225a2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HfpClientProfile.java
@@ -125,23 +125,6 @@
     }
 
     @Override
-    public boolean connect(BluetoothDevice device) {
-        if (mService == null) {
-            return false;
-        }
-        return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
-    }
-
-    @Override
-    public boolean disconnect(BluetoothDevice device) {
-        if (mService == null) {
-            return false;
-        }
-
-        return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
-    }
-
-    @Override
     public int getConnectionStatus(BluetoothDevice device) {
         if (mService == null) {
             return BluetoothProfile.STATE_DISCONNECTED;
@@ -150,7 +133,7 @@
     }
 
     @Override
-    public boolean isPreferred(BluetoothDevice device) {
+    public boolean isEnabled(BluetoothDevice device) {
         if (mService == null) {
             return false;
         }
@@ -158,7 +141,7 @@
     }
 
     @Override
-    public int getPreferred(BluetoothDevice device) {
+    public int getConnectionPolicy(BluetoothDevice device) {
         if (mService == null) {
             return CONNECTION_POLICY_FORBIDDEN;
         }
@@ -166,17 +149,20 @@
     }
 
     @Override
-    public void setPreferred(BluetoothDevice device, boolean preferred) {
+    public boolean setEnabled(BluetoothDevice device, boolean enabled) {
+        boolean isEnabled = false;
         if (mService == null) {
-            return;
+            return false;
         }
-        if (preferred) {
+        if (enabled) {
             if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
-                mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
+                isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
+            isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
+
+        return isEnabled;
     }
 
     @Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidDeviceProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidDeviceProfile.java
index 35600b5..8a2c4f8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidDeviceProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidDeviceProfile.java
@@ -16,6 +16,8 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
@@ -102,20 +104,6 @@
     }
 
     @Override
-    public boolean connect(BluetoothDevice device) {
-        // Don't invoke method in service because settings is not allowed to connect this profile.
-        return false;
-    }
-
-    @Override
-    public boolean disconnect(BluetoothDevice device) {
-        if (mService == null) {
-            return false;
-        }
-        return mService.disconnect(device);
-    }
-
-    @Override
     public int getConnectionStatus(BluetoothDevice device) {
         if (mService == null) {
             return BluetoothProfile.STATE_DISCONNECTED;
@@ -124,21 +112,24 @@
     }
 
     @Override
-    public boolean isPreferred(BluetoothDevice device) {
+    public boolean isEnabled(BluetoothDevice device) {
         return getConnectionStatus(device) != BluetoothProfile.STATE_DISCONNECTED;
     }
 
     @Override
-    public int getPreferred(BluetoothDevice device) {
+    public int getConnectionPolicy(BluetoothDevice device) {
         return PREFERRED_VALUE;
     }
 
     @Override
-    public void setPreferred(BluetoothDevice device, boolean preferred) {
+    public boolean setEnabled(BluetoothDevice device, boolean enabled) {
+        boolean isEnabled = false;
         // if set preferred to false, then disconnect to the current device
-        if (!preferred) {
-            mService.disconnect(device);
+        if (!enabled) {
+            isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
+
+        return isEnabled;
     }
 
     @Override
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java
index 588083e..3c24b4a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HidProfile.java
@@ -101,20 +101,6 @@
         return true;
     }
 
-    public boolean connect(BluetoothDevice device) {
-        if (mService == null) {
-            return false;
-        }
-        return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
-    }
-
-    public boolean disconnect(BluetoothDevice device) {
-        if (mService == null) {
-            return false;
-        }
-        return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
-    }
-
     public int getConnectionStatus(BluetoothDevice device) {
         if (mService == null) {
             return BluetoothProfile.STATE_DISCONNECTED;
@@ -122,29 +108,37 @@
         return mService.getConnectionState(device);
     }
 
-    public boolean isPreferred(BluetoothDevice device) {
+    @Override
+    public boolean isEnabled(BluetoothDevice device) {
         if (mService == null) {
             return false;
         }
         return mService.getConnectionPolicy(device) != CONNECTION_POLICY_FORBIDDEN;
     }
 
-    public int getPreferred(BluetoothDevice device) {
+    @Override
+    public int getConnectionPolicy(BluetoothDevice device) {
         if (mService == null) {
             return CONNECTION_POLICY_FORBIDDEN;
         }
         return mService.getConnectionPolicy(device);
     }
 
-    public void setPreferred(BluetoothDevice device, boolean preferred) {
-        if (mService == null) return;
-        if (preferred) {
+    @Override
+    public boolean setEnabled(BluetoothDevice device, boolean enabled) {
+        boolean isEnabled = false;
+        if (mService == null) {
+            return false;
+        }
+        if (enabled) {
             if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
-                mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
+                isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
+            isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
+
+        return isEnabled;
     }
 
     public String toString() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfile.java
index 4b0ca74..f609e43 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfile.java
@@ -35,17 +35,26 @@
      */
     boolean isAutoConnectable();
 
-    boolean connect(BluetoothDevice device);
-
-    boolean disconnect(BluetoothDevice device);
-
     int getConnectionStatus(BluetoothDevice device);
 
-    boolean isPreferred(BluetoothDevice device);
+    /**
+     * Return {@code true} if the profile is enabled, otherwise return {@code false}.
+     * @param device the device to query for enable status
+     */
+    boolean isEnabled(BluetoothDevice device);
 
-    int getPreferred(BluetoothDevice device);
+    /**
+     * Get the connection policy of the profile.
+     * @param device the device to query for enable status
+     */
+    int getConnectionPolicy(BluetoothDevice device);
 
-    void setPreferred(BluetoothDevice device, boolean preferred);
+    /**
+     * Enable the profile if {@code enabled} is {@code true}, otherwise disable profile.
+     * @param device the device to set profile status
+     * @param enabled {@code true} for enable profile, otherwise disable profile.
+     */
+    boolean setEnabled(BluetoothDevice device, boolean enabled);
 
     boolean isProfileReady();
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
index ae2acbe..c72efb7 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothProfileManager.java
@@ -528,14 +528,14 @@
             (mMapProfile.getConnectionStatus(device) == BluetoothProfile.STATE_CONNECTED)) {
             profiles.add(mMapProfile);
             removedProfiles.remove(mMapProfile);
-            mMapProfile.setPreferred(device, true);
+            mMapProfile.setEnabled(device, true);
         }
 
         if ((mPbapProfile != null) &&
             (mPbapProfile.getConnectionStatus(device) == BluetoothProfile.STATE_CONNECTED)) {
             profiles.add(mPbapProfile);
             removedProfiles.remove(mPbapProfile);
-            mPbapProfile.setPreferred(device, true);
+            mPbapProfile.setEnabled(device, true);
         }
 
         if (mMapClientProfile != null) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java
index 7d121aa..19cb2f5 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapClientProfile.java
@@ -114,21 +114,6 @@
         return true;
     }
 
-    public boolean connect(BluetoothDevice device) {
-        if (mService == null) {
-            return false;
-        }
-        return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
-    }
-
-    public boolean disconnect(BluetoothDevice device) {
-        if (mService == null) {
-            return false;
-        }
-
-        return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
-    }
-
     public int getConnectionStatus(BluetoothDevice device) {
         if (mService == null) {
             return BluetoothProfile.STATE_DISCONNECTED;
@@ -136,31 +121,37 @@
         return mService.getConnectionState(device);
     }
 
-    public boolean isPreferred(BluetoothDevice device) {
+    @Override
+    public boolean isEnabled(BluetoothDevice device) {
         if (mService == null) {
             return false;
         }
         return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
     }
 
-    public int getPreferred(BluetoothDevice device) {
+    @Override
+    public int getConnectionPolicy(BluetoothDevice device) {
         if (mService == null) {
             return CONNECTION_POLICY_FORBIDDEN;
         }
         return mService.getConnectionPolicy(device);
     }
 
-    public void setPreferred(BluetoothDevice device, boolean preferred) {
+    @Override
+    public boolean setEnabled(BluetoothDevice device, boolean enabled) {
+        boolean isEnabled = false;
         if (mService == null) {
-            return;
+            return false;
         }
-        if (preferred) {
+        if (enabled) {
             if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
-                mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
+                isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
+            isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
+
+        return isEnabled;
     }
 
     public List<BluetoothDevice> getConnectedDevices() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java
index a96a4e7..75c1926 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/MapProfile.java
@@ -16,6 +16,7 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
 import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
 
 import android.bluetooth.BluetoothAdapter;
@@ -112,19 +113,6 @@
         return true;
     }
 
-    public boolean connect(BluetoothDevice device) {
-        Log.d(TAG, "connect() - should not get called");
-        return false; // MAP never connects out
-    }
-
-    public boolean disconnect(BluetoothDevice device) {
-        if (mService == null) {
-            return false;
-        }
-
-        return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
-    }
-
     public int getConnectionStatus(BluetoothDevice device) {
         if (mService == null) {
             return BluetoothProfile.STATE_DISCONNECTED;
@@ -132,31 +120,37 @@
         return mService.getConnectionState(device);
     }
 
-    public boolean isPreferred(BluetoothDevice device) {
+    @Override
+    public boolean isEnabled(BluetoothDevice device) {
         if (mService == null) {
             return false;
         }
         return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
     }
 
-    public int getPreferred(BluetoothDevice device) {
+    @Override
+    public int getConnectionPolicy(BluetoothDevice device) {
         if (mService == null) {
             return CONNECTION_POLICY_FORBIDDEN;
         }
         return mService.getConnectionPolicy(device);
     }
 
-    public void setPreferred(BluetoothDevice device, boolean preferred) {
+    @Override
+    public boolean setEnabled(BluetoothDevice device, boolean enabled) {
+        boolean isEnabled = false;
         if (mService == null) {
-            return;
+            return false;
         }
-        if (preferred) {
-            if (mService.getConnectionPolicy(device) < BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
-                mService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_ALLOWED);
+        if (enabled) {
+            if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
+                isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
+            isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
+
+        return isEnabled;
     }
 
     public List<BluetoothDevice> getConnectedDevices() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/OppProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/OppProfile.java
index 8e3f3ed..5a6e6e8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/OppProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/OppProfile.java
@@ -40,27 +40,23 @@
         return false;
     }
 
-    public boolean connect(BluetoothDevice device) {
-        return false;
-    }
-
-    public boolean disconnect(BluetoothDevice device) {
-        return false;
-    }
-
     public int getConnectionStatus(BluetoothDevice device) {
         return BluetoothProfile.STATE_DISCONNECTED; // Settings app doesn't handle OPP
     }
 
-    public boolean isPreferred(BluetoothDevice device) {
+    @Override
+    public boolean isEnabled(BluetoothDevice device) {
         return false;
     }
 
-    public int getPreferred(BluetoothDevice device) {
+    @Override
+    public int getConnectionPolicy(BluetoothDevice device) {
         return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN; // Settings app doesn't handle OPP
     }
 
-    public void setPreferred(BluetoothDevice device, boolean preferred) {
+    @Override
+    public boolean setEnabled(BluetoothDevice device, boolean enabled) {
+        return false;
     }
 
     public boolean isProfileReady() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java
index 6638592..767df35 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PanProfile.java
@@ -16,6 +16,9 @@
 
 package com.android.settingslib.bluetooth;
 
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
@@ -83,22 +86,6 @@
         return false;
     }
 
-    public boolean connect(BluetoothDevice device) {
-        if (mService == null) return false;
-        List<BluetoothDevice> sinks = mService.getConnectedDevices();
-        if (sinks != null) {
-            for (BluetoothDevice sink : sinks) {
-                mService.disconnect(sink);
-            }
-        }
-        return mService.connect(device);
-    }
-
-    public boolean disconnect(BluetoothDevice device) {
-        if (mService == null) return false;
-        return mService.disconnect(device);
-    }
-
     public int getConnectionStatus(BluetoothDevice device) {
         if (mService == null) {
             return BluetoothProfile.STATE_DISCONNECTED;
@@ -106,16 +93,36 @@
         return mService.getConnectionState(device);
     }
 
-    public boolean isPreferred(BluetoothDevice device) {
+    @Override
+    public boolean isEnabled(BluetoothDevice device) {
         return true;
     }
 
-    public int getPreferred(BluetoothDevice device) {
+    @Override
+    public int getConnectionPolicy(BluetoothDevice device) {
         return -1;
     }
 
-    public void setPreferred(BluetoothDevice device, boolean preferred) {
-        // ignore: isPreferred is always true for PAN
+    @Override
+    public boolean setEnabled(BluetoothDevice device, boolean enabled) {
+        boolean isEnabled = false;
+        if (mService == null) {
+            return false;
+        }
+
+        if (enabled) {
+            final List<BluetoothDevice> sinks = mService.getConnectedDevices();
+            if (sinks != null) {
+                for (BluetoothDevice sink : sinks) {
+                    mService.setConnectionPolicy(sink, CONNECTION_POLICY_FORBIDDEN);
+                }
+            }
+            isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
+        } else {
+            isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
+        }
+
+        return isEnabled;
     }
 
     public String toString() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java
index 56267fc..0d11293 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapClientProfile.java
@@ -126,23 +126,6 @@
                          BluetoothProfile.STATE_DISCONNECTING});
     }
 
-    public boolean connect(BluetoothDevice device) {
-        Log.d(TAG,"PBAPClientProfile got connect request");
-        if (mService == null) {
-            return false;
-        }
-        Log.d(TAG,"PBAPClientProfile attempting to connect to " + device.getAddress());
-        return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
-    }
-
-    public boolean disconnect(BluetoothDevice device) {
-        Log.d(TAG,"PBAPClientProfile got disconnect request");
-        if (mService == null) {
-            return false;
-        }
-        return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
-    }
-
     public int getConnectionStatus(BluetoothDevice device) {
         if (mService == null) {
             return BluetoothProfile.STATE_DISCONNECTED;
@@ -150,31 +133,37 @@
         return mService.getConnectionState(device);
     }
 
-    public boolean isPreferred(BluetoothDevice device) {
+    @Override
+    public boolean isEnabled(BluetoothDevice device) {
         if (mService == null) {
             return false;
         }
         return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
     }
 
-    public int getPreferred(BluetoothDevice device) {
+    @Override
+    public int getConnectionPolicy(BluetoothDevice device) {
         if (mService == null) {
             return CONNECTION_POLICY_FORBIDDEN;
         }
         return mService.getConnectionPolicy(device);
     }
 
-    public void setPreferred(BluetoothDevice device, boolean preferred) {
+    @Override
+    public boolean setEnabled(BluetoothDevice device, boolean enabled) {
+        boolean isEnabled = false;
         if (mService == null) {
-            return;
+            return false;
         }
-        if (preferred) {
+        if (enabled) {
             if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
-                mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
+                isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
+            isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
+
+        return isEnabled;
     }
 
     public String toString() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java
index f7c0bf5..9e2e4a1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/PbapServerProfile.java
@@ -91,34 +91,33 @@
         return false;
     }
 
-    public boolean connect(BluetoothDevice device) {
-        /*Can't connect from server */
-        return false;
-
-    }
-
-    public boolean disconnect(BluetoothDevice device) {
-        if (mService == null) {
-            return false;
-        }
-        return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
-    }
-
     public int getConnectionStatus(BluetoothDevice device) {
         if (mService == null) return BluetoothProfile.STATE_DISCONNECTED;
         return mService.getConnectionState(device);
     }
 
-    public boolean isPreferred(BluetoothDevice device) {
+    @Override
+    public boolean isEnabled(BluetoothDevice device) {
         return false;
     }
 
-    public int getPreferred(BluetoothDevice device) {
+    @Override
+    public int getConnectionPolicy(BluetoothDevice device) {
         return -1;
     }
 
-    public void setPreferred(BluetoothDevice device, boolean preferred) {
-        // ignore: isPreferred is always true for PBAP
+    @Override
+    public boolean setEnabled(BluetoothDevice device, boolean enabled) {
+        boolean isEnabled = false;
+        if (mService == null) {
+            return false;
+        }
+
+        if (!enabled) {
+            isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
+        }
+
+        return isEnabled;
     }
 
     public String toString() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java
index 3022c5b..104f1d7 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/SapProfile.java
@@ -111,21 +111,6 @@
         return true;
     }
 
-    public boolean connect(BluetoothDevice device) {
-        if (mService == null) {
-            return false;
-        }
-        return mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
-    }
-
-    public boolean disconnect(BluetoothDevice device) {
-        if (mService == null) {
-            return false;
-        }
-
-        return mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
-    }
-
     public int getConnectionStatus(BluetoothDevice device) {
         if (mService == null) {
             return BluetoothProfile.STATE_DISCONNECTED;
@@ -133,31 +118,37 @@
         return mService.getConnectionState(device);
     }
 
-    public boolean isPreferred(BluetoothDevice device) {
+    @Override
+    public boolean isEnabled(BluetoothDevice device) {
         if (mService == null) {
             return false;
         }
         return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
     }
 
-    public int getPreferred(BluetoothDevice device) {
+    @Override
+    public int getConnectionPolicy(BluetoothDevice device) {
         if (mService == null) {
             return CONNECTION_POLICY_FORBIDDEN;
         }
         return mService.getConnectionPolicy(device);
     }
 
-    public void setPreferred(BluetoothDevice device, boolean preferred) {
+    @Override
+    public boolean setEnabled(BluetoothDevice device, boolean enabled) {
+        boolean isEnabled = false;
         if (mService == null) {
-            return;
+            return false;
         }
-        if (preferred) {
+        if (enabled) {
             if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
-                mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
+                isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
             }
         } else {
-            mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
+            isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
         }
+
+        return isEnabled;
     }
 
     public List<BluetoothDevice> getConnectedDevices() {
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaManager.java
index 12d054e..3a807c9 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/BluetoothMediaManager.java
@@ -108,9 +108,9 @@
 
             Log.d(TAG, "addConnectableA2dpDevices() device : " + cachedDevice.getName()
                     + ", is connected : " + cachedDevice.isConnected()
-                    + ", is preferred : " + a2dpProfile.isPreferred(device));
+                    + ", is enabled : " + a2dpProfile.isEnabled(device));
 
-            if (a2dpProfile.isPreferred(device)
+            if (a2dpProfile.isEnabled(device)
                     && BluetoothDevice.BOND_BONDED == cachedDevice.getBondState()) {
                 addMediaDevice(cachedDevice);
             }
@@ -143,9 +143,9 @@
 
             Log.d(TAG, "addConnectableHearingAidDevices() device : " + cachedDevice.getName()
                     + ", is connected : " + cachedDevice.isConnected()
-                    + ", is preferred : " + hapProfile.isPreferred(device));
+                    + ", is enabled : " + hapProfile.isEnabled(device));
 
-            if (hapProfile.isPreferred(device)
+            if (hapProfile.isEnabled(device)
                     && BluetoothDevice.BOND_BONDED == cachedDevice.getBondState()) {
                 addMediaDevice(cachedDevice);
             }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpSinkProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpSinkProfileTest.java
index ccb6646..9bb2f22d 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpSinkProfileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/A2dpSinkProfileTest.java
@@ -16,12 +16,8 @@
 
 package com.android.settingslib.bluetooth;
 
-import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
-import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
-
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.bluetooth.BluetoothA2dpSink;
@@ -68,18 +64,6 @@
     }
 
     @Test
-    public void connect_shouldConnectBluetoothA2dpSink() {
-        mProfile.connect(mBluetoothDevice);
-        verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED);
-    }
-
-    @Test
-    public void disconnect_shouldDisconnectBluetoothA2dpSink() {
-        mProfile.disconnect(mBluetoothDevice);
-        verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN);
-    }
-
-    @Test
     public void getConnectionStatus_shouldReturnConnectionState() {
         when(mService.getConnectionState(mBluetoothDevice)).
                 thenReturn(BluetoothProfile.STATE_CONNECTED);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HfpClientProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HfpClientProfileTest.java
index 9180760..d121e0b 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HfpClientProfileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HfpClientProfileTest.java
@@ -16,12 +16,8 @@
 
 package com.android.settingslib.bluetooth;
 
-import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
-import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
-
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.bluetooth.BluetoothAdapter;
@@ -68,18 +64,6 @@
     }
 
     @Test
-    public void connect_shouldConnectBluetoothHeadsetClient() {
-        mProfile.connect(mBluetoothDevice);
-        verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED);
-    }
-
-    @Test
-    public void disconnect_shouldDisconnectBluetoothHeadsetClient() {
-        mProfile.disconnect(mBluetoothDevice);
-        verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN);
-    }
-
-    @Test
     public void getConnectionStatus_shouldReturnConnectionState() {
         when(mService.getConnectionState(mBluetoothDevice)).
                 thenReturn(BluetoothProfile.STATE_CONNECTED);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HidDeviceProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HidDeviceProfileTest.java
index f38af70..3665d9c 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HidDeviceProfileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HidDeviceProfileTest.java
@@ -18,7 +18,6 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.bluetooth.BluetoothAdapter;
@@ -65,17 +64,6 @@
     }
 
     @Test
-    public void connect_shouldReturnFalse() {
-        assertThat(mProfile.connect(mBluetoothDevice)).isFalse();
-    }
-
-    @Test
-    public void disconnect_shouldDisconnectBluetoothHidDevice() {
-        mProfile.disconnect(mBluetoothDevice);
-        verify(mService).disconnect(mBluetoothDevice);
-    }
-
-    @Test
     public void getConnectionStatus_shouldReturnConnectionState() {
         when(mService.getConnectionState(mBluetoothDevice)).
                 thenReturn(BluetoothProfile.STATE_CONNECTED);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/MapClientProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/MapClientProfileTest.java
index 1425c38..25031a6 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/MapClientProfileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/MapClientProfileTest.java
@@ -16,12 +16,8 @@
 
 package com.android.settingslib.bluetooth;
 
-import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
-import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
-
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.bluetooth.BluetoothAdapter;
@@ -68,18 +64,6 @@
     }
 
     @Test
-    public void connect_shouldConnectBluetoothMapClient() {
-        mProfile.connect(mBluetoothDevice);
-        verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED);
-    }
-
-    @Test
-    public void disconnect_shouldDisconnectBluetoothMapClient() {
-        mProfile.disconnect(mBluetoothDevice);
-        verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN);
-    }
-
-    @Test
     public void getConnectionStatus_shouldReturnConnectionState() {
         when(mService.getConnectionState(mBluetoothDevice)).
                 thenReturn(BluetoothProfile.STATE_CONNECTED);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/PbapClientProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/PbapClientProfileTest.java
index 15f560b..4305a3b 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/PbapClientProfileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/PbapClientProfileTest.java
@@ -16,12 +16,8 @@
 
 package com.android.settingslib.bluetooth;
 
-import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
-import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
-
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.bluetooth.BluetoothAdapter;
@@ -68,18 +64,6 @@
     }
 
     @Test
-    public void connect_shouldConnectBluetoothPbapClient() {
-        mProfile.connect(mBluetoothDevice);
-        verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED);
-    }
-
-    @Test
-    public void disconnect_shouldDisconnectBluetoothPbapClient() {
-        mProfile.disconnect(mBluetoothDevice);
-        verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN);
-    }
-
-    @Test
     public void getConnectionStatus_shouldReturnConnectionState() {
         when(mService.getConnectionState(mBluetoothDevice)).
                 thenReturn(BluetoothProfile.STATE_CONNECTED);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/SapProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/SapProfileTest.java
index 4f978a8..e460eaf 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/SapProfileTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/SapProfileTest.java
@@ -16,12 +16,8 @@
 
 package com.android.settingslib.bluetooth;
 
-import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
-import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
-
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.bluetooth.BluetoothAdapter;
@@ -67,18 +63,6 @@
     }
 
     @Test
-    public void connect_shouldConnectBluetoothSap() {
-        mProfile.connect(mBluetoothDevice);
-        verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED);
-    }
-
-    @Test
-    public void disconnect_shouldDisconnectBluetoothSap() {
-        mProfile.disconnect(mBluetoothDevice);
-        verify(mService).setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN);
-    }
-
-    @Test
     public void getConnectionStatus_shouldReturnConnectionState() {
         when(mService.getConnectionState(mBluetoothDevice)).
                 thenReturn(BluetoothProfile.STATE_CONNECTED);
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/BluetoothMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/BluetoothMediaManagerTest.java
index f27cef9..0ee5ea8 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/BluetoothMediaManagerTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/BluetoothMediaManagerTest.java
@@ -96,7 +96,7 @@
         when(mA2dpProfile.getConnectableDevices()).thenReturn(devices);
         when(mCachedDeviceManager.findDevice(bluetoothDevice)).thenReturn(cachedDevice);
         when(cachedDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
-        when(mA2dpProfile.isPreferred(bluetoothDevice)).thenReturn(true);
+        when(mA2dpProfile.isEnabled(bluetoothDevice)).thenReturn(true);
 
         assertThat(mMediaManager.mMediaDevices).isEmpty();
         mMediaManager.startScan();
@@ -113,7 +113,7 @@
         when(mA2dpProfile.getConnectableDevices()).thenReturn(devices);
         when(mCachedDeviceManager.findDevice(bluetoothDevice)).thenReturn(cachedDevice);
         when(cachedDevice.getBondState()).thenReturn(BluetoothDevice.BOND_NONE);
-        when(mA2dpProfile.isPreferred(bluetoothDevice)).thenReturn(true);
+        when(mA2dpProfile.isEnabled(bluetoothDevice)).thenReturn(true);
 
         assertThat(mMediaManager.mMediaDevices).isEmpty();
         mMediaManager.startScan();
@@ -141,7 +141,7 @@
         when(mHapProfile.getConnectableDevices()).thenReturn(devices);
         when(mCachedDeviceManager.findDevice(bluetoothDevice)).thenReturn(cachedDevice);
         when(cachedDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
-        when(mHapProfile.isPreferred(bluetoothDevice)).thenReturn(true);
+        when(mHapProfile.isEnabled(bluetoothDevice)).thenReturn(true);
 
         assertThat(mMediaManager.mMediaDevices).isEmpty();
         mMediaManager.startScan();
diff --git a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java
index d67a9bc..8ff595b 100644
--- a/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java
+++ b/packages/SettingsProvider/test/src/com/android/providers/settings/SettingsProviderTest.java
@@ -33,7 +33,6 @@
 import android.provider.Settings;
 import android.util.Log;
 
-import org.junit.Ignore;
 import org.junit.Test;
 
 import java.util.concurrent.atomic.AtomicBoolean;
@@ -692,125 +691,4 @@
             cursor.close();
         }
     }
-
-    @Test
-    @Ignore("b/140250974")
-    public void testLocationModeChanges_viaFrontEndApi() throws Exception {
-        setStringViaFrontEndApiSetting(
-                SETTING_TYPE_SECURE,
-                Settings.Secure.LOCATION_MODE,
-                String.valueOf(Settings.Secure.LOCATION_MODE_OFF),
-                UserHandle.USER_SYSTEM);
-        assertEquals(
-                "Wrong location providers",
-                "",
-                getStringViaFrontEndApiSetting(
-                        SETTING_TYPE_SECURE,
-                        Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
-                        UserHandle.USER_SYSTEM));
-
-        setStringViaFrontEndApiSetting(
-                SETTING_TYPE_SECURE,
-                Settings.Secure.LOCATION_MODE,
-                String.valueOf(Settings.Secure.LOCATION_MODE_BATTERY_SAVING),
-                UserHandle.USER_SYSTEM);
-        assertEquals(
-                "Wrong location providers",
-                "network",
-                getStringViaFrontEndApiSetting(
-                        SETTING_TYPE_SECURE,
-                        Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
-                        UserHandle.USER_SYSTEM));
-
-        setStringViaFrontEndApiSetting(
-                SETTING_TYPE_SECURE,
-                Settings.Secure.LOCATION_MODE,
-                String.valueOf(Settings.Secure.LOCATION_MODE_HIGH_ACCURACY),
-                UserHandle.USER_SYSTEM);
-        assertEquals(
-                "Wrong location providers",
-                "gps,network",
-                getStringViaFrontEndApiSetting(
-                        SETTING_TYPE_SECURE,
-                        Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
-                        UserHandle.USER_SYSTEM));
-    }
-
-    @Test
-    @Ignore("b/140250974")
-    public void testLocationProvidersAllowed_disableProviders() throws Exception {
-        setStringViaFrontEndApiSetting(
-                SETTING_TYPE_SECURE,
-                Settings.Secure.LOCATION_MODE,
-                String.valueOf(Settings.Secure.LOCATION_MODE_HIGH_ACCURACY),
-                UserHandle.USER_SYSTEM);
-
-        // Disable providers that were enabled
-        updateStringViaProviderApiSetting(
-                SETTING_TYPE_SECURE,
-                Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
-                "-gps,-network");
-        assertEquals(
-                "Wrong location providers",
-                "",
-                queryStringViaProviderApi(
-                        SETTING_TYPE_SECURE, Settings.Secure.LOCATION_PROVIDERS_ALLOWED));
-
-        // Disable a provider that was not enabled
-        updateStringViaProviderApiSetting(
-                SETTING_TYPE_SECURE,
-                Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
-                "-test");
-        assertEquals(
-                "Wrong location providers",
-                "",
-                queryStringViaProviderApi(
-                        SETTING_TYPE_SECURE, Settings.Secure.LOCATION_PROVIDERS_ALLOWED));
-    }
-
-    @Test
-    @Ignore("b/140250974")
-    public void testLocationProvidersAllowed_enableAndDisable() throws Exception {
-        setStringViaFrontEndApiSetting(
-                SETTING_TYPE_SECURE,
-                Settings.Secure.LOCATION_MODE,
-                String.valueOf(Settings.Secure.LOCATION_MODE_OFF),
-                UserHandle.USER_SYSTEM);
-
-        updateStringViaProviderApiSetting(
-                SETTING_TYPE_SECURE,
-                Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
-                "+gps,+network,+test");
-        updateStringViaProviderApiSetting(
-                SETTING_TYPE_SECURE, Settings.Secure.LOCATION_PROVIDERS_ALLOWED, "-test");
-
-        assertEquals(
-                "Wrong location providers",
-                "gps,network",
-                queryStringViaProviderApi(
-                        SETTING_TYPE_SECURE, Settings.Secure.LOCATION_PROVIDERS_ALLOWED));
-    }
-
-    @Test
-    @Ignore("b/140250974")
-    public void testLocationProvidersAllowedLocked_invalidInput() throws Exception {
-        setStringViaFrontEndApiSetting(
-                SETTING_TYPE_SECURE,
-                Settings.Secure.LOCATION_MODE,
-                String.valueOf(Settings.Secure.LOCATION_MODE_OFF),
-                UserHandle.USER_SYSTEM);
-
-        // update providers with a invalid string
-        updateStringViaProviderApiSetting(
-                SETTING_TYPE_SECURE,
-                Settings.Secure.LOCATION_PROVIDERS_ALLOWED,
-                "+gps, invalid-string");
-
-        // Verifies providers list does not change
-        assertEquals(
-                "Wrong location providers",
-                "",
-                queryStringViaProviderApi(
-                        SETTING_TYPE_SECURE, Settings.Secure.LOCATION_PROVIDERS_ALLOWED));
-    }
 }
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 9c997e8..5a1d1e2 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1090,7 +1090,7 @@
 
     <!--  Blur radius on status bar window and power menu  -->
     <dimen name="min_window_blur_radius">1px</dimen>
-    <dimen name="max_window_blur_radius">100px</dimen>
+    <dimen name="max_window_blur_radius">250px</dimen>
 
     <!-- How much into a DisplayCutout's bounds we can go, on each side -->
     <dimen name="display_cutout_margin_consumption">0px</dimen>
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/InflationTask.java b/packages/SystemUI/src/com/android/systemui/statusbar/InflationTask.java
index 22fd37c..eb580c4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/InflationTask.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/InflationTask.java
@@ -22,11 +22,4 @@
  */
 public interface InflationTask {
     void abort();
-
-    /**
-     * Supersedes an existing task. i.e another task was superceeded by this.
-     *
-     * @param task the task that was previously running
-     */
-    default void supersedeTask(InflationTask task) {}
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java
index 81833a4..d0e238a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationAlertingManager.java
@@ -28,7 +28,6 @@
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 
 import javax.inject.Inject;
@@ -64,8 +63,8 @@
 
         notificationEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
             @Override
-            public void onEntryInflated(NotificationEntry entry, int inflatedFlags) {
-                showAlertingView(entry, inflatedFlags);
+            public void onEntryInflated(NotificationEntry entry) {
+                showAlertingView(entry);
             }
 
             @Override
@@ -90,12 +89,11 @@
     /**
      * Adds the entry to the respective alerting manager if the content view was inflated and
      * the entry should still alert.
-     *
-     * @param entry         entry to add
-     * @param inflatedFlags flags representing content views that were inflated
      */
-    private void showAlertingView(NotificationEntry entry, @InflationFlag int inflatedFlags) {
-        if ((inflatedFlags & FLAG_CONTENT_VIEW_HEADS_UP) != 0) {
+    private void showAlertingView(NotificationEntry entry) {
+        // TODO: Instead of this back and forth, we should listen to changes in heads up and
+        // cancel on-going heads up view inflation using the bind pipeline.
+        if (entry.getRow().getPrivateLayout().getHeadsUpChild() != null) {
             // Possible for shouldHeadsUp to change between the inflation starting and ending.
             // If it does and we no longer need to heads up, we should free the view.
             if (mNotificationInterruptionStateProvider.shouldHeadsUp(entry)) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java
index f6b5583..25253a1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryListener.java
@@ -24,7 +24,6 @@
 
 import com.android.internal.statusbar.NotificationVisibility;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
 
 /**
  * Listener interface for changes sent by NotificationEntryManager.
@@ -62,7 +61,7 @@
     /**
      * Called when a notification's views are inflated for the first time.
      */
-    default void onEntryInflated(NotificationEntry entry, @InflationFlag int inflatedFlags) {
+    default void onEntryInflated(NotificationEntry entry) {
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
index 6bb377e8..61915ad 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationEntryManager.java
@@ -18,6 +18,7 @@
 import static android.service.notification.NotificationListenerService.REASON_CANCEL;
 import static android.service.notification.NotificationListenerService.REASON_ERROR;
 
+import static com.android.systemui.statusbar.notification.collection.NotifCollection.REASON_UNKNOWN;
 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationCallback;
 
 import android.annotation.NonNull;
@@ -44,10 +45,11 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationRankingManager;
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
+import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
 import com.android.systemui.statusbar.notification.logging.NotifEvent;
 import com.android.systemui.statusbar.notification.logging.NotifLog;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
-import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -96,6 +98,7 @@
  */
 @Singleton
 public class NotificationEntryManager implements
+        CommonNotifCollection,
         Dumpable,
         InflationCallback,
         VisualStabilityManager.Callback {
@@ -130,6 +133,7 @@
     private final Lazy<NotificationRowBinder> mNotificationRowBinderLazy;
     private final Lazy<NotificationRemoteInputManager> mRemoteInputManagerLazy;
     private final LeakDetector mLeakDetector;
+    private final List<NotifCollectionListener> mNotifCollectionListeners = new ArrayList<>();
 
     private final KeyguardEnvironment mKeyguardEnvironment;
     private final NotificationGroupManager mGroupManager;
@@ -318,8 +322,7 @@
     }
 
     @Override
-    public void onAsyncInflationFinished(NotificationEntry entry,
-            @InflationFlag int inflatedFlags) {
+    public void onAsyncInflationFinished(NotificationEntry entry) {
         mPendingNotifications.remove(entry.getKey());
         // If there was an async task started after the removal, we don't want to add it back to
         // the list, otherwise we might get leaks.
@@ -328,7 +331,7 @@
             if (isNew) {
                 for (NotificationEntryListener listener : mNotificationEntryListeners) {
                     mNotifLog.log(NotifEvent.INFLATED, entry);
-                    listener.onEntryInflated(entry, inflatedFlags);
+                    listener.onEntryInflated(entry);
                 }
                 addActiveNotification(entry);
                 updateNotifications("onAsyncInflationFinished");
@@ -488,6 +491,13 @@
                 for (NotificationEntryListener listener : mNotificationEntryListeners) {
                     listener.onEntryRemoved(entry, visibility, removedByUser);
                 }
+                for (NotifCollectionListener listener : mNotifCollectionListeners) {
+                    // NEM doesn't have a good knowledge of reasons so defaulting to unknown.
+                    listener.onEntryRemoved(entry, REASON_UNKNOWN);
+                }
+                for (NotifCollectionListener listener : mNotifCollectionListeners) {
+                    listener.onEntryCleanUp(entry);
+                }
             }
         }
     }
@@ -553,6 +563,10 @@
 
         mLeakDetector.trackInstance(entry);
 
+        for (NotifCollectionListener listener : mNotifCollectionListeners) {
+            listener.onEntryInit(entry);
+        }
+
         // Construct the expanded view.
         if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
             mNotificationRowBinderLazy.get()
@@ -566,6 +580,9 @@
         for (NotificationEntryListener listener : mNotificationEntryListeners) {
             listener.onPendingEntryAdded(entry);
         }
+        for (NotifCollectionListener listener : mNotifCollectionListeners) {
+            listener.onEntryAdded(entry);
+        }
     }
 
     public void addNotification(StatusBarNotification notification, RankingMap ranking) {
@@ -600,6 +617,9 @@
         for (NotificationEntryListener listener : mNotificationEntryListeners) {
             listener.onPreEntryUpdated(entry);
         }
+        for (NotifCollectionListener listener : mNotifCollectionListeners) {
+            listener.onEntryUpdated(entry);
+        }
 
         if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
             mNotificationRowBinderLazy.get()
@@ -674,6 +694,9 @@
         for (NotificationEntryListener listener : mNotificationEntryListeners) {
             listener.onNotificationRankingUpdated(rankingMap);
         }
+        for (NotifCollectionListener listener : mNotifCollectionListeners) {
+            listener.onRankingUpdate(rankingMap);
+        }
     }
 
     private void updateRankingOfPendingNotifications(@Nullable RankingMap rankingMap) {
@@ -862,6 +885,11 @@
         return mReadOnlyNotifications.size() != 0;
     }
 
+    @Override
+    public void addCollectionListener(NotifCollectionListener listener) {
+        mNotifCollectionListeners.add(listener);
+    }
+
     /*
      * End annexation
      * -----
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java
index 7a6d4f1..9272e51b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifInflaterImpl.java
@@ -149,9 +149,7 @@
                 }
 
                 @Override
-                public void onAsyncInflationFinished(
-                        NotificationEntry entry,
-                        int inflatedFlags) {
+                public void onAsyncInflationFinished(NotificationEntry entry) {
                     if (mExternalInflationCallback != null) {
                         mExternalInflationCallback.onInflationFinished(entry);
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java
index 9142388..5767ad9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotifPipeline.java
@@ -23,6 +23,7 @@
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
 import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSection;
+import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
 
@@ -66,7 +67,7 @@
  *  9. The list is handed off to the view layer to be rendered
  */
 @Singleton
-public class NotifPipeline {
+public class NotifPipeline implements CommonNotifCollection {
     private final NotifCollection mNotifCollection;
     private final ShadeListBuilder mShadeListBuilder;
 
@@ -89,10 +90,7 @@
         return mNotifCollection.getActiveNotifs();
     }
 
-    /**
-     * Registers a listener to be informed when there is a notification entry event such as an add,
-     * update, or remove.
-     */
+    @Override
     public void addCollectionListener(NotifCollectionListener listener) {
         mNotifCollection.addCollectionListener(listener);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
index df65dac..5dbf47e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/NotificationEntry.java
@@ -275,7 +275,12 @@
         return mHasInflationError;
     }
 
-    void setHasInflationError(boolean hasError) {
+    /**
+     * Set whether the notification has an error while inflating.
+     *
+     * TODO: Move this into an inflation error manager class.
+     */
+    public void setHasInflationError(boolean hasError) {
         mHasInflationError = hasError;
     }
 
@@ -595,12 +600,8 @@
 
     public void setInflationTask(InflationTask abortableTask) {
         // abort any existing inflation
-        InflationTask existing = mRunningTask;
         abortTask();
         mRunningTask = abortableTask;
-        if (existing != null && mRunningTask != null) {
-            mRunningTask.supersedeTask(existing);
-        }
     }
 
     public void onInflationTaskFinished() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
index 2a7683a..59d82a1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/inflation/NotificationRowBinderImpl.java
@@ -42,8 +42,11 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.NotifBindPipeline;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder;
+import com.android.systemui.statusbar.notification.row.RowContentBindParams;
+import com.android.systemui.statusbar.notification.row.RowContentBindStage;
 import com.android.systemui.statusbar.notification.row.RowInflaterTask;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
@@ -67,8 +70,10 @@
     private final NotificationGroupManager mGroupManager;
     private final NotificationGutsManager mGutsManager;
     private final NotificationInterruptionStateProvider mNotificationInterruptionStateProvider;
+
     private final Context mContext;
-    private final NotificationRowContentBinder mRowContentBinder;
+    private final NotifBindPipeline mNotifBindPipeline;
+    private final RowContentBindStage mRowContentBindStage;
     private final NotificationMessagingUtil mMessagingUtil;
     private final ExpandableNotificationRow.ExpansionLogger mExpansionLogger =
             this::logNotificationExpansion;
@@ -93,7 +98,8 @@
             Context context,
             NotificationRemoteInputManager notificationRemoteInputManager,
             NotificationLockscreenUserManager notificationLockscreenUserManager,
-            NotificationRowContentBinder rowContentBinder,
+            NotifBindPipeline notifBindPipeline,
+            RowContentBindStage rowContentBindStage,
             @Named(ALLOW_NOTIFICATION_LONG_PRESS_NAME) boolean allowLongPress,
             KeyguardBypassController keyguardBypassController,
             StatusBarStateController statusBarStateController,
@@ -103,7 +109,8 @@
             Provider<RowInflaterTask> rowInflaterTaskProvider,
             NotificationLogger logger) {
         mContext = context;
-        mRowContentBinder = rowContentBinder;
+        mNotifBindPipeline = notifBindPipeline;
+        mRowContentBindStage = rowContentBindStage;
         mMessagingUtil = new NotificationMessagingUtil(context);
         mNotificationRemoteInputManager = notificationRemoteInputManager;
         mNotificationLockscreenUserManager = notificationLockscreenUserManager;
@@ -167,6 +174,7 @@
         }
     }
 
+    //TODO: This method associates a row with an entry, but eventually needs to not do that
     private void bindRow(NotificationEntry entry, PackageManager pmUser,
             StatusBarNotification sbn, ExpandableNotificationRow row,
             Runnable onDismissRunnable) {
@@ -195,12 +203,11 @@
                 mKeyguardBypassController,
                 mGroupManager,
                 mHeadsUpManager,
-                mRowContentBinder,
+                mRowContentBindStage,
                 mPresenter);
 
         // TODO: Either move these into ExpandableNotificationRow#initialize or out of row entirely
         row.setStatusBarStateController(mStatusBarStateController);
-        row.setInflationCallback(mInflationCallback);
         row.setAppOpsOnClickListener(mOnAppOpsClickListener);
         if (mAllowLongPress) {
             row.setLongPressListener(mGutsManager::openGuts);
@@ -214,6 +221,10 @@
             row.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
         }
 
+        entry.setRow(row);
+        row.setEntry(entry);
+        mNotifBindPipeline.manageRow(entry, row);
+
         mBindRowCallback.onBindRow(entry, pmUser, sbn, row);
     }
 
@@ -247,13 +258,11 @@
         }
     }
 
-    //TODO: This method associates a row with an entry, but eventually needs to not do that
     private void updateNotification(
             NotificationEntry entry,
             PackageManager pmUser,
             StatusBarNotification sbn,
             ExpandableNotificationRow row) {
-        row.setIsLowPriority(entry.isAmbient());
 
         // Extract target SDK version.
         try {
@@ -268,22 +277,30 @@
         // TODO: should updates to the entry be happening somewhere else?
         entry.setIconTag(R.id.icon_is_pre_L, entry.targetSdk < Build.VERSION_CODES.LOLLIPOP);
 
-        entry.setRow(row);
         row.setOnActivatedListener(mPresenter);
 
-        boolean useIncreasedCollapsedHeight =
+        final boolean useIncreasedCollapsedHeight =
                 mMessagingUtil.isImportantMessaging(sbn, entry.getImportance());
-        boolean useIncreasedHeadsUp = useIncreasedCollapsedHeight
+        final boolean useIncreasedHeadsUp = useIncreasedCollapsedHeight
                 && !mPresenter.isPresenterFullyCollapsed();
-        row.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight);
-        row.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp);
-        row.setEntry(entry);
+        final boolean isLowPriority = entry.isAmbient();
+
+        RowContentBindParams params = mRowContentBindStage.getStageParams(entry);
+        params.setUseIncreasedCollapsedHeight(useIncreasedCollapsedHeight);
+        params.setUseIncreasedHeadsUpHeight(useIncreasedHeadsUp);
+        params.setUseLowPriority(entry.isAmbient());
 
         if (mNotificationInterruptionStateProvider.shouldHeadsUp(entry)) {
-            row.setInflationFlags(FLAG_CONTENT_VIEW_HEADS_UP);
+            params.requireContentViews(FLAG_CONTENT_VIEW_HEADS_UP);
         }
+        //TODO: Replace this API with RowContentBindParams directly
         row.setNeedsRedaction(mNotificationLockscreenUserManager.needsRedaction(entry));
-        row.inflateViews();
+        mRowContentBindStage.requestRebind(entry, en -> {
+            row.setUsesIncreasedCollapsedHeight(useIncreasedCollapsedHeight);
+            row.setUsesIncreasedHeadsUpHeight(useIncreasedHeadsUp);
+            row.setIsLowPriority(isLowPriority);
+            mInflationCallback.onAsyncInflationFinished(en);
+        });
 
         // bind the click event to the content area
         Objects.requireNonNull(mNotificationClicker).register(row, sbn);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CommonNotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CommonNotifCollection.java
new file mode 100644
index 0000000..171816f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CommonNotifCollection.java
@@ -0,0 +1,37 @@
+/*
+ * 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.collection.notifcollection;
+
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+
+/**
+ * A notification collection that manages the list of {@link NotificationEntry}s that will be
+ * rendered.
+ *
+ * TODO: (b/145659174) Once we fully switch off {@link NotificationEntryManager} to
+ * {@link NotifPipeline}, we probably won't need this, but having it for now makes it easy to
+ * switch between the two.
+ */
+public interface CommonNotifCollection {
+    /**
+     * Registers a listener to be informed when notifications are created, added, updated, removed,
+     * or deleted.
+     */
+    void addCollectionListener(NotifCollectionListener listener);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index c7666e4..39f4dfa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -19,6 +19,10 @@
 import android.content.Context;
 
 import com.android.systemui.R;
+import com.android.systemui.statusbar.FeatureFlags;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotifPipeline;
+import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
 import com.android.systemui.statusbar.notification.init.NotificationsController;
 import com.android.systemui.statusbar.notification.init.NotificationsControllerImpl;
 import com.android.systemui.statusbar.notification.init.NotificationsControllerStub;
@@ -45,4 +49,16 @@
             return stubController.get();
         }
     }
+
+    /**
+     * Provide the active notification collection managing the notifications to render.
+     */
+    @Provides
+    @Singleton
+    public CommonNotifCollection provideCommonNotifCollection(
+            FeatureFlags featureFlags,
+            Lazy<NotifPipeline> pipeline,
+            NotificationEntryManager entryManager) {
+        return featureFlags.isNewNotifPipelineRenderingEnabled() ? pipeline.get() : entryManager;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
index 61e3192..254b64f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.statusbar.notification.NotificationListController
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinderImpl
 import com.android.systemui.statusbar.notification.collection.init.NotifPipelineInitializer
+import com.android.systemui.statusbar.notification.row.NotifBindPipelineInitializer
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer
 import com.android.systemui.statusbar.phone.NotificationGroupAlertTransferHelper
 import com.android.systemui.statusbar.phone.NotificationGroupManager
@@ -55,6 +56,7 @@
     private val notificationListener: NotificationListener,
     private val entryManager: NotificationEntryManager,
     private val newNotifPipeline: Lazy<NotifPipelineInitializer>,
+    private val notifBindPipelineInitializer: NotifBindPipelineInitializer,
     private val deviceProvisionedController: DeviceProvisionedController,
     private val notificationRowBinder: NotificationRowBinderImpl,
     private val remoteInputUriController: RemoteInputUriController,
@@ -98,6 +100,7 @@
         if (featureFlags.isNewNotifPipelineRenderingEnabled) {
             // TODO
         } else {
+            notifBindPipelineInitializer.initialize()
             notificationRowBinder.setInflationCallback(entryManager)
 
             remoteInputUriController.attach(entryManager)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt
index 88b4147..fc221d4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/people/PeopleHubNotificationListener.kt
@@ -93,8 +93,7 @@
     private val peopleHubManagerForUser = SparseArray<PeopleHubManager>()
 
     private val notificationEntryListener = object : NotificationEntryListener {
-        override fun onEntryInflated(entry: NotificationEntry, inflatedFlags: Int) =
-                addVisibleEntry(entry)
+        override fun onEntryInflated(entry: NotificationEntry) = addVisibleEntry(entry)
 
         override fun onEntryReinflated(entry: NotificationEntry) = addVisibleEntry(entry)
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindRequester.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindRequester.java
new file mode 100644
index 0000000..1cf6b4f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindRequester.java
@@ -0,0 +1,76 @@
+/*
+ * 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.row;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.os.CancellationSignal;
+
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.row.NotifBindPipeline.BindCallback;
+
+/**
+ * A {@link BindRequester} is a general superclass for something that notifies
+ * {@link NotifBindPipeline} when it needs it to kick off a bind run.
+ */
+public abstract class BindRequester {
+    private @Nullable BindRequestListener mBindRequestListener;
+
+    /**
+     * Notifies the listener that some parameters/state has changed for some notification and that
+     * content needs to be bound again.
+     *
+     * The caller can also specify a callback for when the entire bind pipeline completes, i.e.
+     * when the change is fully propagated to the final view. The caller can cancel this
+     * callback with the returned cancellation signal.
+     *
+     * @param callback callback after bind completely finishes
+     * @return cancellation signal to cancel callback
+     */
+    public final CancellationSignal requestRebind(
+            @NonNull NotificationEntry entry,
+            @Nullable BindCallback callback) {
+        CancellationSignal signal = new CancellationSignal();
+        if (mBindRequestListener != null) {
+            mBindRequestListener.onBindRequest(entry, signal, callback);
+        }
+        return signal;
+    }
+
+    final void setBindRequestListener(BindRequestListener listener) {
+        mBindRequestListener = listener;
+    }
+
+    /**
+     * Listener interface for when content needs to be bound again.
+     */
+    public interface BindRequestListener {
+
+        /**
+         * Called when {@link #requestRebind} is called.
+         *
+         * @param entry notification that has outdated content
+         * @param signal cancellation signal to cancel callback
+         * @param callback callback after content is fully updated
+         */
+        void onBindRequest(
+                @NonNull NotificationEntry entry,
+                @NonNull CancellationSignal signal,
+                @Nullable BindCallback callback);
+
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindStage.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindStage.java
new file mode 100644
index 0000000..29447ca
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BindStage.java
@@ -0,0 +1,104 @@
+/*
+ * 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.row;
+
+import android.annotation.MainThread;
+import android.util.ArrayMap;
+
+import androidx.annotation.NonNull;
+
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+
+import java.util.Map;
+
+/**
+ * A {@link BindStage} is an abstraction for a unit of work in inflating/binding/unbinding
+ * views to a notification. Used by {@link NotifBindPipeline}.
+ *
+ * Clients may also use {@link #getStageParams} to provide parameters for this stage for a given
+ * notification and request a rebind.
+ *
+ * @param <Params> params to do this stage
+ */
+@MainThread
+public abstract class BindStage<Params> extends BindRequester {
+
+    private Map<NotificationEntry, Params> mContentParams = new ArrayMap<>();
+
+    /**
+     * Execute the stage asynchronously.
+     *
+     * @param row notification top-level view to bind views to
+     * @param callback callback after stage finishes
+     */
+    protected abstract void executeStage(
+            @NonNull NotificationEntry entry,
+            @NonNull ExpandableNotificationRow row,
+            @NonNull StageCallback callback);
+
+    /**
+     * Abort the stage if in progress.
+     *
+     * @param row notification top-level view to bind views to
+     */
+    protected abstract void abortStage(
+            @NonNull NotificationEntry entry,
+            @NonNull ExpandableNotificationRow row);
+
+    /**
+     * Get the stage parameters for the entry. Clients should use this to modify how the stage
+     * handles the notification content.
+     */
+    public final Params getStageParams(@NonNull NotificationEntry entry) {
+        Params params = mContentParams.get(entry);
+        if (params == null) {
+            throw new IllegalStateException(
+                    String.format("Entry does not have any stage parameters. key: %s",
+                            entry.getKey()));
+        }
+        return params;
+    }
+
+    /**
+     * Create a params entry for the notification for this stage.
+     */
+    final void createStageParams(@NonNull NotificationEntry entry) {
+        mContentParams.put(entry, newStageParams());
+    }
+
+    /**
+     * Delete params entry for notification.
+     */
+    final void deleteStageParams(@NonNull NotificationEntry entry) {
+        mContentParams.remove(entry);
+    }
+
+    /**
+     * Create a new, empty stage params object.
+     */
+    protected abstract Params newStageParams();
+
+    /**
+     * Interface for callback.
+     */
+    interface StageCallback {
+        /**
+         * Callback for when the stage is complete.
+         */
+        void onStageFinished(NotificationEntry entry);
+    }
+}
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 253be2fc..c34bba7 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
@@ -19,8 +19,6 @@
 import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters;
 import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_CONTRACTED;
 import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP;
-import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED;
-import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED;
 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP;
 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC;
 
@@ -88,8 +86,6 @@
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.logging.NotificationCounters;
-import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams;
-import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationCallback;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
 import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
 import com.android.systemui.statusbar.notification.stack.AmbientState;
@@ -127,14 +123,6 @@
     public static final float DEFAULT_HEADER_VISIBLE_AMOUNT = 1.0f;
     private static final long RECENTLY_ALERTED_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(30);
 
-    /**
-     * Content views that must be inflated at all times.
-     */
-    @InflationFlag
-    static final int REQUIRED_INFLATION_FLAGS =
-            FLAG_CONTENT_VIEW_CONTRACTED
-            | FLAG_CONTENT_VIEW_EXPANDED;
-
     private boolean mUpdateBackgroundOnUpdate;
     private boolean mNotificationTranslationFinished = false;
 
@@ -149,7 +137,7 @@
     private StatusBarStateController mStatusbarStateController;
     private KeyguardBypassController mBypassController;
     private LayoutListener mLayoutListener;
-    private NotificationRowContentBinder mNotificationContentBinder;
+    private RowContentBindStage mRowContentBindStage;
     private int mIconTransformContentShift;
     private int mIconTransformContentShiftNoIcon;
     private int mMaxHeadsUpHeightBeforeN;
@@ -244,10 +232,7 @@
     private ExpandableNotificationRow mNotificationParent;
     private OnExpandClickListener mOnExpandClickListener;
     private View.OnClickListener mOnAppOpsClickListener;
-    private InflationCallback mInflationCallback;
     private boolean mIsChildInGroup;
-    private @InflationFlag int mInflationFlags = REQUIRED_INFLATION_FLAGS;
-    private final BindParams mBindParams = new BindParams();
 
     // Listener will be called when receiving a long click event.
     // Use #setLongPressPosition to optionally assign positional data with the long press.
@@ -460,24 +445,25 @@
     }
 
     /**
-     * Inflate views based off the inflation flags set. Inflation happens asynchronously.
-     */
-    public void inflateViews() {
-        mNotificationContentBinder.bindContent(mEntry, this, mInflationFlags, mBindParams,
-                false /* forceInflate */, mInflationCallback);
-    }
-
-    /**
      * Marks a content view as freeable, setting it so that future inflations do not reinflate
      * and ensuring that the view is freed when it is safe to remove.
      *
+     * TODO: This should be moved to the respective coordinator and call
+     * {@link RowContentBindParams#freeContentViews} directly after disappear animation
+     * finishes instead of depending on binding API to know when it's "safe".
+     *
      * @param inflationFlag flag corresponding to the content view to be freed
      */
     public void freeContentViewWhenSafe(@InflationFlag int inflationFlag) {
         // View should not be reinflated in the future
-        clearInflationFlags(inflationFlag);
-        Runnable freeViewRunnable =
-                () -> mNotificationContentBinder.unbindContent(mEntry, this, inflationFlag);
+        Runnable freeViewRunnable = () -> {
+            // Possible for notification to be removed after free request.
+            if (!isRemoved()) {
+                RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry);
+                params.freeContentViews(inflationFlag);
+                mRowContentBindStage.requestRebind(mEntry, null /* callback */);
+            }
+        };
         switch (inflationFlag) {
             case FLAG_CONTENT_VIEW_HEADS_UP:
                 getPrivateLayout().performWhenContentInactive(VISIBLE_TYPE_HEADSUP,
@@ -492,35 +478,6 @@
     }
 
     /**
-     * Set flags for content views that should be inflated
-     *
-     * @param flags flags to inflate
-     */
-    public void setInflationFlags(@InflationFlag int flags) {
-        mInflationFlags |= flags;
-    }
-
-    /**
-     * Clear flags for content views that should not be inflated
-     *
-     * @param flags flags that should not be inflated
-     */
-    public void clearInflationFlags(@InflationFlag int flags) {
-        mInflationFlags &= ~flags;
-        mInflationFlags |= REQUIRED_INFLATION_FLAGS;
-    }
-
-    /**
-     * Whether or not a content view should be inflated.
-     *
-     * @param flag the flag corresponding to the content view
-     * @return true if the flag is set, false otherwise
-     */
-    public boolean isInflationFlagSet(@InflationFlag int flag) {
-        return ((mInflationFlags & flag) != 0);
-    }
-
-    /**
      * Caches whether or not this row contains a system notification. Note, this is only cached
      * once per notification as the packageInfo can't technically change for a notification row.
      */
@@ -838,13 +795,13 @@
         }
         mNotificationParent = isChildInGroup ? parent : null;
         mPrivateLayout.setIsChildInGroup(isChildInGroup);
-        mBindParams.isChildInGroup = isChildInGroup;
+        // TODO: Move inflation logic out of this call
         if (mIsChildInGroup != isChildInGroup) {
             mIsChildInGroup = isChildInGroup;
             if (mIsLowPriority) {
-                int flags = FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED;
-                mNotificationContentBinder.bindContent(mEntry, this, flags, mBindParams,
-                        false /* forceInflate */, mInflationCallback);
+                RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry);
+                params.setUseLowPriority(mIsLowPriority);
+                mRowContentBindStage.requestRebind(mEntry, null /* callback */);
             }
         }
         resetBackgroundAlpha();
@@ -1243,8 +1200,10 @@
             l.reInflateViews();
         }
         mEntry.getSbn().clearPackageContext();
-        mNotificationContentBinder.bindContent(mEntry, this, mInflationFlags, mBindParams,
-                true /* forceInflate */, mInflationCallback);
+        // TODO: Move content inflation logic out of this call
+        RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry);
+        params.setNeedsReinflation(true);
+        mRowContentBindStage.requestRebind(mEntry, null /* callback */);
     }
 
     @Override
@@ -1598,7 +1557,6 @@
     public void setIsLowPriority(boolean isLowPriority) {
         mIsLowPriority = isLowPriority;
         mPrivateLayout.setIsLowPriority(isLowPriority);
-        mBindParams.isLowPriority = mIsLowPriority;
         if (mChildrenContainer != null) {
             mChildrenContainer.setIsLowPriority(isLowPriority);
         }
@@ -1608,36 +1566,25 @@
         return mIsLowPriority;
     }
 
-    public void setUseIncreasedCollapsedHeight(boolean use) {
+    public void setUsesIncreasedCollapsedHeight(boolean use) {
         mUseIncreasedCollapsedHeight = use;
-        mBindParams.usesIncreasedHeight = use;
     }
 
-    public void setUseIncreasedHeadsUpHeight(boolean use) {
+    public void setUsesIncreasedHeadsUpHeight(boolean use) {
         mUseIncreasedHeadsUpHeight = use;
-        mBindParams.usesIncreasedHeadsUpHeight = use;
-    }
-
-    /**
-     * Set callback for notification content inflation
-     *
-     * @param callback inflation callback
-     */
-    public void setInflationCallback(InflationCallback callback) {
-        mInflationCallback = callback;
     }
 
     public void setNeedsRedaction(boolean needsRedaction) {
+        // TODO: Move inflation logic out of this call and remove this method
         if (mNeedsRedaction != needsRedaction) {
             mNeedsRedaction = needsRedaction;
+            RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry);
             if (needsRedaction) {
-                setInflationFlags(FLAG_CONTENT_VIEW_PUBLIC);
-                mNotificationContentBinder.bindContent(mEntry, this, FLAG_CONTENT_VIEW_PUBLIC,
-                        mBindParams, false /* forceInflate */, mInflationCallback);
+                params.requireContentViews(FLAG_CONTENT_VIEW_PUBLIC);
             } else {
-                clearInflationFlags(FLAG_CONTENT_VIEW_PUBLIC);
-                freeContentViewWhenSafe(FLAG_CONTENT_VIEW_PUBLIC);
+                params.freeContentViews(FLAG_CONTENT_VIEW_PUBLIC);
             }
+            mRowContentBindStage.requestRebind(mEntry, null /* callback */);
         }
     }
 
@@ -1664,7 +1611,7 @@
             KeyguardBypassController bypassController,
             NotificationGroupManager groupManager,
             HeadsUpManager headsUpManager,
-            NotificationRowContentBinder rowContentBinder,
+            RowContentBindStage rowContentBindStage,
             OnExpandClickListener onExpandClickListener) {
         mAppName = appName;
         if (mMenuRow != null && mMenuRow.getMenuView() != null) {
@@ -1676,7 +1623,7 @@
         mGroupManager = groupManager;
         mPrivateLayout.setGroupManager(groupManager);
         mHeadsUpManager = headsUpManager;
-        mNotificationContentBinder = rowContentBinder;
+        mRowContentBindStage = rowContentBindStage;
         mOnExpandClickListener = onExpandClickListener;
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java
new file mode 100644
index 0000000..af2d084
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipeline.java
@@ -0,0 +1,207 @@
+/*
+ * 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.row;
+
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.widget.FrameLayout;
+
+import androidx.annotation.MainThread;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.os.CancellationSignal;
+
+import com.android.internal.statusbar.NotificationVisibility;
+import com.android.systemui.statusbar.notification.NotificationEntryListener;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
+
+import java.util.Map;
+import java.util.Set;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * {@link NotifBindPipeline} is responsible for converting notifications from their data form to
+ * their actual inflated views. It is essentially a control class that composes notification view
+ * binding logic (i.e. {@link BindStage}) in response to explicit bind requests. At the end of the
+ * pipeline, the notification's bound views are guaranteed to be correct and up-to-date, and any
+ * registered callbacks will be called.
+ *
+ * The pipeline ensures that a notification's top-level view and its content views are bound.
+ * Currently, a notification's top-level view, the {@link ExpandableNotificationRow} is essentially
+ * just a {@link FrameLayout} for various different content views that are switched in and out as
+ * appropriate. These include a contracted view, expanded view, heads up view, and sensitive view on
+ * keyguard. See {@link InflationFlag}. These content views themselves can have child views added
+ * on depending on different factors. For example, notification actions and smart replies are views
+ * that are dynamically added to these content views after they're inflated. Finally, aside from
+ * the app provided content views, System UI itself also provides some content views that are shown
+ * occasionally (e.g. {@link NotificationGuts}). Many of these are business logic specific views
+ * and the requirements surrounding them may change over time, so the pipeline must handle
+ * composing the logic as necessary.
+ *
+ * Note that bind requests do not only occur from add/updates from updates from the app. For
+ * example, the user may make changes to device settings (e.g. sensitive notifications on lock
+ * screen) or we may want to make certain optimizations for the sake of memory or performance (e.g
+ * freeing views when not visible). Oftentimes, we also need to wait for these changes to complete
+ * before doing something else (e.g. moving a notification to the top of the screen to heads up).
+ * The pipeline thus handles bind requests from across the system and provides a way for
+ * requesters to know when the change is propagated to the view.
+ *
+ * Right now, we only support one attached {@link BindStage} which just does all the binding but we
+ * should eventually support multiple stages once content inflation is made more modular.
+ * In particular, row inflation/binding, which is handled by {@link NotificationRowBinder} should
+ * probably be moved here in the future as a stage. Right now, the pipeline just manages content
+ * views and assumes that a row is given to it when it's inflated.
+ */
+@MainThread
+@Singleton
+public final class NotifBindPipeline {
+    private final Map<NotificationEntry, BindEntry> mBindEntries = new ArrayMap<>();
+    private BindStage mStage;
+
+    @Inject
+    NotifBindPipeline(NotificationEntryManager entryManager) {
+        entryManager.addNotificationEntryListener(mEntryListener);
+    }
+
+    /**
+     * Set the bind stage for binding notification row content.
+     */
+    public void setStage(
+            BindStage stage) {
+        mStage = stage;
+        mStage.setBindRequestListener(this::onBindRequested);
+    }
+
+    /**
+     * Start managing the row's content for a given notification.
+     */
+    public void manageRow(
+            @NonNull NotificationEntry entry,
+            @NonNull ExpandableNotificationRow row) {
+        final BindEntry bindEntry = getBindEntry(entry);
+        bindEntry.row = row;
+        if (bindEntry.invalidated) {
+            startPipeline(entry);
+        }
+    }
+
+    private void onBindRequested(
+            @NonNull NotificationEntry entry,
+            @NonNull CancellationSignal signal,
+            @Nullable BindCallback callback) {
+        final BindEntry bindEntry = getBindEntry(entry);
+        if (bindEntry == null) {
+            // Invalidating views for a notification that is not active.
+            return;
+        }
+
+        bindEntry.invalidated = true;
+
+        // Put in new callback.
+        if (callback != null) {
+            final Set<BindCallback> callbacks = bindEntry.callbacks;
+            callbacks.add(callback);
+            signal.setOnCancelListener(() -> callbacks.remove(callback));
+        }
+
+        startPipeline(entry);
+    }
+
+    /**
+     * Run the pipeline for the notification, ensuring all views are bound when finished. Call all
+     * callbacks when the run finishes. If a run is already in progress, it is restarted.
+     */
+    private void startPipeline(NotificationEntry entry) {
+        if (mStage == null) {
+            throw new IllegalStateException("No stage was ever set on the pipeline");
+        }
+
+        final BindEntry bindEntry = mBindEntries.get(entry);
+        final ExpandableNotificationRow row = bindEntry.row;
+        if (row == null) {
+            // Row is not managed yet but may be soon. Stop for now.
+            return;
+        }
+
+        mStage.abortStage(entry, row);
+        mStage.executeStage(entry, row, (en) -> onPipelineComplete(en));
+    }
+
+    private void onPipelineComplete(NotificationEntry entry) {
+        final BindEntry bindEntry = getBindEntry(entry);
+
+        bindEntry.invalidated = false;
+
+        final Set<BindCallback> callbacks = bindEntry.callbacks;
+        for (BindCallback cb : callbacks) {
+            cb.onBindFinished(entry);
+        }
+        callbacks.clear();
+    }
+
+    //TODO: Move this to onManageEntry hook when we split that from add/remove
+    private final NotificationEntryListener mEntryListener = new NotificationEntryListener() {
+        @Override
+        public void onPendingEntryAdded(NotificationEntry entry) {
+            mBindEntries.put(entry, new BindEntry());
+            mStage.createStageParams(entry);
+        }
+
+        @Override
+        public void onEntryRemoved(NotificationEntry entry,
+                @Nullable NotificationVisibility visibility,
+                boolean removedByUser) {
+            BindEntry bindEntry = mBindEntries.remove(entry);
+            ExpandableNotificationRow row = bindEntry.row;
+            if (row != null) {
+                mStage.abortStage(entry, row);
+            }
+            mStage.deleteStageParams(entry);
+        }
+    };
+
+    private @NonNull BindEntry getBindEntry(NotificationEntry entry) {
+        final BindEntry bindEntry = mBindEntries.get(entry);
+        if (bindEntry == null) {
+            throw new IllegalStateException(
+                    String.format("Attempting bind on an inactive notification. key: %s",
+                            entry.getKey()));
+        }
+        return bindEntry;
+    }
+
+    /**
+     * Interface for bind callback.
+     */
+    public interface BindCallback {
+        /**
+         * Called when all views are fully bound on the notification.
+         */
+        void onBindFinished(NotificationEntry entry);
+    }
+
+    private class BindEntry {
+        public ExpandableNotificationRow row;
+        public final Set<BindCallback> callbacks = new ArraySet<>();
+        public boolean invalidated;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineInitializer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineInitializer.java
new file mode 100644
index 0000000..7754991
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineInitializer.java
@@ -0,0 +1,47 @@
+/*
+ * 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.row;
+
+import javax.inject.Inject;
+
+/**
+ * Initialize {@link NotifBindPipeline} with all its mandatory stages and dynamically added stages.
+ *
+ * In the future, coordinators should be able to register their own {@link BindStage} to the
+ * {@link NotifBindPipeline}.
+ */
+public class NotifBindPipelineInitializer {
+    NotifBindPipeline mNotifBindPipeline;
+    RowContentBindStage mRowContentBindStage;
+
+    @Inject
+    NotifBindPipelineInitializer(
+            NotifBindPipeline pipeline,
+            RowContentBindStage stage) {
+        mNotifBindPipeline = pipeline;
+        mRowContentBindStage = stage;
+        // TODO: Inject coordinators and allow them to add BindStages in initialize
+    }
+
+    /**
+     * Hooks up stages to the pipeline.
+     */
+    public void initialize() {
+        // Mandatory bind stages
+        mNotifBindPipeline.setStage(mRowContentBindStage);
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImpl.java
index a6e5c2b..b62dfa8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImpl.java
@@ -22,10 +22,9 @@
 
 import androidx.annotation.Nullable;
 
-import com.android.internal.statusbar.NotificationVisibility;
-import com.android.systemui.statusbar.notification.NotificationEntryListener;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
 
 import java.util.Map;
@@ -40,8 +39,8 @@
             new ArrayMap<>();
 
     @Inject
-    NotifRemoteViewCacheImpl(NotificationEntryManager entryManager) {
-        entryManager.addNotificationEntryListener(mEntryListener);
+    NotifRemoteViewCacheImpl(CommonNotifCollection collection) {
+        collection.addCollectionListener(mCollectionListener);
     }
 
     @Override
@@ -93,17 +92,14 @@
         contentViews.clear();
     }
 
-    private final NotificationEntryListener mEntryListener = new NotificationEntryListener() {
+    private final NotifCollectionListener mCollectionListener = new NotifCollectionListener() {
         @Override
-        public void onPendingEntryAdded(NotificationEntry entry) {
+        public void onEntryInit(NotificationEntry entry) {
             mNotifCachedContentViews.put(entry, new SparseArray<>());
         }
 
         @Override
-        public void onEntryRemoved(
-                NotificationEntry entry,
-                @Nullable NotificationVisibility visibility,
-                boolean removedByUser) {
+        public void onEntryCleanUp(NotificationEntry entry) {
             mNotifCachedContentViews.remove(entry);
         }
     };
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
index e1a6747..566da65 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
@@ -68,7 +68,7 @@
     private final NotifRemoteViewCache mRemoteViewCache;
 
     @Inject
-    public NotificationContentInflater(
+    NotificationContentInflater(
             NotifRemoteViewCache remoteViewCache,
             NotificationRemoteInputManager remoteInputManager) {
         mRemoteViewCache = remoteViewCache;
@@ -575,7 +575,7 @@
             entry.headsUpStatusBarText = result.headsUpStatusBarText;
             entry.headsUpStatusBarTextPublic = result.headsUpStatusBarTextPublic;
             if (endListener != null) {
-                endListener.onAsyncInflationFinished(entry, reInflateFlags);
+                endListener.onAsyncInflationFinished(entry);
             }
             return true;
         }
@@ -641,7 +641,7 @@
         private final boolean mUsesIncreasedHeight;
         private final InflationCallback mCallback;
         private final boolean mUsesIncreasedHeadsUpHeight;
-        private @InflationFlag int mReInflateFlags;
+        private final @InflationFlag int mReInflateFlags;
         private final NotifRemoteViewCache mRemoteViewCache;
         private ExpandableNotificationRow mRow;
         private Exception mError;
@@ -739,25 +739,16 @@
         }
 
         @Override
-        public void supersedeTask(InflationTask task) {
-            if (task instanceof AsyncInflationTask) {
-                // We want to inflate all flags of the previous task as well
-                mReInflateFlags |= ((AsyncInflationTask) task).mReInflateFlags;
-            }
-        }
-
-        @Override
         public void handleInflationException(NotificationEntry entry, Exception e) {
             handleError(e);
         }
 
         @Override
-        public void onAsyncInflationFinished(NotificationEntry entry,
-                @InflationFlag int inflatedFlags) {
+        public void onAsyncInflationFinished(NotificationEntry entry) {
             mEntry.onInflationTaskFinished();
             mRow.onNotificationUpdated();
             if (mCallback != null) {
-                mCallback.onAsyncInflationFinished(mEntry, inflatedFlags);
+                mCallback.onAsyncInflationFinished(mEntry);
             }
 
             // Notify the resolver that the inflation task has finished,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java
index 9b95bff..9bd8d47 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowContentBinder.java
@@ -101,7 +101,7 @@
      */
     int FLAG_CONTENT_VIEW_PUBLIC = 1 << 3;
 
-    int FLAG_CONTENT_VIEW_ALL = ~0;
+    int FLAG_CONTENT_VIEW_ALL = (1 << 4) - 1;
 
     /**
      * Parameters for content view binding
@@ -146,9 +146,7 @@
          * Callback for after the content views finish inflating.
          *
          * @param entry the entry with the content views set
-         * @param inflatedFlags the flags associated with the content views that were inflated
          */
-        void onAsyncInflationFinished(NotificationEntry entry,
-                @InflationFlag int inflatedFlags);
+        void onAsyncInflationFinished(NotificationEntry entry);
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java
new file mode 100644
index 0000000..8280a63
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindParams.java
@@ -0,0 +1,157 @@
+/*
+ * 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.row;
+
+import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED;
+import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED;
+import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP;
+
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
+
+/**
+ * Parameters for {@link RowContentBindStage}.
+ */
+public final class RowContentBindParams {
+    private boolean mUseLowPriority;
+    private boolean mUseChildInGroup;
+    private boolean mUseIncreasedHeight;
+    private boolean mUseIncreasedHeadsUpHeight;
+    private boolean mViewsNeedReinflation;
+    private @InflationFlag int mContentViews = DEFAULT_INFLATION_FLAGS;
+
+    /**
+     * Content views that are out of date and need to be rebound.
+     *
+     * TODO: This should go away once {@link NotificationContentInflater} is broken down into
+     * smaller stages as then the stage itself would be invalidated.
+     */
+    private @InflationFlag int mDirtyContentViews = mContentViews;
+
+    /**
+     * Set whether content should use a low priority version of its content views.
+     */
+    public void setUseLowPriority(boolean useLowPriority) {
+        if (mUseLowPriority != useLowPriority) {
+            mDirtyContentViews |= (FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED);
+        }
+        mUseLowPriority = useLowPriority;
+    }
+
+    public boolean useLowPriority() {
+        return mUseLowPriority;
+    }
+
+    /**
+     * Set whether content should use group child version of its content views.
+     */
+    public void setUseChildInGroup(boolean useChildInGroup) {
+        if (mUseChildInGroup != useChildInGroup) {
+            mDirtyContentViews |= (FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED);
+        }
+        mUseChildInGroup = useChildInGroup;
+    }
+
+    public boolean useChildInGroup() {
+        return mUseChildInGroup;
+    }
+
+    /**
+     * Set whether content should use an increased height version of its contracted view.
+     */
+    public void setUseIncreasedCollapsedHeight(boolean useIncreasedHeight) {
+        if (mUseIncreasedHeight != useIncreasedHeight) {
+            mDirtyContentViews |= FLAG_CONTENT_VIEW_CONTRACTED;
+        }
+        mUseIncreasedHeight = useIncreasedHeight;
+    }
+
+    public boolean useIncreasedHeight() {
+        return mUseIncreasedHeight;
+    }
+
+    /**
+     * Set whether content should use an increased height version of its heads up view.
+     */
+    public void setUseIncreasedHeadsUpHeight(boolean useIncreasedHeadsUpHeight) {
+        if (mUseIncreasedHeadsUpHeight != useIncreasedHeadsUpHeight) {
+            mDirtyContentViews |= FLAG_CONTENT_VIEW_HEADS_UP;
+        }
+        mUseIncreasedHeadsUpHeight = useIncreasedHeadsUpHeight;
+    }
+
+    public boolean useIncreasedHeadsUpHeight() {
+        return mUseIncreasedHeadsUpHeight;
+    }
+
+    /**
+     * Require the specified content views to be bound after the rebind request.
+     *
+     * @see InflationFlag
+     */
+    public void requireContentViews(@InflationFlag int contentViews) {
+        @InflationFlag int newContentViews = contentViews &= ~mContentViews;
+        mContentViews |= contentViews;
+        mDirtyContentViews |= newContentViews;
+    }
+
+    /**
+     * Free the content view so that it will no longer be bound after the rebind request.
+     *
+     * @see InflationFlag
+     */
+    public void freeContentViews(@InflationFlag int contentViews) {
+        mContentViews &= ~contentViews;
+        mDirtyContentViews &= ~contentViews;
+    }
+
+    public @InflationFlag int getContentViews() {
+        return mContentViews;
+    }
+
+    /**
+     * Clears all dirty content views so that they no longer need to be rebound.
+     */
+    void clearDirtyContentViews() {
+        mDirtyContentViews = 0;
+    }
+
+    public @InflationFlag int getDirtyContentViews() {
+        return mDirtyContentViews;
+    }
+
+    /**
+     * Set whether all content views need to be reinflated even if cached.
+     *
+     * TODO: This should probably be a more global config on {@link NotifBindPipeline} since this
+     * generally corresponds to a Context/Configuration change that all stages should know about.
+     */
+    public void setNeedsReinflation(boolean needsReinflation) {
+        mViewsNeedReinflation = needsReinflation;
+        @InflationFlag int currentContentViews = mContentViews;
+        mDirtyContentViews |= currentContentViews;
+    }
+
+    public boolean needsReinflation() {
+        return mViewsNeedReinflation;
+    }
+
+    /**
+     * Content views that should be inflated by default for all notifications.
+     */
+    @InflationFlag private static final int DEFAULT_INFLATION_FLAGS =
+            FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED;
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java
new file mode 100644
index 0000000..f124179
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowContentBindStage.java
@@ -0,0 +1,118 @@
+/*
+ * 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.row;
+
+import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL;
+
+import android.os.RemoteException;
+import android.service.notification.StatusBarNotification;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.statusbar.IStatusBarService;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams;
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationCallback;
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+/**
+ * A stage that binds all content views for an already inflated {@link ExpandableNotificationRow}.
+ *
+ * In the farther future, the binder logic and consequently this stage should be broken into
+ * smaller stages.
+ */
+@Singleton
+public class RowContentBindStage extends BindStage<RowContentBindParams> {
+    private final NotificationRowContentBinder mBinder;
+    private final IStatusBarService mStatusBarService;
+
+    @Inject
+    RowContentBindStage(
+            NotificationRowContentBinder binder,
+            IStatusBarService statusBarService) {
+        mBinder = binder;
+        mStatusBarService = statusBarService;
+    }
+
+    @Override
+    protected void executeStage(
+            @NonNull NotificationEntry entry,
+            @NonNull ExpandableNotificationRow row,
+            @NonNull StageCallback callback) {
+        RowContentBindParams params = getStageParams(entry);
+
+        // Resolve content to bind/unbind.
+        @InflationFlag int inflationFlags = params.getContentViews();
+        @InflationFlag int invalidatedFlags = params.getDirtyContentViews();
+
+        @InflationFlag int contentToBind = invalidatedFlags & inflationFlags;
+        @InflationFlag int contentToUnbind = inflationFlags ^ FLAG_CONTENT_VIEW_ALL;
+
+        // Bind/unbind with parameters
+        mBinder.unbindContent(entry, row, contentToUnbind);
+
+        BindParams bindParams = new BindParams();
+        bindParams.isLowPriority = params.useLowPriority();
+        bindParams.isChildInGroup = params.useChildInGroup();
+        bindParams.usesIncreasedHeight = params.useIncreasedHeight();
+        bindParams.usesIncreasedHeadsUpHeight = params.useIncreasedHeadsUpHeight();
+        boolean forceInflate = params.needsReinflation();
+
+        InflationCallback inflationCallback = new InflationCallback() {
+            @Override
+            public void handleInflationException(NotificationEntry entry, Exception e) {
+                entry.setHasInflationError(true);
+                try {
+                    final StatusBarNotification sbn = entry.getSbn();
+                    mStatusBarService.onNotificationError(
+                            sbn.getPackageName(),
+                            sbn.getTag(),
+                            sbn.getId(),
+                            sbn.getUid(),
+                            sbn.getInitialPid(),
+                            e.getMessage(),
+                            sbn.getUserId());
+                } catch (RemoteException ex) {
+                }
+            }
+
+            @Override
+            public void onAsyncInflationFinished(NotificationEntry entry) {
+                entry.setHasInflationError(false);
+                getStageParams(entry).clearDirtyContentViews();
+                callback.onStageFinished(entry);
+            }
+        };
+        mBinder.cancelBind(entry, row);
+        mBinder.bindContent(entry, row, contentToBind, bindParams, forceInflate, inflationCallback);
+    }
+
+    @Override
+    protected void abortStage(
+            @NonNull NotificationEntry entry,
+            @NonNull ExpandableNotificationRow row) {
+        mBinder.cancelBind(entry, row);
+    }
+
+    @Override
+    protected RowContentBindParams newStageParams() {
+        return new RowContentBindParams();
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
index 896b6e5..bdca9a4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelper.java
@@ -28,11 +28,12 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
 import com.android.systemui.statusbar.AlertingNotificationManager;
-import com.android.systemui.statusbar.InflationTask;
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
+import com.android.systemui.statusbar.notification.row.RowContentBindParams;
+import com.android.systemui.statusbar.notification.row.RowContentBindStage;
 import com.android.systemui.statusbar.phone.NotificationGroupManager.NotificationGroup;
 import com.android.systemui.statusbar.phone.NotificationGroupManager.OnGroupChangeListener;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
@@ -67,6 +68,7 @@
     private final ArrayMap<String, PendingAlertInfo> mPendingAlerts = new ArrayMap<>();
 
     private HeadsUpManager mHeadsUpManager;
+    private final RowContentBindStage mRowContentBindStage;
     private final NotificationGroupManager mGroupManager =
             Dependency.get(NotificationGroupManager.class);
 
@@ -75,8 +77,9 @@
     private boolean mIsDozing;
 
     @Inject
-    public NotificationGroupAlertTransferHelper() {
+    public NotificationGroupAlertTransferHelper(RowContentBindStage bindStage) {
         Dependency.get(StatusBarStateController.class).addCallback(this);
+        mRowContentBindStage = bindStage;
     }
 
     /** Causes the TransferHelper to register itself as a listener to the appropriate classes. */
@@ -190,21 +193,6 @@
             }
         }
 
-        // Called when the entry's reinflation has finished. If there is an alert pending, we
-        // then show the alert.
-        @Override
-        public void onEntryReinflated(NotificationEntry entry) {
-            PendingAlertInfo alertInfo = mPendingAlerts.remove(entry.getKey());
-            if (alertInfo != null) {
-                if (alertInfo.isStillValid()) {
-                    alertNotificationWhenPossible(entry, mHeadsUpManager);
-                } else {
-                    // The transfer is no longer valid. Free the content.
-                    entry.getRow().freeContentViewWhenSafe(mHeadsUpManager.getContentFlag());
-                }
-            }
-        }
-
         @Override
         public void onEntryRemoved(
                 @Nullable NotificationEntry entry,
@@ -392,10 +380,21 @@
     private void alertNotificationWhenPossible(@NonNull NotificationEntry entry,
             @NonNull AlertingNotificationManager alertManager) {
         @InflationFlag int contentFlag = alertManager.getContentFlag();
-        if (!entry.getRow().isInflationFlagSet(contentFlag)) {
+        final RowContentBindParams params = mRowContentBindStage.getStageParams(entry);
+        if ((params.getContentViews() & contentFlag) == 0) {
             mPendingAlerts.put(entry.getKey(), new PendingAlertInfo(entry));
-            entry.getRow().setInflationFlags(contentFlag);
-            entry.getRow().inflateViews();
+            params.requireContentViews(contentFlag);
+            mRowContentBindStage.requestRebind(entry, en -> {
+                PendingAlertInfo alertInfo = mPendingAlerts.remove(entry.getKey());
+                if (alertInfo != null) {
+                    if (alertInfo.isStillValid()) {
+                        alertNotificationWhenPossible(entry, mHeadsUpManager);
+                    } else {
+                        // The transfer is no longer valid. Free the content.
+                        entry.getRow().freeContentViewWhenSafe(mHeadsUpManager.getContentFlag());
+                    }
+                }
+            });
             return;
         }
         if (alertManager.isAlerting(entry.getKey())) {
@@ -426,9 +425,9 @@
         /**
          * The notification is still pending inflation but we've decided that we no longer need
          * the content view (e.g. suppression might have changed and we decided we need to transfer
-         * back). However, there is no way to abort just this inflation if other inflation requests
-         * have started (see {@link InflationTask#supersedeTask(InflationTask)}). So instead
-         * we just flag it as aborted and free when it's inflated.
+         * back).
+         *
+         * TODO: Replace this entire structure with {@link RowContentBindStage#requestRebind)}.
          */
         boolean mAbortOnInflation;
 
diff --git a/packages/SystemUI/src/com/android/systemui/util/FloatingContentCoordinator.kt b/packages/SystemUI/src/com/android/systemui/util/FloatingContentCoordinator.kt
new file mode 100644
index 0000000..70bcc214
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/FloatingContentCoordinator.kt
@@ -0,0 +1,320 @@
+package com.android.systemui.util
+
+import android.graphics.Rect
+import android.util.Log
+import com.android.systemui.util.FloatingContentCoordinator.FloatingContent
+import java.util.HashMap
+import javax.inject.Inject
+import javax.inject.Singleton
+
+/** Tag for debug logging. */
+private const val TAG = "FloatingCoordinator"
+
+/**
+ * Coordinates the positions and movement of floating content, such as PIP and Bubbles, to ensure
+ * that they don't overlap. If content does overlap due to content appearing or moving, the
+ * coordinator will ask content to move to resolve the conflict.
+ *
+ * After implementing [FloatingContent], content should call [onContentAdded] to begin coordination.
+ * Subsequently, call [onContentMoved] whenever the content moves, and the coordinator will move
+ * other content out of the way. [onContentRemoved] should be called when the content is removed or
+ * no longer visible.
+ */
+@Singleton
+class FloatingContentCoordinator @Inject constructor() {
+
+    /**
+     * Represents a piece of floating content, such as PIP or the Bubbles stack. Provides methods
+     * that allow the [FloatingContentCoordinator] to determine the current location of the content,
+     * as well as the ability to ask it to move out of the way of other content.
+     *
+     * The default implementation of [calculateNewBoundsOnOverlap] moves the content up or down,
+     * depending on the position of the conflicting content. You can override this method if you
+     * want your own custom conflict resolution logic.
+     */
+    interface FloatingContent {
+
+        /**
+         * Return the bounds claimed by this content. This should include the bounds occupied by the
+         * content itself, as well as any padding, if desired. The coordinator will ensure that no
+         * other content is located within these bounds.
+         *
+         * If the content is animating, this method should return the bounds to which the content is
+         * animating. If that animation is cancelled, or updated, be sure that your implementation
+         * of this method returns the appropriate bounds, and call [onContentMoved] so that the
+         * coordinator moves other content out of the way.
+         */
+        fun getFloatingBoundsOnScreen(): Rect
+
+        /**
+         * Return the area within which this floating content is allowed to move. When resolving
+         * conflicts, the coordinator will never ask your content to move to a position where any
+         * part of the content would be out of these bounds.
+         */
+        fun getAllowedFloatingBoundsRegion(): Rect
+
+        /**
+         * Called when the coordinator needs this content to move to the given bounds. It's up to
+         * you how to do that.
+         *
+         * Note that if you start an animation to these bounds, [getFloatingBoundsOnScreen] should
+         * return the destination bounds, not the in-progress animated bounds. This is so the
+         * coordinator knows where floating content is going to be and can resolve conflicts
+         * accordingly.
+         */
+        fun moveToBounds(bounds: Rect)
+
+        /**
+         * Called by the coordinator when it needs to find a new home for this floating content,
+         * because a new or moving piece of content is now overlapping with it.
+         *
+         * [findAreaForContentVertically] and [findAreaForContentAboveOrBelow] are helpful utility
+         * functions that will find new bounds for your content automatically. Unless you require
+         * specific conflict resolution logic, these should be sufficient. By default, this method
+         * delegates to [findAreaForContentVertically].
+         *
+         * @param overlappingContentBounds The bounds of the other piece of content, which
+         * necessitated this content's relocation. Your new position must not overlap with these
+         * bounds.
+         * @param otherContentBounds The bounds of any other pieces of floating content. Your new
+         * position must not overlap with any of these either. These bounds are guaranteed to be
+         * non-overlapping.
+         * @return The new bounds for this content.
+         */
+        @JvmDefault
+        fun calculateNewBoundsOnOverlap(
+            overlappingContentBounds: Rect,
+            otherContentBounds: List<Rect>
+        ): Rect {
+            return findAreaForContentVertically(
+                    getFloatingBoundsOnScreen(),
+                    overlappingContentBounds,
+                    otherContentBounds,
+                    getAllowedFloatingBoundsRegion())
+        }
+    }
+
+    /** The bounds of all pieces of floating content added to the coordinator. */
+    private val allContentBounds: MutableMap<FloatingContent, Rect> = HashMap()
+
+    /**
+     * Makes the coordinator aware of a new piece of floating content, and moves any existing
+     * content out of the way, if necessary.
+     *
+     * If you don't want your new content to move existing content, use [getOccupiedBounds] to find
+     * an unoccupied area, and move the content there before calling this method.
+     */
+    fun onContentAdded(newContent: FloatingContent) {
+        updateContentBounds()
+        allContentBounds[newContent] = newContent.getFloatingBoundsOnScreen()
+        maybeMoveConflictingContent(newContent)
+    }
+
+    /**
+     * Called to notify the coordinator that a piece of floating content has moved (or is animating)
+     * to a new position, and that any conflicting floating content should be moved out of the way.
+     *
+     * The coordinator will call [FloatingContent.getFloatingBoundsOnScreen] to find the new bounds
+     * for the moving content. If you're animating the content, be sure that your implementation of
+     * getFloatingBoundsOnScreen returns the bounds to which it's animating, not the content's
+     * current bounds.
+     *
+     * If the animation moving this content is cancelled or updated, you'll need to call this method
+     * again, to ensure that content is moved out of the way of the latest bounds.
+     *
+     * @param content The content that has moved.
+     */
+    @JvmOverloads
+    fun onContentMoved(content: FloatingContent) {
+        if (!allContentBounds.containsKey(content)) {
+            Log.wtf(TAG, "Received onContentMoved call before onContentAdded! " +
+                    "This should never happen.")
+            return
+        }
+
+        updateContentBounds()
+        maybeMoveConflictingContent(content)
+    }
+
+    /**
+     * Called to notify the coordinator that a piece of floating content has been removed or is no
+     * longer visible.
+     */
+    fun onContentRemoved(removedContent: FloatingContent) {
+        allContentBounds.remove(removedContent)
+    }
+
+    /**
+     * Returns a set of Rects that represent the bounds of all of the floating content on the
+     * screen.
+     *
+     * [onContentAdded] will move existing content out of the way if the added content intersects
+     * existing content. That's fine - but if your specific starting position is not important, you
+     * can use this function to find unoccupied space for your content before calling
+     * [onContentAdded], so that moving existing content isn't necessary.
+     */
+    fun getOccupiedBounds(): Collection<Rect> {
+        return allContentBounds.values
+    }
+
+    /**
+     * Identifies any pieces of content that are now overlapping with the given content, and asks
+     * them to move out of the way.
+     */
+    private fun maybeMoveConflictingContent(fromContent: FloatingContent) {
+        val conflictingNewBounds = allContentBounds[fromContent]!!
+        allContentBounds
+                // Filter to content that intersects with the new bounds. That's content that needs
+                // to move.
+                .filter { (content, bounds) ->
+                    content != fromContent && Rect.intersects(conflictingNewBounds, bounds) }
+                // Tell that content to get out of the way, and save the bounds it says it's moving
+                // (or animating) to.
+                .forEach { (content, bounds) ->
+                    content.moveToBounds(
+                            content.calculateNewBoundsOnOverlap(
+                                    conflictingNewBounds,
+                                    // Pass all of the content bounds except the bounds of the
+                                    // content we're asking to move, and the conflicting new bounds
+                                    // (since those are passed separately).
+                                    otherContentBounds = allContentBounds.values
+                                            .minus(bounds)
+                                            .minus(conflictingNewBounds)))
+                    allContentBounds[content] = content.getFloatingBoundsOnScreen()
+                }
+    }
+
+    /**
+     * Update [allContentBounds] by calling [FloatingContent.getFloatingBoundsOnScreen] for all
+     * content and saving the result.
+     */
+    private fun updateContentBounds() {
+        allContentBounds.keys.forEach { allContentBounds[it] = it.getFloatingBoundsOnScreen() }
+    }
+
+    companion object {
+        /**
+         * Finds new bounds for the given content, either above or below its current position. The
+         * new bounds won't intersect with the newly overlapping rect or the exclusion rects, and
+         * will be within the allowed bounds unless no possible position exists.
+         *
+         * You can use this method to help find a new position for your content when the coordinator
+         * calls [FloatingContent.moveToAreaExcluding].
+         *
+         * @param contentRect The bounds of the content for which we're finding a new home.
+         * @param newlyOverlappingRect The bounds of the content that forced this relocation by
+         * intersecting with the content we now need to move. If the overlapping content is
+         * overlapping the top half of this content, we'll try to move this content downward if
+         * possible (since the other content is 'pushing' it down), and vice versa.
+         * @param exclusionRects Any other areas that we need to avoid when finding a new home for
+         * the content. These areas must be non-overlapping with each other.
+         * @param allowedBounds The area within which we're allowed to find new bounds for the
+         * content.
+         * @return New bounds for the content that don't intersect the exclusion rects or the
+         * newly overlapping rect, and that is within bounds unless no possible in-bounds position
+         * exists.
+         */
+        @JvmStatic
+        fun findAreaForContentVertically(
+            contentRect: Rect,
+            newlyOverlappingRect: Rect,
+            exclusionRects: Collection<Rect>,
+            allowedBounds: Rect
+        ): Rect {
+            // If the newly overlapping Rect's center is above the content's center, we'll prefer to
+            // find a space for this content that is below the overlapping content, since it's
+            // 'pushing' it down. This may not be possible due to to screen bounds, in which case
+            // we'll find space in the other direction.
+            val overlappingContentPushingDown =
+                    newlyOverlappingRect.centerY() < contentRect.centerY()
+
+            // Filter to exclusion rects that are above or below the content that we're finding a
+            // place for. Then, split into two lists - rects above the content, and rects below it.
+            var (rectsToAvoidAbove, rectsToAvoidBelow) = exclusionRects
+                    .filter { rectToAvoid -> rectsIntersectVertically(rectToAvoid, contentRect) }
+                    .partition { rectToAvoid -> rectToAvoid.top < contentRect.top }
+
+            // Lazily calculate the closest possible new tops for the content, above and below its
+            // current location.
+            val newContentBoundsAbove by lazy { findAreaForContentAboveOrBelow(
+                    contentRect,
+                    exclusionRects = rectsToAvoidAbove.plus(newlyOverlappingRect),
+                    findAbove = true) }
+            val newContentBoundsBelow by lazy { findAreaForContentAboveOrBelow(
+                    contentRect,
+                    exclusionRects = rectsToAvoidBelow.plus(newlyOverlappingRect),
+                    findAbove = false) }
+
+            val positionAboveInBounds by lazy { allowedBounds.contains(newContentBoundsAbove) }
+            val positionBelowInBounds by lazy { allowedBounds.contains(newContentBoundsBelow) }
+
+            // Use the 'below' position if the content is being overlapped from the top, unless it's
+            // out of bounds. Also use it if the content is being overlapped from the bottom, but
+            // the 'above' position is out of bounds. Otherwise, use the 'above' position.
+            val usePositionBelow =
+                    overlappingContentPushingDown && positionBelowInBounds ||
+                            !overlappingContentPushingDown && !positionAboveInBounds
+
+            // Return the content rect, but offset to reflect the new position.
+            return if (usePositionBelow) newContentBoundsBelow else newContentBoundsAbove
+        }
+
+        /**
+         * Finds a new position for the given content, either above or below its current position
+         * depending on whether [findAbove] is true or false, respectively. This new position will
+         * not intersect with any of the [exclusionRects].
+         *
+         * This method is useful as a helper method for implementing your own conflict resolution
+         * logic. Otherwise, you'd want to use [findAreaForContentVertically], which takes screen
+         * bounds and conflicting bounds' location into account when deciding whether to move to new
+         * bounds above or below the current bounds.
+         *
+         * @param contentRect The content we're finding an area for.
+         * @param exclusionRects The areas we need to avoid when finding a new area for the content.
+         * These areas must be non-overlapping with each other.
+         * @param findAbove Whether we are finding an area above the content's current position,
+         * rather than an area below it.
+         */
+        fun findAreaForContentAboveOrBelow(
+            contentRect: Rect,
+            exclusionRects: Collection<Rect>,
+            findAbove: Boolean
+        ): Rect {
+            // Sort the rects, since we want to move the content as little as possible. We'll
+            // start with the rects closest to the content and move outward. If we're finding an
+            // area above the content, that means we sort in reverse order to search the rects
+            // from highest to lowest y-value.
+            val sortedExclusionRects =
+                    exclusionRects.sortedBy { if (findAbove) -it.top else it.top }
+
+            val proposedNewBounds = Rect(contentRect)
+            for (exclusionRect in sortedExclusionRects) {
+                // If the proposed new bounds don't intersect with this exclusion rect, that
+                // means there's room for the content here. We know this because the rects are
+                // sorted and non-overlapping, so any subsequent exclusion rects would be higher
+                // (or lower) than this one and can't possibly intersect if this one doesn't.
+                if (!Rect.intersects(proposedNewBounds, exclusionRect)) {
+                    break
+                } else {
+                    // Otherwise, we need to keep searching for new bounds. If we're finding an
+                    // area above, propose new bounds that place the content just above the
+                    // exclusion rect. If we're finding an area below, propose new bounds that
+                    // place the content just below the exclusion rect.
+                    val verticalOffset =
+                            if (findAbove) -contentRect.height() else exclusionRect.height()
+                    proposedNewBounds.offsetTo(
+                            proposedNewBounds.left,
+                            exclusionRect.top + verticalOffset)
+                }
+            }
+
+            return proposedNewBounds
+        }
+
+        /** Returns whether or not the two Rects share any of the same space on the X axis. */
+        private fun rectsIntersectVertically(r1: Rect, r2: Rect): Boolean {
+            return (r1.left >= r2.left && r1.left <= r2.right) ||
+                    (r1.right <= r2.right && r1.right >= r2.left)
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java
index 364ee66..ffe8c28 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/ExpandHelperTest.java
@@ -31,8 +31,8 @@
 
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.statusbar.NotificationMediaManager;
-import com.android.systemui.statusbar.NotificationTestHelper;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
 import com.android.systemui.util.Assert;
 
 import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
index dcaf4ec..d7f0f50 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleControllerTest.java
@@ -62,7 +62,6 @@
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.NotificationRemoveInterceptor;
-import com.android.systemui.statusbar.NotificationTestHelper;
 import com.android.systemui.statusbar.SuperStatusBarViewFactory;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
@@ -72,6 +71,7 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
 import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent;
 import com.android.systemui.statusbar.phone.DozeParameters;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
index c9f5b40..f40fc94 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bubbles/BubbleDataTest.java
@@ -39,10 +39,10 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.bubbles.BubbleData.TimeSource;
-import com.android.systemui.statusbar.NotificationTestHelper;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
 
 import com.google.common.collect.ImmutableList;
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
index 63c911b5..60163f2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationViewHierarchyManagerTest.java
@@ -49,6 +49,7 @@
 import com.android.systemui.statusbar.notification.logging.NotificationLogger;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
+import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
 import com.android.systemui.statusbar.notification.stack.ForegroundServiceSectionController;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java
index 4103ede..9d667a9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/AboveShelfObserverTest.java
@@ -26,8 +26,8 @@
 import android.widget.FrameLayout;
 
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.NotificationTestHelper;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
 
 import org.junit.Assert;
 import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
index 20c67fa..b51581f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationEntryManagerTest.java
@@ -84,9 +84,10 @@
 import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationViewController;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
-import com.android.systemui.statusbar.notification.row.NotifRemoteViewCache;
-import com.android.systemui.statusbar.notification.row.NotificationContentInflater;
+import com.android.systemui.statusbar.notification.row.NotifBindPipeline;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
+import com.android.systemui.statusbar.notification.row.RowContentBindParams;
+import com.android.systemui.statusbar.notification.row.RowContentBindStage;
 import com.android.systemui.statusbar.notification.row.RowInflaterTask;
 import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
@@ -99,6 +100,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -206,20 +208,20 @@
 
         mEntry.expandedIcon = mock(StatusBarIconView.class);
 
-        NotificationContentInflater contentBinder = new NotificationContentInflater(
-                mock(NotifRemoteViewCache.class),
-                mRemoteInputManager);
-        contentBinder.setInflateSynchronously(true);
-
         when(mNotificationRowComponentBuilder.activatableNotificationView(any()))
                 .thenReturn(mNotificationRowComponentBuilder);
         when(mNotificationRowComponentBuilder.build()).thenReturn(
                 () -> mActivatableNotificationViewController);
+
+        RowContentBindStage bindStage = mock(RowContentBindStage.class);
+        when(bindStage.getStageParams(any())).thenReturn(new RowContentBindParams());
+
         NotificationRowBinderImpl notificationRowBinder =
                 new NotificationRowBinderImpl(mContext,
                         mRemoteInputManager,
                         mLockscreenUserManager,
-                        contentBinder,
+                        mock(NotifBindPipeline.class),
+                        bindStage,
                         true, /* allowLongPress */
                         mock(KeyguardBypassController.class),
                         mock(StatusBarStateController.class),
@@ -269,7 +271,10 @@
         mEntry.abortTask();
     }
 
+    // TODO: These tests are closer to functional tests and we should move them to their own file.
+    // and also strip some of the verifies that make the test too complex
     @Test
+    @Ignore
     public void testAddNotification() throws Exception {
         TestableLooper.get(this).processAllMessages();
 
@@ -306,6 +311,7 @@
     }
 
     @Test
+    @Ignore
     public void testUpdateNotification() throws Exception {
         TestableLooper.get(this).processAllMessages();
 
@@ -331,6 +337,7 @@
     }
 
     @Test
+    @Ignore
     public void testUpdateNotification_prePostEntryOrder() throws Exception {
         TestableLooper.get(this).processAllMessages();
 
@@ -399,7 +406,6 @@
         setSmartActions(mEntry.getKey(), new ArrayList<>(Arrays.asList(createAction())));
 
         mEntryManager.updateNotificationRanking(mRankingMap);
-        verify(mRow).setEntry(eq(mEntry));
         assertEquals(1, mEntry.getSmartActions().size());
         assertEquals("action", mEntry.getSmartActions().get(0).title);
         verify(mEntryListener).onNotificationRankingUpdated(mRankingMap);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java
index 5aed61b..1116a33 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationFilterTest.java
@@ -42,11 +42,11 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
-import com.android.systemui.statusbar.NotificationTestHelper;
 import com.android.systemui.statusbar.notification.NotificationEntryManager.KeyguardEnvironment;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
 import com.android.systemui.statusbar.phone.NotificationGroupManager;
 import com.android.systemui.statusbar.phone.ShadeController;
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/TestableNotificationEntryManager.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/TestableNotificationEntryManager.kt
index 7431459..a9f9db6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/TestableNotificationEntryManager.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/TestableNotificationEntryManager.kt
@@ -48,8 +48,8 @@
 
     public var countDownLatch: CountDownLatch = CountDownLatch(1)
 
-    override fun onAsyncInflationFinished(entry: NotificationEntry?, inflatedFlags: Int) {
-        super.onAsyncInflationFinished(entry, inflatedFlags)
+    override fun onAsyncInflationFinished(entry: NotificationEntry) {
+        super.onAsyncInflationFinished(entry)
         countDownLatch.countDown()
     }
 
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 3d79ce1..d8cf6ed 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
@@ -21,7 +21,6 @@
 import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking;
 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL;
 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP;
-import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -51,7 +50,6 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.NotificationTestHelper;
 import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
 import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainer;
 
@@ -147,15 +145,6 @@
     }
 
     @Test
-    public void setNeedsRedactionSetsInflationFlag() throws Exception {
-        ExpandableNotificationRow row = mNotificationTestHelper.createRow();
-
-        row.setNeedsRedaction(true);
-
-        assertTrue(row.isInflationFlagSet(FLAG_CONTENT_VIEW_PUBLIC));
-    }
-
-    @Test
     public void setNeedsRedactionFreesViewWhenFalse() throws Exception {
         ExpandableNotificationRow row = mNotificationTestHelper.createRow(FLAG_CONTENT_VIEW_ALL);
         row.setNeedsRedaction(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java
new file mode 100644
index 0000000..8f9f65d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifBindPipelineTest.java
@@ -0,0 +1,158 @@
+/*
+ * 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.
+ */
+
+package com.android.systemui.statusbar.notification.row;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.annotation.NonNull;
+import androidx.core.os.CancellationSignal;
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.notification.NotificationEntryListener;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.row.NotifBindPipeline.BindCallback;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class NotifBindPipelineTest extends SysuiTestCase {
+
+    private NotifBindPipeline mBindPipeline;
+    private TestBindStage mStage = new TestBindStage();
+
+    @Mock private NotificationEntry mEntry;
+    @Mock private ExpandableNotificationRow mRow;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        NotificationEntryManager entryManager = mock(NotificationEntryManager.class);
+
+        mBindPipeline = new NotifBindPipeline(entryManager);
+        mBindPipeline.setStage(mStage);
+
+        ArgumentCaptor<NotificationEntryListener> entryListenerCaptor =
+                ArgumentCaptor.forClass(NotificationEntryListener.class);
+        verify(entryManager).addNotificationEntryListener(entryListenerCaptor.capture());
+        NotificationEntryListener entryListener = entryListenerCaptor.getValue();
+
+        entryListener.onPendingEntryAdded(mEntry);
+    }
+
+    @Test
+    public void testCallbackCalled() {
+        // GIVEN a bound row
+        mBindPipeline.manageRow(mEntry, mRow);
+
+        // WHEN content is invalidated
+        BindCallback callback = mock(BindCallback.class);
+        mStage.requestRebind(mEntry, callback);
+
+        // WHEN stage finishes its work
+        mStage.doWorkSynchronously();
+
+        // THEN the callback is called when bind finishes
+        verify(callback).onBindFinished(mEntry);
+    }
+
+    @Test
+    public void testCallbackCancelled() {
+        // GIVEN a bound row
+        mBindPipeline.manageRow(mEntry, mRow);
+
+        // GIVEN an in-progress pipeline run
+        BindCallback callback = mock(BindCallback.class);
+        CancellationSignal signal = mStage.requestRebind(mEntry, callback);
+
+        // WHEN the callback is cancelled.
+        signal.cancel();
+
+        // WHEN the stage finishes all its work
+        mStage.doWorkSynchronously();
+
+        // THEN the callback is not called when bind finishes
+        verify(callback, never()).onBindFinished(mEntry);
+    }
+
+    @Test
+    public void testMultipleCallbacks() {
+        // GIVEN a bound row
+        mBindPipeline.manageRow(mEntry, mRow);
+
+        // WHEN the pipeline is invalidated.
+        BindCallback callback = mock(BindCallback.class);
+        mStage.requestRebind(mEntry, callback);
+
+        // WHEN the pipeline is invalidated again before the work completes.
+        BindCallback callback2 = mock(BindCallback.class);
+        mStage.requestRebind(mEntry, callback2);
+
+        // WHEN the stage finishes all work.
+        mStage.doWorkSynchronously();
+
+        // THEN both callbacks are called when the bind finishes
+        verify(callback).onBindFinished(mEntry);
+        verify(callback2).onBindFinished(mEntry);
+    }
+
+    /**
+     * Bind stage for testing where asynchronous work can be synchronously controlled.
+     */
+    private static class TestBindStage extends BindStage {
+        private List<Runnable> mExecutionRequests = new ArrayList<>();
+
+        @Override
+        protected void executeStage(@NonNull NotificationEntry entry,
+                @NonNull ExpandableNotificationRow row, @NonNull StageCallback callback) {
+            mExecutionRequests.add(() -> callback.onStageFinished(entry));
+        }
+
+        @Override
+        protected void abortStage(@NonNull NotificationEntry entry,
+                @NonNull ExpandableNotificationRow row) {
+
+        }
+
+        @Override
+        protected Object newStageParams() {
+            return null;
+        }
+
+        public void doWorkSynchronously() {
+            for (Runnable work: mExecutionRequests) {
+                work.run();
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java
index d7214f3..20cc01a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewCacheImplTest.java
@@ -32,10 +32,10 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.notification.NotificationEntryListener;
-import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
+import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
+import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -50,7 +50,7 @@
 
     private NotifRemoteViewCacheImpl mNotifRemoteViewCache;
     private NotificationEntry mEntry;
-    private NotificationEntryListener mEntryListener;
+    private NotifCollectionListener mEntryListener;
     @Mock private RemoteViews mRemoteViews;
 
     @Before
@@ -58,19 +58,17 @@
         MockitoAnnotations.initMocks(this);
         mEntry = new NotificationEntryBuilder().build();
 
-        NotificationEntryManager entryManager = mock(NotificationEntryManager.class);
-        mNotifRemoteViewCache = new NotifRemoteViewCacheImpl(entryManager);
-        ArgumentCaptor<NotificationEntryListener> entryListenerCaptor =
-                ArgumentCaptor.forClass(NotificationEntryListener.class);
-        verify(entryManager).addNotificationEntryListener(entryListenerCaptor.capture());
+        CommonNotifCollection collection = mock(CommonNotifCollection.class);
+        mNotifRemoteViewCache = new NotifRemoteViewCacheImpl(collection);
+        ArgumentCaptor<NotifCollectionListener> entryListenerCaptor =
+                ArgumentCaptor.forClass(NotifCollectionListener.class);
+        verify(collection).addCollectionListener(entryListenerCaptor.capture());
         mEntryListener = entryListenerCaptor.getValue();
+        mEntryListener.onEntryInit(mEntry);
     }
 
     @Test
     public void testPutCachedView() {
-        // GIVEN an initialized cache for an entry.
-        mEntryListener.onPendingEntryAdded(mEntry);
-
         // WHEN a notification's cached remote views is put in.
         mNotifRemoteViewCache.putCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED, mRemoteViews);
 
@@ -85,7 +83,6 @@
     @Test
     public void testRemoveCachedView() {
         // GIVEN a cache with a cached view.
-        mEntryListener.onPendingEntryAdded(mEntry);
         mNotifRemoteViewCache.putCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED, mRemoteViews);
 
         // WHEN we remove the cached view.
@@ -98,7 +95,6 @@
     @Test
     public void testClearCache() {
         // GIVEN a non-empty cache.
-        mEntryListener.onPendingEntryAdded(mEntry);
         mNotifRemoteViewCache.putCachedView(mEntry, FLAG_CONTENT_VIEW_CONTRACTED, mRemoteViews);
         mNotifRemoteViewCache.putCachedView(mEntry, FLAG_CONTENT_VIEW_EXPANDED, mRemoteViews);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java
index 444a6e5..1dfe7bc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationBlockingHelperManagerTest.java
@@ -46,7 +46,6 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
-import com.android.systemui.statusbar.NotificationTestHelper;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.util.Assert;
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
index cb9da6a..8a42e5f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java
@@ -49,9 +49,7 @@
 import androidx.test.filters.Suppress;
 
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.InflationTask;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
-import com.android.systemui.statusbar.NotificationTestHelper;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationCallback;
@@ -200,8 +198,7 @@
                     }
 
                     @Override
-                    public void onAsyncInflationFinished(NotificationEntry entry,
-                            @InflationFlag int inflatedFlags) {
+                    public void onAsyncInflationFinished(NotificationEntry entry) {
                         countDownLatch.countDown();
                     }
                 }, mRow.getPrivateLayout(), null, null, new HashMap<>(),
@@ -219,34 +216,6 @@
         assertTrue(countDownLatch.await(500, TimeUnit.MILLISECONDS));
     }
 
-    /* Cancelling requires us to be on the UI thread otherwise we might have a race */
-    @Test
-    public void testSupersedesExistingTask() {
-        mNotificationInflater.bindContent(
-                mRow.getEntry(),
-                mRow,
-                FLAG_CONTENT_VIEW_ALL,
-                new BindParams(),
-                false /* forceInflate */,
-                null /* callback */);
-
-        // Trigger inflation of contracted only.
-        mNotificationInflater.bindContent(
-                mRow.getEntry(),
-                mRow,
-                FLAG_CONTENT_VIEW_CONTRACTED,
-                new BindParams(),
-                false /* forceInflate */,
-                null /* callback */);
-
-        InflationTask runningTask = mRow.getEntry().getRunningTask();
-        NotificationContentInflater.AsyncInflationTask asyncInflationTask =
-                (NotificationContentInflater.AsyncInflationTask) runningTask;
-        assertEquals("Successive inflations don't inherit the previous flags!",
-                FLAG_CONTENT_VIEW_ALL, asyncInflationTask.getReInflateFlags());
-        runningTask.abort();
-    }
-
     @Test
     public void doesntReapplyDisallowedRemoteView() throws Exception {
         mBuilder.setStyle(new Notification.MediaStyle());
@@ -349,8 +318,7 @@
             }
 
             @Override
-            public void onAsyncInflationFinished(NotificationEntry entry,
-                    @InflationFlag int inflatedFlags) {
+            public void onAsyncInflationFinished(NotificationEntry entry) {
                 if (expectingException) {
                     exceptionHolder.setException(new RuntimeException(
                             "Inflation finished even though there should be an error"));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
index 4e27770..bbb6723 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.java
@@ -66,7 +66,6 @@
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationPresenter;
-import com.android.systemui.statusbar.NotificationTestHelper;
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
 import com.android.systemui.statusbar.notification.VisualStabilityManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
similarity index 88%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
rename to packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index 457bbe23..3d9832d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2017 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.
@@ -11,10 +11,10 @@
  * 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
+ * limitations under the License.
  */
 
-package com.android.systemui.statusbar;
+package com.android.systemui.statusbar.notification.row;
 
 import static android.app.Notification.FLAG_BUBBLE;
 import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
@@ -24,6 +24,7 @@
 
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
 
 import android.annotation.Nullable;
 import android.app.ActivityManager;
@@ -40,17 +41,20 @@
 import android.view.LayoutInflater;
 import android.widget.RemoteViews;
 
+import com.android.internal.statusbar.IStatusBarService;
 import com.android.systemui.TestableDependency;
 import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.bubbles.BubblesTestActivity;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.statusbar.NotificationMediaManager;
+import com.android.systemui.statusbar.NotificationRemoteInputManager;
+import com.android.systemui.statusbar.SmartReplyController;
+import com.android.systemui.statusbar.notification.NotificationEntryListener;
+import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
-import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.ExpansionLogger;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.OnExpandClickListener;
-import com.android.systemui.statusbar.notification.row.NotifRemoteViewCache;
-import com.android.systemui.statusbar.notification.row.NotificationContentInflater;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
@@ -58,6 +62,8 @@
 import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
 import com.android.systemui.tests.R;
 
+import org.mockito.ArgumentCaptor;
+
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
@@ -82,6 +88,9 @@
     private final NotificationGroupManager mGroupManager;
     private ExpandableNotificationRow mRow;
     private HeadsUpManagerPhone mHeadsUpManager;
+    private final NotifBindPipeline mBindPipeline;
+    private final NotificationEntryListener mBindPipelineEntryListener;
+    private final RowContentBindStage mBindStage;
 
     public NotificationTestHelper(Context context, TestableDependency dependency) {
         mContext = context;
@@ -95,6 +104,23 @@
                 mock(KeyguardBypassController.class));
         mHeadsUpManager.setUp(null, mGroupManager, null, null);
         mGroupManager.setHeadsUpManager(mHeadsUpManager);
+
+
+        NotificationContentInflater contentBinder = new NotificationContentInflater(
+                mock(NotifRemoteViewCache.class),
+                mock(NotificationRemoteInputManager.class));
+        contentBinder.setInflateSynchronously(true);
+        mBindStage = new RowContentBindStage(contentBinder, mock(IStatusBarService.class));
+
+        NotificationEntryManager entryManager = mock(NotificationEntryManager.class);
+
+        mBindPipeline = new NotifBindPipeline(entryManager);
+        mBindPipeline.setStage(mBindStage);
+
+        ArgumentCaptor<NotificationEntryListener> entryListenerCaptor =
+                ArgumentCaptor.forClass(NotificationEntryListener.class);
+        verify(entryManager).addNotificationEntryListener(entryListenerCaptor.capture());
+        mBindPipelineEntryListener = entryListenerCaptor.getValue();
     }
 
     /**
@@ -331,10 +357,8 @@
         entry.createIcons(mContext, entry.getSbn());
         row.setEntry(entry);
 
-        NotificationContentInflater contentBinder = new NotificationContentInflater(
-                mock(NotifRemoteViewCache.class),
-                mock(NotificationRemoteInputManager.class));
-        contentBinder.setInflateSynchronously(true);
+        mBindPipelineEntryListener.onPendingEntryAdded(entry);
+        mBindPipeline.manageRow(entry, row);
 
         row.initialize(
                 APP_NAME,
@@ -343,12 +367,11 @@
                 mock(KeyguardBypassController.class),
                 mGroupManager,
                 mHeadsUpManager,
-                contentBinder,
+                mBindStage,
                 mock(OnExpandClickListener.class));
         row.setAboveShelfChangedListener(aboveShelf -> { });
-
-        row.setInflationFlags(extraInflationFlags);
-        inflateAndWait(row);
+        mBindStage.getStageParams(entry).requireContentViews(extraInflationFlags);
+        inflateAndWait(entry, mBindStage);
 
         // This would be done as part of onAsyncInflationFinished, but we skip large amounts of
         // the callback chain, so we need to make up for not adding it to the group manager
@@ -357,24 +380,10 @@
         return row;
     }
 
-    private static void inflateAndWait(ExpandableNotificationRow row) throws Exception {
+    private static void inflateAndWait(NotificationEntry entry, RowContentBindStage stage)
+            throws Exception {
         CountDownLatch countDownLatch = new CountDownLatch(1);
-        NotificationContentInflater.InflationCallback callback =
-                new NotificationContentInflater.InflationCallback() {
-                    @Override
-                    public void handleInflationException(NotificationEntry entry,
-                            Exception e) {
-                        countDownLatch.countDown();
-                    }
-
-                    @Override
-                    public void onAsyncInflationFinished(NotificationEntry entry,
-                            int inflatedFlags) {
-                        countDownLatch.countDown();
-                    }
-                };
-        row.setInflationCallback(callback);
-        row.inflateViews();
+        stage.requestRebind(entry, en -> countDownLatch.countDown());
         assertTrue(countDownLatch.await(500, TimeUnit.MILLISECONDS));
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java
new file mode 100644
index 0000000..66aa5e1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/RowContentBindStageTest.java
@@ -0,0 +1,247 @@
+/*
+ * 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.row;
+
+import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL;
+import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED;
+import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED;
+import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP;
+
+import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.statusbar.IStatusBarService;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.BindParams;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class RowContentBindStageTest extends SysuiTestCase {
+
+    private RowContentBindStage mRowContentBindStage;
+
+    @Mock private NotificationRowContentBinder mBinder;
+    @Mock private NotificationEntry mEntry;
+    @Mock private ExpandableNotificationRow mRow;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mRowContentBindStage = new RowContentBindStage(mBinder,
+                mock(IStatusBarService.class));
+        mRowContentBindStage.createStageParams(mEntry);
+    }
+
+    @Test
+    public void testSetShouldContentViewsBeBound_bindsContent() {
+        // WHEN inflation flags are set and pipeline is invalidated.
+        final int flags = FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED;
+        RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry);
+        params.requireContentViews(flags);
+        mRowContentBindStage.executeStage(mEntry, mRow, (en) -> { });
+
+        // THEN binder binds inflation flags.
+        verify(mBinder).bindContent(
+                eq(mEntry),
+                any(),
+                eq(flags),
+                any(),
+                anyBoolean(),
+                any());
+    }
+
+    @Test
+    public void testSetShouldContentViewsBeBound_unbindsContent() {
+        // GIVEN a view with all content bound.
+        RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry);
+        params.requireContentViews(FLAG_CONTENT_VIEW_ALL);
+
+        // WHEN inflation flags are cleared and stage executed.
+        final int flags = FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED;
+        params.freeContentViews(flags);
+        mRowContentBindStage.executeStage(mEntry, mRow, (en) -> { });
+
+        // THEN binder unbinds flags.
+        verify(mBinder).unbindContent(eq(mEntry), any(), eq(flags));
+    }
+
+    @Test
+    public void testSetUseLowPriority() {
+        // GIVEN a view with all content bound.
+        RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry);
+        params.requireContentViews(FLAG_CONTENT_VIEW_ALL);
+        params.clearDirtyContentViews();
+
+        // WHEN low priority is set and stage executed.
+        params.setUseLowPriority(true);
+        mRowContentBindStage.executeStage(mEntry, mRow, (en) -> { });
+
+        // THEN binder is called with use low priority and contracted/expanded are called to bind.
+        ArgumentCaptor<BindParams> bindParamsCaptor = ArgumentCaptor.forClass(BindParams.class);
+        verify(mBinder).bindContent(
+                eq(mEntry),
+                any(),
+                eq(FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED),
+                bindParamsCaptor.capture(),
+                anyBoolean(),
+                any());
+        BindParams usedParams = bindParamsCaptor.getValue();
+        assertTrue(usedParams.isLowPriority);
+    }
+
+    @Test
+    public void testSetUseGroupInChild() {
+        // GIVEN a view with all content bound.
+        RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry);
+        params.requireContentViews(FLAG_CONTENT_VIEW_ALL);
+        params.clearDirtyContentViews();
+
+        // WHEN use group is set and stage executed.
+        params.setUseChildInGroup(true);
+        mRowContentBindStage.executeStage(mEntry, mRow, (en) -> { });
+
+        // THEN binder is called with use group view and contracted/expanded are called to bind.
+        ArgumentCaptor<BindParams> bindParamsCaptor = ArgumentCaptor.forClass(BindParams.class);
+        verify(mBinder).bindContent(
+                eq(mEntry),
+                any(),
+                eq(FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED),
+                bindParamsCaptor.capture(),
+                anyBoolean(),
+                any());
+        BindParams usedParams = bindParamsCaptor.getValue();
+        assertTrue(usedParams.isChildInGroup);
+    }
+
+    @Test
+    public void testSetUseIncreasedHeight() {
+        // GIVEN a view with all content bound.
+        RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry);
+        params.requireContentViews(FLAG_CONTENT_VIEW_ALL);
+        params.clearDirtyContentViews();
+
+        // WHEN use increased height is set and stage executed.
+        params.setUseIncreasedCollapsedHeight(true);
+        mRowContentBindStage.executeStage(mEntry, mRow, (en) -> { });
+
+        // THEN binder is called with group view and contracted is bound.
+        ArgumentCaptor<BindParams> bindParamsCaptor = ArgumentCaptor.forClass(BindParams.class);
+        verify(mBinder).bindContent(
+                eq(mEntry),
+                any(),
+                eq(FLAG_CONTENT_VIEW_CONTRACTED),
+                bindParamsCaptor.capture(),
+                anyBoolean(),
+                any());
+        BindParams usedParams = bindParamsCaptor.getValue();
+        assertTrue(usedParams.usesIncreasedHeight);
+    }
+
+    @Test
+    public void testSetUseIncreasedHeadsUpHeight() {
+        // GIVEN a view with all content bound.
+        RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry);
+        params.requireContentViews(FLAG_CONTENT_VIEW_ALL);
+        params.clearDirtyContentViews();
+
+        // WHEN use increased heads up height is set and stage executed.
+        params.setUseIncreasedHeadsUpHeight(true);
+        mRowContentBindStage.executeStage(mEntry, mRow, (en) -> { });
+
+        // THEN binder is called with use group view and heads up is bound.
+        ArgumentCaptor<BindParams> bindParamsCaptor = ArgumentCaptor.forClass(BindParams.class);
+        verify(mBinder).bindContent(
+                eq(mEntry),
+                any(),
+                eq(FLAG_CONTENT_VIEW_HEADS_UP),
+                bindParamsCaptor.capture(),
+                anyBoolean(),
+                any());
+        BindParams usedParams = bindParamsCaptor.getValue();
+        assertTrue(usedParams.usesIncreasedHeadsUpHeight);
+    }
+
+    @Test
+    public void testSetNeedsReinflation() {
+        // GIVEN a view with all content bound.
+        RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry);
+        params.requireContentViews(FLAG_CONTENT_VIEW_ALL);
+        params.clearDirtyContentViews();
+
+        // WHEN needs reinflation is set.
+        params.setNeedsReinflation(true);
+        mRowContentBindStage.executeStage(mEntry, mRow, (en) -> { });
+
+        // THEN binder is called with forceInflate and all views are requested to bind.
+        verify(mBinder).bindContent(
+                eq(mEntry),
+                any(),
+                eq(FLAG_CONTENT_VIEW_ALL),
+                any(),
+                eq(true),
+                any());
+    }
+
+    @Test
+    public void testSupersedesPreviousContentViews() {
+        // GIVEN a view with content view bind already in progress.
+        RowContentBindParams params = mRowContentBindStage.getStageParams(mEntry);
+        int defaultFlags = FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED;
+        params.requireContentViews(defaultFlags);
+        mRowContentBindStage.executeStage(mEntry, mRow, (en) -> { });
+
+        // WHEN we bind with another content view before the first finishes.
+        params.requireContentViews(FLAG_CONTENT_VIEW_HEADS_UP);
+        mRowContentBindStage.executeStage(mEntry, mRow, (en) -> { });
+
+        // THEN binder is called with BOTH content views.
+        verify(mBinder).bindContent(
+                eq(mEntry),
+                any(),
+                eq(defaultFlags),
+                any(),
+                anyBoolean(),
+                any());
+        verify(mBinder).bindContent(
+                eq(mEntry),
+                any(),
+                eq(defaultFlags | FLAG_CONTENT_VIEW_HEADS_UP),
+                any(),
+                anyBoolean(),
+                any());
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java
index d280f18..0790cb7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationCustomViewWrapperTest.java
@@ -25,8 +25,8 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.NotificationTestHelper;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
 import com.android.systemui.tests.R;
 
 import org.junit.Assert;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java
index 4f45f68..038eff7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapperTest.java
@@ -38,8 +38,8 @@
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.NotificationTestHelper;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java
index 14e2fde..9567f33 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationViewWrapperTest.java
@@ -29,8 +29,8 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.NotificationTestHelper;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
 import com.android.systemui.util.Assert;
 
 import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
index ddd2884e..1773175 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
@@ -25,8 +25,8 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.NotificationTestHelper;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
 
 import org.junit.Assert;
 import org.junit.Before;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
index 34a309f..e84f14a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationRoundnessManagerTest.java
@@ -31,11 +31,11 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.statusbar.NotificationTestHelper;
 import com.android.systemui.statusbar.notification.NotificationSectionsFeatureManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
+import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.util.DeviceConfigProxy;
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
index 7448dbd..f71d0fc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpAppearanceControllerTest.java
@@ -35,9 +35,9 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.HeadsUpStatusBarView;
-import com.android.systemui.statusbar.NotificationTestHelper;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java
index 5b54fba..e171a28 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupAlertTransferHelperTest.java
@@ -16,8 +16,12 @@
 
 package com.android.systemui.statusbar.phone;
 
+import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP;
+
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
@@ -38,6 +42,9 @@
 import com.android.systemui.statusbar.notification.NotificationEntryListener;
 import com.android.systemui.statusbar.notification.NotificationEntryManager;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.row.NotifBindPipeline.BindCallback;
+import com.android.systemui.statusbar.notification.row.RowContentBindParams;
+import com.android.systemui.statusbar.notification.row.RowContentBindStage;
 import com.android.systemui.statusbar.policy.HeadsUpManager;
 
 import org.junit.Before;
@@ -47,6 +54,7 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
 import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
@@ -62,8 +70,8 @@
     private NotificationGroupManager mGroupManager;
     private HeadsUpManager mHeadsUpManager;
     @Mock private NotificationEntryManager mNotificationEntryManager;
-    @Captor
-    private ArgumentCaptor<NotificationEntryListener> mListenerCaptor;
+    @Mock private RowContentBindStage mBindStage;
+    @Captor private ArgumentCaptor<NotificationEntryListener> mListenerCaptor;
     private NotificationEntryListener mNotificationEntryListener;
     private final HashMap<String, NotificationEntry> mPendingEntries = new HashMap<>();
     private final NotificationGroupTestHelper mGroupTestHelper =
@@ -72,6 +80,7 @@
 
     @Before
     public void setup() {
+        MockitoAnnotations.initMocks(this);
         mDependency.injectMockDependency(BubbleController.class);
         mHeadsUpManager = new HeadsUpManager(mContext) {};
 
@@ -82,7 +91,9 @@
         mDependency.injectTestDependency(NotificationGroupManager.class, mGroupManager);
         mGroupManager.setHeadsUpManager(mHeadsUpManager);
 
-        mGroupAlertTransferHelper = new NotificationGroupAlertTransferHelper();
+        when(mBindStage.getStageParams(any())).thenReturn(new RowContentBindParams());
+
+        mGroupAlertTransferHelper = new NotificationGroupAlertTransferHelper(mBindStage);
         mGroupAlertTransferHelper.setHeadsUpManager(mHeadsUpManager);
 
         mGroupAlertTransferHelper.bind(mNotificationEntryManager, mGroupManager);
@@ -97,6 +108,10 @@
         mHeadsUpManager.showNotification(summaryEntry);
         NotificationEntry childEntry = mGroupTestHelper.createChildNotification();
 
+        RowContentBindParams params = new RowContentBindParams();
+        params.requireContentViews(FLAG_CONTENT_VIEW_HEADS_UP);
+        when(mBindStage.getStageParams(eq(childEntry))).thenReturn(params);
+
         // Summary will be suppressed because there is only one child.
         mGroupManager.onEntryAdded(summaryEntry);
         mGroupManager.onEntryAdded(childEntry);
@@ -160,8 +175,8 @@
         NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification();
         mHeadsUpManager.showNotification(summaryEntry);
         NotificationEntry childEntry = mGroupTestHelper.createChildNotification();
-        when(childEntry.getRow().isInflationFlagSet(mHeadsUpManager.getContentFlag()))
-            .thenReturn(false);
+        RowContentBindParams params = new RowContentBindParams();
+        when(mBindStage.getStageParams(eq(childEntry))).thenReturn(params);
 
         mGroupManager.onEntryAdded(summaryEntry);
         mGroupManager.onEntryAdded(childEntry);
@@ -178,15 +193,16 @@
         NotificationEntry summaryEntry = mGroupTestHelper.createSummaryNotification();
         mHeadsUpManager.showNotification(summaryEntry);
         NotificationEntry childEntry = mGroupTestHelper.createChildNotification();
-        when(childEntry.getRow().isInflationFlagSet(mHeadsUpManager.getContentFlag()))
-            .thenReturn(false);
+        RowContentBindParams params = new RowContentBindParams();
+        when(mBindStage.getStageParams(eq(childEntry))).thenReturn(params);
 
         mGroupManager.onEntryAdded(summaryEntry);
         mGroupManager.onEntryAdded(childEntry);
 
-        when(childEntry.getRow().isInflationFlagSet(mHeadsUpManager.getContentFlag()))
-            .thenReturn(true);
-        mNotificationEntryListener.onEntryReinflated(childEntry);
+        // Child entry finishes its inflation.
+        ArgumentCaptor<BindCallback> callbackCaptor = ArgumentCaptor.forClass(BindCallback.class);
+        verify(mBindStage).requestRebind(eq(childEntry), callbackCaptor.capture());
+        callbackCaptor.getValue().onBindFinished(childEntry);
 
         // Alert is immediately removed from summary, and we show child as its content is inflated.
         assertFalse(mHeadsUpManager.isAlerting(summaryEntry.getKey()));
@@ -199,8 +215,9 @@
                 mGroupTestHelper.createSummaryNotification(Notification.GROUP_ALERT_SUMMARY);
         NotificationEntry childEntry =
                 mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY);
-        when(childEntry.getRow().isInflationFlagSet(mHeadsUpManager.getContentFlag()))
-            .thenReturn(false);
+        RowContentBindParams params = new RowContentBindParams();
+        when(mBindStage.getStageParams(eq(childEntry))).thenReturn(params);
+
         NotificationEntry childEntry2 =
                 mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY);
         mHeadsUpManager.showNotification(summaryEntry);
@@ -214,9 +231,9 @@
         mGroupManager.onEntryAdded(childEntry2);
 
         // Child entry finishes its inflation.
-        when(childEntry.getRow().isInflationFlagSet(mHeadsUpManager.getContentFlag()))
-            .thenReturn(true);
-        mNotificationEntryListener.onEntryReinflated(childEntry);
+        ArgumentCaptor<BindCallback> callbackCaptor = ArgumentCaptor.forClass(BindCallback.class);
+        verify(mBindStage).requestRebind(eq(childEntry), callbackCaptor.capture());
+        callbackCaptor.getValue().onBindFinished(childEntry);
 
         verify(childEntry.getRow(), times(1)).freeContentViewWhenSafe(mHeadsUpManager
             .getContentFlag());
@@ -229,8 +246,9 @@
                 mGroupTestHelper.createSummaryNotification(Notification.GROUP_ALERT_SUMMARY);
         NotificationEntry childEntry =
                 mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY);
-        when(childEntry.getRow().isInflationFlagSet(mHeadsUpManager.getContentFlag()))
-            .thenReturn(false);
+        RowContentBindParams params = new RowContentBindParams();
+        when(mBindStage.getStageParams(eq(childEntry))).thenReturn(params);
+
         mHeadsUpManager.showNotification(summaryEntry);
         // Trigger a transfer of alert state from summary to child.
         mGroupManager.onEntryAdded(summaryEntry);
@@ -247,8 +265,9 @@
                 mGroupTestHelper.createSummaryNotification(Notification.GROUP_ALERT_SUMMARY);
         NotificationEntry childEntry =
                 mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY);
-        when(childEntry.getRow().isInflationFlagSet(mHeadsUpManager.getContentFlag()))
-            .thenReturn(false);
+        RowContentBindParams params = new RowContentBindParams();
+        when(mBindStage.getStageParams(eq(childEntry))).thenReturn(params);
+
         mHeadsUpManager.showNotification(summaryEntry);
         // Trigger a transfer of alert state from summary to child.
         mGroupManager.onEntryAdded(summaryEntry);
@@ -270,8 +289,9 @@
                 mGroupTestHelper.createSummaryNotification(Notification.GROUP_ALERT_SUMMARY);
         NotificationEntry childEntry =
                 mGroupTestHelper.createChildNotification(Notification.GROUP_ALERT_SUMMARY, 47);
-        when(childEntry.getRow().isInflationFlagSet(mHeadsUpManager.getContentFlag()))
-            .thenReturn(false);
+        RowContentBindParams params = new RowContentBindParams();
+        when(mBindStage.getStageParams(eq(childEntry))).thenReturn(params);
+
         mHeadsUpManager.showNotification(summaryEntry);
         // Trigger a transfer of alert state from summary to child.
         mGroupManager.onEntryAdded(summaryEntry);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java
index 54dc728..d405fea 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationGroupTestHelper.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.statusbar.phone;
 
-import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
@@ -87,7 +86,6 @@
         ExpandableNotificationRow row = mock(ExpandableNotificationRow.class);
         entry.setRow(row);
         when(row.getEntry()).thenReturn(entry);
-        when(row.isInflationFlagSet(anyInt())).thenReturn(true);
         return entry;
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index fea4b8b..5027610 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -61,7 +61,6 @@
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationPresenter;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
-import com.android.systemui.statusbar.NotificationTestHelper;
 import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.ActivityLaunchAnimator;
@@ -70,6 +69,7 @@
 import com.android.systemui.statusbar.notification.NotificationInterruptionStateProvider;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
index 390e812..df62254 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/RemoteInputViewTest.java
@@ -39,9 +39,9 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.statusbar.NotificationTestHelper;
 import com.android.systemui.statusbar.RemoteInputController;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
 import com.android.systemui.statusbar.phone.LightBarController;
 import com.android.systemui.util.Assert;
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/FloatingContentCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/util/FloatingContentCoordinatorTest.kt
new file mode 100644
index 0000000..8eecde1
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/FloatingContentCoordinatorTest.kt
@@ -0,0 +1,218 @@
+package com.android.systemui.util
+
+import android.graphics.Rect
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@TestableLooper.RunWithLooper
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class FloatingContentCoordinatorTest : SysuiTestCase() {
+
+    private val screenBounds = Rect(0, 0, 1000, 1000)
+
+    private val rect100px = Rect()
+    private val rect100pxFloating = FloatingRect(rect100px)
+
+    private val rect200px = Rect()
+    private val rect200pxFloating = FloatingRect(rect200px)
+
+    private val rect300px = Rect()
+    private val rect300pxFloating = FloatingRect(rect300px)
+
+    private val floatingCoordinator = FloatingContentCoordinator()
+
+    @Before
+    fun setup() {
+        rect100px.set(0, 0, 100, 100)
+        rect200px.set(0, 0, 200, 200)
+        rect300px.set(0, 0, 300, 300)
+    }
+
+    @After
+    fun tearDown() {
+        // We need to remove this stuff since it's a singleton object and it'll be there for the
+        // next test.
+        floatingCoordinator.onContentRemoved(rect100pxFloating)
+        floatingCoordinator.onContentRemoved(rect200pxFloating)
+        floatingCoordinator.onContentRemoved(rect300pxFloating)
+    }
+
+    @Test
+    fun testOnContentAdded() {
+        // Add rect1, and verify that the coordinator didn't move it.
+        floatingCoordinator.onContentAdded(rect100pxFloating)
+        assertEquals(rect100px.top, 0)
+
+        // Add rect2, which intersects rect1. Verify that rect2 was not moved, since newly added
+        // content is allowed to remain where it is. rect1 should have been moved below rect2
+        // since it was in the way.
+        floatingCoordinator.onContentAdded(rect200pxFloating)
+        assertEquals(rect200px.top, 0)
+        assertEquals(rect100px.top, 200)
+
+        verifyRectSizes()
+    }
+
+    @Test
+    fun testOnContentRemoved() {
+        // Add rect1, and remove it. Then add rect2. Since rect1 was removed before that, it should
+        // no longer be considered in the way, so it shouldn't move when rect2 is added.
+        floatingCoordinator.onContentAdded(rect100pxFloating)
+        floatingCoordinator.onContentRemoved(rect100pxFloating)
+        floatingCoordinator.onContentAdded(rect200pxFloating)
+
+        assertEquals(rect100px.top, 0)
+        assertEquals(rect200px.top, 0)
+
+        verifyRectSizes()
+    }
+
+    @Test
+    fun testOnContentMoved_twoRects() {
+        // Add rect1, which is at y = 0.
+        floatingCoordinator.onContentAdded(rect100pxFloating)
+
+        // Move rect2 down to 500px, where it won't conflict with rect1.
+        rect200px.offsetTo(0, 500)
+        floatingCoordinator.onContentAdded(rect200pxFloating)
+
+        // Then, move it to 0px where it will absolutely conflict with rect1.
+        rect200px.offsetTo(0, 0)
+        floatingCoordinator.onContentMoved(rect200pxFloating)
+
+        // The coordinator should have left rect2 alone, and moved rect1 below it. rect1 should now
+        // be at y = 200.
+        assertEquals(rect200px.top, 0)
+        assertEquals(rect100px.top, 200)
+
+        verifyRectSizes()
+
+        // Move rect2 to y = 275px. Since this puts it at the bottom half of rect1, it should push
+        // rect1 upward and leave rect2 alone.
+        rect200px.offsetTo(0, 275)
+        floatingCoordinator.onContentMoved(rect200pxFloating)
+
+        assertEquals(rect200px.top, 275)
+        assertEquals(rect100px.top, 175)
+
+        verifyRectSizes()
+
+        // Move rect2 to y = 110px. This makes it intersect rect1 again, but above its center of
+        // mass. That means rect1 should be pushed downward.
+        rect200px.offsetTo(0, 110)
+        floatingCoordinator.onContentMoved(rect200pxFloating)
+
+        assertEquals(rect200px.top, 110)
+        assertEquals(rect100px.top, 310)
+
+        verifyRectSizes()
+    }
+
+    @Test
+    fun testOnContentMoved_threeRects() {
+        floatingCoordinator.onContentAdded(rect100pxFloating)
+
+        // Add rect2, which should displace rect1 to y = 200
+        floatingCoordinator.onContentAdded(rect200pxFloating)
+        assertEquals(rect200px.top, 0)
+        assertEquals(rect100px.top, 200)
+
+        // Add rect3, which should completely cover both rect1 and rect2. That should cause them to
+        // move away. The order in which they do so is non-deterministic, so just make sure none of
+        // the three Rects intersect.
+        floatingCoordinator.onContentAdded(rect300pxFloating)
+
+        assertFalse(Rect.intersects(rect100px, rect200px))
+        assertFalse(Rect.intersects(rect100px, rect300px))
+        assertFalse(Rect.intersects(rect200px, rect300px))
+
+        // Move rect2 to intersect both rect1 and rect3.
+        rect200px.offsetTo(0, 150)
+        floatingCoordinator.onContentMoved(rect200pxFloating)
+
+        assertFalse(Rect.intersects(rect100px, rect200px))
+        assertFalse(Rect.intersects(rect100px, rect300px))
+        assertFalse(Rect.intersects(rect200px, rect300px))
+    }
+
+    @Test
+    fun testOnContentMoved_respectsUpperBounds() {
+        // Add rect1, which is at y = 0.
+        floatingCoordinator.onContentAdded(rect100pxFloating)
+
+        // Move rect2 down to 500px, where it won't conflict with rect1.
+        rect200px.offsetTo(0, 500)
+        floatingCoordinator.onContentAdded(rect200pxFloating)
+
+        // Then, move it to 90px where it will conflict with rect1, but with a center of mass below
+        // that of rect1's. This would normally mean that rect1 moves upward. However, since it's at
+        // the top of the screen, it should go downward instead.
+        rect200px.offsetTo(0, 90)
+        floatingCoordinator.onContentMoved(rect200pxFloating)
+
+        // rect2 should have been left alone, rect1 is now below rect2 at y = 290px even though it
+        // was intersected from below.
+        assertEquals(rect200px.top, 90)
+        assertEquals(rect100px.top, 290)
+    }
+
+    @Test
+    fun testOnContentMoved_respectsLowerBounds() {
+        // Put rect1 at the bottom of the screen and add it.
+        rect100px.offsetTo(0, screenBounds.bottom - 100)
+        floatingCoordinator.onContentAdded(rect100pxFloating)
+
+        // Put rect2 at the bottom as well. Since its center of mass is above rect1's, rect1 would
+        // normally move downward. Since it's at the bottom of the screen, it should go upward
+        // instead.
+        rect200px.offsetTo(0, 800)
+        floatingCoordinator.onContentAdded(rect200pxFloating)
+
+        assertEquals(rect200px.top, 800)
+        assertEquals(rect100px.top, 700)
+    }
+
+    /**
+     * Tests that the rect sizes didn't change when the coordinator manipulated them. This allows us
+     * to assert only the value of rect.top in tests, since if top, width, and height are correct,
+     * that means top/left/right/bottom are all correct.
+     */
+    private fun verifyRectSizes() {
+        assertEquals(100, rect100px.width())
+        assertEquals(200, rect200px.width())
+        assertEquals(300, rect300px.width())
+
+        assertEquals(100, rect100px.height())
+        assertEquals(200, rect200px.height())
+        assertEquals(300, rect300px.height())
+    }
+
+    /**
+     * Helper class that uses [floatingCoordinator.findAreaForContentVertically] to move a
+     * Rect when needed.
+     */
+    inner class FloatingRect(
+        private val underlyingRect: Rect
+    ) : FloatingContentCoordinator.FloatingContent {
+        override fun moveToBounds(bounds: Rect) {
+            underlyingRect.set(bounds)
+        }
+
+        override fun getAllowedFloatingBoundsRegion(): Rect {
+            return screenBounds
+        }
+
+        override fun getFloatingBoundsOnScreen(): Rect {
+            return underlyingRect
+        }
+    }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/LocationManagerService.java b/services/core/java/com/android/server/LocationManagerService.java
index 50843b0..0ab8af6a 100644
--- a/services/core/java/com/android/server/LocationManagerService.java
+++ b/services/core/java/com/android/server/LocationManagerService.java
@@ -305,11 +305,7 @@
                         public void onOpChanged(int op, String packageName) {
                             // onOpChanged invoked on ui thread, move to our thread to reduce risk
                             // of blocking ui thread
-                            mHandler.post(() -> {
-                                synchronized (mLock) {
-                                    onAppOpChangedLocked();
-                                }
-                            });
+                            mHandler.post(() -> onAppOpChanged(packageName));
                         }
                     });
             mPackageManager.addOnPermissionsChangeListener(
@@ -392,13 +388,26 @@
         }
     }
 
-    @GuardedBy("mLock")
-    private void onAppOpChangedLocked() {
-        for (Receiver receiver : mReceivers.values()) {
-            receiver.updateMonitoring(true);
-        }
-        for (LocationProviderManager manager : mProviderManagers) {
-            applyRequirementsLocked(manager);
+    private void onAppOpChanged(String packageName) {
+        synchronized (mLock) {
+            for (Receiver receiver : mReceivers.values()) {
+                if (receiver.mCallerIdentity.mPackageName.equals(packageName)) {
+                    receiver.updateMonitoring(true);
+                }
+            }
+
+            HashSet<String> affectedProviders = new HashSet<>(mRecordsByProvider.size());
+            for (Entry<String, ArrayList<UpdateRecord>> entry : mRecordsByProvider.entrySet()) {
+                String provider = entry.getKey();
+                for (UpdateRecord record : entry.getValue()) {
+                    if (record.mReceiver.mCallerIdentity.mPackageName.equals(packageName)) {
+                        affectedProviders.add(provider);
+                    }
+                }
+            }
+            for (String provider : affectedProviders) {
+                applyRequirementsLocked(provider);
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
index b19a37e..f872c6b 100644
--- a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
+++ b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java
@@ -50,7 +50,7 @@
     static final boolean DEBUG_BROADCAST_LIGHT = DEBUG_BROADCAST || false;
     static final boolean DEBUG_BROADCAST_DEFERRAL = DEBUG_BROADCAST || false;
     static final boolean DEBUG_COMPACTION = DEBUG_ALL || false;
-    static final boolean DEBUG_FREEZER = DEBUG_ALL || false;
+    static final boolean DEBUG_FREEZER = DEBUG_ALL || true;
     static final boolean DEBUG_LRU = DEBUG_ALL || false;
     static final boolean DEBUG_MU = DEBUG_ALL || false;
     static final boolean DEBUG_NETWORK = DEBUG_ALL || false;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 148f7de..12b1cbf 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -6843,7 +6843,8 @@
                     cpi = cpr.info;
                     if (isSingleton(cpi.processName, cpi.applicationInfo,
                             cpi.name, cpi.flags)
-                            && isValidSingletonCall(r.uid, cpi.applicationInfo.uid)) {
+                            && isValidSingletonCall(r == null ? callingUid : r.uid,
+                                    cpi.applicationInfo.uid)) {
                         userId = UserHandle.USER_SYSTEM;
                         checkCrossUser = false;
                     } else {
@@ -6931,7 +6932,8 @@
                 conn = incProviderCountLocked(r, cpr, token, callingUid, callingPackage, callingTag,
                         stable);
                 if (conn != null && (conn.stableCount+conn.unstableCount) == 1) {
-                    if (cpr.proc != null && r.setAdj <= ProcessList.PERCEPTIBLE_LOW_APP_ADJ) {
+                    if (cpr.proc != null
+                            && r != null && r.setAdj <= ProcessList.PERCEPTIBLE_LOW_APP_ADJ) {
                         // If this is a perceptible app accessing the provider,
                         // make sure to count it as being accessed and thus
                         // back up on the LRU list.  This is good because
@@ -7003,7 +7005,8 @@
                 // Then allow connecting to the singleton provider
                 boolean singleton = isSingleton(cpi.processName, cpi.applicationInfo,
                         cpi.name, cpi.flags)
-                        && isValidSingletonCall(r.uid, cpi.applicationInfo.uid);
+                        && isValidSingletonCall(r == null ? callingUid : r.uid,
+                                cpi.applicationInfo.uid);
                 if (singleton) {
                     userId = UserHandle.USER_SYSTEM;
                 }
diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java
index 313c185..d047a3c 100644
--- a/services/core/java/com/android/server/am/CachedAppOptimizer.java
+++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java
@@ -442,7 +442,7 @@
      */
     @GuardedBy("mPhenotypeFlagLock")
     private void updateUseFreezer() {
-        if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
+        if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER_NATIVE_BOOT,
                     KEY_USE_FREEZER, DEFAULT_USE_FREEZER)) {
             mUseFreezer = isFreezerSupported();
         }
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index b107626..a651d9d 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -330,6 +330,7 @@
             // If this proc state is changed, need to update its uid record here
             if (uidRec.getCurProcState() != PROCESS_STATE_NONEXISTENT
                     && (uidRec.setProcState != uidRec.getCurProcState()
+                    || uidRec.setCapability != uidRec.curCapability
                     || uidRec.setWhitelist != uidRec.curWhitelist)) {
                 ActiveUids uids = mTmpUidRecords;
                 uids.clear();
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 71486d3..dcada89 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -3271,6 +3271,9 @@
     }
 
     final ProcessRecord getLRURecordForAppLocked(IApplicationThread thread) {
+        if (thread == null) {
+            return null;
+        }
         final IBinder threadBinder = thread.asBinder();
         // Find the application record.
         for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 89af5b5..cb88c4e 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -49,6 +49,7 @@
 import android.net.ConnectivityManager;
 import android.net.INetworkManagementEventObserver;
 import android.net.IpPrefix;
+import android.net.IpSecManager;
 import android.net.LinkAddress;
 import android.net.LinkProperties;
 import android.net.LocalSocket;
@@ -180,7 +181,10 @@
     private boolean mIsPackageTargetingAtLeastQ;
     private String mInterface;
     private Connection mConnection;
-    private LegacyVpnRunner mLegacyVpnRunner;
+
+    /** Tracks the runners for all VPN types managed by the platform (eg. LegacyVpn, PlatformVpn) */
+    private VpnRunner mVpnRunner;
+
     private PendingIntent mStatusIntent;
     private volatile boolean mEnableTeardown = true;
     private final INetworkManagementService mNetd;
@@ -762,7 +766,7 @@
                 mNetworkCapabilities.setUids(null);
             }
 
-            // Revoke the connection or stop LegacyVpnRunner.
+            // Revoke the connection or stop the VpnRunner.
             if (mConnection != null) {
                 try {
                     mConnection.mService.transact(IBinder.LAST_CALL_TRANSACTION,
@@ -772,9 +776,9 @@
                 }
                 mContext.unbindService(mConnection);
                 mConnection = null;
-            } else if (mLegacyVpnRunner != null) {
-                mLegacyVpnRunner.exit();
-                mLegacyVpnRunner = null;
+            } else if (mVpnRunner != null) {
+                mVpnRunner.exit();
+                mVpnRunner = null;
             }
 
             try {
@@ -1510,8 +1514,8 @@
         @Override
         public void interfaceStatusChanged(String interfaze, boolean up) {
             synchronized (Vpn.this) {
-                if (!up && mLegacyVpnRunner != null) {
-                    mLegacyVpnRunner.check(interfaze);
+                if (!up && mVpnRunner != null && mVpnRunner instanceof LegacyVpnRunner) {
+                    ((LegacyVpnRunner) mVpnRunner).exitIfOuterInterfaceIs(interfaze);
                 }
             }
         }
@@ -1528,9 +1532,10 @@
                         mContext.unbindService(mConnection);
                         mConnection = null;
                         agentDisconnect();
-                    } else if (mLegacyVpnRunner != null) {
-                        mLegacyVpnRunner.exit();
-                        mLegacyVpnRunner = null;
+                    } else if (mVpnRunner != null) {
+                        // agentDisconnect must be called from mVpnRunner.exit()
+                        mVpnRunner.exit();
+                        mVpnRunner = null;
                     }
                 }
             }
@@ -1913,23 +1918,40 @@
 
     private synchronized void startLegacyVpn(VpnConfig config, String[] racoon, String[] mtpd,
             VpnProfile profile) {
-        stopLegacyVpnPrivileged();
+        stopVpnRunnerPrivileged();
 
         // Prepare for the new request.
         prepareInternal(VpnConfig.LEGACY_VPN);
         updateState(DetailedState.CONNECTING, "startLegacyVpn");
 
         // Start a new LegacyVpnRunner and we are done!
-        mLegacyVpnRunner = new LegacyVpnRunner(config, racoon, mtpd, profile);
-        mLegacyVpnRunner.start();
+        mVpnRunner = new LegacyVpnRunner(config, racoon, mtpd, profile);
+        mVpnRunner.start();
     }
 
-    /** Stop legacy VPN. Permissions must be checked by callers. */
-    public synchronized void stopLegacyVpnPrivileged() {
-        if (mLegacyVpnRunner != null) {
-            mLegacyVpnRunner.exit();
-            mLegacyVpnRunner = null;
+    /**
+     * Checks if this the currently running VPN (if any) was started by the Settings app
+     *
+     * <p>This includes both Legacy VPNs and Platform VPNs.
+     */
+    private boolean isSettingsVpnLocked() {
+        return mVpnRunner != null && VpnConfig.LEGACY_VPN.equals(mPackage);
+    }
 
+    /** Stop VPN runner. Permissions must be checked by callers. */
+    public synchronized void stopVpnRunnerPrivileged() {
+        if (!isSettingsVpnLocked()) {
+            return;
+        }
+
+        final boolean isLegacyVpn = mVpnRunner instanceof LegacyVpnRunner;
+
+        mVpnRunner.exit();
+        mVpnRunner = null;
+
+        // LegacyVpn uses daemons that must be shut down before new ones are brought up.
+        // The same limitation does not apply to Platform VPNs.
+        if (isLegacyVpn) {
             synchronized (LegacyVpnRunner.TAG) {
                 // wait for old thread to completely finish before spinning up
                 // new instance, otherwise state updates can be out of order.
@@ -1951,7 +1973,7 @@
      * Callers are responsible for checking permissions if needed.
      */
     private synchronized LegacyVpnInfo getLegacyVpnInfoPrivileged() {
-        if (mLegacyVpnRunner == null) return null;
+        if (!isSettingsVpnLocked()) return null;
 
         final LegacyVpnInfo info = new LegacyVpnInfo();
         info.key = mConfig.user;
@@ -1962,14 +1984,53 @@
         return info;
     }
 
-    public VpnConfig getLegacyVpnConfig() {
-        if (mLegacyVpnRunner != null) {
+    public synchronized VpnConfig getLegacyVpnConfig() {
+        if (isSettingsVpnLocked()) {
             return mConfig;
         } else {
             return null;
         }
     }
 
+    /** This class represents the common interface for all VPN runners. */
+    private abstract class VpnRunner extends Thread {
+
+        protected VpnRunner(String name) {
+            super(name);
+        }
+
+        public abstract void run();
+
+        protected abstract void exit();
+    }
+
+    private class IkeV2VpnRunner extends VpnRunner {
+        private static final String TAG = "IkeV2VpnRunner";
+
+        private final IpSecManager mIpSecManager;
+        private final VpnProfile mProfile;
+
+        IkeV2VpnRunner(VpnProfile profile) {
+            super(TAG);
+            mProfile = profile;
+
+            // TODO: move this to startVpnRunnerPrivileged()
+            mConfig = new VpnConfig();
+            mIpSecManager = mContext.getSystemService(IpSecManager.class);
+        }
+
+        @Override
+        public void run() {
+            // TODO: Build IKE config, start IKE session
+        }
+
+        @Override
+        public void exit() {
+            // TODO: Teardown IKE session & any resources.
+            agentDisconnect();
+        }
+    }
+
     /**
      * Bringing up a VPN connection takes time, and that is all this thread
      * does. Here we have plenty of time. The only thing we need to take
@@ -1977,7 +2038,7 @@
      * requests will pile up. This could be done in a Handler as a state
      * machine, but it is much easier to read in the current form.
      */
-    private class LegacyVpnRunner extends Thread {
+    private class LegacyVpnRunner extends VpnRunner {
         private static final String TAG = "LegacyVpnRunner";
 
         private final String[] mDaemons;
@@ -2047,13 +2108,21 @@
             mContext.registerReceiver(mBroadcastReceiver, filter);
         }
 
-        public void check(String interfaze) {
+        /**
+         * Checks if the parameter matches the underlying interface
+         *
+         * <p>If the underlying interface is torn down, the LegacyVpnRunner also should be. It has
+         * no ability to migrate between interfaces (or Networks).
+         */
+        public void exitIfOuterInterfaceIs(String interfaze) {
             if (interfaze.equals(mOuterInterface)) {
                 Log.i(TAG, "Legacy VPN is going down with " + interfaze);
                 exit();
             }
         }
 
+        /** Tears down this LegacyVpn connection */
+        @Override
         public void exit() {
             // We assume that everything is reset after stopping the daemons.
             interrupt();
diff --git a/services/core/java/com/android/server/display/DisplayAdapter.java b/services/core/java/com/android/server/display/DisplayAdapter.java
index 6ba25a5..838dc84 100644
--- a/services/core/java/com/android/server/display/DisplayAdapter.java
+++ b/services/core/java/com/android/server/display/DisplayAdapter.java
@@ -109,24 +109,14 @@
      */
     protected final void sendDisplayDeviceEventLocked(
             final DisplayDevice device, final int event) {
-        mHandler.post(new Runnable() {
-            @Override
-            public void run() {
-                mListener.onDisplayDeviceEvent(device, event);
-            }
-        });
+        mHandler.post(() -> mListener.onDisplayDeviceEvent(device, event));
     }
 
     /**
      * Sends a request to perform traversals.
      */
     protected final void sendTraversalRequestLocked() {
-        mHandler.post(new Runnable() {
-            @Override
-            public void run() {
-                mListener.onTraversalRequested();
-            }
-        });
+        mHandler.post(() -> mListener.onTraversalRequested());
     }
 
     public static Display.Mode createMode(int width, int height, float refreshRate) {
@@ -135,7 +125,7 @@
     }
 
     public interface Listener {
-        public void onDisplayDeviceEvent(DisplayDevice device, int event);
-        public void onTraversalRequested();
+        void onDisplayDeviceEvent(DisplayDevice device, int event);
+        void onTraversalRequested();
     }
 }
diff --git a/services/core/java/com/android/server/display/LocalDisplayAdapter.java b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
index 704cbff4..fc9542a 100644
--- a/services/core/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/core/java/com/android/server/display/LocalDisplayAdapter.java
@@ -65,8 +65,9 @@
 
     private static final String PROPERTY_EMULATOR_CIRCULAR = "ro.emulator.circular";
 
-    private final LongSparseArray<LocalDisplayDevice> mDevices =
-            new LongSparseArray<LocalDisplayDevice>();
+    private static final int NO_DISPLAY_MODE_ID = 0;
+
+    private final LongSparseArray<LocalDisplayDevice> mDevices = new LongSparseArray<>();
 
     @SuppressWarnings("unused")  // Becomes active at instantiation time.
     private PhysicalDisplayEventReceiver mPhysicalDisplayEventReceiver;
@@ -133,14 +134,9 @@
                         hdrCapabilities, isInternal);
                 mDevices.put(physicalDisplayId, device);
                 sendDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_ADDED);
-            } else {
-                boolean changed = device.updateDisplayConfigsLocked(configs, activeConfig,
-                        configSpecs);
-                changed |= device.updateColorModesLocked(colorModes, activeColorMode);
-                changed |= device.updateHdrCapabilitiesLocked(hdrCapabilities);
-                if (changed) {
-                    sendDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_CHANGED);
-                }
+            } else if (device.updateDisplayProperties(configs, activeConfig,
+                    configSpecs, colorModes, activeColorMode, hdrCapabilities)) {
+                sendDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_CHANGED);
             }
         } else {
             // The display is no longer available. Ignore the attempt to add it.
@@ -215,10 +211,8 @@
             mPhysicalDisplayId = physicalDisplayId;
             mIsInternal = isInternal;
             mDisplayInfo = info;
-
-            updateDisplayConfigsLocked(configs, activeConfigId, configSpecs);
-            updateColorModesLocked(colorModes, activeColorMode);
-            updateHdrCapabilitiesLocked(hdrCapabilities);
+            updateDisplayProperties(configs, activeConfigId, configSpecs, colorModes,
+                    activeColorMode, hdrCapabilities);
             mSidekickInternal = LocalServices.getService(SidekickInternal.class);
             if (mIsInternal) {
                 LightsManager lights = LocalServices.getService(LightsManager.class);
@@ -240,13 +234,25 @@
             return true;
         }
 
+        /**
+         * Returns true if there is a change.
+         **/
+        public boolean updateDisplayProperties(SurfaceControl.DisplayConfig[] configs,
+                int activeConfigId, SurfaceControl.DesiredDisplayConfigSpecs configSpecs,
+                int[] colorModes, int activeColorMode, Display.HdrCapabilities hdrCapabilities) {
+            boolean changed = updateDisplayConfigsLocked(configs, activeConfigId, configSpecs);
+            changed |= updateColorModesLocked(colorModes, activeColorMode);
+            changed |= updateHdrCapabilitiesLocked(hdrCapabilities);
+            return changed;
+        }
+
         public boolean updateDisplayConfigsLocked(
                 SurfaceControl.DisplayConfig[] configs, int activeConfigId,
                 SurfaceControl.DesiredDisplayConfigSpecs configSpecs) {
             mDisplayConfigs = Arrays.copyOf(configs, configs.length);
             mActiveConfigId = activeConfigId;
             // Build an updated list of all existing modes.
-            ArrayList<DisplayModeRecord> records = new ArrayList<DisplayModeRecord>();
+            ArrayList<DisplayModeRecord> records = new ArrayList<>();
             boolean modesAdded = false;
             for (int i = 0; i < configs.length; i++) {
                 SurfaceControl.DisplayConfig config = configs[i];
@@ -286,7 +292,7 @@
 
             // Check whether surface flinger spontaneously changed modes out from under us.
             // Schedule traversals to ensure that the correct state is reapplied if necessary.
-            if (mActiveModeId != 0
+            if (mActiveModeId != NO_DISPLAY_MODE_ID
                     && mActiveModeId != activeRecord.mMode.getModeId()) {
                 mActiveModeInvalid = true;
                 sendTraversalRequestLocked();
@@ -294,12 +300,12 @@
 
             // Check whether surface flinger spontaneously changed display config specs out from
             // under us. If so, schedule a traversal to reapply our display config specs.
-            if (mDisplayModeSpecs.baseModeId != 0) {
+            if (mDisplayModeSpecs.baseModeId != NO_DISPLAY_MODE_ID) {
                 int activeBaseMode = findMatchingModeIdLocked(configSpecs.defaultConfig);
                 // If we can't map the defaultConfig index to a mode, then the physical display
                 // configs must have changed, and the code below for handling changes to the
                 // list of available modes will take care of updating display config specs.
-                if (activeBaseMode != 0) {
+                if (activeBaseMode != NO_DISPLAY_MODE_ID) {
                     if (mDisplayModeSpecs.baseModeId != activeBaseMode
                             || mDisplayModeSpecs.refreshRateRange.min != configSpecs.minRefreshRate
                             || mDisplayModeSpecs.refreshRateRange.max
@@ -323,18 +329,23 @@
                 mSupportedModes.put(record.mMode.getModeId(), record);
             }
 
-            // Update the default mode, if needed.
-            if (findDisplayConfigIdLocked(mDefaultModeId) < 0) {
-                if (mDefaultModeId != 0) {
-                    Slog.w(TAG, "Default display mode no longer available, using currently"
-                            + " active mode as default.");
-                }
+            // For a new display, we need to initialize the default mode ID.
+            if (mDefaultModeId == NO_DISPLAY_MODE_ID) {
+                mDefaultModeId = activeRecord.mMode.getModeId();
+            } else if (modesAdded && mActiveModeId != activeRecord.mMode.getModeId()) {
+                Slog.d(TAG, "New display modes are added and the active mode has changed, "
+                        + "use active mode as default mode.");
+                mActiveModeId = activeRecord.mMode.getModeId();
+                mDefaultModeId = activeRecord.mMode.getModeId();
+            } else if (findDisplayConfigIdLocked(mDefaultModeId) < 0) {
+                Slog.w(TAG, "Default display mode no longer available, using currently"
+                        + " active mode as default.");
                 mDefaultModeId = activeRecord.mMode.getModeId();
             }
 
             // Determine whether the display mode specs' base mode is still there.
             if (mSupportedModes.indexOfKey(mDisplayModeSpecs.baseModeId) < 0) {
-                if (mDisplayModeSpecs.baseModeId != 0) {
+                if (mDisplayModeSpecs.baseModeId != NO_DISPLAY_MODE_ID) {
                     Slog.w(TAG,
                             "DisplayModeSpecs base mode no longer available, using currently"
                                     + " active mode.");
@@ -345,7 +356,7 @@
 
             // Determine whether the active mode is still there.
             if (mSupportedModes.indexOfKey(mActiveModeId) < 0) {
-                if (mActiveModeId != 0) {
+                if (mActiveModeId != NO_DISPLAY_MODE_ID) {
                     Slog.w(TAG, "Active display mode no longer available, reverting to default"
                             + " mode.");
                 }
@@ -797,7 +808,7 @@
             }
             mActiveConfigId = activeConfigId;
             mActiveModeId = findMatchingModeIdLocked(activeConfigId);
-            mActiveModeInvalid = mActiveModeId == 0;
+            mActiveModeInvalid = mActiveModeId == NO_DISPLAY_MODE_ID;
             if (mActiveModeInvalid) {
                 Slog.w(TAG, "In unknown mode after setting allowed configs"
                         + ", activeConfigId=" + mActiveConfigId);
@@ -922,7 +933,7 @@
                     return record.mMode.getModeId();
                 }
             }
-            return 0;
+            return NO_DISPLAY_MODE_ID;
         }
 
         private void updateDeviceInfoLocked() {
diff --git a/services/core/java/com/android/server/incremental/IncrementalManagerShellCommand.java b/services/core/java/com/android/server/incremental/IncrementalManagerShellCommand.java
index 5161a77..b0e2e64 100644
--- a/services/core/java/com/android/server/incremental/IncrementalManagerShellCommand.java
+++ b/services/core/java/com/android/server/incremental/IncrementalManagerShellCommand.java
@@ -113,11 +113,27 @@
             return ERROR_COMMAND_EXECUTION;
         }
 
-        final Map<String, ParcelFileDescriptor> dataLoaderDynamicArgs = getDataLoaderDynamicArgs();
-        if (dataLoaderDynamicArgs == null) {
+        final Map<String, ParcelFileDescriptor> fds = getShellFileDescriptors();
+        if (fds == null) {
             pw.println("File names and sizes don't match.");
             return ERROR_DATA_LOADER_INIT;
         }
+        // dup FDs before closing them
+        final Map<String, ParcelFileDescriptor> dataLoaderDynamicArgs = new HashMap<>();
+        for (Map.Entry<String, ParcelFileDescriptor> nfd : fds.entrySet()) {
+            try {
+                dataLoaderDynamicArgs.put(nfd.getKey(), nfd.getValue().dup());
+            } catch (IOException ignored) {
+                pw.println("Failed to dup shell file descriptor");
+                return ERROR_DATA_LOADER_INIT;
+            } finally {
+                try {
+                    nfd.getValue().close();
+                } catch (IOException ignored) {
+                }
+            }
+        }
+
         final DataLoaderParams params = DataLoaderParams.forIncremental(
                 new ComponentName(LOADER_PACKAGE_NAME, LOADER_CLASS_NAME), "",
                 dataLoaderDynamicArgs);
@@ -131,17 +147,9 @@
         try {
             int sessionId = packageInstaller.createSession(sessionParams);
             pw.println("Successfully opened session: sessionId = " + sessionId);
-        } catch (Exception ex) {
+        } catch (IOException ex) {
             pw.println("Failed to create session.");
             return ERROR_COMMAND_EXECUTION;
-        } finally {
-            try {
-                for (Map.Entry<String, ParcelFileDescriptor> nfd
-                        : dataLoaderDynamicArgs.entrySet()) {
-                    nfd.getValue().close();
-                }
-            } catch (IOException ignored) {
-            }
         }
         return 0;
     }
@@ -177,7 +185,8 @@
                 InstallationFile file = installationFiles.get(i);
                 final int location = file.getFileType() == FILE_TYPE_OBB ? LOCATION_MEDIA_OBB
                         : LOCATION_DATA_APP;
-                session.addFile(location, file.getName(), file.getSize(), file.getMetadata(), null);
+                session.addFile(location, file.getName(), file.getSize(), file.getMetadata(),
+                        null);
             }
             session.commit(localReceiver.getIntentSender());
             final Intent result = localReceiver.getResult();
@@ -212,7 +221,8 @@
 
         private IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
             @Override
-            public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken,
+            public void send(int code, Intent intent, String resolvedType,
+                    IBinder whitelistToken,
                     IIntentReceiver finishedReceiver, String requiredPermission,
                     Bundle options) {
                 try {
@@ -237,14 +247,14 @@
     }
 
     /** Helpers. */
-    private Map<String, ParcelFileDescriptor> getDataLoaderDynamicArgs() {
-        Map<String, ParcelFileDescriptor> dataLoaderDynamicArgs = new HashMap<>();
+    private Map<String, ParcelFileDescriptor> getShellFileDescriptors() {
+        Map<String, ParcelFileDescriptor> fds = new HashMap<>();
         final FileDescriptor outFd = getOutFileDescriptor();
         final FileDescriptor inFd = getInFileDescriptor();
         try {
-            dataLoaderDynamicArgs.put("inFd", ParcelFileDescriptor.dup(inFd));
-            dataLoaderDynamicArgs.put("outFd", ParcelFileDescriptor.dup(outFd));
-            return dataLoaderDynamicArgs;
+            fds.put("inFd", ParcelFileDescriptor.dup(inFd));
+            fds.put("outFd", ParcelFileDescriptor.dup(outFd));
+            return fds;
         } catch (Exception ex) {
             Slog.e(TAG, "Failed to dup FDs");
             return null;
@@ -292,7 +302,8 @@
                         pw.println("Invalid file index in: " + fileArgs);
                         return null;
                     }
-                    final byte[] metadata = String.valueOf(index).getBytes(StandardCharsets.UTF_8);
+                    final byte[] metadata = String.valueOf(index).getBytes(
+                            StandardCharsets.UTF_8);
                     fileList.add(new InstallationFile(name, size, metadata));
                     break;
                 }
diff --git a/services/core/java/com/android/server/net/LockdownVpnTracker.java b/services/core/java/com/android/server/net/LockdownVpnTracker.java
index ef8f647..3cafaff 100644
--- a/services/core/java/com/android/server/net/LockdownVpnTracker.java
+++ b/services/core/java/com/android/server/net/LockdownVpnTracker.java
@@ -138,7 +138,7 @@
 
         if (egressDisconnected || egressChanged) {
             mAcceptedEgressIface = null;
-            mVpn.stopLegacyVpnPrivileged();
+            mVpn.stopVpnRunnerPrivileged();
         }
         if (egressDisconnected) {
             hideNotification();
@@ -218,7 +218,7 @@
         mAcceptedEgressIface = null;
         mErrorCount = 0;
 
-        mVpn.stopLegacyVpnPrivileged();
+        mVpn.stopVpnRunnerPrivileged();
         mVpn.setLockdown(false);
         hideNotification();
 
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 7bb782b..3e64e98 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -29,12 +29,14 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentSender;
+import android.content.LocusId;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.ILauncherApps;
 import android.content.pm.IOnAppsChangedListener;
 import android.content.pm.IPackageInstallerCallback;
 import android.content.pm.IPackageManager;
+import android.content.pm.IShortcutChangeCallback;
 import android.content.pm.LauncherApps;
 import android.content.pm.LauncherApps.ShortcutQuery;
 import android.content.pm.PackageInfo;
@@ -661,8 +663,8 @@
 
         @Override
         public ParceledListSlice getShortcuts(String callingPackage, long changedSince,
-                String packageName, List shortcutIds, ComponentName componentName, int flags,
-                UserHandle targetUser) {
+                String packageName, List shortcutIds, List<LocusId> locusIds,
+                ComponentName componentName, int flags, UserHandle targetUser) {
             ensureShortcutPermission(callingPackage);
             if (!canAccessProfile(targetUser.getIdentifier(), "Cannot get shortcuts")) {
                 return new ParceledListSlice<>(Collections.EMPTY_LIST);
@@ -671,16 +673,31 @@
                 throw new IllegalArgumentException(
                         "To query by shortcut ID, package name must also be set");
             }
+            if (locusIds != null && packageName == null) {
+                throw new IllegalArgumentException(
+                        "To query by locus ID, package name must also be set");
+            }
 
             // TODO(b/29399275): Eclipse compiler requires explicit List<ShortcutInfo> cast below.
             return new ParceledListSlice<>((List<ShortcutInfo>)
                     mShortcutServiceInternal.getShortcuts(getCallingUserId(),
-                            callingPackage, changedSince, packageName, shortcutIds,
+                            callingPackage, changedSince, packageName, shortcutIds, locusIds,
                             componentName, flags, targetUser.getIdentifier(),
                             injectBinderCallingPid(), injectBinderCallingUid()));
         }
 
         @Override
+        public void registerShortcutChangeCallback(String callingPackage, long changedSince,
+                String packageName, List shortcutIds, List<LocusId> locusIds,
+                ComponentName componentName, int flags, IShortcutChangeCallback callback,
+                int callbackId) {
+        }
+
+        @Override
+        public void unregisterShortcutChangeCallback(String callingPackage, int callbackId) {
+        }
+
+        @Override
         public void pinShortcuts(String callingPackage, String packageName, List<String> ids,
                 UserHandle targetUser) {
             ensureShortcutPermission(callingPackage);
@@ -1137,7 +1154,7 @@
                                 mShortcutServiceInternal.getShortcuts(launcherUserId,
                                         cookie.packageName,
                                         /* changedSince= */ 0, packageName, /* shortcutIds=*/ null,
-                                        /* component= */ null,
+                                        /* locusIds=*/ null, /* component= */ null,
                                         ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY
                                         | ShortcutQuery.FLAG_MATCH_ALL_KINDS_WITH_ALL_PINNED
                                         , userId, cookie.callingPid, cookie.callingUid);
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 8e5ff66..c14b42d 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -22757,6 +22757,11 @@
 
     private class PackageManagerNative extends IPackageManagerNative.Stub {
         @Override
+        public String[] getAllPackages() {
+            return PackageManagerService.this.getAllPackages().toArray(new String[0]);
+        }
+
+        @Override
         public String[] getNamesForUids(int[] uids) throws RemoteException {
             final String[] results = PackageManagerService.this.getNamesForUids(uids);
             // massage results so they can be parsed by the native binder
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index f7889ea..d16c074 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -33,6 +33,7 @@
 import android.content.IntentFilter;
 import android.content.IntentSender;
 import android.content.IntentSender.SendIntentException;
+import android.content.LocusId;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
@@ -1985,7 +1986,6 @@
             // Verify if caller is the shortcut owner, only if caller doesn't have ACCESS_SHORTCUTS.
             verifyShortcutInfoPackage(callingPackage, shortcut);
         }
-        final String shortcutPackage = shortcut.getPackage();
 
         final boolean ret;
         synchronized (mLock) {
@@ -1999,6 +1999,7 @@
             // someone already), then we just replace the existing one with this new one,
             // and then proceed the rest of the process.
             if (shortcut != null) {
+                final String shortcutPackage = shortcut.getPackage();
                 final ShortcutPackage ps = getPackageShortcutsForPublisherLocked(
                         shortcutPackage, userId);
                 final String id = shortcut.getId();
@@ -2155,48 +2156,6 @@
     }
 
     @Override
-    public ParceledListSlice<ShortcutInfo> getDynamicShortcuts(String packageName,
-            @UserIdInt int userId) {
-        verifyCaller(packageName, userId);
-
-        synchronized (mLock) {
-            throwIfUserLockedL(userId);
-
-            return getShortcutsWithQueryLocked(
-                    packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR,
-                    ShortcutInfo::isDynamicVisible);
-        }
-    }
-
-    @Override
-    public ParceledListSlice<ShortcutInfo> getManifestShortcuts(String packageName,
-            @UserIdInt int userId) {
-        verifyCaller(packageName, userId);
-
-        synchronized (mLock) {
-            throwIfUserLockedL(userId);
-
-            return getShortcutsWithQueryLocked(
-                    packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR,
-                    ShortcutInfo::isManifestVisible);
-        }
-    }
-
-    @Override
-    public ParceledListSlice<ShortcutInfo> getPinnedShortcuts(String packageName,
-            @UserIdInt int userId) {
-        verifyCaller(packageName, userId);
-
-        synchronized (mLock) {
-            throwIfUserLockedL(userId);
-
-            return getShortcutsWithQueryLocked(
-                    packageName, userId, ShortcutInfo.CLONE_REMOVE_FOR_CREATOR,
-                    ShortcutInfo::isPinnedVisible);
-        }
-    }
-
-    @Override
     public ParceledListSlice<ShortcutInfo> getShortcuts(String packageName,
             @ShortcutManager.ShortcutMatchFlags int matchFlags, @UserIdInt int userId) {
         verifyCaller(packageName, userId);
@@ -2631,7 +2590,7 @@
         public List<ShortcutInfo> getShortcuts(int launcherUserId,
                 @NonNull String callingPackage, long changedSince,
                 @Nullable String packageName, @Nullable List<String> shortcutIds,
-                @Nullable ComponentName componentName,
+                @Nullable List<LocusId> locusIds, @Nullable ComponentName componentName,
                 int queryFlags, int userId, int callingPid, int callingUid) {
             final ArrayList<ShortcutInfo> ret = new ArrayList<>();
 
@@ -2652,15 +2611,16 @@
 
                 if (packageName != null) {
                     getShortcutsInnerLocked(launcherUserId,
-                            callingPackage, packageName, shortcutIds, changedSince,
+                            callingPackage, packageName, shortcutIds, locusIds, changedSince,
                             componentName, queryFlags, userId, ret, cloneFlag,
                             callingPid, callingUid);
                 } else {
                     final List<String> shortcutIdsF = shortcutIds;
+                    final List<LocusId> locusIdsF = locusIds;
                     getUserShortcutsLocked(userId).forAllPackages(p -> {
                         getShortcutsInnerLocked(launcherUserId,
-                                callingPackage, p.getPackageName(), shortcutIdsF, changedSince,
-                                componentName, queryFlags, userId, ret, cloneFlag,
+                                callingPackage, p.getPackageName(), shortcutIdsF, locusIdsF,
+                                changedSince, componentName, queryFlags, userId, ret, cloneFlag,
                                 callingPid, callingUid);
                     });
                 }
@@ -2670,12 +2630,15 @@
 
         @GuardedBy("ShortcutService.this.mLock")
         private void getShortcutsInnerLocked(int launcherUserId, @NonNull String callingPackage,
-                @Nullable String packageName, @Nullable List<String> shortcutIds, long changedSince,
+                @Nullable String packageName, @Nullable List<String> shortcutIds,
+                @Nullable List<LocusId> locusIds, long changedSince,
                 @Nullable ComponentName componentName, int queryFlags,
                 int userId, ArrayList<ShortcutInfo> ret, int cloneFlag,
                 int callingPid, int callingUid) {
             final ArraySet<String> ids = shortcutIds == null ? null
                     : new ArraySet<>(shortcutIds);
+            final ArraySet<LocusId> locIds = locusIds == null ? null
+                    : new ArraySet<>(locusIds);
 
             final ShortcutUser user = getUserShortcutsLocked(userId);
             final ShortcutPackage p = user.getPackageShortcutsIfExists(packageName);
@@ -2702,6 +2665,9 @@
                         if (ids != null && !ids.contains(si.getId())) {
                             return false;
                         }
+                        if (locIds != null && !locIds.contains(si.getLocusId())) {
+                            return false;
+                        }
                         if (componentName != null) {
                             if (si.getActivity() != null
                                     && !si.getActivity().equals(componentName)) {
diff --git a/services/core/java/com/android/server/wm/ActivityStack.java b/services/core/java/com/android/server/wm/ActivityStack.java
index d5961a8..d380f8c 100644
--- a/services/core/java/com/android/server/wm/ActivityStack.java
+++ b/services/core/java/com/android/server/wm/ActivityStack.java
@@ -356,6 +356,8 @@
     // TODO(task-hierarchy): remove when tiles can be actual parents
     TaskTile mTile = null;
 
+    private int mLastTaskOrganizerWindowingMode = -1;
+
     private final Handler mHandler;
 
     private class ActivityStackHandler extends Handler {
@@ -794,6 +796,13 @@
         }
 
         final int windowingMode = getWindowingMode();
+        if (windowingMode == mLastTaskOrganizerWindowingMode) {
+            // If our windowing mode hasn't actually changed, then just stick
+            // with our old organizer. This lets us implement the semantic
+            // where SysUI can continue to manage it's old tasks
+            // while CTS temporarily takes over the registration.
+            return;
+        }
         /*
          * Different windowing modes may be managed by different task organizers. If
          * getTaskOrganizer returns null, we still call setTaskOrganizer to
@@ -802,6 +811,7 @@
         final ITaskOrganizer org =
             mWmService.mAtmService.mTaskOrganizerController.getTaskOrganizer(windowingMode);
         setTaskOrganizer(org);
+        mLastTaskOrganizerWindowingMode = windowingMode;
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index 44a6fc9..0733a72 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -74,11 +74,10 @@
         @Override
         public void binderDied() {
             synchronized (mGlobalLock) {
-                final TaskOrganizerState state = mTaskOrganizerStates.get(mTaskOrganizer);
-                for (int i = 0; i < state.mOrganizedTasks.size(); i++) {
-                    state.mOrganizedTasks.get(i).taskOrganizerDied();
-                }
-                mTaskOrganizerStates.remove(mTaskOrganizer);
+                final TaskOrganizerState state =
+                    mTaskOrganizerStates.get(mTaskOrganizer.asBinder());
+                state.releaseTasks();
+                mTaskOrganizerStates.remove(mTaskOrganizer.asBinder());
                 if (mTaskOrganizersForWindowingMode.get(mWindowingMode) == mTaskOrganizer) {
                     mTaskOrganizersForWindowingMode.remove(mWindowingMode);
                 }
@@ -89,26 +88,76 @@
     class TaskOrganizerState {
         ITaskOrganizer mOrganizer;
         DeathRecipient mDeathRecipient;
+        int mWindowingMode;
 
         ArrayList<Task> mOrganizedTasks = new ArrayList<>();
 
+        // Save the TaskOrganizer which we replaced registration for
+        // so it can be re-registered if we unregister.
+        TaskOrganizerState mReplacementFor;
+        boolean mDisposed = false;
+
+
+        TaskOrganizerState(ITaskOrganizer organizer, int windowingMode,
+                TaskOrganizerState replacing) {
+            mOrganizer = organizer;
+            mDeathRecipient = new DeathRecipient(organizer, windowingMode);
+            try {
+                organizer.asBinder().linkToDeath(mDeathRecipient, 0);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "TaskOrganizer failed to register death recipient");
+            }
+            mWindowingMode = windowingMode;
+            mReplacementFor = replacing;
+        }
+
         void addTask(Task t) {
             mOrganizedTasks.add(t);
+            try {
+                mOrganizer.taskAppeared(t.getTaskInfo());
+            } catch (Exception e) {
+                Slog.e(TAG, "Exception sending taskAppeared callback" + e);
+            }
         }
 
         void removeTask(Task t) {
+            try {
+                mOrganizer.taskVanished(t.getRemoteToken());
+            } catch (Exception e) {
+                Slog.e(TAG, "Exception sending taskVanished callback" + e);
+            }
             mOrganizedTasks.remove(t);
         }
 
-        TaskOrganizerState(ITaskOrganizer organizer, DeathRecipient deathRecipient) {
-            mOrganizer = organizer;
-            mDeathRecipient = deathRecipient;
+        void dispose() {
+            mDisposed = true;
+            releaseTasks();
+            handleReplacement();
+        }
+
+        void releaseTasks() {
+            for (int i = mOrganizedTasks.size() - 1; i >= 0; i--) {
+                final Task t = mOrganizedTasks.get(i);
+                t.taskOrganizerDied();
+                removeTask(t);
+            }
+        }
+
+        void handleReplacement() {
+            if (mReplacementFor != null && !mReplacementFor.mDisposed) {
+                mTaskOrganizersForWindowingMode.put(mWindowingMode, mReplacementFor);
+            }
+        }
+
+        void unlinkDeath() {
+            mDisposed = true;
+            mOrganizer.asBinder().unlinkToDeath(mDeathRecipient, 0);
         }
     };
 
 
     final HashMap<Integer, TaskOrganizerState> mTaskOrganizersForWindowingMode = new HashMap();
-    final HashMap<ITaskOrganizer, TaskOrganizerState> mTaskOrganizerStates = new HashMap();
+    final HashMap<IBinder, TaskOrganizerState> mTaskOrganizerStates = new HashMap();
 
     final HashMap<Integer, ITaskOrganizer> mTaskOrganizersByPendingSyncId = new HashMap();
 
@@ -128,17 +177,10 @@
         mService.mAmInternal.enforceCallingPermission(MANAGE_ACTIVITY_STACKS, func);
     }
 
-    private void clearIfNeeded(int windowingMode) {
-        final TaskOrganizerState oldState = mTaskOrganizersForWindowingMode.get(windowingMode);
-        if (oldState != null) {
-            oldState.mOrganizer.asBinder().unlinkToDeath(oldState.mDeathRecipient, 0);
-        }
-    }
-
     /**
      * 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.
+     * but will continue to organize it's existing tasks.
      */
     @Override
     public void registerTaskOrganizer(ITaskOrganizer organizer, int windowingMode) {
@@ -153,24 +195,25 @@
         final long origId = Binder.clearCallingIdentity();
         try {
             synchronized (mGlobalLock) {
-                clearIfNeeded(windowingMode);
-                DeathRecipient dr = new DeathRecipient(organizer, windowingMode);
-                try {
-                    organizer.asBinder().linkToDeath(dr, 0);
-                } catch (RemoteException e) {
-                    Slog.e(TAG, "TaskOrganizer failed to register death recipient");
-                }
-
-                final TaskOrganizerState state = new TaskOrganizerState(organizer, dr);
+                final TaskOrganizerState state = new TaskOrganizerState(organizer, windowingMode,
+                        mTaskOrganizersForWindowingMode.get(windowingMode));
                 mTaskOrganizersForWindowingMode.put(windowingMode, state);
-
-                mTaskOrganizerStates.put(organizer, state);
+                mTaskOrganizerStates.put(organizer.asBinder(), state);
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
         }
     }
 
+    void unregisterTaskOrganizer(ITaskOrganizer organizer) {
+        final TaskOrganizerState state = mTaskOrganizerStates.get(organizer.asBinder());
+        state.unlinkDeath();
+        if (mTaskOrganizersForWindowingMode.get(state.mWindowingMode) == state) {
+            mTaskOrganizersForWindowingMode.remove(state.mWindowingMode);
+        }
+        state.dispose();
+    }
+
     ITaskOrganizer getTaskOrganizer(int windowingMode) {
         final TaskOrganizerState state = mTaskOrganizersForWindowingMode.get(windowingMode);
         if (state == null) {
@@ -179,35 +222,13 @@
         return state.mOrganizer;
     }
 
-    private void sendTaskAppeared(ITaskOrganizer organizer, Task task) {
-        try {
-            organizer.taskAppeared(task.getTaskInfo());
-        } catch (Exception e) {
-            Slog.e(TAG, "Exception sending taskAppeared callback" + e);
-        }
-    }
-
-    private void sendTaskVanished(ITaskOrganizer organizer, Task task) {
-        try {
-            organizer.taskVanished(task.getRemoteToken());
-        } catch (Exception e) {
-            Slog.e(TAG, "Exception sending taskVanished callback" + e);
-        }
-    }
-
     void onTaskAppeared(ITaskOrganizer organizer, Task task) {
-        TaskOrganizerState state = mTaskOrganizerStates.get(organizer);
-
+        TaskOrganizerState state = mTaskOrganizerStates.get(organizer.asBinder());
         state.addTask(task);
-        sendTaskAppeared(organizer, task);
     }
 
     void onTaskVanished(ITaskOrganizer organizer, Task task) {
-        final TaskOrganizerState state = mTaskOrganizerStates.get(organizer);
-        sendTaskVanished(organizer, task);
-
-        // This could trigger TaskAppeared for other tasks in the same stack so make sure
-        // we do this AFTER sending taskVanished.
+        final TaskOrganizerState state = mTaskOrganizerStates.get(organizer.asBinder());
         state.removeTask(task);
     }
 
diff --git a/services/core/java/com/android/server/wm/utils/DisplayRotationUtil.java b/services/core/java/com/android/server/wm/utils/DisplayRotationUtil.java
index 9f307bb..59abaab 100644
--- a/services/core/java/com/android/server/wm/utils/DisplayRotationUtil.java
+++ b/services/core/java/com/android/server/wm/utils/DisplayRotationUtil.java
@@ -59,7 +59,7 @@
     }
 
     /**
-     * Compute bounds after rotating teh screen.
+     * Compute bounds after rotating the screen.
      *
      * @param bounds Bounds before the rotation. The array must contain exactly 4 non-null elements.
      * @param rotation rotation constant defined in android.view.Surface.
diff --git a/services/people/java/com/android/server/people/data/DataManager.java b/services/people/java/com/android/server/people/data/DataManager.java
index 79503f7..43e7738 100644
--- a/services/people/java/com/android/server/people/data/DataManager.java
+++ b/services/people/java/com/android/server/people/data/DataManager.java
@@ -293,13 +293,14 @@
                 | ShortcutQuery.FLAG_MATCH_PINNED | ShortcutQuery.FLAG_MATCH_PINNED_BY_ANY_LAUNCHER;
         return mShortcutServiceInternal.getShortcuts(
                 mInjector.getCallingUserId(), /*callingPackage=*/ PLATFORM_PACKAGE_NAME,
-                /*changedSince=*/ 0, packageName, shortcutIds, /*componentName=*/ null, queryFlags,
-                userId, MY_PID, MY_UID);
+                /*changedSince=*/ 0, packageName, shortcutIds, /*locusIds=*/ null,
+                /*componentName=*/ null, queryFlags, userId, MY_PID, MY_UID);
     }
 
     private void forAllUnlockedUsers(Consumer<UserData> consumer) {
         for (int i = 0; i < mUserDataArray.size(); i++) {
-            UserData userData = mUserDataArray.get(i);
+            int userId = mUserDataArray.keyAt(i);
+            UserData userData = mUserDataArray.get(userId);
             if (userData.isUnlocked()) {
                 consumer.accept(userData);
             }
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
index 4a40b80..6d15302 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/LocalDisplayAdapterTest.java
@@ -20,6 +20,8 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 
@@ -29,6 +31,7 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
+import android.view.Display;
 import android.view.DisplayAddress;
 import android.view.SurfaceControl;
 
@@ -47,6 +50,7 @@
 import org.mockito.quality.Strictness;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.LinkedList;
 
 
@@ -167,6 +171,7 @@
      */
     @Test
     public void testDpiValues() throws Exception {
+        // needs default one always
         setUpDisplay(new FakeDisplay(PORT_A));
         setUpDisplay(new FakeDisplay(PORT_B));
         updateAvailableDisplays();
@@ -182,6 +187,67 @@
                 16000);
     }
 
+    @Test
+    public void testAfterDisplayChange_ModesAreUpdated() throws Exception {
+        SurfaceControl.DisplayConfig displayInfo = createFakeDisplayConfig(1920, 1080, 60f);
+        SurfaceControl.DisplayConfig[] configs =
+                new SurfaceControl.DisplayConfig[]{displayInfo};
+        FakeDisplay display = new FakeDisplay(PORT_A, configs, 0);
+        setUpDisplay(display);
+        updateAvailableDisplays();
+        mAdapter.registerLocked();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+        assertThat(mListener.changedDisplays).isEmpty();
+
+        DisplayDeviceInfo displayDeviceInfo = mListener.addedDisplays.get(
+                0).getDisplayDeviceInfoLocked();
+
+        assertThat(displayDeviceInfo.supportedModes.length).isEqualTo(configs.length);
+        assertModeIsSupported(displayDeviceInfo.supportedModes, displayInfo);
+
+        Display.Mode defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId);
+        assertThat(defaultMode.matches(displayInfo.width, displayInfo.height,
+                displayInfo.refreshRate)).isTrue();
+
+        Display.Mode activeMode = getModeById(displayDeviceInfo, displayDeviceInfo.modeId);
+        assertThat(activeMode.matches(displayInfo.width, displayInfo.height,
+                displayInfo.refreshRate)).isTrue();
+
+        // Change the display
+        SurfaceControl.DisplayConfig addedDisplayInfo = createFakeDisplayConfig(3840, 2160,
+                60f);
+        configs = new SurfaceControl.DisplayConfig[]{displayInfo, addedDisplayInfo};
+        display.configs = configs;
+        display.activeConfig = 1;
+        setUpDisplay(display);
+        mAdapter.registerLocked();
+        waitForHandlerToComplete(mHandler, HANDLER_WAIT_MS);
+
+        assertThat(SurfaceControl.getActiveConfig(display.token)).isEqualTo(1);
+        assertThat(SurfaceControl.getDisplayConfigs(display.token).length).isEqualTo(2);
+
+        assertThat(mListener.addedDisplays.size()).isEqualTo(1);
+        assertThat(mListener.changedDisplays.size()).isEqualTo(1);
+
+        DisplayDevice displayDevice = mListener.changedDisplays.get(0);
+        displayDevice.applyPendingDisplayDeviceInfoChangesLocked();
+        displayDeviceInfo = displayDevice.getDisplayDeviceInfoLocked();
+
+        assertThat(displayDeviceInfo.supportedModes.length).isEqualTo(configs.length);
+        assertModeIsSupported(displayDeviceInfo.supportedModes, displayInfo);
+        assertModeIsSupported(displayDeviceInfo.supportedModes, addedDisplayInfo);
+
+        activeMode = getModeById(displayDeviceInfo, displayDeviceInfo.modeId);
+        assertThat(activeMode.matches(addedDisplayInfo.width, addedDisplayInfo.height,
+                addedDisplayInfo.refreshRate)).isTrue();
+
+        defaultMode = getModeById(displayDeviceInfo, displayDeviceInfo.defaultModeId);
+        assertThat(defaultMode.matches(addedDisplayInfo.width, addedDisplayInfo.height,
+                addedDisplayInfo.refreshRate)).isTrue();
+    }
+
     private void assertDisplayDpi(DisplayDeviceInfo info, int expectedPort,
                                   float expectedXdpi,
                                   float expectedYDpi,
@@ -194,16 +260,40 @@
         assertEquals(expectedDensityDpi, info.densityDpi);
     }
 
+    private Display.Mode getModeById(DisplayDeviceInfo displayDeviceInfo, int modeId) {
+        return Arrays.stream(displayDeviceInfo.supportedModes)
+                .filter(mode -> mode.getModeId() == modeId)
+                .findFirst()
+                .get();
+    }
+
+    private void assertModeIsSupported(Display.Mode[] supportedModes,
+            SurfaceControl.DisplayConfig mode) {
+        assertThat(Arrays.stream(supportedModes).anyMatch(
+                x -> x.matches(mode.width, mode.height, mode.refreshRate))).isTrue();
+    }
+
     private static class FakeDisplay {
         public final DisplayAddress.Physical address;
         public final IBinder token = new Binder();
         public final SurfaceControl.DisplayInfo info;
-        public final SurfaceControl.DisplayConfig config;
+        public SurfaceControl.DisplayConfig[] configs;
+        public int activeConfig;
 
         private FakeDisplay(int port) {
             this.address = createDisplayAddress(port);
             this.info = createFakeDisplayInfo();
-            this.config = createFakeDisplayConfig();
+            this.configs = new SurfaceControl.DisplayConfig[]{
+                    createFakeDisplayConfig(800, 600, 60f)
+            };
+            this.activeConfig = 0;
+        }
+
+        private FakeDisplay(int port, SurfaceControl.DisplayConfig[] configs, int activeConfig) {
+            this.address = createDisplayAddress(port);
+            this.info = createFakeDisplayInfo();
+            this.configs = configs;
+            this.activeConfig = activeConfig;
         }
     }
 
@@ -212,9 +302,9 @@
         doReturn(display.token).when(() ->
                 SurfaceControl.getPhysicalDisplayToken(display.address.getPhysicalDisplayId()));
         doReturn(display.info).when(() -> SurfaceControl.getDisplayInfo(display.token));
-        doReturn(new SurfaceControl.DisplayConfig[] { display.config }).when(
+        doReturn(display.configs).when(
                 () -> SurfaceControl.getDisplayConfigs(display.token));
-        doReturn(0).when(() -> SurfaceControl.getActiveConfig(display.token));
+        doReturn(display.activeConfig).when(() -> SurfaceControl.getActiveConfig(display.token));
         doReturn(0).when(() -> SurfaceControl.getActiveColorMode(display.token));
         doReturn(new int[] { 0 }).when(
                 () -> SurfaceControl.getDisplayColorModes(display.token));
@@ -242,10 +332,12 @@
         return info;
     }
 
-    private static SurfaceControl.DisplayConfig createFakeDisplayConfig() {
+    private static SurfaceControl.DisplayConfig createFakeDisplayConfig(int width, int height,
+            float refreshRate) {
         final SurfaceControl.DisplayConfig config = new SurfaceControl.DisplayConfig();
-        config.width = 800;
-        config.height = 600;
+        config.width = width;
+        config.height = height;
+        config.refreshRate = refreshRate;
         config.xDpi = 100;
         config.yDpi = 100;
         return config;
@@ -266,17 +358,19 @@
 
     private class TestListener implements DisplayAdapter.Listener {
         public ArrayList<DisplayDevice> addedDisplays = new ArrayList<>();
+        public ArrayList<DisplayDevice> changedDisplays = new ArrayList<>();
 
         @Override
         public void onDisplayDeviceEvent(DisplayDevice device, int event) {
             if (event == DisplayAdapter.DISPLAY_DEVICE_EVENT_ADDED) {
                 addedDisplays.add(device);
+            } else if (event == DisplayAdapter.DISPLAY_DEVICE_EVENT_CHANGED) {
+                changedDisplays.add(device);
             }
         }
 
         @Override
         public void onTraversalRequested() {
-
         }
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index 7d5cb13..39a749f 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -5447,28 +5447,28 @@
         assertTrue(dpms.isAdminActive(admin1, UserHandle.USER_SYSTEM));
     }
 
-    @FlakyTest(bugId = 148934649)
-    public void testRevertDeviceOwnership_adminAndDeviceMigrated() throws Exception {
-        DpmTestUtils.writeInputStreamToFile(
-                getRawStream(com.android.frameworks.servicestests.R.raw.active_admin_migrated),
-                getDeviceOwnerPoliciesFile());
-        DpmTestUtils.writeInputStreamToFile(
-                getRawStream(com.android.frameworks.servicestests.R.raw.device_owner_migrated),
-                getDeviceOwnerFile());
-        assertDeviceOwnershipRevertedWithFakeTransferMetadata();
-    }
+    // @FlakyTest(bugId = 148934649)
+    // public void testRevertDeviceOwnership_adminAndDeviceMigrated() throws Exception {
+    //     DpmTestUtils.writeInputStreamToFile(
+    //             getRawStream(com.android.frameworks.servicestests.R.raw.active_admin_migrated),
+    //             getDeviceOwnerPoliciesFile());
+    //     DpmTestUtils.writeInputStreamToFile(
+    //             getRawStream(com.android.frameworks.servicestests.R.raw.device_owner_migrated),
+    //             getDeviceOwnerFile());
+    //     assertDeviceOwnershipRevertedWithFakeTransferMetadata();
+    // }
 
-    @FlakyTest(bugId = 148934649)
-    public void testRevertDeviceOwnership_deviceNotMigrated()
-            throws Exception {
-        DpmTestUtils.writeInputStreamToFile(
-                getRawStream(com.android.frameworks.servicestests.R.raw.active_admin_migrated),
-                getDeviceOwnerPoliciesFile());
-        DpmTestUtils.writeInputStreamToFile(
-                getRawStream(com.android.frameworks.servicestests.R.raw.device_owner_not_migrated),
-                getDeviceOwnerFile());
-        assertDeviceOwnershipRevertedWithFakeTransferMetadata();
-    }
+    // @FlakyTest(bugId = 148934649)
+    // public void testRevertDeviceOwnership_deviceNotMigrated()
+    //         throws Exception {
+    //     DpmTestUtils.writeInputStreamToFile(
+    //             getRawStream(com.android.frameworks.servicestests.R.raw.active_admin_migrated),
+    //             getDeviceOwnerPoliciesFile());
+    //     DpmTestUtils.writeInputStreamToFile(
+    //             getRawStream(com.android.frameworks.servicestests.R.raw.device_owner_not_migrated),
+    //             getDeviceOwnerFile());
+    //     assertDeviceOwnershipRevertedWithFakeTransferMetadata();
+    // }
 
     public void testRevertDeviceOwnership_adminAndDeviceNotMigrated()
             throws Exception {
@@ -5490,31 +5490,31 @@
         UserHandle userHandle = UserHandle.of(DpmMockContext.CALLER_USER_HANDLE);
     }
 
-    @FlakyTest(bugId = 148934649)
-    public void testRevertProfileOwnership_adminAndProfileMigrated() throws Exception {
-        getServices().addUser(DpmMockContext.CALLER_USER_HANDLE, 0,
-                UserManager.USER_TYPE_PROFILE_MANAGED, UserHandle.USER_SYSTEM);
-        DpmTestUtils.writeInputStreamToFile(
-                getRawStream(com.android.frameworks.servicestests.R.raw.active_admin_migrated),
-                getProfileOwnerPoliciesFile());
-        DpmTestUtils.writeInputStreamToFile(
-                getRawStream(com.android.frameworks.servicestests.R.raw.profile_owner_migrated),
-                getProfileOwnerFile());
-        assertProfileOwnershipRevertedWithFakeTransferMetadata();
-    }
+    // @FlakyTest(bugId = 148934649)
+    // public void testRevertProfileOwnership_adminAndProfileMigrated() throws Exception {
+    //     getServices().addUser(DpmMockContext.CALLER_USER_HANDLE, 0,
+    //             UserManager.USER_TYPE_PROFILE_MANAGED, UserHandle.USER_SYSTEM);
+    //     DpmTestUtils.writeInputStreamToFile(
+    //             getRawStream(com.android.frameworks.servicestests.R.raw.active_admin_migrated),
+    //             getProfileOwnerPoliciesFile());
+    //     DpmTestUtils.writeInputStreamToFile(
+    //             getRawStream(com.android.frameworks.servicestests.R.raw.profile_owner_migrated),
+    //             getProfileOwnerFile());
+    //     assertProfileOwnershipRevertedWithFakeTransferMetadata();
+    // }
 
-    @FlakyTest(bugId = 148934649)
-    public void testRevertProfileOwnership_profileNotMigrated() throws Exception {
-        getServices().addUser(DpmMockContext.CALLER_USER_HANDLE, 0,
-                UserManager.USER_TYPE_PROFILE_MANAGED, UserHandle.USER_SYSTEM);
-        DpmTestUtils.writeInputStreamToFile(
-                getRawStream(com.android.frameworks.servicestests.R.raw.active_admin_migrated),
-                getProfileOwnerPoliciesFile());
-        DpmTestUtils.writeInputStreamToFile(
-                getRawStream(com.android.frameworks.servicestests.R.raw.profile_owner_not_migrated),
-                getProfileOwnerFile());
-        assertProfileOwnershipRevertedWithFakeTransferMetadata();
-    }
+    // @FlakyTest(bugId = 148934649)
+    // public void testRevertProfileOwnership_profileNotMigrated() throws Exception {
+    //     getServices().addUser(DpmMockContext.CALLER_USER_HANDLE, 0,
+    //             UserManager.USER_TYPE_PROFILE_MANAGED, UserHandle.USER_SYSTEM);
+    //     DpmTestUtils.writeInputStreamToFile(
+    //             getRawStream(com.android.frameworks.servicestests.R.raw.active_admin_migrated),
+    //             getProfileOwnerPoliciesFile());
+    //     DpmTestUtils.writeInputStreamToFile(
+    //             getRawStream(com.android.frameworks.servicestests.R.raw.profile_owner_not_migrated),
+    //             getProfileOwnerFile());
+    //     assertProfileOwnershipRevertedWithFakeTransferMetadata();
+    // }
 
     public void testRevertProfileOwnership_adminAndProfileNotMigrated() throws Exception {
         getServices().addUser(DpmMockContext.CALLER_USER_HANDLE, 0,
diff --git a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
index 62ea425..9d2091a 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
@@ -232,7 +232,7 @@
         mDataManager.getShortcut(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID);
         verify(mShortcutServiceInternal).getShortcuts(anyInt(), anyString(), anyLong(),
                 eq(TEST_PKG_NAME), eq(Collections.singletonList(TEST_SHORTCUT_ID)),
-                eq(null), anyInt(), eq(USER_ID_PRIMARY), anyInt(), anyInt());
+                eq(null), eq(null), anyInt(), eq(USER_ID_PRIMARY), anyInt(), anyInt());
     }
 
     @Test
@@ -263,6 +263,7 @@
     @Test
     public void testContactsChanged() {
         mDataManager.onUserUnlocked(USER_ID_PRIMARY);
+        mDataManager.onUserUnlocked(USER_ID_SECONDARY);
 
         ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
                 buildPerson());
@@ -289,6 +290,7 @@
     @Test
     public void testNotificationListener() {
         mDataManager.onUserUnlocked(USER_ID_PRIMARY);
+        mDataManager.onUserUnlocked(USER_ID_SECONDARY);
 
         ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
                 buildPerson());
@@ -341,6 +343,7 @@
     @Test
     public void testCallLogContentObserver() {
         mDataManager.onUserUnlocked(USER_ID_PRIMARY);
+        mDataManager.onUserUnlocked(USER_ID_SECONDARY);
 
         ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
                 buildPerson());
@@ -368,6 +371,7 @@
     @Test
     public void testMmsSmsContentObserver() {
         mDataManager.onUserUnlocked(USER_ID_PRIMARY);
+        mDataManager.onUserUnlocked(USER_ID_SECONDARY);
 
         ShortcutInfo shortcut = buildShortcutInfo(TEST_PKG_NAME, USER_ID_PRIMARY, TEST_SHORTCUT_ID,
                 buildPerson());
diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
index 41416f1..3d190be 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
@@ -52,6 +52,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.IntentSender;
+import android.content.LocusId;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.ILauncherApps;
@@ -1600,6 +1601,22 @@
     }
 
     /**
+     * Make a shortcut with an ID and a locus ID.
+     */
+    protected ShortcutInfo makeShortcutWithLocusId(String id, LocusId locusId) {
+        final ShortcutInfo.Builder  b = new ShortcutInfo.Builder(mClientContext, id)
+                .setActivity(new ComponentName(mClientContext.getPackageName(), "main"))
+                .setShortLabel("title-" + id)
+                .setIntent(makeIntent(Intent.ACTION_VIEW, ShortcutActivity.class))
+                .setLocusId(locusId);
+        final ShortcutInfo s = b.build();
+
+        s.setTimestamp(mInjectedCurrentTimeMillis); // HACK
+
+        return s;
+    }
+
+    /**
      * Make an intent.
      */
     protected Intent makeIntent(String action, Class<?> clazz, Object... bundleKeysAndValues) {
@@ -1618,6 +1635,13 @@
     }
 
     /**
+     * Make a LocusId.
+     */
+    protected LocusId makeLocusId(String id) {
+        return new LocusId(id);
+    }
+
+    /**
      * Make an component name, with the client context.
      */
     @NonNull
@@ -1955,16 +1979,17 @@
     protected static ShortcutQuery buildQuery(long changedSince,
             String packageName, ComponentName componentName,
             /* @ShortcutQuery.QueryFlags */ int flags) {
-        return buildQuery(changedSince, packageName, null, componentName, flags);
+        return buildQuery(changedSince, packageName, null, null, componentName, flags);
     }
 
     protected static ShortcutQuery buildQuery(long changedSince,
-            String packageName, List<String> shortcutIds, ComponentName componentName,
-            /* @ShortcutQuery.QueryFlags */ int flags) {
+            String packageName, List<String> shortcutIds, List<LocusId> locusIds,
+            ComponentName componentName, /* @ShortcutQuery.QueryFlags */ int flags) {
         final ShortcutQuery q = new ShortcutQuery();
         q.setChangedSince(changedSince);
         q.setPackage(packageName);
         q.setShortcutIds(shortcutIds);
+        q.setLocusIds(locusIds);
         q.setActivity(componentName);
         q.setQueryFlags(flags);
         return q;
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 798420e..63da5fb 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -1372,7 +1372,7 @@
 
         setCaller(CALLING_PACKAGE_1);
         final ShortcutInfo s1_1 = makeShortcut("s1");
-        final ShortcutInfo s1_2 = makeShortcut("s2");
+        final ShortcutInfo s1_2 = makeShortcutWithLocusId("s2", makeLocusId("l1"));
 
         assertTrue(mManager.setDynamicShortcuts(list(s1_1, s1_2)));
 
@@ -1394,7 +1394,7 @@
         getCallerShortcut("s4").setTimestamp(500);
 
         setCaller(CALLING_PACKAGE_3);
-        final ShortcutInfo s3_2 = makeShortcut("s3");
+        final ShortcutInfo s3_2 = makeShortcutWithLocusId("s3", makeLocusId("l2"));
         assertTrue(mManager.setDynamicShortcuts(list(s3_2)));
 
         getCallerShortcut("s3").setTimestamp(START_TIME + 5000);
@@ -1446,7 +1446,7 @@
         // With ID.
         assertAllDynamic(assertAllNotHaveTitle(assertAllNotHaveIntents(assertShortcutIds(
                 assertAllKeyFieldsOnly(mLauncherApps.getShortcuts(buildQuery(
-                        /* time =*/ 1000, CALLING_PACKAGE_2, list("s3"),
+                        /* time =*/ 1000, CALLING_PACKAGE_2, list("s3"), /* locusIds =*/ null,
                         /* activity =*/ null,
                         ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY),
                         getCallingUser())),
@@ -1454,20 +1454,51 @@
         assertAllDynamic(assertAllNotHaveTitle(assertAllNotHaveIntents(assertShortcutIds(
                 assertAllKeyFieldsOnly(mLauncherApps.getShortcuts(buildQuery(
                         /* time =*/ 1000, CALLING_PACKAGE_2, list("s3", "s2", "ss"),
-                        /* activity =*/ null,
+                        /* locusIds =*/ null, /* activity =*/ null,
                         ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY),
                         getCallingUser())),
                 "s2", "s3"))));
         assertAllDynamic(assertAllNotHaveTitle(assertAllNotHaveIntents(assertShortcutIds(
                 assertAllKeyFieldsOnly(mLauncherApps.getShortcuts(buildQuery(
                         /* time =*/ 1000, CALLING_PACKAGE_2, list("s3x", "s2x"),
-                        /* activity =*/ null,
+                        /* locusIds =*/ null, /* activity =*/ null,
                         ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY),
                         getCallingUser()))
                 /* empty */))));
         assertAllDynamic(assertAllNotHaveTitle(assertAllNotHaveIntents(assertShortcutIds(
                 assertAllKeyFieldsOnly(mLauncherApps.getShortcuts(buildQuery(
-                        /* time =*/ 1000, CALLING_PACKAGE_2, list(),
+                        /* time =*/ 1000, CALLING_PACKAGE_2, list(), /* locusIds =*/ null,
+                        /* activity =*/ null,
+                        ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY),
+                        getCallingUser()))
+                /* empty */))));
+
+        // With locus ID.
+        assertAllDynamic(assertAllNotHaveTitle(assertAllNotHaveIntents(assertShortcutIds(
+                assertAllKeyFieldsOnly(mLauncherApps.getShortcuts(buildQuery(
+                        /* time =*/ 1000, CALLING_PACKAGE_3, /* shortcutIds =*/ null,
+                        list(makeLocusId("l2")), /* activity =*/ null,
+                        ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY),
+                        getCallingUser())),
+                "s3"))));
+        assertAllDynamic(assertAllNotHaveTitle(assertAllNotHaveIntents(assertShortcutIds(
+                assertAllKeyFieldsOnly(mLauncherApps.getShortcuts(buildQuery(
+                        /* time =*/ 1000, CALLING_PACKAGE_1, /* shortcutIds =*/ null,
+                        list(makeLocusId("l1"), makeLocusId("l2"), makeLocusId("l3")),
+                        /* activity =*/ null,
+                        ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY),
+                        getCallingUser())),
+                "s2"))));
+        assertAllDynamic(assertAllNotHaveTitle(assertAllNotHaveIntents(assertShortcutIds(
+                assertAllKeyFieldsOnly(mLauncherApps.getShortcuts(buildQuery(
+                        /* time =*/ 1000, CALLING_PACKAGE_1, /* shortcutIds =*/ null,
+                        list(makeLocusId("lx1"), makeLocusId("lx2")), /* activity =*/ null,
+                        ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY),
+                        getCallingUser()))
+                /* empty */))));
+        assertAllDynamic(assertAllNotHaveTitle(assertAllNotHaveIntents(assertShortcutIds(
+                assertAllKeyFieldsOnly(mLauncherApps.getShortcuts(buildQuery(
+                        /* time =*/ 1000, CALLING_PACKAGE_3, /* shortcutIds =*/ null, list(),
                         /* activity =*/ null,
                         ShortcutQuery.FLAG_GET_DYNAMIC | ShortcutQuery.FLAG_GET_KEY_FIELDS_ONLY),
                         getCallingUser()))
@@ -1498,7 +1529,7 @@
         assertExpectException(
                 IllegalArgumentException.class, "package name must also be set", () -> {
                     mLauncherApps.getShortcuts(buildQuery(
-                    /* time =*/ 0, /* package= */ null, list("id"),
+                    /* time =*/ 0, /* package= */ null, list("id"), /* locusIds =*/ null,
                     /* activity =*/ null, /* flags */ 0), getCallingUser());
                 });
 
@@ -1537,7 +1568,7 @@
         assertExpectException(
                 IllegalArgumentException.class, "package name must also be set", () -> {
                     mLauncherApps.getShortcuts(buildQuery(
-                            /* time =*/ 0, /* package= */ null, list("id"),
+                            /* time =*/ 0, /* package= */ null, list("id"), /* locusIds= */ null,
                             /* activity =*/ null, /* flags */ 0), getCallingUser());
                 });
 
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 078347e..a9a20f6 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskOrganizerTests.java
@@ -139,6 +139,52 @@
     }
 
     @Test
+    public void testUnregisterOrganizer() throws RemoteException {
+        final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent);
+        final Task task = createTaskInStack(stack, 0 /* userId */);
+        final ITaskOrganizer organizer = registerMockOrganizer();
+
+        stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+        verify(organizer).taskAppeared(any());
+        assertTrue(stack.isControlledByTaskOrganizer());
+
+        mWm.mAtmService.mTaskOrganizerController.unregisterTaskOrganizer(organizer);
+        verify(organizer).taskVanished(any());
+        assertFalse(stack.isControlledByTaskOrganizer());
+    }
+
+    @Test
+    public void testUnregisterOrganizerReturnsRegistrationToPrevious() throws RemoteException {
+        final ActivityStack stack = createTaskStackOnDisplay(mDisplayContent);
+        final Task task = createTaskInStack(stack, 0 /* userId */);
+        final ActivityStack stack2 = createTaskStackOnDisplay(mDisplayContent);
+        final Task task2 = createTaskInStack(stack2, 0 /* userId */);
+        final ActivityStack stack3 = createTaskStackOnDisplay(mDisplayContent);
+        final Task task3 = createTaskInStack(stack3, 0 /* userId */);
+        final ITaskOrganizer organizer = registerMockOrganizer(WINDOWING_MODE_MULTI_WINDOW);
+
+        // First organizer is registered, verify a task appears when changing windowing mode
+        stack.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+        verify(organizer, times(1)).taskAppeared(any());
+        assertTrue(stack.isControlledByTaskOrganizer());
+
+        // Now we replace the registration and1 verify the new organizer receives tasks
+        // newly entering the windowing mode.
+        final ITaskOrganizer organizer2 = registerMockOrganizer(WINDOWING_MODE_MULTI_WINDOW);
+        stack2.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+        verify(organizer2).taskAppeared(any());
+        assertTrue(stack2.isControlledByTaskOrganizer());
+
+        // Now we unregister the second one, the first one should automatically be reregistered
+        // so we verify that it's now seeing changes.
+        mWm.mAtmService.mTaskOrganizerController.unregisterTaskOrganizer(organizer2);
+
+        stack3.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+        verify(organizer, times(2)).taskAppeared(any());
+        assertTrue(stack3.isControlledByTaskOrganizer());
+    }
+
+    @Test
     public void testRegisterTaskOrganizerStackWindowingModeChanges() throws RemoteException {
         final ITaskOrganizer organizer = registerMockOrganizer(WINDOWING_MODE_PINNED);
 
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 6723522..e3d031d 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -11290,14 +11290,6 @@
      */
     public static final int INDICATION_UPDATE_MODE_IGNORE_SCREEN_OFF        = 2;
 
-    /** @hide */
-    @IntDef(prefix = { "INDICATION_UPDATE_MODE_" }, value = {
-            INDICATION_UPDATE_MODE_NORMAL,
-            INDICATION_UPDATE_MODE_IGNORE_SCREEN_OFF
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface IndicationUpdateMode{}
-
     /**
      * The indication for signal strength update.
      * @hide
@@ -11328,51 +11320,6 @@
      */
     public static final int INDICATION_FILTER_PHYSICAL_CHANNEL_CONFIG       = 0x10;
 
-    /** @hide */
-    @IntDef(flag = true, prefix = { "INDICATION_FILTER_" }, value = {
-            INDICATION_FILTER_SIGNAL_STRENGTH,
-            INDICATION_FILTER_FULL_NETWORK_STATE,
-            INDICATION_FILTER_DATA_CALL_DORMANCY_CHANGED,
-            INDICATION_FILTER_LINK_CAPACITY_ESTIMATE,
-            INDICATION_FILTER_PHYSICAL_CHANNEL_CONFIG
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface IndicationFilters{}
-
-    /**
-     * Sets radio indication update mode. This can be used to control the behavior of indication
-     * update from modem to Android frameworks. For example, by default several indication updates
-     * are turned off when screen is off, but in some special cases (e.g. carkit is connected but
-     * screen is off) we want to turn on those indications even when the screen is off.
-     *
-     * <p>Requires Permission:
-     *   {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
-     *
-     * @param filters Indication filters. Should be a bitmask of INDICATION_FILTER_XXX.
-     * @see #INDICATION_FILTER_SIGNAL_STRENGTH
-     * @see #INDICATION_FILTER_FULL_NETWORK_STATE
-     * @see #INDICATION_FILTER_DATA_CALL_DORMANCY_CHANGED
-     * @param updateMode The voice activation state
-     * @see #INDICATION_UPDATE_MODE_NORMAL
-     * @see #INDICATION_UPDATE_MODE_IGNORE_SCREEN_OFF
-     * @hide
-     */
-    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
-    public void setRadioIndicationUpdateMode(@IndicationFilters int filters,
-                                             @IndicationUpdateMode int updateMode) {
-        try {
-            ITelephony telephony = getITelephony();
-            if (telephony != null) {
-                telephony.setRadioIndicationUpdateMode(getSubId(), filters, updateMode);
-            }
-        } catch (RemoteException ex) {
-            // This could happen if binder process crashes.
-            if (!isSystemProcess()) {
-                ex.rethrowAsRuntimeException();
-            }
-        }
-    }
-
     /**
      * A test API to override carrier information including mccmnc, imsi, iccid, gid1, gid2,
      * plmn and spn. This would be handy for, eg, forcing a particular carrier id, carrier's config
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index beb3c8c..168c8b6 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -1827,14 +1827,6 @@
     boolean switchSlots(in int[] physicalSlots);
 
     /**
-     * Sets radio indication update mode. This can be used to control the behavior of indication
-     * update from modem to Android frameworks. For example, by default several indication updates
-     * are turned off when screen is off, but in some special cases (e.g. carkit is connected but
-     * screen is off) we want to turn on those indications even when the screen is off.
-     */
-    void setRadioIndicationUpdateMode(int subId, int filters, int mode);
-
-    /**
      * Returns whether mobile data roaming is enabled on the subscription with id {@code subId}.
      *
      * @param subId the subscription id
diff --git a/tools/streaming_proto/cpp/main.cpp b/tools/streaming_proto/cpp/main.cpp
index d6b9d81..fe9a438 100644
--- a/tools/streaming_proto/cpp/main.cpp
+++ b/tools/streaming_proto/cpp/main.cpp
@@ -33,13 +33,13 @@
     if (GENERATE_MAPPING) {
         string name = make_constant_name(enu.name());
         string prefix = name + "_";
-        text << indent << "const int _ENUM_" << name << "_COUNT = " << N << ";" << endl;
-        text << indent << "const char* _ENUM_" << name << "_NAMES[" << N << "] = {" << endl;
+        text << indent << "static const int _ENUM_" << name << "_COUNT = " << N << ";" << endl;
+        text << indent << "static const char* _ENUM_" << name << "_NAMES[" << N << "] = {" << endl;
         for (int i=0; i<N; i++) {
             text << indent << INDENT << "\"" << stripPrefix(enu.value(i).name(), prefix) << "\"," << endl;
         }
         text << indent << "};" << endl;
-        text << indent << "const int _ENUM_" << name << "_VALUES[" << N << "] = {" << endl;
+        text << indent << "static const int _ENUM_" << name << "_VALUES[" << N << "] = {" << endl;
         for (int i=0; i<N; i++) {
             text << indent << INDENT << make_constant_name(enu.value(i).name()) << "," << endl;
         }
@@ -102,13 +102,13 @@
 
     if (GENERATE_MAPPING) {
         N = message.field_size();
-        text << indented << "const int _FIELD_COUNT = " << N << ";" << endl;
-        text << indented << "const char* _FIELD_NAMES[" << N << "] = {" << endl;
+        text << indented << "static const int _FIELD_COUNT = " << N << ";" << endl;
+        text << indented << "static const char* _FIELD_NAMES[" << N << "] = {" << endl;
         for (int i=0; i<N; i++) {
             text << indented << INDENT << "\"" << message.field(i).name() << "\"," << endl;
         }
         text << indented << "};" << endl;
-        text << indented << "const uint64_t _FIELD_IDS[" << N << "] = {" << endl;
+        text << indented << "static const uint64_t _FIELD_IDS[" << N << "] = {" << endl;
         for (int i=0; i<N; i++) {
             text << indented << INDENT << make_constant_name(message.field(i).name()) << "," << endl;
         }
@@ -152,7 +152,7 @@
         write_message(text, file_descriptor.message_type(i), "");
     }
 
-    for (vector<string>::iterator it = namespaces.begin(); it != namespaces.end(); it++) {
+    for (vector<string>::reverse_iterator it = namespaces.rbegin(); it != namespaces.rend(); it++) {
         text << "} // " << *it << endl;
     }
 
diff --git a/wifi/java/android/net/wifi/wificond/NativeScanResult.java b/wifi/java/android/net/wifi/wificond/NativeScanResult.java
index 85251e8b..7cc617d 100644
--- a/wifi/java/android/net/wifi/wificond/NativeScanResult.java
+++ b/wifi/java/android/net/wifi/wificond/NativeScanResult.java
@@ -24,7 +24,6 @@
 import com.android.internal.annotations.VisibleForTesting;
 
 import java.util.ArrayList;
-import java.util.BitSet;
 import java.util.List;
 
 /**
@@ -34,8 +33,6 @@
  */
 @SystemApi
 public final class NativeScanResult implements Parcelable {
-    private static final int CAPABILITY_SIZE = 16;
-
     /** @hide */
     @VisibleForTesting
     public byte[] ssid;
@@ -56,7 +53,7 @@
     public long tsf;
     /** @hide */
     @VisibleForTesting
-    public BitSet capability;
+    public int capability;
     /** @hide */
     @VisibleForTesting
     public boolean associated;
@@ -134,7 +131,7 @@
      *  Returns the capabilities of the AP repseresented by this scan result as advertised in the
      *  received probe response or beacon.
      *
-     *  This is a bit mask describing the capabilities of a BSS. See IEEE Std 802.11: 8.4.1.4:
+     *  This is a bit mask describing the capabilities of a BSS. See IEEE Std 802.11: 9.4.1.4:
      *    Bit 0 - ESS
      *    Bit 1 - IBSS
      *    Bit 2 - CF Pollable
@@ -143,7 +140,7 @@
      *    Bit 5 - Short Preamble
      *    Bit 6 - PBCC
      *    Bit 7 - Channel Agility
-     *    Bit 8 - Spectrum Mgmt
+     *    Bit 8 - Spectrum Management
      *    Bit 9 - QoS
      *    Bit 10 - Short Slot Time
      *    Bit 11 - APSD
@@ -154,7 +151,7 @@
      *
      * @return a bit mask of capabilities.
      */
-    @NonNull public BitSet getCapabilities() {
+    @NonNull public int getCapabilities() {
         return capability;
     }
 
@@ -188,13 +185,7 @@
         out.writeInt(frequency);
         out.writeInt(signalMbm);
         out.writeLong(tsf);
-        int capabilityInt = 0;
-        for (int i = 0; i < CAPABILITY_SIZE; i++) {
-            if (capability.get(i)) {
-                capabilityInt |= 1 << i;
-            }
-        }
-        out.writeInt(capabilityInt);
+        out.writeInt(capability);
         out.writeInt(associated ? 1 : 0);
         out.writeTypedList(radioChainInfos);
     }
@@ -220,13 +211,7 @@
             result.frequency = in.readInt();
             result.signalMbm = in.readInt();
             result.tsf = in.readLong();
-            int capabilityInt = in.readInt();
-            result.capability = new BitSet(CAPABILITY_SIZE);
-            for (int i = 0; i < CAPABILITY_SIZE; i++) {
-                if ((capabilityInt & (1 << i)) != 0) {
-                    result.capability.set(i);
-                }
-            }
+            result.capability = in.readInt();
             result.associated = (in.readInt() != 0);
             result.radioChainInfos = new ArrayList<>();
             in.readTypedList(result.radioChainInfos, RadioChainInfo.CREATOR);
diff --git a/wifi/java/android/net/wifi/wificond/PnoSettings.java b/wifi/java/android/net/wifi/wificond/PnoSettings.java
index 57c9ca5..533d37d 100644
--- a/wifi/java/android/net/wifi/wificond/PnoSettings.java
+++ b/wifi/java/android/net/wifi/wificond/PnoSettings.java
@@ -16,6 +16,7 @@
 
 package android.net.wifi.wificond;
 
+import android.annotation.DurationMillisLong;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.os.Parcel;
@@ -33,7 +34,7 @@
  */
 @SystemApi
 public final class PnoSettings implements Parcelable {
-    private int mIntervalMs;
+    private long mIntervalMs;
     private int mMin2gRssi;
     private int mMin5gRssi;
     private int mMin6gRssi;
@@ -47,17 +48,17 @@
      *
      * @return An interval in milliseconds.
      */
-    public int getIntervalMillis() {
+    public @DurationMillisLong long getIntervalMillis() {
         return mIntervalMs;
     }
 
     /**
      * Set the requested PNO scan interval in milliseconds.
      *
-     * @param intervalMs An interval in milliseconds.
+     * @param intervalMillis An interval in milliseconds.
      */
-    public void setIntervalMillis(int intervalMs) {
-        this.mIntervalMs = intervalMs;
+    public void setIntervalMillis(@DurationMillisLong long intervalMillis) {
+        this.mIntervalMs = intervalMillis;
     }
 
     /**
@@ -176,7 +177,7 @@
      **/
     @Override
     public void writeToParcel(@NonNull Parcel out, int flags) {
-        out.writeInt(mIntervalMs);
+        out.writeLong(mIntervalMs);
         out.writeInt(mMin2gRssi);
         out.writeInt(mMin5gRssi);
         out.writeInt(mMin6gRssi);
@@ -189,7 +190,7 @@
         @Override
         public PnoSettings createFromParcel(Parcel in) {
             PnoSettings result = new PnoSettings();
-            result.mIntervalMs = in.readInt();
+            result.mIntervalMs = in.readLong();
             result.mMin2gRssi = in.readInt();
             result.mMin5gRssi = in.readInt();
             result.mMin6gRssi = in.readInt();
diff --git a/wifi/java/android/net/wifi/wificond/WifiCondManager.java b/wifi/java/android/net/wifi/wificond/WifiCondManager.java
index 43aa1b6..7a31a5a 100644
--- a/wifi/java/android/net/wifi/wificond/WifiCondManager.java
+++ b/wifi/java/android/net/wifi/wificond/WifiCondManager.java
@@ -496,22 +496,17 @@
     }
 
     /**
-     * Initializes WifiCondManager & registers a death notification for the WifiCondManager which
-     * acts as a proxy for the wificond daemon (i.e. the death listener will be called when and if
-     * the wificond daemon dies).
-     *
-     * Note: This method clears any existing state in wificond daemon.
+     * Register a death notification for the WifiCondManager which acts as a proxy for the
+     * wificond daemon (i.e. the death listener will be called when and if the wificond daemon
+     * dies).
      *
      * @param deathEventHandler A {@link Runnable} to be called whenever the wificond daemon dies.
-     * @return Returns true on success.
      */
-    public boolean initialize(@NonNull Runnable deathEventHandler) {
+    public void setOnServiceDeadCallback(@NonNull Runnable deathEventHandler) {
         if (mDeathEventHandler != null) {
             Log.e(TAG, "Death handler already present");
         }
         mDeathEventHandler = deathEventHandler;
-        tearDownInterfaces();
-        return true;
     }
 
     /**
@@ -603,11 +598,12 @@
     }
 
     /**
-     * Tear down a specific client (STA) interface, initially configured using
+     * Tear down a specific client (STA) interface configured using
      * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)}.
      *
      * @param ifaceName Name of the interface to tear down.
-     * @return Returns true on success.
+     * @return Returns true on success, false on failure (e.g. when called before an interface was
+     * set up).
      */
     public boolean tearDownClientInterface(@NonNull String ifaceName) {
         if (getClientInterface(ifaceName) == null) {
@@ -681,11 +677,12 @@
     }
 
     /**
-     * Tear down a Soft AP interface initially configured using
+     * Tear down a Soft AP interface configured using
      * {@link #setupInterfaceForSoftApMode(String)}.
      *
      * @param ifaceName Name of the interface to tear down.
-     * @return Returns true on success.
+     * @return Returns true on success, false on failure (e.g. when called before an interface was
+     * set up).
      */
     public boolean tearDownSoftApInterface(@NonNull String ifaceName) {
         if (getApInterface(ifaceName) == null) {
@@ -750,9 +747,13 @@
     /**
      * Request signal polling.
      *
-     * @param ifaceName Name of the interface on which to poll.
+     * @param ifaceName Name of the interface on which to poll. The interface must have been
+     *                  already set up using
+     *{@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)}
+     *                  or {@link #setupInterfaceForSoftApMode(String)}.
+     *
      * @return A {@link SignalPollResult} object containing interface statistics, or a null on
-     * error.
+     * error (e.g. the interface hasn't been set up yet).
      */
     @Nullable public SignalPollResult signalPoll(@NonNull String ifaceName) {
         IClientInterface iface = getClientInterface(ifaceName);
@@ -776,10 +777,14 @@
     }
 
     /**
-     * Get current transmit (Tx) packet counters of the specified interface.
+     * Get current transmit (Tx) packet counters of the specified interface. The interface must
+     * have been already set up using
+     * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)}
+     * or {@link #setupInterfaceForSoftApMode(String)}.
      *
      * @param ifaceName Name of the interface.
-     * @return {@link TxPacketCounters} of the current interface or null on error.
+     * @return {@link TxPacketCounters} of the current interface or null on error (e.g. when
+     * called before the interface has been set up).
      */
     @Nullable public TxPacketCounters getTxPacketCounters(@NonNull String ifaceName) {
         IClientInterface iface = getClientInterface(ifaceName);
@@ -813,10 +818,15 @@
      * be done using {@link #startScan(String, int, Set, List)} or
      * {@link #startPnoScan(String, PnoSettings, Executor, PnoScanRequestCallback)}.
      *
+     * Note: The interface must have been already set up using
+     * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)}
+     * or {@link #setupInterfaceForSoftApMode(String)}.
+     *
      * @param ifaceName Name of the interface.
      * @param scanType The type of scan result to be returned, can be
      * {@link #SCAN_TYPE_SINGLE_SCAN} or {@link #SCAN_TYPE_PNO_SCAN}.
-     * @return Returns an array of {@link NativeScanResult} or an empty array on failure.
+     * @return Returns an array of {@link NativeScanResult} or an empty array on failure (e.g. when
+     * called before the interface has been set up).
      */
     @NonNull public List<NativeScanResult> getScanResults(@NonNull String ifaceName,
             @ScanResultType int scanType) {
@@ -869,13 +879,19 @@
      * The latest scans can be obtained using {@link #getScanResults(String, int)} and using a
      * {@link #SCAN_TYPE_SINGLE_SCAN} for the {@code scanType}.
      *
+     * Note: The interface must have been already set up using
+     * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)}
+     * or {@link #setupInterfaceForSoftApMode(String)}.
+     *
      * @param ifaceName Name of the interface on which to initiate the scan.
      * @param scanType Type of scan to perform, can be any of
      * {@link WifiScanner#SCAN_TYPE_HIGH_ACCURACY}, {@link WifiScanner#SCAN_TYPE_LOW_POWER}, or
      * {@link WifiScanner#SCAN_TYPE_LOW_LATENCY}.
      * @param freqs list of frequencies to scan for, if null scan all supported channels.
-     * @param hiddenNetworkSSIDs List of hidden networks to be scanned for.
-     * @return Returns true on success.
+     * @param hiddenNetworkSSIDs List of hidden networks to be scanned for, a null indicates that
+     *                           no hidden frequencies will be scanned for.
+     * @return Returns true on success, false on failure (e.g. when called before the interface
+     * has been set up).
      */
     public boolean startScan(@NonNull String ifaceName, @WifiAnnotations.ScanType int scanType,
             @Nullable Set<Integer> freqs, @Nullable List<byte[]> hiddenNetworkSSIDs) {
@@ -931,11 +947,16 @@
      * The latest PNO scans can be obtained using {@link #getScanResults(String, int)} with the
      * {@code scanType} set to {@link #SCAN_TYPE_PNO_SCAN}.
      *
+     * Note: The interface must have been already set up using
+     * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)}
+     * or {@link #setupInterfaceForSoftApMode(String)}.
+     *
      * @param ifaceName Name of the interface on which to request a PNO.
      * @param pnoSettings PNO scan configuration.
      * @param executor The Executor on which to execute the callback.
      * @param callback Callback for the results of the offload request.
-     * @return true on success.
+     * @return true on success, false on failure (e.g. when called before the interface has been set
+     * up).
      */
     public boolean startPnoScan(@NonNull String ifaceName, @NonNull PnoSettings pnoSettings,
             @NonNull @CallbackExecutor Executor executor,
@@ -969,8 +990,13 @@
      * Stop PNO scan configured with
      * {@link #startPnoScan(String, PnoSettings, Executor, PnoScanRequestCallback)}.
      *
+     * Note: The interface must have been already set up using
+     * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)}
+     * or {@link #setupInterfaceForSoftApMode(String)}.
+     *
      * @param ifaceName Name of the interface on which the PNO scan was configured.
-     * @return true on success.
+     * @return true on success, false on failure (e.g. when called before the interface has been
+     * set up).
      */
     public boolean stopPnoScan(@NonNull String ifaceName) {
         IWifiScannerImpl scannerImpl = getScannerImpl(ifaceName);
@@ -987,7 +1013,13 @@
     }
 
     /**
-     * Abort ongoing single scan started with {@link #startScan(String, int, Set, List)}.
+     * Abort ongoing single scan started with {@link #startScan(String, int, Set, List)}. No failure
+     * callback, e.g. {@link ScanEventCallback#onScanFailed()}, is triggered by this operation.
+     *
+     * Note: The interface must have been already set up using
+     * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)}
+     * or {@link #setupInterfaceForSoftApMode(String)}. If the interface has not been set up then
+     * this method has no impact.
      *
      * @param ifaceName Name of the interface on which the scan was started.
      */
@@ -1055,7 +1087,14 @@
     }
 
     /**
-     * Get the device phy capabilities for a given interface
+     * Get the device phy capabilities for a given interface.
+     *
+     * Note: The interface must have been already set up using
+     * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)}
+     * or {@link #setupInterfaceForSoftApMode(String)}.
+     *
+     * @return DeviceWiphyCapabilities or null on error (e.g. when called on an interface which has
+     * not been set up).
      */
     @Nullable public DeviceWiphyCapabilities getDeviceWiphyCapabilities(@NonNull String ifaceName) {
         if (mWificond == null) {
@@ -1071,13 +1110,19 @@
     }
 
     /**
-     * Register the provided callback handler for SoftAp events. Note that the Soft AP itself is
-     * configured using {@link #setupInterfaceForSoftApMode(String)}.
+     * Register the provided callback handler for SoftAp events. The interface must first be created
+     * using {@link #setupInterfaceForSoftApMode(String)}. The callback registration is valid until
+     * the interface is deleted using {@link #tearDownSoftApInterface(String)} (no deregistration
+     * method is provided).
+     * <p>
+     * Note that only one callback can be registered at a time - any registration overrides previous
+     * registrations.
      *
      * @param ifaceName Name of the interface on which to register the callback.
      * @param executor The Executor on which to execute the callbacks.
      * @param callback Callback for AP events.
-     * @return true on success, false otherwise.
+     * @return true on success, false on failure (e.g. when called on an interface which has not
+     * been set up).
      */
     public boolean registerApCallback(@NonNull String ifaceName,
             @NonNull @CallbackExecutor Executor executor,
@@ -1113,6 +1158,10 @@
      * Send a management frame on the specified interface at the specified rate. Useful for probing
      * the link with arbitrary frames.
      *
+     * Note: The interface must have been already set up using
+     * {@link #setupInterfaceForClientMode(String, Executor, ScanEventCallback, ScanEventCallback)}
+     * or {@link #setupInterfaceForSoftApMode(String)}.
+     *
      * @param ifaceName The interface on which to send the frame.
      * @param frame The raw byte array of the management frame to tramit.
      * @param mcs The MCS (modulation and coding scheme), i.e. rate, at which to transmit the
diff --git a/wifi/tests/src/android/net/wifi/wificond/NativeScanResultTest.java b/wifi/tests/src/android/net/wifi/wificond/NativeScanResultTest.java
index 06f12f7..0df170f 100644
--- a/wifi/tests/src/android/net/wifi/wificond/NativeScanResultTest.java
+++ b/wifi/tests/src/android/net/wifi/wificond/NativeScanResultTest.java
@@ -28,7 +28,6 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.BitSet;
 
 /**
  * Unit tests for {@link android.net.wifi.wificond.NativeScanResult}.
@@ -46,7 +45,7 @@
     private static final int TEST_FREQUENCY = 2456;
     private static final int TEST_SIGNAL_MBM = -45;
     private static final long TEST_TSF = 34455441;
-    private static final BitSet TEST_CAPABILITY = new BitSet(16) {{ set(2); set(5); }};
+    private static final int TEST_CAPABILITY = (0x1 << 2) | (0x1 << 5);
     private static final boolean TEST_ASSOCIATED = true;
     private static final int[] RADIO_CHAIN_IDS = { 0, 1 };
     private static final int[] RADIO_CHAIN_LEVELS = { -56, -65 };
diff --git a/wifi/tests/src/android/net/wifi/wificond/WifiCondManagerTest.java b/wifi/tests/src/android/net/wifi/wificond/WifiCondManagerTest.java
index 5ba02a7..32105be 100644
--- a/wifi/tests/src/android/net/wifi/wificond/WifiCondManagerTest.java
+++ b/wifi/tests/src/android/net/wifi/wificond/WifiCondManagerTest.java
@@ -720,8 +720,7 @@
     @Test
     public void testRegisterDeathHandler() throws Exception {
         Runnable deathHandler = mock(Runnable.class);
-        assertTrue(mWificondControl.initialize(deathHandler));
-        verify(mWificond).tearDownInterfaces();
+        mWificondControl.setOnServiceDeadCallback(deathHandler);
         mWificondControl.binderDied();
         mLooper.dispatchAll();
         verify(deathHandler).run();
@@ -734,7 +733,7 @@
     @Test
     public void testDeathHandling() throws Exception {
         Runnable deathHandler = mock(Runnable.class);
-        assertTrue(mWificondControl.initialize(deathHandler));
+        mWificondControl.setOnServiceDeadCallback(deathHandler);
 
         testSetupInterfaceForClientMode();