Merge "Use native boot namespace for freezer"
diff --git a/apex/statsd/aidl/android/os/IStatsd.aidl b/apex/statsd/aidl/android/os/IStatsd.aidl
index a2564212..253b2c1 100644
--- a/apex/statsd/aidl/android/os/IStatsd.aidl
+++ b/apex/statsd/aidl/android/os/IStatsd.aidl
@@ -222,12 +222,6 @@
     const int FLAG_REQUIRE_LOW_LATENCY_MONITOR = 0x04;
 
     /**
-     * Logs an event for binary push for module updates.
-     */
-     oneway void sendBinaryPushStateChangedAtom(in String trainName, in long trainVersionCode,
-         in int options, in int state, in long[] experimentId);
-
-    /**
      * Logs an event for watchdog rollbacks.
      */
      oneway void sendWatchdogRollbackOccurredAtom(in int rollbackType, in String packageName,
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/tests/libstatspull/jni/stats_pull_helper.cpp b/apex/statsd/tests/libstatspull/jni/stats_pull_helper.cpp
index e4ab823..22daa8e 100644
--- a/apex/statsd/tests/libstatspull/jni/stats_pull_helper.cpp
+++ b/apex/statsd/tests/libstatspull/jni/stats_pull_helper.cpp
@@ -46,15 +46,15 @@
     }
 }
 
-static status_pull_atom_return_t pullAtomCallback(int32_t atomTag, pulled_stats_event_list* data,
-                                                  void* /*cookie*/) {
+static AStatsManager_PullAtomCallbackReturn pullAtomCallback(int32_t atomTag, AStatsEventList* data,
+                                                             void* /*cookie*/) {
     sNumPulls++;
     sleep_for(std::chrono::milliseconds(sLatencyMillis));
     for (int i = 0; i < sAtomsPerPull; i++) {
-        stats_event* event = add_stats_event_to_pull_data(data);
-        stats_event_set_atom_id(event, atomTag);
-        stats_event_write_int64(event, (int64_t) sNumPulls);
-        stats_event_build(event);
+        AStatsEvent* event = AStatsEventList_addStatsEvent(data);
+        AStatsEvent_setAtomId(event, atomTag);
+        AStatsEvent_writeInt64(event, (int64_t) sNumPulls);
+        AStatsEvent_build(event);
     }
     return sPullReturnVal;
 }
@@ -71,11 +71,12 @@
     sLatencyMillis = latencyMillis;
     sAtomsPerPull = atomsPerPull;
     sNumPulls = 0;
-    pull_atom_metadata metadata = {.cool_down_ns = coolDownNs,
-                                   .timeout_ns = timeoutNs,
-                                   .additive_fields = nullptr,
-                                   .additive_fields_size = 0};
-    register_stats_pull_atom_callback(sAtomTag, &pullAtomCallback, &metadata, nullptr);
+    AStatsManager_PullAtomMetadata* metadata = AStatsManager_PullAtomMetadata_obtain();
+    AStatsManager_PullAtomMetadata_setCoolDownNs(metadata, coolDownNs);
+    AStatsManager_PullAtomMetadata_setTimeoutNs(metadata, timeoutNs);
+
+    AStatsManager_registerPullAtomCallback(sAtomTag, &pullAtomCallback, metadata, nullptr);
+    AStatsManager_PullAtomMetadata_release(metadata);
 }
 
 extern "C"
@@ -83,6 +84,6 @@
 Java_com_android_internal_os_statsd_libstats_LibStatsPullTests_unregisterStatsPuller(
         JNIEnv* /*env*/, jobject /* this */, jint /*atomTag*/)
 {
-    unregister_stats_pull_atom_callback(sAtomTag);
+    AStatsManager_unregisterPullAtomCallback(sAtomTag);
 }
-} // namespace
\ No newline at end of file
+} // namespace
diff --git a/apex/statsd/tests/libstatspull/src/com/android/internal/os/statsd/libstats/LibStatsPullTests.java b/apex/statsd/tests/libstatspull/src/com/android/internal/os/statsd/libstats/LibStatsPullTests.java
index dbd636d..e119b4c 100644
--- a/apex/statsd/tests/libstatspull/src/com/android/internal/os/statsd/libstats/LibStatsPullTests.java
+++ b/apex/statsd/tests/libstatspull/src/com/android/internal/os/statsd/libstats/LibStatsPullTests.java
@@ -71,7 +71,6 @@
      */
     @Before
     public void setup() {
-//        Debug.waitForDebugger();
         mContext = InstrumentationRegistry.getTargetContext();
         assertThat(InstrumentationRegistry.getInstrumentation()).isNotNull();
         sPullReturnValue = StatsManager.PULL_SUCCESS;
diff --git a/api/current.txt b/api/current.txt
index 87a5cd7..006ac0b 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -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 1128556..253d6b6 100755
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -2201,6 +2201,8 @@
     field public static final String FEATURE_TELEPHONY_CARRIERLOCK = "android.hardware.telephony.carrierlock";
     field public static final int FLAGS_PERMISSION_RESERVED_PERMISSIONCONTROLLER = -268435456; // 0xf0000000
     field public static final int FLAG_PERMISSION_APPLY_RESTRICTION = 16384; // 0x4000
+    field public static final int FLAG_PERMISSION_DONT_AUTO_REVOKE = 131072; // 0x20000
+    field public static final int FLAG_PERMISSION_DONT_AUTO_REVOKE_USER_SET = 262144; // 0x40000
     field public static final int FLAG_PERMISSION_GRANTED_BY_DEFAULT = 32; // 0x20
     field public static final int FLAG_PERMISSION_GRANTED_BY_ROLE = 32768; // 0x8000
     field public static final int FLAG_PERMISSION_ONE_TIME = 65536; // 0x10000
@@ -2284,7 +2286,7 @@
     method public void onPermissionsChanged(int);
   }
 
-  @IntDef(prefix={"FLAG_PERMISSION_"}, value={android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET, android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE, android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT, android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED, android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION, android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE, android.content.pm.PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, android.content.pm.PackageManager.FLAG_PERMISSION_ONE_TIME}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface PackageManager.PermissionFlags {
+  @IntDef(prefix={"FLAG_PERMISSION_"}, value={android.content.pm.PackageManager.FLAG_PERMISSION_USER_SET, android.content.pm.PackageManager.FLAG_PERMISSION_USER_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_POLICY_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE, android.content.pm.PackageManager.FLAG_PERMISSION_SYSTEM_FIXED, android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT, android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED, android.content.pm.PackageManager.FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_UPGRADE_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_SYSTEM_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_RESTRICTION_INSTALLER_EXEMPT, android.content.pm.PackageManager.FLAG_PERMISSION_APPLY_RESTRICTION, android.content.pm.PackageManager.FLAG_PERMISSION_GRANTED_BY_ROLE, android.content.pm.PackageManager.FLAG_PERMISSION_REVOKED_COMPAT, android.content.pm.PackageManager.FLAG_PERMISSION_ONE_TIME, android.content.pm.PackageManager.FLAG_PERMISSION_DONT_AUTO_REVOKE}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface PackageManager.PermissionFlags {
   }
 
   public class PermissionGroupInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable {
@@ -4317,7 +4319,7 @@
   }
 
   public final class AudioRecordingConfiguration implements android.os.Parcelable {
-    method public int getClientUid();
+    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getClientUid();
   }
 
   public class HwAudioSource {
@@ -7517,9 +7519,6 @@
     method @Deprecated public boolean isNoInternetAccessExpected();
     method @Deprecated public void setIpConfiguration(@Nullable android.net.IpConfiguration);
     method @Deprecated public void setNetworkSelectionStatus(@NonNull android.net.wifi.WifiConfiguration.NetworkSelectionStatus);
-    field @Deprecated public static final int AP_BAND_2GHZ = 0; // 0x0
-    field @Deprecated public static final int AP_BAND_5GHZ = 1; // 0x1
-    field @Deprecated public static final int AP_BAND_ANY = -1; // 0xffffffff
     field @Deprecated public static final int INVALID_NETWORK_ID = -1; // 0xffffffff
     field @Deprecated public static final int METERED_OVERRIDE_METERED = 1; // 0x1
     field @Deprecated public static final int METERED_OVERRIDE_NONE = 0; // 0x0
@@ -7529,7 +7528,6 @@
     field @Deprecated public static final int RECENT_FAILURE_AP_UNABLE_TO_HANDLE_NEW_STA = 17; // 0x11
     field @Deprecated public static final int RECENT_FAILURE_NONE = 0; // 0x0
     field @Deprecated public boolean allowAutojoin;
-    field @Deprecated public int apBand;
     field @Deprecated public int carrierId;
     field @Deprecated public String creatorName;
     field @Deprecated public int creatorUid;
diff --git a/api/system-lint-baseline.txt b/api/system-lint-baseline.txt
index 2f1889c..a57e178 100644
--- a/api/system-lint-baseline.txt
+++ b/api/system-lint-baseline.txt
@@ -200,8 +200,6 @@
     
 MutableBareField: android.net.wifi.WifiConfiguration#allowAutojoin:
     
-MutableBareField: android.net.wifi.WifiConfiguration#apBand:
-    
 MutableBareField: android.net.wifi.WifiConfiguration#carrierId:
     
 MutableBareField: android.net.wifi.WifiConfiguration#fromWifiNetworkSpecifier:
diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp
index 2237bf2..e6cc1da 100644
--- a/cmds/statsd/Android.bp
+++ b/cmds/statsd/Android.bp
@@ -64,6 +64,7 @@
         "src/config/ConfigKey.cpp",
         "src/config/ConfigListener.cpp",
         "src/config/ConfigManager.cpp",
+        "src/experiment_ids.proto",
         "src/external/GpuStatsPuller.cpp",
         "src/external/Perfetto.cpp",
         "src/external/PullResultReceiver.cpp",
diff --git a/cmds/statsd/benchmark/log_event_benchmark.cpp b/cmds/statsd/benchmark/log_event_benchmark.cpp
index 30dfe32..8b68743 100644
--- a/cmds/statsd/benchmark/log_event_benchmark.cpp
+++ b/cmds/statsd/benchmark/log_event_benchmark.cpp
@@ -23,14 +23,14 @@
 namespace statsd {
 
 static size_t createAndParseStatsEvent(uint8_t* msg) {
-    struct stats_event* event = stats_event_obtain();
-    stats_event_set_atom_id(event, 100);
-    stats_event_write_int32(event, 2);
-    stats_event_write_float(event, 2.0);
-    stats_event_build(event);
+    AStatsEvent* event = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(event, 100);
+    AStatsEvent_writeInt32(event, 2);
+    AStatsEvent_writeFloat(event, 2.0);
+    AStatsEvent_build(event);
 
     size_t size;
-    uint8_t* buf = stats_event_get_buffer(event, &size);
+    uint8_t* buf = AStatsEvent_getBuffer(event, &size);
     memcpy(msg, buf, size);
     return size;
 }
diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp
index 879b3c3..bde15a5 100644
--- a/cmds/statsd/src/StatsLogProcessor.cpp
+++ b/cmds/statsd/src/StatsLogProcessor.cpp
@@ -20,13 +20,17 @@
 #include "StatsLogProcessor.h"
 
 #include <android-base/file.h>
+#include <cutils/multiuser.h>
 #include <frameworks/base/cmds/statsd/src/active_config_list.pb.h>
+#include <frameworks/base/cmds/statsd/src/experiment_ids.pb.h>
 
 #include "android-base/stringprintf.h"
 #include "atoms_info.h"
 #include "external/StatsPullerManager.h"
 #include "guardrail/StatsdStats.h"
+#include "logd/LogEvent.h"
 #include "metrics/CountMetricProducer.h"
+#include "StatsService.h"
 #include "state/StateManager.h"
 #include "stats_log_util.h"
 #include "stats_util.h"
@@ -68,6 +72,10 @@
 // for ActiveConfigList
 const int FIELD_ID_ACTIVE_CONFIG_LIST_CONFIG = 1;
 
+// for permissions checks
+constexpr const char* kPermissionDump = "android.permission.DUMP";
+constexpr const char* kPermissionUsage = "android.permission.PACKAGE_USAGE_STATS";
+
 #define NS_PER_HOUR 3600 * NS_PER_SEC
 
 #define STATS_ACTIVE_METRIC_DIR "/data/misc/stats-active-metric"
@@ -181,6 +189,115 @@
     }
 }
 
+void StatsLogProcessor::onBinaryPushStateChangedEventLocked(LogEvent* event) {
+    pid_t pid = event->GetPid();
+    uid_t uid = event->GetUid();
+    if (!checkPermissionForIds(kPermissionDump, pid, uid) ||
+        !checkPermissionForIds(kPermissionUsage, pid, uid)) {
+        return;
+    }
+    status_t err = NO_ERROR, err2 = NO_ERROR, err3 = NO_ERROR, err4 = NO_ERROR;
+    string trainName = string(event->GetString(1 /*train name field id*/, &err));
+    int64_t trainVersionCode = event->GetLong(2 /*train version field id*/, &err2);
+    int32_t state = int32_t(event->GetLong(6 /*state field id*/, &err3));
+#ifdef NEW_ENCODING_SCHEME
+    std::vector<uint8_t> trainExperimentIdBytes =
+        event->GetStorage(7 /*experiment ids field id*/, &err4);
+#else
+    string trainExperimentIdString = event->GetString(7 /*experiment ids field id*/, &err4);
+#endif
+    if (err != NO_ERROR || err2 != NO_ERROR || err3 != NO_ERROR || err4 != NO_ERROR) {
+        ALOGE("Failed to parse fields in binary push state changed log event");
+        return;
+    }
+    ExperimentIds trainExperimentIds;
+#ifdef NEW_ENCODING_SCHEME
+    if (!trainExperimentIds.ParseFromArray(trainExperimentIdBytes.data(),
+                                           trainExperimentIdBytes.size())) {
+#else
+    if (!trainExperimentIds.ParseFromString(trainExperimentIdString)) {
+#endif
+        ALOGE("Failed to parse experimentids in binary push state changed.");
+        return;
+    }
+    vector<int64_t> experimentIdVector = {trainExperimentIds.experiment_id().begin(),
+                                          trainExperimentIds.experiment_id().end()};
+    // Update the train info on disk and get any data the logevent is missing.
+    getAndUpdateTrainInfoOnDisk(
+        state, &trainVersionCode, &trainName, &experimentIdVector);
+
+    std::vector<uint8_t> trainExperimentIdProto;
+    writeExperimentIdsToProto(experimentIdVector, &trainExperimentIdProto);
+    int32_t userId = multiuser_get_user_id(uid);
+
+    event->updateValue(1 /*train name field id*/, trainName, STRING);
+    event->updateValue(2 /*train version field id*/, trainVersionCode, LONG);
+#ifdef NEW_ENCODING_SCHEME
+    event->updateValue(7 /*experiment ids field id*/, trainExperimentIdProto, STORAGE);
+#else
+    event->updateValue(7 /*experiment ids field id*/, trainExperimentIdProto, STRING);
+#endif
+    event->updateValue(8 /*user id field id*/, userId, INT);
+}
+
+void StatsLogProcessor::getAndUpdateTrainInfoOnDisk(int32_t state,
+                                         int64_t* trainVersionCode,
+                                         string* trainName,
+                                         std::vector<int64_t>* experimentIds) {
+    bool readTrainInfoSuccess = false;
+    InstallTrainInfo trainInfoOnDisk;
+    readTrainInfoSuccess = StorageManager::readTrainInfo(trainInfoOnDisk);
+
+    bool resetExperimentIds = false;
+    if (readTrainInfoSuccess) {
+        // Keep the old train version if we received an empty version.
+        if (*trainVersionCode == -1) {
+            *trainVersionCode = trainInfoOnDisk.trainVersionCode;
+        } else if (*trainVersionCode != trainInfoOnDisk.trainVersionCode) {
+        // Reset experiment ids if we receive a new non-empty train version.
+            resetExperimentIds = true;
+        }
+
+        // Keep the old train name if we received an empty train name.
+        if (trainName->size() == 0) {
+            *trainName = trainInfoOnDisk.trainName;
+        } else if (*trainName != trainInfoOnDisk.trainName) {
+            // Reset experiment ids if we received a new valid train name.
+            resetExperimentIds = true;
+        }
+
+        // Reset if we received a different experiment id.
+        if (!experimentIds->empty() &&
+                (trainInfoOnDisk.experimentIds.empty() ||
+                 experimentIds->at(0) != trainInfoOnDisk.experimentIds[0])) {
+            resetExperimentIds = true;
+        }
+    }
+
+    // Find the right experiment IDs
+    if (!resetExperimentIds && readTrainInfoSuccess) {
+        *experimentIds = trainInfoOnDisk.experimentIds;
+    }
+
+    if (!experimentIds->empty()) {
+        int64_t firstId = experimentIds->at(0);
+        switch (state) {
+            case android::util::BINARY_PUSH_STATE_CHANGED__STATE__INSTALL_SUCCESS:
+                experimentIds->push_back(firstId + 1);
+                break;
+            case android::util::BINARY_PUSH_STATE_CHANGED__STATE__INSTALLER_ROLLBACK_INITIATED:
+                experimentIds->push_back(firstId + 2);
+                break;
+            case android::util::BINARY_PUSH_STATE_CHANGED__STATE__INSTALLER_ROLLBACK_SUCCESS:
+                experimentIds->push_back(firstId + 3);
+                break;
+        }
+    }
+
+    StorageManager::writeTrainInfo(*trainVersionCode, *trainName, state, *experimentIds);
+}
+
+
 void StatsLogProcessor::resetConfigs() {
     std::lock_guard<std::mutex> lock(mMetricsMutex);
     resetConfigsLocked(getElapsedRealtimeNs());
@@ -201,6 +318,12 @@
 void StatsLogProcessor::OnLogEvent(LogEvent* event, int64_t elapsedRealtimeNs) {
     std::lock_guard<std::mutex> lock(mMetricsMutex);
 
+    // Hard-coded logic to update train info on disk and fill in any information
+    // this log event may be missing.
+    if (event->GetTagId() == android::util::BINARY_PUSH_STATE_CHANGED) {
+      onBinaryPushStateChangedEventLocked(event);
+    }
+
 #ifdef VERY_VERBOSE_PRINTING
     if (mPrintAllLogs) {
         ALOGI("%s", event->ToString().c_str());
diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h
index c569bc1..c49f2e0 100644
--- a/cmds/statsd/src/StatsLogProcessor.h
+++ b/cmds/statsd/src/StatsLogProcessor.h
@@ -196,6 +196,14 @@
     // Handler over the isolated uid change event.
     void onIsolatedUidChangedEventLocked(const LogEvent& event);
 
+    // Handler over the binary push state changed event.
+    void onBinaryPushStateChangedEventLocked(LogEvent* event);
+
+    // Updates train info on disk based on binary push state changed info and
+    // write disk info into parameters.
+    void getAndUpdateTrainInfoOnDisk(int32_t state, int64_t* trainVersionCode,
+                                     string* trainName, std::vector<int64_t>* experimentIds);
+
     // Reset all configs.
     void resetConfigsLocked(const int64_t timestampNs);
     // Reset the specified configs.
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index 8a8c1e6..0256e36 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -70,25 +70,12 @@
     return binder::Status::fromExceptionCode(code, String8(msg.c_str()));
 }
 
-
 static bool checkPermission(const char* permission) {
-    sp<IStatsCompanionService> scs = getStatsCompanionService();
-    if (scs == nullptr) {
-        return false;
-    }
-
-    bool success;
     pid_t pid = IPCThreadState::self()->getCallingPid();
     uid_t uid = IPCThreadState::self()->getCallingUid();
-
-    binder::Status status = scs->checkPermission(String16(permission), pid, uid, &success);
-    if (!status.isOk()) {
-        return false;
-    }
-    return success;
+    return checkPermissionForIds(permission, pid, uid);
 }
 
-
 binder::Status checkUid(uid_t expectedUid) {
     uid_t uid = IPCThreadState::self()->getCallingUid();
     if (uid == expectedUid || uid == AID_ROOT) {
@@ -870,18 +857,8 @@
         dprintf(out, "Incorrect number of argument supplied\n");
         return UNKNOWN_ERROR;
     }
-    android::String16 trainName = android::String16(args[1].c_str());
+    string trainName = string(args[1].c_str());
     int64_t trainVersion = strtoll(args[2].c_str(), nullptr, 10);
-    int options = 0;
-    if (args[3] == "1") {
-        options = options | IStatsd::FLAG_REQUIRE_STAGING;
-    }
-    if (args[4] == "1") {
-        options = options | IStatsd::FLAG_ROLLBACK_ENABLED;
-    }
-    if (args[5] == "1") {
-        options = options | IStatsd::FLAG_REQUIRE_LOW_LATENCY_MONITOR;
-    }
     int32_t state = atoi(args[6].c_str());
     vector<int64_t> experimentIds;
     if (argCount == 8) {
@@ -892,7 +869,10 @@
         }
     }
     dprintf(out, "Logging BinaryPushStateChanged\n");
-    sendBinaryPushStateChangedAtom(trainName, trainVersion, options, state, experimentIds);
+    vector<uint8_t> experimentIdBytes;
+    writeExperimentIdsToProto(experimentIds, &experimentIdBytes);
+    LogEvent event(trainName, trainVersion, args[3], args[4], args[5], state, experimentIdBytes, 0);
+    mProcessor->OnLogEvent(&event);
     return NO_ERROR;
 }
 
@@ -1313,101 +1293,6 @@
     return Status::ok();
 }
 
-Status StatsService::sendBinaryPushStateChangedAtom(const android::String16& trainNameIn,
-                                                    const int64_t trainVersionCodeIn,
-                                                    const int options,
-                                                    const int32_t state,
-                                                    const std::vector<int64_t>& experimentIdsIn) {
-    // Note: We skip the usage stats op check here since we do not have a package name.
-    // This is ok since we are overloading the usage_stats permission.
-    // This method only sends data, it does not receive it.
-    pid_t pid = IPCThreadState::self()->getCallingPid();
-    uid_t uid = IPCThreadState::self()->getCallingUid();
-    // Root, system, and shell always have access
-    if (uid != AID_ROOT && uid != AID_SYSTEM && uid != AID_SHELL) {
-        // Caller must be granted these permissions
-        if (!checkPermission(kPermissionDump)) {
-            return exception(binder::Status::EX_SECURITY,
-                             StringPrintf("UID %d / PID %d lacks permission %s", uid, pid,
-                                          kPermissionDump));
-        }
-        if (!checkPermission(kPermissionUsage)) {
-            return exception(binder::Status::EX_SECURITY,
-                             StringPrintf("UID %d / PID %d lacks permission %s", uid, pid,
-                                          kPermissionUsage));
-        }
-    }
-
-    bool readTrainInfoSuccess = false;
-    InstallTrainInfo trainInfoOnDisk;
-    readTrainInfoSuccess = StorageManager::readTrainInfo(trainInfoOnDisk);
-
-    bool resetExperimentIds = false;
-    int64_t trainVersionCode = trainVersionCodeIn;
-    std::string trainNameUtf8 = std::string(String8(trainNameIn).string());
-    if (readTrainInfoSuccess) {
-        // Keep the old train version if we received an empty version.
-        if (trainVersionCodeIn == -1) {
-            trainVersionCode = trainInfoOnDisk.trainVersionCode;
-        } else if (trainVersionCodeIn != trainInfoOnDisk.trainVersionCode) {
-        // Reset experiment ids if we receive a new non-empty train version.
-            resetExperimentIds = true;
-        }
-
-        // Keep the old train name if we received an empty train name.
-        if (trainNameUtf8.size() == 0) {
-            trainNameUtf8 = trainInfoOnDisk.trainName;
-        } else if (trainNameUtf8 != trainInfoOnDisk.trainName) {
-            // Reset experiment ids if we received a new valid train name.
-            resetExperimentIds = true;
-        }
-
-        // Reset if we received a different experiment id.
-        if (!experimentIdsIn.empty() &&
-                (trainInfoOnDisk.experimentIds.empty() ||
-                 experimentIdsIn[0] != trainInfoOnDisk.experimentIds[0])) {
-            resetExperimentIds = true;
-        }
-    }
-
-    // Find the right experiment IDs
-    std::vector<int64_t> experimentIds;
-    if (resetExperimentIds || !readTrainInfoSuccess) {
-        experimentIds = experimentIdsIn;
-    } else {
-        experimentIds = trainInfoOnDisk.experimentIds;
-    }
-
-    if (!experimentIds.empty()) {
-        int64_t firstId = experimentIds[0];
-        switch (state) {
-            case android::util::BINARY_PUSH_STATE_CHANGED__STATE__INSTALL_SUCCESS:
-                experimentIds.push_back(firstId + 1);
-                break;
-            case android::util::BINARY_PUSH_STATE_CHANGED__STATE__INSTALLER_ROLLBACK_INITIATED:
-                experimentIds.push_back(firstId + 2);
-                break;
-            case android::util::BINARY_PUSH_STATE_CHANGED__STATE__INSTALLER_ROLLBACK_SUCCESS:
-                experimentIds.push_back(firstId + 3);
-                break;
-        }
-    }
-
-    // Flatten the experiment IDs to proto
-    vector<uint8_t> experimentIdsProtoBuffer;
-    writeExperimentIdsToProto(experimentIds, &experimentIdsProtoBuffer);
-    StorageManager::writeTrainInfo(trainVersionCode, trainNameUtf8, state, experimentIds);
-
-    userid_t userId = multiuser_get_user_id(uid);
-    bool requiresStaging = options & IStatsd::FLAG_REQUIRE_STAGING;
-    bool rollbackEnabled = options & IStatsd::FLAG_ROLLBACK_ENABLED;
-    bool requiresLowLatencyMonitor = options & IStatsd::FLAG_REQUIRE_LOW_LATENCY_MONITOR;
-    LogEvent event(trainNameUtf8, trainVersionCode, requiresStaging, rollbackEnabled,
-                   requiresLowLatencyMonitor, state, experimentIdsProtoBuffer, userId);
-    mProcessor->OnLogEvent(&event);
-    return Status::ok();
-}
-
 Status StatsService::sendWatchdogRollbackOccurredAtom(const int32_t rollbackTypeIn,
                                                       const android::String16& packageNameIn,
                                                       const int64_t packageVersionCodeIn,
diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h
index 3bfaa98..af3016f 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -193,16 +193,6 @@
     virtual Status unregisterNativePullAtomCallback(int32_t atomTag) override;
 
     /**
-     * Binder call to log BinaryPushStateChanged atom.
-     */
-    virtual Status sendBinaryPushStateChangedAtom(
-            const android::String16& trainNameIn,
-            const int64_t trainVersionCodeIn,
-            const int options,
-            const int32_t state,
-            const std::vector<int64_t>& experimentIdsIn) override;
-
-    /**
      * Binder call to log WatchdogRollbackOccurred atom.
      */
     virtual Status sendWatchdogRollbackOccurredAtom(
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index fccefdc..183d741 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -202,7 +202,8 @@
         DocsUIStartupMsReported docs_ui_startup_ms = 111 [(module) = "docsui"];
         DocsUIUserActionReported docs_ui_user_action_reported = 112 [(module) = "docsui"];
         WifiEnabledStateChanged wifi_enabled_state_changed = 113 [(module) = "framework"];
-        WifiRunningStateChanged wifi_running_state_changed = 114 [(module) = "framework"];
+        WifiRunningStateChanged wifi_running_state_changed = 114
+                [(module) = "framework", deprecated = true];
         AppCompacted app_compacted = 115 [(module) = "framework"];
         NetworkDnsEventReported network_dns_event_reported = 116 [(module) = "resolv"];
         DocsUIPickerLaunchedFromReported docs_ui_picker_launched_from_reported =
@@ -1258,6 +1259,8 @@
 }
 
 /**
+ * This atom is deprecated starting in R.
+ *
  * Logs when an app causes Wifi to run. In this context, 'to run' means to use Wifi Client Mode.
  * TODO: Include support for Hotspot, perhaps by using an extra field to denote 'mode'.
  * Note that Wifi Scanning is monitored separately in WifiScanStateChanged.
@@ -3706,6 +3709,7 @@
 
 /**
  * Potential experiment ids that goes with a train install.
+ * Should be kept in sync with experiment_ids.proto.
  */
 message TrainExperimentIds {
     repeated int64 experiment_id = 1;
diff --git a/cmds/statsd/src/experiment_ids.proto b/cmds/statsd/src/experiment_ids.proto
new file mode 100644
index 0000000..c203631
--- /dev/null
+++ b/cmds/statsd/src/experiment_ids.proto
@@ -0,0 +1,29 @@
+/*
+ * 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.os.statsd;
+
+option java_package = "com.android.internal.os";
+option java_outer_classname = "ExperimentIdsProto";
+
+// StatsLogProcessor uses the proto to parse experiment ids from
+// BinaryPushStateChanged atoms. This needs to be in sync with
+// TrainExperimentIds in atoms.proto.
+message ExperimentIds {
+    repeated int64 experiment_id = 1;
+}
diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp
index 9a0693a..e8fc603 100644
--- a/cmds/statsd/src/logd/LogEvent.cpp
+++ b/cmds/statsd/src/logd/LogEvent.cpp
@@ -35,6 +35,34 @@
 using std::string;
 using std::vector;
 
+// stats_event.h socket types. Keep in sync.
+/* ERRORS */
+#define ERROR_NO_TIMESTAMP 0x1
+#define ERROR_NO_ATOM_ID 0x2
+#define ERROR_OVERFLOW 0x4
+#define ERROR_ATTRIBUTION_CHAIN_TOO_LONG 0x8
+#define ERROR_TOO_MANY_KEY_VALUE_PAIRS 0x10
+#define ERROR_ANNOTATION_DOES_NOT_FOLLOW_FIELD 0x20
+#define ERROR_INVALID_ANNOTATION_ID 0x40
+#define ERROR_ANNOTATION_ID_TOO_LARGE 0x80
+#define ERROR_TOO_MANY_ANNOTATIONS 0x100
+#define ERROR_TOO_MANY_FIELDS 0x200
+#define ERROR_INVALID_VALUE_TYPE 0x400
+#define ERROR_STRING_NOT_NULL_TERMINATED 0x800
+
+/* TYPE IDS */
+#define INT32_TYPE 0x00
+#define INT64_TYPE 0x01
+#define STRING_TYPE 0x02
+#define LIST_TYPE 0x03
+#define FLOAT_TYPE 0x04
+#define BOOL_TYPE 0x05
+#define BYTE_ARRAY_TYPE 0x06
+#define OBJECT_TYPE 0x07
+#define KEY_VALUE_PAIRS_TYPE 0x08
+#define ATTRIBUTION_CHAIN_TYPE 0x09
+#define ERROR_TYPE 0x0F
+
 // Msg is expected to begin at the start of the serialized atom -- it should not
 // include the android_log_header_t or the StatsEventTag.
 LogEvent::LogEvent(uint8_t* msg, uint32_t len, int32_t uid, int32_t pid)
@@ -809,6 +837,26 @@
     return 0.0;
 }
 
+std::vector<uint8_t> LogEvent::GetStorage(size_t key, status_t* err) const {
+    int field = getSimpleField(key);
+    for (const auto& value : mValues) {
+      if (value.mField.getField() == field) {
+        if (value.mValue.getType() == STORAGE) {
+          return value.mValue.storage_value;
+        } else {
+          *err = BAD_TYPE;
+          return vector<uint8_t>();
+        }
+      }
+      if ((size_t)value.mField.getPosAtDepth(0) > key) {
+        break;
+      }
+    }
+
+    *err = BAD_INDEX;
+    return vector<uint8_t>();
+}
+
 string LogEvent::ToString() const {
     string result;
     result += StringPrintf("{ uid(%d) %lld %lld (%d)", mLogUid, (long long)mLogdTimestampNs,
diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h
index 3db2676..e4b784e 100644
--- a/cmds/statsd/src/logd/LogEvent.h
+++ b/cmds/statsd/src/logd/LogEvent.h
@@ -144,6 +144,7 @@
     const char* GetString(size_t key, status_t* err) const;
     bool GetBool(size_t key, status_t* err) const;
     float GetFloat(size_t key, status_t* err) const;
+    std::vector<uint8_t> GetStorage(size_t key, status_t* err) const;
 
     /**
      * Write test data to the LogEvent. This can only be used when the LogEvent is constructed
@@ -214,6 +215,22 @@
         return LogEvent(*this);
     }
 
+    template <class T>
+    status_t updateValue(size_t key, T& value, Type type) {
+        int field = getSimpleField(key);
+        for (auto& fieldValue : mValues) {
+            if (fieldValue.mField.getField() == field) {
+                if (fieldValue.mValue.getType() == type) {
+                    fieldValue.mValue = Value(value);
+                   return OK;
+               } else {
+                   return BAD_TYPE;
+                }
+            }
+        }
+        return BAD_INDEX;
+    }
+
 private:
     /**
      * Only use this if copy is absolutely needed.
diff --git a/cmds/statsd/src/stats_log_util.cpp b/cmds/statsd/src/stats_log_util.cpp
index 8e0c628..73f640e 100644
--- a/cmds/statsd/src/stats_log_util.cpp
+++ b/cmds/statsd/src/stats_log_util.cpp
@@ -21,6 +21,8 @@
 #include <set>
 #include <utils/SystemClock.h>
 
+#include "statscompanion_util.h"
+
 using android::util::AtomsInfo;
 using android::util::FIELD_COUNT_REPEATED;
 using android::util::FIELD_TYPE_BOOL;
@@ -584,6 +586,21 @@
     return millis * 1000000;
 }
 
+bool checkPermissionForIds(const char* permission, pid_t pid, uid_t uid) {
+    sp<IStatsCompanionService> scs = getStatsCompanionService();
+    if (scs == nullptr) {
+        return false;
+    }
+
+    bool success;
+    binder::Status status = scs->checkPermission(String16(permission), pid, uid, &success);
+    if (!status.isOk()) {
+        return false;
+    }
+
+    return success;
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/statsd/src/stats_log_util.h b/cmds/statsd/src/stats_log_util.h
index 5fdf6e2..aec0956 100644
--- a/cmds/statsd/src/stats_log_util.h
+++ b/cmds/statsd/src/stats_log_util.h
@@ -95,6 +95,9 @@
 // Returns the truncated timestamp to the nearest 5 minutes if needed.
 int64_t truncateTimestampIfNecessary(int atomId, int64_t timestampNs);
 
+// Checks permission for given pid and uid.
+bool checkPermissionForIds(const char* permission, pid_t pid, uid_t uid);
+
 inline bool isVendorPulledAtom(int atomId) {
     return atomId >= StatsdStats::kVendorPulledAtomStartTag && atomId < StatsdStats::kMaxAtomTag;
 }
diff --git a/cmds/statsd/tests/LogEvent_test.cpp b/cmds/statsd/tests/LogEvent_test.cpp
index 35b0396..f624e12 100644
--- a/cmds/statsd/tests/LogEvent_test.cpp
+++ b/cmds/statsd/tests/LogEvent_test.cpp
@@ -46,16 +46,16 @@
 }
 
 TEST(LogEventTest, TestPrimitiveParsing) {
-    struct stats_event* event = stats_event_obtain();
-    stats_event_set_atom_id(event, 100);
-    stats_event_write_int32(event, 10);
-    stats_event_write_int64(event, 0x123456789);
-    stats_event_write_float(event, 2.0);
-    stats_event_write_bool(event, true);
-    stats_event_build(event);
+    AStatsEvent* event = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(event, 100);
+    AStatsEvent_writeInt32(event, 10);
+    AStatsEvent_writeInt64(event, 0x123456789);
+    AStatsEvent_writeFloat(event, 2.0);
+    AStatsEvent_writeBool(event, true);
+    AStatsEvent_build(event);
 
     size_t size;
-    uint8_t* buf = stats_event_get_buffer(event, &size);
+    uint8_t* buf = AStatsEvent_getBuffer(event, &size);
 
     LogEvent logEvent(buf, size, /*uid=*/ 1000, /*pid=*/ 1001);
     EXPECT_TRUE(logEvent.isValid());
@@ -90,20 +90,20 @@
     EXPECT_EQ(Type::INT, boolItem.mValue.getType()); // FieldValue does not support boolean type
     EXPECT_EQ(1, boolItem.mValue.int_value);
 
-    stats_event_release(event);
+    AStatsEvent_release(event);
 }
 
 
 TEST(LogEventTest, TestStringAndByteArrayParsing) {
-    struct stats_event* event = stats_event_obtain();
-    stats_event_set_atom_id(event, 100);
+    AStatsEvent* event = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(event, 100);
     string str = "test";
-    stats_event_write_string8(event, str.c_str());
-    stats_event_write_byte_array(event, (uint8_t*)str.c_str(), str.length());
-    stats_event_build(event);
+    AStatsEvent_writeString(event, str.c_str());
+    AStatsEvent_writeByteArray(event, (uint8_t*)str.c_str(), str.length());
+    AStatsEvent_build(event);
 
     size_t size;
-    uint8_t* buf = stats_event_get_buffer(event, &size);
+    uint8_t* buf = AStatsEvent_getBuffer(event, &size);
 
     LogEvent logEvent(buf, size, /*uid=*/ 1000, /*pid=*/ 1001);
     EXPECT_TRUE(logEvent.isValid());
@@ -127,18 +127,18 @@
     vector<uint8_t> expectedValue = {'t', 'e', 's', 't'};
     EXPECT_EQ(expectedValue, storageItem.mValue.storage_value);
 
-    stats_event_release(event);
+    AStatsEvent_release(event);
 }
 
 TEST(LogEventTest, TestEmptyString) {
-    struct stats_event* event = stats_event_obtain();
-    stats_event_set_atom_id(event, 100);
+    AStatsEvent* event = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(event, 100);
     string empty = "";
-    stats_event_write_string8(event, empty.c_str());
-    stats_event_build(event);
+    AStatsEvent_writeString(event, empty.c_str());
+    AStatsEvent_build(event);
 
     size_t size;
-    uint8_t* buf = stats_event_get_buffer(event, &size);
+    uint8_t* buf = AStatsEvent_getBuffer(event, &size);
 
     LogEvent logEvent(buf, size, /*uid=*/ 1000, /*pid=*/ 1001);
     EXPECT_TRUE(logEvent.isValid());
@@ -155,18 +155,18 @@
     EXPECT_EQ(Type::STRING, item.mValue.getType());
     EXPECT_EQ(empty, item.mValue.str_value);
 
-    stats_event_release(event);
+    AStatsEvent_release(event);
 }
 
 TEST(LogEventTest, TestByteArrayWithNullCharacter) {
-    struct stats_event* event = stats_event_obtain();
-    stats_event_set_atom_id(event, 100);
+    AStatsEvent* event = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(event, 100);
     uint8_t message[] = {'\t', 'e', '\0', 's', 't'};
-    stats_event_write_byte_array(event, message, 5);
-    stats_event_build(event);
+    AStatsEvent_writeByteArray(event, message, 5);
+    AStatsEvent_build(event);
 
     size_t size;
-    uint8_t* buf = stats_event_get_buffer(event, &size);
+    uint8_t* buf = AStatsEvent_getBuffer(event, &size);
 
     LogEvent logEvent(buf, size, /*uid=*/ 1000, /*pid=*/ 1001);
     EXPECT_TRUE(logEvent.isValid());
@@ -184,79 +184,12 @@
     vector<uint8_t> expectedValue(message, message + 5);
     EXPECT_EQ(expectedValue, item.mValue.storage_value);
 
-    stats_event_release(event);
-}
-
-TEST(LogEventTest, TestKeyValuePairs) {
-    struct stats_event* event = stats_event_obtain();
-    stats_event_set_atom_id(event, 100);
-
-    struct key_value_pair pairs[4];
-    pairs[0] = {.key = 0, .valueType = INT32_TYPE, .int32Value = 1};
-    pairs[1] = {.key = 1, .valueType = INT64_TYPE, .int64Value = 0x123456789};
-    pairs[2] = {.key = 2, .valueType = FLOAT_TYPE, .floatValue = 2.0};
-    string str = "test";
-    pairs[3] = {.key = 3, .valueType = STRING_TYPE, .stringValue = str.c_str()};
-
-    stats_event_write_key_value_pairs(event, pairs, 4);
-    stats_event_build(event);
-
-    size_t size;
-    uint8_t* buf = stats_event_get_buffer(event, &size);
-
-    LogEvent logEvent(buf, size, /*uid=*/ 1000, /*pid=*/ 1001);
-    EXPECT_TRUE(logEvent.isValid());
-    EXPECT_EQ(100, logEvent.GetTagId());
-    EXPECT_EQ(1000, logEvent.GetUid());
-    EXPECT_EQ(1001, logEvent.GetPid());
-
-    const vector<FieldValue>& values = logEvent.getValues();
-    EXPECT_EQ(8, values.size()); // 2 FieldValues per key-value pair
-
-    // Check the keys first
-    for (int i = 0; i < values.size() / 2; i++) {
-        const FieldValue& item = values[2 * i];
-        int32_t depth1Pos = i + 1;
-        bool depth1Last = i == (values.size() / 2 - 1);
-        Field expectedField = getField(100, {1, depth1Pos, 1}, 2, {true, depth1Last, false});
-
-        EXPECT_EQ(expectedField, item.mField);
-        EXPECT_EQ(Type::INT, item.mValue.getType());
-        EXPECT_EQ(i, item.mValue.int_value);
-    }
-
-    // Check the values now
-    // Note: pos[2] = index of type in KeyValuePair in atoms.proto
-    const FieldValue& int32Item = values[1];
-    Field expectedField = getField(100, {1, 1, 2}, 2, {true, false, true});
-    EXPECT_EQ(expectedField, int32Item.mField);
-    EXPECT_EQ(Type::INT, int32Item.mValue.getType());
-    EXPECT_EQ(1, int32Item.mValue.int_value);
-
-    const FieldValue& int64Item = values[3];
-    expectedField = getField(100, {1, 2, 3}, 2, {true, false, true});
-    EXPECT_EQ(expectedField, int64Item.mField);
-    EXPECT_EQ(Type::LONG, int64Item.mValue.getType());
-    EXPECT_EQ(0x123456789, int64Item.mValue.long_value);
-
-    const FieldValue& floatItem = values[5];
-    expectedField = getField(100, {1, 3, 5}, 2, {true, false, true});
-    EXPECT_EQ(expectedField, floatItem.mField);
-    EXPECT_EQ(Type::FLOAT, floatItem.mValue.getType());
-    EXPECT_EQ(2.0, floatItem.mValue.float_value);
-
-    const FieldValue& stringItem = values[7];
-    expectedField = getField(100, {1, 4, 4}, 2, {true, true, true});
-    EXPECT_EQ(expectedField, stringItem.mField);
-    EXPECT_EQ(Type::STRING, stringItem.mValue.getType());
-    EXPECT_EQ(str, stringItem.mValue.str_value);
-
-    stats_event_release(event);
+    AStatsEvent_release(event);
 }
 
 TEST(LogEventTest, TestAttributionChain) {
-    struct stats_event* event = stats_event_obtain();
-    stats_event_set_atom_id(event, 100);
+    AStatsEvent* event = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(event, 100);
 
     string tag1 = "tag1";
     string tag2 = "tag2";
@@ -264,11 +197,11 @@
     uint32_t uids[] = {1001, 1002};
     const char* tags[] = {tag1.c_str(), tag2.c_str()};
 
-    stats_event_write_attribution_chain(event, uids, tags, 2);
-    stats_event_build(event);
+    AStatsEvent_writeAttributionChain(event, uids, tags, 2);
+    AStatsEvent_build(event);
 
     size_t size;
-    uint8_t* buf = stats_event_get_buffer(event, &size);
+    uint8_t* buf = AStatsEvent_getBuffer(event, &size);
 
     LogEvent logEvent(buf, size, /*uid=*/ 1000, /*pid=*/ 1001);
     EXPECT_TRUE(logEvent.isValid());
@@ -305,7 +238,7 @@
     EXPECT_EQ(Type::STRING, tag2Item.mValue.getType());
     EXPECT_EQ(tag2, tag2Item.mValue.str_value);
 
-    stats_event_release(event);
+    AStatsEvent_release(event);
 }
 
 #else // NEW_ENCODING_SCHEME
diff --git a/cmds/statsd/tests/external/StatsCallbackPuller_test.cpp b/cmds/statsd/tests/external/StatsCallbackPuller_test.cpp
index 2576cf5..a011692e 100644
--- a/cmds/statsd/tests/external/StatsCallbackPuller_test.cpp
+++ b/cmds/statsd/tests/external/StatsCallbackPuller_test.cpp
@@ -50,11 +50,11 @@
 int64_t pullCoolDownNs;
 std::thread pullThread;
 
-stats_event* createSimpleEvent(int64_t value) {
-    stats_event* event = stats_event_obtain();
-    stats_event_set_atom_id(event, pullTagId);
-    stats_event_write_int64(event, value);
-    stats_event_build(event);
+AStatsEvent* createSimpleEvent(int64_t value) {
+    AStatsEvent* event = AStatsEvent_obtain();
+    AStatsEvent_setAtomId(event, pullTagId);
+    AStatsEvent_writeInt64(event, value);
+    AStatsEvent_build(event);
     return event;
 }
 
@@ -62,16 +62,16 @@
     // Convert stats_events into StatsEventParcels.
     std::vector<android::util::StatsEventParcel> parcels;
     for (int i = 0; i < values.size(); i++) {
-        stats_event* event = createSimpleEvent(values[i]);
+        AStatsEvent* event = createSimpleEvent(values[i]);
         size_t size;
-        uint8_t* buffer = stats_event_get_buffer(event, &size);
+        uint8_t* buffer = AStatsEvent_getBuffer(event, &size);
 
         android::util::StatsEventParcel p;
         // vector.assign() creates a copy, but this is inevitable unless
         // stats_event.h/c uses a vector as opposed to a buffer.
         p.buffer.assign(buffer, buffer + size);
         parcels.push_back(std::move(p));
-        stats_event_release(event);
+        AStatsEvent_release(event);
     }
 
     sleep_for(std::chrono::nanoseconds(pullDelayNs));
diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp
index 6e1890a..db09ee9 100644
--- a/cmds/statsd/tests/statsd_test_util.cpp
+++ b/cmds/statsd/tests/statsd_test_util.cpp
@@ -953,24 +953,24 @@
     // Convert stats_events into StatsEventParcels.
     std::vector<android::util::StatsEventParcel> parcels;
     for (int i = 1; i < 3; i++) {
-        stats_event* event = stats_event_obtain();
-        stats_event_set_atom_id(event, atomTag);
+        AStatsEvent* event = AStatsEvent_obtain();
+        AStatsEvent_setAtomId(event, atomTag);
         std::string subsystemName = "subsystem_name_";
         subsystemName = subsystemName + std::to_string(i);
-        stats_event_write_string8(event, subsystemName.c_str());
-        stats_event_write_string8(event, "subsystem_subname foo");
-        stats_event_write_int64(event, /*count= */ i);
-        stats_event_write_int64(event, /*time_millis= */ i * 100);
-        stats_event_build(event);
+        AStatsEvent_writeString(event, subsystemName.c_str());
+        AStatsEvent_writeString(event, "subsystem_subname foo");
+        AStatsEvent_writeInt64(event, /*count= */ i);
+        AStatsEvent_writeInt64(event, /*time_millis= */ i * 100);
+        AStatsEvent_build(event);
         size_t size;
-        uint8_t* buffer = stats_event_get_buffer(event, &size);
+        uint8_t* buffer = AStatsEvent_getBuffer(event, &size);
 
         android::util::StatsEventParcel p;
         // vector.assign() creates a copy, but this is inevitable unless
         // stats_event.h/c uses a vector as opposed to a buffer.
         p.buffer.assign(buffer, buffer + size);
         parcels.push_back(std::move(p));
-        stats_event_release(event);
+        AStatsEvent_write(event);
     }
     resultReceiver->pullFinished(atomTag, /*success=*/true, parcels);
     return binder::Status::ok();
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 46f8669..861d41d 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -88,10 +88,15 @@
 import java.util.function.Supplier;
 
 /**
- * API for interacting with "application operation" tracking.
+ * AppOps are mappings of [package/uid, op-name] -> [mode]. The list of existing appops is defined
+ * by the system and cannot be amended by apps. Only system apps can change appop-modes.
  *
- * <p>This API is not generally intended for third party application developers; most
- * features are only available to system applications.
+ * <p>Beside a mode the system tracks when an op was {@link #noteOp noted}. The tracked data can
+ * only be read by system components.
+ *
+ * <p>Installed apps can usually only listen to changes and events on their own ops. E.g.
+ * {@link AppOpsCollector} allows to get a callback each time an app called {@link #noteOp} or
+ * {@link #startOp} for an op belonging to the app.
  */
 @SystemService(Context.APP_OPS_SERVICE)
 public class AppOpsManager {
@@ -6774,6 +6779,9 @@
      * succeeds, the last execution time of the operation for this app will be updated to
      * the current time.
      *
+     * <p>If this is a check that is not preceding the protected operation, use
+     * {@link #unsafeCheckOp} instead.
+     *
      * @param op The operation to note.  One of the OPSTR_* constants.
      * @param uid The user id of the application attempting to perform the operation.
      * @param packageName The name of the application attempting to perform the operation.
@@ -6798,6 +6806,9 @@
      * succeeds, the last execution time of the operation for this app will be updated to
      * the current time.
      *
+     * <p>If this is a check that is not preceding the protected operation, use
+     * {@link #unsafeCheckOp} instead.
+     *
      * @param op The operation to note.  One of the OP_* constants.
      * @param uid The user id of the application attempting to perform the operation.
      * @param packageName The name of the application attempting to perform the operation.
@@ -7757,9 +7768,38 @@
 
     /**
      * Callback an app can choose to {@link #setNotedAppOpsCollector register} to monitor it's noted
-     * appops.
+     * appops. I.e. each time any app calls {@link #noteOp} or {@link #startOp} one of the callback
+     * methods of this object is called.
      *
      * <p><b>Only appops related to dangerous permissions are collected.</b>
+     *
+     * <pre>
+     * setNotedAppOpsCollector(new AppOpsCollector() {
+     *     ArraySet<Pair<String, String>> opsNotedForThisProcess = new ArraySet<>();
+     *
+     *     private synchronized void addAccess(String op, String accessLocation) {
+     *         // Ops are often noted when permission protected APIs were called.
+     *         // In this case permissionToOp() allows to resolve the permission<->op
+     *         opsNotedForThisProcess.add(new Pair(accessType, accessLocation));
+     *     }
+     *
+     *     public void onNoted(SyncNotedAppOp op) {
+     *         // Accesses is currently happening, hence stack trace describes location of access
+     *         addAccess(op.getOp(), Arrays.toString(Thread.currentThread().getStackTrace()));
+     *     }
+     *
+     *     public void onSelfNoted(SyncNotedAppOp op) {
+     *         onNoted(op);
+     *     }
+     *
+     *     public void onAsyncNoted(AsyncNotedAppOp asyncOp) {
+     *         // Stack trace is not useful for async ops as accessed happened on different thread
+     *         addAccess(asyncOp.getOp(), asyncOp.getMessage());
+     *     }
+     * });
+     * </pre>
+     *
+     * @see #setNotedAppOpsCollector
      */
     public abstract static class AppOpsCollector {
         /** Callback registered with the system. This will receive the async notes ops */
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/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 51d5c3f6..5a0bcf0 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -3354,6 +3354,22 @@
     public static final int FLAG_PERMISSION_ONE_TIME = 1 << 16;
 
     /**
+     * Permission flag: The permission is whitelisted to not be auto-revoked when app goes unused.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int FLAG_PERMISSION_DONT_AUTO_REVOKE = 1 << 17;
+
+    /**
+     * Permission flag: Whether {@link #FLAG_PERMISSION_DONT_AUTO_REVOKE} state was set by user.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int FLAG_PERMISSION_DONT_AUTO_REVOKE_USER_SET = 1 << 18;
+
+    /**
      * Permission flags: Reserved for use by the permission controller.
      *
      * @hide
@@ -3404,7 +3420,9 @@
             | FLAG_PERMISSION_APPLY_RESTRICTION
             | FLAG_PERMISSION_GRANTED_BY_ROLE
             | FLAG_PERMISSION_REVOKED_COMPAT
-            | FLAG_PERMISSION_ONE_TIME;
+            | FLAG_PERMISSION_ONE_TIME
+            | FLAG_PERMISSION_DONT_AUTO_REVOKE
+            | FLAG_PERMISSION_DONT_AUTO_REVOKE_USER_SET;
 
     /**
      * Injected activity in app that forwards user to setting activity of that app.
@@ -4227,7 +4245,8 @@
             FLAG_PERMISSION_APPLY_RESTRICTION,
             FLAG_PERMISSION_GRANTED_BY_ROLE,
             FLAG_PERMISSION_REVOKED_COMPAT,
-            FLAG_PERMISSION_ONE_TIME
+            FLAG_PERMISSION_ONE_TIME,
+            FLAG_PERMISSION_DONT_AUTO_REVOKE
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface PermissionFlags {}
@@ -7364,6 +7383,8 @@
             case FLAG_PERMISSION_GRANTED_BY_ROLE: return "GRANTED_BY_ROLE";
             case FLAG_PERMISSION_REVOKED_COMPAT: return "REVOKED_COMPAT";
             case FLAG_PERMISSION_ONE_TIME: return "ONE_TIME";
+            case FLAG_PERMISSION_DONT_AUTO_REVOKE: return "DONT_AUTO_REVOKE";
+            case FLAG_PERMISSION_DONT_AUTO_REVOKE_USER_SET: return "DONT_AUTO_REVOKE_USER_SET";
             default: return Integer.toString(flag);
         }
     }
diff --git a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java
index 368c94c..79852d3 100644
--- a/core/java/android/service/autofill/augmented/AugmentedAutofillService.java
+++ b/core/java/android/service/autofill/augmented/AugmentedAutofillService.java
@@ -31,7 +31,6 @@
 import android.content.Intent;
 import android.graphics.Rect;
 import android.os.Build;
-import android.os.Bundle;
 import android.os.CancellationSignal;
 import android.os.Handler;
 import android.os.IBinder;
@@ -93,7 +92,7 @@
     // Used for metrics / debug only
     private ComponentName mServiceComponentName;
 
-    private final IAugmentedAutofillService mInterface = new IAugmentedAutofillService.Stub() {
+    private final class AugmentedAutofillServiceImpl extends IAugmentedAutofillService.Stub {
 
         @Override
         public void onConnected(boolean debug, boolean verbose) {
@@ -137,7 +136,7 @@
     public final IBinder onBind(Intent intent) {
         mServiceComponentName = intent.getComponent();
         if (SERVICE_INTERFACE.equals(intent.getAction())) {
-            return mInterface.asBinder();
+            return new AugmentedAutofillServiceImpl();
         }
         Log.w(TAG, "Tried to bind to wrong intent (should be " + SERVICE_INTERFACE + ": " + intent);
         return null;
@@ -352,11 +351,13 @@
         static final int REPORT_EVENT_NO_RESPONSE = 1;
         static final int REPORT_EVENT_UI_SHOWN = 2;
         static final int REPORT_EVENT_UI_DESTROYED = 3;
+        static final int REPORT_EVENT_INLINE_RESPONSE = 4;
 
         @IntDef(prefix = { "REPORT_EVENT_" }, value = {
                 REPORT_EVENT_NO_RESPONSE,
                 REPORT_EVENT_UI_SHOWN,
-                REPORT_EVENT_UI_DESTROYED
+                REPORT_EVENT_UI_DESTROYED,
+                REPORT_EVENT_INLINE_RESPONSE
         })
         @Retention(RetentionPolicy.SOURCE)
         @interface ReportEvent{}
@@ -365,8 +366,8 @@
         private final Object mLock = new Object();
         private final IAugmentedAutofillManagerClient mClient;
         private final int mSessionId;
-        public final int taskId;
-        public final ComponentName componentName;
+        public final int mTaskId;
+        public final ComponentName mComponentName;
         // Used for metrics / debug only
         private String mServicePackageName;
         @GuardedBy("mLock")
@@ -406,8 +407,8 @@
             mSessionId = sessionId;
             mClient = IAugmentedAutofillManagerClient.Stub.asInterface(client);
             mCallback = callback;
-            this.taskId = taskId;
-            this.componentName = componentName;
+            mTaskId = taskId;
+            mComponentName = componentName;
             mServicePackageName = serviceComponentName.getPackageName();
             mFocusedId = focusedId;
             mFocusedValue = focusedValue;
@@ -514,22 +515,24 @@
             }
         }
 
-        public void onInlineSuggestionsDataReady(@NonNull List<Dataset> inlineSuggestionsData,
-                @Nullable Bundle clientState) {
+        void reportResult(@Nullable List<Dataset> inlineSuggestionsData) {
             try {
-                mCallback.onSuccess(inlineSuggestionsData.toArray(new Dataset[]{}), clientState);
+                final Dataset[] inlineSuggestions = (inlineSuggestionsData != null)
+                        ? inlineSuggestionsData.toArray(new Dataset[inlineSuggestionsData.size()])
+                        : null;
+                mCallback.onSuccess(inlineSuggestions);
             } catch (RemoteException e) {
                 Log.e(TAG, "Error calling back with the inline suggestions data: " + e);
             }
         }
 
-        // Used (mostly) for metrics.
-        public void report(@ReportEvent int event) {
-            if (sVerbose) Log.v(TAG, "report(): " + event);
+        void logEvent(@ReportEvent int event) {
+            if (sVerbose) Log.v(TAG, "returnAndLogResult(): " + event);
             long duration = -1;
             int type = MetricsEvent.TYPE_UNKNOWN;
+
             switch (event) {
-                case REPORT_EVENT_NO_RESPONSE:
+                case REPORT_EVENT_NO_RESPONSE: {
                     type = MetricsEvent.TYPE_SUCCESS;
                     if (mFirstOnSuccessTime == 0) {
                         mFirstOnSuccessTime = SystemClock.elapsedRealtime();
@@ -538,40 +541,49 @@
                             Log.d(TAG, "Service responded nothing in " + formatDuration(duration));
                         }
                     }
-                    try {
-                        mCallback.onSuccess(/* inlineSuggestionsData= */null, /* clientState=*/
-                                null);
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Error reporting success: " + e);
+                } break;
+
+                case REPORT_EVENT_INLINE_RESPONSE: {
+                    // TODO: Define a constant and log this event
+                    // type = MetricsEvent.TYPE_SUCCESS_INLINE;
+                    if (mFirstOnSuccessTime == 0) {
+                        mFirstOnSuccessTime = SystemClock.elapsedRealtime();
+                        duration = mFirstOnSuccessTime - mFirstRequestTime;
+                        if (sDebug) {
+                            Log.d(TAG, "Service responded nothing in " + formatDuration(duration));
+                        }
                     }
-                    break;
-                case REPORT_EVENT_UI_SHOWN:
+                } break;
+
+                case REPORT_EVENT_UI_SHOWN: {
                     type = MetricsEvent.TYPE_OPEN;
                     if (mUiFirstShownTime == 0) {
                         mUiFirstShownTime = SystemClock.elapsedRealtime();
                         duration = mUiFirstShownTime - mFirstRequestTime;
                         if (sDebug) Log.d(TAG, "UI shown in " + formatDuration(duration));
                     }
-                    break;
-                case REPORT_EVENT_UI_DESTROYED:
+                } break;
+
+                case REPORT_EVENT_UI_DESTROYED: {
                     type = MetricsEvent.TYPE_CLOSE;
                     if (mUiFirstDestroyedTime == 0) {
                         mUiFirstDestroyedTime = SystemClock.elapsedRealtime();
-                        duration =  mUiFirstDestroyedTime - mFirstRequestTime;
+                        duration = mUiFirstDestroyedTime - mFirstRequestTime;
                         if (sDebug) Log.d(TAG, "UI destroyed in " + formatDuration(duration));
                     }
-                    break;
+                } break;
+
                 default:
                     Log.w(TAG, "invalid event reported: " + event);
             }
-            logResponse(type, mServicePackageName, componentName, mSessionId, duration);
+            logResponse(type, mServicePackageName, mComponentName, mSessionId, duration);
         }
 
         public void dump(@NonNull String prefix, @NonNull PrintWriter pw) {
             pw.print(prefix); pw.print("sessionId: "); pw.println(mSessionId);
-            pw.print(prefix); pw.print("taskId: "); pw.println(taskId);
+            pw.print(prefix); pw.print("taskId: "); pw.println(mTaskId);
             pw.print(prefix); pw.print("component: ");
-            pw.println(componentName.flattenToShortString());
+            pw.println(mComponentName.flattenToShortString());
             pw.print(prefix); pw.print("focusedId: "); pw.println(mFocusedId);
             if (mFocusedValue != null) {
                 pw.print(prefix); pw.print("focusedValue: "); pw.println(mFocusedValue);
diff --git a/core/java/android/service/autofill/augmented/FillCallback.java b/core/java/android/service/autofill/augmented/FillCallback.java
index d0ffd7b..19eff57 100644
--- a/core/java/android/service/autofill/augmented/FillCallback.java
+++ b/core/java/android/service/autofill/augmented/FillCallback.java
@@ -54,13 +54,15 @@
         if (sDebug) Log.d(TAG, "onSuccess(): " + response);
 
         if (response == null) {
-            mProxy.report(AutofillProxy.REPORT_EVENT_NO_RESPONSE);
+            mProxy.logEvent(AutofillProxy.REPORT_EVENT_NO_RESPONSE);
+            mProxy.reportResult(null /*inlineSuggestions*/);
             return;
         }
 
         List<Dataset> inlineSuggestions = response.getInlineSuggestions();
         if (inlineSuggestions != null && !inlineSuggestions.isEmpty()) {
-            mProxy.onInlineSuggestionsDataReady(inlineSuggestions, response.getClientState());
+            mProxy.logEvent(AutofillProxy.REPORT_EVENT_INLINE_RESPONSE);
+            mProxy.reportResult(inlineSuggestions);
             return;
         }
 
diff --git a/core/java/android/service/autofill/augmented/FillController.java b/core/java/android/service/autofill/augmented/FillController.java
index 63ec2d8..7d552d6 100644
--- a/core/java/android/service/autofill/augmented/FillController.java
+++ b/core/java/android/service/autofill/augmented/FillController.java
@@ -62,12 +62,13 @@
 
         try {
             mProxy.autofill(values);
-            final FillWindow fillWindow = mProxy.getFillWindow();
-            if (fillWindow != null) {
-                fillWindow.destroy();
-            }
         } catch (RemoteException e) {
             e.rethrowAsRuntimeException();
         }
+
+        final FillWindow fillWindow = mProxy.getFillWindow();
+        if (fillWindow != null) {
+            fillWindow.destroy();
+        }
     }
 }
diff --git a/core/java/android/service/autofill/augmented/FillRequest.java b/core/java/android/service/autofill/augmented/FillRequest.java
index ca49e7d..6927cf6 100644
--- a/core/java/android/service/autofill/augmented/FillRequest.java
+++ b/core/java/android/service/autofill/augmented/FillRequest.java
@@ -53,7 +53,7 @@
      * Gets the task of the activity associated with this request.
      */
     public int getTaskId() {
-        return mProxy.taskId;
+        return mProxy.mTaskId;
     }
 
     /**
@@ -61,7 +61,7 @@
      */
     @NonNull
     public ComponentName getActivityComponent() {
-        return mProxy.componentName;
+        return mProxy.mComponentName;
     }
 
     /**
diff --git a/core/java/android/service/autofill/augmented/FillWindow.java b/core/java/android/service/autofill/augmented/FillWindow.java
index 5d00370..077df6c 100644
--- a/core/java/android/service/autofill/augmented/FillWindow.java
+++ b/core/java/android/service/autofill/augmented/FillWindow.java
@@ -21,6 +21,7 @@
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.graphics.Rect;
@@ -41,6 +42,7 @@
 import dalvik.system.CloseGuard;
 
 import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
 
 /**
  * Handle to a window used to display the augmented autofill UI.
@@ -70,23 +72,22 @@
     private final CloseGuard mCloseGuard = CloseGuard.get();
 
     private final @NonNull Handler mUiThreadHandler = new Handler(Looper.getMainLooper());
-    private final @NonNull FillWindowPresenter mFillWindowPresenter = new FillWindowPresenter();
 
     @GuardedBy("mLock")
-    private WindowManager mWm;
+    private @NonNull WindowManager mWm;
     @GuardedBy("mLock")
     private View mFillView;
     @GuardedBy("mLock")
     private boolean mShowing;
     @GuardedBy("mLock")
-    private Rect mBounds;
+    private @Nullable Rect mBounds;
 
     @GuardedBy("mLock")
     private boolean mUpdateCalled;
     @GuardedBy("mLock")
     private boolean mDestroyed;
 
-    private AutofillProxy mProxy;
+    private @NonNull AutofillProxy mProxy;
 
     /**
      * Updates the content of the window.
@@ -172,11 +173,11 @@
                 try {
                     mProxy.requestShowFillUi(mBounds.right - mBounds.left,
                             mBounds.bottom - mBounds.top,
-                            /*anchorBounds=*/ null, mFillWindowPresenter);
+                            /*anchorBounds=*/ null, new FillWindowPresenter(this));
                 } catch (RemoteException e) {
                     Log.w(TAG, "Error requesting to show fill window", e);
                 }
-                mProxy.report(AutofillProxy.REPORT_EVENT_UI_SHOWN);
+                mProxy.logEvent(AutofillProxy.REPORT_EVENT_UI_SHOWN);
             }
         }
     }
@@ -244,7 +245,7 @@
             if (mUpdateCalled) {
                 mFillView.setOnClickListener(null);
                 hide();
-                mProxy.report(AutofillProxy.REPORT_EVENT_UI_DESTROYED);
+                mProxy.logEvent(AutofillProxy.REPORT_EVENT_UI_DESTROYED);
             }
             mDestroyed = true;
             mCloseGuard.close();
@@ -254,9 +255,7 @@
     @Override
     protected void finalize() throws Throwable {
         try {
-            if (mCloseGuard != null) {
-                mCloseGuard.warnIfOpen();
-            }
+            mCloseGuard.warnIfOpen();
             destroy();
         } finally {
             super.finalize();
@@ -289,22 +288,36 @@
 
     /** @hide */
     @Override
-    public void close() throws Exception {
+    public void close() {
         destroy();
     }
 
-    private final class FillWindowPresenter extends IAutofillWindowPresenter.Stub {
+    private static final class FillWindowPresenter extends IAutofillWindowPresenter.Stub {
+        private final @NonNull WeakReference<FillWindow> mFillWindowReference;
+
+        FillWindowPresenter(@NonNull FillWindow fillWindow) {
+            mFillWindowReference = new WeakReference<>(fillWindow);
+        }
+
         @Override
         public void show(WindowManager.LayoutParams p, Rect transitionEpicenter,
                 boolean fitsSystemWindows, int layoutDirection) {
             if (sDebug) Log.d(TAG, "FillWindowPresenter.show()");
-            mUiThreadHandler.sendMessage(obtainMessage(FillWindow::handleShow, FillWindow.this, p));
+            final FillWindow fillWindow = mFillWindowReference.get();
+            if (fillWindow != null) {
+                fillWindow.mUiThreadHandler.sendMessage(
+                        obtainMessage(FillWindow::handleShow, fillWindow, p));
+            }
         }
 
         @Override
         public void hide(Rect transitionEpicenter) {
             if (sDebug) Log.d(TAG, "FillWindowPresenter.hide()");
-            mUiThreadHandler.sendMessage(obtainMessage(FillWindow::handleHide, FillWindow.this));
+            final FillWindow fillWindow = mFillWindowReference.get();
+            if (fillWindow != null) {
+                fillWindow.mUiThreadHandler.sendMessage(
+                        obtainMessage(FillWindow::handleHide, fillWindow));
+            }
         }
     }
 }
diff --git a/core/java/android/service/autofill/augmented/IFillCallback.aidl b/core/java/android/service/autofill/augmented/IFillCallback.aidl
index 31e77f35..d983721 100644
--- a/core/java/android/service/autofill/augmented/IFillCallback.aidl
+++ b/core/java/android/service/autofill/augmented/IFillCallback.aidl
@@ -28,7 +28,7 @@
  */
 interface IFillCallback {
     void onCancellable(in ICancellationSignal cancellation);
-    void onSuccess(in @nullable Dataset[] inlineSuggestionsData, in @nullable Bundle clientState);
+    void onSuccess(in @nullable Dataset[] inlineSuggestionsData);
     boolean isCompleted();
     void cancel();
 }
diff --git a/core/java/android/util/StatsLog.java b/core/java/android/util/StatsLog.java
index feeff6c..79107379 100644
--- a/core/java/android/util/StatsLog.java
+++ b/core/java/android/util/StatsLog.java
@@ -27,6 +27,7 @@
 import android.os.IStatsd;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.util.FrameworkStatsLog;
 
@@ -37,6 +38,7 @@
 public final class StatsLog {
     private static final String TAG = "StatsLog";
     private static final boolean DEBUG = false;
+    private static final int EXPERIMENT_IDS_FIELD_ID = 1;
 
     private static IStatsd sService;
 
@@ -152,28 +154,25 @@
     public static boolean logBinaryPushStateChanged(@NonNull String trainName,
             long trainVersionCode, int options, int state,
             @NonNull long[] experimentIds) {
-        synchronized (sLogLock) {
-            try {
-                IStatsd service = getIStatsdLocked();
-                if (service == null) {
-                    if (DEBUG) {
-                        Slog.d(TAG, "Failed to find statsd when logging event");
-                    }
-                    return false;
-                }
-                service.sendBinaryPushStateChangedAtom(
-                        trainName, trainVersionCode, options, state, experimentIds);
-                return true;
-            } catch (RemoteException e) {
-                sService = null;
-                if (DEBUG) {
-                    Slog.d(TAG,
-                            "Failed to connect to StatsCompanionService when logging "
-                                    + "BinaryPushStateChanged");
-                }
-                return false;
-            }
+        ProtoOutputStream proto = new ProtoOutputStream();
+        for (long id : experimentIds) {
+            proto.write(
+                    ProtoOutputStream.FIELD_TYPE_INT64
+                    | ProtoOutputStream.FIELD_COUNT_REPEATED
+                    | EXPERIMENT_IDS_FIELD_ID,
+                    id);
         }
+        FrameworkStatsLog.write(FrameworkStatsLog.BINARY_PUSH_STATE_CHANGED,
+                trainName,
+                trainVersionCode,
+                (options & IStatsd.FLAG_REQUIRE_STAGING) > 0,
+                (options & IStatsd.FLAG_ROLLBACK_ENABLED) > 0,
+                (options & IStatsd.FLAG_REQUIRE_LOW_LATENCY_MONITOR) > 0,
+                state,
+                proto.getBytes(),
+                0,
+                0);
+        return true;
     }
 
     /**
diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java
index 4752ead..9f03d95 100644
--- a/core/java/android/widget/AbsListView.java
+++ b/core/java/android/widget/AbsListView.java
@@ -756,9 +756,6 @@
      */
     private ListItemAccessibilityDelegate mAccessibilityDelegate;
 
-    private int mLastAccessibilityScrollEventFromIndex;
-    private int mLastAccessibilityScrollEventToIndex;
-
     /**
      * Track the item count from the last time we handled a data change.
      */
@@ -1520,25 +1517,10 @@
         onScrollChanged(0, 0, 0, 0); // dummy values, View's implementation does not use these.
     }
 
-    /** @hide */
-    @Override
-    public void sendAccessibilityEventUnchecked(AccessibilityEvent event) {
-        // Since this class calls onScrollChanged even if the mFirstPosition and the
-        // child count have not changed we will avoid sending duplicate accessibility
-        // events.
-        if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
-            final int firstVisiblePosition = getFirstVisiblePosition();
-            final int lastVisiblePosition = getLastVisiblePosition();
-            if (mLastAccessibilityScrollEventFromIndex == firstVisiblePosition
-                    && mLastAccessibilityScrollEventToIndex == lastVisiblePosition) {
-                return;
-            } else {
-                mLastAccessibilityScrollEventFromIndex = firstVisiblePosition;
-                mLastAccessibilityScrollEventToIndex = lastVisiblePosition;
-            }
-        }
-        super.sendAccessibilityEventUnchecked(event);
-    }
+    /**
+     * A TYPE_VIEW_SCROLLED event should be sent whenever a scroll happens, even if the
+     * mFirstPosition and the child count have not changed.
+     */
 
     @Override
     public CharSequence getAccessibilityClassName() {
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index 79ec680..3c3daa3 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -3255,6 +3255,9 @@
      */
     @UnsupportedAppUsage
     private void scrollListItemsBy(int amount) {
+        int oldX = mScrollX;
+        int oldY = mScrollY;
+
         offsetChildrenTopAndBottom(amount);
 
         final int listBottom = getHeight() - mListPadding.bottom;
@@ -3327,6 +3330,7 @@
         recycleBin.fullyDetachScrapViews();
         removeUnusedFixedViews(mHeaderViewInfos);
         removeUnusedFixedViews(mFooterViewInfos);
+        onScrollChanged(mScrollX, mScrollY, oldX, oldY);
     }
 
     private View addViewAbove(View theView, int position) {
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index cec68df..9758673 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -35,8 +35,13 @@
         "android_animation_PropertyValuesHolder.cpp",
         "android_os_SystemClock.cpp",
         "android_os_SystemProperties.cpp",
+        "android_os_Trace.cpp",
+        "android_text_AndroidCharacter.cpp",
         "android_util_EventLog.cpp",
         "android_util_Log.cpp",
+        "android_util_StringBlock.cpp",
+        "android_util_XmlBlock.cpp",
+        "android_view_RenderNodeAnimator.cpp",
         "com_android_internal_util_VirtualRefBasePtr.cpp",
         "com_android_internal_view_animation_NativeInterpolatorFactoryHelper.cpp",
     ],
@@ -114,14 +119,12 @@
                 "android_view_KeyEvent.cpp",
                 "android_view_MotionEvent.cpp",
                 "android_view_PointerIcon.cpp",
-                "android_view_RenderNodeAnimator.cpp",
                 "android_view_Surface.cpp",
                 "android_view_SurfaceControl.cpp",
                 "android_graphics_BLASTBufferQueue.cpp",
                 "android_view_SurfaceSession.cpp",
                 "android_view_TextureView.cpp",
                 "android_view_VelocityTracker.cpp",
-                "android_text_AndroidCharacter.cpp",
                 "android_text_Hyphenator.cpp",
                 "android_os_Debug.cpp",
                 "android_os_GraphicsEnvironment.cpp",
@@ -138,7 +141,6 @@
                 "android_os_SELinux.cpp",
                 "android_os_SharedMemory.cpp",
                 "android_os_storage_StorageManager.cpp",
-                "android_os_Trace.cpp",
                 "android_os_UEventObserver.cpp",
                 "android_os_VintfObject.cpp",
                 "android_os_VintfRuntimeInfo.cpp",
@@ -150,8 +152,6 @@
                 "android_util_Binder.cpp",
                 "android_util_MemoryIntArray.cpp",
                 "android_util_Process.cpp",
-                "android_util_StringBlock.cpp",
-                "android_util_XmlBlock.cpp",
                 "android_util_jar_StrictJarFile.cpp",
                 "android_media_AudioDeviceAddress.cpp",
                 "android_media_AudioEffectDescriptor.cpp",
@@ -311,11 +311,8 @@
             srcs: [
                 "android_content_res_ApkAssets.cpp",
                 "android_os_MessageQueue.cpp",
-                "android_os_Trace.cpp",
                 "android_util_AssetManager.cpp",
                 "android_util_FileObserver.cpp",
-                "android_util_StringBlock.cpp",
-                "android_util_XmlBlock.cpp",
             ],
         },
     },
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 481be24..657336e 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -634,8 +634,6 @@
     char cachePruneBuf[sizeof("-Xzygote-max-boot-retry=")-1 + PROPERTY_VALUE_MAX];
     char dex2oatXmsImageFlagsBuf[sizeof("-Xms")-1 + PROPERTY_VALUE_MAX];
     char dex2oatXmxImageFlagsBuf[sizeof("-Xmx")-1 + PROPERTY_VALUE_MAX];
-    char dex2oatXmsFlagsBuf[sizeof("-Xms")-1 + PROPERTY_VALUE_MAX];
-    char dex2oatXmxFlagsBuf[sizeof("-Xmx")-1 + PROPERTY_VALUE_MAX];
     char dex2oatCompilerFilterBuf[sizeof("--compiler-filter=")-1 + PROPERTY_VALUE_MAX];
     char dex2oatImageCompilerFilterBuf[sizeof("--compiler-filter=")-1 + PROPERTY_VALUE_MAX];
     char dex2oatThreadsBuf[sizeof("-j")-1 + PROPERTY_VALUE_MAX];
@@ -885,88 +883,45 @@
     bool skip_compilation = ((strcmp(voldDecryptBuf, "trigger_restart_min_framework") == 0) ||
                              (strcmp(voldDecryptBuf, "1") == 0));
 
-    // Extra options for boot.art/boot.oat image generation.
-    parseCompilerRuntimeOption("dalvik.vm.image-dex2oat-Xms", dex2oatXmsImageFlagsBuf,
-                               "-Xms", "-Ximage-compiler-option");
-    parseCompilerRuntimeOption("dalvik.vm.image-dex2oat-Xmx", dex2oatXmxImageFlagsBuf,
-                               "-Xmx", "-Ximage-compiler-option");
-    if (skip_compilation) {
-        addOption("-Ximage-compiler-option");
-        addOption("--compiler-filter=assume-verified");
-    } else {
-        parseCompilerOption("dalvik.vm.image-dex2oat-filter", dex2oatImageCompilerFilterBuf,
-                            "--compiler-filter=", "-Ximage-compiler-option");
-    }
-
-    // If there is a boot profile, it takes precedence over the image and preloaded classes.
-    if (hasFile("/system/etc/boot-image.prof")) {
-        addOption("-Ximage-compiler-option");
-        addOption("--profile-file=/system/etc/boot-image.prof");
-        addOption("-Ximage-compiler-option");
-        addOption("--compiler-filter=speed-profile");
-    } else {
-        ALOGE("Missing boot-image.prof file, /system/etc/boot-image.prof not found: %s\n",
-              strerror(errno));
-        return -1;
-    }
-
-
-    // If there is a dirty-image-objects file, push it.
-    if (hasFile("/system/etc/dirty-image-objects")) {
-        addOption("-Ximage-compiler-option");
-        addOption("--dirty-image-objects=/system/etc/dirty-image-objects");
-    }
-
-    property_get("dalvik.vm.image-dex2oat-flags", dex2oatImageFlagsBuf, "");
-    parseExtraOpts(dex2oatImageFlagsBuf, "-Ximage-compiler-option");
-
-    // Extra options for DexClassLoader.
-    parseCompilerRuntimeOption("dalvik.vm.dex2oat-Xms", dex2oatXmsFlagsBuf,
-                               "-Xms", "-Xcompiler-option");
-    parseCompilerRuntimeOption("dalvik.vm.dex2oat-Xmx", dex2oatXmxFlagsBuf,
-                               "-Xmx", "-Xcompiler-option");
+    // Extra options for JIT.
     if (skip_compilation) {
         addOption("-Xcompiler-option");
         addOption("--compiler-filter=assume-verified");
-
-        // We skip compilation when a minimal runtime is brought up for decryption. In that case
-        // /data is temporarily backed by a tmpfs, which is usually small.
-        // If the system image contains prebuilts, they will be relocated into the tmpfs. In this
-        // specific situation it is acceptable to *not* relocate and run out of the prebuilts
-        // directly instead.
-        addOption("--runtime-arg");
-        addOption("-Xnorelocate");
     } else {
         parseCompilerOption("dalvik.vm.dex2oat-filter", dex2oatCompilerFilterBuf,
                             "--compiler-filter=", "-Xcompiler-option");
     }
     parseCompilerOption("dalvik.vm.dex2oat-threads", dex2oatThreadsBuf, "-j", "-Xcompiler-option");
-    parseCompilerOption("dalvik.vm.image-dex2oat-threads", dex2oatThreadsImageBuf, "-j",
-                        "-Ximage-compiler-option");
     parseCompilerOption("dalvik.vm.dex2oat-cpu-set", dex2oatCpuSetBuf, "--cpu-set=",
                         "-Xcompiler-option");
-    parseCompilerOption("dalvik.vm.image-dex2oat-cpu-set", dex2oatCpuSetImageBuf, "--cpu-set=",
-                        "-Ximage-compiler-option");
-
-    // The runtime will compile a boot image, when necessary, not using installd. Thus, we need to
-    // pass the instruction-set-features/variant as an image-compiler-option.
-    // Note: it is OK to reuse the buffer, as the values are exactly the same between
-    //       * compiler-option, used for runtime compilation (DexClassLoader)
-    //       * image-compiler-option, used for boot-image compilation on device
 
     // Copy the variant.
     sprintf(dex2oat_isa_variant_key, "dalvik.vm.isa.%s.variant", ABI_STRING);
     parseCompilerOption(dex2oat_isa_variant_key, dex2oat_isa_variant,
-                        "--instruction-set-variant=", "-Ximage-compiler-option");
-    parseCompilerOption(dex2oat_isa_variant_key, dex2oat_isa_variant,
                         "--instruction-set-variant=", "-Xcompiler-option");
     // Copy the features.
     sprintf(dex2oat_isa_features_key, "dalvik.vm.isa.%s.features", ABI_STRING);
     parseCompilerOption(dex2oat_isa_features_key, dex2oat_isa_features,
-                        "--instruction-set-features=", "-Ximage-compiler-option");
-    parseCompilerOption(dex2oat_isa_features_key, dex2oat_isa_features,
                         "--instruction-set-features=", "-Xcompiler-option");
 
+    /*
+     * When running with debug.generate-debug-info, add --generate-debug-info to
+     * the compiler options so that both JITted code and the boot image extension,
+     * if it is compiled on device, will include native debugging information.
+     */
+    property_get("debug.generate-debug-info", propBuf, "");
+    bool generate_debug_info = (strcmp(propBuf, "true") == 0);
+    if (generate_debug_info) {
+        addOption("-Xcompiler-option");
+        addOption("--generate-debug-info");
+    }
+
+    // The mini-debug-info makes it possible to backtrace through compiled code.
+    bool generate_mini_debug_info = property_get_bool("dalvik.vm.minidebuginfo", 0);
+    if (generate_mini_debug_info) {
+        addOption("-Xcompiler-option");
+        addOption("--generate-mini-debug-info");
+    }
 
     property_get("dalvik.vm.dex2oat-flags", dex2oatFlagsBuf, "");
     parseExtraOpts(dex2oatFlagsBuf, "-Xcompiler-option");
@@ -975,6 +930,53 @@
     property_get("dalvik.vm.extra-opts", extraOptsBuf, "");
     parseExtraOpts(extraOptsBuf, NULL);
 
+    // Extra options for boot image extension generation.
+    if (skip_compilation) {
+        addOption("-Xnoimage-dex2oat");
+    } else {
+        parseCompilerRuntimeOption("dalvik.vm.image-dex2oat-Xms", dex2oatXmsImageFlagsBuf,
+                                   "-Xms", "-Ximage-compiler-option");
+        parseCompilerRuntimeOption("dalvik.vm.image-dex2oat-Xmx", dex2oatXmxImageFlagsBuf,
+                                   "-Xmx", "-Ximage-compiler-option");
+
+        parseCompilerOption("dalvik.vm.image-dex2oat-filter", dex2oatImageCompilerFilterBuf,
+                            "--compiler-filter=", "-Ximage-compiler-option");
+
+        // If there is a dirty-image-objects file, push it.
+        if (hasFile("/system/etc/dirty-image-objects")) {
+            addOption("-Ximage-compiler-option");
+            addOption("--dirty-image-objects=/system/etc/dirty-image-objects");
+        }
+
+        parseCompilerOption("dalvik.vm.image-dex2oat-threads", dex2oatThreadsImageBuf, "-j",
+                            "-Ximage-compiler-option");
+        parseCompilerOption("dalvik.vm.image-dex2oat-cpu-set", dex2oatCpuSetImageBuf, "--cpu-set=",
+                            "-Ximage-compiler-option");
+
+        // The runtime may compile a boot image extension, when necessary, not using installd.
+        // Thus, we need to pass the instruction-set-features/variant as an image-compiler-option.
+        // Note: it is OK to reuse the buffer, as the values are exactly the same between
+        //       * compiler-option, used for runtime compilation (DexClassLoader)
+        //       * image-compiler-option, used for boot-image compilation on device
+        parseCompilerOption(dex2oat_isa_variant_key, dex2oat_isa_variant,
+                            "--instruction-set-variant=", "-Ximage-compiler-option");
+        parseCompilerOption(dex2oat_isa_features_key, dex2oat_isa_features,
+                            "--instruction-set-features=", "-Ximage-compiler-option");
+
+        if (generate_debug_info) {
+            addOption("-Ximage-compiler-option");
+            addOption("--generate-debug-info");
+        }
+
+        if (generate_mini_debug_info) {
+            addOption("-Ximage-compiler-option");
+            addOption("--generate-mini-debug-info");
+        }
+
+        property_get("dalvik.vm.image-dex2oat-flags", dex2oatImageFlagsBuf, "");
+        parseExtraOpts(dex2oatImageFlagsBuf, "-Ximage-compiler-option");
+    }
+
     /* Set the properties for locale */
     {
         strcpy(localeOption, "-Duser.locale=");
@@ -1032,25 +1034,6 @@
     parseRuntimeOption("dalvik.vm.zygote.max-boot-retry", cachePruneBuf,
                        "-Xzygote-max-boot-retry=");
 
-    /*
-     * When running with debug.generate-debug-info, add --generate-debug-info to
-     * the compiler options so that the boot image, if it is compiled on device,
-     * will include native debugging information.
-     */
-    property_get("debug.generate-debug-info", propBuf, "");
-    if (strcmp(propBuf, "true") == 0) {
-        addOption("-Xcompiler-option");
-        addOption("--generate-debug-info");
-        addOption("-Ximage-compiler-option");
-        addOption("--generate-debug-info");
-    }
-
-    // The mini-debug-info makes it possible to backtrace through JIT code.
-    if (property_get_bool("dalvik.vm.minidebuginfo", 0)) {
-        addOption("-Xcompiler-option");
-        addOption("--generate-mini-debug-info");
-    }
-
     // If set, the property below can be used to enable core platform API violation reporting.
     property_get("persist.debug.dalvik.vm.core_platform_api_policy", propBuf, "");
     if (propBuf[0] != '\0') {
diff --git a/core/jni/LayoutlibLoader.cpp b/core/jni/LayoutlibLoader.cpp
index 6c0680f..571a3387 100644
--- a/core/jni/LayoutlibLoader.cpp
+++ b/core/jni/LayoutlibLoader.cpp
@@ -75,10 +75,12 @@
 extern int register_android_os_SystemClock(JNIEnv* env);
 extern int register_android_os_SystemProperties(JNIEnv* env);
 extern int register_android_os_Trace(JNIEnv* env);
+extern int register_android_text_AndroidCharacter(JNIEnv* env);
 extern int register_android_util_EventLog(JNIEnv* env);
 extern int register_android_util_Log(JNIEnv* env);
 extern int register_android_util_PathParser(JNIEnv* env);
 extern int register_android_view_RenderNode(JNIEnv* env);
+extern int register_android_view_RenderNodeAnimator(JNIEnv* env);
 extern int register_android_view_DisplayListCanvas(JNIEnv* env);
 extern int register_com_android_internal_util_VirtualRefBasePtr(JNIEnv *env);
 extern int register_com_android_internal_view_animation_NativeInterpolatorFactoryHelper(JNIEnv *env);
@@ -90,58 +92,66 @@
 
 // Map of all possible class names to register to their corresponding JNI registration function pointer
 // The actual list of registered classes will be determined at runtime via the 'native_classes' System property
-static const std::unordered_map<std::string, RegJNIRec>  gRegJNIMap = {
-    {"android.animation.PropertyValuesHolder", REG_JNI(register_android_animation_PropertyValuesHolder)},
+static const std::unordered_map<std::string, RegJNIRec> gRegJNIMap = {
+        {"android.animation.PropertyValuesHolder",
+         REG_JNI(register_android_animation_PropertyValuesHolder)},
 #ifdef __linux__
-    {"android.content.AssetManager", REG_JNI(register_android_content_AssetManager)},
-    {"android.content.StringBlock", REG_JNI(register_android_content_StringBlock)},
-    {"android.content.XmlBlock", REG_JNI(register_android_content_XmlBlock)},
-    {"android.content.res.ApkAssets", REG_JNI(register_android_content_res_ApkAssets)},
+        {"android.content.res.ApkAssets", REG_JNI(register_android_content_res_ApkAssets)},
+        {"android.content.res.AssetManager", REG_JNI(register_android_content_AssetManager)},
 #endif
-    {"android.graphics.Bitmap", REG_JNI(register_android_graphics_Bitmap)},
-    {"android.graphics.BitmapFactory", REG_JNI(register_android_graphics_BitmapFactory)},
-    {"android.graphics.ByteBufferStreamAdaptor", REG_JNI(register_android_graphics_ByteBufferStreamAdaptor)},
-    {"android.graphics.Canvas", REG_JNI(register_android_graphics_Canvas)},
-    {"android.graphics.RenderNode", REG_JNI(register_android_view_RenderNode)},
-    {"android.graphics.ColorFilter", REG_JNI(register_android_graphics_ColorFilter)},
-    {"android.graphics.ColorSpace", REG_JNI(register_android_graphics_ColorSpace)},
-    {"android.graphics.CreateJavaOutputStreamAdaptor", REG_JNI(register_android_graphics_CreateJavaOutputStreamAdaptor)},
-    {"android.graphics.DrawFilter", REG_JNI(register_android_graphics_DrawFilter)},
-    {"android.graphics.FontFamily", REG_JNI(register_android_graphics_FontFamily)},
-    {"android.graphics.Graphics", REG_JNI(register_android_graphics_Graphics)},
-    {"android.graphics.ImageDecoder", REG_JNI(register_android_graphics_ImageDecoder)},
-    {"android.graphics.MaskFilter", REG_JNI(register_android_graphics_MaskFilter)},
-    {"android.graphics.Matrix", REG_JNI(register_android_graphics_Matrix)},
-    {"android.graphics.NinePatch", REG_JNI(register_android_graphics_NinePatch)},
-    {"android.graphics.Paint", REG_JNI(register_android_graphics_Paint)},
-    {"android.graphics.Path", REG_JNI(register_android_graphics_Path)},
-    {"android.graphics.PathEffect", REG_JNI(register_android_graphics_PathEffect)},
-    {"android.graphics.PathMeasure", REG_JNI(register_android_graphics_PathMeasure)},
-    {"android.graphics.Picture", REG_JNI(register_android_graphics_Picture)},
-    {"android.graphics.RecordingCanvas", REG_JNI(register_android_view_DisplayListCanvas)},
-    {"android.graphics.Region", REG_JNI(register_android_graphics_Region)},
-    {"android.graphics.Shader", REG_JNI(register_android_graphics_Shader)},
-    {"android.graphics.Typeface", REG_JNI(register_android_graphics_Typeface)},
-    {"android.graphics.drawable.AnimatedVectorDrawable", REG_JNI(register_android_graphics_drawable_AnimatedVectorDrawable)},
-    {"android.graphics.drawable.VectorDrawable", REG_JNI(register_android_graphics_drawable_VectorDrawable)},
-    {"android.graphics.fonts.Font", REG_JNI(register_android_graphics_fonts_Font)},
-    {"android.graphics.fonts.FontFamily", REG_JNI(register_android_graphics_fonts_FontFamily)},
-    {"android.graphics.text.LineBreaker", REG_JNI(register_android_graphics_text_LineBreaker)},
-    {"android.graphics.text.MeasuredText", REG_JNI(register_android_graphics_text_MeasuredText)},
+        {"android.content.res.StringBlock", REG_JNI(register_android_content_StringBlock)},
+        {"android.content.res.XmlBlock", REG_JNI(register_android_content_XmlBlock)},
+        {"android.graphics.Bitmap", REG_JNI(register_android_graphics_Bitmap)},
+        {"android.graphics.BitmapFactory", REG_JNI(register_android_graphics_BitmapFactory)},
+        {"android.graphics.ByteBufferStreamAdaptor",
+         REG_JNI(register_android_graphics_ByteBufferStreamAdaptor)},
+        {"android.graphics.Canvas", REG_JNI(register_android_graphics_Canvas)},
+        {"android.graphics.RenderNode", REG_JNI(register_android_view_RenderNode)},
+        {"android.graphics.ColorFilter", REG_JNI(register_android_graphics_ColorFilter)},
+        {"android.graphics.ColorSpace", REG_JNI(register_android_graphics_ColorSpace)},
+        {"android.graphics.CreateJavaOutputStreamAdaptor",
+         REG_JNI(register_android_graphics_CreateJavaOutputStreamAdaptor)},
+        {"android.graphics.DrawFilter", REG_JNI(register_android_graphics_DrawFilter)},
+        {"android.graphics.FontFamily", REG_JNI(register_android_graphics_FontFamily)},
+        {"android.graphics.Graphics", REG_JNI(register_android_graphics_Graphics)},
+        {"android.graphics.ImageDecoder", REG_JNI(register_android_graphics_ImageDecoder)},
+        {"android.graphics.MaskFilter", REG_JNI(register_android_graphics_MaskFilter)},
+        {"android.graphics.Matrix", REG_JNI(register_android_graphics_Matrix)},
+        {"android.graphics.NinePatch", REG_JNI(register_android_graphics_NinePatch)},
+        {"android.graphics.Paint", REG_JNI(register_android_graphics_Paint)},
+        {"android.graphics.Path", REG_JNI(register_android_graphics_Path)},
+        {"android.graphics.PathEffect", REG_JNI(register_android_graphics_PathEffect)},
+        {"android.graphics.PathMeasure", REG_JNI(register_android_graphics_PathMeasure)},
+        {"android.graphics.Picture", REG_JNI(register_android_graphics_Picture)},
+        {"android.graphics.RecordingCanvas", REG_JNI(register_android_view_DisplayListCanvas)},
+        {"android.graphics.Region", REG_JNI(register_android_graphics_Region)},
+        {"android.graphics.Shader", REG_JNI(register_android_graphics_Shader)},
+        {"android.graphics.Typeface", REG_JNI(register_android_graphics_Typeface)},
+        {"android.graphics.drawable.AnimatedVectorDrawable",
+         REG_JNI(register_android_graphics_drawable_AnimatedVectorDrawable)},
+        {"android.graphics.drawable.VectorDrawable",
+         REG_JNI(register_android_graphics_drawable_VectorDrawable)},
+        {"android.graphics.fonts.Font", REG_JNI(register_android_graphics_fonts_Font)},
+        {"android.graphics.fonts.FontFamily", REG_JNI(register_android_graphics_fonts_FontFamily)},
+        {"android.graphics.text.LineBreaker", REG_JNI(register_android_graphics_text_LineBreaker)},
+        {"android.graphics.text.MeasuredText",
+         REG_JNI(register_android_graphics_text_MeasuredText)},
 #ifdef __linux__
-    {"android.os.FileObserver", REG_JNI(register_android_os_FileObserver)},
-    {"android.os.MessageQueue", REG_JNI(register_android_os_MessageQueue)},
+        {"android.os.FileObserver", REG_JNI(register_android_os_FileObserver)},
+        {"android.os.MessageQueue", REG_JNI(register_android_os_MessageQueue)},
 #endif
-    {"android.os.SystemClock", REG_JNI(register_android_os_SystemClock)},
-    {"android.os.SystemProperties", REG_JNI(register_android_os_SystemProperties)},
-#ifdef __linux__
-    {"android.os.Trace", REG_JNI(register_android_os_Trace)},
-#endif
-    {"android.util.EventLog", REG_JNI(register_android_util_EventLog)},
-    {"android.util.Log", REG_JNI(register_android_util_Log)},
-    {"android.util.PathParser", REG_JNI(register_android_util_PathParser)},
-    {"com.android.internal.util.VirtualRefBasePtr", REG_JNI(register_com_android_internal_util_VirtualRefBasePtr)},
-    {"com.android.internal.view.animation.NativeInterpolatorFactoryHelper", REG_JNI(register_com_android_internal_view_animation_NativeInterpolatorFactoryHelper)},
+        {"android.os.SystemClock", REG_JNI(register_android_os_SystemClock)},
+        {"android.os.SystemProperties", REG_JNI(register_android_os_SystemProperties)},
+        {"android.os.Trace", REG_JNI(register_android_os_Trace)},
+        {"android.text.AndroidCharacter", REG_JNI(register_android_text_AndroidCharacter)},
+        {"android.util.EventLog", REG_JNI(register_android_util_EventLog)},
+        {"android.util.Log", REG_JNI(register_android_util_Log)},
+        {"android.util.PathParser", REG_JNI(register_android_util_PathParser)},
+        {"android.view.RenderNodeAnimator", REG_JNI(register_android_view_RenderNodeAnimator)},
+        {"com.android.internal.util.VirtualRefBasePtr",
+         REG_JNI(register_com_android_internal_util_VirtualRefBasePtr)},
+        {"com.android.internal.view.animation.NativeInterpolatorFactoryHelper",
+         REG_JNI(register_com_android_internal_view_animation_NativeInterpolatorFactoryHelper)},
 };
 // Vector to store the names of classes that need delegates of their native methods
 static vector<string> classesToDelegate;
@@ -159,7 +169,6 @@
 
 int AndroidRuntime::registerNativeMethods(JNIEnv* env,
         const char* className, const JNINativeMethod* gMethods, int numMethods) {
-
     string classNameString = string(className);
     if (find(classesToDelegate.begin(), classesToDelegate.end(), classNameString)
             != classesToDelegate.end()) {
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 7a30256..501e616 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 -->
diff --git a/core/tests/coretests/src/android/provider/DeviceConfigTest.java b/core/tests/coretests/src/android/provider/DeviceConfigTest.java
index 84c42db..d649b94 100644
--- a/core/tests/coretests/src/android/provider/DeviceConfigTest.java
+++ b/core/tests/coretests/src/android/provider/DeviceConfigTest.java
@@ -376,6 +376,24 @@
     }
 
     @Test
+    public void resetToDefault_makeDefault() {
+        DeviceConfig.setProperty(NAMESPACE, KEY, VALUE, true);
+        assertThat(DeviceConfig.getProperty(NAMESPACE, KEY)).isEqualTo(VALUE);
+
+        DeviceConfig.resetToDefaults(Settings.RESET_MODE_PACKAGE_DEFAULTS, NAMESPACE);
+        assertThat(DeviceConfig.getProperty(NAMESPACE, KEY)).isEqualTo(VALUE);
+    }
+
+    @Test
+    public void resetToDefault_doNotMakeDefault() {
+        DeviceConfig.setProperty(NAMESPACE, KEY, VALUE, false);
+        assertThat(DeviceConfig.getProperty(NAMESPACE, KEY)).isEqualTo(VALUE);
+
+        DeviceConfig.resetToDefaults(Settings.RESET_MODE_PACKAGE_DEFAULTS, NAMESPACE);
+        assertThat(DeviceConfig.getProperty(NAMESPACE, KEY)).isNull();
+    }
+
+    @Test
     public void getProperties_fullNamespace() {
         Properties properties = DeviceConfig.getProperties(NAMESPACE);
         assertThat(properties.getKeyset()).isEmpty();
diff --git a/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java b/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java
index dcc6b95..1290633 100644
--- a/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java
+++ b/identity/java/android/security/identity/CredstoreIdentityCredentialStore.java
@@ -38,6 +38,10 @@
         ICredentialStoreFactory storeFactory =
                 ICredentialStoreFactory.Stub.asInterface(
                     ServiceManager.getService("android.security.identity"));
+        if (storeFactory == null) {
+            // This can happen if credstore is not running or not installed.
+            return null;
+        }
 
         ICredentialStore credStore = null;
         try {
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index c1435d1e..0b5e005 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -429,7 +429,8 @@
 
     if (renderAhead) {
         presentTime = mCurrentFrameInfo->get(FrameInfoIndex::Vsync) +
-                (frameIntervalNanos * (renderAhead + 1));
+                (frameIntervalNanos * (renderAhead + 1)) - DeviceInfo::get()->getAppOffset() +
+                (frameIntervalNanos / 2);
     }
     native_window_set_buffers_timestamp(mNativeSurface->getNativeWindow(), presentTime);
 }
diff --git a/media/java/android/media/AudioRecordingConfiguration.java b/media/java/android/media/AudioRecordingConfiguration.java
index f3613d3..42841d1 100644
--- a/media/java/android/media/AudioRecordingConfiguration.java
+++ b/media/java/android/media/AudioRecordingConfiguration.java
@@ -18,6 +18,7 @@
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -229,13 +230,19 @@
      * <p>This information is only available if the caller has the
      * {@link android.Manifest.permission.MODIFY_AUDIO_ROUTING}
      * permission.
-     * <br>The result is -1 without the permission.
      * @return the user id
+     * @throws SecurityException Thrown if the caller is missing the MODIFY_AUDIO_ROUTING permission
      *
      * @hide
      */
     @SystemApi
-    public int getClientUid() { return mClientUid; }
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public int getClientUid() {
+        if (mClientUid == -1) {
+            throw new SecurityException("MODIFY_AUDIO_ROUTING permission is missing");
+        }
+        return mClientUid;
+    }
 
     /**
      * Returns information about the audio input device used for this recording.
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/DynamicSystemInstallationService/res/values/strings.xml b/packages/DynamicSystemInstallationService/res/values/strings.xml
index 9bd5be7..7595d2b 100644
--- a/packages/DynamicSystemInstallationService/res/values/strings.xml
+++ b/packages/DynamicSystemInstallationService/res/values/strings.xml
@@ -35,4 +35,7 @@
     <!-- Toast when we fail to launch into Dynamic System [CHAR LIMIT=64] -->
     <string name="toast_failed_to_reboot_to_dynsystem">Can\u2019t restart or load dynamic system</string>
 
+    <!-- URL of Dynamic System Key Revocation List [DO NOT TRANSLATE] -->
+    <string name="key_revocation_list_url" translatable="false">https://dl.google.com/developers/android/gsi/gsi-keyblacklist.json</string>
+
 </resources>
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
index 9ccb837..9bae223 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
@@ -46,6 +46,7 @@
 import android.app.Service;
 import android.content.Context;
 import android.content.Intent;
+import android.net.http.HttpResponseCache;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
@@ -60,6 +61,8 @@
 import android.util.Log;
 import android.widget.Toast;
 
+import java.io.File;
+import java.io.IOException;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 
@@ -146,10 +149,26 @@
         prepareNotification();
 
         mDynSystem = (DynamicSystemManager) getSystemService(Context.DYNAMIC_SYSTEM_SERVICE);
+
+        // Install an HttpResponseCache in the application cache directory so we can cache
+        // gsi key revocation list. The http(s) protocol handler uses this cache transparently.
+        // The cache size is chosen heuristically. Since we don't have too much traffic right now,
+        // a moderate size of 1MiB should be enough.
+        try {
+            File httpCacheDir = new File(getCacheDir(), "httpCache");
+            long httpCacheSize = 1 * 1024 * 1024; // 1 MiB
+            HttpResponseCache.install(httpCacheDir, httpCacheSize);
+        } catch (IOException e) {
+            Log.d(TAG, "HttpResponseCache.install() failed: " + e);
+        }
     }
 
     @Override
     public void onDestroy() {
+        HttpResponseCache cache = HttpResponseCache.getInstalled();
+        if (cache != null) {
+            cache.flush();
+        }
         // Cancel the persistent notification.
         mNM.cancel(NOTIFICATION_ID);
     }
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java
index 9aea0e7..438c435 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java
@@ -25,6 +25,8 @@
 import android.util.Log;
 import android.webkit.URLUtil;
 
+import org.json.JSONException;
+
 import java.io.BufferedInputStream;
 import java.io.File;
 import java.io.IOException;
@@ -100,7 +102,9 @@
     private final Context mContext;
     private final DynamicSystemManager mDynSystem;
     private final ProgressListener mListener;
+    private final boolean mIsNetworkUrl;
     private DynamicSystemManager.Session mInstallationSession;
+    private KeyRevocationList mKeyRevocationList;
 
     private boolean mIsZip;
     private boolean mIsCompleted;
@@ -123,6 +127,7 @@
         mContext = context;
         mDynSystem = dynSystem;
         mListener = listener;
+        mIsNetworkUrl = URLUtil.isNetworkUrl(mUrl);
     }
 
     @Override
@@ -152,9 +157,11 @@
                 return null;
             }
 
+            // TODO(yochiang): do post-install public key check (revocation list / boot-ramdisk)
+
             mDynSystem.finishInstallation();
         } catch (Exception e) {
-            e.printStackTrace();
+            Log.e(TAG, e.toString(), e);
             mDynSystem.remove();
             return e;
         } finally {
@@ -220,7 +227,7 @@
                 String.format(Locale.US, "Unsupported file format: %s", mUrl));
         }
 
-        if (URLUtil.isNetworkUrl(mUrl)) {
+        if (mIsNetworkUrl) {
             mStream = new URL(mUrl).openStream();
         } else if (URLUtil.isFileUrl(mUrl)) {
             if (mIsZip) {
@@ -234,6 +241,25 @@
             throw new UnsupportedUrlException(
                     String.format(Locale.US, "Unsupported URL: %s", mUrl));
         }
+
+        // TODO(yochiang): Bypass this check if device is unlocked
+        try {
+            String listUrl = mContext.getString(R.string.key_revocation_list_url);
+            mKeyRevocationList = KeyRevocationList.fromUrl(new URL(listUrl));
+        } catch (IOException | JSONException e) {
+            Log.d(TAG, "Failed to fetch Dynamic System Key Revocation List");
+            mKeyRevocationList = new KeyRevocationList();
+            keyRevocationThrowOrWarning(e);
+        }
+    }
+
+    private void keyRevocationThrowOrWarning(Exception e) throws Exception {
+        if (mIsNetworkUrl) {
+            throw e;
+        } else {
+            // If DSU is being installed from a local file URI, then be permissive
+            Log.w(TAG, e.toString());
+        }
     }
 
     private void installUserdata() throws Exception {
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/KeyRevocationList.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/KeyRevocationList.java
new file mode 100644
index 0000000..522bc54
--- /dev/null
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/KeyRevocationList.java
@@ -0,0 +1,148 @@
+/*
+ * 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.dynsystem;
+
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.HashMap;
+
+class KeyRevocationList {
+
+    private static final String TAG = "KeyRevocationList";
+
+    private static final String JSON_ENTRIES = "entries";
+    private static final String JSON_PUBLIC_KEY = "public_key";
+    private static final String JSON_STATUS = "status";
+    private static final String JSON_REASON = "reason";
+
+    private static final String STATUS_REVOKED = "REVOKED";
+
+    @VisibleForTesting
+    HashMap<String, RevocationStatus> mEntries;
+
+    static class RevocationStatus {
+        final String mStatus;
+        final String mReason;
+
+        RevocationStatus(String status, String reason) {
+            mStatus = status;
+            mReason = reason;
+        }
+    }
+
+    KeyRevocationList() {
+        mEntries = new HashMap<String, RevocationStatus>();
+    }
+
+    /**
+     * Returns the revocation status of a public key.
+     *
+     * @return a RevocationStatus for |publicKey|, null if |publicKey| doesn't exist.
+     */
+    RevocationStatus getRevocationStatusForKey(String publicKey) {
+        return mEntries.get(publicKey);
+    }
+
+    /** Test if a public key is revoked or not. */
+    boolean isRevoked(String publicKey) {
+        RevocationStatus entry = getRevocationStatusForKey(publicKey);
+        return entry != null && TextUtils.equals(entry.mStatus, STATUS_REVOKED);
+    }
+
+    @VisibleForTesting
+    void addEntry(String publicKey, String status, String reason) {
+        mEntries.put(publicKey, new RevocationStatus(status, reason));
+    }
+
+    /**
+     * Creates a KeyRevocationList from a JSON String.
+     *
+     * @param jsonString the revocation list, for example:
+     *     <pre>{@code
+     *      {
+     *        "entries": [
+     *          {
+     *            "public_key": "00fa2c6637c399afa893fe83d85f3569998707d5",
+     *            "status": "REVOKED",
+     *            "reason": "Revocation Reason"
+     *          }
+     *        ]
+     *      }
+     *     }</pre>
+     *
+     * @throws JSONException if |jsonString| is malformed.
+     */
+    static KeyRevocationList fromJsonString(String jsonString) throws JSONException {
+        JSONObject jsonObject = new JSONObject(jsonString);
+        KeyRevocationList list = new KeyRevocationList();
+        Log.d(TAG, "Begin of revocation list");
+        if (jsonObject.has(JSON_ENTRIES)) {
+            JSONArray entries = jsonObject.getJSONArray(JSON_ENTRIES);
+            for (int i = 0; i < entries.length(); ++i) {
+                JSONObject entry = entries.getJSONObject(i);
+                String publicKey = entry.getString(JSON_PUBLIC_KEY);
+                String status = entry.getString(JSON_STATUS);
+                String reason = entry.has(JSON_REASON) ? entry.getString(JSON_REASON) : "";
+                list.addEntry(publicKey, status, reason);
+                Log.d(TAG, "Revocation entry: " + entry.toString());
+            }
+        }
+        Log.d(TAG, "End of revocation list");
+        return list;
+    }
+
+    /**
+     * Creates a KeyRevocationList from a URL.
+     *
+     * @throws IOException if |url| is inaccessible.
+     * @throws JSONException if fetched content is malformed.
+     */
+    static KeyRevocationList fromUrl(URL url) throws IOException, JSONException {
+        Log.d(TAG, "Fetch from URL: " + url.toString());
+        // Force "conditional GET"
+        // Force validate the cached result with server each time, and use the cached result
+        // only if it is validated by server, else fetch new data from server.
+        // Ref: https://developer.android.com/reference/android/net/http/HttpResponseCache#force-a-network-response
+        URLConnection connection = url.openConnection();
+        connection.setUseCaches(true);
+        connection.addRequestProperty("Cache-Control", "max-age=0");
+        try (InputStream stream = connection.getInputStream()) {
+            return fromJsonString(readFully(stream));
+        }
+    }
+
+    private static String readFully(InputStream in) throws IOException {
+        int n;
+        byte[] buffer = new byte[4096];
+        StringBuilder builder = new StringBuilder();
+        while ((n = in.read(buffer, 0, 4096)) > -1) {
+            builder.append(new String(buffer, 0, n));
+        }
+        return builder.toString();
+    }
+}
diff --git a/packages/DynamicSystemInstallationService/tests/Android.bp b/packages/DynamicSystemInstallationService/tests/Android.bp
new file mode 100644
index 0000000..3bdf829
--- /dev/null
+++ b/packages/DynamicSystemInstallationService/tests/Android.bp
@@ -0,0 +1,15 @@
+android_test {
+    name: "DynamicSystemInstallationServiceTests",
+
+    srcs: ["src/**/*.java"],
+    static_libs: [
+        "androidx.test.runner",
+        "androidx.test.rules",
+        "mockito-target-minus-junit4",
+    ],
+
+    resource_dirs: ["res"],
+    platform_apis: true,
+    instrumentation_for: "DynamicSystemInstallationService",
+    certificate: "platform",
+}
diff --git a/packages/DynamicSystemInstallationService/tests/AndroidManifest.xml b/packages/DynamicSystemInstallationService/tests/AndroidManifest.xml
new file mode 100644
index 0000000..f5f0ae6
--- /dev/null
+++ b/packages/DynamicSystemInstallationService/tests/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.dynsystem.tests">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.dynsystem"
+        android:label="Tests for DynamicSystemInstallationService" />
+
+</manifest>
diff --git a/packages/DynamicSystemInstallationService/tests/res/values/strings.xml b/packages/DynamicSystemInstallationService/tests/res/values/strings.xml
new file mode 100644
index 0000000..fdb620b
--- /dev/null
+++ b/packages/DynamicSystemInstallationService/tests/res/values/strings.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <!-- testFromJsonString -->
+    <string name="blacklist_json_string" translatable="false">
+      {
+        \"entries\":[
+          {
+            \"public_key\":\"00fa2c6637c399afa893fe83d85f3569998707d5\",
+            \"status\":\"REVOKED\",
+            \"reason\":\"Key revocation test key\"
+          },
+          {
+            \"public_key\":\"key2\",
+            \"status\":\"REVOKED\"
+          }
+        ]
+      }
+    </string>
+</resources>
diff --git a/packages/DynamicSystemInstallationService/tests/src/com/android/dynsystem/KeyRevocationListTest.java b/packages/DynamicSystemInstallationService/tests/src/com/android/dynsystem/KeyRevocationListTest.java
new file mode 100644
index 0000000..82ce542
--- /dev/null
+++ b/packages/DynamicSystemInstallationService/tests/src/com/android/dynsystem/KeyRevocationListTest.java
@@ -0,0 +1,132 @@
+/*
+ * 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.dynsystem;
+
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+
+import android.content.Context;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.json.JSONException;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLStreamHandler;
+
+/**
+ * A test for KeyRevocationList.java
+ */
+@RunWith(AndroidJUnit4.class)
+public class KeyRevocationListTest {
+
+    private static final String TAG = "KeyRevocationListTest";
+
+    private static Context sContext;
+
+    private static String sBlacklistJsonString;
+
+    @BeforeClass
+    public static void setUpClass() throws Exception {
+        sContext = InstrumentationRegistry.getInstrumentation().getContext();
+        sBlacklistJsonString =
+                sContext.getString(com.android.dynsystem.tests.R.string.blacklist_json_string);
+    }
+
+    @Test
+    @SmallTest
+    public void testFromJsonString() throws JSONException {
+        KeyRevocationList blacklist;
+        blacklist = KeyRevocationList.fromJsonString(sBlacklistJsonString);
+        Assert.assertNotNull(blacklist);
+        Assert.assertFalse(blacklist.mEntries.isEmpty());
+        blacklist = KeyRevocationList.fromJsonString("{}");
+        Assert.assertNotNull(blacklist);
+        Assert.assertTrue(blacklist.mEntries.isEmpty());
+    }
+
+    @Test
+    @SmallTest
+    public void testFromUrl() throws IOException, JSONException {
+        URLConnection mockConnection = mock(URLConnection.class);
+        doReturn(new ByteArrayInputStream(sBlacklistJsonString.getBytes()))
+                .when(mockConnection).getInputStream();
+        URL mockUrl = new URL(
+                "http",     // protocol
+                "foo.bar",  // host
+                80,         // port
+                "baz",      // file
+                new URLStreamHandler() {
+                    @Override
+                    protected URLConnection openConnection(URL url) {
+                        return mockConnection;
+                    }
+                });
+        URL mockBadUrl = new URL(
+                "http",     // protocol
+                "foo.bar",  // host
+                80,         // port
+                "baz",      // file
+                new URLStreamHandler() {
+                    @Override
+                    protected URLConnection openConnection(URL url) throws IOException {
+                        throw new IOException();
+                    }
+                });
+
+        KeyRevocationList blacklist = KeyRevocationList.fromUrl(mockUrl);
+        Assert.assertNotNull(blacklist);
+        Assert.assertFalse(blacklist.mEntries.isEmpty());
+
+        blacklist = null;
+        try {
+            blacklist = KeyRevocationList.fromUrl(mockBadUrl);
+            // Up should throw, down should be unreachable
+            Assert.fail("Expected IOException not thrown");
+        } catch (IOException e) {
+            // This is expected, do nothing
+        }
+        Assert.assertNull(blacklist);
+    }
+
+    @Test
+    @SmallTest
+    public void testIsRevoked() {
+        KeyRevocationList blacklist = new KeyRevocationList();
+        blacklist.addEntry("key1", "REVOKED", "reason for key1");
+
+        KeyRevocationList.RevocationStatus revocationStatus =
+                blacklist.getRevocationStatusForKey("key1");
+        Assert.assertNotNull(revocationStatus);
+        Assert.assertEquals(revocationStatus.mReason, "reason for key1");
+
+        revocationStatus = blacklist.getRevocationStatusForKey("key2");
+        Assert.assertNull(revocationStatus);
+
+        Assert.assertTrue(blacklist.isRevoked("key1"));
+        Assert.assertFalse(blacklist.isRevoked("key2"));
+    }
+}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
index 9934e59..cd62420 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java
@@ -425,7 +425,8 @@
             }
             newState = oldState;
         } else {
-            newState = new Setting(name, value, makeDefault, packageName, tag);
+            newState = new Setting(name, value, makeDefault, packageName, tag,
+                    forceNonSystemPackage);
             mSettings.put(name, newState);
         }
 
@@ -1173,11 +1174,15 @@
 
         public Setting(String name, String value, boolean makeDefault, String packageName,
                 String tag) {
+            this(name, value, makeDefault, packageName, tag, false);
+        }
+
+        Setting(String name, String value, boolean makeDefault, String packageName,
+                String tag, boolean forceNonSystemPackage) {
             this.name = name;
             // overrideableByRestore = true as the first initialization isn't considered a
             // modification.
-            update(value, makeDefault, packageName, tag, false,
-                    /* overrideableByRestore */ true);
+            update(value, makeDefault, packageName, tag, forceNonSystemPackage, true);
         }
 
         public Setting(String name, String value, String defaultValue,
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/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index b8d32ae..5c65977 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -583,11 +583,13 @@
      * @return
      */
     public static WindowManager.LayoutParams getLayoutParams(IBinder windowToken) {
+        final int windowFlags = WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED
+                | WindowManager.LayoutParams.FLAG_SECURE;
         final WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
                 ViewGroup.LayoutParams.MATCH_PARENT,
                 ViewGroup.LayoutParams.MATCH_PARENT,
                 WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL,
-                WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED,
+                windowFlags,
                 PixelFormat.TRANSLUCENT);
         lp.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
         lp.setTitle("BiometricPrompt");
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/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/biometrics/AuthContainerViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java
index f264259..486aac8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.java
@@ -36,6 +36,7 @@
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricPrompt;
 import android.os.Bundle;
+import android.os.IBinder;
 import android.os.UserManager;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.testing.AndroidTestingRunner;
@@ -43,6 +44,7 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.WindowManager;
 import android.widget.FrameLayout;
 import android.widget.ImageView;
 import android.widget.ScrollView;
@@ -175,6 +177,14 @@
         assertEquals(Utils.CREDENTIAL_PATTERN, mAuthContainer.mCredentialView.mCredentialType);
     }
 
+    @Test
+    public void testLayoutParams_hasSecureWindowFlag() {
+        final IBinder windowToken = mock(IBinder.class);
+        final WindowManager.LayoutParams layoutParams =
+                AuthContainerView.getLayoutParams(windowToken);
+        assertTrue((layoutParams.flags & WindowManager.LayoutParams.FLAG_SECURE) != 0);
+    }
+
     private void initializeContainer(int authenticators) {
         AuthContainerView.Config config = new AuthContainerView.Config();
         config.mContext = mContext;
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/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index 1a4fc32..1cb9313 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -818,27 +818,26 @@
         }
     }
 
-    void logAugmentedAutofillSelected(int sessionId, @Nullable String suggestionId,
-            @Nullable Bundle clientState) {
+    void logAugmentedAutofillSelected(int sessionId, @Nullable String suggestionId) {
         synchronized (mLock) {
             if (mAugmentedAutofillEventHistory == null
                     || mAugmentedAutofillEventHistory.getSessionId() != sessionId) {
                 return;
             }
             mAugmentedAutofillEventHistory.addEvent(
-                    new Event(Event.TYPE_DATASET_SELECTED, suggestionId, clientState, null, null,
+                    new Event(Event.TYPE_DATASET_SELECTED, suggestionId, null, null, null,
                             null, null, null, null, null, null));
         }
     }
 
-    void logAugmentedAutofillShown(int sessionId, @Nullable Bundle clientState) {
+    void logAugmentedAutofillShown(int sessionId) {
         synchronized (mLock) {
             if (mAugmentedAutofillEventHistory == null
                     || mAugmentedAutofillEventHistory.getSessionId() != sessionId) {
                 return;
             }
             mAugmentedAutofillEventHistory.addEvent(
-                    new Event(Event.TYPE_DATASETS_SHOWN, null, clientState, null, null, null,
+                    new Event(Event.TYPE_DATASETS_SHOWN, null, null, null, null, null,
                             null, null, null, null, null));
 
         }
@@ -1227,16 +1226,15 @@
                         }
 
                         @Override
-                        public void logAugmentedAutofillShown(int sessionId, Bundle clientState) {
-                            AutofillManagerServiceImpl.this.logAugmentedAutofillShown(sessionId,
-                                    clientState);
+                        public void logAugmentedAutofillShown(int sessionId) {
+                            AutofillManagerServiceImpl.this.logAugmentedAutofillShown(sessionId);
                         }
 
                         @Override
-                        public void logAugmentedAutofillSelected(int sessionId, String suggestionId,
-                                Bundle clientState) {
+                        public void logAugmentedAutofillSelected(int sessionId,
+                                String suggestionId) {
                             AutofillManagerServiceImpl.this.logAugmentedAutofillSelected(sessionId,
-                                    suggestionId, clientState);
+                                    suggestionId);
                         }
 
                         @Override
diff --git a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
index 5e6f6fea..880c401 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteAugmentedAutofillService.java
@@ -55,6 +55,7 @@
 import com.android.internal.os.IResultReceiver;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.view.IInlineSuggestionsResponseCallback;
+import com.android.server.autofill.ui.InlineSuggestionFactory;
 
 import java.util.concurrent.CancellationException;
 import java.util.concurrent.TimeUnit;
@@ -144,7 +145,8 @@
             int taskId, @NonNull ComponentName activityComponent, @NonNull AutofillId focusedId,
             @Nullable AutofillValue focusedValue,
             @Nullable InlineSuggestionsRequest inlineSuggestionsRequest,
-            @Nullable IInlineSuggestionsResponseCallback inlineSuggestionsCallback) {
+            @Nullable IInlineSuggestionsResponseCallback inlineSuggestionsCallback,
+            @NonNull Runnable onErrorCallback) {
         long requestTime = SystemClock.elapsedRealtime();
         AtomicReference<ICancellationSignal> cancellationRef = new AtomicReference<>();
 
@@ -161,12 +163,12 @@
                             focusedId, focusedValue, requestTime, inlineSuggestionsRequest,
                             new IFillCallback.Stub() {
                                 @Override
-                                public void onSuccess(@Nullable Dataset[] inlineSuggestionsData,
-                                        @Nullable Bundle clientState) {
+                                public void onSuccess(@Nullable Dataset[] inlineSuggestionsData) {
                                     mCallbacks.resetLastResponse();
                                     maybeRequestShowInlineSuggestions(sessionId,
                                             inlineSuggestionsData, focusedId,
-                                            inlineSuggestionsCallback, client, clientState);
+                                            inlineSuggestionsCallback, client,
+                                            onErrorCallback);
                                     requestAutofill.complete(null);
                                 }
 
@@ -231,29 +233,31 @@
     private void maybeRequestShowInlineSuggestions(int sessionId,
             @Nullable Dataset[] inlineSuggestionsData, @NonNull AutofillId focusedId,
             @Nullable IInlineSuggestionsResponseCallback inlineSuggestionsCallback,
-            @NonNull IAutoFillManagerClient client, @Nullable Bundle clientState) {
+            @NonNull IAutoFillManagerClient client, @NonNull Runnable onErrorCallback) {
         if (ArrayUtils.isEmpty(inlineSuggestionsData) || inlineSuggestionsCallback == null) {
             return;
         }
         mCallbacks.setLastResponse(sessionId);
+
         try {
             inlineSuggestionsCallback.onInlineSuggestionsResponse(
                     InlineSuggestionFactory.createAugmentedInlineSuggestionsResponse(
                             inlineSuggestionsData, focusedId, mContext,
                             dataset -> {
                                 mCallbacks.logAugmentedAutofillSelected(sessionId,
-                                        dataset.getId(), clientState);
+                                        dataset.getId());
                                 try {
                                     client.autofill(sessionId, dataset.getFieldIds(),
                                             dataset.getFieldValues());
                                 } catch (RemoteException e) {
                                     Slog.w(TAG, "Encounter exception autofilling the values");
                                 }
-                            }));
+                            }, onErrorCallback));
         } catch (RemoteException e) {
             Slog.w(TAG, "Exception sending inline suggestions response back to IME.");
         }
-        mCallbacks.logAugmentedAutofillShown(sessionId, clientState);
+
+        mCallbacks.logAugmentedAutofillShown(sessionId);
     }
 
     @Override
@@ -275,9 +279,8 @@
 
         void setLastResponse(int sessionId);
 
-        void logAugmentedAutofillShown(int sessionId, @Nullable Bundle clientState);
+        void logAugmentedAutofillShown(int sessionId);
 
-        void logAugmentedAutofillSelected(int sessionId, @Nullable String suggestionId,
-                @Nullable Bundle clientState);
+        void logAugmentedAutofillSelected(int sessionId, @Nullable String suggestionId);
     }
 }
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 415ecd8..7e5123c 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -103,6 +103,7 @@
 import com.android.internal.view.IInlineSuggestionsRequestCallback;
 import com.android.internal.view.IInlineSuggestionsResponseCallback;
 import com.android.server.autofill.ui.AutoFillUI;
+import com.android.server.autofill.ui.InlineSuggestionFactory;
 import com.android.server.autofill.ui.PendingUi;
 import com.android.server.inputmethod.InputMethodManagerInternal;
 
@@ -2681,7 +2682,6 @@
             }
         }
 
-
         getUiForShowing().showFillUi(filledId, response, filterText,
                 mService.getServicePackageName(), mComponentName,
                 serviceLabel, serviceIcon, this, id, mCompatMode);
@@ -2733,7 +2733,11 @@
 
         InlineSuggestionsResponse inlineSuggestionsResponse =
                 InlineSuggestionFactory.createInlineSuggestionsResponse(response.getRequestId(),
-                        datasets.toArray(new Dataset[]{}), mCurrentViewId, mContext, this);
+                        datasets.toArray(new Dataset[]{}), mCurrentViewId, mContext, this, () -> {
+                    synchronized (mLock) {
+                        requestHideFillUi(mCurrentViewId);
+                    }
+                });
         try  {
             inlineContentCallback.onInlineSuggestionsResponse(inlineSuggestionsResponse);
         } catch (RemoteException e) {
@@ -3024,7 +3028,11 @@
                 mInlineSuggestionsRequestCallback != null
                         ? mInlineSuggestionsRequestCallback.getResponseCallback() : null;
         remoteService.onRequestAutofillLocked(id, mClient, taskId, mComponentName, focusedId,
-                currentValue, inlineSuggestionsRequest, inlineSuggestionsResponseCallback);
+                currentValue, inlineSuggestionsRequest, inlineSuggestionsResponseCallback, () -> {
+                    synchronized (mLock) {
+                        cancelAugmentedAutofillLocked();
+                    }
+                });
 
         if (mAugmentedAutofillDestroyer == null) {
             mAugmentedAutofillDestroyer = () -> remoteService.onDestroyAutofillWindowsRequest();
diff --git a/services/autofill/java/com/android/server/autofill/InlineSuggestionFactory.java b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java
similarity index 80%
rename from services/autofill/java/com/android/server/autofill/InlineSuggestionFactory.java
rename to services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java
index cb6c8f5..38a5b5b 100644
--- a/services/autofill/java/com/android/server/autofill/InlineSuggestionFactory.java
+++ b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionFactory.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.autofill;
+package com.android.server.autofill.ui;
 
 import static com.android.server.autofill.Helper.sDebug;
 
@@ -32,18 +32,13 @@
 import android.view.inputmethod.InlineSuggestionInfo;
 import android.view.inputmethod.InlineSuggestionsResponse;
 
+import com.android.internal.util.function.QuadFunction;
 import com.android.internal.view.inline.IInlineContentCallback;
 import com.android.internal.view.inline.IInlineContentProvider;
 import com.android.server.UiThread;
-import com.android.server.autofill.ui.AutoFillUI;
-import com.android.server.autofill.ui.InlineSuggestionUi;
 
 import java.util.ArrayList;
 
-
-/**
- * @hide
- */
 public final class InlineSuggestionFactory {
     private static final String TAG = "InlineSuggestionFactory";
 
@@ -65,28 +60,12 @@
             @NonNull Dataset[] datasets,
             @NonNull AutofillId autofillId,
             @NonNull Context context,
-            @NonNull InlineSuggestionUiCallback inlineSuggestionUiCallback) {
-        if (sDebug) Slog.d(TAG, "createAugmentedInlineSuggestionsResponse called");
-
-        final ArrayList<InlineSuggestion> inlineSuggestions = new ArrayList<>();
-        final InlineSuggestionUi inlineSuggestionUi = new InlineSuggestionUi(context);
-        for (Dataset dataset : datasets) {
-            final int fieldIndex = dataset.getFieldIds().indexOf(autofillId);
-            if (fieldIndex < 0) {
-                Slog.w(TAG, "AutofillId=" + autofillId + " not found in dataset");
-                return null;
-            }
-            final InlinePresentation inlinePresentation = dataset.getFieldInlinePresentation(
-                    fieldIndex);
-            if (inlinePresentation == null) {
-                Slog.w(TAG, "InlinePresentation not found in dataset");
-                return null;
-            }
-            InlineSuggestion inlineSuggestion = createAugmentedInlineSuggestion(dataset,
-                    inlinePresentation, inlineSuggestionUi, inlineSuggestionUiCallback);
-            inlineSuggestions.add(inlineSuggestion);
-        }
-        return new InlineSuggestionsResponse(inlineSuggestions);
+            @NonNull InlineSuggestionUiCallback inlineSuggestionUiCallback,
+            @NonNull Runnable onErrorCallback) {
+        return createInlineSuggestionsResponseInternal(datasets, autofillId,
+                context, onErrorCallback, (dataset, inlinePresentation, inlineSuggestionUi,
+                        filedIndex) -> createAugmentedInlineSuggestion(dataset,
+                    inlinePresentation, inlineSuggestionUi, inlineSuggestionUiCallback));
     }
 
     /**
@@ -97,11 +76,26 @@
             @NonNull Dataset[] datasets,
             @NonNull AutofillId autofillId,
             @NonNull Context context,
-            @NonNull AutoFillUI.AutoFillUiCallback client) {
-        if (sDebug) Slog.d(TAG, "createInlineSuggestionsResponse called");
+            @NonNull AutoFillUI.AutoFillUiCallback client,
+            @NonNull Runnable onErrorCallback) {
+        return createInlineSuggestionsResponseInternal(datasets, autofillId,
+                context, onErrorCallback, (dataset, inlinePresentation, inlineSuggestionUi,
+                        filedIndex) -> createInlineSuggestion(requestId, dataset, filedIndex,
+                        inlinePresentation, inlineSuggestionUi, client));
+    }
+
+    private static InlineSuggestionsResponse createInlineSuggestionsResponseInternal(
+            @NonNull Dataset[] datasets,
+            @NonNull AutofillId autofillId,
+            @NonNull Context context,
+            @NonNull Runnable onErrorCallback,
+            @NonNull QuadFunction<Dataset, InlinePresentation, InlineSuggestionUi,
+                    Integer, InlineSuggestion> suggestionFactory) {
+        if (sDebug) Slog.d(TAG, "createAugmentedInlineSuggestionsResponse called");
 
         final ArrayList<InlineSuggestion> inlineSuggestions = new ArrayList<>();
-        final InlineSuggestionUi inlineSuggestionUi = new InlineSuggestionUi(context);
+        final InlineSuggestionUi inlineSuggestionUi = new InlineSuggestionUi(context,
+                onErrorCallback);
         for (Dataset dataset : datasets) {
             final int fieldIndex = dataset.getFieldIds().indexOf(autofillId);
             if (fieldIndex < 0) {
@@ -114,9 +108,8 @@
                 Slog.w(TAG, "InlinePresentation not found in dataset");
                 return null;
             }
-            InlineSuggestion inlineSuggestion = createInlineSuggestion(requestId, dataset,
-                    fieldIndex,
-                    inlinePresentation, inlineSuggestionUi, client);
+            InlineSuggestion inlineSuggestion = suggestionFactory.apply(dataset,
+                    inlinePresentation, inlineSuggestionUi, fieldIndex);
             inlineSuggestions.add(inlineSuggestion);
         }
         return new InlineSuggestionsResponse(inlineSuggestions);
@@ -131,9 +124,8 @@
                 inlinePresentation.getInlinePresentationSpec(),
                 InlineSuggestionInfo.SOURCE_PLATFORM, new String[]{""},
                 InlineSuggestionInfo.TYPE_SUGGESTION);
-        final View.OnClickListener onClickListener = v -> {
+        final View.OnClickListener onClickListener = v ->
             inlineSuggestionUiCallback.autofill(dataset);
-        };
         final InlineSuggestion inlineSuggestion = new InlineSuggestion(inlineSuggestionInfo,
                 createInlineContentProvider(inlinePresentation, inlineSuggestionUi,
                         onClickListener));
diff --git a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionRoot.java b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionRoot.java
new file mode 100644
index 0000000..8d476d7
--- /dev/null
+++ b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionRoot.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.autofill.ui;
+
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.util.Log;
+import android.util.MathUtils;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+import android.widget.FrameLayout;
+
+import com.android.server.LocalServices;
+import com.android.server.wm.WindowManagerInternal;
+
+/**
+ * This class is the root view for an inline suggestion. It is responsible for
+ * detecting the click on the item and to also transfer input focus to the IME
+ * window if we detect the user is scrolling.
+ */
+ // TODO(b/146453086) Move to ExtServices and add @SystemApi to transfer touch focus
+@SuppressLint("ViewConstructor")
+class InlineSuggestionRoot extends FrameLayout {
+    private static final String LOG_TAG = InlineSuggestionRoot.class.getSimpleName();
+
+    private final @NonNull Runnable mOnErrorCallback;
+    private final int mTouchSlop;
+
+    private float mDownX;
+    private float mDownY;
+
+    InlineSuggestionRoot(@NonNull Context context, @NonNull Runnable onErrorCallback) {
+        super(context);
+        mOnErrorCallback = onErrorCallback;
+        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
+    }
+
+    @Override
+    @SuppressLint("ClickableViewAccessibility")
+    public boolean onTouchEvent(@NonNull MotionEvent event) {
+        switch (event.getActionMasked()) {
+            case MotionEvent.ACTION_DOWN: {
+                mDownX = event.getX();
+                mDownY = event.getY();
+            } break;
+
+            case MotionEvent.ACTION_MOVE: {
+                final float distance = MathUtils.dist(mDownX, mDownY,
+                        event.getX(), event.getY());
+                if (distance > mTouchSlop) {
+                    transferTouchFocusToImeWindow();
+                }
+            } break;
+        }
+        return super.onTouchEvent(event);
+    }
+
+    private void transferTouchFocusToImeWindow() {
+        final WindowManagerInternal windowManagerInternal = LocalServices.getService(
+                WindowManagerInternal.class);
+        if (!windowManagerInternal.transferTouchFocusToImeWindow(getViewRootImpl().getInputToken(),
+                getContext().getDisplayId())) {
+            Log.e(LOG_TAG, "Cannot transfer touch focus from suggestion to IME");
+            mOnErrorCallback.run();
+        }
+    }
+}
diff --git a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionUi.java b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionUi.java
index 2adefea..bf148a6 100644
--- a/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/InlineSuggestionUi.java
@@ -67,10 +67,12 @@
     // (int)}. This name is a single string of the form "package:type/entry".
     private static final Pattern RESOURCE_NAME_PATTERN = Pattern.compile("([^:]+):([^/]+)/(\\S+)");
 
-    private final Context mContext;
+    private final @NonNull Context mContext;
+    private final @NonNull Runnable mOnErrorCallback;
 
-    public InlineSuggestionUi(Context context) {
+    InlineSuggestionUi(@NonNull Context context, @NonNull Runnable onErrorCallback) {
         this.mContext = context;
+        mOnErrorCallback = onErrorCallback;
     }
 
     /**
@@ -94,15 +96,17 @@
         }
         final View suggestionView = renderSlice(inlinePresentation.getSlice(),
                 contextThemeWrapper);
-        if (onClickListener != null) {
-            suggestionView.setOnClickListener(onClickListener);
-        }
+
+        final InlineSuggestionRoot suggestionRoot = new InlineSuggestionRoot(
+                mContext, mOnErrorCallback);
+        suggestionRoot.addView(suggestionView);
+        suggestionRoot.setOnClickListener(onClickListener);
 
         WindowManager.LayoutParams lp =
                 new WindowManager.LayoutParams(width, height,
                         WindowManager.LayoutParams.TYPE_APPLICATION, 0,
                         PixelFormat.TRANSPARENT);
-        wvr.addView(suggestionView, lp);
+        wvr.addView(suggestionRoot, lp);
         return sc;
     }
 
diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java
index d0cd7ff..ec3dbe9 100644
--- a/services/core/java/com/android/server/ConnectivityService.java
+++ b/services/core/java/com/android/server/ConnectivityService.java
@@ -66,6 +66,7 @@
 import android.net.CaptivePortal;
 import android.net.ConnectionInfo;
 import android.net.ConnectivityDiagnosticsManager.ConnectivityReport;
+import android.net.ConnectivityDiagnosticsManager.DataStallReport;
 import android.net.ConnectivityManager;
 import android.net.ICaptivePortal;
 import android.net.IConnectivityDiagnosticsCallback;
@@ -3016,6 +3017,21 @@
         }
 
         @Override
+        public void notifyDataStallSuspected(
+                long timestampMillis, int detectionMethod, PersistableBundle extras) {
+            final Message msg =
+                    mConnectivityDiagnosticsHandler.obtainMessage(
+                            ConnectivityDiagnosticsHandler.EVENT_DATA_STALL_SUSPECTED,
+                            detectionMethod, mNetId, timestampMillis);
+            msg.setData(new Bundle(extras));
+
+            // NetworkStateTrackerHandler currently doesn't take any actions based on data
+            // stalls so send the message directly to ConnectivityDiagnosticsHandler and avoid
+            // the cost of going through two handlers.
+            mConnectivityDiagnosticsHandler.sendMessage(msg);
+        }
+
+        @Override
         public int getInterfaceVersion() {
             return this.VERSION;
         }
@@ -4189,6 +4205,19 @@
         final int connectivityInfo = encodeBool(hasConnectivity);
         mHandler.sendMessage(
                 mHandler.obtainMessage(EVENT_REVALIDATE_NETWORK, uid, connectivityInfo, network));
+
+        final NetworkAgentInfo nai;
+        if (network == null) {
+            nai = getDefaultNetwork();
+        } else {
+            nai = getNetworkAgentInfoForNetwork(network);
+        }
+        if (nai != null) {
+            mConnectivityDiagnosticsHandler.sendMessage(
+                    mConnectivityDiagnosticsHandler.obtainMessage(
+                            ConnectivityDiagnosticsHandler.EVENT_NETWORK_CONNECTIVITY_REPORTED,
+                            connectivityInfo, 0, nai));
+        }
     }
 
     private void handleReportNetworkConnectivity(
@@ -7519,6 +7548,8 @@
      */
     @VisibleForTesting
     class ConnectivityDiagnosticsHandler extends Handler {
+        private final String mTag = ConnectivityDiagnosticsHandler.class.getSimpleName();
+
         /**
          * Used to handle ConnectivityDiagnosticsCallback registration events from {@link
          * android.net.ConnectivityDiagnosticsManager}.
@@ -7546,6 +7577,26 @@
          */
         private static final int EVENT_NETWORK_TESTED = ConnectivityService.EVENT_NETWORK_TESTED;
 
+        /**
+         * Event for NetworkMonitor to inform ConnectivityService that a potential data stall has
+         * been detected on the network.
+         * obj = Long the timestamp (in millis) for when the suspected data stall was detected.
+         * arg1 = {@link DataStallReport#DetectionMethod} indicating the detection method.
+         * arg2 = NetID.
+         * data = PersistableBundle of extras passed from NetworkMonitor.
+         */
+        private static final int EVENT_DATA_STALL_SUSPECTED = 4;
+
+        /**
+         * Event for ConnectivityDiagnosticsHandler to handle network connectivity being reported to
+         * the platform. This event will invoke {@link
+         * IConnectivityDiagnosticsCallback#onNetworkConnectivityReported} for permissioned
+         * callbacks.
+         * obj = Network that was reported on
+         * arg1 = boolint for the quality reported
+         */
+        private static final int EVENT_NETWORK_CONNECTIVITY_REPORTED = 5;
+
         private ConnectivityDiagnosticsHandler(Looper looper) {
             super(looper);
         }
@@ -7576,6 +7627,24 @@
                     handleNetworkTestedWithExtras(reportEvent, extras);
                     break;
                 }
+                case EVENT_DATA_STALL_SUSPECTED: {
+                    final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(msg.arg2);
+                    if (nai == null) break;
+
+                    // This is safe because NetworkMonitorCallbacks#notifyDataStallSuspected
+                    // receives a PersistableBundle and converts it to the Bundle in the incoming
+                    // Message.
+                    final PersistableBundle extras = new PersistableBundle(msg.getData());
+                    handleDataStallSuspected(nai, (long) msg.obj, msg.arg1, extras);
+                    break;
+                }
+                case EVENT_NETWORK_CONNECTIVITY_REPORTED: {
+                    handleNetworkConnectivityReported((NetworkAgentInfo) msg.obj, toBool(msg.arg1));
+                    break;
+                }
+                default: {
+                    Log.e(mTag, "Unrecognized event in ConnectivityDiagnostics: " + msg.what);
+                }
             }
         }
     }
@@ -7705,6 +7774,35 @@
         }
     }
 
+    private void handleDataStallSuspected(
+            @NonNull NetworkAgentInfo nai, long timestampMillis, int detectionMethod,
+            @NonNull PersistableBundle extras) {
+        final DataStallReport report =
+                new DataStallReport(nai.network, timestampMillis, detectionMethod, extras);
+        final List<IConnectivityDiagnosticsCallback> results =
+                getMatchingPermissionedCallbacks(nai);
+        for (final IConnectivityDiagnosticsCallback cb : results) {
+            try {
+                cb.onDataStallSuspected(report);
+            } catch (RemoteException ex) {
+                loge("Error invoking onDataStallSuspected", ex);
+            }
+        }
+    }
+
+    private void handleNetworkConnectivityReported(
+            @NonNull NetworkAgentInfo nai, boolean connectivity) {
+        final List<IConnectivityDiagnosticsCallback> results =
+                getMatchingPermissionedCallbacks(nai);
+        for (final IConnectivityDiagnosticsCallback cb : results) {
+            try {
+                cb.onNetworkConnectivityReported(nai.network, connectivity);
+            } catch (RemoteException ex) {
+                loge("Error invoking onNetworkConnectivityReported", ex);
+            }
+        }
+    }
+
     private List<IConnectivityDiagnosticsCallback> getMatchingPermissionedCallbacks(
             @NonNull NetworkAgentInfo nai) {
         final List<IConnectivityDiagnosticsCallback> results = new ArrayList<>();
diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java
index 4a65a96..fabe92db 100644
--- a/services/core/java/com/android/server/am/ActivityManagerConstants.java
+++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java
@@ -287,7 +287,7 @@
     // When the restriction is enabled, foreground service started from background will not have
     // while-in-use permissions like location, camera and microphone. (The foreground service can be
     // started, the restriction is on while-in-use permissions.)
-    volatile boolean mFlagBackgroundFgsStartRestrictionEnabled;
+    volatile boolean mFlagBackgroundFgsStartRestrictionEnabled = true;
 
     private final ActivityManagerService mService;
     private ContentResolver mResolver;
@@ -304,18 +304,19 @@
     // we have no limit on the number of service, visible, foreground, or other such
     // processes and the number of those processes does not count against the cached
     // process limit.
-    public int CUR_MAX_CACHED_PROCESSES;
+    public int CUR_MAX_CACHED_PROCESSES = DEFAULT_MAX_CACHED_PROCESSES;
 
     // The maximum number of empty app processes we will let sit around.
-    public int CUR_MAX_EMPTY_PROCESSES;
+    public int CUR_MAX_EMPTY_PROCESSES = computeEmptyProcessLimit(CUR_MAX_CACHED_PROCESSES);
 
     // The number of empty apps at which we don't consider it necessary to do
     // memory trimming.
-    public int CUR_TRIM_EMPTY_PROCESSES;
+    public int CUR_TRIM_EMPTY_PROCESSES = computeEmptyProcessLimit(MAX_CACHED_PROCESSES) / 2;
 
     // The number of cached at which we don't consider it necessary to do
     // memory trimming.
-    public int CUR_TRIM_CACHED_PROCESSES;
+    public int CUR_TRIM_CACHED_PROCESSES =
+            (MAX_CACHED_PROCESSES - computeEmptyProcessLimit(MAX_CACHED_PROCESSES)) / 3;
 
     /**
      * Packages that can't be killed even if it's requested to be killed on imperceptible.
@@ -419,6 +420,8 @@
                 context.getResources().getIntArray(
                 com.android.internal.R.array.config_defaultImperceptibleKillingExemptionProcStates))
                 .boxed().collect(Collectors.toList());
+        IMPERCEPTIBLE_KILL_EXEMPT_PACKAGES.addAll(mDefaultImperceptibleKillExemptPackages);
+        IMPERCEPTIBLE_KILL_EXEMPT_PROC_STATES.addAll(mDefaultImperceptibleKillExemptProcStates);
     }
 
     public void start(ContentResolver resolver) {
@@ -550,8 +553,6 @@
 
             // For new flags that are intended for server-side experiments, please use the new
             // DeviceConfig package.
-
-            updateMaxCachedProcesses();
         }
     }
 
@@ -580,6 +581,9 @@
     }
 
     private void updateOomAdjUpdatePolicy() {
+
+
+
         OOMADJ_UPDATE_QUICK = DeviceConfig.getInt(
                 DeviceConfig.NAMESPACE_ACTIVITY_MANAGER,
                 KEY_OOMADJ_UPDATE_POLICY,
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/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/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index 784ce4a..a0589c5 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -2126,24 +2126,27 @@
             UserHandle user = UserHandle.getUserHandleForUid(uid);
             boolean isRevokedCompat;
             if (permissionInfo.backgroundPermission != null) {
-                boolean isBackgroundRevokedCompat = mode != AppOpsManager.MODE_ALLOWED;
+                if (packageManager.checkPermission(permissionInfo.backgroundPermission, packageName)
+                        == PackageManager.PERMISSION_GRANTED) {
+                    boolean isBackgroundRevokedCompat = mode != AppOpsManager.MODE_ALLOWED;
 
-                if (isBackgroundRevokedCompat && supportsRuntimePermissions) {
-                    Slog.w(TAG, "setUidMode() called with a mode inconsistent with runtime"
-                            + " permission state, this is discouraged and you should revoke the"
-                            + " runtime permission instead: uid=" + uid + ", switchCode="
-                            + switchCode + ", mode=" + mode + ", permission="
-                            + permissionInfo.backgroundPermission);
-                }
+                    if (isBackgroundRevokedCompat && supportsRuntimePermissions) {
+                        Slog.w(TAG, "setUidMode() called with a mode inconsistent with runtime"
+                                + " permission state, this is discouraged and you should revoke the"
+                                + " runtime permission instead: uid=" + uid + ", switchCode="
+                                + switchCode + ", mode=" + mode + ", permission="
+                                + permissionInfo.backgroundPermission);
+                    }
 
-                long identity = Binder.clearCallingIdentity();
-                try {
-                    packageManager.updatePermissionFlags(permissionInfo.backgroundPermission,
-                            packageName, PackageManager.FLAG_PERMISSION_REVOKED_COMPAT,
-                            isBackgroundRevokedCompat
-                                    ? PackageManager.FLAG_PERMISSION_REVOKED_COMPAT : 0, user);
-                } finally {
-                    Binder.restoreCallingIdentity(identity);
+                    long identity = Binder.clearCallingIdentity();
+                    try {
+                        packageManager.updatePermissionFlags(permissionInfo.backgroundPermission,
+                                packageName, PackageManager.FLAG_PERMISSION_REVOKED_COMPAT,
+                                isBackgroundRevokedCompat
+                                        ? PackageManager.FLAG_PERMISSION_REVOKED_COMPAT : 0, user);
+                    } finally {
+                        Binder.restoreCallingIdentity(identity);
+                    }
                 }
 
                 isRevokedCompat = mode != AppOpsManager.MODE_ALLOWED
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index b2548af..82a2f01 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -3406,13 +3406,11 @@
         }
 
         public void binderDied() {
-            int oldModeOwnerPid = 0;
+            int oldModeOwnerPid;
             int newModeOwnerPid = 0;
             synchronized (mDeviceBroker.mSetModeLock) {
                 Log.w(TAG, "setMode() client died");
-                if (!mSetModeDeathHandlers.isEmpty()) {
-                    oldModeOwnerPid = mSetModeDeathHandlers.get(0).getPid();
-                }
+                oldModeOwnerPid = getModeOwnerPid();
                 int index = mSetModeDeathHandlers.indexOf(this);
                 if (index < 0) {
                     Log.w(TAG, "unregistered setMode() client died");
@@ -3446,17 +3444,19 @@
 
     /** @see AudioManager#setMode(int) */
     public void setMode(int mode, IBinder cb, String callingPackage) {
-        if (DEBUG_MODE) { Log.v(TAG, "setMode(mode=" + mode + ", callingPackage=" + callingPackage + ")"); }
+        if (DEBUG_MODE) {
+            Log.v(TAG, "setMode(mode=" + mode + ", callingPackage=" + callingPackage + ")");
+        }
         if (!checkAudioSettingsPermission("setMode()")) {
             return;
         }
-
-        if ( (mode == AudioSystem.MODE_IN_CALL) &&
-                (mContext.checkCallingOrSelfPermission(
+        final boolean hasModifyPhoneStatePermission = mContext.checkCallingOrSelfPermission(
                         android.Manifest.permission.MODIFY_PHONE_STATE)
-                            != PackageManager.PERMISSION_GRANTED)) {
+                        == PackageManager.PERMISSION_GRANTED;
+        final int callingPid = Binder.getCallingPid();
+        if ((mode == AudioSystem.MODE_IN_CALL) && !hasModifyPhoneStatePermission) {
             Log.w(TAG, "MODIFY_PHONE_STATE Permission Denial: setMode(MODE_IN_CALL) from pid="
-                    + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid());
+                    + callingPid + ", uid=" + Binder.getCallingUid());
             return;
         }
 
@@ -3470,16 +3470,25 @@
             return;
         }
 
-        int oldModeOwnerPid = 0;
-        int newModeOwnerPid = 0;
+        int oldModeOwnerPid;
+        int newModeOwnerPid;
         synchronized (mDeviceBroker.mSetModeLock) {
-            if (!mSetModeDeathHandlers.isEmpty()) {
-                oldModeOwnerPid = mSetModeDeathHandlers.get(0).getPid();
-            }
             if (mode == AudioSystem.MODE_CURRENT) {
                 mode = mMode;
             }
-            newModeOwnerPid = setModeInt(mode, cb, Binder.getCallingPid(), callingPackage);
+            oldModeOwnerPid = getModeOwnerPid();
+            // Do not allow changing mode if a call is active and the requester
+            // does not have permission to modify phone state or is not the mode owner.
+            if (((mMode == AudioSystem.MODE_IN_CALL)
+                    || (mMode == AudioSystem.MODE_IN_COMMUNICATION))
+                    && !(hasModifyPhoneStatePermission || (oldModeOwnerPid == callingPid))) {
+                Log.w(TAG, "setMode(" + mode + ") from pid=" + callingPid
+                        + ", uid=" + Binder.getCallingUid()
+                        + ", cannot change mode from " + mMode
+                        + " without permission or being mode owner");
+                return;
+            }
+            newModeOwnerPid = setModeInt(mode, cb, callingPid, callingPackage);
         }
         // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all
         // SCO connections not started by the application changing the mode when pid changes
@@ -3569,10 +3578,9 @@
 
         if (status == AudioSystem.AUDIO_STATUS_OK) {
             if (actualMode != AudioSystem.MODE_NORMAL) {
-                if (mSetModeDeathHandlers.isEmpty()) {
+                newModeOwnerPid = getModeOwnerPid();
+                if (newModeOwnerPid == 0) {
                     Log.e(TAG, "setMode() different from MODE_NORMAL with empty mode client stack");
-                } else {
-                    newModeOwnerPid = mSetModeDeathHandlers.get(0).getPid();
                 }
             }
             // Note: newModeOwnerPid is always 0 when actualMode is MODE_NORMAL
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/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 90358ca..a8f706c 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -223,7 +223,7 @@
             int displayId, InputApplicationHandle application);
     private static native void nativeSetFocusedDisplay(long ptr, int displayId);
     private static native boolean nativeTransferTouchFocus(long ptr,
-            InputChannel fromChannel, InputChannel toChannel);
+            IBinder fromChannelToken, IBinder toChannelToken);
     private static native void nativeSetPointerSpeed(long ptr, int speed);
     private static native void nativeSetShowTouches(long ptr, boolean enabled);
     private static native void nativeSetInteractive(long ptr, boolean interactive);
@@ -1554,14 +1554,29 @@
      * @return True if the transfer was successful.  False if the window with the
      * specified channel did not actually have touch focus at the time of the request.
      */
-    public boolean transferTouchFocus(InputChannel fromChannel, InputChannel toChannel) {
-        if (fromChannel == null) {
-            throw new IllegalArgumentException("fromChannel must not be null.");
-        }
-        if (toChannel == null) {
-            throw new IllegalArgumentException("toChannel must not be null.");
-        }
-        return nativeTransferTouchFocus(mPtr, fromChannel, toChannel);
+    public boolean transferTouchFocus(@NonNull InputChannel fromChannel,
+            @NonNull InputChannel toChannel) {
+        return nativeTransferTouchFocus(mPtr, fromChannel.getToken(), toChannel.getToken());
+    }
+
+    /**
+     * Atomically transfers touch focus from one window to another as identified by
+     * their input channels.  It is possible for multiple windows to have
+     * touch focus if they support split touch dispatch
+     * {@link android.view.WindowManager.LayoutParams#FLAG_SPLIT_TOUCH} but this
+     * method only transfers touch focus of the specified window without affecting
+     * other windows that may also have touch focus at the same time.
+     * @param fromChannelToken The channel token of a window that currently has touch focus.
+     * @param toChannelToken The channel token of the window that should receive touch focus in
+     * place of the first.
+     * @return True if the transfer was successful.  False if the window with the
+     * specified channel did not actually have touch focus at the time of the request.
+     */
+    public boolean transferTouchFocus(@NonNull IBinder fromChannelToken,
+            @NonNull IBinder toChannelToken) {
+        Objects.nonNull(fromChannelToken);
+        Objects.nonNull(toChannelToken);
+        return nativeTransferTouchFocus(mPtr, fromChannelToken, toChannelToken);
     }
 
     @Override // Binder call
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index f6276fb..b52289e 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -5387,8 +5387,8 @@
         try {
             fixNotification(notification, pkg, tag, id, userId);
 
-        } catch (NameNotFoundException e) {
-            Slog.e(TAG, "Cannot create a context for sending app", e);
+        } catch (Exception e) {
+            Slog.e(TAG, "Cannot fix notification", e);
             return;
         }
 
@@ -7594,7 +7594,7 @@
             for (int i = 0; i < newUris.size(); i++) {
                 final Uri uri = newUris.valueAt(i);
                 if (oldUris == null || !oldUris.contains(uri)) {
-                    if (DBG) Slog.d(TAG, key + ": granting " + uri);
+                    Slog.d(TAG, key + ": granting " + uri);
                     grantUriPermission(permissionOwner, uri, newRecord.getUid(), targetPkg,
                             targetUserId);
                 }
@@ -7631,6 +7631,8 @@
                     targetUserId);
         } catch (RemoteException ignored) {
             // Ignored because we're in same process
+        } catch (SecurityException e) {
+            Slog.e(TAG, "Cannot grant uri access; " + sourceUid + " does not own " + uri);
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
@@ -9010,59 +9012,64 @@
         @GuardedBy("mNotificationLock")
         private void notifyPostedLocked(NotificationRecord r, NotificationRecord old,
                 boolean notifyAllListeners) {
-            // Lazily initialized snapshots of the notification.
-            StatusBarNotification sbn = r.getSbn();
-            StatusBarNotification oldSbn = (old != null) ? old.getSbn() : null;
-            TrimCache trimCache = new TrimCache(sbn);
+            try {
+                // Lazily initialized snapshots of the notification.
+                StatusBarNotification sbn = r.getSbn();
+                StatusBarNotification oldSbn = (old != null) ? old.getSbn() : null;
+                TrimCache trimCache = new TrimCache(sbn);
 
-            for (final ManagedServiceInfo info : getServices()) {
-                boolean sbnVisible = isVisibleToListener(sbn, info);
-                boolean oldSbnVisible = oldSbn != null ? isVisibleToListener(oldSbn, info) : false;
-                // This notification hasn't been and still isn't visible -> ignore.
-                if (!oldSbnVisible && !sbnVisible) {
-                    continue;
-                }
-                // If the notification is hidden, don't notifyPosted listeners targeting < P.
-                // Instead, those listeners will receive notifyPosted when the notification is
-                // unhidden.
-                if (r.isHidden() && info.targetSdkVersion < Build.VERSION_CODES.P) {
-                    continue;
-                }
+                for (final ManagedServiceInfo info : getServices()) {
+                    boolean sbnVisible = isVisibleToListener(sbn, info);
+                    boolean oldSbnVisible = oldSbn != null ? isVisibleToListener(oldSbn, info)
+                            : false;
+                    // This notification hasn't been and still isn't visible -> ignore.
+                    if (!oldSbnVisible && !sbnVisible) {
+                        continue;
+                    }
+                    // If the notification is hidden, don't notifyPosted listeners targeting < P.
+                    // Instead, those listeners will receive notifyPosted when the notification is
+                    // unhidden.
+                    if (r.isHidden() && info.targetSdkVersion < Build.VERSION_CODES.P) {
+                        continue;
+                    }
 
-                // If we shouldn't notify all listeners, this means the hidden state of
-                // a notification was changed.  Don't notifyPosted listeners targeting >= P.
-                // Instead, those listeners will receive notifyRankingUpdate.
-                if (!notifyAllListeners && info.targetSdkVersion >= Build.VERSION_CODES.P) {
-                    continue;
-                }
+                    // If we shouldn't notify all listeners, this means the hidden state of
+                    // a notification was changed.  Don't notifyPosted listeners targeting >= P.
+                    // Instead, those listeners will receive notifyRankingUpdate.
+                    if (!notifyAllListeners && info.targetSdkVersion >= Build.VERSION_CODES.P) {
+                        continue;
+                    }
 
-                final NotificationRankingUpdate update = makeRankingUpdateLocked(info);
+                    final NotificationRankingUpdate update = makeRankingUpdateLocked(info);
 
-                // This notification became invisible -> remove the old one.
-                if (oldSbnVisible && !sbnVisible) {
-                    final StatusBarNotification oldSbnLightClone = oldSbn.cloneLight();
+                    // This notification became invisible -> remove the old one.
+                    if (oldSbnVisible && !sbnVisible) {
+                        final StatusBarNotification oldSbnLightClone = oldSbn.cloneLight();
+                        mHandler.post(new Runnable() {
+                            @Override
+                            public void run() {
+                                notifyRemoved(
+                                        info, oldSbnLightClone, update, null, REASON_USER_STOPPED);
+                            }
+                        });
+                        continue;
+                    }
+
+                    // Grant access before listener is notified
+                    final int targetUserId = (info.userid == UserHandle.USER_ALL)
+                            ? UserHandle.USER_SYSTEM : info.userid;
+                    updateUriPermissions(r, old, info.component.getPackageName(), targetUserId);
+
+                    final StatusBarNotification sbnToPost = trimCache.ForListener(info);
                     mHandler.post(new Runnable() {
                         @Override
                         public void run() {
-                            notifyRemoved(
-                                    info, oldSbnLightClone, update, null, REASON_USER_STOPPED);
+                            notifyPosted(info, sbnToPost, update);
                         }
                     });
-                    continue;
                 }
-
-                // Grant access before listener is notified
-                final int targetUserId = (info.userid == UserHandle.USER_ALL)
-                        ? UserHandle.USER_SYSTEM : info.userid;
-                updateUriPermissions(r, old, info.component.getPackageName(), targetUserId);
-
-                final StatusBarNotification sbnToPost = trimCache.ForListener(info);
-                mHandler.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        notifyPosted(info, sbnToPost, update);
-                    }
-                });
+            } catch (Exception e) {
+                Slog.e(TAG, "Could not notify listeners for " + r.getKey(), e);
             }
         }
 
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index f7889ea..c31aa24 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -1985,7 +1985,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 +1998,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 +2155,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);
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorService.java b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
index b7d6360..0bb0f94 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorService.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorService.java
@@ -37,6 +37,9 @@
 import java.io.PrintWriter;
 import java.util.Objects;
 
+/**
+ * The implementation of ITimeDetectorService.aidl.
+ */
 public final class TimeDetectorService extends ITimeDetectorService.Stub {
     private static final String TAG = "TimeDetectorService";
 
@@ -75,7 +78,7 @@
                 Settings.Global.getUriFor(Settings.Global.AUTO_TIME), true,
                 new ContentObserver(handler) {
                     public void onChange(boolean selfChange) {
-                        timeDetectorService.handleAutoTimeDetectionToggle();
+                        timeDetectorService.handleAutoTimeDetectionChanged();
                     }
                 });
 
@@ -114,8 +117,9 @@
         mHandler.post(() -> mTimeDetectorStrategy.suggestNetworkTime(timeSignal));
     }
 
+    /** Internal method for handling the auto time setting being changed. */
     @VisibleForTesting
-    public void handleAutoTimeDetectionToggle() {
+    public void handleAutoTimeDetectionChanged() {
         mHandler.post(mTimeDetectorStrategy::handleAutoTimeDetectionChanged);
     }
 
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
index 468b806..a7c3b4d 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategy.java
@@ -26,8 +26,8 @@
 import java.io.PrintWriter;
 
 /**
- * The interface for classes that implement the time detection algorithm used by the
- * TimeDetectorService.
+ * The interface for the class that implements the time detection algorithm used by the
+ * {@link TimeDetectorService}.
  *
  * <p>Most calls will be handled by a single thread but that is not true for all calls. For example
  * {@link #dump(PrintWriter, String[])}) may be called on a different thread so implementations must
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
index a1e643f..19435ee 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
@@ -38,7 +38,7 @@
 import java.lang.annotation.RetentionPolicy;
 
 /**
- * An implementation of TimeDetectorStrategy that passes phone and manual suggestions to
+ * An implementation of {@link TimeDetectorStrategy} that passes phone and manual suggestions to
  * {@link AlarmManager}. When there are multiple phone sources, the one with the lowest ID is used
  * unless the data becomes too stale.
  *
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java
index adf6d7e..2520316 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorCallbackImpl.java
@@ -24,9 +24,9 @@
 import android.provider.Settings;
 
 /**
- * The real implementation of {@link TimeZoneDetectorStrategy.Callback}.
+ * The real implementation of {@link TimeZoneDetectorStrategyImpl.Callback}.
  */
-public final class TimeZoneDetectorCallbackImpl implements TimeZoneDetectorStrategy.Callback {
+public final class TimeZoneDetectorCallbackImpl implements TimeZoneDetectorStrategyImpl.Callback {
 
     private static final String TIMEZONE_PROPERTY = "persist.sys.timezone";
 
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
index 9a1fe65..381ee10 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
@@ -67,19 +67,21 @@
 
     private static TimeZoneDetectorService create(@NonNull Context context) {
         final TimeZoneDetectorStrategy timeZoneDetectorStrategy =
-                TimeZoneDetectorStrategy.create(context);
+                TimeZoneDetectorStrategyImpl.create(context);
 
         Handler handler = FgThread.getHandler();
+        TimeZoneDetectorService service =
+                new TimeZoneDetectorService(context, handler, timeZoneDetectorStrategy);
+
         ContentResolver contentResolver = context.getContentResolver();
         contentResolver.registerContentObserver(
                 Settings.Global.getUriFor(Settings.Global.AUTO_TIME_ZONE), true,
                 new ContentObserver(handler) {
                     public void onChange(boolean selfChange) {
-                        timeZoneDetectorStrategy.handleAutoTimeZoneDetectionChange();
+                        service.handleAutoTimeZoneDetectionChanged();
                     }
                 });
-
-        return new TimeZoneDetectorService(context, handler, timeZoneDetectorStrategy);
+        return service;
     }
 
     @VisibleForTesting
@@ -111,17 +113,25 @@
             @Nullable String[] args) {
         if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
 
-        mTimeZoneDetectorStrategy.dumpState(pw, args);
+        mTimeZoneDetectorStrategy.dump(pw, args);
+    }
+
+    /** Internal method for handling the auto time zone setting being changed. */
+    @VisibleForTesting
+    public void handleAutoTimeZoneDetectionChanged() {
+        mHandler.post(mTimeZoneDetectorStrategy::handleAutoTimeZoneDetectionChanged);
     }
 
     private void enforceSuggestPhoneTimeZonePermission() {
         mContext.enforceCallingPermission(
-                android.Manifest.permission.SET_TIME_ZONE, "set time zone");
+                android.Manifest.permission.SUGGEST_PHONE_TIME_AND_ZONE,
+                "suggest phone time and time zone");
     }
 
     private void enforceSuggestManualTimeZonePermission() {
         mContext.enforceCallingOrSelfPermission(
-                android.Manifest.permission.SET_TIME_ZONE, "set time zone");
+                android.Manifest.permission.SUGGEST_MANUAL_TIME_AND_ZONE,
+                "suggest manual time and time zone");
     }
 }
 
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
index b0e0069..1d439e9 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
@@ -15,192 +15,26 @@
  */
 package com.android.server.timezonedetector;
 
-import static android.app.timezonedetector.PhoneTimeZoneSuggestion.MATCH_TYPE_EMULATOR_ZONE_ID;
-import static android.app.timezonedetector.PhoneTimeZoneSuggestion.MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY;
-import static android.app.timezonedetector.PhoneTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS;
-import static android.app.timezonedetector.PhoneTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET;
-import static android.app.timezonedetector.PhoneTimeZoneSuggestion.QUALITY_SINGLE_ZONE;
-
-import android.annotation.IntDef;
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.app.timezonedetector.ManualTimeZoneSuggestion;
 import android.app.timezonedetector.PhoneTimeZoneSuggestion;
-import android.content.Context;
-import android.util.LocalLog;
-import android.util.Slog;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.IndentingPrintWriter;
 
 import java.io.PrintWriter;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.Objects;
 
 /**
- * A singleton, stateful time zone detection strategy that is aware of user (manual) suggestions and
- * suggestions from multiple phone devices. Suggestions are acted on or ignored as needed, dependent
- * on the current "auto time zone detection" setting.
+ * The interface for the class that implement the time detection algorithm used by the
+ * {@link TimeZoneDetectorService}.
  *
- * <p>For automatic detection it keeps track of the most recent suggestion from each phone it uses
- * the best suggestion based on a scoring algorithm. If several phones provide the same score then
- * the phone with the lowest numeric ID "wins". If the situation changes and it is no longer
- * possible to be confident about the time zone, phones must submit an empty suggestion in order to
- * "withdraw" their previous suggestion.
+ * <p>Most calls will be handled by a single thread but that is not true for all calls. For example
+ * {@link #dump(PrintWriter, String[])}) may be called on a different thread so implementations must
+ * handle thread safety.
+ *
+ * @hide
  */
-public class TimeZoneDetectorStrategy {
+public interface TimeZoneDetectorStrategy {
 
-    /**
-     * Used by {@link TimeZoneDetectorStrategy} to interact with the surrounding service. It can be
-     * faked for tests.
-     *
-     * <p>Note: Because the system properties-derived values like
-     * {@link #isAutoTimeZoneDetectionEnabled()}, {@link #isAutoTimeZoneDetectionEnabled()},
-     * {@link #getDeviceTimeZone()} can be modified independently and from different threads (and
-     * processes!), their use are prone to race conditions. That will be true until the
-     * responsibility for setting their values is moved to {@link TimeZoneDetectorStrategy}.
-     */
-    @VisibleForTesting
-    public interface Callback {
-
-        /**
-         * Returns true if automatic time zone detection is enabled in settings.
-         */
-        boolean isAutoTimeZoneDetectionEnabled();
-
-        /**
-         * Returns true if the device has had an explicit time zone set.
-         */
-        boolean isDeviceTimeZoneInitialized();
-
-        /**
-         * Returns the device's currently configured time zone.
-         */
-        String getDeviceTimeZone();
-
-        /**
-         * Sets the device's time zone.
-         */
-        void setDeviceTimeZone(@NonNull String zoneId);
-    }
-
-    private static final String LOG_TAG = "TimeZoneDetectorStrategy";
-    private static final boolean DBG = false;
-
-    @IntDef({ ORIGIN_PHONE, ORIGIN_MANUAL })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface Origin {}
-
-    /** Used when a time value originated from a telephony signal. */
-    @Origin
-    private static final int ORIGIN_PHONE = 1;
-
-    /** Used when a time value originated from a user / manual settings. */
-    @Origin
-    private static final int ORIGIN_MANUAL = 2;
-
-    /**
-     * The abstract score for an empty or invalid phone suggestion.
-     *
-     * Used to score phone suggestions where there is no zone.
-     */
-    @VisibleForTesting
-    public static final int PHONE_SCORE_NONE = 0;
-
-    /**
-     * The abstract score for a low quality phone suggestion.
-     *
-     * Used to score suggestions where:
-     * The suggested zone ID is one of several possibilities, and the possibilities have different
-     * offsets.
-     *
-     * You would have to be quite desperate to want to use this choice.
-     */
-    @VisibleForTesting
-    public static final int PHONE_SCORE_LOW = 1;
-
-    /**
-     * The abstract score for a medium quality phone suggestion.
-     *
-     * Used for:
-     * The suggested zone ID is one of several possibilities but at least the possibilities have the
-     * same offset. Users would get the correct time but for the wrong reason. i.e. their device may
-     * switch to DST at the wrong time and (for example) their calendar events.
-     */
-    @VisibleForTesting
-    public static final int PHONE_SCORE_MEDIUM = 2;
-
-    /**
-     * The abstract score for a high quality phone suggestion.
-     *
-     * Used for:
-     * The suggestion was for one zone ID and the answer was unambiguous and likely correct given
-     * the info available.
-     */
-    @VisibleForTesting
-    public static final int PHONE_SCORE_HIGH = 3;
-
-    /**
-     * The abstract score for a highest quality phone suggestion.
-     *
-     * Used for:
-     * Suggestions that must "win" because they constitute test or emulator zone ID.
-     */
-    @VisibleForTesting
-    public static final int PHONE_SCORE_HIGHEST = 4;
-
-    /**
-     * The threshold at which phone suggestions are good enough to use to set the device's time
-     * zone.
-     */
-    @VisibleForTesting
-    public static final int PHONE_SCORE_USAGE_THRESHOLD = PHONE_SCORE_MEDIUM;
-
-    /** The number of previous phone suggestions to keep for each ID (for use during debugging). */
-    private static final int KEEP_PHONE_SUGGESTION_HISTORY_SIZE = 30;
-
-    @NonNull
-    private final Callback mCallback;
-
-    /**
-     * A log that records the decisions / decision metadata that affected the device's time zone
-     * (for use during debugging).
-     */
-    @NonNull
-    private final LocalLog mTimeZoneChangesLog = new LocalLog(30, false /* useLocalTimestamps */);
-
-    /**
-     * A mapping from slotIndex to a phone time zone suggestion. We typically expect one or two
-     * mappings: devices will have a small number of telephony devices and slotIndexs are assumed to
-     * be stable.
-     */
-    @GuardedBy("this")
-    private ArrayMapWithHistory<Integer, QualifiedPhoneTimeZoneSuggestion> mSuggestionBySlotIndex =
-            new ArrayMapWithHistory<>(KEEP_PHONE_SUGGESTION_HISTORY_SIZE);
-
-    /**
-     * Creates a new instance of {@link TimeZoneDetectorStrategy}.
-     */
-    public static TimeZoneDetectorStrategy create(Context context) {
-        Callback timeZoneDetectionServiceHelper = new TimeZoneDetectorCallbackImpl(context);
-        return new TimeZoneDetectorStrategy(timeZoneDetectionServiceHelper);
-    }
-
-    @VisibleForTesting
-    public TimeZoneDetectorStrategy(Callback callback) {
-        mCallback = Objects.requireNonNull(callback);
-    }
-
-    /** Process the suggested manually- / user-entered time zone. */
-    public synchronized void suggestManualTimeZone(@NonNull ManualTimeZoneSuggestion suggestion) {
-        Objects.requireNonNull(suggestion);
-
-        String timeZoneId = suggestion.getZoneId();
-        String cause = "Manual time suggestion received: suggestion=" + suggestion;
-        setDeviceTimeZoneIfRequired(ORIGIN_MANUAL, timeZoneId, cause);
-    }
+    /** Process the suggested manually-entered (i.e. user sourced) time zone. */
+    void suggestManualTimeZone(@NonNull ManualTimeZoneSuggestion suggestion);
 
     /**
      * Suggests a time zone for the device, or withdraws a previous suggestion if
@@ -210,312 +44,15 @@
      * suggestion. The strategy uses suggestions to decide whether to modify the device's time zone
      * setting and what to set it to.
      */
-    public synchronized void suggestPhoneTimeZone(@NonNull PhoneTimeZoneSuggestion suggestion) {
-        if (DBG) {
-            Slog.d(LOG_TAG, "Phone suggestion received. newSuggestion=" + suggestion);
-        }
-        Objects.requireNonNull(suggestion);
-
-        // Score the suggestion.
-        int score = scorePhoneSuggestion(suggestion);
-        QualifiedPhoneTimeZoneSuggestion scoredSuggestion =
-                new QualifiedPhoneTimeZoneSuggestion(suggestion, score);
-
-        // Store the suggestion against the correct slotIndex.
-        mSuggestionBySlotIndex.put(suggestion.getSlotIndex(), scoredSuggestion);
-
-        // Now perform auto time zone detection. The new suggestion may be used to modify the time
-        // zone setting.
-        String reason = "New phone time suggested. suggestion=" + suggestion;
-        doAutoTimeZoneDetection(reason);
-    }
-
-    private static int scorePhoneSuggestion(@NonNull PhoneTimeZoneSuggestion suggestion) {
-        int score;
-        if (suggestion.getZoneId() == null) {
-            score = PHONE_SCORE_NONE;
-        } else if (suggestion.getMatchType() == MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY
-                || suggestion.getMatchType() == MATCH_TYPE_EMULATOR_ZONE_ID) {
-            // Handle emulator / test cases : These suggestions should always just be used.
-            score = PHONE_SCORE_HIGHEST;
-        } else if (suggestion.getQuality() == QUALITY_SINGLE_ZONE) {
-            score = PHONE_SCORE_HIGH;
-        } else if (suggestion.getQuality() == QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET) {
-            // The suggestion may be wrong, but at least the offset should be correct.
-            score = PHONE_SCORE_MEDIUM;
-        } else if (suggestion.getQuality() == QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS) {
-            // The suggestion has a good chance of being wrong.
-            score = PHONE_SCORE_LOW;
-        } else {
-            throw new AssertionError();
-        }
-        return score;
-    }
-
-    /**
-     * Finds the best available time zone suggestion from all phones. If it is high-enough quality
-     * and automatic time zone detection is enabled then it will be set on the device. The outcome
-     * can be that this strategy becomes / remains un-opinionated and nothing is set.
-     */
-    @GuardedBy("this")
-    private void doAutoTimeZoneDetection(@NonNull String detectionReason) {
-        if (!mCallback.isAutoTimeZoneDetectionEnabled()) {
-            // Avoid doing unnecessary work with this (race-prone) check.
-            return;
-        }
-
-        QualifiedPhoneTimeZoneSuggestion bestPhoneSuggestion = findBestPhoneSuggestion();
-
-        // Work out what to do with the best suggestion.
-        if (bestPhoneSuggestion == null) {
-            // There is no phone suggestion available at all. Become un-opinionated.
-            if (DBG) {
-                Slog.d(LOG_TAG, "Could not determine time zone: No best phone suggestion."
-                        + " detectionReason=" + detectionReason);
-            }
-            return;
-        }
-
-        // Special case handling for uninitialized devices. This should only happen once.
-        String newZoneId = bestPhoneSuggestion.suggestion.getZoneId();
-        if (newZoneId != null && !mCallback.isDeviceTimeZoneInitialized()) {
-            String cause = "Device has no time zone set. Attempting to set the device to the best"
-                    + " available suggestion."
-                    + " bestPhoneSuggestion=" + bestPhoneSuggestion
-                    + ", detectionReason=" + detectionReason;
-            Slog.i(LOG_TAG, cause);
-            setDeviceTimeZoneIfRequired(ORIGIN_PHONE, newZoneId, cause);
-            return;
-        }
-
-        boolean suggestionGoodEnough = bestPhoneSuggestion.score >= PHONE_SCORE_USAGE_THRESHOLD;
-        if (!suggestionGoodEnough) {
-            if (DBG) {
-                Slog.d(LOG_TAG, "Best suggestion not good enough."
-                        + " bestPhoneSuggestion=" + bestPhoneSuggestion
-                        + ", detectionReason=" + detectionReason);
-            }
-            return;
-        }
-
-        // Paranoia: Every suggestion above the SCORE_USAGE_THRESHOLD should have a non-null time
-        // zone ID.
-        if (newZoneId == null) {
-            Slog.w(LOG_TAG, "Empty zone suggestion scored higher than expected. This is an error:"
-                    + " bestPhoneSuggestion=" + bestPhoneSuggestion
-                    + " detectionReason=" + detectionReason);
-            return;
-        }
-
-        String zoneId = bestPhoneSuggestion.suggestion.getZoneId();
-        String cause = "Found good suggestion."
-                + ", bestPhoneSuggestion=" + bestPhoneSuggestion
-                + ", detectionReason=" + detectionReason;
-        setDeviceTimeZoneIfRequired(ORIGIN_PHONE, zoneId, cause);
-    }
-
-    @GuardedBy("this")
-    private void setDeviceTimeZoneIfRequired(
-            @Origin int origin, @NonNull String newZoneId, @NonNull String cause) {
-        Objects.requireNonNull(newZoneId);
-        Objects.requireNonNull(cause);
-
-        boolean isOriginAutomatic = isOriginAutomatic(origin);
-        if (isOriginAutomatic) {
-            if (!mCallback.isAutoTimeZoneDetectionEnabled()) {
-                if (DBG) {
-                    Slog.d(LOG_TAG, "Auto time zone detection is not enabled."
-                            + " origin=" + origin
-                            + ", newZoneId=" + newZoneId
-                            + ", cause=" + cause);
-                }
-                return;
-            }
-        } else {
-            if (mCallback.isAutoTimeZoneDetectionEnabled()) {
-                if (DBG) {
-                    Slog.d(LOG_TAG, "Auto time zone detection is enabled."
-                            + " origin=" + origin
-                            + ", newZoneId=" + newZoneId
-                            + ", cause=" + cause);
-                }
-                return;
-            }
-        }
-
-        String currentZoneId = mCallback.getDeviceTimeZone();
-
-        // Avoid unnecessary changes / intents.
-        if (newZoneId.equals(currentZoneId)) {
-            // No need to set the device time zone - the setting is already what we would be
-            // suggesting.
-            if (DBG) {
-                Slog.d(LOG_TAG, "No need to change the time zone;"
-                        + " device is already set to the suggested zone."
-                        + " origin=" + origin
-                        + ", newZoneId=" + newZoneId
-                        + ", cause=" + cause);
-            }
-            return;
-        }
-
-        mCallback.setDeviceTimeZone(newZoneId);
-        String msg = "Set device time zone."
-                + " origin=" + origin
-                + ", currentZoneId=" + currentZoneId
-                + ", newZoneId=" + newZoneId
-                + ", cause=" + cause;
-        if (DBG) {
-            Slog.d(LOG_TAG, msg);
-        }
-        mTimeZoneChangesLog.log(msg);
-    }
-
-    private static boolean isOriginAutomatic(@Origin int origin) {
-        return origin != ORIGIN_MANUAL;
-    }
-
-    @GuardedBy("this")
-    @Nullable
-    private QualifiedPhoneTimeZoneSuggestion findBestPhoneSuggestion() {
-        QualifiedPhoneTimeZoneSuggestion bestSuggestion = null;
-
-        // Iterate over the latest QualifiedPhoneTimeZoneSuggestion objects received for each phone
-        // and find the best. Note that we deliberately do not look at age: the caller can
-        // rate-limit so age is not a strong indicator of confidence. Instead, the callers are
-        // expected to withdraw suggestions they no longer have confidence in.
-        for (int i = 0; i < mSuggestionBySlotIndex.size(); i++) {
-            QualifiedPhoneTimeZoneSuggestion candidateSuggestion =
-                    mSuggestionBySlotIndex.valueAt(i);
-            if (candidateSuggestion == null) {
-                // Unexpected
-                continue;
-            }
-
-            if (bestSuggestion == null) {
-                bestSuggestion = candidateSuggestion;
-            } else if (candidateSuggestion.score > bestSuggestion.score) {
-                bestSuggestion = candidateSuggestion;
-            } else if (candidateSuggestion.score == bestSuggestion.score) {
-                // Tie! Use the suggestion with the lowest slotIndex.
-                int candidateSlotIndex = candidateSuggestion.suggestion.getSlotIndex();
-                int bestSlotIndex = bestSuggestion.suggestion.getSlotIndex();
-                if (candidateSlotIndex < bestSlotIndex) {
-                    bestSuggestion = candidateSuggestion;
-                }
-            }
-        }
-        return bestSuggestion;
-    }
-
-    /**
-     * Returns the current best phone suggestion. Not intended for general use: it is used during
-     * tests to check strategy behavior.
-     */
-    @VisibleForTesting
-    @Nullable
-    public synchronized QualifiedPhoneTimeZoneSuggestion findBestPhoneSuggestionForTests() {
-        return findBestPhoneSuggestion();
-    }
+    void suggestPhoneTimeZone(@NonNull PhoneTimeZoneSuggestion suggestion);
 
     /**
      * Called when there has been a change to the automatic time zone detection setting.
      */
-    @VisibleForTesting
-    public synchronized void handleAutoTimeZoneDetectionChange() {
-        if (DBG) {
-            Slog.d(LOG_TAG, "handleTimeZoneDetectionChange() called");
-        }
-        if (mCallback.isAutoTimeZoneDetectionEnabled()) {
-            // When the user enabled time zone detection, run the time zone detection and change the
-            // device time zone if possible.
-            String reason = "Auto time zone detection setting enabled.";
-            doAutoTimeZoneDetection(reason);
-        }
-    }
+    void handleAutoTimeZoneDetectionChanged();
 
     /**
      * Dumps internal state such as field values.
      */
-    public synchronized void dumpState(PrintWriter pw, String[] args) {
-        IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
-        ipw.println("TimeZoneDetectorStrategy:");
-
-        ipw.increaseIndent(); // level 1
-        ipw.println("mCallback.isTimeZoneDetectionEnabled()="
-                + mCallback.isAutoTimeZoneDetectionEnabled());
-        ipw.println("mCallback.isDeviceTimeZoneInitialized()="
-                + mCallback.isDeviceTimeZoneInitialized());
-        ipw.println("mCallback.getDeviceTimeZone()="
-                + mCallback.getDeviceTimeZone());
-
-        ipw.println("Time zone change log:");
-        ipw.increaseIndent(); // level 2
-        mTimeZoneChangesLog.dump(ipw);
-        ipw.decreaseIndent(); // level 2
-
-        ipw.println("Phone suggestion history:");
-        ipw.increaseIndent(); // level 2
-        mSuggestionBySlotIndex.dump(ipw);
-        ipw.decreaseIndent(); // level 2
-        ipw.decreaseIndent(); // level 1
-        ipw.flush();
-    }
-
-    /**
-     * A method used to inspect strategy state during tests. Not intended for general use.
-     */
-    @VisibleForTesting
-    public synchronized QualifiedPhoneTimeZoneSuggestion getLatestPhoneSuggestion(int slotIndex) {
-        return mSuggestionBySlotIndex.get(slotIndex);
-    }
-
-    /**
-     * A {@link PhoneTimeZoneSuggestion} with additional qualifying metadata.
-     */
-    @VisibleForTesting
-    public static class QualifiedPhoneTimeZoneSuggestion {
-
-        @VisibleForTesting
-        public final PhoneTimeZoneSuggestion suggestion;
-
-        /**
-         * The score the suggestion has been given. This can be used to rank against other
-         * suggestions of the same type.
-         */
-        @VisibleForTesting
-        public final int score;
-
-        @VisibleForTesting
-        public QualifiedPhoneTimeZoneSuggestion(PhoneTimeZoneSuggestion suggestion, int score) {
-            this.suggestion = suggestion;
-            this.score = score;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (this == o) {
-                return true;
-            }
-            if (o == null || getClass() != o.getClass()) {
-                return false;
-            }
-            QualifiedPhoneTimeZoneSuggestion that = (QualifiedPhoneTimeZoneSuggestion) o;
-            return score == that.score
-                    && suggestion.equals(that.suggestion);
-        }
-
-        @Override
-        public int hashCode() {
-            return Objects.hash(score, suggestion);
-        }
-
-        @Override
-        public String toString() {
-            return "QualifiedPhoneTimeZoneSuggestion{"
-                    + "suggestion=" + suggestion
-                    + ", score=" + score
-                    + '}';
-        }
-    }
+    void dump(PrintWriter pw, String[] args);
 }
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
new file mode 100644
index 0000000..f85f9fe
--- /dev/null
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
@@ -0,0 +1,514 @@
+/*
+ * Copyright 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.server.timezonedetector;
+
+import static android.app.timezonedetector.PhoneTimeZoneSuggestion.MATCH_TYPE_EMULATOR_ZONE_ID;
+import static android.app.timezonedetector.PhoneTimeZoneSuggestion.MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY;
+import static android.app.timezonedetector.PhoneTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS;
+import static android.app.timezonedetector.PhoneTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET;
+import static android.app.timezonedetector.PhoneTimeZoneSuggestion.QUALITY_SINGLE_ZONE;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.timezonedetector.ManualTimeZoneSuggestion;
+import android.app.timezonedetector.PhoneTimeZoneSuggestion;
+import android.content.Context;
+import android.util.LocalLog;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
+
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * An implementation of {@link TimeZoneDetectorStrategy} that handle telephony and manual
+ * suggestions. Suggestions are acted on or ignored as needed, dependent on the current "auto time
+ * zone detection" setting.
+ *
+ * <p>For automatic detection it keeps track of the most recent suggestion from each phone it uses
+ * the best suggestion based on a scoring algorithm. If several phones provide the same score then
+ * the phone with the lowest numeric ID "wins". If the situation changes and it is no longer
+ * possible to be confident about the time zone, phones must submit an empty suggestion in order to
+ * "withdraw" their previous suggestion.
+ *
+ * <p>Most public methods are marked synchronized to ensure thread safety around internal state.
+ */
+public final class TimeZoneDetectorStrategyImpl implements TimeZoneDetectorStrategy {
+
+    /**
+     * Used by {@link TimeZoneDetectorStrategyImpl} to interact with the surrounding service. It can
+     * be faked for tests.
+     *
+     * <p>Note: Because the system properties-derived values like
+     * {@link #isAutoTimeZoneDetectionEnabled()}, {@link #isAutoTimeZoneDetectionEnabled()},
+     * {@link #getDeviceTimeZone()} can be modified independently and from different threads (and
+     * processes!), their use are prone to race conditions. That will be true until the
+     * responsibility for setting their values is moved to {@link TimeZoneDetectorStrategyImpl}.
+     */
+    @VisibleForTesting
+    public interface Callback {
+
+        /**
+         * Returns true if automatic time zone detection is enabled in settings.
+         */
+        boolean isAutoTimeZoneDetectionEnabled();
+
+        /**
+         * Returns true if the device has had an explicit time zone set.
+         */
+        boolean isDeviceTimeZoneInitialized();
+
+        /**
+         * Returns the device's currently configured time zone.
+         */
+        String getDeviceTimeZone();
+
+        /**
+         * Sets the device's time zone.
+         */
+        void setDeviceTimeZone(@NonNull String zoneId);
+    }
+
+    private static final String LOG_TAG = "TimeZoneDetectorStrategy";
+    private static final boolean DBG = false;
+
+    @IntDef({ ORIGIN_PHONE, ORIGIN_MANUAL })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Origin {}
+
+    /** Used when a time value originated from a telephony signal. */
+    @Origin
+    private static final int ORIGIN_PHONE = 1;
+
+    /** Used when a time value originated from a user / manual settings. */
+    @Origin
+    private static final int ORIGIN_MANUAL = 2;
+
+    /**
+     * The abstract score for an empty or invalid phone suggestion.
+     *
+     * Used to score phone suggestions where there is no zone.
+     */
+    @VisibleForTesting
+    public static final int PHONE_SCORE_NONE = 0;
+
+    /**
+     * The abstract score for a low quality phone suggestion.
+     *
+     * Used to score suggestions where:
+     * The suggested zone ID is one of several possibilities, and the possibilities have different
+     * offsets.
+     *
+     * You would have to be quite desperate to want to use this choice.
+     */
+    @VisibleForTesting
+    public static final int PHONE_SCORE_LOW = 1;
+
+    /**
+     * The abstract score for a medium quality phone suggestion.
+     *
+     * Used for:
+     * The suggested zone ID is one of several possibilities but at least the possibilities have the
+     * same offset. Users would get the correct time but for the wrong reason. i.e. their device may
+     * switch to DST at the wrong time and (for example) their calendar events.
+     */
+    @VisibleForTesting
+    public static final int PHONE_SCORE_MEDIUM = 2;
+
+    /**
+     * The abstract score for a high quality phone suggestion.
+     *
+     * Used for:
+     * The suggestion was for one zone ID and the answer was unambiguous and likely correct given
+     * the info available.
+     */
+    @VisibleForTesting
+    public static final int PHONE_SCORE_HIGH = 3;
+
+    /**
+     * The abstract score for a highest quality phone suggestion.
+     *
+     * Used for:
+     * Suggestions that must "win" because they constitute test or emulator zone ID.
+     */
+    @VisibleForTesting
+    public static final int PHONE_SCORE_HIGHEST = 4;
+
+    /**
+     * The threshold at which phone suggestions are good enough to use to set the device's time
+     * zone.
+     */
+    @VisibleForTesting
+    public static final int PHONE_SCORE_USAGE_THRESHOLD = PHONE_SCORE_MEDIUM;
+
+    /** The number of previous phone suggestions to keep for each ID (for use during debugging). */
+    private static final int KEEP_PHONE_SUGGESTION_HISTORY_SIZE = 30;
+
+    @NonNull
+    private final Callback mCallback;
+
+    /**
+     * A log that records the decisions / decision metadata that affected the device's time zone
+     * (for use during debugging).
+     */
+    @NonNull
+    private final LocalLog mTimeZoneChangesLog = new LocalLog(30, false /* useLocalTimestamps */);
+
+    /**
+     * A mapping from slotIndex to a phone time zone suggestion. We typically expect one or two
+     * mappings: devices will have a small number of telephony devices and slotIndexs are assumed to
+     * be stable.
+     */
+    @GuardedBy("this")
+    private ArrayMapWithHistory<Integer, QualifiedPhoneTimeZoneSuggestion> mSuggestionBySlotIndex =
+            new ArrayMapWithHistory<>(KEEP_PHONE_SUGGESTION_HISTORY_SIZE);
+
+    /**
+     * Creates a new instance of {@link TimeZoneDetectorStrategyImpl}.
+     */
+    public static TimeZoneDetectorStrategyImpl create(Context context) {
+        Callback timeZoneDetectionServiceHelper = new TimeZoneDetectorCallbackImpl(context);
+        return new TimeZoneDetectorStrategyImpl(timeZoneDetectionServiceHelper);
+    }
+
+    @VisibleForTesting
+    public TimeZoneDetectorStrategyImpl(Callback callback) {
+        mCallback = Objects.requireNonNull(callback);
+    }
+
+    @Override
+    public synchronized void suggestManualTimeZone(@NonNull ManualTimeZoneSuggestion suggestion) {
+        Objects.requireNonNull(suggestion);
+
+        String timeZoneId = suggestion.getZoneId();
+        String cause = "Manual time suggestion received: suggestion=" + suggestion;
+        setDeviceTimeZoneIfRequired(ORIGIN_MANUAL, timeZoneId, cause);
+    }
+
+    @Override
+    public synchronized void suggestPhoneTimeZone(@NonNull PhoneTimeZoneSuggestion suggestion) {
+        if (DBG) {
+            Slog.d(LOG_TAG, "Phone suggestion received. newSuggestion=" + suggestion);
+        }
+        Objects.requireNonNull(suggestion);
+
+        // Score the suggestion.
+        int score = scorePhoneSuggestion(suggestion);
+        QualifiedPhoneTimeZoneSuggestion scoredSuggestion =
+                new QualifiedPhoneTimeZoneSuggestion(suggestion, score);
+
+        // Store the suggestion against the correct slotIndex.
+        mSuggestionBySlotIndex.put(suggestion.getSlotIndex(), scoredSuggestion);
+
+        // Now perform auto time zone detection. The new suggestion may be used to modify the time
+        // zone setting.
+        String reason = "New phone time suggested. suggestion=" + suggestion;
+        doAutoTimeZoneDetection(reason);
+    }
+
+    private static int scorePhoneSuggestion(@NonNull PhoneTimeZoneSuggestion suggestion) {
+        int score;
+        if (suggestion.getZoneId() == null) {
+            score = PHONE_SCORE_NONE;
+        } else if (suggestion.getMatchType() == MATCH_TYPE_TEST_NETWORK_OFFSET_ONLY
+                || suggestion.getMatchType() == MATCH_TYPE_EMULATOR_ZONE_ID) {
+            // Handle emulator / test cases : These suggestions should always just be used.
+            score = PHONE_SCORE_HIGHEST;
+        } else if (suggestion.getQuality() == QUALITY_SINGLE_ZONE) {
+            score = PHONE_SCORE_HIGH;
+        } else if (suggestion.getQuality() == QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET) {
+            // The suggestion may be wrong, but at least the offset should be correct.
+            score = PHONE_SCORE_MEDIUM;
+        } else if (suggestion.getQuality() == QUALITY_MULTIPLE_ZONES_WITH_DIFFERENT_OFFSETS) {
+            // The suggestion has a good chance of being wrong.
+            score = PHONE_SCORE_LOW;
+        } else {
+            throw new AssertionError();
+        }
+        return score;
+    }
+
+    /**
+     * Finds the best available time zone suggestion from all phones. If it is high-enough quality
+     * and automatic time zone detection is enabled then it will be set on the device. The outcome
+     * can be that this strategy becomes / remains un-opinionated and nothing is set.
+     */
+    @GuardedBy("this")
+    private void doAutoTimeZoneDetection(@NonNull String detectionReason) {
+        if (!mCallback.isAutoTimeZoneDetectionEnabled()) {
+            // Avoid doing unnecessary work with this (race-prone) check.
+            return;
+        }
+
+        QualifiedPhoneTimeZoneSuggestion bestPhoneSuggestion = findBestPhoneSuggestion();
+
+        // Work out what to do with the best suggestion.
+        if (bestPhoneSuggestion == null) {
+            // There is no phone suggestion available at all. Become un-opinionated.
+            if (DBG) {
+                Slog.d(LOG_TAG, "Could not determine time zone: No best phone suggestion."
+                        + " detectionReason=" + detectionReason);
+            }
+            return;
+        }
+
+        // Special case handling for uninitialized devices. This should only happen once.
+        String newZoneId = bestPhoneSuggestion.suggestion.getZoneId();
+        if (newZoneId != null && !mCallback.isDeviceTimeZoneInitialized()) {
+            String cause = "Device has no time zone set. Attempting to set the device to the best"
+                    + " available suggestion."
+                    + " bestPhoneSuggestion=" + bestPhoneSuggestion
+                    + ", detectionReason=" + detectionReason;
+            Slog.i(LOG_TAG, cause);
+            setDeviceTimeZoneIfRequired(ORIGIN_PHONE, newZoneId, cause);
+            return;
+        }
+
+        boolean suggestionGoodEnough = bestPhoneSuggestion.score >= PHONE_SCORE_USAGE_THRESHOLD;
+        if (!suggestionGoodEnough) {
+            if (DBG) {
+                Slog.d(LOG_TAG, "Best suggestion not good enough."
+                        + " bestPhoneSuggestion=" + bestPhoneSuggestion
+                        + ", detectionReason=" + detectionReason);
+            }
+            return;
+        }
+
+        // Paranoia: Every suggestion above the SCORE_USAGE_THRESHOLD should have a non-null time
+        // zone ID.
+        if (newZoneId == null) {
+            Slog.w(LOG_TAG, "Empty zone suggestion scored higher than expected. This is an error:"
+                    + " bestPhoneSuggestion=" + bestPhoneSuggestion
+                    + " detectionReason=" + detectionReason);
+            return;
+        }
+
+        String zoneId = bestPhoneSuggestion.suggestion.getZoneId();
+        String cause = "Found good suggestion."
+                + ", bestPhoneSuggestion=" + bestPhoneSuggestion
+                + ", detectionReason=" + detectionReason;
+        setDeviceTimeZoneIfRequired(ORIGIN_PHONE, zoneId, cause);
+    }
+
+    @GuardedBy("this")
+    private void setDeviceTimeZoneIfRequired(
+            @Origin int origin, @NonNull String newZoneId, @NonNull String cause) {
+        Objects.requireNonNull(newZoneId);
+        Objects.requireNonNull(cause);
+
+        boolean isOriginAutomatic = isOriginAutomatic(origin);
+        if (isOriginAutomatic) {
+            if (!mCallback.isAutoTimeZoneDetectionEnabled()) {
+                if (DBG) {
+                    Slog.d(LOG_TAG, "Auto time zone detection is not enabled."
+                            + " origin=" + origin
+                            + ", newZoneId=" + newZoneId
+                            + ", cause=" + cause);
+                }
+                return;
+            }
+        } else {
+            if (mCallback.isAutoTimeZoneDetectionEnabled()) {
+                if (DBG) {
+                    Slog.d(LOG_TAG, "Auto time zone detection is enabled."
+                            + " origin=" + origin
+                            + ", newZoneId=" + newZoneId
+                            + ", cause=" + cause);
+                }
+                return;
+            }
+        }
+
+        String currentZoneId = mCallback.getDeviceTimeZone();
+
+        // Avoid unnecessary changes / intents.
+        if (newZoneId.equals(currentZoneId)) {
+            // No need to set the device time zone - the setting is already what we would be
+            // suggesting.
+            if (DBG) {
+                Slog.d(LOG_TAG, "No need to change the time zone;"
+                        + " device is already set to the suggested zone."
+                        + " origin=" + origin
+                        + ", newZoneId=" + newZoneId
+                        + ", cause=" + cause);
+            }
+            return;
+        }
+
+        mCallback.setDeviceTimeZone(newZoneId);
+        String msg = "Set device time zone."
+                + " origin=" + origin
+                + ", currentZoneId=" + currentZoneId
+                + ", newZoneId=" + newZoneId
+                + ", cause=" + cause;
+        if (DBG) {
+            Slog.d(LOG_TAG, msg);
+        }
+        mTimeZoneChangesLog.log(msg);
+    }
+
+    private static boolean isOriginAutomatic(@Origin int origin) {
+        return origin != ORIGIN_MANUAL;
+    }
+
+    @GuardedBy("this")
+    @Nullable
+    private QualifiedPhoneTimeZoneSuggestion findBestPhoneSuggestion() {
+        QualifiedPhoneTimeZoneSuggestion bestSuggestion = null;
+
+        // Iterate over the latest QualifiedPhoneTimeZoneSuggestion objects received for each phone
+        // and find the best. Note that we deliberately do not look at age: the caller can
+        // rate-limit so age is not a strong indicator of confidence. Instead, the callers are
+        // expected to withdraw suggestions they no longer have confidence in.
+        for (int i = 0; i < mSuggestionBySlotIndex.size(); i++) {
+            QualifiedPhoneTimeZoneSuggestion candidateSuggestion =
+                    mSuggestionBySlotIndex.valueAt(i);
+            if (candidateSuggestion == null) {
+                // Unexpected
+                continue;
+            }
+
+            if (bestSuggestion == null) {
+                bestSuggestion = candidateSuggestion;
+            } else if (candidateSuggestion.score > bestSuggestion.score) {
+                bestSuggestion = candidateSuggestion;
+            } else if (candidateSuggestion.score == bestSuggestion.score) {
+                // Tie! Use the suggestion with the lowest slotIndex.
+                int candidateSlotIndex = candidateSuggestion.suggestion.getSlotIndex();
+                int bestSlotIndex = bestSuggestion.suggestion.getSlotIndex();
+                if (candidateSlotIndex < bestSlotIndex) {
+                    bestSuggestion = candidateSuggestion;
+                }
+            }
+        }
+        return bestSuggestion;
+    }
+
+    /**
+     * Returns the current best phone suggestion. Not intended for general use: it is used during
+     * tests to check strategy behavior.
+     */
+    @VisibleForTesting
+    @Nullable
+    public synchronized QualifiedPhoneTimeZoneSuggestion findBestPhoneSuggestionForTests() {
+        return findBestPhoneSuggestion();
+    }
+
+    @Override
+    public synchronized void handleAutoTimeZoneDetectionChanged() {
+        if (DBG) {
+            Slog.d(LOG_TAG, "handleTimeZoneDetectionChange() called");
+        }
+        if (mCallback.isAutoTimeZoneDetectionEnabled()) {
+            // When the user enabled time zone detection, run the time zone detection and change the
+            // device time zone if possible.
+            String reason = "Auto time zone detection setting enabled.";
+            doAutoTimeZoneDetection(reason);
+        }
+    }
+
+    /**
+     * Dumps internal state such as field values.
+     */
+    @Override
+    public synchronized void dump(PrintWriter pw, String[] args) {
+        IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ");
+        ipw.println("TimeZoneDetectorStrategy:");
+
+        ipw.increaseIndent(); // level 1
+        ipw.println("mCallback.isTimeZoneDetectionEnabled()="
+                + mCallback.isAutoTimeZoneDetectionEnabled());
+        ipw.println("mCallback.isDeviceTimeZoneInitialized()="
+                + mCallback.isDeviceTimeZoneInitialized());
+        ipw.println("mCallback.getDeviceTimeZone()="
+                + mCallback.getDeviceTimeZone());
+
+        ipw.println("Time zone change log:");
+        ipw.increaseIndent(); // level 2
+        mTimeZoneChangesLog.dump(ipw);
+        ipw.decreaseIndent(); // level 2
+
+        ipw.println("Phone suggestion history:");
+        ipw.increaseIndent(); // level 2
+        mSuggestionBySlotIndex.dump(ipw);
+        ipw.decreaseIndent(); // level 2
+        ipw.decreaseIndent(); // level 1
+        ipw.flush();
+    }
+
+    /**
+     * A method used to inspect strategy state during tests. Not intended for general use.
+     */
+    @VisibleForTesting
+    public synchronized QualifiedPhoneTimeZoneSuggestion getLatestPhoneSuggestion(int slotIndex) {
+        return mSuggestionBySlotIndex.get(slotIndex);
+    }
+
+    /**
+     * A {@link PhoneTimeZoneSuggestion} with additional qualifying metadata.
+     */
+    @VisibleForTesting
+    public static class QualifiedPhoneTimeZoneSuggestion {
+
+        @VisibleForTesting
+        public final PhoneTimeZoneSuggestion suggestion;
+
+        /**
+         * The score the suggestion has been given. This can be used to rank against other
+         * suggestions of the same type.
+         */
+        @VisibleForTesting
+        public final int score;
+
+        @VisibleForTesting
+        public QualifiedPhoneTimeZoneSuggestion(PhoneTimeZoneSuggestion suggestion, int score) {
+            this.suggestion = suggestion;
+            this.score = score;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (o == null || getClass() != o.getClass()) {
+                return false;
+            }
+            QualifiedPhoneTimeZoneSuggestion that = (QualifiedPhoneTimeZoneSuggestion) o;
+            return score == that.score
+                    && suggestion.equals(that.suggestion);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(score, suggestion);
+        }
+
+        @Override
+        public String toString() {
+            return "QualifiedPhoneTimeZoneSuggestion{"
+                    + "suggestion=" + suggestion
+                    + ", score=" + score
+                    + '}';
+        }
+    }
+}
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/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 6e243f0..59eee9c 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -562,4 +562,14 @@
      */
     public abstract void setAccessibilityIdToSurfaceMetadata(
             IBinder windowToken, int accessibilityWindowId);
+
+    /**
+     * Transfers input focus from a given input token to that of the IME window.
+     *
+     * @param sourceInputToken The source token.
+     * @param displayId The display hosting the IME window.
+     * @return Whether transfer was successful.
+     */
+    public abstract boolean transferTouchFocusToImeWindow(@NonNull IBinder sourceInputToken,
+            int displayId);
 }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index a5b99b0..6e1f46bb 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -7558,6 +7558,29 @@
                 }
             }
         }
+
+        @Override
+        public boolean transferTouchFocusToImeWindow(@NonNull IBinder sourceInputToken,
+                int displayId) {
+            final IBinder destinationInputToken;
+
+            synchronized (mGlobalLock) {
+                final DisplayContent displayContent = mRoot.getDisplayContent(displayId);
+                if (displayContent == null) {
+                    return false;
+                }
+                final WindowState imeWindow = displayContent.mInputMethodWindow;
+                if (imeWindow == null) {
+                    return false;
+                }
+                if (imeWindow.mInputChannel == null) {
+                    return false;
+                }
+                destinationInputToken = imeWindow.mInputChannel.getToken();
+            }
+
+            return mInputManager.transferTouchFocus(sourceInputToken, destinationInputToken);
+        }
     }
 
     void registerAppFreezeListener(AppFreezeListener listener) {
diff --git a/services/core/jni/com_android_server_GraphicsStatsService.cpp b/services/core/jni/com_android_server_GraphicsStatsService.cpp
index 7644ade..aa7067e 100644
--- a/services/core/jni/com_android_server_GraphicsStatsService.cpp
+++ b/services/core/jni/com_android_server_GraphicsStatsService.cpp
@@ -137,7 +137,7 @@
 #define TIME_MILLIS_BUCKETS_FIELD_NUMBER 1
 #define FRAME_COUNTS_FIELD_NUMBER 2
 
-static void writeCpuHistogram(stats_event* event,
+static void writeCpuHistogram(AStatsEvent* event,
                               const uirenderer::protos::GraphicsStatsProto& stat) {
     util::ProtoOutputStream proto;
     for (int bucketIndex = 0; bucketIndex < stat.histogram_size(); bucketIndex++) {
@@ -154,10 +154,10 @@
     }
     std::vector<uint8_t> outVector;
     proto.serializeToVector(&outVector);
-    stats_event_write_byte_array(event, outVector.data(), outVector.size());
+    AStatsEvent_writeByteArray(event, outVector.data(), outVector.size());
 }
 
-static void writeGpuHistogram(stats_event* event,
+static void writeGpuHistogram(AStatsEvent* event,
                               const uirenderer::protos::GraphicsStatsProto& stat) {
     util::ProtoOutputStream proto;
     for (int bucketIndex = 0; bucketIndex < stat.gpu_histogram_size(); bucketIndex++) {
@@ -174,20 +174,20 @@
     }
     std::vector<uint8_t> outVector;
     proto.serializeToVector(&outVector);
-    stats_event_write_byte_array(event, outVector.data(), outVector.size());
+    AStatsEvent_writeByteArray(event, outVector.data(), outVector.size());
 }
 
 // graphicsStatsPullCallback is invoked by statsd service to pull GRAPHICS_STATS atom.
-static status_pull_atom_return_t graphicsStatsPullCallback(int32_t atom_tag,
-                                                           pulled_stats_event_list* data,
-                                                           void* cookie) {
+static AStatsManager_PullAtomCallbackReturn graphicsStatsPullCallback(int32_t atom_tag,
+                                                                      AStatsEventList* data,
+                                                                      void* cookie) {
     JNIEnv* env = getJNIEnv();
     if (!env) {
         return false;
     }
     if (gGraphicsStatsServiceObject == nullptr) {
         ALOGE("Failed to get graphicsstats service");
-        return STATS_PULL_SKIP;
+        return AStatsManager_PULL_SKIP;
     }
 
     for (bool lastFullDay : {true, false}) {
@@ -199,7 +199,7 @@
             env->ExceptionDescribe();
             env->ExceptionClear();
             ALOGE("Failed to invoke graphicsstats service");
-            return STATS_PULL_SKIP;
+            return AStatsManager_PULL_SKIP;
         }
         if (!jdata) {
             // null means data is not available for that day.
@@ -218,49 +218,51 @@
         if (!success) {
             ALOGW("Parse failed on GraphicsStatsPuller error='%s' dataSize='%d'",
                   serviceDump.InitializationErrorString().c_str(), dataSize);
-            return STATS_PULL_SKIP;
+            return AStatsManager_PULL_SKIP;
         }
 
         for (int stat_index = 0; stat_index < serviceDump.stats_size(); stat_index++) {
             auto& stat = serviceDump.stats(stat_index);
-            stats_event* event = add_stats_event_to_pull_data(data);
-            stats_event_set_atom_id(event, android::util::GRAPHICS_STATS);
-            stats_event_write_string8(event, stat.package_name().c_str());
-            stats_event_write_int64(event, (int64_t)stat.version_code());
-            stats_event_write_int64(event, (int64_t)stat.stats_start());
-            stats_event_write_int64(event, (int64_t)stat.stats_end());
-            stats_event_write_int32(event, (int32_t)stat.pipeline());
-            stats_event_write_int32(event, (int32_t)stat.summary().total_frames());
-            stats_event_write_int32(event, (int32_t)stat.summary().missed_vsync_count());
-            stats_event_write_int32(event, (int32_t)stat.summary().high_input_latency_count());
-            stats_event_write_int32(event, (int32_t)stat.summary().slow_ui_thread_count());
-            stats_event_write_int32(event, (int32_t)stat.summary().slow_bitmap_upload_count());
-            stats_event_write_int32(event, (int32_t)stat.summary().slow_draw_count());
-            stats_event_write_int32(event, (int32_t)stat.summary().missed_deadline_count());
+            AStatsEvent* event = AStatsEventList_addStatsEvent(data);
+            AStatsEvent_setAtomId(event, android::util::GRAPHICS_STATS);
+            AStatsEvent_writeString(event, stat.package_name().c_str());
+            AStatsEvent_writeInt64(event, (int64_t)stat.version_code());
+            AStatsEvent_writeInt64(event, (int64_t)stat.stats_start());
+            AStatsEvent_writeInt64(event, (int64_t)stat.stats_end());
+            AStatsEvent_writeInt32(event, (int32_t)stat.pipeline());
+            AStatsEvent_writeInt32(event, (int32_t)stat.summary().total_frames());
+            AStatsEvent_writeInt32(event, (int32_t)stat.summary().missed_vsync_count());
+            AStatsEvent_writeInt32(event, (int32_t)stat.summary().high_input_latency_count());
+            AStatsEvent_writeInt32(event, (int32_t)stat.summary().slow_ui_thread_count());
+            AStatsEvent_writeInt32(event, (int32_t)stat.summary().slow_bitmap_upload_count());
+            AStatsEvent_writeInt32(event, (int32_t)stat.summary().slow_draw_count());
+            AStatsEvent_writeInt32(event, (int32_t)stat.summary().missed_deadline_count());
             writeCpuHistogram(event, stat);
             writeGpuHistogram(event, stat);
             // TODO: fill in UI mainline module version, when the feature is available.
-            stats_event_write_int64(event, (int64_t)0);
-            stats_event_write_bool(event, !lastFullDay);
-            stats_event_build(event);
+            AStatsEvent_writeInt64(event, (int64_t)0);
+            AStatsEvent_writeBool(event, !lastFullDay);
+            AStatsEvent_build(event);
         }
     }
-    return STATS_PULL_SUCCESS;
+    return AStatsManager_PULL_SUCCESS;
 }
 
 // Register a puller for GRAPHICS_STATS atom with the statsd service.
 static void nativeInit(JNIEnv* env, jobject javaObject) {
     gGraphicsStatsServiceObject = env->NewGlobalRef(javaObject);
-    pull_atom_metadata metadata = {.cool_down_ns = 10 * 1000000, // 10 milliseconds
-                                   .timeout_ns = 2 * NS_PER_SEC, // 2 seconds
-                                   .additive_fields = nullptr,
-                                   .additive_fields_size = 0};
-    register_stats_pull_atom_callback(android::util::GRAPHICS_STATS, &graphicsStatsPullCallback,
-            &metadata, nullptr);
+    AStatsManager_PullAtomMetadata* metadata = AStatsManager_PullAtomMetadata_obtain();
+    AStatsManager_PullAtomMetadata_setCoolDownNs(metadata, 10 * 1000000);  // 10 milliseconds
+    AStatsManager_PullAtomMetadata_setTimeoutNs(metadata, 2 * NS_PER_SEC); // 2 seconds
+
+    AStatsManager_registerPullAtomCallback(android::util::GRAPHICS_STATS,
+                                           &graphicsStatsPullCallback, metadata, nullptr);
+
+    AStatsManager_PullAtomMetadata_release(metadata);
 }
 
 static void nativeDestructor(JNIEnv* env, jobject javaObject) {
-    //TODO: Unregister the puller callback when a new API is available.
+    AStatsManager_unregisterPullAtomCallback(android::util::GRAPHICS_STATS);
     env->DeleteGlobalRef(gGraphicsStatsServiceObject);
     gGraphicsStatsServiceObject = nullptr;
 }
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 212a3a6..49db3d5 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -1563,20 +1563,17 @@
 }
 
 static jboolean nativeTransferTouchFocus(JNIEnv* env,
-        jclass /* clazz */, jlong ptr, jobject fromChannelObj, jobject toChannelObj) {
-    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
-
-    sp<InputChannel> fromChannel =
-            android_view_InputChannel_getInputChannel(env, fromChannelObj);
-    sp<InputChannel> toChannel =
-            android_view_InputChannel_getInputChannel(env, toChannelObj);
-
-    if (fromChannel == nullptr || toChannel == nullptr) {
+        jclass /* clazz */, jlong ptr, jobject fromChannelTokenObj, jobject toChannelTokenObj) {
+    if (fromChannelTokenObj == nullptr || toChannelTokenObj == nullptr) {
         return JNI_FALSE;
     }
 
+    sp<IBinder> fromChannelToken = ibinderForJavaObject(env, fromChannelTokenObj);
+    sp<IBinder> toChannelToken = ibinderForJavaObject(env, toChannelTokenObj);
+
+    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
     if (im->getInputManager()->getDispatcher()->transferTouchFocus(
-            fromChannel->getConnectionToken(), toChannel->getConnectionToken())) {
+            fromChannelToken, toChannelToken)) {
         return JNI_TRUE;
     } else {
         return JNI_FALSE;
@@ -1784,7 +1781,7 @@
             (void*) nativeSetInputDispatchMode },
     { "nativeSetSystemUiVisibility", "(JI)V",
             (void*) nativeSetSystemUiVisibility },
-    { "nativeTransferTouchFocus", "(JLandroid/view/InputChannel;Landroid/view/InputChannel;)Z",
+    { "nativeTransferTouchFocus", "(JLandroid/os/IBinder;Landroid/os/IBinder;)Z",
             (void*) nativeTransferTouchFocus },
     { "nativeSetPointerSpeed", "(JI)V",
             (void*) nativeSetPointerSpeed },
diff --git a/services/core/jni/com_android_server_stats_pull_StatsPullAtomService.cpp b/services/core/jni/com_android_server_stats_pull_StatsPullAtomService.cpp
index f5b778e..43cd0a2 100644
--- a/services/core/jni/com_android_server_stats_pull_StatsPullAtomService.cpp
+++ b/services/core/jni/com_android_server_stats_pull_StatsPullAtomService.cpp
@@ -31,32 +31,32 @@
 static server::stats::PowerStatsPuller gPowerStatsPuller;
 static server::stats::SubsystemSleepStatePuller gSubsystemSleepStatePuller;
 
-static status_pull_atom_return_t onDevicePowerMeasurementCallback(int32_t atom_tag,
-                                                                  pulled_stats_event_list* data,
-                                                                  void* cookie) {
+static AStatsManager_PullAtomCallbackReturn onDevicePowerMeasurementCallback(int32_t atom_tag,
+                                                                             AStatsEventList* data,
+                                                                             void* cookie) {
     return gPowerStatsPuller.Pull(atom_tag, data);
 }
 
-static status_pull_atom_return_t subsystemSleepStateCallback(int32_t atom_tag,
-                                                             pulled_stats_event_list* data,
-                                                             void* cookie) {
+static AStatsManager_PullAtomCallbackReturn subsystemSleepStateCallback(int32_t atom_tag,
+                                                                        AStatsEventList* data,
+                                                                        void* cookie) {
     return gSubsystemSleepStatePuller.Pull(atom_tag, data);
 }
 
 static void nativeInit(JNIEnv* env, jobject javaObject) {
     // on device power measurement
     gPowerStatsPuller = server::stats::PowerStatsPuller();
-    register_stats_pull_atom_callback(android::util::ON_DEVICE_POWER_MEASUREMENT,
-                                      onDevicePowerMeasurementCallback,
-                                      /* metadata= */ nullptr,
-                                      /* cookie= */ nullptr);
+    AStatsManager_registerPullAtomCallback(android::util::ON_DEVICE_POWER_MEASUREMENT,
+                                           onDevicePowerMeasurementCallback,
+                                           /* metadata= */ nullptr,
+                                           /* cookie= */ nullptr);
 
     // subsystem sleep state
     gSubsystemSleepStatePuller = server::stats::SubsystemSleepStatePuller();
-    register_stats_pull_atom_callback(android::util::SUBSYSTEM_SLEEP_STATE,
-                                      subsystemSleepStateCallback,
-                                      /* metadata= */ nullptr,
-                                      /* cookie= */ nullptr);
+    AStatsManager_registerPullAtomCallback(android::util::SUBSYSTEM_SLEEP_STATE,
+                                           subsystemSleepStateCallback,
+                                           /* metadata= */ nullptr,
+                                           /* cookie= */ nullptr);
 }
 
 static const JNINativeMethod sMethods[] = {{"nativeInit", "()V", (void*)nativeInit}};
diff --git a/services/core/jni/stats/PowerStatsPuller.cpp b/services/core/jni/stats/PowerStatsPuller.cpp
index e80b5cf..d8f6faa 100644
--- a/services/core/jni/stats/PowerStatsPuller.cpp
+++ b/services/core/jni/stats/PowerStatsPuller.cpp
@@ -78,11 +78,12 @@
 
 PowerStatsPuller::PowerStatsPuller() {}
 
-status_pull_atom_return_t PowerStatsPuller::Pull(int32_t atomTag, pulled_stats_event_list* data) {
+AStatsManager_PullAtomCallbackReturn PowerStatsPuller::Pull(int32_t atomTag,
+                                                            AStatsEventList* data) {
     std::lock_guard<std::mutex> lock(gPowerStatsHalMutex);
 
     if (!getPowerStatsHalLocked()) {
-        return STATS_PULL_SKIP;
+        return AStatsManager_PULL_SKIP;
     }
 
     // Pull getRailInfo if necessary
@@ -100,14 +101,14 @@
         if (!resultSuccess || !ret.isOk()) {
             ALOGE("power.stats getRailInfo() failed. Description: %s", ret.description().c_str());
             gPowerStatsHal = nullptr;
-            return STATS_PULL_SKIP;
+            return AStatsManager_PULL_SKIP;
         }
         // If SUCCESS but empty, or if NOT_SUPPORTED, then never try again.
         if (gRailInfo.empty()) {
             ALOGE("power.stats has no rail information");
             gPowerStatsExist = false; // No rail info, so never try again.
             gPowerStatsHal = nullptr;
-            return STATS_PULL_SKIP;
+            return AStatsManager_PULL_SKIP;
         }
     }
 
@@ -134,15 +135,16 @@
                                             }
                                             const RailInfo& rail = gRailInfo[energyData.index];
 
-                                            stats_event* event = add_stats_event_to_pull_data(data);
-                                            stats_event_set_atom_id(event,
-                                                                    android::util::ON_DEVICE_POWER_MEASUREMENT);
-                                            stats_event_write_string8(event,
-                                                                      rail.subsysName.c_str());
-                                            stats_event_write_string8(event, rail.railName.c_str());
-                                            stats_event_write_int64(event, energyData.timestamp);
-                                            stats_event_write_int64(event, energyData.energy);
-                                            stats_event_build(event);
+                                            AStatsEvent* event =
+                                                    AStatsEventList_addStatsEvent(data);
+                                            AStatsEvent_setAtomId(
+                                                    event,
+                                                    android::util::ON_DEVICE_POWER_MEASUREMENT);
+                                            AStatsEvent_writeString(event, rail.subsysName.c_str());
+                                            AStatsEvent_writeString(event, rail.railName.c_str());
+                                            AStatsEvent_writeInt64(event, energyData.timestamp);
+                                            AStatsEvent_writeInt64(event, energyData.energy);
+                                            AStatsEvent_build(event);
 
                                             ALOGV("power.stat: %s.%s: %llu, %llu",
                                                   rail.subsysName.c_str(), rail.railName.c_str(),
@@ -153,9 +155,9 @@
     if (!resultSuccess || !ret.isOk()) {
         ALOGE("power.stats getEnergyData() failed. Description: %s", ret.description().c_str());
         gPowerStatsHal = nullptr;
-        return STATS_PULL_SKIP;
+        return AStatsManager_PULL_SKIP;
     }
-    return STATS_PULL_SUCCESS;
+    return AStatsManager_PULL_SUCCESS;
 }
 
 } // namespace stats
diff --git a/services/core/jni/stats/PowerStatsPuller.h b/services/core/jni/stats/PowerStatsPuller.h
index 048dbb9..db07d60 100644
--- a/services/core/jni/stats/PowerStatsPuller.h
+++ b/services/core/jni/stats/PowerStatsPuller.h
@@ -29,7 +29,7 @@
 class PowerStatsPuller {
 public:
     PowerStatsPuller();
-    status_pull_atom_return_t Pull(int32_t atomTag, pulled_stats_event_list* data);
+    AStatsManager_PullAtomCallbackReturn Pull(int32_t atomTag, AStatsEventList* data);
 };
 
 } // namespace stats
diff --git a/services/core/jni/stats/SubsystemSleepStatePuller.cpp b/services/core/jni/stats/SubsystemSleepStatePuller.cpp
index c6a836c..45afb5e 100644
--- a/services/core/jni/stats/SubsystemSleepStatePuller.cpp
+++ b/services/core/jni/stats/SubsystemSleepStatePuller.cpp
@@ -55,7 +55,7 @@
 namespace server {
 namespace stats {
 
-static std::function<status_pull_atom_return_t(int32_t atomTag, pulled_stats_event_list* data)>
+static std::function<AStatsManager_PullAtomCallbackReturn(int32_t atomTag, AStatsEventList* data)>
         gPuller = {};
 
 static sp<android::hardware::power::V1_0::IPower> gPowerHalV1_0 = nullptr;
@@ -176,12 +176,12 @@
 }
 
 // The caller must be holding gPowerHalMutex.
-static status_pull_atom_return_t getIPowerStatsDataLocked(int32_t atomTag,
-                                                          pulled_stats_event_list* data) {
+static AStatsManager_PullAtomCallbackReturn getIPowerStatsDataLocked(int32_t atomTag,
+                                                                     AStatsEventList* data) {
     using android::hardware::power::stats::V1_0::Status;
 
     if(!getPowerStatsHalLocked()) {
-        return STATS_PULL_SKIP;
+        return AStatsManager_PULL_SKIP;
     }
     // Get power entity state residency data
     bool success = false;
@@ -194,17 +194,17 @@
                 }
                 for (auto result : results) {
                     for (auto stateResidency : result.stateResidencyData) {
-                        stats_event* event = add_stats_event_to_pull_data(data);
-                        stats_event_set_atom_id(event, android::util::SUBSYSTEM_SLEEP_STATE);
-                        stats_event_write_string8(event,
-                                                  gEntityNames.at(result.powerEntityId).c_str());
-                        stats_event_write_string8(event,
-                                                  gStateNames.at(result.powerEntityId)
-                                                          .at(stateResidency.powerEntityStateId)
-                                                          .c_str());
-                        stats_event_write_int64(event, stateResidency.totalStateEntryCount);
-                        stats_event_write_int64(event, stateResidency.totalTimeInStateMs);
-                        stats_event_build(event);
+                        AStatsEvent* event = AStatsEventList_addStatsEvent(data);
+                        AStatsEvent_setAtomId(event, android::util::SUBSYSTEM_SLEEP_STATE);
+                        AStatsEvent_writeString(event,
+                                                gEntityNames.at(result.powerEntityId).c_str());
+                        AStatsEvent_writeString(event,
+                                                gStateNames.at(result.powerEntityId)
+                                                        .at(stateResidency.powerEntityStateId)
+                                                        .c_str());
+                        AStatsEvent_writeInt64(event, stateResidency.totalStateEntryCount);
+                        AStatsEvent_writeInt64(event, stateResidency.totalTimeInStateMs);
+                        AStatsEvent_build(event);
                     }
                 }
                 success = true;
@@ -213,9 +213,9 @@
     // bool success determines if this succeeded or not.
     checkResultLocked(ret, __func__);
     if (!success) {
-        return STATS_PULL_SKIP;
+        return AStatsManager_PULL_SKIP;
     }
-    return STATS_PULL_SUCCESS;
+    return AStatsManager_PULL_SUCCESS;
 }
 
 // The caller must be holding gPowerHalMutex.
@@ -244,12 +244,12 @@
 }
 
 // The caller must be holding gPowerHalMutex.
-static status_pull_atom_return_t getIPowerDataLocked(int32_t atomTag,
-                                                     pulled_stats_event_list* data) {
+static AStatsManager_PullAtomCallbackReturn getIPowerDataLocked(int32_t atomTag,
+                                                                AStatsEventList* data) {
     using android::hardware::power::V1_0::Status;
 
     if(!getPowerHalLocked()) {
-        return STATS_PULL_SKIP;
+        return AStatsManager_PULL_SKIP;
     }
 
         Return<void> ret;
@@ -259,26 +259,26 @@
 
                     for (size_t i = 0; i < states.size(); i++) {
                         const PowerStatePlatformSleepState& state = states[i];
-                        stats_event* event = add_stats_event_to_pull_data(data);
-                        stats_event_set_atom_id(event, android::util::SUBSYSTEM_SLEEP_STATE);
-                        stats_event_write_string8(event, state.name.c_str());
-                        stats_event_write_string8(event, "");
-                        stats_event_write_int64(event, state.totalTransitions);
-                        stats_event_write_int64(event, state.residencyInMsecSinceBoot);
-                        stats_event_build(event);
+                        AStatsEvent* event = AStatsEventList_addStatsEvent(data);
+                        AStatsEvent_setAtomId(event, android::util::SUBSYSTEM_SLEEP_STATE);
+                        AStatsEvent_writeString(event, state.name.c_str());
+                        AStatsEvent_writeString(event, "");
+                        AStatsEvent_writeInt64(event, state.totalTransitions);
+                        AStatsEvent_writeInt64(event, state.residencyInMsecSinceBoot);
+                        AStatsEvent_build(event);
 
                         ALOGV("powerstate: %s, %lld, %lld, %d", state.name.c_str(),
                               (long long)state.residencyInMsecSinceBoot,
                               (long long)state.totalTransitions,
                               state.supportedOnlyInSuspend ? 1 : 0);
                         for (const auto& voter : state.voters) {
-                            stats_event* event = add_stats_event_to_pull_data(data);
-                            stats_event_set_atom_id(event, android::util::SUBSYSTEM_SLEEP_STATE);
-                            stats_event_write_string8(event, state.name.c_str());
-                            stats_event_write_string8(event, voter.name.c_str());
-                            stats_event_write_int64(event, voter.totalNumberOfTimesVotedSinceBoot);
-                            stats_event_write_int64(event, voter.totalTimeInMsecVotedForSinceBoot);
-                            stats_event_build(event);
+                            AStatsEvent* event = AStatsEventList_addStatsEvent(data);
+                            AStatsEvent_setAtomId(event, android::util::SUBSYSTEM_SLEEP_STATE);
+                            AStatsEvent_writeString(event, state.name.c_str());
+                            AStatsEvent_writeString(event, voter.name.c_str());
+                            AStatsEvent_writeInt64(event, voter.totalNumberOfTimesVotedSinceBoot);
+                            AStatsEvent_writeInt64(event, voter.totalTimeInMsecVotedForSinceBoot);
+                            AStatsEvent_build(event);
 
                             ALOGV("powerstatevoter: %s, %s, %lld, %lld", state.name.c_str(),
                                   voter.name.c_str(),
@@ -288,7 +288,7 @@
                     }
                 });
         if (!checkResultLocked(ret, __func__)) {
-            return STATS_PULL_SKIP;
+            return AStatsManager_PULL_SKIP;
         }
 
         // Trying to cast to IPower 1.1, this will succeed only for devices supporting 1.1
@@ -305,14 +305,14 @@
                                 for (size_t j = 0; j < subsystem.states.size(); j++) {
                                     const PowerStateSubsystemSleepState& state =
                                             subsystem.states[j];
-                                    stats_event* event = add_stats_event_to_pull_data(data);
-                                    stats_event_set_atom_id(event,
-                                                            android::util::SUBSYSTEM_SLEEP_STATE);
-                                    stats_event_write_string8(event, subsystem.name.c_str());
-                                    stats_event_write_string8(event, state.name.c_str());
-                                    stats_event_write_int64(event, state.totalTransitions);
-                                    stats_event_write_int64(event, state.residencyInMsecSinceBoot);
-                                    stats_event_build(event);
+                                    AStatsEvent* event = AStatsEventList_addStatsEvent(data);
+                                    AStatsEvent_setAtomId(event,
+                                                          android::util::SUBSYSTEM_SLEEP_STATE);
+                                    AStatsEvent_writeString(event, subsystem.name.c_str());
+                                    AStatsEvent_writeString(event, state.name.c_str());
+                                    AStatsEvent_writeInt64(event, state.totalTransitions);
+                                    AStatsEvent_writeInt64(event, state.residencyInMsecSinceBoot);
+                                    AStatsEvent_build(event);
 
                                     ALOGV("subsystemstate: %s, %s, %lld, %lld, %lld",
                                           subsystem.name.c_str(), state.name.c_str(),
@@ -324,14 +324,14 @@
                         }
                     });
         }
-        return STATS_PULL_SUCCESS;
+        return AStatsManager_PULL_SUCCESS;
 }
 
 // The caller must be holding gPowerHalMutex.
-std::function<status_pull_atom_return_t(int32_t atomTag, pulled_stats_event_list* data)>
+std::function<AStatsManager_PullAtomCallbackReturn(int32_t atomTag, AStatsEventList* data)>
 getPullerLocked() {
-    std::function<status_pull_atom_return_t(int32_t atomTag, pulled_stats_event_list * data)> ret =
-            {};
+    std::function<AStatsManager_PullAtomCallbackReturn(int32_t atomTag, AStatsEventList * data)>
+            ret = {};
 
     // First see if power.stats HAL is available. Fall back to power HAL if
     // power.stats HAL is unavailable.
@@ -346,8 +346,8 @@
     return ret;
 }
 
-status_pull_atom_return_t SubsystemSleepStatePuller::Pull(int32_t atomTag,
-                                                          pulled_stats_event_list* data) {
+AStatsManager_PullAtomCallbackReturn SubsystemSleepStatePuller::Pull(int32_t atomTag,
+                                                                     AStatsEventList* data) {
     std::lock_guard<std::mutex> lock(gPowerHalMutex);
 
     if(!gPuller) {
@@ -359,7 +359,7 @@
     }
 
     ALOGE("Unable to load Power Hal or power.stats HAL");
-    return STATS_PULL_SKIP;
+    return AStatsManager_PULL_SKIP;
 }
 
 } // namespace stats
diff --git a/services/core/jni/stats/SubsystemSleepStatePuller.h b/services/core/jni/stats/SubsystemSleepStatePuller.h
index 59dbbd2..da9679c 100644
--- a/services/core/jni/stats/SubsystemSleepStatePuller.h
+++ b/services/core/jni/stats/SubsystemSleepStatePuller.h
@@ -29,7 +29,7 @@
 class SubsystemSleepStatePuller {
 public:
     SubsystemSleepStatePuller();
-    status_pull_atom_return_t Pull(int32_t atomTag, pulled_stats_event_list* data);
+    AStatsManager_PullAtomCallbackReturn Pull(int32_t atomTag, AStatsEventList* data);
 };
 
 } // namespace stats
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 e0c6d6a..39a749f 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -96,6 +96,7 @@
 import android.util.ArraySet;
 import android.util.Pair;
 
+import androidx.test.filters.FlakyTest;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.R;
@@ -5446,26 +5447,28 @@
         assertTrue(dpms.isAdminActive(admin1, UserHandle.USER_SYSTEM));
     }
 
-    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();
+    // }
 
-    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 {
@@ -5487,29 +5490,31 @@
         UserHandle userHandle = UserHandle.of(DpmMockContext.CALLER_USER_HANDLE);
     }
 
-    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();
+    // }
 
-    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/timedetector/TimeDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
index ae53692..218f43c 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorServiceTest.java
@@ -33,14 +33,13 @@
 import android.app.timedetector.PhoneTimeSuggestion;
 import android.content.Context;
 import android.content.pm.PackageManager;
-import android.os.Handler;
 import android.os.HandlerThread;
-import android.os.Looper;
-import android.os.Message;
 import android.os.TimestampedValue;
 
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.server.timezonedetector.TestHandler;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -108,7 +107,7 @@
                 eq(android.Manifest.permission.SUGGEST_PHONE_TIME_AND_ZONE),
                 anyString());
 
-        mTestHandler.waitForEmptyQueue();
+        mTestHandler.waitForMessagesToBeProcessed();
         mStubbedTimeDetectorStrategy.verifySuggestPhoneTimeCalled(phoneTimeSuggestion);
     }
 
@@ -140,7 +139,7 @@
                 eq(android.Manifest.permission.SUGGEST_MANUAL_TIME_AND_ZONE),
                 anyString());
 
-        mTestHandler.waitForEmptyQueue();
+        mTestHandler.waitForMessagesToBeProcessed();
         mStubbedTimeDetectorStrategy.verifySuggestManualTimeCalled(manualTimeSuggestion);
     }
 
@@ -170,7 +169,7 @@
         verify(mMockContext).enforceCallingOrSelfPermission(
                 eq(android.Manifest.permission.SET_TIME), anyString());
 
-        mTestHandler.waitForEmptyQueue();
+        mTestHandler.waitForMessagesToBeProcessed();
         mStubbedTimeDetectorStrategy.verifySuggestNetworkTimeCalled(NetworkTimeSuggestion);
     }
 
@@ -187,21 +186,23 @@
 
     @Test
     public void testAutoTimeDetectionToggle() throws Exception {
-        mTimeDetectorService.handleAutoTimeDetectionToggle();
+        mTimeDetectorService.handleAutoTimeDetectionChanged();
         mTestHandler.assertTotalMessagesEnqueued(1);
-        mTestHandler.waitForEmptyQueue();
-        mStubbedTimeDetectorStrategy.verifyHandleAutoTimeDetectionToggleCalled();
+        mTestHandler.waitForMessagesToBeProcessed();
+        mStubbedTimeDetectorStrategy.verifyHandleAutoTimeDetectionChangedCalled();
 
-        mTimeDetectorService.handleAutoTimeDetectionToggle();
+        mStubbedTimeDetectorStrategy.resetCallTracking();
+
+        mTimeDetectorService.handleAutoTimeDetectionChanged();
         mTestHandler.assertTotalMessagesEnqueued(2);
-        mTestHandler.waitForEmptyQueue();
-        mStubbedTimeDetectorStrategy.verifyHandleAutoTimeDetectionToggleCalled();
+        mTestHandler.waitForMessagesToBeProcessed();
+        mStubbedTimeDetectorStrategy.verifyHandleAutoTimeDetectionChangedCalled();
     }
 
     private static PhoneTimeSuggestion createPhoneTimeSuggestion() {
-        int phoneId = 1234;
+        int slotIndex = 1234;
         TimestampedValue<Long> timeValue = new TimestampedValue<>(100L, 1_000_000L);
-        return new PhoneTimeSuggestion.Builder(phoneId)
+        return new PhoneTimeSuggestion.Builder(slotIndex)
                 .setUtcTime(timeValue)
                 .build();
     }
@@ -222,7 +223,7 @@
         private PhoneTimeSuggestion mLastPhoneSuggestion;
         private ManualTimeSuggestion mLastManualSuggestion;
         private NetworkTimeSuggestion mLastNetworkSuggestion;
-        private boolean mLastAutoTimeDetectionToggleCalled;
+        private boolean mHandleAutoTimeDetectionChangedCalled;
         private boolean mDumpCalled;
 
         @Override
@@ -231,31 +232,26 @@
 
         @Override
         public void suggestPhoneTime(PhoneTimeSuggestion timeSuggestion) {
-            resetCallTracking();
             mLastPhoneSuggestion = timeSuggestion;
         }
 
         @Override
         public void suggestManualTime(ManualTimeSuggestion timeSuggestion) {
-            resetCallTracking();
             mLastManualSuggestion = timeSuggestion;
         }
 
         @Override
         public void suggestNetworkTime(NetworkTimeSuggestion timeSuggestion) {
-            resetCallTracking();
             mLastNetworkSuggestion = timeSuggestion;
         }
 
         @Override
         public void handleAutoTimeDetectionChanged() {
-            resetCallTracking();
-            mLastAutoTimeDetectionToggleCalled = true;
+            mHandleAutoTimeDetectionChangedCalled = true;
         }
 
         @Override
         public void dump(PrintWriter pw, String[] args) {
-            resetCallTracking();
             mDumpCalled = true;
         }
 
@@ -263,7 +259,7 @@
             mLastPhoneSuggestion = null;
             mLastManualSuggestion = null;
             mLastNetworkSuggestion = null;
-            mLastAutoTimeDetectionToggleCalled = false;
+            mHandleAutoTimeDetectionChangedCalled = false;
             mDumpCalled = false;
         }
 
@@ -279,45 +275,12 @@
             assertEquals(expectedSuggestion, mLastNetworkSuggestion);
         }
 
-        void verifyHandleAutoTimeDetectionToggleCalled() {
-            assertTrue(mLastAutoTimeDetectionToggleCalled);
+        void verifyHandleAutoTimeDetectionChangedCalled() {
+            assertTrue(mHandleAutoTimeDetectionChangedCalled);
         }
 
         void verifyDumpCalled() {
             assertTrue(mDumpCalled);
         }
     }
-
-    /**
-     * A Handler that can track posts/sends and wait for work to be completed.
-     */
-    private static class TestHandler extends Handler {
-
-        private int mMessagesSent;
-
-        TestHandler(Looper looper) {
-            super(looper);
-        }
-
-        @Override
-        public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
-            mMessagesSent++;
-            return super.sendMessageAtTime(msg, uptimeMillis);
-        }
-
-        /** Asserts the number of messages posted or sent is as expected. */
-        void assertTotalMessagesEnqueued(int expected) {
-            assertEquals(expected, mMessagesSent);
-        }
-
-        /**
-         * Waits for all currently enqueued work due to be processed to be completed before
-         * returning.
-         */
-        void waitForEmptyQueue() throws InterruptedException {
-            while (!getLooper().getQueue().isIdle()) {
-                Thread.sleep(100);
-            }
-        }
-    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TestHandler.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TestHandler.java
new file mode 100644
index 0000000..21c9685
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TestHandler.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.server.timezonedetector;
+
+import static org.junit.Assert.assertEquals;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+
+/**
+ * A Handler that can track posts/sends and wait for them to be completed.
+ */
+public class TestHandler extends Handler {
+
+    private final Object mMonitor = new Object();
+    private int mMessagesProcessed = 0;
+    private int mMessagesSent = 0;
+
+    public TestHandler(Looper looper) {
+        super(looper);
+    }
+
+    @Override
+    public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
+        synchronized (mMonitor) {
+            mMessagesSent++;
+        }
+
+        Runnable callback = msg.getCallback();
+        // Have the callback increment the mMessagesProcessed when it is done. It will notify
+        // any threads waiting for all messages to be processed if appropriate.
+        Runnable newCallback = () -> {
+            callback.run();
+            synchronized (mMonitor) {
+                mMessagesProcessed++;
+                if (mMessagesSent == mMessagesProcessed) {
+                    mMonitor.notifyAll();
+                }
+            }
+        };
+        msg.setCallback(newCallback);
+        return super.sendMessageAtTime(msg, uptimeMillis);
+    }
+
+    /** Asserts the number of messages posted or sent is as expected. */
+    public void assertTotalMessagesEnqueued(int expected) {
+        synchronized (mMonitor) {
+            assertEquals(expected, mMessagesSent);
+        }
+    }
+
+    /**
+     * Waits for all enqueued work to be completed before returning.
+     */
+    public void waitForMessagesToBeProcessed() throws InterruptedException {
+        synchronized (mMonitor) {
+            if (mMessagesSent != mMessagesProcessed) {
+                mMonitor.wait();
+            }
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
new file mode 100644
index 0000000..3e7d40a
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.timezonedetector;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.timezonedetector.ManualTimeZoneSuggestion;
+import android.app.timezonedetector.PhoneTimeZoneSuggestion;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.HandlerThread;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.PrintWriter;
+
+@RunWith(AndroidJUnit4.class)
+public class TimeZoneDetectorServiceTest {
+
+    private Context mMockContext;
+    private StubbedTimeZoneDetectorStrategy mStubbedTimeZoneDetectorStrategy;
+
+    private TimeZoneDetectorService mTimeZoneDetectorService;
+    private HandlerThread mHandlerThread;
+    private TestHandler mTestHandler;
+
+
+    @Before
+    public void setUp() {
+        mMockContext = mock(Context.class);
+
+        // Create a thread + handler for processing the work that the service posts.
+        mHandlerThread = new HandlerThread("TimeZoneDetectorServiceTest");
+        mHandlerThread.start();
+        mTestHandler = new TestHandler(mHandlerThread.getLooper());
+
+        mStubbedTimeZoneDetectorStrategy = new StubbedTimeZoneDetectorStrategy();
+
+        mTimeZoneDetectorService = new TimeZoneDetectorService(
+                mMockContext, mTestHandler, mStubbedTimeZoneDetectorStrategy);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mHandlerThread.quit();
+        mHandlerThread.join();
+    }
+
+    @Test(expected = SecurityException.class)
+    public void testSuggestPhoneTime_withoutPermission() {
+        doThrow(new SecurityException("Mock"))
+                .when(mMockContext).enforceCallingPermission(anyString(), any());
+        PhoneTimeZoneSuggestion timeZoneSuggestion = createPhoneTimeZoneSuggestion();
+
+        try {
+            mTimeZoneDetectorService.suggestPhoneTimeZone(timeZoneSuggestion);
+            fail();
+        } finally {
+            verify(mMockContext).enforceCallingPermission(
+                    eq(android.Manifest.permission.SUGGEST_PHONE_TIME_AND_ZONE),
+                    anyString());
+        }
+    }
+
+    @Test
+    public void testSuggestPhoneTimeZone() throws Exception {
+        doNothing().when(mMockContext).enforceCallingPermission(anyString(), any());
+
+        PhoneTimeZoneSuggestion timeZoneSuggestion = createPhoneTimeZoneSuggestion();
+        mTimeZoneDetectorService.suggestPhoneTimeZone(timeZoneSuggestion);
+        mTestHandler.assertTotalMessagesEnqueued(1);
+
+        verify(mMockContext).enforceCallingPermission(
+                eq(android.Manifest.permission.SUGGEST_PHONE_TIME_AND_ZONE),
+                anyString());
+
+        mTestHandler.waitForMessagesToBeProcessed();
+        mStubbedTimeZoneDetectorStrategy.verifySuggestPhoneTimeZoneCalled(timeZoneSuggestion);
+    }
+
+    @Test(expected = SecurityException.class)
+    public void testSuggestManualTime_withoutPermission() {
+        doThrow(new SecurityException("Mock"))
+                .when(mMockContext).enforceCallingOrSelfPermission(anyString(), any());
+        ManualTimeZoneSuggestion timeZoneSuggestion = createManualTimeZoneSuggestion();
+
+        try {
+            mTimeZoneDetectorService.suggestManualTimeZone(timeZoneSuggestion);
+            fail();
+        } finally {
+            verify(mMockContext).enforceCallingOrSelfPermission(
+                    eq(android.Manifest.permission.SUGGEST_MANUAL_TIME_AND_ZONE),
+                    anyString());
+        }
+    }
+
+    @Test
+    public void testSuggestManualTimeZone() throws Exception {
+        doNothing().when(mMockContext).enforceCallingOrSelfPermission(anyString(), any());
+
+        ManualTimeZoneSuggestion timeZoneSuggestion = createManualTimeZoneSuggestion();
+        mTimeZoneDetectorService.suggestManualTimeZone(timeZoneSuggestion);
+        mTestHandler.assertTotalMessagesEnqueued(1);
+
+        verify(mMockContext).enforceCallingOrSelfPermission(
+                eq(android.Manifest.permission.SUGGEST_MANUAL_TIME_AND_ZONE),
+                anyString());
+
+        mTestHandler.waitForMessagesToBeProcessed();
+        mStubbedTimeZoneDetectorStrategy.verifySuggestManualTimeZoneCalled(timeZoneSuggestion);
+    }
+
+    @Test
+    public void testDump() {
+        when(mMockContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP))
+                .thenReturn(PackageManager.PERMISSION_GRANTED);
+
+        mTimeZoneDetectorService.dump(null, null, null);
+
+        verify(mMockContext).checkCallingOrSelfPermission(eq(android.Manifest.permission.DUMP));
+        mStubbedTimeZoneDetectorStrategy.verifyDumpCalled();
+    }
+
+    @Test
+    public void testAutoTimeZoneDetectionChanged() throws Exception {
+        mTimeZoneDetectorService.handleAutoTimeZoneDetectionChanged();
+        mTestHandler.assertTotalMessagesEnqueued(1);
+        mTestHandler.waitForMessagesToBeProcessed();
+        mStubbedTimeZoneDetectorStrategy.verifyHandleAutoTimeZoneDetectionChangedCalled();
+
+        mStubbedTimeZoneDetectorStrategy.resetCallTracking();
+
+        mTimeZoneDetectorService.handleAutoTimeZoneDetectionChanged();
+        mTestHandler.assertTotalMessagesEnqueued(2);
+        mTestHandler.waitForMessagesToBeProcessed();
+        mStubbedTimeZoneDetectorStrategy.verifyHandleAutoTimeZoneDetectionChangedCalled();
+    }
+
+    private static PhoneTimeZoneSuggestion createPhoneTimeZoneSuggestion() {
+        int slotIndex = 1234;
+        return new PhoneTimeZoneSuggestion.Builder(slotIndex)
+                .setZoneId("TestZoneId")
+                .setMatchType(PhoneTimeZoneSuggestion.MATCH_TYPE_NETWORK_COUNTRY_AND_OFFSET)
+                .setQuality(PhoneTimeZoneSuggestion.QUALITY_SINGLE_ZONE)
+                .build();
+    }
+
+    private static ManualTimeZoneSuggestion createManualTimeZoneSuggestion() {
+        return new ManualTimeZoneSuggestion("TestZoneId");
+    }
+
+    private static class StubbedTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy {
+
+        // Call tracking.
+        private PhoneTimeZoneSuggestion mLastPhoneSuggestion;
+        private ManualTimeZoneSuggestion mLastManualSuggestion;
+        private boolean mHandleAutoTimeZoneDetectionChangedCalled;
+        private boolean mDumpCalled;
+
+        @Override
+        public void suggestPhoneTimeZone(PhoneTimeZoneSuggestion timeZoneSuggestion) {
+            mLastPhoneSuggestion = timeZoneSuggestion;
+        }
+
+        @Override
+        public void suggestManualTimeZone(ManualTimeZoneSuggestion timeZoneSuggestion) {
+            mLastManualSuggestion = timeZoneSuggestion;
+        }
+
+        @Override
+        public void handleAutoTimeZoneDetectionChanged() {
+            mHandleAutoTimeZoneDetectionChangedCalled = true;
+        }
+
+        @Override
+        public void dump(PrintWriter pw, String[] args) {
+            mDumpCalled = true;
+        }
+
+        void resetCallTracking() {
+            mLastPhoneSuggestion = null;
+            mLastManualSuggestion = null;
+            mHandleAutoTimeZoneDetectionChangedCalled = false;
+            mDumpCalled = false;
+        }
+
+        void verifySuggestPhoneTimeZoneCalled(PhoneTimeZoneSuggestion expectedSuggestion) {
+            assertEquals(expectedSuggestion, mLastPhoneSuggestion);
+        }
+
+        public void verifySuggestManualTimeZoneCalled(ManualTimeZoneSuggestion expectedSuggestion) {
+            assertEquals(expectedSuggestion, mLastManualSuggestion);
+        }
+
+        void verifyHandleAutoTimeZoneDetectionChangedCalled() {
+            assertTrue(mHandleAutoTimeZoneDetectionChangedCalled);
+        }
+
+        void verifyDumpCalled() {
+            assertTrue(mDumpCalled);
+        }
+    }
+
+}
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
similarity index 96%
rename from services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyTest.java
rename to services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
index 2429cfc..1e38711 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
@@ -24,12 +24,12 @@
 import static android.app.timezonedetector.PhoneTimeZoneSuggestion.QUALITY_MULTIPLE_ZONES_WITH_SAME_OFFSET;
 import static android.app.timezonedetector.PhoneTimeZoneSuggestion.QUALITY_SINGLE_ZONE;
 
-import static com.android.server.timezonedetector.TimeZoneDetectorStrategy.PHONE_SCORE_HIGH;
-import static com.android.server.timezonedetector.TimeZoneDetectorStrategy.PHONE_SCORE_HIGHEST;
-import static com.android.server.timezonedetector.TimeZoneDetectorStrategy.PHONE_SCORE_LOW;
-import static com.android.server.timezonedetector.TimeZoneDetectorStrategy.PHONE_SCORE_MEDIUM;
-import static com.android.server.timezonedetector.TimeZoneDetectorStrategy.PHONE_SCORE_NONE;
-import static com.android.server.timezonedetector.TimeZoneDetectorStrategy.PHONE_SCORE_USAGE_THRESHOLD;
+import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.PHONE_SCORE_HIGH;
+import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.PHONE_SCORE_HIGHEST;
+import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.PHONE_SCORE_LOW;
+import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.PHONE_SCORE_MEDIUM;
+import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.PHONE_SCORE_NONE;
+import static com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.PHONE_SCORE_USAGE_THRESHOLD;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -41,7 +41,7 @@
 import android.app.timezonedetector.PhoneTimeZoneSuggestion.MatchType;
 import android.app.timezonedetector.PhoneTimeZoneSuggestion.Quality;
 
-import com.android.server.timezonedetector.TimeZoneDetectorStrategy.QualifiedPhoneTimeZoneSuggestion;
+import com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.QualifiedPhoneTimeZoneSuggestion;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -52,9 +52,9 @@
 import java.util.LinkedList;
 
 /**
- * White-box unit tests for {@link TimeZoneDetectorStrategy}.
+ * White-box unit tests for {@link TimeZoneDetectorStrategyImpl}.
  */
-public class TimeZoneDetectorStrategyTest {
+public class TimeZoneDetectorStrategyImplTest {
 
     /** A time zone used for initialization that does not occur elsewhere in tests. */
     private static final String ARBITRARY_TIME_ZONE_ID = "Etc/UTC";
@@ -78,14 +78,14 @@
             newTestCase(MATCH_TYPE_EMULATOR_ZONE_ID, QUALITY_SINGLE_ZONE, PHONE_SCORE_HIGHEST),
     };
 
-    private TimeZoneDetectorStrategy mTimeZoneDetectorStrategy;
+    private TimeZoneDetectorStrategyImpl mTimeZoneDetectorStrategy;
     private FakeTimeZoneDetectorStrategyCallback mFakeTimeZoneDetectorStrategyCallback;
 
     @Before
     public void setUp() {
         mFakeTimeZoneDetectorStrategyCallback = new FakeTimeZoneDetectorStrategyCallback();
         mTimeZoneDetectorStrategy =
-                new TimeZoneDetectorStrategy(mFakeTimeZoneDetectorStrategyCallback);
+                new TimeZoneDetectorStrategyImpl(mFakeTimeZoneDetectorStrategyCallback);
     }
 
     @Test
@@ -364,7 +364,7 @@
     }
 
     /**
-     * The {@link TimeZoneDetectorStrategy.Callback} is left to detect whether changing the time
+     * The {@link TimeZoneDetectorStrategyImpl.Callback} is left to detect whether changing the time
      * zone is actually necessary. This test proves that the service doesn't assume it knows the
      * current setting.
      */
@@ -441,7 +441,8 @@
         return new PhoneTimeZoneSuggestion.Builder(PHONE2_ID).build();
     }
 
-    static class FakeTimeZoneDetectorStrategyCallback implements TimeZoneDetectorStrategy.Callback {
+    static class FakeTimeZoneDetectorStrategyCallback
+            implements TimeZoneDetectorStrategyImpl.Callback {
 
         private boolean mAutoTimeZoneDetectionEnabled;
         private TestState<String> mTimeZoneId = new TestState<>();
@@ -560,7 +561,7 @@
 
         Script autoTimeZoneDetectionEnabled(boolean enabled) {
             mFakeTimeZoneDetectorStrategyCallback.setAutoTimeZoneDetectionEnabled(enabled);
-            mTimeZoneDetectorStrategy.handleAutoTimeZoneDetectionChange();
+            mTimeZoneDetectorStrategy.handleAutoTimeZoneDetectionChanged();
             return this;
         }
 
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index ad5be43..e0ee3ce 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -70,6 +70,7 @@
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
@@ -3644,6 +3645,33 @@
     }
 
     @Test
+    public void updateUriPermissions_posterDoesNotOwnUri() throws Exception {
+        NotificationChannel c = new NotificationChannel(
+                TEST_CHANNEL_ID, TEST_CHANNEL_ID, IMPORTANCE_DEFAULT);
+        c.setSound(null, Notification.AUDIO_ATTRIBUTES_DEFAULT);
+        Message message1 = new Message("", 0, "");
+        message1.setData("",
+                ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, 1));
+
+        Notification.Builder nbA = new Notification.Builder(mContext, c.getId())
+                .setContentTitle("foo")
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .setStyle(new Notification.MessagingStyle("")
+                        .addMessage(message1));
+        NotificationRecord recordA = new NotificationRecord(mContext, new StatusBarNotification(
+                PKG, PKG, 0, "tag", mUid, 0, nbA.build(), new UserHandle(mUid), null, 0), c);
+
+        doThrow(new SecurityException("no access")).when(mUgm)
+                .grantUriPermissionFromOwner(
+                        any(), anyInt(), any(), any(), anyInt(), anyInt(), anyInt());
+
+        when(mUgmInternal.newUriPermissionOwner(any())).thenReturn(new Binder());
+        mService.updateUriPermissions(recordA, null, mContext.getPackageName(),  USER_SYSTEM);
+
+        // yay, no crash
+    }
+
+    @Test
     public void testVisitUris() throws Exception {
         final Uri audioContents = Uri.parse("content://com.example/audio");
         final Uri backgroundImage = Uri.parse("content://com.example/background");
diff --git a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
index 5acc0f2..956c200 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
@@ -126,7 +126,8 @@
         mTarget = new TestDragDropController(mWm, mWm.mH.getLooper());
         mWindow = createDropTargetWindow("Drag test window", 0);
         doReturn(mWindow).when(mDisplayContent).getTouchableWinAtPointLocked(0, 0);
-        when(mWm.mInputManager.transferTouchFocus(any(), any())).thenReturn(true);
+        when(mWm.mInputManager.transferTouchFocus(any(InputChannel.class),
+                any(InputChannel.class))).thenReturn(true);
 
         mWm.mWindowMap.put(mWindow.mClient.asBinder(), mWindow);
     }
@@ -176,7 +177,8 @@
                     .setFormat(PixelFormat.TRANSLUCENT)
                     .build();
 
-            assertTrue(mWm.mInputManager.transferTouchFocus(null, null));
+            assertTrue(mWm.mInputManager.transferTouchFocus(new InputChannel(),
+                    new InputChannel()));
             mToken = mTarget.performDrag(
                     new SurfaceSession(), 0, 0, mWindow.mClient, flag, surface, 0, 0, 0, 0, 0,
                     data);
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/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java
index 50c312c..b4f32e7 100644
--- a/tests/net/java/com/android/server/ConnectivityServiceTest.java
+++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java
@@ -24,6 +24,7 @@
 import static android.content.pm.PackageManager.PERMISSION_DENIED;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport;
+import static android.net.ConnectivityDiagnosticsManager.DataStallReport;
 import static android.net.ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN;
 import static android.net.ConnectivityManager.CONNECTIVITY_ACTION;
 import static android.net.ConnectivityManager.CONNECTIVITY_ACTION_SUPL;
@@ -570,6 +571,9 @@
                 | NETWORK_VALIDATION_RESULT_PARTIAL;
         private static final int VALIDATION_RESULT_INVALID = 0;
 
+        private static final long DATA_STALL_TIMESTAMP = 10L;
+        private static final int DATA_STALL_DETECTION_METHOD = 1;
+
         private INetworkMonitor mNetworkMonitor;
         private INetworkMonitorCallbacks mNmCallbacks;
         private int mNmValidationResult = VALIDATION_RESULT_BASE;
@@ -577,6 +581,7 @@
         private int mProbesSucceeded;
         private String mNmValidationRedirectUrl = null;
         private PersistableBundle mValidationExtras = PersistableBundle.EMPTY;
+        private PersistableBundle mDataStallExtras = PersistableBundle.EMPTY;
         private boolean mNmProvNotificationRequested = false;
 
         private final ConditionVariable mNetworkStatusReceived = new ConditionVariable();
@@ -804,6 +809,11 @@
         public void expectPreventReconnectReceived() {
             expectPreventReconnectReceived(TIMEOUT_MS);
         }
+
+        void notifyDataStallSuspected() throws Exception {
+            mNmCallbacks.notifyDataStallSuspected(
+                    DATA_STALL_TIMESTAMP, DATA_STALL_DETECTION_METHOD, mDataStallExtras);
+        }
     }
 
     /**
@@ -6599,8 +6609,7 @@
         mServiceContext.setPermission(perm, PERMISSION_GRANTED);
     }
 
-    @Test
-    public void testConnectivityDiagnosticsCallbackOnConnectivityReport() throws Exception {
+    private void setUpConnectivityDiagnosticsCallback() throws Exception {
         final NetworkRequest request = new NetworkRequest.Builder().build();
         when(mConnectivityDiagnosticsCallback.asBinder()).thenReturn(mIBinder);
 
@@ -6620,9 +6629,47 @@
         mCellNetworkAgent.connect(true);
         callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent);
         callback.assertNoCallback();
+    }
+
+    @Test
+    public void testConnectivityDiagnosticsCallbackOnConnectivityReport() throws Exception {
+        setUpConnectivityDiagnosticsCallback();
 
         // Wait for onConnectivityReport to fire
         verify(mConnectivityDiagnosticsCallback, timeout(TIMEOUT_MS))
                 .onConnectivityReport(any(ConnectivityReport.class));
     }
+
+    @Test
+    public void testConnectivityDiagnosticsCallbackOnDataStallSuspected() throws Exception {
+        setUpConnectivityDiagnosticsCallback();
+
+        // Trigger notifyDataStallSuspected() on the INetworkMonitorCallbacks instance in the
+        // cellular network agent
+        mCellNetworkAgent.notifyDataStallSuspected();
+
+        // Wait for onDataStallSuspected to fire
+        verify(mConnectivityDiagnosticsCallback, timeout(TIMEOUT_MS))
+                .onDataStallSuspected(any(DataStallReport.class));
+    }
+
+    @Test
+    public void testConnectivityDiagnosticsCallbackOnConnectivityReported() throws Exception {
+        setUpConnectivityDiagnosticsCallback();
+
+        final Network n = mCellNetworkAgent.getNetwork();
+        final boolean hasConnectivity = true;
+        mService.reportNetworkConnectivity(n, hasConnectivity);
+
+        // Wait for onNetworkConnectivityReported to fire
+        verify(mConnectivityDiagnosticsCallback, timeout(TIMEOUT_MS))
+                .onNetworkConnectivityReported(eq(n), eq(hasConnectivity));
+
+        final boolean noConnectivity = false;
+        mService.reportNetworkConnectivity(n, noConnectivity);
+
+        // Wait for onNetworkConnectivityReported to fire
+        verify(mConnectivityDiagnosticsCallback, timeout(TIMEOUT_MS))
+                .onNetworkConnectivityReported(eq(n), eq(noConnectivity));
+    }
 }
diff --git a/tools/stats_log_api_gen/native_writer.cpp b/tools/stats_log_api_gen/native_writer.cpp
index c7a34fe..285514d 100644
--- a/tools/stats_log_api_gen/native_writer.cpp
+++ b/tools/stats_log_api_gen/native_writer.cpp
@@ -22,15 +22,6 @@
 namespace stats_log_api_gen {
 
 #if !defined(STATS_SCHEMA_LEGACY)
-static void write_native_key_value_pairs_for_type(FILE* out, const int argIndex,
-        const int typeIndex, const string& type, const string& valueFieldName) {
-    fprintf(out, "    for (const auto& it : arg%d_%d) {\n", argIndex, typeIndex);
-    fprintf(out, "        pairs.push_back("
-            "{ .key = it.first, .valueType = %s, .%s = it.second });\n",
-            type.c_str(), valueFieldName.c_str());
-    fprintf(out, "    }\n");
-
-}
 
 static int write_native_stats_write_methods(FILE* out, const Atoms& atoms,
         const AtomDecl& attributionDecl, const string& moduleName, const bool supportQ) {
@@ -41,7 +32,10 @@
             continue;
         }
         vector<java_type_t> signature = signature_to_modules_it->first;
-
+        // Key value pairs not supported in native.
+        if (find(signature.begin(), signature.end(), JAVA_TYPE_KEY_VALUE_PAIR) != signature.end()) {
+            continue;
+        }
         write_native_method_signature(out, "int stats_write", signature,
                 attributionDecl, " {");
 
@@ -59,11 +53,6 @@
                                 uidName, uidName, tagName);
                         break;
                     }
-                    case JAVA_TYPE_KEY_VALUE_PAIR:
-                        fprintf(out, "    event.writeKeyValuePairs("
-                                "arg%d_1, arg%d_2, arg%d_3, arg%d_4);\n",
-                                argIndex, argIndex, argIndex, argIndex);
-                        break;
                     case JAVA_TYPE_BYTE_ARRAY:
                         fprintf(out, "    event.writeByteArray(arg%d.arg, arg%d.arg_length);\n",
                                 argIndex, argIndex);
@@ -85,7 +74,7 @@
                         fprintf(out, "    event.writeString(arg%d);\n", argIndex);
                         break;
                     default:
-                        // Unsupported types: OBJECT, DOUBLE.
+                        // Unsupported types: OBJECT, DOUBLE, KEY_VALUE_PAIRS.
                         fprintf(stderr, "Encountered unsupported type.");
                         return 1;
                 }
@@ -93,8 +82,8 @@
             }
             fprintf(out, "    return event.writeToSocket();\n");
         } else {
-            fprintf(out, "    struct stats_event* event = stats_event_obtain();\n");
-            fprintf(out, "    stats_event_set_atom_id(event, code);\n");
+            fprintf(out, "    AStatsEvent* event = AStatsEvent_obtain();\n");
+            fprintf(out, "    AStatsEvent_setAtomId(event, code);\n");
             for (vector<java_type_t>::const_iterator arg = signature.begin();
                     arg != signature.end(); arg++) {
                 switch (*arg) {
@@ -102,57 +91,43 @@
                         const char* uidName = attributionDecl.fields.front().name.c_str();
                         const char* tagName = attributionDecl.fields.back().name.c_str();
                         fprintf(out,
-                                "    stats_event_write_attribution_chain(event, "
+                                "    AStatsEvent_writeAttributionChain(event, "
                                 "reinterpret_cast<const uint32_t*>(%s), %s.data(), "
                                 "static_cast<uint8_t>(%s_length));\n",
                                 uidName, tagName, uidName);
                         break;
                     }
-                    case JAVA_TYPE_KEY_VALUE_PAIR:
-                        fprintf(out, "    std::vector<key_value_pair> pairs;\n");
-                        write_native_key_value_pairs_for_type(
-                                out, argIndex, 1, "INT32_TYPE", "int32Value");
-                        write_native_key_value_pairs_for_type(
-                                out, argIndex, 2, "INT64_TYPE", "int64Value");
-                        write_native_key_value_pairs_for_type(
-                                out, argIndex, 3, "STRING_TYPE", "stringValue");
-                        write_native_key_value_pairs_for_type(
-                                out, argIndex, 4, "FLOAT_TYPE", "floatValue");
-                        fprintf(out,
-                                "    stats_event_write_key_value_pairs(event, pairs.data(), "
-                                "static_cast<uint8_t>(pairs.size()));\n");
-                        break;
                     case JAVA_TYPE_BYTE_ARRAY:
                         fprintf(out,
-                                "    stats_event_write_byte_array(event, "
+                                "    AStatsEvent_writeByteArray(event, "
                                 "reinterpret_cast<const uint8_t*>(arg%d.arg), arg%d.arg_length);\n",
                                 argIndex, argIndex);
                         break;
                     case JAVA_TYPE_BOOLEAN:
-                        fprintf(out, "    stats_event_write_bool(event, arg%d);\n", argIndex);
+                        fprintf(out, "    AStatsEvent_writeBool(event, arg%d);\n", argIndex);
                         break;
                     case JAVA_TYPE_INT: // Fall through.
                     case JAVA_TYPE_ENUM:
-                        fprintf(out, "    stats_event_write_int32(event, arg%d);\n", argIndex);
+                        fprintf(out, "    AStatsEvent_writeInt32(event, arg%d);\n", argIndex);
                         break;
                     case JAVA_TYPE_FLOAT:
-                        fprintf(out, "    stats_event_write_float(event, arg%d);\n", argIndex);
+                        fprintf(out, "    AStatsEvent_writeFloat(event, arg%d);\n", argIndex);
                         break;
                     case JAVA_TYPE_LONG:
-                        fprintf(out, "    stats_event_write_int64(event, arg%d);\n", argIndex);
+                        fprintf(out, "    AStatsEvent_writeInt64(event, arg%d);\n", argIndex);
                         break;
                     case JAVA_TYPE_STRING:
-                        fprintf(out, "    stats_event_write_string8(event, arg%d);\n", argIndex);
+                        fprintf(out, "    AStatsEvent_writeString(event, arg%d);\n", argIndex);
                         break;
                     default:
-                        // Unsupported types: OBJECT, DOUBLE.
+                        // Unsupported types: OBJECT, DOUBLE, KEY_VALUE_PAIRS
                         fprintf(stderr, "Encountered unsupported type.");
                         return 1;
                 }
                 argIndex++;
             }
-            fprintf(out, "    const int ret = stats_event_write(event);\n");
-            fprintf(out, "    stats_event_release(event);\n");
+            fprintf(out, "    const int ret = AStatsEvent_write(event);\n");
+            fprintf(out, "    AStatsEvent_release(event);\n");
             fprintf(out, "    return ret;\n");
         }
         fprintf(out, "}\n\n");
@@ -169,6 +144,10 @@
             continue;
         }
         vector<java_type_t> signature = signature_it->first;
+        // Key value pairs not supported in native.
+        if (find(signature.begin(), signature.end(), JAVA_TYPE_KEY_VALUE_PAIR) != signature.end()) {
+            continue;
+        }
 
         write_native_method_signature(out, "int stats_write_non_chained", signature,
                 attributionDecl, " {");
@@ -210,8 +189,14 @@
         if (!signature_needed_for_module(signature_to_modules_it->second, moduleName)) {
             continue;
         }
-
         vector<java_type_t> signature = signature_to_modules_it->first;
+
+#if !defined(STATS_SCHEMA_LEGACY)
+        // Key value pairs not supported in native.
+        if (find(signature.begin(), signature.end(), JAVA_TYPE_KEY_VALUE_PAIR) != signature.end()) {
+            continue;
+        }
+#endif
         write_native_method_signature(out, methodName, signature, attributionDecl, ";");
     }
 }
diff --git a/wifi/java/android/net/wifi/WifiConfiguration.java b/wifi/java/android/net/wifi/WifiConfiguration.java
index 91b7df3..62d6067 100644
--- a/wifi/java/android/net/wifi/WifiConfiguration.java
+++ b/wifi/java/android/net/wifi/WifiConfiguration.java
@@ -568,14 +568,12 @@
      * 2GHz band.
      * @hide
      */
-    @SystemApi
     public static final int AP_BAND_2GHZ = 0;
 
     /**
      * 5GHz band.
      * @hide
      */
-    @SystemApi
     public static final int AP_BAND_5GHZ = 1;
 
     /**
@@ -583,7 +581,6 @@
      * operating country code and current radio conditions.
      * @hide
      */
-    @SystemApi
     public static final int AP_BAND_ANY = -1;
 
     /**
@@ -593,7 +590,7 @@
      *
      * @hide
      */
-    @SystemApi
+    @UnsupportedAppUsage
     @ApBand
     public int apBand = AP_BAND_2GHZ;
 
diff --git a/wifi/java/android/net/wifi/WifiFrameworkInitializer.java b/wifi/java/android/net/wifi/WifiFrameworkInitializer.java
index 002820b..1507199 100644
--- a/wifi/java/android/net/wifi/WifiFrameworkInitializer.java
+++ b/wifi/java/android/net/wifi/WifiFrameworkInitializer.java
@@ -102,15 +102,6 @@
                 }
         );
         SystemServiceRegistry.registerContextAwareService(
-                Context.WIFI_RTT_SERVICE,
-                RttManager.class,
-                (context, serviceBinder) -> {
-                    IWifiRttManager service = IWifiRttManager.Stub.asInterface(serviceBinder);
-                    WifiRttManager wifiRttManager = new WifiRttManager(context, service);
-                    return new RttManager(context, wifiRttManager);
-                }
-        );
-        SystemServiceRegistry.registerContextAwareService(
                 Context.WIFI_RTT_RANGING_SERVICE,
                 WifiRttManager.class,
                 (context, serviceBinder) -> {
@@ -118,5 +109,13 @@
                     return new WifiRttManager(context, service);
                 }
         );
+        SystemServiceRegistry.registerContextAwareService(
+                Context.WIFI_RTT_SERVICE,
+                RttManager.class,
+                context -> {
+                    WifiRttManager wifiRttManager = context.getSystemService(WifiRttManager.class);
+                    return new RttManager(context, wifiRttManager);
+                }
+        );
     }
 }
diff --git a/wifi/java/android/net/wifi/WifiScanner.java b/wifi/java/android/net/wifi/WifiScanner.java
index a85f40b..b4eb30b 100644
--- a/wifi/java/android/net/wifi/WifiScanner.java
+++ b/wifi/java/android/net/wifi/WifiScanner.java
@@ -293,26 +293,34 @@
         public final List<HiddenNetwork> hiddenNetworks = new ArrayList<>();
         /**
          * period of background scan; in millisecond, 0 => single shot scan
-         * @deprecated Background scan support is removed.
+         * @deprecated Background scan support has always been hardware vendor dependent. This
+         * support may not be present on newer devices. Use {@link #startScan(ScanSettings,
+         * ScanListener)} instead for single scans.
          */
         @Deprecated
         public int periodInMs;
         /**
          * must have a valid REPORT_EVENT value
-         * @deprecated Background scan support is removed.
+         * @deprecated Background scan support has always been hardware vendor dependent. This
+         * support may not be present on newer devices. Use {@link #startScan(ScanSettings,
+         * ScanListener)} instead for single scans.
          */
         @Deprecated
         public int reportEvents;
         /**
          * defines number of bssids to cache from each scan
-         * @deprecated Background scan support is removed.
+         * @deprecated Background scan support has always been hardware vendor dependent. This
+         * support may not be present on newer devices. Use {@link #startScan(ScanSettings,
+         * ScanListener)} instead for single scans.
          */
         @Deprecated
         public int numBssidsPerScan;
         /**
          * defines number of scans to cache; use it with REPORT_EVENT_AFTER_BUFFER_FULL
          * to wake up at fixed interval
-         * @deprecated Background scan support is removed.
+         * @deprecated Background scan support has always been hardware vendor dependent. This
+         * support may not be present on newer devices. Use {@link #startScan(ScanSettings,
+         * ScanListener)} instead for single scans.
          */
         @Deprecated
         public int maxScansToCache;
@@ -321,14 +329,18 @@
          * a truncated binary exponential backoff bucket and the scan period will grow
          * exponentially as per formula: actual_period(N) = period * (2 ^ (N/stepCount))
          * to maxPeriodInMs
-         * @deprecated Background scan support is removed.
+         * @deprecated Background scan support has always been hardware vendor dependent. This
+         * support may not be present on newer devices. Use {@link #startScan(ScanSettings,
+         * ScanListener)} instead for single scans.
          */
         @Deprecated
         public int maxPeriodInMs;
         /**
          * for truncated binary exponential back off bucket, number of scans to perform
          * for a given period
-         * @deprecated Background scan support is removed.
+         * @deprecated Background scan support has always been hardware vendor dependent. This
+         * support may not be present on newer devices. Use {@link #startScan(ScanSettings,
+         * ScanListener)} instead for single scans.
          */
         @Deprecated
         public int stepCount;
@@ -806,7 +818,9 @@
         /**
          * Framework co-ordinates scans across multiple apps; so it may not give exactly the
          * same period requested. If period of a scan is changed; it is reported by this event.
-         * @deprecated Background scan support is removed.
+         * @deprecated Background scan support has always been hardware vendor dependent. This
+         * support may not be present on newer devices. Use {@link #startScan(ScanSettings,
+         * ScanListener)} instead for single scans.
          */
         @Deprecated
         public void onPeriodChanged(int periodInMs);
@@ -913,7 +927,9 @@
      * @param listener specifies the object to report events to. This object is also treated as a
      *                 key for this scan, and must also be specified to cancel the scan. Multiple
      *                 scans should also not share this object.
-     * @deprecated Background scan support is removed.
+     * @deprecated Background scan support has always been hardware vendor dependent. This support
+     * may not be present on newer devices. Use {@link #startScan(ScanSettings, ScanListener)}
+     * instead for single scans.
      */
     @Deprecated
     @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
@@ -935,7 +951,9 @@
      * stop an ongoing wifi scan
      * @param listener specifies which scan to cancel; must be same object as passed in {@link
      *  #startBackgroundScan}
-     * @deprecated Background scan support is removed.
+     * @deprecated Background scan support has always been hardware vendor dependent. This support
+     * may not be present on newer devices. Use {@link #startScan(ScanSettings, ScanListener)}
+     * instead for single scans.
      */
     @Deprecated
     @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)
@@ -953,7 +971,9 @@
     /**
      * reports currently available scan results on appropriate listeners
      * @return true if all scan results were reported correctly
-     * @deprecated Background scan support is removed.
+     * @deprecated Background scan support has always been hardware vendor dependent. This support
+     * may not be present on newer devices. Use {@link #startScan(ScanSettings, ScanListener)}
+     * instead for single scans.
      */
     @Deprecated
     @RequiresPermission(android.Manifest.permission.LOCATION_HARDWARE)