Merge "Ignore task-info changed callbacks in uninitialized Divider" into rvc-dev
diff --git a/apex/statsd/aidl/android/os/IStatsd.aidl b/apex/statsd/aidl/android/os/IStatsd.aidl
index 80308d2..0d3f420 100644
--- a/apex/statsd/aidl/android/os/IStatsd.aidl
+++ b/apex/statsd/aidl/android/os/IStatsd.aidl
@@ -182,12 +182,6 @@
     void unsetBroadcastSubscriber(long configId, long subscriberId, int callingUid);
 
     /**
-     * Apps can send an atom via this application breadcrumb with the specified label and state for
-     * this label. This allows building custom metrics and predicates.
-     */
-    void sendAppBreadcrumbAtom(int label, int state);
-
-    /**
      * Tell the stats daemon that all the pullers registered during boot have been sent.
      */
     oneway void allPullersFromBootRegistered();
diff --git a/apex/statsd/framework/java/android/util/StatsLog.java b/apex/statsd/framework/java/android/util/StatsLog.java
index 536b71a..4eeae57 100644
--- a/apex/statsd/framework/java/android/util/StatsLog.java
+++ b/apex/statsd/framework/java/android/util/StatsLog.java
@@ -25,8 +25,7 @@
 import android.annotation.SystemApi;
 import android.content.Context;
 import android.os.IStatsd;
-import android.os.RemoteException;
-import android.os.StatsFrameworkInitializer;
+import android.os.Process;
 import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.util.StatsdStatsLog;
@@ -45,10 +44,6 @@
     private static final boolean DEBUG = false;
     private static final int EXPERIMENT_IDS_FIELD_ID = 1;
 
-    private static IStatsd sService;
-
-    private static Object sLogLock = new Object();
-
     private StatsLog() {
     }
 
@@ -59,26 +54,13 @@
      * @return True if the log request was sent to statsd.
      */
     public static boolean logStart(int label) {
-        synchronized (sLogLock) {
-            try {
-                IStatsd service = getIStatsdLocked();
-                if (service == null) {
-                    if (DEBUG) {
-                        Log.d(TAG, "Failed to find statsd when logging start");
-                    }
-                    return false;
-                }
-                service.sendAppBreadcrumbAtom(label,
-                        StatsdStatsLog.APP_BREADCRUMB_REPORTED__STATE__START);
-                return true;
-            } catch (RemoteException e) {
-                sService = null;
-                if (DEBUG) {
-                    Log.d(TAG, "Failed to connect to statsd when logging start");
-                }
-                return false;
-            }
-        }
+        int callingUid = Process.myUid();
+        StatsdStatsLog.write(
+                StatsdStatsLog.APP_BREADCRUMB_REPORTED,
+                callingUid,
+                label,
+                StatsdStatsLog.APP_BREADCRUMB_REPORTED__STATE__START);
+        return true;
     }
 
     /**
@@ -88,26 +70,13 @@
      * @return True if the log request was sent to statsd.
      */
     public static boolean logStop(int label) {
-        synchronized (sLogLock) {
-            try {
-                IStatsd service = getIStatsdLocked();
-                if (service == null) {
-                    if (DEBUG) {
-                        Log.d(TAG, "Failed to find statsd when logging stop");
-                    }
-                    return false;
-                }
-                service.sendAppBreadcrumbAtom(
-                        label, StatsdStatsLog.APP_BREADCRUMB_REPORTED__STATE__STOP);
-                return true;
-            } catch (RemoteException e) {
-                sService = null;
-                if (DEBUG) {
-                    Log.d(TAG, "Failed to connect to statsd when logging stop");
-                }
-                return false;
-            }
-        }
+        int callingUid = Process.myUid();
+        StatsdStatsLog.write(
+                StatsdStatsLog.APP_BREADCRUMB_REPORTED,
+                callingUid,
+                label,
+                StatsdStatsLog.APP_BREADCRUMB_REPORTED__STATE__STOP);
+        return true;
     }
 
     /**
@@ -117,26 +86,13 @@
      * @return True if the log request was sent to statsd.
      */
     public static boolean logEvent(int label) {
-        synchronized (sLogLock) {
-            try {
-                IStatsd service = getIStatsdLocked();
-                if (service == null) {
-                    if (DEBUG) {
-                        Log.d(TAG, "Failed to find statsd when logging event");
-                    }
-                    return false;
-                }
-                service.sendAppBreadcrumbAtom(
-                        label, StatsdStatsLog.APP_BREADCRUMB_REPORTED__STATE__UNSPECIFIED);
-                return true;
-            } catch (RemoteException e) {
-                sService = null;
-                if (DEBUG) {
-                    Log.d(TAG, "Failed to connect to statsd when logging event");
-                }
-                return false;
-            }
-        }
+        int callingUid = Process.myUid();
+        StatsdStatsLog.write(
+                StatsdStatsLog.APP_BREADCRUMB_REPORTED,
+                callingUid,
+                label,
+                StatsdStatsLog.APP_BREADCRUMB_REPORTED__STATE__UNSPECIFIED);
+        return true;
     }
 
     /**
@@ -181,17 +137,6 @@
         return true;
     }
 
-    private static IStatsd getIStatsdLocked() throws RemoteException {
-        if (sService != null) {
-            return sService;
-        }
-        sService = IStatsd.Stub.asInterface(StatsFrameworkInitializer
-            .getStatsServiceManager()
-            .getStatsdServiceRegisterer()
-            .get());
-        return sService;
-    }
-
     /**
      * Write an event to stats log using the raw format.
      *
diff --git a/api/current.txt b/api/current.txt
index 80e2d00..3f8bee9 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -48759,12 +48759,8 @@
     method public void onCapabilitiesStatusChanged(@NonNull android.telephony.ims.feature.MmTelFeature.MmTelCapabilities);
   }
 
-  public class ImsRcsManager implements android.telephony.ims.RegistrationManager {
-    method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void getRegistrationState(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
-    method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void getRegistrationTransportType(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
+  public class ImsRcsManager {
     method @NonNull public android.telephony.ims.RcsUceAdapter getUceAdapter();
-    method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void registerImsRegistrationCallback(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.ims.RegistrationManager.RegistrationCallback) throws android.telephony.ims.ImsException;
-    method @RequiresPermission("android.permission.READ_PRIVILEGED_PHONE_STATE") public void unregisterImsRegistrationCallback(@NonNull android.telephony.ims.RegistrationManager.RegistrationCallback);
     field public static final String ACTION_SHOW_CAPABILITY_DISCOVERY_OPT_IN = "android.telephony.ims.action.SHOW_CAPABILITY_DISCOVERY_OPT_IN";
   }
 
diff --git a/api/test-current.txt b/api/test-current.txt
index 755380e..ca291f3 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -126,6 +126,7 @@
 
   public class ActivityTaskManager {
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void clearLaunchParamsForPackages(java.util.List<java.lang.String>);
+    method public static boolean currentUiModeSupportsErrorDialogs(@NonNull android.content.Context);
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public String listAllStacks();
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public void moveTaskToStack(int, int, boolean);
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS) public boolean moveTopActivityToPinnedStack(int, android.graphics.Rect);
@@ -504,6 +505,11 @@
     method public boolean isStatusBarExpansionDisabled();
   }
 
+  public class TaskInfo {
+    method @NonNull public android.content.res.Configuration getConfiguration();
+    method @NonNull public android.window.WindowContainerToken getToken();
+  }
+
   public class TimePickerDialog extends android.app.AlertDialog implements android.content.DialogInterface.OnClickListener android.widget.TimePicker.OnTimeChangedListener {
     method public android.widget.TimePicker getTimePicker();
   }
diff --git a/cmds/statsd/Android.bp b/cmds/statsd/Android.bp
index acd9ec3..f30ed17 100644
--- a/cmds/statsd/Android.bp
+++ b/cmds/statsd/Android.bp
@@ -104,7 +104,7 @@
         "src/subscriber/IncidentdReporter.cpp",
         "src/subscriber/SubscriberReporter.cpp",
         "src/uid_data.proto",
-        "src/utils/NamedLatch.cpp",
+        "src/utils/MultiConditionTrigger.cpp",
     ],
 
     local_include_dirs: [
@@ -295,7 +295,12 @@
 
     //TODO(b/153588990): Remove when the build system properly separates
     //32bit and 64bit architectures.
-    compile_multilib: "prefer32",
+    compile_multilib: "both",
+    multilib: {
+        lib64: {
+            suffix: "64",
+        }
+    },
 
     cflags: [
         "-Wall",
@@ -366,7 +371,7 @@
         "tests/StatsService_test.cpp",
         "tests/storage/StorageManager_test.cpp",
         "tests/UidMap_test.cpp",
-        "tests/utils/NamedLatch_test.cpp",
+        "tests/utils/MultiConditionTrigger_test.cpp",
     ],
 
     static_libs: [
diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp
index d914ab2..cc48d50 100644
--- a/cmds/statsd/src/StatsLogProcessor.cpp
+++ b/cmds/statsd/src/StatsLogProcessor.cpp
@@ -389,15 +389,24 @@
 void StatsLogProcessor::OnLogEvent(LogEvent* event, int64_t elapsedRealtimeNs) {
     std::lock_guard<std::mutex> lock(mMetricsMutex);
 
+    // Tell StatsdStats about new event
+    const int64_t eventElapsedTimeNs = event->GetElapsedTimestampNs();
+    int atomId = event->GetTagId();
+    StatsdStats::getInstance().noteAtomLogged(atomId, eventElapsedTimeNs / NS_PER_SEC);
+    if (!event->isValid()) {
+        StatsdStats::getInstance().noteAtomError(atomId);
+        return;
+    }
+
     // Hard-coded logic to update train info on disk and fill in any information
     // this log event may be missing.
-    if (event->GetTagId() == android::os::statsd::util::BINARY_PUSH_STATE_CHANGED) {
+    if (atomId == android::os::statsd::util::BINARY_PUSH_STATE_CHANGED) {
         onBinaryPushStateChangedEventLocked(event);
     }
 
     // Hard-coded logic to update experiment ids on disk for certain rollback
     // types and fill the rollback atom with experiment ids
-    if (event->GetTagId() == android::os::statsd::util::WATCHDOG_ROLLBACK_OCCURRED) {
+    if (atomId == android::os::statsd::util::WATCHDOG_ROLLBACK_OCCURRED) {
         onWatchdogRollbackOccurredLocked(event);
     }
 
@@ -406,16 +415,11 @@
         ALOGI("%s", event->ToString().c_str());
     }
 #endif
-    const int64_t eventElapsedTimeNs = event->GetElapsedTimestampNs();
-
     resetIfConfigTtlExpiredLocked(eventElapsedTimeNs);
 
-    StatsdStats::getInstance().noteAtomLogged(
-        event->GetTagId(), event->GetElapsedTimestampNs() / NS_PER_SEC);
-
     // Hard-coded logic to update the isolated uid's in the uid-map.
     // The field numbers need to be currently updated by hand with atoms.proto
-    if (event->GetTagId() == android::os::statsd::util::ISOLATED_UID_CHANGED) {
+    if (atomId == android::os::statsd::util::ISOLATED_UID_CHANGED) {
         onIsolatedUidChangedEventLocked(*event);
     }
 
@@ -432,7 +436,7 @@
     }
 
 
-    if (event->GetTagId() != android::os::statsd::util::ISOLATED_UID_CHANGED) {
+    if (atomId != android::os::statsd::util::ISOLATED_UID_CHANGED) {
         // Map the isolated uid to host uid if necessary.
         mapIsolatedUidToHostUidIfNecessaryLocked(event);
     }
@@ -1051,8 +1055,8 @@
 void StatsLogProcessor::notifyAppUpgrade(const int64_t& eventTimeNs, const string& apk,
                                          const int uid, const int64_t version) {
     std::lock_guard<std::mutex> lock(mMetricsMutex);
-    ALOGW("Received app upgrade");
-    for (auto it : mMetricsManagers) {
+    VLOG("Received app upgrade");
+    for (const auto& it : mMetricsManagers) {
         it.second->notifyAppUpgrade(eventTimeNs, apk, uid, version);
     }
 }
@@ -1060,20 +1064,28 @@
 void StatsLogProcessor::notifyAppRemoved(const int64_t& eventTimeNs, const string& apk,
                                          const int uid) {
     std::lock_guard<std::mutex> lock(mMetricsMutex);
-    ALOGW("Received app removed");
-    for (auto it : mMetricsManagers) {
+    VLOG("Received app removed");
+    for (const auto& it : mMetricsManagers) {
         it.second->notifyAppRemoved(eventTimeNs, apk, uid);
     }
 }
 
 void StatsLogProcessor::onUidMapReceived(const int64_t& eventTimeNs) {
     std::lock_guard<std::mutex> lock(mMetricsMutex);
-    ALOGW("Received uid map");
-    for (auto it : mMetricsManagers) {
+    VLOG("Received uid map");
+    for (const auto& it : mMetricsManagers) {
         it.second->onUidMapReceived(eventTimeNs);
     }
 }
 
+void StatsLogProcessor::onStatsdInitCompleted(const int64_t& elapsedTimeNs) {
+    std::lock_guard<std::mutex> lock(mMetricsMutex);
+    VLOG("Received boot completed signal");
+    for (const auto& it : mMetricsManagers) {
+        it.second->onStatsdInitCompleted(elapsedTimeNs);
+    }
+}
+
 void StatsLogProcessor::noteOnDiskData(const ConfigKey& key) {
     std::lock_guard<std::mutex> lock(mMetricsMutex);
     mOnDiskDataConfigs.insert(key);
diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h
index 97512ed..ffd83ba 100644
--- a/cmds/statsd/src/StatsLogProcessor.h
+++ b/cmds/statsd/src/StatsLogProcessor.h
@@ -120,6 +120,11 @@
     /* Notify all MetricsManagers of uid map snapshots received */
     void onUidMapReceived(const int64_t& eventTimeNs) override;
 
+    /* Notify all metrics managers of boot completed
+     * This will force a bucket split when the boot is finished.
+     */
+    void onStatsdInitCompleted(const int64_t& elapsedTimeNs);
+
     // Reset all configs.
     void resetConfigs();
 
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index ae7a8d0..bd9f7a5 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -118,7 +118,8 @@
                   }
               })),
       mEventQueue(queue),
-      mBootCompleteLatch({kBootCompleteTag, kUidMapReceivedTag, kAllPullersRegisteredTag}),
+      mBootCompleteTrigger({kBootCompleteTag, kUidMapReceivedTag, kAllPullersRegisteredTag},
+                           [this]() { mProcessor->onStatsdInitCompleted(getElapsedRealtimeNs()); }),
       mStatsCompanionServiceDeathRecipient(
               AIBinder_DeathRecipient_new(StatsService::statsCompanionServiceDied)) {
     mUidMap = UidMap::getInstance();
@@ -165,12 +166,6 @@
         std::thread pushedEventThread([this] { readLogs(); });
         pushedEventThread.detach();
     }
-
-    std::thread bootCompletedThread([this] {
-        mBootCompleteLatch.wait();
-        VLOG("In the boot completed thread");
-    });
-    bootCompletedThread.detach();
 }
 
 StatsService::~StatsService() {
@@ -946,7 +941,7 @@
                        packageNames,
                        installers);
 
-    mBootCompleteLatch.countDown(kUidMapReceivedTag);
+    mBootCompleteTrigger.markComplete(kUidMapReceivedTag);
     VLOG("StatsService::informAllUidData UidData proto parsed successfully.");
     return Status::ok();
 }
@@ -1066,7 +1061,7 @@
     ENFORCE_UID(AID_SYSTEM);
 
     VLOG("StatsService::bootCompleted was called");
-    mBootCompleteLatch.countDown(kBootCompleteTag);
+    mBootCompleteTrigger.markComplete(kBootCompleteTag);
     return Status::ok();
 }
 
@@ -1222,20 +1217,11 @@
     return Status::ok();
 }
 
-Status StatsService::sendAppBreadcrumbAtom(int32_t label, int32_t state) {
-    // Permission check not necessary as it's meant for applications to write to
-    // statsd.
-    android::os::statsd::util::stats_write(android::os::statsd::util::APP_BREADCRUMB_REPORTED,
-                               (int32_t) AIBinder_getCallingUid(), label,
-                               state);
-    return Status::ok();
-}
-
 Status StatsService::allPullersFromBootRegistered() {
     ENFORCE_UID(AID_SYSTEM);
 
     VLOG("StatsService::allPullersFromBootRegistered was called");
-    mBootCompleteLatch.countDown(kAllPullersRegisteredTag);
+    mBootCompleteTrigger.markComplete(kAllPullersRegisteredTag);
     return Status::ok();
 }
 
diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h
index 79324d8..b49fa1d 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -33,7 +33,7 @@
 #include "packages/UidMap.h"
 #include "shell/ShellSubscriber.h"
 #include "statscompanion_util.h"
-#include "utils/NamedLatch.h"
+#include "utils/MultiConditionTrigger.h"
 
 using namespace android;
 using namespace android::os;
@@ -162,11 +162,6 @@
     virtual void sayHiToStatsCompanion();
 
     /**
-     * Binder call to get AppBreadcrumbReported atom.
-     */
-    virtual Status sendAppBreadcrumbAtom(int32_t label, int32_t state) override;
-
-    /**
      * Binder call to notify statsd that all pullers from boot have been registered.
      */
     virtual Status allPullersFromBootRegistered();
@@ -386,7 +381,7 @@
     mutable mutex mShellSubscriberMutex;
     std::shared_ptr<LogEventQueue> mEventQueue;
 
-    NamedLatch mBootCompleteLatch;
+    MultiConditionTrigger mBootCompleteTrigger;
     static const inline string kBootCompleteTag = "BOOT_COMPLETE";
     static const inline string kUidMapReceivedTag = "UID_MAP";
     static const inline string kAllPullersRegisteredTag = "PULLERS_REGISTERED";
@@ -399,11 +394,14 @@
     FRIEND_TEST(StatsServiceTest, TestAddConfig_invalid);
     FRIEND_TEST(StatsServiceTest, TestGetUidFromArgs);
     FRIEND_TEST(PartialBucketE2eTest, TestCountMetricNoSplitOnNewApp);
+    FRIEND_TEST(PartialBucketE2eTest, TestCountMetricSplitOnBoot);
     FRIEND_TEST(PartialBucketE2eTest, TestCountMetricSplitOnUpgrade);
     FRIEND_TEST(PartialBucketE2eTest, TestCountMetricSplitOnRemoval);
     FRIEND_TEST(PartialBucketE2eTest, TestCountMetricWithoutSplit);
+    FRIEND_TEST(PartialBucketE2eTest, TestValueMetricOnBootWithoutMinPartialBucket);
     FRIEND_TEST(PartialBucketE2eTest, TestValueMetricWithoutMinPartialBucket);
     FRIEND_TEST(PartialBucketE2eTest, TestValueMetricWithMinPartialBucket);
+    FRIEND_TEST(PartialBucketE2eTest, TestGaugeMetricOnBootWithoutMinPartialBucket);
     FRIEND_TEST(PartialBucketE2eTest, TestGaugeMetricWithoutMinPartialBucket);
     FRIEND_TEST(PartialBucketE2eTest, TestGaugeMetricWithMinPartialBucket);
 };
diff --git a/cmds/statsd/src/external/StatsCallbackPuller.cpp b/cmds/statsd/src/external/StatsCallbackPuller.cpp
index 933f48d..3618bb0 100644
--- a/cmds/statsd/src/external/StatsCallbackPuller.cpp
+++ b/cmds/statsd/src/external/StatsCallbackPuller.cpp
@@ -67,8 +67,14 @@
                     lock_guard<mutex> lk(*cv_mutex);
                     for (const StatsEventParcel& parcel: output) {
                         shared_ptr<LogEvent> event = make_shared<LogEvent>(/*uid=*/-1, /*pid=*/-1);
-                        event->parseBuffer((uint8_t*)parcel.buffer.data(), parcel.buffer.size());
-                        sharedData->push_back(event);
+                        bool valid = event->parseBuffer((uint8_t*)parcel.buffer.data(),
+                                                        parcel.buffer.size());
+                        if (valid) {
+                            sharedData->push_back(event);
+                        } else {
+                            StatsdStats::getInstance().noteAtomError(event->GetTagId(),
+                                                                     /*pull=*/true);
+                        }
                     }
                     *pullSuccess = success;
                     *pullFinish = true;
diff --git a/cmds/statsd/src/external/StatsPullerManager.cpp b/cmds/statsd/src/external/StatsPullerManager.cpp
index ebe9610..cfd5d14 100644
--- a/cmds/statsd/src/external/StatsPullerManager.cpp
+++ b/cmds/statsd/src/external/StatsPullerManager.cpp
@@ -252,9 +252,13 @@
     mPullUidProviders[configKey] = provider;
 }
 
-void StatsPullerManager::UnregisterPullUidProvider(const ConfigKey& configKey) {
+void StatsPullerManager::UnregisterPullUidProvider(const ConfigKey& configKey,
+                                                   wp<PullUidProvider> provider) {
     std::lock_guard<std::mutex> _l(mLock);
-    mPullUidProviders.erase(configKey);
+    const auto& it = mPullUidProviders.find(configKey);
+    if (it != mPullUidProviders.end() && it->second == provider) {
+        mPullUidProviders.erase(it);
+    }
 }
 
 void StatsPullerManager::OnAlarmFired(int64_t elapsedTimeNs) {
diff --git a/cmds/statsd/src/external/StatsPullerManager.h b/cmds/statsd/src/external/StatsPullerManager.h
index ab0ccee..5e18aaa 100644
--- a/cmds/statsd/src/external/StatsPullerManager.h
+++ b/cmds/statsd/src/external/StatsPullerManager.h
@@ -78,11 +78,12 @@
                                     wp<PullDataReceiver> receiver);
 
     // Registers a pull uid provider for the config key. When pulling atoms, it will be used to
-    // determine which atoms to pull from.
+    // determine which uids to pull from.
     virtual void RegisterPullUidProvider(const ConfigKey& configKey, wp<PullUidProvider> provider);
 
     // Unregister a pull uid provider.
-    virtual void UnregisterPullUidProvider(const ConfigKey& configKey);
+    virtual void UnregisterPullUidProvider(const ConfigKey& configKey,
+                                           wp<PullUidProvider> provider);
 
     // Verify if we know how to pull for this matcher
     bool PullerForMatcherExists(int tagId) const;
@@ -180,6 +181,8 @@
     FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents);
     FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_LateAlarm);
     FRIEND_TEST(ValueMetricE2eTest, TestPulledEvents_WithActivation);
+
+    FRIEND_TEST(StatsLogProcessorTest, TestPullUidProviderSetOnConfigUpdate);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/guardrail/StatsdStats.cpp b/cmds/statsd/src/guardrail/StatsdStats.cpp
index db637b1..46f5dbd 100644
--- a/cmds/statsd/src/guardrail/StatsdStats.cpp
+++ b/cmds/statsd/src/guardrail/StatsdStats.cpp
@@ -54,6 +54,7 @@
 
 const int FIELD_ID_ATOM_STATS_TAG = 1;
 const int FIELD_ID_ATOM_STATS_COUNT = 2;
+const int FIELD_ID_ATOM_STATS_ERROR_COUNT = 3;
 
 const int FIELD_ID_ANOMALY_ALARMS_REGISTERED = 1;
 const int FIELD_ID_PERIODIC_ALARMS_REGISTERED = 1;
@@ -549,6 +550,20 @@
             std::min(pullStats.minBucketBoundaryDelayNs, timeDelayNs);
 }
 
+void StatsdStats::noteAtomError(int atomTag, bool pull) {
+    lock_guard<std::mutex> lock(mLock);
+    if (pull) {
+        mPulledAtomStats[atomTag].atomErrorCount++;
+        return;
+    }
+
+    bool present = (mPushedAtomErrorStats.find(atomTag) != mPushedAtomErrorStats.end());
+    bool full = (mPushedAtomErrorStats.size() >= (size_t)kMaxPushedAtomErrorStatsSize);
+    if (!full || present) {
+        mPushedAtomErrorStats[atomTag]++;
+    }
+}
+
 StatsdStats::AtomMetricStats& StatsdStats::getAtomMetricStats(int64_t metricId) {
     auto atomMetricStatsIter = mAtomMetricStats.find(metricId);
     if (atomMetricStatsIter != mAtomMetricStats.end()) {
@@ -604,9 +619,11 @@
         pullStats.second.pullExceedMaxDelay = 0;
         pullStats.second.registeredCount = 0;
         pullStats.second.unregisteredCount = 0;
+        pullStats.second.atomErrorCount = 0;
     }
     mAtomMetricStats.clear();
     mActivationBroadcastGuardrailStats.clear();
+    mPushedAtomErrorStats.clear();
 }
 
 string buildTimeString(int64_t timeSec) {
@@ -617,6 +634,15 @@
     return string(timeBuffer);
 }
 
+int StatsdStats::getPushedAtomErrors(int atomId) const {
+    const auto& it = mPushedAtomErrorStats.find(atomId);
+    if (it != mPushedAtomErrorStats.end()) {
+        return it->second;
+    } else {
+        return 0;
+    }
+}
+
 void StatsdStats::dumpStats(int out) const {
     lock_guard<std::mutex> lock(mLock);
     time_t t = mStartTimeSec;
@@ -721,11 +747,13 @@
     const size_t atomCounts = mPushedAtomStats.size();
     for (size_t i = 2; i < atomCounts; i++) {
         if (mPushedAtomStats[i] > 0) {
-            dprintf(out, "Atom %lu->%d\n", (unsigned long)i, mPushedAtomStats[i]);
+            dprintf(out, "Atom %zu->(total count)%d, (error count)%d\n", i, mPushedAtomStats[i],
+                    getPushedAtomErrors((int)i));
         }
     }
     for (const auto& pair : mNonPlatformPushedAtomStats) {
-        dprintf(out, "Atom %lu->%d\n", (unsigned long)pair.first, pair.second);
+        dprintf(out, "Atom %d->(total count)%d, (error count)%d\n", pair.first, pair.second,
+                getPushedAtomErrors(pair.first));
     }
 
     dprintf(out, "********Pulled Atom stats***********\n");
@@ -737,13 +765,15 @@
                 "nanos)%lld, "
                 "  (max pull delay nanos)%lld, (data error)%ld\n"
                 "  (pull timeout)%ld, (pull exceed max delay)%ld\n"
-                "  (registered count) %ld, (unregistered count) %ld\n",
+                "  (registered count) %ld, (unregistered count) %ld\n"
+                "  (atom error count) %d\n",
                 (int)pair.first, (long)pair.second.totalPull, (long)pair.second.totalPullFromCache,
                 (long)pair.second.pullFailed, (long)pair.second.minPullIntervalSec,
                 (long long)pair.second.avgPullTimeNs, (long long)pair.second.maxPullTimeNs,
                 (long long)pair.second.avgPullDelayNs, (long long)pair.second.maxPullDelayNs,
                 pair.second.dataError, pair.second.pullTimeout, pair.second.pullExceedMaxDelay,
-                pair.second.registeredCount, pair.second.unregisteredCount);
+                pair.second.registeredCount, pair.second.unregisteredCount,
+                pair.second.atomErrorCount);
     }
 
     if (mAnomalyAlarmRegisteredStats > 0) {
@@ -919,6 +949,10 @@
                     proto.start(FIELD_TYPE_MESSAGE | FIELD_ID_ATOM_STATS | FIELD_COUNT_REPEATED);
             proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_TAG, (int32_t)i);
             proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_COUNT, mPushedAtomStats[i]);
+            int errors = getPushedAtomErrors(i);
+            if (errors > 0) {
+                proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_ERROR_COUNT, errors);
+            }
             proto.end(token);
         }
     }
@@ -928,6 +962,10 @@
                 proto.start(FIELD_TYPE_MESSAGE | FIELD_ID_ATOM_STATS | FIELD_COUNT_REPEATED);
         proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_TAG, pair.first);
         proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_COUNT, pair.second);
+        int errors = getPushedAtomErrors(pair.first);
+        if (errors > 0) {
+            proto.write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_STATS_ERROR_COUNT, errors);
+        }
         proto.end(token);
     }
 
diff --git a/cmds/statsd/src/guardrail/StatsdStats.h b/cmds/statsd/src/guardrail/StatsdStats.h
index ff31e9e..21e524a 100644
--- a/cmds/statsd/src/guardrail/StatsdStats.h
+++ b/cmds/statsd/src/guardrail/StatsdStats.h
@@ -461,6 +461,16 @@
      */
      void noteActivationBroadcastGuardrailHit(const int uid);
 
+     /**
+      * Reports that an atom is erroneous or cannot be parsed successfully by
+      * statsd. An atom tag of 0 indicates that the client did not supply the
+      * atom id within the encoding.
+      *
+      * For pushed atoms only, this call should be preceded by a call to
+      * noteAtomLogged.
+      */
+     void noteAtomError(int atomTag, bool pull=false);
+
     /**
      * Reset the historical stats. Including all stats in icebox, and the tracked stats about
      * metrics, matchers, and atoms. The active configs will be kept and StatsdStats will continue
@@ -499,6 +509,7 @@
         long emptyData = 0;
         long registeredCount = 0;
         long unregisteredCount = 0;
+        int32_t atomErrorCount = 0;
     } PulledAtomStats;
 
     typedef struct {
@@ -546,6 +557,12 @@
     // Maps PullAtomId to its stats. The size is capped by the puller atom counts.
     std::map<int, PulledAtomStats> mPulledAtomStats;
 
+    // Stores the number of times a pushed atom was logged erroneously. The
+    // corresponding counts for pulled atoms are stored in PulledAtomStats.
+    // The max size of this map is kMaxAtomErrorsStatsSize.
+    std::map<int, int> mPushedAtomErrorStats;
+    int kMaxPushedAtomErrorStatsSize = 100;
+
     // Maps metric ID to its stats. The size is capped by the number of metrics.
     std::map<int64_t, AtomMetricStats> mAtomMetricStats;
 
@@ -613,6 +630,8 @@
 
     void addToIceBoxLocked(std::shared_ptr<ConfigStats>& stats);
 
+    int getPushedAtomErrors(int atomId) const;
+
     /**
      * Get a reference to AtomMetricStats for a metric. If none exists, create it. The reference
      * will live as long as `this`.
@@ -631,6 +650,7 @@
     FRIEND_TEST(StatsdStatsTest, TestPullAtomStats);
     FRIEND_TEST(StatsdStatsTest, TestAtomMetricsStats);
     FRIEND_TEST(StatsdStatsTest, TestActivationBroadcastGuardrailHit);
+    FRIEND_TEST(StatsdStatsTest, TestAtomErrorStats);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/logd/LogEvent.cpp b/cmds/statsd/src/logd/LogEvent.cpp
index f41ff74..eb830e1 100644
--- a/cmds/statsd/src/logd/LogEvent.cpp
+++ b/cmds/statsd/src/logd/LogEvent.cpp
@@ -377,7 +377,6 @@
         typeInfo = readNextValue<uint8_t>();
         uint8_t typeId = getTypeId(typeInfo);
 
-        // TODO(b/144373276): handle errors passed to the socket
         switch (typeId) {
             case BOOL_TYPE:
                 parseBool(pos, /*depth=*/0, last, getNumAnnotations(typeInfo));
@@ -404,8 +403,13 @@
                 parseAttributionChain(pos, /*depth=*/0, last, getNumAnnotations(typeInfo));
                 if (mAttributionChainIndex == -1) mAttributionChainIndex = pos[0];
                 break;
+            case ERROR_TYPE:
+                mErrorBitmask = readNextValue<int32_t>();
+                mValid = false;
+                break;
             default:
                 mValid = false;
+                break;
         }
     }
 
diff --git a/cmds/statsd/src/logd/LogEvent.h b/cmds/statsd/src/logd/LogEvent.h
index 8d0e794..dedcfaf 100644
--- a/cmds/statsd/src/logd/LogEvent.h
+++ b/cmds/statsd/src/logd/LogEvent.h
@@ -210,6 +210,14 @@
         return BAD_INDEX;
     }
 
+    bool isValid() const {
+        return mValid;
+    }
+
+    int32_t getErrorBitmask() const {
+        return mErrorBitmask;
+    }
+
 private:
     /**
      * Only use this if copy is absolutely needed.
@@ -236,12 +244,13 @@
     bool checkPreviousValueType(Type expected);
 
     /**
-     * The below three variables are only valid during the execution of
+     * The below two variables are only valid during the execution of
      * parseBuffer. There are no guarantees about the state of these variables
      * before/after.
      */
     uint8_t* mBuf;
     uint32_t mRemainingLen; // number of valid bytes left in the buffer being parsed
+
     bool mValid = true; // stores whether the event we received from the socket is valid
 
     /**
@@ -299,8 +308,9 @@
     // The elapsed timestamp set by statsd log writer.
     int64_t mElapsedTimestampNs;
 
-    // The atom tag of the event.
-    int mTagId;
+    // The atom tag of the event (defaults to 0 if client does not
+    // appropriately set the atom id).
+    int mTagId = 0;
 
     // The uid of the logging client (defaults to -1).
     int32_t mLogUid = -1;
@@ -308,6 +318,9 @@
     // The pid of the logging client (defaults to -1).
     int32_t mLogPid = -1;
 
+    // Bitmask of errors sent by StatsEvent/AStatsEvent.
+    int32_t mErrorBitmask = 0;
+
     // Annotations
     bool mTruncateTimestamp = false;
     int mUidFieldIndex = -1;
diff --git a/cmds/statsd/src/metrics/CountMetricProducer.h b/cmds/statsd/src/metrics/CountMetricProducer.h
index a4711e8..f9a8842 100644
--- a/cmds/statsd/src/metrics/CountMetricProducer.h
+++ b/cmds/statsd/src/metrics/CountMetricProducer.h
@@ -109,10 +109,11 @@
     FRIEND_TEST(CountMetricProducerTest, TestEventsWithNonSlicedCondition);
     FRIEND_TEST(CountMetricProducerTest, TestEventsWithSlicedCondition);
     FRIEND_TEST(CountMetricProducerTest, TestAnomalyDetectionUnSliced);
-    FRIEND_TEST(CountMetricProducerTest, TestEventWithAppUpgrade);
-    FRIEND_TEST(CountMetricProducerTest, TestEventWithAppUpgradeInNextBucket);
     FRIEND_TEST(CountMetricProducerTest, TestFirstBucket);
     FRIEND_TEST(CountMetricProducerTest, TestOneWeekTimeUnit);
+
+    FRIEND_TEST(CountMetricProducerTest_PartialBucket, TestSplitInCurrentBucket);
+    FRIEND_TEST(CountMetricProducerTest_PartialBucket, TestSplitInNextBucket);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/DurationMetricProducer.h b/cmds/statsd/src/metrics/DurationMetricProducer.h
index cc48f99..6f84076 100644
--- a/cmds/statsd/src/metrics/DurationMetricProducer.h
+++ b/cmds/statsd/src/metrics/DurationMetricProducer.h
@@ -154,12 +154,14 @@
     FRIEND_TEST(DurationMetricTrackerTest, TestNoCondition);
     FRIEND_TEST(DurationMetricTrackerTest, TestNonSlicedCondition);
     FRIEND_TEST(DurationMetricTrackerTest, TestNonSlicedConditionUnknownState);
-    FRIEND_TEST(DurationMetricTrackerTest, TestSumDurationWithUpgrade);
-    FRIEND_TEST(DurationMetricTrackerTest, TestSumDurationWithUpgradeInFollowingBucket);
-    FRIEND_TEST(DurationMetricTrackerTest, TestMaxDurationWithUpgrade);
-    FRIEND_TEST(DurationMetricTrackerTest, TestMaxDurationWithUpgradeInNextBucket);
     FRIEND_TEST(WakelockDurationE2eTest, TestAggregatedPredicates);
     FRIEND_TEST(DurationMetricTrackerTest, TestFirstBucket);
+
+    FRIEND_TEST(DurationMetricProducerTest_PartialBucket, TestSumDuration);
+    FRIEND_TEST(DurationMetricProducerTest_PartialBucket,
+                TestSumDurationWithSplitInFollowingBucket);
+    FRIEND_TEST(DurationMetricProducerTest_PartialBucket, TestMaxDuration);
+    FRIEND_TEST(DurationMetricProducerTest_PartialBucket, TestMaxDurationWithSplitInNextBucket);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/GaugeMetricProducer.h b/cmds/statsd/src/metrics/GaugeMetricProducer.h
index aa0cae2..2eb584b 100644
--- a/cmds/statsd/src/metrics/GaugeMetricProducer.h
+++ b/cmds/statsd/src/metrics/GaugeMetricProducer.h
@@ -73,18 +73,23 @@
                       bool pullSuccess, int64_t originalPullTimeNs) override;
 
     // GaugeMetric needs to immediately trigger another pull when we create the partial bucket.
-    void notifyAppUpgrade(const int64_t& eventTimeNs, const string& apk, const int uid,
-                          const int64_t version) override {
+    void notifyAppUpgrade(const int64_t& eventTimeNs) override {
         std::lock_guard<std::mutex> lock(mMutex);
 
         if (!mSplitBucketForAppUpgrade) {
             return;
         }
-        if (eventTimeNs > getCurrentBucketEndTimeNs()) {
-            // Flush full buckets on the normal path up to the latest bucket boundary.
-            flushIfNeededLocked(eventTimeNs);
+        flushLocked(eventTimeNs);
+        if (mIsPulled && mSamplingType == GaugeMetric::RANDOM_ONE_SAMPLE) {
+            pullAndMatchEventsLocked(eventTimeNs);
         }
-        flushCurrentBucketLocked(eventTimeNs, eventTimeNs);
+    };
+
+    // GaugeMetric needs to immediately trigger another pull when we create the partial bucket.
+    void onStatsdInitCompleted(const int64_t& eventTimeNs) override {
+        std::lock_guard<std::mutex> lock(mMutex);
+
+        flushLocked(eventTimeNs);
         if (mIsPulled && mSamplingType == GaugeMetric::RANDOM_ONE_SAMPLE) {
             pullAndMatchEventsLocked(eventTimeNs);
         }
@@ -190,13 +195,14 @@
     FRIEND_TEST(GaugeMetricProducerTest, TestPulledEventsWithCondition);
     FRIEND_TEST(GaugeMetricProducerTest, TestPulledEventsWithSlicedCondition);
     FRIEND_TEST(GaugeMetricProducerTest, TestPulledEventsNoCondition);
-    FRIEND_TEST(GaugeMetricProducerTest, TestPushedEventsWithUpgrade);
-    FRIEND_TEST(GaugeMetricProducerTest, TestPulledWithUpgrade);
     FRIEND_TEST(GaugeMetricProducerTest, TestPulledWithAppUpgradeDisabled);
     FRIEND_TEST(GaugeMetricProducerTest, TestPulledEventsAnomalyDetection);
     FRIEND_TEST(GaugeMetricProducerTest, TestFirstBucket);
     FRIEND_TEST(GaugeMetricProducerTest, TestPullOnTrigger);
     FRIEND_TEST(GaugeMetricProducerTest, TestRemoveDimensionInOutput);
+
+    FRIEND_TEST(GaugeMetricProducerTest_PartialBucket, TestPushedEvents);
+    FRIEND_TEST(GaugeMetricProducerTest_PartialBucket, TestPulled);
 };
 
 }  // namespace statsd
diff --git a/cmds/statsd/src/metrics/MetricProducer.h b/cmds/statsd/src/metrics/MetricProducer.h
index 6aba13ca..91c98ea2 100644
--- a/cmds/statsd/src/metrics/MetricProducer.h
+++ b/cmds/statsd/src/metrics/MetricProducer.h
@@ -141,30 +141,25 @@
     }
 
     /**
-     * Forces this metric to split into a partial bucket right now. If we're past a full bucket, we
-     * first call the standard flushing code to flush up to the latest full bucket. Then we call
-     * the flush again when the end timestamp is forced to be now, and then after flushing, update
-     * the start timestamp to be now.
+     * Force a partial bucket split on app upgrade
      */
-    virtual void notifyAppUpgrade(const int64_t& eventTimeNs, const string& apk, const int uid,
-                          const int64_t version) {
+    virtual void notifyAppUpgrade(const int64_t& eventTimeNs) {
         std::lock_guard<std::mutex> lock(mMutex);
-
-        if (eventTimeNs > getCurrentBucketEndTimeNs()) {
-            // Flush full buckets on the normal path up to the latest bucket boundary.
-            flushIfNeededLocked(eventTimeNs);
-        }
-        // Now flush a partial bucket.
-        flushCurrentBucketLocked(eventTimeNs, eventTimeNs);
-        // Don't update the current bucket number so that the anomaly tracker knows this bucket
-        // is a partial bucket and can merge it with the previous bucket.
+        flushLocked(eventTimeNs);
     };
 
-    void notifyAppRemoved(const int64_t& eventTimeNs, const string& apk, const int uid) {
+    void notifyAppRemoved(const int64_t& eventTimeNs) {
         // Force buckets to split on removal also.
-        notifyAppUpgrade(eventTimeNs, apk, uid, 0);
+        notifyAppUpgrade(eventTimeNs);
     };
 
+    /**
+     * Force a partial bucket split on boot complete.
+     */
+    virtual void onStatsdInitCompleted(const int64_t& eventTimeNs) {
+        std::lock_guard<std::mutex> lock(mMutex);
+        flushLocked(eventTimeNs);
+    }
     // Consume the parsed stats log entry that already matched the "what" of the metric.
     void onMatchedLogEvent(const size_t matcherIndex, const LogEvent& event) {
         std::lock_guard<std::mutex> lock(mMutex);
@@ -292,8 +287,7 @@
     // End: getters/setters
 protected:
     /**
-     * Flushes the current bucket if the eventTime is after the current bucket's end time. This will
-       also flush the current partial bucket in memory.
+     * Flushes the current bucket if the eventTime is after the current bucket's end time.
      */
     virtual void flushIfNeededLocked(const int64_t& eventTime){};
 
diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp
index d832ed8..e03de0b 100644
--- a/cmds/statsd/src/metrics/MetricsManager.cpp
+++ b/cmds/statsd/src/metrics/MetricsManager.cpp
@@ -189,7 +189,7 @@
             StateManager::getInstance().unregisterListener(atomId, it);
         }
     }
-    mPullerManager->UnregisterPullUidProvider(mConfigKey);
+    mPullerManager->UnregisterPullUidProvider(mConfigKey, this);
 
     VLOG("~MetricsManager()");
 }
@@ -231,8 +231,8 @@
 void MetricsManager::notifyAppUpgrade(const int64_t& eventTimeNs, const string& apk, const int uid,
                                       const int64_t version) {
     // Inform all metric producers.
-    for (auto it : mAllMetricProducers) {
-        it->notifyAppUpgrade(eventTimeNs, apk, uid, version);
+    for (const auto& it : mAllMetricProducers) {
+        it->notifyAppUpgrade(eventTimeNs);
     }
     // check if we care this package
     if (std::find(mAllowedPkg.begin(), mAllowedPkg.end(), apk) != mAllowedPkg.end()) {
@@ -252,8 +252,8 @@
 void MetricsManager::notifyAppRemoved(const int64_t& eventTimeNs, const string& apk,
                                       const int uid) {
     // Inform all metric producers.
-    for (auto it : mAllMetricProducers) {
-        it->notifyAppRemoved(eventTimeNs, apk, uid);
+    for (const auto& it : mAllMetricProducers) {
+        it->notifyAppRemoved(eventTimeNs);
     }
     // check if we care this package
     if (std::find(mAllowedPkg.begin(), mAllowedPkg.end(), apk) != mAllowedPkg.end()) {
@@ -282,6 +282,13 @@
     initLogSourceWhiteList();
 }
 
+void MetricsManager::onStatsdInitCompleted(const int64_t& eventTimeNs) {
+    // Inform all metric producers.
+    for (const auto& it : mAllMetricProducers) {
+        it->onStatsdInitCompleted(eventTimeNs);
+    }
+}
+
 void MetricsManager::init() {
     for (const auto& producer : mAllMetricProducers) {
         producer->prepareFirstBucket();
diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h
index 1fd6572..ef03d20 100644
--- a/cmds/statsd/src/metrics/MetricsManager.h
+++ b/cmds/statsd/src/metrics/MetricsManager.h
@@ -70,6 +70,8 @@
 
     void onUidMapReceived(const int64_t& eventTimeNs);
 
+    void onStatsdInitCompleted(const int64_t& elapsedTimeNs);
+
     void init();
 
     vector<int32_t> getPullAtomUids(int32_t atomId) override;
diff --git a/cmds/statsd/src/metrics/ValueMetricProducer.h b/cmds/statsd/src/metrics/ValueMetricProducer.h
index e9273dc..c8dc8cc 100644
--- a/cmds/statsd/src/metrics/ValueMetricProducer.h
+++ b/cmds/statsd/src/metrics/ValueMetricProducer.h
@@ -69,8 +69,7 @@
                       bool pullSuccess, int64_t originalPullTimeNs) override;
 
     // ValueMetric needs special logic if it's a pulled atom.
-    void notifyAppUpgrade(const int64_t& eventTimeNs, const string& apk, const int uid,
-                          const int64_t version) override {
+    void notifyAppUpgrade(const int64_t& eventTimeNs) override {
         std::lock_guard<std::mutex> lock(mMutex);
         if (!mSplitBucketForAppUpgrade) {
             return;
@@ -81,6 +80,15 @@
         flushCurrentBucketLocked(eventTimeNs, eventTimeNs);
     };
 
+    // ValueMetric needs special logic if it's a pulled atom.
+    void onStatsdInitCompleted(const int64_t& eventTimeNs) override {
+        std::lock_guard<std::mutex> lock(mMutex);
+        if (mIsPulled && mCondition) {
+            pullAndMatchEventsLocked(eventTimeNs);
+        }
+        flushCurrentBucketLocked(eventTimeNs, eventTimeNs);
+    };
+
     void onStateChanged(int64_t eventTimeNs, int32_t atomId, const HashableDimensionKey& primaryKey,
                         int oldState, int newState) override;
 
@@ -256,7 +264,6 @@
 
     FRIEND_TEST(ValueMetricProducerTest, TestAnomalyDetection);
     FRIEND_TEST(ValueMetricProducerTest, TestBaseSetOnConditionChange);
-    FRIEND_TEST(ValueMetricProducerTest, TestBucketBoundariesOnAppUpgrade);
     FRIEND_TEST(ValueMetricProducerTest, TestBucketBoundariesOnConditionChange);
     FRIEND_TEST(ValueMetricProducerTest, TestBucketBoundaryNoCondition);
     FRIEND_TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition);
@@ -269,10 +276,8 @@
     FRIEND_TEST(ValueMetricProducerTest, TestEmptyDataResetsBase_onDataPulled);
     FRIEND_TEST(ValueMetricProducerTest, TestEventsWithNonSlicedCondition);
     FRIEND_TEST(ValueMetricProducerTest, TestFirstBucket);
-    FRIEND_TEST(ValueMetricProducerTest, TestFullBucketResetWhenLastBucketInvalid);
     FRIEND_TEST(ValueMetricProducerTest, TestLateOnDataPulledWithDiff);
     FRIEND_TEST(ValueMetricProducerTest, TestLateOnDataPulledWithoutDiff);
-    FRIEND_TEST(ValueMetricProducerTest, TestPartialBucketCreated);
     FRIEND_TEST(ValueMetricProducerTest, TestPartialResetOnBucketBoundaries);
     FRIEND_TEST(ValueMetricProducerTest, TestPulledData_noDiff_bucketBoundaryFalse);
     FRIEND_TEST(ValueMetricProducerTest, TestPulledData_noDiff_bucketBoundaryTrue);
@@ -283,15 +288,12 @@
     FRIEND_TEST(ValueMetricProducerTest, TestPulledEventsTakeAbsoluteValueOnReset);
     FRIEND_TEST(ValueMetricProducerTest, TestPulledEventsTakeZeroOnReset);
     FRIEND_TEST(ValueMetricProducerTest, TestPulledEventsWithFiltering);
-    FRIEND_TEST(ValueMetricProducerTest, TestPulledValueWithUpgrade);
-    FRIEND_TEST(ValueMetricProducerTest, TestPulledValueWithUpgradeWhileConditionFalse);
     FRIEND_TEST(ValueMetricProducerTest, TestPulledWithAppUpgradeDisabled);
     FRIEND_TEST(ValueMetricProducerTest, TestPushedAggregateAvg);
     FRIEND_TEST(ValueMetricProducerTest, TestPushedAggregateMax);
     FRIEND_TEST(ValueMetricProducerTest, TestPushedAggregateMin);
     FRIEND_TEST(ValueMetricProducerTest, TestPushedAggregateSum);
     FRIEND_TEST(ValueMetricProducerTest, TestPushedEventsWithCondition);
-    FRIEND_TEST(ValueMetricProducerTest, TestPushedEventsWithUpgrade);
     FRIEND_TEST(ValueMetricProducerTest, TestPushedEventsWithoutCondition);
     FRIEND_TEST(ValueMetricProducerTest, TestResetBaseOnPullDelayExceeded);
     FRIEND_TEST(ValueMetricProducerTest, TestResetBaseOnPullFailAfterConditionChange);
@@ -313,6 +315,14 @@
     FRIEND_TEST(ValueMetricProducerTest_BucketDrop, TestInvalidBucketWhenGuardRailHit);
     FRIEND_TEST(ValueMetricProducerTest_BucketDrop,
                 TestInvalidBucketWhenAccumulateEventWrongBucket);
+
+    FRIEND_TEST(ValueMetricProducerTest_PartialBucket, TestBucketBoundariesOnPartialBucket);
+    FRIEND_TEST(ValueMetricProducerTest_PartialBucket, TestFullBucketResetWhenLastBucketInvalid);
+    FRIEND_TEST(ValueMetricProducerTest_PartialBucket, TestPartialBucketCreated);
+    FRIEND_TEST(ValueMetricProducerTest_PartialBucket, TestPushedEvents);
+    FRIEND_TEST(ValueMetricProducerTest_PartialBucket, TestPulledValue);
+    FRIEND_TEST(ValueMetricProducerTest_PartialBucket, TestPulledValueWhileConditionFalse);
+
     friend class ValueMetricProducerTestHelper;
 };
 
diff --git a/cmds/statsd/src/stats_log.proto b/cmds/statsd/src/stats_log.proto
index f4247ec..868247b 100644
--- a/cmds/statsd/src/stats_log.proto
+++ b/cmds/statsd/src/stats_log.proto
@@ -425,6 +425,7 @@
     message AtomStats {
         optional int32 tag = 1;
         optional int32 count = 2;
+        optional int32 error_count = 3;
     }
 
     repeated AtomStats atom_stats = 7;
@@ -460,6 +461,7 @@
         optional int64 empty_data = 15;
         optional int64 registered_count = 16;
         optional int64 unregistered_count = 17;
+        optional int32 atom_error_count = 18;
     }
     repeated PulledAtomStats pulled_atom_stats = 10;
 
diff --git a/cmds/statsd/src/stats_log_util.cpp b/cmds/statsd/src/stats_log_util.cpp
index 5635313..f9fddc8 100644
--- a/cmds/statsd/src/stats_log_util.cpp
+++ b/cmds/statsd/src/stats_log_util.cpp
@@ -80,6 +80,8 @@
 const int FIELD_ID_EMPTY_DATA = 15;
 const int FIELD_ID_PULL_REGISTERED_COUNT = 16;
 const int FIELD_ID_PULL_UNREGISTERED_COUNT = 17;
+const int FIELD_ID_ATOM_ERROR_COUNT = 18;
+
 // for AtomMetricStats proto
 const int FIELD_ID_ATOM_METRIC_STATS = 17;
 const int FIELD_ID_METRIC_ID = 1;
@@ -492,6 +494,7 @@
                        (long long) pair.second.registeredCount);
     protoOutput->write(FIELD_TYPE_INT64 | FIELD_ID_PULL_UNREGISTERED_COUNT,
                        (long long) pair.second.unregisteredCount);
+    protoOutput->write(FIELD_TYPE_INT32 | FIELD_ID_ATOM_ERROR_COUNT, pair.second.atomErrorCount);
     protoOutput->end(token);
 }
 
diff --git a/cmds/statsd/src/utils/MultiConditionTrigger.cpp b/cmds/statsd/src/utils/MultiConditionTrigger.cpp
new file mode 100644
index 0000000..43a6933
--- /dev/null
+++ b/cmds/statsd/src/utils/MultiConditionTrigger.cpp
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#define DEBUG false  // STOPSHIP if true
+
+#include "MultiConditionTrigger.h"
+
+#include <thread>
+
+using namespace std;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+MultiConditionTrigger::MultiConditionTrigger(const set<string>& conditionNames,
+                                             function<void()> trigger)
+    : mRemainingConditionNames(conditionNames),
+      mTrigger(trigger),
+      mCompleted(mRemainingConditionNames.empty()) {
+    if (mCompleted) {
+        thread executorThread([this] { mTrigger(); });
+        executorThread.detach();
+    }
+}
+
+void MultiConditionTrigger::markComplete(const string& conditionName) {
+    bool doTrigger = false;
+    {
+        lock_guard<mutex> lg(mMutex);
+        if (mCompleted) {
+            return;
+        }
+        mRemainingConditionNames.erase(conditionName);
+        mCompleted = mRemainingConditionNames.empty();
+        doTrigger = mCompleted;
+    }
+    if (doTrigger) {
+        std::thread executorThread([this] { mTrigger(); });
+        executorThread.detach();
+    }
+}
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/utils/MultiConditionTrigger.h b/cmds/statsd/src/utils/MultiConditionTrigger.h
new file mode 100644
index 0000000..51f6029
--- /dev/null
+++ b/cmds/statsd/src/utils/MultiConditionTrigger.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <gtest/gtest_prod.h>
+
+#include <mutex>
+#include <set>
+
+namespace android {
+namespace os {
+namespace statsd {
+
+/**
+ * This class provides a utility to wait for a set of named conditions to occur.
+ *
+ * It will execute the trigger runnable in a detached thread once all conditions have been marked
+ * true.
+ */
+class MultiConditionTrigger {
+public:
+    explicit MultiConditionTrigger(const std::set<std::string>& conditionNames,
+                                   std::function<void()> trigger);
+
+    MultiConditionTrigger(const MultiConditionTrigger&) = delete;
+    MultiConditionTrigger& operator=(const MultiConditionTrigger&) = delete;
+
+    // Mark a specific condition as true. If this condition has called markComplete already or if
+    // the event was not specified in the constructor, the function is a no-op.
+    void markComplete(const std::string& eventName);
+
+private:
+    mutable std::mutex mMutex;
+    std::set<std::string> mRemainingConditionNames;
+    std::function<void()> mTrigger;
+    bool mCompleted;
+
+    FRIEND_TEST(MultiConditionTriggerTest, TestCountDownCalledBySameEventName);
+};
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
diff --git a/cmds/statsd/src/utils/NamedLatch.cpp b/cmds/statsd/src/utils/NamedLatch.cpp
deleted file mode 100644
index 6e77977..0000000
--- a/cmds/statsd/src/utils/NamedLatch.cpp
+++ /dev/null
@@ -1,48 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#define DEBUG false  // STOPSHIP if true
-
-#include "NamedLatch.h"
-
-using namespace std;
-
-namespace android {
-namespace os {
-namespace statsd {
-
-NamedLatch::NamedLatch(const set<string>& eventNames) : mRemainingEventNames(eventNames) {
-}
-
-void NamedLatch::countDown(const string& eventName) {
-    bool notify = false;
-    {
-        lock_guard<mutex> lg(mMutex);
-        mRemainingEventNames.erase(eventName);
-        notify = mRemainingEventNames.empty();
-    }
-    if (notify) {
-        mConditionVariable.notify_all();
-    }
-}
-
-void NamedLatch::wait() const {
-    unique_lock<mutex> unique_lk(mMutex);
-    mConditionVariable.wait(unique_lk, [this] { return mRemainingEventNames.empty(); });
-}
-
-}  // namespace statsd
-}  // namespace os
-}  // namespace android
diff --git a/cmds/statsd/src/utils/NamedLatch.h b/cmds/statsd/src/utils/NamedLatch.h
deleted file mode 100644
index 70238370..0000000
--- a/cmds/statsd/src/utils/NamedLatch.h
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#pragma once
-
-#include <gtest/gtest_prod.h>
-
-#include <condition_variable>
-#include <mutex>
-#include <set>
-
-namespace android {
-namespace os {
-namespace statsd {
-
-/**
- * This class provides a threading primitive similar to a latch.
- * The primary difference is that it waits for named events to occur instead of waiting for
- * N threads to reach a certain point.
- *
- * It uses a condition variable under the hood.
- */
-class NamedLatch {
-public:
-    explicit NamedLatch(const std::set<std::string>& eventNames);
-
-    NamedLatch(const NamedLatch&) = delete;
-    NamedLatch& operator=(const NamedLatch&) = delete;
-
-    // Mark a specific event as completed. If this event has called countDown already or if the
-    // event was not specified in the constructor, the function is a no-op.
-    void countDown(const std::string& eventName);
-
-    // Blocks the calling thread until all events in eventNames have called countDown.
-    void wait() const;
-
-private:
-    mutable std::mutex mMutex;
-    mutable std::condition_variable mConditionVariable;
-    std::set<std::string> mRemainingEventNames;
-
-    FRIEND_TEST(NamedLatchTest, TestCountDownCalledBySameEventName);
-};
-}  // namespace statsd
-}  // namespace os
-}  // namespace android
diff --git a/cmds/statsd/tests/MetricsManager_test.cpp b/cmds/statsd/tests/MetricsManager_test.cpp
index 1075fe4..a44541d 100644
--- a/cmds/statsd/tests/MetricsManager_test.cpp
+++ b/cmds/statsd/tests/MetricsManager_test.cpp
@@ -528,7 +528,7 @@
             }));
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
     EXPECT_CALL(*pullerManager, RegisterPullUidProvider(kConfigKey, _)).Times(1);
-    EXPECT_CALL(*pullerManager, UnregisterPullUidProvider(kConfigKey)).Times(1);
+    EXPECT_CALL(*pullerManager, UnregisterPullUidProvider(kConfigKey, _)).Times(1);
 
     sp<AlarmMonitor> anomalyAlarmMonitor;
     sp<AlarmMonitor> periodicAlarmMonitor;
diff --git a/cmds/statsd/tests/StatsLogProcessor_test.cpp b/cmds/statsd/tests/StatsLogProcessor_test.cpp
index d29394b..b809286 100644
--- a/cmds/statsd/tests/StatsLogProcessor_test.cpp
+++ b/cmds/statsd/tests/StatsLogProcessor_test.cpp
@@ -301,6 +301,29 @@
     EXPECT_TRUE(noData);
 }
 
+TEST(StatsLogProcessorTest, TestPullUidProviderSetOnConfigUpdate) {
+    // Setup simple config key corresponding to empty config.
+    sp<UidMap> m = new UidMap();
+    sp<StatsPullerManager> pullerManager = new StatsPullerManager();
+    sp<AlarmMonitor> anomalyAlarmMonitor;
+    sp<AlarmMonitor> subscriberAlarmMonitor;
+    StatsLogProcessor p(
+            m, pullerManager, anomalyAlarmMonitor, subscriberAlarmMonitor, 0,
+            [](const ConfigKey& key) { return true; },
+            [](const int&, const vector<int64_t>&) { return true; });
+    ConfigKey key(3, 4);
+    StatsdConfig config = MakeConfig(false);
+    p.OnConfigUpdated(0, key, config);
+    EXPECT_NE(pullerManager->mPullUidProviders.find(key), pullerManager->mPullUidProviders.end());
+
+    config.add_default_pull_packages("AID_STATSD");
+    p.OnConfigUpdated(5, key, config);
+    EXPECT_NE(pullerManager->mPullUidProviders.find(key), pullerManager->mPullUidProviders.end());
+
+    p.OnConfigRemoved(key);
+    EXPECT_EQ(pullerManager->mPullUidProviders.find(key), pullerManager->mPullUidProviders.end());
+}
+
 TEST(StatsLogProcessorTest, TestActiveConfigMetricDiskWriteRead) {
     int uid = 1111;
 
diff --git a/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp b/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp
index b173ee0..9117623 100644
--- a/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp
+++ b/cmds/statsd/tests/e2e/PartialBucket_e2e_test.cpp
@@ -89,6 +89,7 @@
     valueMetric->set_bucket(FIVE_MINUTES);
     valueMetric->set_min_bucket_size_nanos(minTime);
     valueMetric->set_use_absolute_value_on_reset(true);
+    valueMetric->set_skip_zero_diff_output(false);
     return config;
 }
 
@@ -217,6 +218,35 @@
     EXPECT_EQ(1, report.metrics(0).count_metrics().data(0).bucket_info(0).count());
 }
 
+TEST(PartialBucketE2eTest, TestCountMetricSplitOnBoot) {
+    shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr);
+    SendConfig(service, MakeConfig());
+    int64_t start = getElapsedRealtimeNs();  // This is the start-time the metrics producers are
+                                             // initialized with.
+
+    // Goes into the first bucket
+    service->mProcessor->OnLogEvent(CreateAppCrashEvent(start + NS_PER_SEC, 100).get());
+    int64_t bootCompleteTimeNs = start + 2 * NS_PER_SEC;
+    service->mProcessor->onStatsdInitCompleted(bootCompleteTimeNs);
+    // Goes into the second bucket.
+    service->mProcessor->OnLogEvent(CreateAppCrashEvent(start + 3 * NS_PER_SEC, 100).get());
+
+    ConfigMetricsReport report = GetReports(service->mProcessor, start + 4 * NS_PER_SEC);
+    backfillStartEndTimestamp(&report);
+
+    ASSERT_EQ(1, report.metrics_size());
+    ASSERT_EQ(1, report.metrics(0).count_metrics().data_size());
+    ASSERT_EQ(1, report.metrics(0).count_metrics().data(0).bucket_info_size());
+    EXPECT_TRUE(report.metrics(0)
+                        .count_metrics()
+                        .data(0)
+                        .bucket_info(0)
+                        .has_start_bucket_elapsed_nanos());
+    EXPECT_EQ(MillisToNano(NanoToMillis(bootCompleteTimeNs)),
+              report.metrics(0).count_metrics().data(0).bucket_info(0).end_bucket_elapsed_nanos());
+    EXPECT_EQ(1, report.metrics(0).count_metrics().data(0).bucket_info(0).count());
+}
+
 TEST(PartialBucketE2eTest, TestValueMetricWithoutMinPartialBucket) {
     shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr);
     service->mPullerManager->RegisterPullAtomCallback(
@@ -229,13 +259,22 @@
                                              // initialized with.
 
     service->mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start);
-    service->mUidMap->updateApp(5 * 60 * NS_PER_SEC + start + 2, String16(kApp1.c_str()), 1, 2,
-                                String16("v2"), String16(""));
+    int64_t appUpgradeTimeNs = 5 * 60 * NS_PER_SEC + start + 2 * NS_PER_SEC;
+    service->mUidMap->updateApp(appUpgradeTimeNs, String16(kApp1.c_str()), 1, 2, String16("v2"),
+                                String16(""));
 
     ConfigMetricsReport report =
-            GetReports(service->mProcessor, 5 * 60 * NS_PER_SEC + start + 100, true);
+            GetReports(service->mProcessor, 5 * 60 * NS_PER_SEC + start + 100 * NS_PER_SEC);
+    backfillStartEndTimestamp(&report);
+
     EXPECT_EQ(1, report.metrics_size());
     EXPECT_EQ(0, report.metrics(0).value_metrics().skipped_size());
+
+    // The fake subsystem state sleep puller returns two atoms.
+    ASSERT_EQ(2, report.metrics(0).value_metrics().data_size());
+    ASSERT_EQ(2, report.metrics(0).value_metrics().data(0).bucket_info_size());
+    EXPECT_EQ(MillisToNano(NanoToMillis(appUpgradeTimeNs)),
+              report.metrics(0).value_metrics().data(0).bucket_info(1).end_bucket_elapsed_nanos());
 }
 
 TEST(PartialBucketE2eTest, TestValueMetricWithMinPartialBucket) {
@@ -249,13 +288,13 @@
     int64_t start = getElapsedRealtimeNs();  // This is the start-time the metrics producers are
                                              // initialized with.
 
-    const int64_t endSkipped = 5 * 60 * NS_PER_SEC + start + 2;
+    const int64_t endSkipped = 5 * 60 * NS_PER_SEC + start + 2 * NS_PER_SEC;
     service->mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start);
     service->mUidMap->updateApp(endSkipped, String16(kApp1.c_str()), 1, 2, String16("v2"),
                                String16(""));
 
     ConfigMetricsReport report =
-            GetReports(service->mProcessor, 5 * 60 * NS_PER_SEC + start + 100 * NS_PER_SEC, true);
+            GetReports(service->mProcessor, 5 * 60 * NS_PER_SEC + start + 100 * NS_PER_SEC);
     backfillStartEndTimestamp(&report);
 
     ASSERT_EQ(1, report.metrics_size());
@@ -264,10 +303,49 @@
     // Can't test the start time since it will be based on the actual time when the pulling occurs.
     EXPECT_EQ(MillisToNano(NanoToMillis(endSkipped)),
               report.metrics(0).value_metrics().skipped(0).end_bucket_elapsed_nanos());
+
+    ASSERT_EQ(2, report.metrics(0).value_metrics().data_size());
+    EXPECT_EQ(1, report.metrics(0).value_metrics().data(0).bucket_info_size());
+}
+
+TEST(PartialBucketE2eTest, TestValueMetricOnBootWithoutMinPartialBucket) {
+    shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr);
+    // Initial pull will fail since puller is not registered.
+    SendConfig(service, MakeValueMetricConfig(0));
+    int64_t start = getElapsedRealtimeNs();  // This is the start-time the metrics producers are
+                                             // initialized with.
+
+    service->mPullerManager->RegisterPullAtomCallback(
+            /*uid=*/0, util::SUBSYSTEM_SLEEP_STATE, NS_PER_SEC, NS_PER_SEC * 10, {},
+            SharedRefBase::make<FakeSubsystemSleepCallback>());
+
+    int64_t bootCompleteTimeNs = start + NS_PER_SEC;
+    service->mProcessor->onStatsdInitCompleted(bootCompleteTimeNs);
+
+    service->mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start);
+
+    ConfigMetricsReport report = GetReports(service->mProcessor, 5 * 60 * NS_PER_SEC + start + 100);
+    backfillStartEndTimestamp(&report);
+
+    // First bucket is dropped due to the initial pull failing
+    ASSERT_EQ(1, report.metrics_size());
+    EXPECT_EQ(1, report.metrics(0).value_metrics().skipped_size());
+    EXPECT_EQ(MillisToNano(NanoToMillis(bootCompleteTimeNs)),
+              report.metrics(0).value_metrics().skipped(0).end_bucket_elapsed_nanos());
+
+    // The fake subsystem state sleep puller returns two atoms.
+    ASSERT_EQ(2, report.metrics(0).value_metrics().data_size());
+    ASSERT_EQ(1, report.metrics(0).value_metrics().data(0).bucket_info_size());
+    EXPECT_EQ(
+            MillisToNano(NanoToMillis(bootCompleteTimeNs)),
+            report.metrics(0).value_metrics().data(0).bucket_info(0).start_bucket_elapsed_nanos());
 }
 
 TEST(PartialBucketE2eTest, TestGaugeMetricWithoutMinPartialBucket) {
     shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr);
+    service->mPullerManager->RegisterPullAtomCallback(
+            /*uid=*/0, util::SUBSYSTEM_SLEEP_STATE, NS_PER_SEC, NS_PER_SEC * 10, {},
+            SharedRefBase::make<FakeSubsystemSleepCallback>());
     // Partial buckets don't occur when app is first installed.
     service->mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1, String16("v1"), String16(""));
     SendConfig(service, MakeGaugeMetricConfig(0));
@@ -278,16 +356,22 @@
     service->mUidMap->updateApp(5 * 60 * NS_PER_SEC + start + 2, String16(kApp1.c_str()), 1, 2,
                                String16("v2"), String16(""));
 
-    ConfigMetricsReport report =
-            GetReports(service->mProcessor, 5 * 60 * NS_PER_SEC + start + 100, true);
-    EXPECT_EQ(1, report.metrics_size());
+    ConfigMetricsReport report = GetReports(service->mProcessor, 5 * 60 * NS_PER_SEC + start + 100);
+    backfillStartEndTimestamp(&report);
+    ASSERT_EQ(1, report.metrics_size());
     EXPECT_EQ(0, report.metrics(0).gauge_metrics().skipped_size());
+    // The fake subsystem state sleep puller returns two atoms.
+    ASSERT_EQ(2, report.metrics(0).gauge_metrics().data_size());
+    EXPECT_EQ(2, report.metrics(0).gauge_metrics().data(0).bucket_info_size());
 }
 
 TEST(PartialBucketE2eTest, TestGaugeMetricWithMinPartialBucket) {
     shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr);
     // Partial buckets don't occur when app is first installed.
     service->mUidMap->updateApp(1, String16(kApp1.c_str()), 1, 1, String16("v1"), String16(""));
+    service->mPullerManager->RegisterPullAtomCallback(
+            /*uid=*/0, util::SUBSYSTEM_SLEEP_STATE, NS_PER_SEC, NS_PER_SEC * 10, {},
+            SharedRefBase::make<FakeSubsystemSleepCallback>());
     SendConfig(service, MakeGaugeMetricConfig(60 * NS_PER_SEC /* One minute */));
     int64_t start = getElapsedRealtimeNs();  // This is the start-time the metrics producers are
                                              // initialized with.
@@ -298,7 +382,7 @@
                                 String16(""));
 
     ConfigMetricsReport report =
-            GetReports(service->mProcessor, 5 * 60 * NS_PER_SEC + start + 100 * NS_PER_SEC, true);
+            GetReports(service->mProcessor, 5 * 60 * NS_PER_SEC + start + 100 * NS_PER_SEC);
     backfillStartEndTimestamp(&report);
     ASSERT_EQ(1, report.metrics_size());
     ASSERT_EQ(1, report.metrics(0).gauge_metrics().skipped_size());
@@ -306,6 +390,38 @@
     EXPECT_TRUE(report.metrics(0).gauge_metrics().skipped(0).has_start_bucket_elapsed_nanos());
     EXPECT_EQ(MillisToNano(NanoToMillis(endSkipped)),
               report.metrics(0).gauge_metrics().skipped(0).end_bucket_elapsed_nanos());
+    ASSERT_EQ(2, report.metrics(0).gauge_metrics().data_size());
+    EXPECT_EQ(1, report.metrics(0).gauge_metrics().data(0).bucket_info_size());
+}
+
+TEST(PartialBucketE2eTest, TestGaugeMetricOnBootWithoutMinPartialBucket) {
+    shared_ptr<StatsService> service = SharedRefBase::make<StatsService>(nullptr, nullptr);
+    // Initial pull will fail since puller hasn't been registered.
+    SendConfig(service, MakeGaugeMetricConfig(0));
+    int64_t start = getElapsedRealtimeNs();  // This is the start-time the metrics producers are
+                                             // initialized with.
+
+    service->mPullerManager->RegisterPullAtomCallback(
+            /*uid=*/0, util::SUBSYSTEM_SLEEP_STATE, NS_PER_SEC, NS_PER_SEC * 10, {},
+            SharedRefBase::make<FakeSubsystemSleepCallback>());
+
+    int64_t bootCompleteTimeNs = start + NS_PER_SEC;
+    service->mProcessor->onStatsdInitCompleted(bootCompleteTimeNs);
+
+    service->mProcessor->informPullAlarmFired(5 * 60 * NS_PER_SEC + start);
+
+    ConfigMetricsReport report = GetReports(service->mProcessor, 5 * 60 * NS_PER_SEC + start + 100);
+    backfillStartEndTimestamp(&report);
+
+    ASSERT_EQ(1, report.metrics_size());
+    EXPECT_EQ(0, report.metrics(0).gauge_metrics().skipped_size());
+    // The fake subsystem state sleep puller returns two atoms.
+    ASSERT_EQ(2, report.metrics(0).gauge_metrics().data_size());
+    // No data in the first bucket, so nothing is reported
+    ASSERT_EQ(1, report.metrics(0).gauge_metrics().data(0).bucket_info_size());
+    EXPECT_EQ(
+            MillisToNano(NanoToMillis(bootCompleteTimeNs)),
+            report.metrics(0).gauge_metrics().data(0).bucket_info(0).start_bucket_elapsed_nanos());
 }
 
 #else
diff --git a/cmds/statsd/tests/guardrail/StatsdStats_test.cpp b/cmds/statsd/tests/guardrail/StatsdStats_test.cpp
index 129fafa..cdde603 100644
--- a/cmds/statsd/tests/guardrail/StatsdStats_test.cpp
+++ b/cmds/statsd/tests/guardrail/StatsdStats_test.cpp
@@ -486,6 +486,41 @@
     EXPECT_TRUE(uid2Good);
 }
 
+TEST(StatsdStatsTest, TestAtomErrorStats) {
+    StatsdStats stats;
+
+    int pushAtomTag = 100;
+    int pullAtomTag = 1000;
+    int numErrors = 10;
+
+    for (int i = 0; i < numErrors; i++) {
+        // We must call noteAtomLogged as well because only those pushed atoms
+        // that have been logged will have stats printed about them in the
+        // proto.
+        stats.noteAtomLogged(pushAtomTag, /*timeSec=*/0);
+        stats.noteAtomError(pushAtomTag, /*pull=*/false);
+
+        stats.noteAtomError(pullAtomTag, /*pull=*/true);
+    }
+
+    vector<uint8_t> output;
+    stats.dumpStats(&output, false);
+    StatsdStatsReport report;
+    EXPECT_TRUE(report.ParseFromArray(&output[0], output.size()));
+
+    // Check error count = numErrors for push atom
+    EXPECT_EQ(1, report.atom_stats_size());
+    const auto& pushedAtomStats = report.atom_stats(0);
+    EXPECT_EQ(pushAtomTag, pushedAtomStats.tag());
+    EXPECT_EQ(numErrors, pushedAtomStats.error_count());
+
+    // Check error count = numErrors for pull atom
+    EXPECT_EQ(1, report.pulled_atom_stats_size());
+    const auto& pulledAtomStats = report.pulled_atom_stats(0);
+    EXPECT_EQ(pullAtomTag, pulledAtomStats.atom_id());
+    EXPECT_EQ(numErrors, pulledAtomStats.atom_error_count());
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
index 65f8de6..8131725 100644
--- a/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/CountMetricProducer_test.cpp
@@ -38,9 +38,9 @@
 namespace os {
 namespace statsd {
 
-const ConfigKey kConfigKey(0, 12345);
 
 namespace {
+const ConfigKey kConfigKey(0, 12345);
 
 void makeLogEvent(LogEvent* logEvent, int64_t timestampNs, int atomId) {
     AStatsEvent* statsEvent = AStatsEvent_obtain();
@@ -61,6 +61,13 @@
 
 }  // namespace
 
+// Setup for parameterized tests.
+class CountMetricProducerTest_PartialBucket : public TestWithParam<BucketSplitEvent> {};
+
+INSTANTIATE_TEST_SUITE_P(CountMetricProducerTest_PartialBucket,
+                         CountMetricProducerTest_PartialBucket,
+                         testing::Values(APP_UPGRADE, BOOT_COMPLETE));
+
 TEST(CountMetricProducerTest, TestFirstBucket) {
     CountMetric metric;
     metric.set_id(1);
@@ -237,11 +244,11 @@
     EXPECT_EQ(1LL, bucketInfo.mCount);
 }
 
-TEST(CountMetricProducerTest, TestEventWithAppUpgrade) {
+TEST_P(CountMetricProducerTest_PartialBucket, TestSplitInCurrentBucket) {
     sp<AlarmMonitor> alarmMonitor;
     int64_t bucketStartTimeNs = 10000000000;
     int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL;
-    int64_t eventUpgradeTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC;
+    int64_t eventTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC;
 
     int tagId = 1;
     int conditionTagId = 2;
@@ -260,22 +267,30 @@
     sp<AnomalyTracker> anomalyTracker = countProducer.addAnomalyTracker(alert, alarmMonitor);
     EXPECT_TRUE(anomalyTracker != nullptr);
 
-    // Bucket is flushed yet.
+    // Bucket is not flushed yet.
     LogEvent event1(/*uid=*/0, /*pid=*/0);
     makeLogEvent(&event1, bucketStartTimeNs + 1, tagId, /*uid=*/"111");
     countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1);
     EXPECT_EQ(0UL, countProducer.mPastBuckets.size());
     EXPECT_EQ(0, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY));
 
-    // App upgrade forces bucket flush.
+    // App upgrade or boot complete forces bucket flush.
     // Check that there's a past bucket and the bucket end is not adjusted.
-    countProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1);
+    switch (GetParam()) {
+        case APP_UPGRADE:
+            countProducer.notifyAppUpgrade(eventTimeNs);
+            break;
+        case BOOT_COMPLETE:
+            countProducer.onStatsdInitCompleted(eventTimeNs);
+            break;
+    }
     EXPECT_EQ(1UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
-    EXPECT_EQ((long long)bucketStartTimeNs,
+    EXPECT_EQ(bucketStartTimeNs,
               countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketStartNs);
-    EXPECT_EQ((long long)eventUpgradeTimeNs,
+    EXPECT_EQ(eventTimeNs,
               countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketEndNs);
-    EXPECT_EQ(eventUpgradeTimeNs, countProducer.mCurrentBucketStartTimeNs);
+    EXPECT_EQ(0, countProducer.getCurrentBucketNum());
+    EXPECT_EQ(eventTimeNs, countProducer.mCurrentBucketStartTimeNs);
     // Anomaly tracker only contains full buckets.
     EXPECT_EQ(0, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY));
 
@@ -285,7 +300,8 @@
     makeLogEvent(&event2, bucketStartTimeNs + 59 * NS_PER_SEC + 10, tagId, /*uid=*/"222");
     countProducer.onMatchedLogEvent(1 /*log matcher index*/, event2);
     EXPECT_EQ(1UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
-    EXPECT_EQ(eventUpgradeTimeNs, countProducer.mCurrentBucketStartTimeNs);
+    EXPECT_EQ(eventTimeNs, countProducer.mCurrentBucketStartTimeNs);
+    EXPECT_EQ(0, countProducer.getCurrentBucketNum());
     EXPECT_EQ(0, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY));
 
     // Third event in following bucket.
@@ -294,13 +310,14 @@
     countProducer.onMatchedLogEvent(1 /*log matcher index*/, event3);
     EXPECT_EQ(2UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
     EXPECT_EQ(lastEndTimeNs, countProducer.mCurrentBucketStartTimeNs);
+    EXPECT_EQ(1, countProducer.getCurrentBucketNum());
     EXPECT_EQ(2, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY));
 }
 
-TEST(CountMetricProducerTest, TestEventWithAppUpgradeInNextBucket) {
+TEST_P(CountMetricProducerTest_PartialBucket, TestSplitInNextBucket) {
     int64_t bucketStartTimeNs = 10000000000;
     int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL;
-    int64_t eventUpgradeTimeNs = bucketStartTimeNs + 65 * NS_PER_SEC;
+    int64_t eventTimeNs = bucketStartTimeNs + 65 * NS_PER_SEC;
 
     int tagId = 1;
     int conditionTagId = 2;
@@ -319,15 +336,23 @@
     countProducer.onMatchedLogEvent(1 /*log matcher index*/, event1);
     EXPECT_EQ(0UL, countProducer.mPastBuckets.size());
 
-    // App upgrade forces bucket flush.
-    // Check that there's a past bucket and the bucket end is not adjusted.
-    countProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1);
+    // App upgrade or boot complete forces bucket flush.
+    // Check that there's a past bucket and the bucket end is not adjusted since the upgrade
+    // occurred after the bucket end time.
+    switch (GetParam()) {
+        case APP_UPGRADE:
+            countProducer.notifyAppUpgrade(eventTimeNs);
+            break;
+        case BOOT_COMPLETE:
+            countProducer.onStatsdInitCompleted(eventTimeNs);
+            break;
+    }
     EXPECT_EQ(1UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
-    EXPECT_EQ((int64_t)bucketStartTimeNs,
+    EXPECT_EQ(bucketStartTimeNs,
               countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketStartNs);
     EXPECT_EQ(bucketStartTimeNs + bucketSizeNs,
               countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketEndNs);
-    EXPECT_EQ(eventUpgradeTimeNs, countProducer.mCurrentBucketStartTimeNs);
+    EXPECT_EQ(eventTimeNs, countProducer.mCurrentBucketStartTimeNs);
 
     // Next event occurs in same bucket as partial bucket created.
     LogEvent event2(/*uid=*/0, /*pid=*/0);
@@ -340,7 +365,7 @@
     makeLogEvent(&event3, bucketStartTimeNs + 121 * NS_PER_SEC + 10, tagId, /*uid=*/"333");
     countProducer.onMatchedLogEvent(1 /*log matcher index*/, event3);
     EXPECT_EQ(2UL, countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
-    EXPECT_EQ((int64_t)eventUpgradeTimeNs,
+    EXPECT_EQ((int64_t)eventTimeNs,
               countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][1].mBucketStartNs);
     EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs,
               countProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][1].mBucketEndNs);
diff --git a/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp b/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp
index 30f8159..8ef2519 100644
--- a/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/DurationMetricProducer_test.cpp
@@ -41,10 +41,10 @@
 namespace os {
 namespace statsd {
 
-const ConfigKey kConfigKey(0, 12345);
 
 namespace {
 
+const ConfigKey kConfigKey(0, 12345);
 void makeLogEvent(LogEvent* logEvent, int64_t timestampNs, int atomId) {
     AStatsEvent* statsEvent = AStatsEvent_obtain();
     AStatsEvent_setAtomId(statsEvent, atomId);
@@ -55,6 +55,13 @@
 
 }  // namespace
 
+// Setup for parameterized tests.
+class DurationMetricProducerTest_PartialBucket : public TestWithParam<BucketSplitEvent> {};
+
+INSTANTIATE_TEST_SUITE_P(DurationMetricProducerTest_PartialBucket,
+                         DurationMetricProducerTest_PartialBucket,
+                         testing::Values(APP_UPGRADE, BOOT_COMPLETE));
+
 TEST(DurationMetricTrackerTest, TestFirstBucket) {
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     DurationMetric metric;
@@ -205,7 +212,7 @@
     EXPECT_EQ(1LL, buckets2[0].mDuration);
 }
 
-TEST(DurationMetricTrackerTest, TestSumDurationWithUpgrade) {
+TEST_P(DurationMetricProducerTest_PartialBucket, TestSumDuration) {
     /**
      * The duration starts from the first bucket, through the two partial buckets (10-70sec),
      * another bucket, and ends at the beginning of the next full bucket.
@@ -217,15 +224,7 @@
      */
     int64_t bucketStartTimeNs = 10000000000;
     int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL;
-    int64_t eventUpgradeTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC;
-    int64_t startTimeNs = bucketStartTimeNs + 1 * NS_PER_SEC;
-    int64_t endTimeNs = startTimeNs + 125 * NS_PER_SEC;
-
     int tagId = 1;
-    LogEvent event1(/*uid=*/0, /*pid=*/0);
-    makeLogEvent(&event1, startTimeNs, tagId);
-    LogEvent event2(/*uid=*/0, /*pid=*/0);
-    makeLogEvent(&event2, endTimeNs, tagId);
 
     DurationMetric metric;
     metric.set_id(1);
@@ -238,32 +237,47 @@
                                             3 /* stop_all index */, false /*nesting*/, wizard,
                                             dimensions, bucketStartTimeNs, bucketStartTimeNs);
 
+    int64_t startTimeNs = bucketStartTimeNs + 1 * NS_PER_SEC;
+    LogEvent event1(/*uid=*/0, /*pid=*/0);
+    makeLogEvent(&event1, startTimeNs, tagId);
     durationProducer.onMatchedLogEvent(1 /* start index*/, event1);
     EXPECT_EQ(0UL, durationProducer.mPastBuckets.size());
     EXPECT_EQ(bucketStartTimeNs, durationProducer.mCurrentBucketStartTimeNs);
 
-    durationProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1);
+    int64_t partialBucketSplitTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC;
+    switch (GetParam()) {
+        case APP_UPGRADE:
+            durationProducer.notifyAppUpgrade(partialBucketSplitTimeNs);
+            break;
+        case BOOT_COMPLETE:
+            durationProducer.onStatsdInitCompleted(partialBucketSplitTimeNs);
+            break;
+    }
     EXPECT_EQ(1UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
     std::vector<DurationBucket> buckets =
             durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY];
     EXPECT_EQ(bucketStartTimeNs, buckets[0].mBucketStartNs);
-    EXPECT_EQ(eventUpgradeTimeNs, buckets[0].mBucketEndNs);
-    EXPECT_EQ(eventUpgradeTimeNs - startTimeNs, buckets[0].mDuration);
-    EXPECT_EQ(eventUpgradeTimeNs, durationProducer.mCurrentBucketStartTimeNs);
+    EXPECT_EQ(partialBucketSplitTimeNs, buckets[0].mBucketEndNs);
+    EXPECT_EQ(partialBucketSplitTimeNs - startTimeNs, buckets[0].mDuration);
+    EXPECT_EQ(partialBucketSplitTimeNs, durationProducer.mCurrentBucketStartTimeNs);
+    EXPECT_EQ(0, durationProducer.getCurrentBucketNum());
 
     // We skip ahead one bucket, so we fill in the first two partial buckets and one full bucket.
+    int64_t endTimeNs = startTimeNs + 125 * NS_PER_SEC;
+    LogEvent event2(/*uid=*/0, /*pid=*/0);
+    makeLogEvent(&event2, endTimeNs, tagId);
     durationProducer.onMatchedLogEvent(2 /* stop index*/, event2);
     buckets = durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY];
     EXPECT_EQ(3UL, buckets.size());
-    EXPECT_EQ(eventUpgradeTimeNs, buckets[1].mBucketStartNs);
+    EXPECT_EQ(partialBucketSplitTimeNs, buckets[1].mBucketStartNs);
     EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[1].mBucketEndNs);
-    EXPECT_EQ(bucketStartTimeNs + bucketSizeNs - eventUpgradeTimeNs, buckets[1].mDuration);
+    EXPECT_EQ(bucketStartTimeNs + bucketSizeNs - partialBucketSplitTimeNs, buckets[1].mDuration);
     EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[2].mBucketStartNs);
     EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets[2].mBucketEndNs);
     EXPECT_EQ(bucketSizeNs, buckets[2].mDuration);
 }
 
-TEST(DurationMetricTrackerTest, TestSumDurationWithUpgradeInFollowingBucket) {
+TEST_P(DurationMetricProducerTest_PartialBucket, TestSumDurationWithSplitInFollowingBucket) {
     /**
      * Expected buckets (start at 11s, upgrade at 75s, end at 135s):
      *  - [10,70]: 59 secs
@@ -272,15 +286,7 @@
      */
     int64_t bucketStartTimeNs = 10000000000;
     int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL;
-    int64_t eventUpgradeTimeNs = bucketStartTimeNs + 65 * NS_PER_SEC;
-    int64_t startTimeNs = bucketStartTimeNs + 1 * NS_PER_SEC;
-    int64_t endTimeNs = startTimeNs + 125 * NS_PER_SEC;
-
     int tagId = 1;
-    LogEvent event1(/*uid=*/0, /*pid=*/0);
-    makeLogEvent(&event1, startTimeNs, tagId);
-    LogEvent event2(/*uid=*/0, /*pid=*/0);
-    makeLogEvent(&event2, endTimeNs, tagId);
 
     DurationMetric metric;
     metric.set_id(1);
@@ -293,11 +299,22 @@
                                             3 /* stop_all index */, false /*nesting*/, wizard,
                                             dimensions, bucketStartTimeNs, bucketStartTimeNs);
 
+    int64_t startTimeNs = bucketStartTimeNs + 1 * NS_PER_SEC;
+    LogEvent event1(/*uid=*/0, /*pid=*/0);
+    makeLogEvent(&event1, startTimeNs, tagId);
     durationProducer.onMatchedLogEvent(1 /* start index*/, event1);
     EXPECT_EQ(0UL, durationProducer.mPastBuckets.size());
     EXPECT_EQ(bucketStartTimeNs, durationProducer.mCurrentBucketStartTimeNs);
 
-    durationProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1);
+    int64_t partialBucketSplitTimeNs = bucketStartTimeNs + 65 * NS_PER_SEC;
+    switch (GetParam()) {
+        case APP_UPGRADE:
+            durationProducer.notifyAppUpgrade(partialBucketSplitTimeNs);
+            break;
+        case BOOT_COMPLETE:
+            durationProducer.onStatsdInitCompleted(partialBucketSplitTimeNs);
+            break;
+    }
     EXPECT_EQ(2UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
     std::vector<DurationBucket> buckets =
             durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY];
@@ -305,32 +322,29 @@
     EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[0].mBucketEndNs);
     EXPECT_EQ(bucketStartTimeNs + bucketSizeNs - startTimeNs, buckets[0].mDuration);
     EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, buckets[1].mBucketStartNs);
-    EXPECT_EQ(eventUpgradeTimeNs, buckets[1].mBucketEndNs);
-    EXPECT_EQ(eventUpgradeTimeNs - (bucketStartTimeNs + bucketSizeNs), buckets[1].mDuration);
-    EXPECT_EQ(eventUpgradeTimeNs, durationProducer.mCurrentBucketStartTimeNs);
+    EXPECT_EQ(partialBucketSplitTimeNs, buckets[1].mBucketEndNs);
+    EXPECT_EQ(partialBucketSplitTimeNs - (bucketStartTimeNs + bucketSizeNs), buckets[1].mDuration);
+    EXPECT_EQ(partialBucketSplitTimeNs, durationProducer.mCurrentBucketStartTimeNs);
+    EXPECT_EQ(1, durationProducer.getCurrentBucketNum());
 
     // We skip ahead one bucket, so we fill in the first two partial buckets and one full bucket.
+    int64_t endTimeNs = startTimeNs + 125 * NS_PER_SEC;
+    LogEvent event2(/*uid=*/0, /*pid=*/0);
+    makeLogEvent(&event2, endTimeNs, tagId);
     durationProducer.onMatchedLogEvent(2 /* stop index*/, event2);
     buckets = durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY];
     EXPECT_EQ(3UL, buckets.size());
-    EXPECT_EQ(eventUpgradeTimeNs, buckets[2].mBucketStartNs);
+    EXPECT_EQ(partialBucketSplitTimeNs, buckets[2].mBucketStartNs);
     EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets[2].mBucketEndNs);
-    EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs - eventUpgradeTimeNs, buckets[2].mDuration);
+    EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs - partialBucketSplitTimeNs,
+              buckets[2].mDuration);
 }
 
-TEST(DurationMetricTrackerTest, TestSumDurationAnomalyWithUpgrade) {
+TEST_P(DurationMetricProducerTest_PartialBucket, TestSumDurationAnomaly) {
     sp<AlarmMonitor> alarmMonitor;
     int64_t bucketStartTimeNs = 10000000000;
     int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL;
-    int64_t eventUpgradeTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC;
-    int64_t startTimeNs = bucketStartTimeNs + 1;
-    int64_t endTimeNs = startTimeNs + 65 * NS_PER_SEC;
-
     int tagId = 1;
-    LogEvent event1(/*uid=*/0, /*pid=*/0);
-    makeLogEvent(&event1, startTimeNs, tagId);
-    LogEvent event2(/*uid=*/0, /*pid=*/0);
-    makeLogEvent(&event2, endTimeNs, tagId);
 
     // Setup metric with alert.
     DurationMetric metric;
@@ -351,27 +365,35 @@
     sp<AnomalyTracker> anomalyTracker = durationProducer.addAnomalyTracker(alert, alarmMonitor);
     EXPECT_TRUE(anomalyTracker != nullptr);
 
+    int64_t startTimeNs = bucketStartTimeNs + 1;
+    LogEvent event1(/*uid=*/0, /*pid=*/0);
+    makeLogEvent(&event1, startTimeNs, tagId);
     durationProducer.onMatchedLogEvent(1 /* start index*/, event1);
-    durationProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1);
+
+    int64_t partialBucketSplitTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC;
+    switch (GetParam()) {
+        case APP_UPGRADE:
+            durationProducer.notifyAppUpgrade(partialBucketSplitTimeNs);
+            break;
+        case BOOT_COMPLETE:
+            durationProducer.onStatsdInitCompleted(partialBucketSplitTimeNs);
+            break;
+    }
 
     // We skip ahead one bucket, so we fill in the first two partial buckets and one full bucket.
+    int64_t endTimeNs = startTimeNs + 65 * NS_PER_SEC;
+    LogEvent event2(/*uid=*/0, /*pid=*/0);
+    makeLogEvent(&event2, endTimeNs, tagId);
     durationProducer.onMatchedLogEvent(2 /* stop index*/, event2);
+
     EXPECT_EQ(bucketStartTimeNs + bucketSizeNs - startTimeNs,
               anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY));
 }
 
-TEST(DurationMetricTrackerTest, TestMaxDurationWithUpgrade) {
+TEST_P(DurationMetricProducerTest_PartialBucket, TestMaxDuration) {
     int64_t bucketStartTimeNs = 10000000000;
     int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL;
-    int64_t eventUpgradeTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC;
-    int64_t startTimeNs = bucketStartTimeNs + 1;
-    int64_t endTimeNs = startTimeNs + 125 * NS_PER_SEC;
-
     int tagId = 1;
-    LogEvent event1(/*uid=*/0, /*pid=*/0);
-    makeLogEvent(&event1, startTimeNs, tagId);
-    LogEvent event2(/*uid=*/0, /*pid=*/0);
-    makeLogEvent(&event2, endTimeNs, tagId);
 
     DurationMetric metric;
     metric.set_id(1);
@@ -385,15 +407,30 @@
                                             3 /* stop_all index */, false /*nesting*/, wizard,
                                             dimensions, bucketStartTimeNs, bucketStartTimeNs);
 
+    int64_t startTimeNs = bucketStartTimeNs + 1;
+    LogEvent event1(/*uid=*/0, /*pid=*/0);
+    makeLogEvent(&event1, startTimeNs, tagId);
     durationProducer.onMatchedLogEvent(1 /* start index*/, event1);
     EXPECT_EQ(0UL, durationProducer.mPastBuckets.size());
     EXPECT_EQ(bucketStartTimeNs, durationProducer.mCurrentBucketStartTimeNs);
 
-    durationProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1);
+    int64_t partialBucketSplitTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC;
+    switch (GetParam()) {
+        case APP_UPGRADE:
+            durationProducer.notifyAppUpgrade(partialBucketSplitTimeNs);
+            break;
+        case BOOT_COMPLETE:
+            durationProducer.onStatsdInitCompleted(partialBucketSplitTimeNs);
+            break;
+    }
     EXPECT_EQ(0UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
-    EXPECT_EQ(eventUpgradeTimeNs, durationProducer.mCurrentBucketStartTimeNs);
+    EXPECT_EQ(partialBucketSplitTimeNs, durationProducer.mCurrentBucketStartTimeNs);
+    EXPECT_EQ(0, durationProducer.getCurrentBucketNum());
 
     // We skip ahead one bucket, so we fill in the first two partial buckets and one full bucket.
+    int64_t endTimeNs = startTimeNs + 125 * NS_PER_SEC;
+    LogEvent event2(/*uid=*/0, /*pid=*/0);
+    makeLogEvent(&event2, endTimeNs, tagId);
     durationProducer.onMatchedLogEvent(2 /* stop index*/, event2);
     EXPECT_EQ(0UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
 
@@ -406,18 +443,10 @@
     EXPECT_EQ(endTimeNs - startTimeNs, buckets[0].mDuration);
 }
 
-TEST(DurationMetricTrackerTest, TestMaxDurationWithUpgradeInNextBucket) {
+TEST_P(DurationMetricProducerTest_PartialBucket, TestMaxDurationWithSplitInNextBucket) {
     int64_t bucketStartTimeNs = 10000000000;
     int64_t bucketSizeNs = TimeUnitToBucketSizeInMillis(ONE_MINUTE) * 1000000LL;
-    int64_t eventUpgradeTimeNs = bucketStartTimeNs + 65 * NS_PER_SEC;
-    int64_t startTimeNs = bucketStartTimeNs + 1;
-    int64_t endTimeNs = startTimeNs + 115 * NS_PER_SEC;
-
     int tagId = 1;
-    LogEvent event1(/*uid=*/0, /*pid=*/0);
-    makeLogEvent(&event1, startTimeNs, tagId);
-    LogEvent event2(/*uid=*/0, /*pid=*/0);
-    makeLogEvent(&event2, endTimeNs, tagId);
 
     DurationMetric metric;
     metric.set_id(1);
@@ -431,24 +460,39 @@
                                             3 /* stop_all index */, false /*nesting*/, wizard,
                                             dimensions, bucketStartTimeNs, bucketStartTimeNs);
 
+    int64_t startTimeNs = bucketStartTimeNs + 1;
+    LogEvent event1(/*uid=*/0, /*pid=*/0);
+    makeLogEvent(&event1, startTimeNs, tagId);
     durationProducer.onMatchedLogEvent(1 /* start index*/, event1);
     EXPECT_EQ(0UL, durationProducer.mPastBuckets.size());
     EXPECT_EQ(bucketStartTimeNs, durationProducer.mCurrentBucketStartTimeNs);
 
-    durationProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1);
+    int64_t partialBucketSplitTimeNs = bucketStartTimeNs + 65 * NS_PER_SEC;
+    switch (GetParam()) {
+        case APP_UPGRADE:
+            durationProducer.notifyAppUpgrade(partialBucketSplitTimeNs);
+            break;
+        case BOOT_COMPLETE:
+            durationProducer.onStatsdInitCompleted(partialBucketSplitTimeNs);
+            break;
+    }
     EXPECT_EQ(0UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
-    EXPECT_EQ(eventUpgradeTimeNs, durationProducer.mCurrentBucketStartTimeNs);
+    EXPECT_EQ(partialBucketSplitTimeNs, durationProducer.mCurrentBucketStartTimeNs);
+    EXPECT_EQ(1, durationProducer.getCurrentBucketNum());
 
     // Stop occurs in the same partial bucket as created for the app upgrade.
+    int64_t endTimeNs = startTimeNs + 115 * NS_PER_SEC;
+    LogEvent event2(/*uid=*/0, /*pid=*/0);
+    makeLogEvent(&event2, endTimeNs, tagId);
     durationProducer.onMatchedLogEvent(2 /* stop index*/, event2);
     EXPECT_EQ(0UL, durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
-    EXPECT_EQ(eventUpgradeTimeNs, durationProducer.mCurrentBucketStartTimeNs);
+    EXPECT_EQ(partialBucketSplitTimeNs, durationProducer.mCurrentBucketStartTimeNs);
 
     durationProducer.flushIfNeededLocked(bucketStartTimeNs + 2 * bucketSizeNs + 1);
     std::vector<DurationBucket> buckets =
             durationProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY];
     EXPECT_EQ(1UL, buckets.size());
-    EXPECT_EQ(eventUpgradeTimeNs, buckets[0].mBucketStartNs);
+    EXPECT_EQ(partialBucketSplitTimeNs, buckets[0].mBucketStartNs);
     EXPECT_EQ(bucketStartTimeNs + 2 * bucketSizeNs, buckets[0].mBucketEndNs);
     EXPECT_EQ(endTimeNs - startTimeNs, buckets[0].mDuration);
 }
diff --git a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
index 42d0d5d..9d2ec88 100644
--- a/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/GaugeMetricProducer_test.cpp
@@ -42,6 +42,8 @@
 namespace os {
 namespace statsd {
 
+namespace {
+
 const ConfigKey kConfigKey(0, 12345);
 const int tagId = 1;
 const int64_t metricId = 123;
@@ -52,9 +54,8 @@
 const int64_t bucket2StartTimeNs = bucketStartTimeNs + bucketSizeNs;
 const int64_t bucket3StartTimeNs = bucketStartTimeNs + 2 * bucketSizeNs;
 const int64_t bucket4StartTimeNs = bucketStartTimeNs + 3 * bucketSizeNs;
-const int64_t eventUpgradeTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC;
+const int64_t partialBucketSplitTimeNs = bucketStartTimeNs + 15 * NS_PER_SEC;
 
-namespace {
 shared_ptr<LogEvent> makeLogEvent(int32_t atomId, int64_t timestampNs, int32_t value1, string str1,
                                   int32_t value2) {
     AStatsEvent* statsEvent = AStatsEvent_obtain();
@@ -71,6 +72,13 @@
 }
 }  // anonymous namespace
 
+// Setup for parameterized tests.
+class GaugeMetricProducerTest_PartialBucket : public TestWithParam<BucketSplitEvent> {};
+
+INSTANTIATE_TEST_SUITE_P(GaugeMetricProducerTest_PartialBucket,
+                         GaugeMetricProducerTest_PartialBucket,
+                         testing::Values(APP_UPGRADE, BOOT_COMPLETE));
+
 /*
  * Tests that the first bucket works correctly
  */
@@ -194,7 +202,7 @@
     EXPECT_EQ(25L, it->mValue.int_value);
 }
 
-TEST(GaugeMetricProducerTest, TestPushedEventsWithUpgrade) {
+TEST_P(GaugeMetricProducerTest_PartialBucket, TestPushedEvents) {
     sp<AlarmMonitor> alarmMonitor;
     GaugeMetric metric;
     metric.set_id(metricId);
@@ -230,11 +238,22 @@
     gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, event1);
     EXPECT_EQ(1UL, (*gaugeProducer.mCurrentSlicedBucket).count(DEFAULT_METRIC_DIMENSION_KEY));
 
-    gaugeProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1);
+    switch (GetParam()) {
+        case APP_UPGRADE:
+            gaugeProducer.notifyAppUpgrade(partialBucketSplitTimeNs);
+            break;
+        case BOOT_COMPLETE:
+            gaugeProducer.onStatsdInitCompleted(partialBucketSplitTimeNs);
+            break;
+    }
     EXPECT_EQ(0UL, (*gaugeProducer.mCurrentSlicedBucket).count(DEFAULT_METRIC_DIMENSION_KEY));
     EXPECT_EQ(1UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+    EXPECT_EQ(bucketStartTimeNs,
+              gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketStartNs);
+    EXPECT_EQ(partialBucketSplitTimeNs,
+              gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketEndNs);
     EXPECT_EQ(0L, gaugeProducer.mCurrentBucketNum);
-    EXPECT_EQ(eventUpgradeTimeNs, gaugeProducer.mCurrentBucketStartTimeNs);
+    EXPECT_EQ(partialBucketSplitTimeNs, gaugeProducer.mCurrentBucketStartTimeNs);
     // Partial buckets are not sent to anomaly tracker.
     EXPECT_EQ(0, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY));
 
@@ -244,7 +263,11 @@
     gaugeProducer.onMatchedLogEvent(1 /*log matcher index*/, event2);
     EXPECT_EQ(0L, gaugeProducer.mCurrentBucketNum);
     EXPECT_EQ(1UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
-    EXPECT_EQ((int64_t)eventUpgradeTimeNs, gaugeProducer.mCurrentBucketStartTimeNs);
+    EXPECT_EQ(bucketStartTimeNs,
+              gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketStartNs);
+    EXPECT_EQ(partialBucketSplitTimeNs,
+              gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketEndNs);
+    EXPECT_EQ((int64_t)partialBucketSplitTimeNs, gaugeProducer.mCurrentBucketStartTimeNs);
     // Partial buckets are not sent to anomaly tracker.
     EXPECT_EQ(0, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY));
 
@@ -267,7 +290,7 @@
     EXPECT_EQ(2, anomalyTracker->getSumOverPastBuckets(DEFAULT_METRIC_DIMENSION_KEY));
 }
 
-TEST(GaugeMetricProducerTest, TestPulledWithUpgrade) {
+TEST_P(GaugeMetricProducerTest_PartialBucket, TestPulled) {
     GaugeMetric metric;
     metric.set_id(metricId);
     metric.set_bucket(ONE_MINUTE);
@@ -293,7 +316,8 @@
             .WillOnce(Invoke(
                     [](int tagId, const ConfigKey&, vector<std::shared_ptr<LogEvent>>* data, bool) {
                         data->clear();
-                        data->push_back(CreateRepeatedValueLogEvent(tagId, eventUpgradeTimeNs, 2));
+                        data->push_back(
+                                CreateRepeatedValueLogEvent(tagId, partialBucketSplitTimeNs, 2));
                         return true;
                     }));
 
@@ -311,10 +335,21 @@
                          .mFields->begin()
                          ->mValue.int_value);
 
-    gaugeProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1);
+    switch (GetParam()) {
+        case APP_UPGRADE:
+            gaugeProducer.notifyAppUpgrade(partialBucketSplitTimeNs);
+            break;
+        case BOOT_COMPLETE:
+            gaugeProducer.onStatsdInitCompleted(partialBucketSplitTimeNs);
+            break;
+    }
     EXPECT_EQ(1UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+    EXPECT_EQ(bucketStartTimeNs,
+              gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketStartNs);
+    EXPECT_EQ(partialBucketSplitTimeNs,
+              gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketEndNs);
     EXPECT_EQ(0L, gaugeProducer.mCurrentBucketNum);
-    EXPECT_EQ((int64_t)eventUpgradeTimeNs, gaugeProducer.mCurrentBucketStartTimeNs);
+    EXPECT_EQ(partialBucketSplitTimeNs, gaugeProducer.mCurrentBucketStartTimeNs);
     EXPECT_EQ(1UL, gaugeProducer.mCurrentSlicedBucket->size());
     EXPECT_EQ(2, gaugeProducer.mCurrentSlicedBucket->begin()
                          ->second.front()
@@ -370,7 +405,7 @@
                          .mFields->begin()
                          ->mValue.int_value);
 
-    gaugeProducer.notifyAppUpgrade(eventUpgradeTimeNs, "ANY.APP", 1, 1);
+    gaugeProducer.notifyAppUpgrade(partialBucketSplitTimeNs);
     EXPECT_EQ(0UL, gaugeProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
     EXPECT_EQ(0L, gaugeProducer.mCurrentBucketNum);
     EXPECT_EQ(bucketStartTimeNs, gaugeProducer.mCurrentBucketStartTimeNs);
diff --git a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
index 3b4d646..f493cc4 100644
--- a/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
+++ b/cmds/statsd/tests/metrics/ValueMetricProducer_test.cpp
@@ -41,6 +41,8 @@
 namespace os {
 namespace statsd {
 
+namespace {
+
 const ConfigKey kConfigKey(0, 12345);
 const int tagId = 1;
 const int64_t metricId = 123;
@@ -58,10 +60,18 @@
 static void assertPastBucketValuesSingleKey(
         const std::unordered_map<MetricDimensionKey, std::vector<ValueBucket>>& mPastBuckets,
         const std::initializer_list<int>& expectedValuesList,
-        const std::initializer_list<int64_t>& expectedDurationNsList) {
-    std::vector<int> expectedValues(expectedValuesList);
-    std::vector<int64_t> expectedDurationNs(expectedDurationNsList);
+        const std::initializer_list<int64_t>& expectedDurationNsList,
+        const std::initializer_list<int64_t>& expectedStartTimeNsList,
+        const std::initializer_list<int64_t>& expectedEndTimeNsList) {
+    vector<int> expectedValues(expectedValuesList);
+    vector<int64_t> expectedDurationNs(expectedDurationNsList);
+    vector<int64_t> expectedStartTimeNs(expectedStartTimeNsList);
+    vector<int64_t> expectedEndTimeNs(expectedEndTimeNsList);
+
     ASSERT_EQ(expectedValues.size(), expectedDurationNs.size());
+    ASSERT_EQ(expectedValues.size(), expectedStartTimeNs.size());
+    ASSERT_EQ(expectedValues.size(), expectedEndTimeNs.size());
+
     if (expectedValues.size() == 0) {
         ASSERT_EQ(0, mPastBuckets.size());
         return;
@@ -70,15 +80,21 @@
     ASSERT_EQ(1, mPastBuckets.size());
     ASSERT_EQ(expectedValues.size(), mPastBuckets.begin()->second.size());
 
-    auto buckets = mPastBuckets.begin()->second;
+    const vector<ValueBucket>& buckets = mPastBuckets.begin()->second;
     for (int i = 0; i < expectedValues.size(); i++) {
         EXPECT_EQ(expectedValues[i], buckets[i].values[0].long_value)
                 << "Values differ at index " << i;
         EXPECT_EQ(expectedDurationNs[i], buckets[i].mConditionTrueNs)
                 << "Condition duration value differ at index " << i;
+        EXPECT_EQ(expectedStartTimeNs[i], buckets[i].mBucketStartNs)
+                << "Start time differs at index " << i;
+        EXPECT_EQ(expectedEndTimeNs[i], buckets[i].mBucketEndNs)
+                << "End time differs at index " << i;
     }
 }
 
+}  // anonymous namespace
+
 class ValueMetricProducerTestHelper {
 public:
     static sp<ValueMetricProducer> createValueProducerNoConditions(
@@ -191,6 +207,13 @@
     }
 };
 
+// Setup for parameterized tests.
+class ValueMetricProducerTest_PartialBucket : public TestWithParam<BucketSplitEvent> {};
+
+INSTANTIATE_TEST_SUITE_P(ValueMetricProducerTest_PartialBucket,
+                         ValueMetricProducerTest_PartialBucket,
+                         testing::Values(APP_UPGRADE, BOOT_COMPLETE));
+
 /*
  * Tests that the first bucket works correctly
  */
@@ -325,9 +348,10 @@
     EXPECT_EQ(bucketSizeNs, valueProducer->mPastBuckets.begin()->second[2].mConditionTrueNs);
 }
 
-TEST(ValueMetricProducerTest, TestPartialBucketCreated) {
+TEST_P(ValueMetricProducerTest_PartialBucket, TestPartialBucketCreated) {
     ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
+    int64_t partialBucketSplitTimeNs = bucket2StartTimeNs + 2;
     EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _))
             // Initialize bucket.
             .WillOnce(Invoke([](int tagId, const ConfigKey&,
@@ -337,10 +361,12 @@
                 return true;
             }))
             // Partial bucket.
-            .WillOnce(Invoke([](int tagId, const ConfigKey&,
-                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+            .WillOnce(Invoke([partialBucketSplitTimeNs](int tagId, const ConfigKey&,
+                                                        vector<std::shared_ptr<LogEvent>>* data,
+                                                        bool) {
                 data->clear();
-                data->push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 10, 5));
+                data->push_back(
+                        CreateRepeatedValueLogEvent(tagId, partialBucketSplitTimeNs + 8, 5));
                 return true;
             }));
 
@@ -354,19 +380,21 @@
     valueProducer->onDataPulled(allData, /** success */ true, bucket2StartTimeNs);
 
     // Partial buckets created in 2nd bucket.
-    valueProducer->notifyAppUpgrade(bucket2StartTimeNs + 2, "com.foo", 10000, 1);
+    switch (GetParam()) {
+        case APP_UPGRADE:
+            valueProducer->notifyAppUpgrade(partialBucketSplitTimeNs);
+            break;
+        case BOOT_COMPLETE:
+            valueProducer->onStatsdInitCompleted(partialBucketSplitTimeNs);
+            break;
+    }
+    EXPECT_EQ(partialBucketSplitTimeNs, valueProducer->mCurrentBucketStartTimeNs);
+    EXPECT_EQ(1, valueProducer->getCurrentBucketNum());
 
-    // One full bucket and one partial bucket.
-    EXPECT_EQ(1UL, valueProducer->mPastBuckets.size());
-    vector<ValueBucket> buckets = valueProducer->mPastBuckets.begin()->second;
-    EXPECT_EQ(2UL, buckets.size());
-    // Full bucket (2 - 1)
-    EXPECT_EQ(1, buckets[0].values[0].long_value);
-    EXPECT_EQ(bucketSizeNs, buckets[0].mConditionTrueNs);
-    // Full bucket (5 - 3)
-    EXPECT_EQ(3, buckets[1].values[0].long_value);
-    // partial bucket [bucket2StartTimeNs, bucket2StartTimeNs + 2]
-    EXPECT_EQ(2, buckets[1].mConditionTrueNs);
+    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {1, 3},
+                                    {bucketSizeNs, partialBucketSplitTimeNs - bucket2StartTimeNs},
+                                    {bucketStartTimeNs, bucket2StartTimeNs},
+                                    {bucket2StartTimeNs, partialBucketSplitTimeNs});
 }
 
 /*
@@ -613,7 +641,8 @@
     allData.clear();
     allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 110));
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs);
-    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {10}, {bucketSizeNs - 8});
+    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {10}, {bucketSizeNs - 8},
+                                    {bucketStartTimeNs}, {bucket2StartTimeNs});
 
     // has one slice
     EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
@@ -625,7 +654,8 @@
     EXPECT_EQ(10, curInterval.value.long_value);
 
     valueProducer->onConditionChanged(false, bucket2StartTimeNs + 1);
-    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {10}, {bucketSizeNs - 8});
+    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {10}, {bucketSizeNs - 8},
+                                    {bucketStartTimeNs}, {bucket2StartTimeNs});
 
     // has one slice
     EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
@@ -636,10 +666,12 @@
     EXPECT_EQ(false, curBaseInfo.hasBase);
 
     valueProducer->onConditionChanged(true, bucket3StartTimeNs + 1);
-    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {10, 20}, {bucketSizeNs - 8, 1});
+    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {10, 20}, {bucketSizeNs - 8, 1},
+                                    {bucketStartTimeNs, bucket2StartTimeNs},
+                                    {bucket2StartTimeNs, bucket3StartTimeNs});
 }
 
-TEST(ValueMetricProducerTest, TestPushedEventsWithUpgrade) {
+TEST_P(ValueMetricProducerTest_PartialBucket, TestPushedEvents) {
     ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
 
     UidMap uidMap;
@@ -660,25 +692,46 @@
     valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1);
     EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
 
-    valueProducer.notifyAppUpgrade(bucketStartTimeNs + 150, "ANY.APP", 1, 1);
-    EXPECT_EQ(1UL, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
-    EXPECT_EQ(bucketStartTimeNs + 150, valueProducer.mCurrentBucketStartTimeNs);
+    int64_t partialBucketSplitTimeNs = bucketStartTimeNs + 150;
+    switch (GetParam()) {
+        case APP_UPGRADE:
+            valueProducer.notifyAppUpgrade(partialBucketSplitTimeNs);
+            break;
+        case BOOT_COMPLETE:
+            valueProducer.onStatsdInitCompleted(partialBucketSplitTimeNs);
+            break;
+    }
+    assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {10},
+                                    {partialBucketSplitTimeNs - bucketStartTimeNs},
+                                    {bucketStartTimeNs}, {partialBucketSplitTimeNs});
+    EXPECT_EQ(partialBucketSplitTimeNs, valueProducer.mCurrentBucketStartTimeNs);
+    EXPECT_EQ(0, valueProducer.getCurrentBucketNum());
 
+    // Event arrives after the bucket split.
     LogEvent event2(/*uid=*/0, /*pid=*/0);
-    CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 59 * NS_PER_SEC, 10);
+    CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 59 * NS_PER_SEC, 20);
     valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event2);
-    EXPECT_EQ(1UL, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
-    EXPECT_EQ(bucketStartTimeNs + 150, valueProducer.mCurrentBucketStartTimeNs);
+
+    assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {10},
+                                    {partialBucketSplitTimeNs - bucketStartTimeNs},
+                                    {bucketStartTimeNs}, {partialBucketSplitTimeNs});
+    EXPECT_EQ(partialBucketSplitTimeNs, valueProducer.mCurrentBucketStartTimeNs);
+    EXPECT_EQ(0, valueProducer.getCurrentBucketNum());
 
     // Next value should create a new bucket.
     LogEvent event3(/*uid=*/0, /*pid=*/0);
-    CreateRepeatedValueLogEvent(&event3, tagId, bucketStartTimeNs + 65 * NS_PER_SEC, 10);
+    CreateRepeatedValueLogEvent(&event3, tagId, bucket2StartTimeNs + 5 * NS_PER_SEC, 10);
     valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event3);
-    EXPECT_EQ(2UL, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
+    assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {10, 20},
+                                    {partialBucketSplitTimeNs - bucketStartTimeNs,
+                                     bucket2StartTimeNs - partialBucketSplitTimeNs},
+                                    {bucketStartTimeNs, partialBucketSplitTimeNs},
+                                    {partialBucketSplitTimeNs, bucket2StartTimeNs});
     EXPECT_EQ(bucketStartTimeNs + bucketSizeNs, valueProducer.mCurrentBucketStartTimeNs);
+    EXPECT_EQ(1, valueProducer.getCurrentBucketNum());
 }
 
-TEST(ValueMetricProducerTest, TestPulledValueWithUpgrade) {
+TEST_P(ValueMetricProducerTest_PartialBucket, TestPulledValue) {
     ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
 
     UidMap uidMap;
@@ -689,14 +742,16 @@
                     atomMatcherId, logEventMatcherIndex, atomMatcher, uidMap)});
     sp<MockConditionWizard> wizard = new NaggyMock<MockConditionWizard>();
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
+    int64_t partialBucketSplitTimeNs = bucket2StartTimeNs + 150;
     EXPECT_CALL(*pullerManager, RegisterReceiver(tagId, kConfigKey, _, _, _)).WillOnce(Return());
     EXPECT_CALL(*pullerManager, UnRegisterReceiver(tagId, kConfigKey, _)).WillOnce(Return());
     EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _))
             .WillOnce(Return(true))
-            .WillOnce(Invoke([](int tagId, const ConfigKey&,
-                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+            .WillOnce(Invoke([partialBucketSplitTimeNs](int tagId, const ConfigKey&,
+                                                        vector<std::shared_ptr<LogEvent>>* data,
+                                                        bool) {
                 data->clear();
-                data->push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 149, 120));
+                data->push_back(CreateRepeatedValueLogEvent(tagId, partialBucketSplitTimeNs, 120));
                 return true;
             }));
     ValueMetricProducer valueProducer(kConfigKey, metric, -1, wizard, logEventMatcherIndex,
@@ -711,20 +766,27 @@
     valueProducer.onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs);
     EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
 
-    valueProducer.notifyAppUpgrade(bucket2StartTimeNs + 150, "ANY.APP", 1, 1);
-    EXPECT_EQ(1UL, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
-    EXPECT_EQ(bucket2StartTimeNs + 150, valueProducer.mCurrentBucketStartTimeNs);
-    assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {20}, {150});
+    switch (GetParam()) {
+        case APP_UPGRADE:
+            valueProducer.notifyAppUpgrade(partialBucketSplitTimeNs);
+            break;
+        case BOOT_COMPLETE:
+            valueProducer.onStatsdInitCompleted(partialBucketSplitTimeNs);
+            break;
+    }
+    EXPECT_EQ(partialBucketSplitTimeNs, valueProducer.mCurrentBucketStartTimeNs);
+    EXPECT_EQ(1, valueProducer.getCurrentBucketNum());
+    assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {20}, {150}, {bucket2StartTimeNs},
+                                    {partialBucketSplitTimeNs});
 
     allData.clear();
     allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket3StartTimeNs + 1, 150));
     valueProducer.onDataPulled(allData, /** succeed */ true, bucket3StartTimeNs);
-    EXPECT_EQ(2UL, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
     EXPECT_EQ(bucket3StartTimeNs, valueProducer.mCurrentBucketStartTimeNs);
-    EXPECT_EQ(20L,
-              valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].values[0].long_value);
-    assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {20, 30},
-                                    {150, bucketSizeNs - 150});
+    EXPECT_EQ(2, valueProducer.getCurrentBucketNum());
+    assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {20, 30}, {150, bucketSizeNs - 150},
+                                    {bucket2StartTimeNs, partialBucketSplitTimeNs},
+                                    {partialBucketSplitTimeNs, bucket3StartTimeNs});
 }
 
 TEST(ValueMetricProducerTest, TestPulledWithAppUpgradeDisabled) {
@@ -754,12 +816,12 @@
     valueProducer.onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs);
     EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
 
-    valueProducer.notifyAppUpgrade(bucket2StartTimeNs + 150, "ANY.APP", 1, 1);
+    valueProducer.notifyAppUpgrade(bucket2StartTimeNs + 150);
     EXPECT_EQ(0UL, valueProducer.mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
     EXPECT_EQ(bucket2StartTimeNs, valueProducer.mCurrentBucketStartTimeNs);
 }
 
-TEST(ValueMetricProducerTest, TestPulledValueWithUpgradeWhileConditionFalse) {
+TEST_P(ValueMetricProducerTest_PartialBucket, TestPulledValueWhileConditionFalse) {
     ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition();
 
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
@@ -784,14 +846,21 @@
     valueProducer->onConditionChanged(false, bucket2StartTimeNs - 100);
     EXPECT_FALSE(valueProducer->mCondition);
 
-    valueProducer->notifyAppUpgrade(bucket2StartTimeNs - 50, "ANY.APP", 1, 1);
+    int64_t partialBucketSplitTimeNs = bucket2StartTimeNs - 50;
+    switch (GetParam()) {
+        case APP_UPGRADE:
+            valueProducer->notifyAppUpgrade(partialBucketSplitTimeNs);
+            break;
+        case BOOT_COMPLETE:
+            valueProducer->onStatsdInitCompleted(partialBucketSplitTimeNs);
+            break;
+    }
     // Expect one full buckets already done and starting a partial bucket.
-    EXPECT_EQ(bucket2StartTimeNs - 50, valueProducer->mCurrentBucketStartTimeNs);
-    EXPECT_EQ(1UL, valueProducer->mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY].size());
-    EXPECT_EQ(bucketStartTimeNs,
-              valueProducer->mPastBuckets[DEFAULT_METRIC_DIMENSION_KEY][0].mBucketStartNs);
+    EXPECT_EQ(partialBucketSplitTimeNs, valueProducer->mCurrentBucketStartTimeNs);
+    EXPECT_EQ(0, valueProducer->getCurrentBucketNum());
     assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20},
-                                    {(bucket2StartTimeNs - 100) - (bucketStartTimeNs + 1)});
+                                    {(bucket2StartTimeNs - 100) - (bucketStartTimeNs + 1)},
+                                    {bucketStartTimeNs}, {partialBucketSplitTimeNs});
     EXPECT_FALSE(valueProducer->mCondition);
 }
 TEST(ValueMetricProducerTest, TestPushedEventsWithoutCondition) {
@@ -834,7 +903,8 @@
     EXPECT_EQ(30, curInterval.value.long_value);
 
     valueProducer.flushIfNeededLocked(bucket2StartTimeNs);
-    assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {30}, {bucketSizeNs});
+    assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {30}, {bucketSizeNs},
+                                    {bucketStartTimeNs}, {bucket2StartTimeNs});
 }
 
 TEST(ValueMetricProducerTest, TestPushedEventsWithCondition) {
@@ -895,7 +965,8 @@
     EXPECT_EQ(50, curInterval.value.long_value);
 
     valueProducer.flushIfNeededLocked(bucket2StartTimeNs);
-    assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {50}, {20});
+    assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {50}, {20}, {bucketStartTimeNs},
+                                    {bucket2StartTimeNs});
 }
 
 TEST(ValueMetricProducerTest, TestAnomalyDetection) {
@@ -1012,7 +1083,8 @@
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(23, curBaseInfo.base.long_value);
     EXPECT_EQ(false, curInterval.hasValue);
-    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {12}, {bucketSizeNs});
+    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {12}, {bucketSizeNs},
+                                    {bucket2StartTimeNs}, {bucket3StartTimeNs});
 
     // pull 3 come late.
     // The previous bucket gets closed with error. (Has start value 23, no ending)
@@ -1028,7 +1100,15 @@
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(36, curBaseInfo.base.long_value);
     EXPECT_EQ(false, curInterval.hasValue);
-    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {12}, {bucketSizeNs});
+    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {12}, {bucketSizeNs},
+                                    {bucket2StartTimeNs}, {bucket3StartTimeNs});
+    // The 3rd bucket is dropped due to multiple buckets being skipped.
+    ASSERT_EQ(1, valueProducer->mSkippedBuckets.size());
+    EXPECT_EQ(bucket3StartTimeNs, valueProducer->mSkippedBuckets[0].bucketStartTimeNs);
+    EXPECT_EQ(bucket4StartTimeNs, valueProducer->mSkippedBuckets[0].bucketEndTimeNs);
+    ASSERT_EQ(1, valueProducer->mSkippedBuckets[0].dropEvents.size());
+    EXPECT_EQ(MULTIPLE_BUCKETS_SKIPPED, valueProducer->mSkippedBuckets[0].dropEvents[0].reason);
+    EXPECT_EQ(bucket6StartTimeNs, valueProducer->mSkippedBuckets[0].dropEvents[0].dropTimeNs);
 }
 
 /*
@@ -1073,7 +1153,8 @@
     valueProducer->onConditionChanged(false, bucket2StartTimeNs + 1);
     curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
     curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
-    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8});
+    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8},
+                                    {bucketStartTimeNs}, {bucket2StartTimeNs});
     EXPECT_EQ(false, curBaseInfo.hasBase);
 
     // Now the alarm is delivered.
@@ -1082,7 +1163,8 @@
     allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 30, 110));
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs);
 
-    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8});
+    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8},
+                                    {bucketStartTimeNs}, {bucket2StartTimeNs});
     curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
     curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
     EXPECT_EQ(false, curBaseInfo.hasBase);
@@ -1090,10 +1172,9 @@
 }
 
 /*
-* Test pulled event with non sliced condition. The pull on boundary come late, after the
-condition
-* change to false, and then true again. This is due to alarm delivered late.
-*/
+ * Test pulled event with non sliced condition. The pull on boundary come late, after the condition
+ * change to false, and then true again. This is due to alarm delivered late.
+ */
 TEST(ValueMetricProducerTest, TestBucketBoundaryWithCondition2) {
     ValueMetric metric = ValueMetricProducerTestHelper::createMetricWithCondition();
 
@@ -1139,7 +1220,8 @@
 
     // pull on bucket boundary come late, condition change happens before it
     valueProducer->onConditionChanged(false, bucket2StartTimeNs + 1);
-    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8});
+    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8},
+                                    {bucketStartTimeNs}, {bucket2StartTimeNs});
     EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
     curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
@@ -1148,7 +1230,8 @@
 
     // condition changed to true again, before the pull alarm is delivered
     valueProducer->onConditionChanged(true, bucket2StartTimeNs + 25);
-    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8});
+    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8},
+                                    {bucketStartTimeNs}, {bucket2StartTimeNs});
     curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
     curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
     EXPECT_EQ(true, curBaseInfo.hasBase);
@@ -1167,13 +1250,15 @@
     EXPECT_EQ(140, curBaseInfo.base.long_value);
     EXPECT_EQ(true, curInterval.hasValue);
     EXPECT_EQ(10, curInterval.value.long_value);
-    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8});
+    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {bucketSizeNs - 8},
+                                    {bucketStartTimeNs}, {bucket2StartTimeNs});
 
     allData.clear();
     allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket3StartTimeNs, 160));
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket3StartTimeNs);
-    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20, 30},
-                                    {bucketSizeNs - 8, bucketSizeNs - 24});
+    assertPastBucketValuesSingleKey(
+            valueProducer->mPastBuckets, {20, 30}, {bucketSizeNs - 8, bucketSizeNs - 24},
+            {bucketStartTimeNs, bucket2StartTimeNs}, {bucket2StartTimeNs, bucket3StartTimeNs});
 }
 
 TEST(ValueMetricProducerTest, TestPushedAggregateMin) {
@@ -1216,7 +1301,8 @@
     EXPECT_EQ(10, curInterval.value.long_value);
 
     valueProducer.flushIfNeededLocked(bucket2StartTimeNs);
-    assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {10}, {bucketSizeNs});
+    assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {10}, {bucketSizeNs},
+                                    {bucketStartTimeNs}, {bucket2StartTimeNs});
 }
 
 TEST(ValueMetricProducerTest, TestPushedAggregateMax) {
@@ -1239,9 +1325,6 @@
 
     LogEvent event1(/*uid=*/0, /*pid=*/0);
     CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10);
-
-    LogEvent event2(/*uid=*/0, /*pid=*/0);
-    CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 20);
     valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1);
 
     // has one slice
@@ -1251,6 +1334,8 @@
     EXPECT_EQ(10, curInterval.value.long_value);
     EXPECT_EQ(true, curInterval.hasValue);
 
+    LogEvent event2(/*uid=*/0, /*pid=*/0);
+    CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 20, 20);
     valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event2);
 
     // has one slice
@@ -1258,10 +1343,9 @@
     curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0];
     EXPECT_EQ(20, curInterval.value.long_value);
 
-    valueProducer.flushIfNeededLocked(bucket3StartTimeNs);
-    /* EXPECT_EQ(1UL, valueProducer.mPastBuckets.size()); */
-    /* EXPECT_EQ(1UL, valueProducer.mPastBuckets.begin()->second.size()); */
-    /* EXPECT_EQ(20, valueProducer.mPastBuckets.begin()->second.back().values[0].long_value); */
+    valueProducer.flushIfNeededLocked(bucket2StartTimeNs);
+    assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {20}, {bucketSizeNs},
+                                    {bucketStartTimeNs}, {bucket2StartTimeNs});
 }
 
 TEST(ValueMetricProducerTest, TestPushedAggregateAvg) {
@@ -1351,7 +1435,8 @@
     EXPECT_EQ(25, curInterval.value.long_value);
 
     valueProducer.flushIfNeededLocked(bucket2StartTimeNs);
-    assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {25}, {bucketSizeNs});
+    assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {25}, {bucketSizeNs},
+                                    {bucketStartTimeNs}, {bucket2StartTimeNs});
 }
 
 TEST(ValueMetricProducerTest, TestSkipZeroDiffOutput) {
@@ -1375,10 +1460,8 @@
 
     LogEvent event1(/*uid=*/0, /*pid=*/0);
     CreateRepeatedValueLogEvent(&event1, tagId, bucketStartTimeNs + 10, 10);
-
-    LogEvent event2(/*uid=*/0, /*pid=*/0);
-    CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 15, 15);
     valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event1);
+
     // has one slice
     EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
     ValueMetricProducer::Interval curInterval =
@@ -1388,6 +1471,8 @@
     EXPECT_EQ(10, curBaseInfo.base.long_value);
     EXPECT_EQ(false, curInterval.hasValue);
 
+    LogEvent event2(/*uid=*/0, /*pid=*/0);
+    CreateRepeatedValueLogEvent(&event2, tagId, bucketStartTimeNs + 15, 15);
     valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event2);
 
     // has one slice
@@ -1400,12 +1485,14 @@
     LogEvent event3(/*uid=*/0, /*pid=*/0);
     CreateRepeatedValueLogEvent(&event3, tagId, bucket2StartTimeNs + 10, 15);
     valueProducer.onMatchedLogEvent(1 /*log matcher index*/, event3);
+
     EXPECT_EQ(1UL, valueProducer.mCurrentSlicedBucket.size());
     curInterval = valueProducer.mCurrentSlicedBucket.begin()->second[0];
     curBaseInfo = valueProducer.mCurrentBaseInfo.begin()->second[0];
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(15, curBaseInfo.base.long_value);
     EXPECT_EQ(true, curInterval.hasValue);
+    EXPECT_EQ(0, curInterval.value.long_value);
 
     LogEvent event4(/*uid=*/0, /*pid=*/0);
     CreateRepeatedValueLogEvent(&event4, tagId, bucket2StartTimeNs + 15, 15);
@@ -1416,11 +1503,11 @@
     EXPECT_EQ(true, curBaseInfo.hasBase);
     EXPECT_EQ(15, curBaseInfo.base.long_value);
     EXPECT_EQ(true, curInterval.hasValue);
+    EXPECT_EQ(0, curInterval.value.long_value);
 
     valueProducer.flushIfNeededLocked(bucket3StartTimeNs);
-    EXPECT_EQ(1UL, valueProducer.mPastBuckets.size());
-    EXPECT_EQ(1UL, valueProducer.mPastBuckets.begin()->second.size());
-    assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {5}, {bucketSizeNs});
+    assertPastBucketValuesSingleKey(valueProducer.mPastBuckets, {5}, {bucketSizeNs},
+                                    {bucketStartTimeNs}, {bucket2StartTimeNs});
 }
 
 TEST(ValueMetricProducerTest, TestSkipZeroDiffOutputMultiValue) {
@@ -1740,8 +1827,8 @@
     EXPECT_EQ(3, baseInfo1.base.long_value);
     EXPECT_EQ(false, interval1.hasValue);
     EXPECT_EQ(0UL, valueProducer->mPastBuckets.size());
-    vector<shared_ptr<LogEvent>> allData;
 
+    vector<shared_ptr<LogEvent>> allData;
     allData.clear();
     allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 1, 2, 4));
     allData.push_back(CreateTwoValueLogEvent(tagId, bucket2StartTimeNs + 1, 1, 11));
@@ -1753,7 +1840,8 @@
     EXPECT_EQ(false, interval1.hasValue);
     EXPECT_EQ(8, interval1.value.long_value);
     EXPECT_FALSE(interval1.seenNewData);
-    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {8}, {bucketSizeNs});
+    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {8}, {bucketSizeNs},
+                                    {bucketStartTimeNs}, {bucket2StartTimeNs});
 
     auto it = valueProducer->mCurrentSlicedBucket.begin();
     for (; it != valueProducer->mCurrentSlicedBucket.end(); it++) {
@@ -1769,14 +1857,13 @@
     }
     EXPECT_TRUE(it != iter);
     EXPECT_TRUE(itBase != iterBase);
-    auto& interval2 = it->second[0];
-    auto& baseInfo2 = itBase->second[0];
+    auto interval2 = it->second[0];
+    auto baseInfo2 = itBase->second[0];
     EXPECT_EQ(2, it->first.getDimensionKeyInWhat().getValues()[0].mValue.int_value);
     EXPECT_EQ(true, baseInfo2.hasBase);
     EXPECT_EQ(4, baseInfo2.base.long_value);
     EXPECT_EQ(false, interval2.hasValue);
     EXPECT_FALSE(interval2.seenNewData);
-    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {8}, {bucketSizeNs});
 
     // next pull somehow did not happen, skip to end of bucket 3
     allData.clear();
@@ -1791,7 +1878,8 @@
     EXPECT_EQ(5, baseInfo2.base.long_value);
     EXPECT_EQ(false, interval2.hasValue);
     EXPECT_FALSE(interval2.seenNewData);
-    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {8}, {bucketSizeNs});
+    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {8}, {bucketSizeNs},
+                                    {bucketStartTimeNs}, {bucket2StartTimeNs});
 
     allData.clear();
     allData.push_back(CreateTwoValueLogEvent(tagId, bucket5StartTimeNs + 1, 2, 14));
@@ -1805,9 +1893,13 @@
     EXPECT_FALSE(interval2.seenNewData);
     ASSERT_EQ(2UL, valueProducer->mPastBuckets.size());
     auto iterator = valueProducer->mPastBuckets.begin();
+    EXPECT_EQ(bucket4StartTimeNs, iterator->second[0].mBucketStartNs);
+    EXPECT_EQ(bucket5StartTimeNs, iterator->second[0].mBucketEndNs);
     EXPECT_EQ(9, iterator->second[0].values[0].long_value);
     EXPECT_EQ(bucketSizeNs, iterator->second[0].mConditionTrueNs);
     iterator++;
+    EXPECT_EQ(bucketStartTimeNs, iterator->second[0].mBucketStartNs);
+    EXPECT_EQ(bucket2StartTimeNs, iterator->second[0].mBucketEndNs);
     EXPECT_EQ(8, iterator->second[0].values[0].long_value);
     EXPECT_EQ(bucketSizeNs, iterator->second[0].mConditionTrueNs);
 }
@@ -2414,7 +2506,8 @@
     EXPECT_EQ(true, valueProducer->mHasGlobalBase);
 
     EXPECT_EQ(1UL, valueProducer->mPastBuckets.size());
-    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {1}, {bucketSizeNs - 12 + 1});
+    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {1}, {bucketSizeNs - 12 + 1},
+                                    {bucketStartTimeNs}, {bucket2StartTimeNs});
 }
 
 TEST(ValueMetricProducerTest, TestPartialResetOnBucketBoundaries) {
@@ -2461,10 +2554,11 @@
     EXPECT_EQ(true, valueProducer->mHasGlobalBase);
 }
 
-TEST(ValueMetricProducerTest, TestFullBucketResetWhenLastBucketInvalid) {
+TEST_P(ValueMetricProducerTest_PartialBucket, TestFullBucketResetWhenLastBucketInvalid) {
     ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
 
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
+    int64_t partialBucketSplitTimeNs = bucketStartTimeNs + bucketSizeNs / 2;
     EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _))
             // Initialization.
             .WillOnce(Invoke(
@@ -2474,23 +2568,41 @@
                         return true;
                     }))
             // notifyAppUpgrade.
-            .WillOnce(Invoke([](int tagId, const ConfigKey&,
-                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+            .WillOnce(Invoke([partialBucketSplitTimeNs](int tagId, const ConfigKey&,
+                                                        vector<std::shared_ptr<LogEvent>>* data,
+                                                        bool) {
                 data->clear();
-                data->push_back(CreateRepeatedValueLogEvent(
-                        tagId, bucketStartTimeNs + bucketSizeNs / 2, 10));
+                data->push_back(CreateRepeatedValueLogEvent(tagId, partialBucketSplitTimeNs, 10));
                 return true;
             }));
     sp<ValueMetricProducer> valueProducer =
             ValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager, metric);
     ASSERT_EQ(0UL, valueProducer->mCurrentFullBucket.size());
 
-    valueProducer->notifyAppUpgrade(bucketStartTimeNs + bucketSizeNs / 2, "com.foo", 10000, 1);
+    switch (GetParam()) {
+        case APP_UPGRADE:
+            valueProducer->notifyAppUpgrade(partialBucketSplitTimeNs);
+            break;
+        case BOOT_COMPLETE:
+            valueProducer->onStatsdInitCompleted(partialBucketSplitTimeNs);
+            break;
+    }
+    EXPECT_EQ(partialBucketSplitTimeNs, valueProducer->mCurrentBucketStartTimeNs);
+    EXPECT_EQ(0, valueProducer->getCurrentBucketNum());
+    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {9},
+                                    {partialBucketSplitTimeNs - bucketStartTimeNs},
+                                    {bucketStartTimeNs}, {partialBucketSplitTimeNs});
     ASSERT_EQ(1UL, valueProducer->mCurrentFullBucket.size());
 
     vector<shared_ptr<LogEvent>> allData;
     allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket3StartTimeNs + 1, 4));
     valueProducer->onDataPulled(allData, /** fails */ false, bucket3StartTimeNs + 1);
+    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {9},
+                                    {partialBucketSplitTimeNs - bucketStartTimeNs},
+                                    {bucketStartTimeNs}, {partialBucketSplitTimeNs});
+    ASSERT_EQ(1, valueProducer->mSkippedBuckets.size());
+    EXPECT_EQ(partialBucketSplitTimeNs, valueProducer->mSkippedBuckets[0].bucketStartTimeNs);
+    EXPECT_EQ(bucket2StartTimeNs, valueProducer->mSkippedBuckets[0].bucketEndTimeNs);
     ASSERT_EQ(0UL, valueProducer->mCurrentFullBucket.size());
 }
 
@@ -2537,7 +2649,8 @@
     valueProducer->onConditionChanged(false, bucket3StartTimeNs + 10);
 
     // Bucket should have been completed.
-    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {2}, {bucketSizeNs - 10});
+    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {2}, {bucketSizeNs - 10},
+                                    {bucket2StartTimeNs}, {bucket3StartTimeNs});
 }
 
 TEST(ValueMetricProducerTest, TestLateOnDataPulledWithoutDiff) {
@@ -2557,7 +2670,8 @@
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs);
 
     // Bucket should have been completed.
-    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {30}, {bucketSizeNs});
+    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {30}, {bucketSizeNs},
+                                    {bucketStartTimeNs}, {bucket2StartTimeNs});
 }
 
 TEST(ValueMetricProducerTest, TestLateOnDataPulledWithDiff) {
@@ -2585,12 +2699,14 @@
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs);
 
     // Bucket should have been completed.
-    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {19}, {bucketSizeNs});
+    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {19}, {bucketSizeNs},
+                                    {bucketStartTimeNs}, {bucket2StartTimeNs});
 }
 
-TEST(ValueMetricProducerTest, TestBucketBoundariesOnAppUpgrade) {
+TEST_P(ValueMetricProducerTest_PartialBucket, TestBucketBoundariesOnPartialBucket) {
     ValueMetric metric = ValueMetricProducerTestHelper::createMetric();
 
+    int64_t partialBucketSplitTimeNs = bucket2StartTimeNs + 2;
     sp<MockStatsPullerManager> pullerManager = new StrictMock<MockStatsPullerManager>();
     EXPECT_CALL(*pullerManager, Pull(tagId, kConfigKey, _, _))
             // Initialization.
@@ -2601,20 +2717,29 @@
                         return true;
                     }))
             // notifyAppUpgrade.
-            .WillOnce(Invoke([](int tagId, const ConfigKey&,
-                                vector<std::shared_ptr<LogEvent>>* data, bool) {
+            .WillOnce(Invoke([partialBucketSplitTimeNs](int tagId, const ConfigKey&,
+                                                        vector<std::shared_ptr<LogEvent>>* data,
+                                                        bool) {
                 data->clear();
-                data->push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 2, 10));
+                data->push_back(CreateRepeatedValueLogEvent(tagId, partialBucketSplitTimeNs, 10));
                 return true;
             }));
 
     sp<ValueMetricProducer> valueProducer =
             ValueMetricProducerTestHelper::createValueProducerNoConditions(pullerManager, metric);
 
-    valueProducer->notifyAppUpgrade(bucket2StartTimeNs + 2, "com.foo", 10000, 1);
+    switch (GetParam()) {
+        case APP_UPGRADE:
+            valueProducer->notifyAppUpgrade(partialBucketSplitTimeNs);
+            break;
+        case BOOT_COMPLETE:
+            valueProducer->onStatsdInitCompleted(partialBucketSplitTimeNs);
+            break;
+    }
 
     // Bucket should have been completed.
-    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {9}, {bucketSizeNs});
+    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {9}, {bucketSizeNs},
+                                    {bucketStartTimeNs}, {bucket2StartTimeNs});
 }
 
 TEST(ValueMetricProducerTest, TestDataIsNotUpdatedWhenNoConditionChanged) {
@@ -2642,7 +2767,7 @@
 
     valueProducer->onConditionChanged(true, bucketStartTimeNs + 8);
     valueProducer->onConditionChanged(false, bucketStartTimeNs + 10);
-    valueProducer->onConditionChanged(false, bucketStartTimeNs + 10);
+    valueProducer->onConditionChanged(false, bucketStartTimeNs + 12);
 
     EXPECT_EQ(1UL, valueProducer->mCurrentSlicedBucket.size());
     auto curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
@@ -2654,7 +2779,8 @@
     allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 1, 10));
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs + 1);
 
-    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {2}, {2});
+    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {2}, {2}, {bucketStartTimeNs},
+                                    {bucket2StartTimeNs});
 }
 
 // TODO: b/145705635 fix or delete this test
@@ -2705,7 +2831,7 @@
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs);
 
     // There was not global base available so all buckets are invalid.
-    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {}, {});
+    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {}, {}, {}, {});
 }
 
 TEST(ValueMetricProducerTest, TestPullNeededFastDump) {
@@ -2849,7 +2975,8 @@
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs + 30);
 
     // Bucket should have been completed.
-    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {10}, {bucketSizeNs});
+    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {10}, {bucketSizeNs},
+                                    {bucketStartTimeNs}, {bucket2StartTimeNs});
 }
 
 TEST(ValueMetricProducerTest, TestPulledData_noDiff_withMultipleConditionChanges) {
@@ -2892,7 +3019,8 @@
     allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 30, 110));
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs);
 
-    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {50 - 8});
+    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {20}, {50 - 8},
+                                    {bucketStartTimeNs}, {bucket2StartTimeNs});
     curInterval = valueProducer->mCurrentSlicedBucket.begin()->second[0];
     curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
     EXPECT_EQ(false, curBaseInfo.hasBase);
@@ -2923,7 +3051,8 @@
     allData.push_back(CreateRepeatedValueLogEvent(tagId, bucket2StartTimeNs + 30, 30));
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs);
 
-    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {30}, {bucketSizeNs - 8});
+    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {30}, {bucketSizeNs - 8},
+                                    {bucketStartTimeNs}, {bucket2StartTimeNs});
     ValueMetricProducer::Interval curInterval =
             valueProducer->mCurrentSlicedBucket.begin()->second[0];
     ValueMetricProducer::BaseInfo curBaseInfo = valueProducer->mCurrentBaseInfo.begin()->second[0];
@@ -2946,7 +3075,7 @@
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs);
 
     // Condition was always false.
-    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {}, {});
+    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {}, {}, {}, {});
 }
 
 TEST(ValueMetricProducerTest, TestPulledData_noDiff_withFailure) {
@@ -2976,7 +3105,7 @@
     valueProducer->onDataPulled(allData, /** succeed */ true, bucket2StartTimeNs);
 
     // No buckets, we had a failure.
-    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {}, {});
+    assertPastBucketValuesSingleKey(valueProducer->mPastBuckets, {}, {}, {}, {});
 }
 
 /*
diff --git a/cmds/statsd/tests/metrics/metrics_test_helper.h b/cmds/statsd/tests/metrics/metrics_test_helper.h
index 69f7e3f..be410b1 100644
--- a/cmds/statsd/tests/metrics/metrics_test_helper.h
+++ b/cmds/statsd/tests/metrics/metrics_test_helper.h
@@ -44,7 +44,8 @@
                             vector<std::shared_ptr<LogEvent>>* data, bool useUids));
     MOCK_METHOD2(RegisterPullUidProvider,
                  void(const ConfigKey& configKey, wp<PullUidProvider> provider));
-    MOCK_METHOD1(UnregisterPullUidProvider, void(const ConfigKey& configKey));
+    MOCK_METHOD2(UnregisterPullUidProvider,
+                 void(const ConfigKey& configKey, wp<PullUidProvider> provider));
 };
 
 class MockUidMap : public UidMap {
diff --git a/cmds/statsd/tests/statsd_test_util.h b/cmds/statsd/tests/statsd_test_util.h
index 37b9889..4d68ea2 100644
--- a/cmds/statsd/tests/statsd_test_util.h
+++ b/cmds/statsd/tests/statsd_test_util.h
@@ -42,6 +42,8 @@
 const int SCREEN_STATE_ATOM_ID = util::SCREEN_STATE_CHANGED;
 const int UID_PROCESS_STATE_ATOM_ID = util::UID_PROCESS_STATE_CHANGED;
 
+enum BucketSplitEvent { APP_UPGRADE, BOOT_COMPLETE };
+
 // Converts a ProtoOutputStream to a StatsLogReport proto.
 StatsLogReport outputStreamToProto(ProtoOutputStream* proto);
 
diff --git a/cmds/statsd/tests/utils/MultiConditionTrigger_test.cpp b/cmds/statsd/tests/utils/MultiConditionTrigger_test.cpp
new file mode 100644
index 0000000..db402a0
--- /dev/null
+++ b/cmds/statsd/tests/utils/MultiConditionTrigger_test.cpp
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "utils/MultiConditionTrigger.h"
+
+#include <gtest/gtest.h>
+
+#include <chrono>
+#include <set>
+#include <thread>
+#include <vector>
+
+#ifdef __ANDROID__
+
+using namespace std;
+using std::this_thread::sleep_for;
+
+namespace android {
+namespace os {
+namespace statsd {
+
+TEST(MultiConditionTrigger, TestMultipleConditions) {
+    int numConditions = 5;
+    string t1 = "t1", t2 = "t2", t3 = "t3", t4 = "t4", t5 = "t5";
+    set<string> conditionNames = {t1, t2, t3, t4, t5};
+
+    mutex lock;
+    condition_variable cv;
+    bool triggerCalled = false;
+
+    // Mark done as true and notify in the done.
+    MultiConditionTrigger trigger(conditionNames, [&lock, &cv, &triggerCalled] {
+        {
+            lock_guard lg(lock);
+            triggerCalled = true;
+        }
+        cv.notify_all();
+    });
+
+    vector<thread> threads;
+    vector<bool> done(numConditions, false);
+
+    int i = 0;
+    for (const string& conditionName : conditionNames) {
+        threads.emplace_back([&done, &conditionName, &trigger, i] {
+            sleep_for(chrono::milliseconds(3));
+            done[i] = true;
+            trigger.markComplete(conditionName);
+        });
+        i++;
+    }
+
+    unique_lock<mutex> unique_lk(lock);
+    cv.wait(unique_lk, [&triggerCalled] {
+        return triggerCalled;
+    });
+
+    for (i = 0; i < numConditions; i++) {
+        EXPECT_EQ(done[i], 1);
+    }
+
+    for (i = 0; i < numConditions; i++) {
+        threads[i].join();
+    }
+}
+
+TEST(MultiConditionTrigger, TestNoConditions) {
+    mutex lock;
+    condition_variable cv;
+    bool triggerCalled = false;
+
+    MultiConditionTrigger trigger({}, [&lock, &cv, &triggerCalled] {
+        {
+            lock_guard lg(lock);
+            triggerCalled = true;
+        }
+        cv.notify_all();
+    });
+
+    unique_lock<mutex> unique_lk(lock);
+    cv.wait(unique_lk, [&triggerCalled] { return triggerCalled; });
+    EXPECT_TRUE(triggerCalled);
+    // Ensure that trigger occurs immediately if no events need to be completed.
+}
+
+TEST(MultiConditionTrigger, TestMarkCompleteCalledBySameCondition) {
+    string t1 = "t1", t2 = "t2";
+    set<string> conditionNames = {t1, t2};
+
+    mutex lock;
+    condition_variable cv;
+    bool triggerCalled = false;
+
+    MultiConditionTrigger trigger(conditionNames, [&lock, &cv, &triggerCalled] {
+        {
+            lock_guard lg(lock);
+            triggerCalled = true;
+        }
+        cv.notify_all();
+    });
+
+    trigger.markComplete(t1);
+    trigger.markComplete(t1);
+
+    // Ensure that the trigger still hasn't fired.
+    {
+        lock_guard lg(lock);
+        EXPECT_FALSE(triggerCalled);
+    }
+
+    trigger.markComplete(t2);
+    unique_lock<mutex> unique_lk(lock);
+    cv.wait(unique_lk, [&triggerCalled] { return triggerCalled; });
+    EXPECT_TRUE(triggerCalled);
+}
+
+TEST(MultiConditionTrigger, TestTriggerOnlyCalledOnce) {
+    string t1 = "t1";
+    set<string> conditionNames = {t1};
+
+    mutex lock;
+    condition_variable cv;
+    bool triggerCalled = false;
+    int triggerCount = 0;
+
+    MultiConditionTrigger trigger(conditionNames, [&lock, &cv, &triggerCalled, &triggerCount] {
+        {
+            lock_guard lg(lock);
+            triggerCount++;
+            triggerCalled = true;
+        }
+        cv.notify_all();
+    });
+
+    trigger.markComplete(t1);
+
+    // Ensure that the trigger fired.
+    {
+        unique_lock<mutex> unique_lk(lock);
+        cv.wait(unique_lk, [&triggerCalled] { return triggerCalled; });
+        EXPECT_TRUE(triggerCalled);
+        EXPECT_EQ(triggerCount, 1);
+        triggerCalled = false;
+    }
+
+    trigger.markComplete(t1);
+
+    // Ensure that the trigger does not fire again.
+    {
+        unique_lock<mutex> unique_lk(lock);
+        cv.wait_for(unique_lk, chrono::milliseconds(5), [&triggerCalled] { return triggerCalled; });
+        EXPECT_FALSE(triggerCalled);
+        EXPECT_EQ(triggerCount, 1);
+    }
+}
+
+}  // namespace statsd
+}  // namespace os
+}  // namespace android
+#else
+GTEST_LOG_(INFO) << "This test does nothing.\n";
+#endif
diff --git a/cmds/statsd/tests/utils/NamedLatch_test.cpp b/cmds/statsd/tests/utils/NamedLatch_test.cpp
deleted file mode 100644
index de48a13..0000000
--- a/cmds/statsd/tests/utils/NamedLatch_test.cpp
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-#include "utils/NamedLatch.h"
-
-#include <gtest/gtest.h>
-
-#include <chrono>
-#include <set>
-#include <thread>
-#include <vector>
-
-#ifdef __ANDROID__
-
-using namespace std;
-using std::this_thread::sleep_for;
-
-namespace android {
-namespace os {
-namespace statsd {
-
-TEST(NamedLatchTest, TestWait) {
-    int numEvents = 5;
-    string t1 = "t1", t2 = "t2", t3 = "t3", t4 = "t4", t5 = "t5";
-    set<string> eventNames = {t1, t2, t3, t4, t5};
-
-    NamedLatch latch(eventNames);
-    vector<thread> threads;
-    vector<bool> done(numEvents, false);
-
-    int i = 0;
-    for (const string& eventName : eventNames) {
-        threads.emplace_back([&done, &eventName, &latch, i] {
-            sleep_for(chrono::milliseconds(3));
-            done[i] = true;
-            latch.countDown(eventName);
-        });
-        i++;
-    }
-
-    latch.wait();
-
-    for (i = 0; i < numEvents; i++) {
-        EXPECT_EQ(done[i], 1);
-    }
-
-    for (i = 0; i < numEvents; i++) {
-        threads[i].join();
-    }
-}
-
-TEST(NamedLatchTest, TestNoWorkers) {
-    NamedLatch latch({});
-    latch.wait();
-    // Ensure that latch does not wait if no events need to countDown.
-}
-
-TEST(NamedLatchTest, TestCountDownCalledBySameEventName) {
-    string t1 = "t1", t2 = "t2";
-    set<string> eventNames = {t1, t2};
-
-    NamedLatch latch(eventNames);
-
-    thread waiterThread([&latch] { latch.wait(); });
-
-    latch.countDown(t1);
-    latch.countDown(t1);
-
-    // Ensure that the latch's remaining threads still has t2.
-    latch.mMutex.lock();
-    ASSERT_EQ(latch.mRemainingEventNames.size(), 1);
-    EXPECT_NE(latch.mRemainingEventNames.find(t2), latch.mRemainingEventNames.end());
-    latch.mMutex.unlock();
-
-    latch.countDown(t2);
-    waiterThread.join();
-}
-
-}  // namespace statsd
-}  // namespace os
-}  // namespace android
-#else
-GTEST_LOG_(INFO) << "This test does nothing.\n";
-#endif
diff --git a/core/java/android/app/ActivityTaskManager.java b/core/java/android/app/ActivityTaskManager.java
index 1b941de..1cc63da 100644
--- a/core/java/android/app/ActivityTaskManager.java
+++ b/core/java/android/app/ActivityTaskManager.java
@@ -23,8 +23,10 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Rect;
+import android.os.Build;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -430,4 +432,14 @@
             throw e.rethrowFromSystemServer();
         }
     }
+
+    /** Returns whether the current UI mode supports error dialogs (ANR, crash, etc). */
+    public static boolean currentUiModeSupportsErrorDialogs(@NonNull Context context) {
+        final Configuration config = context.getResources().getConfiguration();
+        int modeType = config.uiMode & Configuration.UI_MODE_TYPE_MASK;
+        return (modeType != Configuration.UI_MODE_TYPE_CAR
+                && !(modeType == Configuration.UI_MODE_TYPE_WATCH && Build.IS_USER)
+                && modeType != Configuration.UI_MODE_TYPE_TELEVISION
+                && modeType != Configuration.UI_MODE_TYPE_VR_HEADSET);
+    }
 }
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 21b56d3..696ee9f 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -375,7 +375,6 @@
     String mInstrumentedLibDir = null;
     boolean mSystemThread = false;
     boolean mSomeActivitiesChanged = false;
-    boolean mUpdatingSystemConfig = false;
     /* package */ boolean mHiddenApiWarningShown = false;
 
     // These can be accessed by multiple threads; mResourcesManager is the lock.
@@ -587,8 +586,11 @@
                     throw new IllegalStateException(
                             "Received config update for non-existing activity");
                 }
+                // Given alwaysReportChange=false because the configuration is from view root, the
+                // activity may not be able to handle the changes. In that case the activity will be
+                // relaunched immediately, then Activity#onConfigurationChanged shouldn't be called.
                 activity.mMainThread.handleActivityConfigurationChanged(token, overrideConfig,
-                        newDisplayId);
+                        newDisplayId, false /* alwaysReportChange */);
             };
         }
 
@@ -2029,12 +2031,7 @@
                     break;
                 }
                 case APPLICATION_INFO_CHANGED:
-                    mUpdatingSystemConfig = true;
-                    try {
-                        handleApplicationInfoChanged((ApplicationInfo) msg.obj);
-                    } finally {
-                        mUpdatingSystemConfig = false;
-                    }
+                    handleApplicationInfoChanged((ApplicationInfo) msg.obj);
                     break;
                 case RUN_ISOLATED_ENTRY_POINT:
                     handleRunIsolatedEntryPoint((String) ((SomeArgs) msg.obj).arg1,
@@ -4467,7 +4464,8 @@
         // simply finishing, and we are not starting another activity.
         if (!r.activity.mFinished && willBeVisible && r.activity.mDecor != null && !r.hideForNow) {
             if (r.newConfig != null) {
-                performConfigurationChangedForActivity(r, r.newConfig);
+                performConfigurationChangedForActivity(r, r.newConfig,
+                        false /* alwaysReportChange */);
                 if (DEBUG_CONFIGURATION) {
                     Slog.v(TAG, "Resuming activity " + r.activityInfo.name + " with newConfig "
                             + r.activity.mCurrentConfig);
@@ -4787,7 +4785,8 @@
                     }
                 }
                 if (r.newConfig != null) {
-                    performConfigurationChangedForActivity(r, r.newConfig);
+                    performConfigurationChangedForActivity(r, r.newConfig,
+                            false /* alwaysReportChange */);
                     if (DEBUG_CONFIGURATION) Slog.v(TAG, "Updating activity vis "
                             + r.activityInfo.name + " with new config "
                             + r.activity.mCurrentConfig);
@@ -5447,11 +5446,12 @@
      * @param r ActivityClientRecord representing the Activity.
      * @param newBaseConfig The new configuration to use. This may be augmented with
      *                      {@link ActivityClientRecord#overrideConfig}.
+     * @param alwaysReportChange If the configuration is changed, always report to activity.
      */
     private void performConfigurationChangedForActivity(ActivityClientRecord r,
-            Configuration newBaseConfig) {
-        performConfigurationChangedForActivity(r, newBaseConfig,
-                r.activity.getDisplayId(), false /* movedToDifferentDisplay */);
+            Configuration newBaseConfig, boolean alwaysReportChange) {
+        performConfigurationChangedForActivity(r, newBaseConfig, r.activity.getDisplayId(),
+                false /* movedToDifferentDisplay */, alwaysReportChange);
     }
 
     /**
@@ -5464,16 +5464,19 @@
      *                      {@link ActivityClientRecord#overrideConfig}.
      * @param displayId The id of the display where the Activity currently resides.
      * @param movedToDifferentDisplay Indicates if the activity was moved to different display.
+     * @param alwaysReportChange If the configuration is changed, always report to activity.
      * @return {@link Configuration} instance sent to client, null if not sent.
      */
     private Configuration performConfigurationChangedForActivity(ActivityClientRecord r,
-            Configuration newBaseConfig, int displayId, boolean movedToDifferentDisplay) {
+            Configuration newBaseConfig, int displayId, boolean movedToDifferentDisplay,
+            boolean alwaysReportChange) {
         r.tmpConfig.setTo(newBaseConfig);
         if (r.overrideConfig != null) {
             r.tmpConfig.updateFrom(r.overrideConfig);
         }
         final Configuration reportedConfig = performActivityConfigurationChanged(r.activity,
-                r.tmpConfig, r.overrideConfig, displayId, movedToDifferentDisplay);
+                r.tmpConfig, r.overrideConfig, displayId, movedToDifferentDisplay,
+                alwaysReportChange);
         freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(r.tmpConfig));
         return reportedConfig;
     }
@@ -5529,11 +5532,12 @@
      *                         ActivityManager.
      * @param displayId Id of the display where activity currently resides.
      * @param movedToDifferentDisplay Indicates if the activity was moved to different display.
+     * @param alwaysReportChange If the configuration is changed, always report to activity.
      * @return Configuration sent to client, null if no changes and not moved to different display.
      */
     private Configuration performActivityConfigurationChanged(Activity activity,
             Configuration newConfig, Configuration amOverrideConfig, int displayId,
-            boolean movedToDifferentDisplay) {
+            boolean movedToDifferentDisplay, boolean alwaysReportChange) {
         if (activity == null) {
             throw new IllegalArgumentException("No activity provided.");
         }
@@ -5556,7 +5560,7 @@
                 // Always send the task-level config changes. For system-level configuration, if
                 // this activity doesn't handle any of the config changes, then don't bother
                 // calling onConfigurationChanged as we're going to destroy it.
-                if (!mUpdatingSystemConfig
+                if (alwaysReportChange
                         || (~activity.mActivityInfo.getRealConfigChanged() & diff) == 0
                         || !REPORT_TO_ACTIVITY) {
                     shouldChangeConfig = true;
@@ -5638,12 +5642,7 @@
     public void handleConfigurationChanged(Configuration config) {
         Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "configChanged");
         mCurDefaultDisplayDpi = config.densityDpi;
-        mUpdatingSystemConfig = true;
-        try {
-            handleConfigurationChanged(config, null /* compat */);
-        } finally {
-            mUpdatingSystemConfig = false;
-        }
+        handleConfigurationChanged(config, null /* compat */);
         Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
     }
 
@@ -5725,7 +5724,7 @@
                     // config and avoid onConfigurationChanged if it hasn't changed.
                     Activity a = (Activity) cb;
                     performConfigurationChangedForActivity(mActivities.get(a.getActivityToken()),
-                            config);
+                            config, false /* alwaysReportChange */);
                 } else if (!equivalent) {
                     performConfigurationChanged(cb, config);
                 } else {
@@ -5832,16 +5831,28 @@
         }
     }
 
+    @Override
+    public void handleActivityConfigurationChanged(IBinder activityToken,
+            Configuration overrideConfig, int displayId) {
+        handleActivityConfigurationChanged(activityToken, overrideConfig, displayId,
+                // This is the only place that uses alwaysReportChange=true. The entry point should
+                // be from ActivityConfigurationChangeItem or MoveToDisplayItem, so the server side
+                // has confirmed the activity should handle the configuration instead of relaunch.
+                // If Activity#onConfigurationChanged is called unexpectedly, then we can know it is
+                // something wrong from server side.
+                true /* alwaysReportChange */);
+    }
+
     /**
      * Handle new activity configuration and/or move to a different display.
      * @param activityToken Target activity token.
      * @param overrideConfig Activity override config.
      * @param displayId Id of the display where activity was moved to, -1 if there was no move and
      *                  value didn't change.
+     * @param alwaysReportChange If the configuration is changed, always report to activity.
      */
-    @Override
-    public void handleActivityConfigurationChanged(IBinder activityToken,
-            Configuration overrideConfig, int displayId) {
+    void handleActivityConfigurationChanged(IBinder activityToken, Configuration overrideConfig,
+            int displayId, boolean alwaysReportChange) {
         ActivityClientRecord r = mActivities.get(activityToken);
         // Check input params.
         if (r == null || r.activity == null) {
@@ -5880,14 +5891,15 @@
                     + ", config=" + overrideConfig);
 
             final Configuration reportedConfig = performConfigurationChangedForActivity(r,
-                    mCompatConfiguration, displayId, true /* movedToDifferentDisplay */);
+                    mCompatConfiguration, displayId, true /* movedToDifferentDisplay */,
+                    alwaysReportChange);
             if (viewRoot != null) {
                 viewRoot.onMovedToDisplay(displayId, reportedConfig);
             }
         } else {
             if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handle activity config changed: "
                     + r.activityInfo.name + ", config=" + overrideConfig);
-            performConfigurationChangedForActivity(r, mCompatConfiguration);
+            performConfigurationChangedForActivity(r, mCompatConfiguration, alwaysReportChange);
         }
         // Notify the ViewRootImpl instance about configuration changes. It may have initiated this
         // update to make sure that resources are updated before updating itself.
diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java
index 8edf03d..af02783 100644
--- a/core/java/android/app/Notification.java
+++ b/core/java/android/app/Notification.java
@@ -2301,11 +2301,11 @@
 
         priority = parcel.readInt();
 
-        category = parcel.readString();
+        category = parcel.readString8();
 
-        mGroupKey = parcel.readString();
+        mGroupKey = parcel.readString8();
 
-        mSortKey = parcel.readString();
+        mSortKey = parcel.readString8();
 
         extras = Bundle.setDefusable(parcel.readBundle(), true); // may be null
         fixDuplicateExtras();
@@ -2329,12 +2329,12 @@
         color = parcel.readInt();
 
         if (parcel.readInt() != 0) {
-            mChannelId = parcel.readString();
+            mChannelId = parcel.readString8();
         }
         mTimeout = parcel.readLong();
 
         if (parcel.readInt() != 0) {
-            mShortcutId = parcel.readString();
+            mShortcutId = parcel.readString8();
         }
 
         if (parcel.readInt() != 0) {
@@ -2766,11 +2766,11 @@
 
         parcel.writeInt(priority);
 
-        parcel.writeString(category);
+        parcel.writeString8(category);
 
-        parcel.writeString(mGroupKey);
+        parcel.writeString8(mGroupKey);
 
-        parcel.writeString(mSortKey);
+        parcel.writeString8(mSortKey);
 
         parcel.writeBundle(extras); // null ok
 
@@ -2803,7 +2803,7 @@
 
         if (mChannelId != null) {
             parcel.writeInt(1);
-            parcel.writeString(mChannelId);
+            parcel.writeString8(mChannelId);
         } else {
             parcel.writeInt(0);
         }
@@ -2811,7 +2811,7 @@
 
         if (mShortcutId != null) {
             parcel.writeInt(1);
-            parcel.writeString(mShortcutId);
+            parcel.writeString8(mShortcutId);
         } else {
             parcel.writeInt(0);
         }
@@ -8873,7 +8873,7 @@
             }
             mDesiredHeightResId = in.readInt();
             if (in.readInt() != 0) {
-                mShortcutId = in.readString();
+                mShortcutId = in.readString8();
             }
         }
 
@@ -9029,7 +9029,7 @@
             out.writeInt(mDesiredHeightResId);
             out.writeInt(TextUtils.isEmpty(mShortcutId) ? 0 : 1);
             if (!TextUtils.isEmpty(mShortcutId)) {
-                out.writeString(mShortcutId);
+                out.writeString8(mShortcutId);
             }
         }
 
diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java
index 2feb927..9f8d3c4 100644
--- a/core/java/android/app/NotificationChannel.java
+++ b/core/java/android/app/NotificationChannel.java
@@ -102,7 +102,7 @@
     private static final String ATT_FG_SERVICE_SHOWN = "fgservice";
     private static final String ATT_GROUP = "group";
     private static final String ATT_BLOCKABLE_SYSTEM = "blockable_system";
-    private static final String ATT_ALLOW_BUBBLE = "allow_bubble";
+    private static final String ATT_ALLOW_BUBBLE = "allow_bubbles";
     private static final String ATT_ORIG_IMP = "orig_imp";
     private static final String ATT_PARENT_CHANNEL = "parent";
     private static final String ATT_CONVERSATION_ID = "conv_id";
@@ -168,7 +168,12 @@
             NotificationManager.IMPORTANCE_UNSPECIFIED;
     private static final boolean DEFAULT_DELETED = false;
     private static final boolean DEFAULT_SHOW_BADGE = true;
-    private static final boolean DEFAULT_ALLOW_BUBBLE = false;
+    /**
+     * @hide
+     */
+    public static final int DEFAULT_ALLOW_BUBBLE = -1;
+    private static final int ALLOW_BUBBLE_ON = 1;
+    private static final int ALLOW_BUBBLE_OFF = 0;
 
     @UnsupportedAppUsage
     private String mId;
@@ -193,7 +198,7 @@
     private AudioAttributes mAudioAttributes = Notification.AUDIO_ATTRIBUTES_DEFAULT;
     // If this is a blockable system notification channel.
     private boolean mBlockableSystem = false;
-    private boolean mAllowBubbles = DEFAULT_ALLOW_BUBBLE;
+    private int mAllowBubbles = DEFAULT_ALLOW_BUBBLE;
     private boolean mImportanceLockedByOEM;
     private boolean mImportanceLockedDefaultApp;
     private String mParentId = null;
@@ -261,7 +266,7 @@
         mAudioAttributes = in.readInt() > 0 ? AudioAttributes.CREATOR.createFromParcel(in) : null;
         mLightColor = in.readInt();
         mBlockableSystem = in.readBoolean();
-        mAllowBubbles = in.readBoolean();
+        mAllowBubbles = in.readInt();
         mImportanceLockedByOEM = in.readBoolean();
         mOriginalImportance = in.readInt();
         mParentId = in.readString();
@@ -320,7 +325,7 @@
         }
         dest.writeInt(mLightColor);
         dest.writeBoolean(mBlockableSystem);
-        dest.writeBoolean(mAllowBubbles);
+        dest.writeInt(mAllowBubbles);
         dest.writeBoolean(mImportanceLockedByOEM);
         dest.writeInt(mOriginalImportance);
         dest.writeString(mParentId);
@@ -550,7 +555,14 @@
      * @see Notification#getBubbleMetadata()
      */
     public void setAllowBubbles(boolean allowBubbles) {
-        mAllowBubbles = allowBubbles;
+        mAllowBubbles = allowBubbles ? ALLOW_BUBBLE_ON : ALLOW_BUBBLE_OFF;
+    }
+
+    /**
+     * @hide
+     */
+    public void setAllowBubbles(int allowed) {
+        mAllowBubbles = allowed;
     }
 
     /**
@@ -701,6 +713,13 @@
      * @see Notification#getBubbleMetadata()
      */
     public boolean canBubble() {
+        return mAllowBubbles == ALLOW_BUBBLE_ON;
+    }
+
+    /**
+     * @hide
+     */
+    public int getAllowBubbles() {
         return mAllowBubbles;
     }
 
@@ -872,7 +891,7 @@
         lockFields(safeInt(parser, ATT_USER_LOCKED, 0));
         setFgServiceShown(safeBool(parser, ATT_FG_SERVICE_SHOWN, false));
         setBlockable(safeBool(parser, ATT_BLOCKABLE_SYSTEM, false));
-        setAllowBubbles(safeBool(parser, ATT_ALLOW_BUBBLE, DEFAULT_ALLOW_BUBBLE));
+        setAllowBubbles(safeInt(parser, ATT_ALLOW_BUBBLE, DEFAULT_ALLOW_BUBBLE));
         setOriginalImportance(safeInt(parser, ATT_ORIG_IMP, DEFAULT_IMPORTANCE));
         setConversationId(parser.getAttributeValue(null, ATT_PARENT_CHANNEL),
                 parser.getAttributeValue(null, ATT_CONVERSATION_ID));
@@ -996,8 +1015,8 @@
         if (isBlockable()) {
             out.attribute(null, ATT_BLOCKABLE_SYSTEM, Boolean.toString(isBlockable()));
         }
-        if (canBubble() != DEFAULT_ALLOW_BUBBLE) {
-            out.attribute(null, ATT_ALLOW_BUBBLE, Boolean.toString(canBubble()));
+        if (getAllowBubbles() != DEFAULT_ALLOW_BUBBLE) {
+            out.attribute(null, ATT_ALLOW_BUBBLE, Integer.toString(getAllowBubbles()));
         }
         if (getOriginalImportance() != DEFAULT_IMPORTANCE) {
             out.attribute(null, ATT_ORIG_IMP, Integer.toString(getOriginalImportance()));
@@ -1059,7 +1078,7 @@
         record.put(ATT_DELETED, Boolean.toString(isDeleted()));
         record.put(ATT_GROUP, getGroup());
         record.put(ATT_BLOCKABLE_SYSTEM, isBlockable());
-        record.put(ATT_ALLOW_BUBBLE, canBubble());
+        record.put(ATT_ALLOW_BUBBLE, getAllowBubbles());
         // TODO: original importance
         return record;
     }
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index 0173731..c7a2a1e 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -20,6 +20,7 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.TestApi;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.ComponentName;
 import android.content.Intent;
@@ -196,6 +197,20 @@
         return resizeMode != RESIZE_MODE_UNRESIZEABLE;
     }
 
+    /** @hide */
+    @NonNull
+    @TestApi
+    public WindowContainerToken getToken() {
+        return token;
+    }
+
+    /** @hide */
+    @NonNull
+    @TestApi
+    public Configuration getConfiguration() {
+        return configuration;
+    }
+
     /**
      * Reads the TaskInfo from a parcel.
      */
diff --git a/core/java/android/content/ClipData.java b/core/java/android/content/ClipData.java
index 9c806fa..1923bf3 100644
--- a/core/java/android/content/ClipData.java
+++ b/core/java/android/content/ClipData.java
@@ -1206,7 +1206,7 @@
             }
         } else {
             dest.writeInt(PARCEL_TYPE_STRING);
-            dest.writeString(text);
+            dest.writeString8(text);
         }
     }
 
@@ -1215,7 +1215,7 @@
      */
     private static String readHtmlTextFromParcel(Parcel in) {
         if (in.readInt() == PARCEL_TYPE_STRING) {
-            return in.readString();
+            return in.readString8();
         }
         ParcelFileDescriptor pfd =
                 in.readParcelable(ParcelFileDescriptor.class.getClassLoader());
diff --git a/core/java/android/content/ContentProviderOperation.java b/core/java/android/content/ContentProviderOperation.java
index 494d2ae..1fb426e 100644
--- a/core/java/android/content/ContentProviderOperation.java
+++ b/core/java/android/content/ContentProviderOperation.java
@@ -91,8 +91,8 @@
     private ContentProviderOperation(Parcel source) {
         mType = source.readInt();
         mUri = Uri.CREATOR.createFromParcel(source);
-        mMethod = source.readInt() != 0 ? source.readString() : null;
-        mArg = source.readInt() != 0 ? source.readString() : null;
+        mMethod = source.readInt() != 0 ? source.readString8() : null;
+        mArg = source.readInt() != 0 ? source.readString8() : null;
         final int valuesSize = source.readInt();
         if (valuesSize != -1) {
             mValues = new ArrayMap<>(valuesSize);
@@ -107,7 +107,7 @@
         } else {
             mExtras = null;
         }
-        mSelection = source.readInt() != 0 ? source.readString() : null;
+        mSelection = source.readInt() != 0 ? source.readString8() : null;
         mSelectionArgs = source.readSparseArray(null);
         mExpectedCount = source.readInt() != 0 ? source.readInt() : null;
         mYieldAllowed = source.readInt() != 0;
@@ -135,13 +135,13 @@
         Uri.writeToParcel(dest, mUri);
         if (mMethod != null) {
             dest.writeInt(1);
-            dest.writeString(mMethod);
+            dest.writeString8(mMethod);
         } else {
             dest.writeInt(0);
         }
         if (mArg != null) {
             dest.writeInt(1);
-            dest.writeString(mArg);
+            dest.writeString8(mArg);
         } else {
             dest.writeInt(0);
         }
@@ -159,7 +159,7 @@
         }
         if (mSelection != null) {
             dest.writeInt(1);
-            dest.writeString(mSelection);
+            dest.writeString8(mSelection);
         } else {
             dest.writeInt(0);
         }
@@ -591,7 +591,7 @@
         public BackReference(Parcel src) {
             this.fromIndex = src.readInt();
             if (src.readInt() != 0) {
-                this.fromKey = src.readString();
+                this.fromKey = src.readString8();
             } else {
                 this.fromKey = null;
             }
@@ -620,7 +620,7 @@
             dest.writeInt(fromIndex);
             if (fromKey != null) {
                 dest.writeInt(1);
-                dest.writeString(fromKey);
+                dest.writeString8(fromKey);
             } else {
                 dest.writeInt(0);
             }
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index b1d6c83..def150a 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -888,8 +888,8 @@
 
                 public ShortcutIconResource createFromParcel(Parcel source) {
                     ShortcutIconResource icon = new ShortcutIconResource();
-                    icon.packageName = source.readString();
-                    icon.resourceName = source.readString();
+                    icon.packageName = source.readString8();
+                    icon.resourceName = source.readString8();
                     return icon;
                 }
 
@@ -906,8 +906,8 @@
         }
 
         public void writeToParcel(Parcel dest, int flags) {
-            dest.writeString(packageName);
-            dest.writeString(resourceName);
+            dest.writeString8(packageName);
+            dest.writeString8(resourceName);
         }
 
         @Override
@@ -10807,12 +10807,12 @@
     }
 
     public void writeToParcel(Parcel out, int flags) {
-        out.writeString(mAction);
+        out.writeString8(mAction);
         Uri.writeToParcel(out, mData);
-        out.writeString(mType);
-        out.writeString(mIdentifier);
+        out.writeString8(mType);
+        out.writeString8(mIdentifier);
         out.writeInt(mFlags);
-        out.writeString(mPackage);
+        out.writeString8(mPackage);
         ComponentName.writeToParcel(mComponent, out);
 
         if (mSourceBounds != null) {
@@ -10826,7 +10826,7 @@
             final int N = mCategories.size();
             out.writeInt(N);
             for (int i=0; i<N; i++) {
-                out.writeString(mCategories.valueAt(i));
+                out.writeString8(mCategories.valueAt(i));
             }
         } else {
             out.writeInt(0);
@@ -10865,12 +10865,12 @@
     }
 
     public void readFromParcel(Parcel in) {
-        setAction(in.readString());
+        setAction(in.readString8());
         mData = Uri.CREATOR.createFromParcel(in);
-        mType = in.readString();
-        mIdentifier = in.readString();
+        mType = in.readString8();
+        mIdentifier = in.readString8();
         mFlags = in.readInt();
-        mPackage = in.readString();
+        mPackage = in.readString8();
         mComponent = ComponentName.readFromParcel(in);
 
         if (in.readInt() != 0) {
@@ -10882,7 +10882,7 @@
             mCategories = new ArraySet<String>();
             int i;
             for (i=0; i<N; i++) {
-                mCategories.add(in.readString().intern());
+                mCategories.add(in.readString8().intern());
             }
         } else {
             mCategories = null;
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index f25ce76..b1f8869 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -1206,17 +1206,17 @@
         dest.writeInt(theme);
         dest.writeInt(launchMode);
         dest.writeInt(documentLaunchMode);
-        dest.writeString(permission);
-        dest.writeString(taskAffinity);
-        dest.writeString(targetActivity);
-        dest.writeString(launchToken);
+        dest.writeString8(permission);
+        dest.writeString8(taskAffinity);
+        dest.writeString8(targetActivity);
+        dest.writeString8(launchToken);
         dest.writeInt(flags);
         dest.writeInt(privateFlags);
         dest.writeInt(screenOrientation);
         dest.writeInt(configChanges);
         dest.writeInt(softInputMode);
         dest.writeInt(uiOptions);
-        dest.writeString(parentActivityName);
+        dest.writeString8(parentActivityName);
         dest.writeInt(persistableMode);
         dest.writeInt(maxRecents);
         dest.writeInt(lockTaskLaunchMode);
@@ -1227,7 +1227,7 @@
             dest.writeInt(0);
         }
         dest.writeInt(resizeMode);
-        dest.writeString(requestedVrComponent);
+        dest.writeString8(requestedVrComponent);
         dest.writeInt(rotationAnimation);
         dest.writeInt(colorMode);
         dest.writeFloat(maxAspectRatio);
@@ -1327,17 +1327,17 @@
         theme = source.readInt();
         launchMode = source.readInt();
         documentLaunchMode = source.readInt();
-        permission = source.readString();
-        taskAffinity = source.readString();
-        targetActivity = source.readString();
-        launchToken = source.readString();
+        permission = source.readString8();
+        taskAffinity = source.readString8();
+        targetActivity = source.readString8();
+        launchToken = source.readString8();
         flags = source.readInt();
         privateFlags = source.readInt();
         screenOrientation = source.readInt();
         configChanges = source.readInt();
         softInputMode = source.readInt();
         uiOptions = source.readInt();
-        parentActivityName = source.readString();
+        parentActivityName = source.readString8();
         persistableMode = source.readInt();
         maxRecents = source.readInt();
         lockTaskLaunchMode = source.readInt();
@@ -1345,7 +1345,7 @@
             windowLayout = new WindowLayout(source);
         }
         resizeMode = source.readInt();
-        requestedVrComponent = source.readString();
+        requestedVrComponent = source.readString8();
         rotationAnimation = source.readInt();
         colorMode = source.readInt();
         maxAspectRatio = source.readFloat();
@@ -1386,7 +1386,7 @@
             gravity = source.readInt();
             minWidth = source.readInt();
             minHeight = source.readInt();
-            windowLayoutAffinity = source.readString();
+            windowLayoutAffinity = source.readString8();
         }
 
         /**
@@ -1476,7 +1476,7 @@
             dest.writeInt(gravity);
             dest.writeInt(minWidth);
             dest.writeInt(minHeight);
-            dest.writeString(windowLayoutAffinity);
+            dest.writeString8(windowLayoutAffinity);
         }
     }
 }
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index b6706011..b37521b 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -1704,10 +1704,10 @@
             return;
         }
         super.writeToParcel(dest, parcelableFlags);
-        dest.writeString(taskAffinity);
-        dest.writeString(permission);
-        dest.writeString(processName);
-        dest.writeString(className);
+        dest.writeString8(taskAffinity);
+        dest.writeString8(permission);
+        dest.writeString8(processName);
+        dest.writeString8(className);
         dest.writeInt(theme);
         dest.writeInt(flags);
         dest.writeInt(privateFlags);
@@ -1721,28 +1721,28 @@
         } else {
             dest.writeInt(0);
         }
-        dest.writeString(scanSourceDir);
-        dest.writeString(scanPublicSourceDir);
-        dest.writeString(sourceDir);
-        dest.writeString(publicSourceDir);
+        dest.writeString8(scanSourceDir);
+        dest.writeString8(scanPublicSourceDir);
+        dest.writeString8(sourceDir);
+        dest.writeString8(publicSourceDir);
         dest.writeStringArray(splitNames);
         dest.writeStringArray(splitSourceDirs);
         dest.writeStringArray(splitPublicSourceDirs);
         dest.writeSparseArray((SparseArray) splitDependencies);
-        dest.writeString(nativeLibraryDir);
-        dest.writeString(secondaryNativeLibraryDir);
-        dest.writeString(nativeLibraryRootDir);
+        dest.writeString8(nativeLibraryDir);
+        dest.writeString8(secondaryNativeLibraryDir);
+        dest.writeString8(nativeLibraryRootDir);
         dest.writeInt(nativeLibraryRootRequiresIsa ? 1 : 0);
-        dest.writeString(primaryCpuAbi);
-        dest.writeString(secondaryCpuAbi);
+        dest.writeString8(primaryCpuAbi);
+        dest.writeString8(secondaryCpuAbi);
         dest.writeStringArray(resourceDirs);
-        dest.writeString(seInfo);
-        dest.writeString(seInfoUser);
+        dest.writeString8(seInfo);
+        dest.writeString8(seInfoUser);
         dest.writeStringArray(sharedLibraryFiles);
         dest.writeTypedList(sharedLibraryInfos);
-        dest.writeString(dataDir);
-        dest.writeString(deviceProtectedDataDir);
-        dest.writeString(credentialProtectedDataDir);
+        dest.writeString8(dataDir);
+        dest.writeString8(deviceProtectedDataDir);
+        dest.writeString8(credentialProtectedDataDir);
         dest.writeInt(uid);
         dest.writeInt(minSdkVersion);
         dest.writeInt(targetSdkVersion);
@@ -1750,8 +1750,8 @@
         dest.writeInt(enabled ? 1 : 0);
         dest.writeInt(enabledSetting);
         dest.writeInt(installLocation);
-        dest.writeString(manageSpaceActivityName);
-        dest.writeString(backupAgentName);
+        dest.writeString8(manageSpaceActivityName);
+        dest.writeString8(backupAgentName);
         dest.writeInt(descriptionRes);
         dest.writeInt(uiOptions);
         dest.writeInt(fullBackupContent);
@@ -1759,16 +1759,16 @@
         dest.writeInt(networkSecurityConfigRes);
         dest.writeInt(category);
         dest.writeInt(targetSandboxVersion);
-        dest.writeString(classLoaderName);
+        dest.writeString8(classLoaderName);
         dest.writeStringArray(splitClassLoaderNames);
         dest.writeInt(compileSdkVersion);
-        dest.writeString(compileSdkVersionCodename);
-        dest.writeString(appComponentFactory);
+        dest.writeString8(compileSdkVersionCodename);
+        dest.writeString8(appComponentFactory);
         dest.writeInt(iconRes);
         dest.writeInt(roundIconRes);
         dest.writeInt(mHiddenApiPolicy);
         dest.writeInt(hiddenUntilInstalled ? 1 : 0);
-        dest.writeString(zygotePreloadName);
+        dest.writeString8(zygotePreloadName);
         dest.writeInt(gwpAsanMode);
     }
 
@@ -1788,10 +1788,10 @@
     @SuppressWarnings("unchecked")
     private ApplicationInfo(Parcel source) {
         super(source);
-        taskAffinity = source.readString();
-        permission = source.readString();
-        processName = source.readString();
-        className = source.readString();
+        taskAffinity = source.readString8();
+        permission = source.readString8();
+        processName = source.readString8();
+        className = source.readString8();
         theme = source.readInt();
         flags = source.readInt();
         privateFlags = source.readInt();
@@ -1802,28 +1802,28 @@
             storageUuid = new UUID(source.readLong(), source.readLong());
             volumeUuid = StorageManager.convert(storageUuid);
         }
-        scanSourceDir = source.readString();
-        scanPublicSourceDir = source.readString();
-        sourceDir = source.readString();
-        publicSourceDir = source.readString();
+        scanSourceDir = source.readString8();
+        scanPublicSourceDir = source.readString8();
+        sourceDir = source.readString8();
+        publicSourceDir = source.readString8();
         splitNames = source.readStringArray();
         splitSourceDirs = source.readStringArray();
         splitPublicSourceDirs = source.readStringArray();
         splitDependencies = source.readSparseArray(null);
-        nativeLibraryDir = source.readString();
-        secondaryNativeLibraryDir = source.readString();
-        nativeLibraryRootDir = source.readString();
+        nativeLibraryDir = source.readString8();
+        secondaryNativeLibraryDir = source.readString8();
+        nativeLibraryRootDir = source.readString8();
         nativeLibraryRootRequiresIsa = source.readInt() != 0;
-        primaryCpuAbi = source.readString();
-        secondaryCpuAbi = source.readString();
+        primaryCpuAbi = source.readString8();
+        secondaryCpuAbi = source.readString8();
         resourceDirs = source.readStringArray();
-        seInfo = source.readString();
-        seInfoUser = source.readString();
+        seInfo = source.readString8();
+        seInfoUser = source.readString8();
         sharedLibraryFiles = source.readStringArray();
         sharedLibraryInfos = source.createTypedArrayList(SharedLibraryInfo.CREATOR);
-        dataDir = source.readString();
-        deviceProtectedDataDir = source.readString();
-        credentialProtectedDataDir = source.readString();
+        dataDir = source.readString8();
+        deviceProtectedDataDir = source.readString8();
+        credentialProtectedDataDir = source.readString8();
         uid = source.readInt();
         minSdkVersion = source.readInt();
         targetSdkVersion = source.readInt();
@@ -1831,8 +1831,8 @@
         enabled = source.readInt() != 0;
         enabledSetting = source.readInt();
         installLocation = source.readInt();
-        manageSpaceActivityName = source.readString();
-        backupAgentName = source.readString();
+        manageSpaceActivityName = source.readString8();
+        backupAgentName = source.readString8();
         descriptionRes = source.readInt();
         uiOptions = source.readInt();
         fullBackupContent = source.readInt();
@@ -1840,16 +1840,16 @@
         networkSecurityConfigRes = source.readInt();
         category = source.readInt();
         targetSandboxVersion = source.readInt();
-        classLoaderName = source.readString();
+        classLoaderName = source.readString8();
         splitClassLoaderNames = source.readStringArray();
         compileSdkVersion = source.readInt();
-        compileSdkVersionCodename = source.readString();
-        appComponentFactory = source.readString();
+        compileSdkVersionCodename = source.readString8();
+        appComponentFactory = source.readString8();
         iconRes = source.readInt();
         roundIconRes = source.readInt();
         mHiddenApiPolicy = source.readInt();
         hiddenUntilInstalled = source.readInt() != 0;
-        zygotePreloadName = source.readString();
+        zygotePreloadName = source.readString8();
         gwpAsanMode = source.readInt();
     }
 
diff --git a/core/java/android/content/pm/ComponentInfo.java b/core/java/android/content/pm/ComponentInfo.java
index 362098c..628bcd7 100644
--- a/core/java/android/content/pm/ComponentInfo.java
+++ b/core/java/android/content/pm/ComponentInfo.java
@@ -197,8 +197,8 @@
     public void writeToParcel(Parcel dest, int parcelableFlags) {
         super.writeToParcel(dest, parcelableFlags);
         applicationInfo.writeToParcel(dest, parcelableFlags);
-        dest.writeString(processName);
-        dest.writeString(splitName);
+        dest.writeString8(processName);
+        dest.writeString8(splitName);
         dest.writeInt(descriptionRes);
         dest.writeInt(enabled ? 1 : 0);
         dest.writeInt(exported ? 1 : 0);
@@ -208,8 +208,8 @@
     protected ComponentInfo(Parcel source) {
         super(source);
         applicationInfo = ApplicationInfo.CREATOR.createFromParcel(source);
-        processName = source.readString();
-        splitName = source.readString();
+        processName = source.readString8();
+        splitName = source.readString8();
         descriptionRes = source.readInt();
         enabled = (source.readInt() != 0);
         exported = (source.readInt() != 0);
diff --git a/core/java/android/content/pm/FeatureInfo.java b/core/java/android/content/pm/FeatureInfo.java
index 9f3ab77..89269e0 100644
--- a/core/java/android/content/pm/FeatureInfo.java
+++ b/core/java/android/content/pm/FeatureInfo.java
@@ -108,7 +108,7 @@
 
     @Override
     public void writeToParcel(Parcel dest, int parcelableFlags) {
-        dest.writeString(name);
+        dest.writeString8(name);
         dest.writeInt(version);
         dest.writeInt(reqGlEsVersion);
         dest.writeInt(flags);
@@ -138,7 +138,7 @@
     };
 
     private FeatureInfo(Parcel source) {
-        name = source.readString();
+        name = source.readString8();
         version = source.readInt();
         reqGlEsVersion = source.readInt();
         flags = source.readInt();
diff --git a/core/java/android/content/pm/IDataLoaderStatusListener.aidl b/core/java/android/content/pm/IDataLoaderStatusListener.aidl
index 9819b5d..ffe8b18 100644
--- a/core/java/android/content/pm/IDataLoaderStatusListener.aidl
+++ b/core/java/android/content/pm/IDataLoaderStatusListener.aidl
@@ -21,16 +21,29 @@
  * @hide
  */
 oneway interface IDataLoaderStatusListener {
-    /** Data loader status */
+    /** When this status is returned from DataLoader, it means that the DataLoader
+    *   process is running, bound to and has handled onCreate(). */
     const int DATA_LOADER_CREATED = 0;
+    /** Listener will receive this status when the DataLoader process died,
+    *   binder disconnected or class destroyed. */
     const int DATA_LOADER_DESTROYED = 1;
 
+    /** DataLoader can receive missing pages and read pages notifications,
+     *  and ready to provide data. */
     const int DATA_LOADER_STARTED = 2;
+    /** DataLoader no longer ready to provide data and is not receiving
+    *   any notifications from IncFS. */
     const int DATA_LOADER_STOPPED = 3;
 
+    /** DataLoader streamed everything necessary to continue installation. */
     const int DATA_LOADER_IMAGE_READY = 4;
+    /** Installation can't continue as DataLoader failed to stream necessary data. */
     const int DATA_LOADER_IMAGE_NOT_READY = 5;
 
+    /** DataLoader reports that this instance is invalid and can never be restored.
+    *   Warning: this is a terminal status that data loader should use carefully and
+    *            the system should almost never use - e.g. only if all recovery attempts
+    *            fail and all retry limits are exceeded. */
     const int DATA_LOADER_UNRECOVERABLE = 6;
 
     /** Data loader status callback */
diff --git a/core/java/android/content/pm/InstrumentationInfo.java b/core/java/android/content/pm/InstrumentationInfo.java
index 574a1ee..745a6c1 100644
--- a/core/java/android/content/pm/InstrumentationInfo.java
+++ b/core/java/android/content/pm/InstrumentationInfo.java
@@ -157,21 +157,21 @@
 
     public void writeToParcel(Parcel dest, int parcelableFlags) {
         super.writeToParcel(dest, parcelableFlags);
-        dest.writeString(targetPackage);
-        dest.writeString(targetProcesses);
-        dest.writeString(sourceDir);
-        dest.writeString(publicSourceDir);
+        dest.writeString8(targetPackage);
+        dest.writeString8(targetProcesses);
+        dest.writeString8(sourceDir);
+        dest.writeString8(publicSourceDir);
         dest.writeStringArray(splitNames);
         dest.writeStringArray(splitSourceDirs);
         dest.writeStringArray(splitPublicSourceDirs);
         dest.writeSparseArray((SparseArray) splitDependencies);
-        dest.writeString(dataDir);
-        dest.writeString(deviceProtectedDataDir);
-        dest.writeString(credentialProtectedDataDir);
-        dest.writeString(primaryCpuAbi);
-        dest.writeString(secondaryCpuAbi);
-        dest.writeString(nativeLibraryDir);
-        dest.writeString(secondaryNativeLibraryDir);
+        dest.writeString8(dataDir);
+        dest.writeString8(deviceProtectedDataDir);
+        dest.writeString8(credentialProtectedDataDir);
+        dest.writeString8(primaryCpuAbi);
+        dest.writeString8(secondaryCpuAbi);
+        dest.writeString8(nativeLibraryDir);
+        dest.writeString8(secondaryNativeLibraryDir);
         dest.writeInt((handleProfiling == false) ? 0 : 1);
         dest.writeInt((functionalTest == false) ? 0 : 1);
     }
@@ -189,21 +189,21 @@
     @SuppressWarnings("unchecked")
     private InstrumentationInfo(Parcel source) {
         super(source);
-        targetPackage = source.readString();
-        targetProcesses = source.readString();
-        sourceDir = source.readString();
-        publicSourceDir = source.readString();
+        targetPackage = source.readString8();
+        targetProcesses = source.readString8();
+        sourceDir = source.readString8();
+        publicSourceDir = source.readString8();
         splitNames = source.readStringArray();
         splitSourceDirs = source.readStringArray();
         splitPublicSourceDirs = source.readStringArray();
         splitDependencies = source.readSparseArray(null);
-        dataDir = source.readString();
-        deviceProtectedDataDir = source.readString();
-        credentialProtectedDataDir = source.readString();
-        primaryCpuAbi = source.readString();
-        secondaryCpuAbi = source.readString();
-        nativeLibraryDir = source.readString();
-        secondaryNativeLibraryDir = source.readString();
+        dataDir = source.readString8();
+        deviceProtectedDataDir = source.readString8();
+        credentialProtectedDataDir = source.readString8();
+        primaryCpuAbi = source.readString8();
+        secondaryCpuAbi = source.readString8();
+        nativeLibraryDir = source.readString8();
+        secondaryNativeLibraryDir = source.readString8();
         handleProfiling = source.readInt() != 0;
         functionalTest = source.readInt() != 0;
     }
diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java
index 85c698f..bb56ef7 100644
--- a/core/java/android/content/pm/PackageInfo.java
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -441,14 +441,14 @@
     public void writeToParcel(Parcel dest, int parcelableFlags) {
         // Allow ApplicationInfo to be squashed.
         final boolean prevAllowSquashing = dest.allowSquashing();
-        dest.writeString(packageName);
+        dest.writeString8(packageName);
         dest.writeStringArray(splitNames);
         dest.writeInt(versionCode);
         dest.writeInt(versionCodeMajor);
-        dest.writeString(versionName);
+        dest.writeString8(versionName);
         dest.writeInt(baseRevisionCode);
         dest.writeIntArray(splitRevisionCodes);
-        dest.writeString(sharedUserId);
+        dest.writeString8(sharedUserId);
         dest.writeInt(sharedUserLabel);
         if (applicationInfo != null) {
             dest.writeInt(1);
@@ -475,14 +475,14 @@
         dest.writeInt(isStub ? 1 : 0);
         dest.writeInt(coreApp ? 1 : 0);
         dest.writeInt(requiredForAllUsers ? 1 : 0);
-        dest.writeString(restrictedAccountType);
-        dest.writeString(requiredAccountType);
-        dest.writeString(overlayTarget);
-        dest.writeString(overlayCategory);
+        dest.writeString8(restrictedAccountType);
+        dest.writeString8(requiredAccountType);
+        dest.writeString8(overlayTarget);
+        dest.writeString8(overlayCategory);
         dest.writeInt(overlayPriority);
         dest.writeBoolean(mOverlayIsStatic);
         dest.writeInt(compileSdkVersion);
-        dest.writeString(compileSdkVersionCodename);
+        dest.writeString8(compileSdkVersionCodename);
         if (signingInfo != null) {
             dest.writeInt(1);
             signingInfo.writeToParcel(dest, parcelableFlags);
@@ -508,14 +508,14 @@
 
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     private PackageInfo(Parcel source) {
-        packageName = source.readString();
+        packageName = source.readString8();
         splitNames = source.createStringArray();
         versionCode = source.readInt();
         versionCodeMajor = source.readInt();
-        versionName = source.readString();
+        versionName = source.readString8();
         baseRevisionCode = source.readInt();
         splitRevisionCodes = source.createIntArray();
-        sharedUserId = source.readString();
+        sharedUserId = source.readString8();
         sharedUserLabel = source.readInt();
         int hasApp = source.readInt();
         if (hasApp != 0) {
@@ -540,14 +540,14 @@
         isStub = source.readInt() != 0;
         coreApp = source.readInt() != 0;
         requiredForAllUsers = source.readInt() != 0;
-        restrictedAccountType = source.readString();
-        requiredAccountType = source.readString();
-        overlayTarget = source.readString();
-        overlayCategory = source.readString();
+        restrictedAccountType = source.readString8();
+        requiredAccountType = source.readString8();
+        overlayTarget = source.readString8();
+        overlayCategory = source.readString8();
         overlayPriority = source.readInt();
         mOverlayIsStatic = source.readBoolean();
         compileSdkVersion = source.readInt();
-        compileSdkVersionCodename = source.readString();
+        compileSdkVersionCodename = source.readString8();
         int hasSigningInfo = source.readInt();
         if (hasSigningInfo != 0) {
             signingInfo = SigningInfo.CREATOR.createFromParcel(source);
diff --git a/core/java/android/content/pm/PackageItemInfo.java b/core/java/android/content/pm/PackageItemInfo.java
index 7fd5531..d41ace5 100644
--- a/core/java/android/content/pm/PackageItemInfo.java
+++ b/core/java/android/content/pm/PackageItemInfo.java
@@ -422,8 +422,8 @@
     }
 
     public void writeToParcel(Parcel dest, int parcelableFlags) {
-        dest.writeString(name);
-        dest.writeString(packageName);
+        dest.writeString8(name);
+        dest.writeString8(packageName);
         dest.writeInt(labelRes);
         TextUtils.writeToParcel(nonLocalizedLabel, dest, parcelableFlags);
         dest.writeInt(icon);
@@ -452,8 +452,8 @@
     }
 
     protected PackageItemInfo(Parcel source) {
-        name = source.readString();
-        packageName = source.readString();
+        name = source.readString8();
+        packageName = source.readString8();
         labelRes = source.readInt();
         nonLocalizedLabel
                 = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 85bafd9..8a57f82 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -6873,9 +6873,9 @@
 
         /** @hide */
         public boolean canHaveOatDir() {
-            // The following app types CANNOT have oat directory
-            // - non-updated system apps
-            return !isSystem() || isUpdatedSystemApp();
+            // Nobody should be calling this method ever, but we can't rely on this.
+            // Thus no logic here and a reasonable return value.
+            return true;
         }
 
         public boolean isMatch(int flags) {
diff --git a/core/java/android/content/pm/PackageParserCacheHelper.java b/core/java/android/content/pm/PackageParserCacheHelper.java
index 44def33..8212224 100644
--- a/core/java/android/content/pm/PackageParserCacheHelper.java
+++ b/core/java/android/content/pm/PackageParserCacheHelper.java
@@ -78,10 +78,19 @@
         /**
          * Read an string index from a parcel, and returns the corresponding string from the pool.
          */
-        @Override
         public String readString(Parcel p) {
             return mStrings.get(p.readInt());
         }
+
+        @Override
+        public String readString8(Parcel p) {
+            return readString(p);
+        }
+
+        @Override
+        public String readString16(Parcel p) {
+            return readString(p);
+        }
     }
 
     /**
@@ -110,7 +119,6 @@
          * Instead of writing a string directly to a parcel, this method adds it to the pool,
          * and write the index in the pool to the parcel.
          */
-        @Override
         public void writeString(Parcel p, String s) {
             final Integer cur = mIndexes.get(s);
             if (cur != null) {
@@ -133,6 +141,16 @@
             }
         }
 
+        @Override
+        public void writeString8(Parcel p, String s) {
+            writeString(p, s);
+        }
+
+        @Override
+        public void writeString16(Parcel p, String s) {
+            writeString(p, s);
+        }
+
         /**
          * Closes a parcel by appending the string pool at the end and updating the pool offset,
          * which it assumes is at the first byte.  It also uninstalls itself as a read-write helper.
diff --git a/core/java/android/content/pm/ProviderInfo.java b/core/java/android/content/pm/ProviderInfo.java
index 07d42dc..3984ade 100644
--- a/core/java/android/content/pm/ProviderInfo.java
+++ b/core/java/android/content/pm/ProviderInfo.java
@@ -145,9 +145,9 @@
 
     @Override public void writeToParcel(Parcel out, int parcelableFlags) {
         super.writeToParcel(out, parcelableFlags);
-        out.writeString(authority);
-        out.writeString(readPermission);
-        out.writeString(writePermission);
+        out.writeString8(authority);
+        out.writeString8(readPermission);
+        out.writeString8(writePermission);
         out.writeInt(grantUriPermissions ? 1 : 0);
         out.writeInt(forceUriPermissions ? 1 : 0);
         out.writeTypedArray(uriPermissionPatterns, parcelableFlags);
@@ -175,9 +175,9 @@
 
     private ProviderInfo(Parcel in) {
         super(in);
-        authority = in.readString();
-        readPermission = in.readString();
-        writePermission = in.readString();
+        authority = in.readString8();
+        readPermission = in.readString8();
+        writePermission = in.readString8();
         grantUriPermissions = in.readInt() != 0;
         forceUriPermissions = in.readInt() != 0;
         uriPermissionPatterns = in.createTypedArray(PatternMatcher.CREATOR);
diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java
index 5f90b6c4..d3f9e24 100644
--- a/core/java/android/content/pm/ServiceInfo.java
+++ b/core/java/android/content/pm/ServiceInfo.java
@@ -244,7 +244,7 @@
 
     public void writeToParcel(Parcel dest, int parcelableFlags) {
         super.writeToParcel(dest, parcelableFlags);
-        dest.writeString(permission);
+        dest.writeString8(permission);
         dest.writeInt(flags);
         dest.writeInt(mForegroundServiceType);
     }
@@ -261,7 +261,7 @@
 
     private ServiceInfo(Parcel source) {
         super(source);
-        permission = source.readString();
+        permission = source.readString8();
         flags = source.readInt();
         mForegroundServiceType = source.readInt();
     }
diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java
index f7c96a3..2f67f6d 100644
--- a/core/java/android/database/sqlite/SQLiteConnection.java
+++ b/core/java/android/database/sqlite/SQLiteConnection.java
@@ -228,19 +228,26 @@
         } catch (SQLiteCantOpenDatabaseException e) {
             String message = String.format("Cannot open database '%s'", file);
 
-            final Path path = FileSystems.getDefault().getPath(file);
-            final Path dir = path.getParent();
+            try {
+                // Try to diagnose for common reasons. If something fails in here, that's fine;
+                // just swallow the exception.
 
-            if (!Files.isDirectory(dir)) {
-                message += ": Directory " + dir + " doesn't exist";
-            } else if (!Files.exists(path)) {
-                message += ": File " + path + " doesn't exist";
-            } else if (!Files.isReadable(path)) {
-                message += ": File " + path + " is not readable";
-            } else if (Files.isDirectory(path)) {
-                message += ": Path " + path + " is a directory";
-            } else {
-                message += ": Unknown reason";
+                final Path path = FileSystems.getDefault().getPath(file);
+                final Path dir = path.getParent();
+
+                if (!Files.isDirectory(dir)) {
+                    message += ": Directory " + dir + " doesn't exist";
+                } else if (!Files.exists(path)) {
+                    message += ": File " + path + " doesn't exist";
+                } else if (!Files.isReadable(path)) {
+                    message += ": File " + path + " is not readable";
+                } else if (Files.isDirectory(path)) {
+                    message += ": Path " + path + " is a directory";
+                } else {
+                    message += ": Unknown reason";
+                }
+            } catch (Throwable th) {
+                message += ": Unknown reason; cannot examine filesystem: " + th.getMessage();
             }
             throw new SQLiteCantOpenDatabaseException(message, e);
         } finally {
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index 462110f..ad9bf07 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -65,21 +65,23 @@
     public abstract boolean isProximitySensorAvailable();
 
     /**
-     * Take a screenshot of the specified display and return a buffer.
+     * Screenshot for internal system-only use such as rotation, etc.  This method includes
+     * secure layers and the result should never be exposed to non-system applications.
+     * This method does not apply any rotation and provides the output in natural orientation.
      *
      * @param displayId The display id to take the screenshot of.
      * @return The buffer or null if we have failed.
      */
-    public abstract SurfaceControl.ScreenshotGraphicBuffer screenshot(int displayId);
+    public abstract SurfaceControl.ScreenshotGraphicBuffer systemScreenshot(int displayId);
 
     /**
-     * Take a screenshot without secure layer of the specified display and return a buffer.
+     * General screenshot functionality that excludes secure layers and applies appropriate
+     * rotation that the device is currently in.
      *
      * @param displayId The display id to take the screenshot of.
      * @return The buffer or null if we have failed.
      */
-    public abstract SurfaceControl.ScreenshotGraphicBuffer screenshotWithoutSecureLayer(
-            int displayId);
+    public abstract SurfaceControl.ScreenshotGraphicBuffer userScreenshot(int displayId);
 
     /**
      * Returns information about the specified logical display.
diff --git a/core/java/android/net/Uri.java b/core/java/android/net/Uri.java
index 525bbfd..1cb4fe8 100644
--- a/core/java/android/net/Uri.java
+++ b/core/java/android/net/Uri.java
@@ -500,7 +500,7 @@
         }
 
         static Uri readFrom(Parcel parcel) {
-            return new StringUri(parcel.readString());
+            return new StringUri(parcel.readString8());
         }
 
         public int describeContents() {
@@ -509,7 +509,7 @@
 
         public void writeToParcel(Parcel parcel, int flags) {
             parcel.writeInt(TYPE_ID);
-            parcel.writeString(uriString);
+            parcel.writeString8(uriString);
         }
 
         /** Cached scheme separator index. */
@@ -875,7 +875,7 @@
 
         static Uri readFrom(Parcel parcel) {
             return new OpaqueUri(
-                parcel.readString(),
+                parcel.readString8(),
                 Part.readFrom(parcel),
                 Part.readFrom(parcel)
             );
@@ -887,7 +887,7 @@
 
         public void writeToParcel(Parcel parcel, int flags) {
             parcel.writeInt(TYPE_ID);
-            parcel.writeString(scheme);
+            parcel.writeString8(scheme);
             ssp.writeTo(parcel);
             fragment.writeTo(parcel);
         }
@@ -1195,7 +1195,7 @@
 
         static Uri readFrom(Parcel parcel) {
             return new HierarchicalUri(
-                parcel.readString(),
+                parcel.readString8(),
                 Part.readFrom(parcel),
                 PathPart.readFrom(parcel),
                 Part.readFrom(parcel),
@@ -1209,7 +1209,7 @@
 
         public void writeToParcel(Parcel parcel, int flags) {
             parcel.writeInt(TYPE_ID);
-            parcel.writeString(scheme);
+            parcel.writeString8(scheme);
             authority.writeTo(parcel);
             path.writeTo(parcel);
             query.writeTo(parcel);
@@ -2028,7 +2028,7 @@
                     + mCanonicalRepresentation + ")");
             }
             parcel.writeInt(mCanonicalRepresentation);
-            parcel.writeString(canonicalValue);
+            parcel.writeString8(canonicalValue);
         }
     }
 
@@ -2060,7 +2060,7 @@
 
         static Part readFrom(Parcel parcel) {
             int representation = parcel.readInt();
-            String value = parcel.readString();
+            String value = parcel.readString8();
             switch (representation) {
                 case REPRESENTATION_ENCODED:
                     return fromEncoded(value);
@@ -2251,9 +2251,9 @@
             int representation = parcel.readInt();
             switch (representation) {
                 case REPRESENTATION_ENCODED:
-                    return fromEncoded(parcel.readString());
+                    return fromEncoded(parcel.readString8());
                 case REPRESENTATION_DECODED:
-                    return fromDecoded(parcel.readString());
+                    return fromDecoded(parcel.readString8());
                 default:
                     throw new IllegalArgumentException("Unknown representation: " + representation);
             }
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index f0b7b5f..93f6607 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -307,7 +307,9 @@
     @FastNative
     private static native void nativeWriteDouble(long nativePtr, double val);
     @FastNative
-    static native void nativeWriteString(long nativePtr, String val);
+    private static native void nativeWriteString8(long nativePtr, String val);
+    @FastNative
+    private static native void nativeWriteString16(long nativePtr, String val);
     @FastNative
     private static native void nativeWriteStrongBinder(long nativePtr, IBinder val);
     @FastNative
@@ -325,7 +327,9 @@
     @CriticalNative
     private static native double nativeReadDouble(long nativePtr);
     @FastNative
-    static native String nativeReadString(long nativePtr);
+    private static native String nativeReadString8(long nativePtr);
+    @FastNative
+    private static native String nativeReadString16(long nativePtr);
     @FastNative
     private static native IBinder nativeReadStrongBinder(long nativePtr);
     @FastNative
@@ -386,8 +390,12 @@
          * must use {@link #writeStringNoHelper(String)} to avoid
          * infinity recursive calls.
          */
-        public void writeString(Parcel p, String s) {
-            nativeWriteString(p.mNativePtr, s);
+        public void writeString8(Parcel p, String s) {
+            p.writeString8NoHelper(s);
+        }
+
+        public void writeString16(Parcel p, String s) {
+            p.writeString16NoHelper(s);
         }
 
         /**
@@ -395,8 +403,12 @@
          * must use {@link #readStringNoHelper()} to avoid
          * infinity recursive calls.
          */
-        public String readString(Parcel p) {
-            return nativeReadString(p.mNativePtr);
+        public String readString8(Parcel p) {
+            return p.readString8NoHelper();
+        }
+
+        public String readString16(Parcel p) {
+            return p.readString16NoHelper();
         }
     }
 
@@ -759,7 +771,17 @@
      * growing dataCapacity() if needed.
      */
     public final void writeString(@Nullable String val) {
-        mReadWriteHelper.writeString(this, val);
+        writeString16(val);
+    }
+
+    /** {@hide} */
+    public final void writeString8(@Nullable String val) {
+        mReadWriteHelper.writeString8(this, val);
+    }
+
+    /** {@hide} */
+    public final void writeString16(@Nullable String val) {
+        mReadWriteHelper.writeString16(this, val);
     }
 
     /**
@@ -770,7 +792,17 @@
      * @hide
      */
     public void writeStringNoHelper(@Nullable String val) {
-        nativeWriteString(mNativePtr, val);
+        writeString16NoHelper(val);
+    }
+
+    /** {@hide} */
+    public void writeString8NoHelper(@Nullable String val) {
+        nativeWriteString8(mNativePtr, val);
+    }
+
+    /** {@hide} */
+    public void writeString16NoHelper(@Nullable String val) {
+        nativeWriteString16(mNativePtr, val);
     }
 
     /**
@@ -2337,7 +2369,17 @@
      */
     @Nullable
     public final String readString() {
-        return mReadWriteHelper.readString(this);
+        return readString16();
+    }
+
+    /** {@hide} */
+    public final @Nullable String readString8() {
+        return mReadWriteHelper.readString8(this);
+    }
+
+    /** {@hide} */
+    public final @Nullable String readString16() {
+        return mReadWriteHelper.readString16(this);
     }
 
     /**
@@ -2347,9 +2389,18 @@
      *
      * @hide
      */
-    @Nullable
-    public String readStringNoHelper() {
-        return nativeReadString(mNativePtr);
+    public @Nullable String readStringNoHelper() {
+        return readString16NoHelper();
+    }
+
+    /** {@hide} */
+    public @Nullable String readString8NoHelper() {
+        return nativeReadString8(mNativePtr);
+    }
+
+    /** {@hide} */
+    public @Nullable String readString16NoHelper() {
+        return nativeReadString16(mNativePtr);
     }
 
     /**
diff --git a/core/java/android/os/incremental/IncrementalFileStorages.java b/core/java/android/os/incremental/IncrementalFileStorages.java
index 251995a..321dc9e 100644
--- a/core/java/android/os/incremental/IncrementalFileStorages.java
+++ b/core/java/android/os/incremental/IncrementalFileStorages.java
@@ -38,16 +38,10 @@
 import android.content.pm.IDataLoaderStatusListener;
 import android.content.pm.InstallationFileParcel;
 import android.text.TextUtils;
-import android.util.Slog;
 
 import java.io.File;
 import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
 import java.util.List;
-import java.util.Objects;
-import java.util.Random;
 
 /**
  * This class manages storage instances used during a package installation session.
@@ -56,13 +50,9 @@
 public final class IncrementalFileStorages {
     private static final String TAG = "IncrementalFileStorages";
 
-    private static final String TMP_DIR_ROOT = "/data/incremental/tmp";
-    private static final Random TMP_DIR_RANDOM = new Random();
-
+    private @NonNull final IncrementalManager mIncrementalManager;
+    private @NonNull final File mStageDir;
     private @Nullable IncrementalStorage mDefaultStorage;
-    private @Nullable String mDefaultDir;
-    private @NonNull IncrementalManager mIncrementalManager;
-    private @NonNull File mStageDir;
 
     /**
      * Set up files and directories used in an installation session. Only used by Incremental.
@@ -85,72 +75,63 @@
             throw new IOException("Failed to obtain incrementalManager.");
         }
 
-        IncrementalFileStorages result = null;
-        try {
-            result = new IncrementalFileStorages(stageDir, incrementalManager, dataLoaderParams,
-                    dataLoaderStatusListener);
-
-            if (!addedFiles.isEmpty()) {
-                result.mDefaultStorage.bind(stageDir.getAbsolutePath());
-            }
-
-            for (InstallationFileParcel file : addedFiles) {
-                if (file.location == LOCATION_DATA_APP) {
-                    try {
-                        result.addApkFile(file);
-                    } catch (IOException e) {
-                        // TODO(b/146080380): add incremental-specific error code
-                        throw new IOException(
-                                "Failed to add file to IncFS: " + file.name + ", reason: ", e);
-                    }
-                } else {
-                    throw new IOException("Unknown file location: " + file.location);
+        final IncrementalFileStorages result =
+                new IncrementalFileStorages(stageDir, incrementalManager, dataLoaderParams,
+                                            dataLoaderStatusListener);
+        for (InstallationFileParcel file : addedFiles) {
+            if (file.location == LOCATION_DATA_APP) {
+                try {
+                    result.addApkFile(file);
+                } catch (IOException e) {
+                    // TODO(b/146080380): add incremental-specific error code
+                    throw new IOException(
+                            "Failed to add file to IncFS: " + file.name + ", reason: ", e);
                 }
+            } else {
+                throw new IOException("Unknown file location: " + file.location);
             }
-
-            // TODO(b/146080380): remove 5 secs wait in startLoading
-            if (!result.mDefaultStorage.startLoading()) {
-                // TODO(b/146080380): add incremental-specific error code
-                throw new IOException("Failed to start loading data for Incremental installation.");
-            }
-
-            return result;
-        } catch (IOException e) {
-            Slog.e(TAG, "Failed to initialize Incremental file storages. Cleaning up...", e);
-            if (result != null) {
-                result.cleanUp();
-            }
-            throw e;
         }
+
+        if (!result.mDefaultStorage.startLoading()) {
+            // TODO(b/146080380): add incremental-specific error code
+            throw new IOException("Failed to start loading data for Incremental installation.");
+        }
+
+        return result;
     }
 
     private IncrementalFileStorages(@NonNull File stageDir,
             @NonNull IncrementalManager incrementalManager,
             @NonNull DataLoaderParams dataLoaderParams,
             @Nullable IDataLoaderStatusListener dataLoaderStatusListener) throws IOException {
-        mStageDir = stageDir;
-        mIncrementalManager = incrementalManager;
-        if (dataLoaderParams.getComponentName().getPackageName().equals("local")) {
-            final String incrementalPath = dataLoaderParams.getArguments();
-            mDefaultDir = incrementalPath;
-            if (TextUtils.isEmpty(mDefaultDir)) {
-                throw new IOException("Failed to create storage: incrementalPath is empty");
+        try {
+            mStageDir = stageDir;
+            mIncrementalManager = incrementalManager;
+            if (dataLoaderParams.getComponentName().getPackageName().equals("local")) {
+                final String incrementalPath = dataLoaderParams.getArguments();
+                if (TextUtils.isEmpty(incrementalPath)) {
+                    throw new IOException("Failed to create storage: incrementalPath is empty");
+                }
+                mDefaultStorage = mIncrementalManager.openStorage(incrementalPath);
+                if (mDefaultStorage == null) {
+                    throw new IOException(
+                            "Couldn't open incremental storage at " + incrementalPath);
+                }
+                mDefaultStorage.bind(stageDir.getAbsolutePath());
+            } else {
+                mDefaultStorage = mIncrementalManager.createStorage(stageDir.getAbsolutePath(),
+                        dataLoaderParams,
+                        dataLoaderStatusListener,
+                        IncrementalManager.CREATE_MODE_CREATE
+                                | IncrementalManager.CREATE_MODE_TEMPORARY_BIND, false);
+                if (mDefaultStorage == null) {
+                    throw new IOException(
+                            "Couldn't create incremental storage at " + stageDir);
+                }
             }
-            mDefaultStorage = mIncrementalManager.openStorage(incrementalPath);
-        } else {
-            mDefaultDir = getTempDir();
-            if (mDefaultDir == null) {
-                throw new IOException("Failed to create storage: tempDir is empty");
-            }
-            mDefaultStorage = mIncrementalManager.createStorage(mDefaultDir,
-                    dataLoaderParams,
-                    dataLoaderStatusListener,
-                    IncrementalManager.CREATE_MODE_CREATE
-                            | IncrementalManager.CREATE_MODE_TEMPORARY_BIND, false);
-        }
-
-        if (mDefaultStorage == null) {
-            throw new IOException("Failed to create storage");
+        } catch (IOException e) {
+            cleanUp();
+            throw e;
         }
     }
 
@@ -167,27 +148,14 @@
      * TODO(b/136132412): make sure unnecessary binds are removed but useful storages are kept
      */
     public void cleanUp() {
-        Objects.requireNonNull(mDefaultStorage);
+        if (mDefaultStorage == null) {
+            return;
+        }
 
         try {
-            mDefaultStorage.unBind(mDefaultDir);
             mDefaultStorage.unBind(mStageDir.getAbsolutePath());
         } catch (IOException ignored) {
         }
-
-        mDefaultDir = null;
         mDefaultStorage = null;
     }
-
-    private static String getTempDir() {
-        final Path tmpDir = Paths.get(TMP_DIR_ROOT,
-                String.valueOf(TMP_DIR_RANDOM.nextInt(Integer.MAX_VALUE - 1)));
-        try {
-            Files.createDirectories(tmpDir);
-        } catch (Exception ex) {
-            Slog.e(TAG, "Failed to create dir", ex);
-            return null;
-        }
-        return tmpDir.toAbsolutePath().toString();
-    }
 }
diff --git a/core/java/android/os/incremental/IncrementalManager.java b/core/java/android/os/incremental/IncrementalManager.java
index 35518db..916edfa 100644
--- a/core/java/android/os/incremental/IncrementalManager.java
+++ b/core/java/android/os/incremental/IncrementalManager.java
@@ -32,8 +32,12 @@
 import java.io.IOException;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
 
 /**
  * Provides operations to open or create an IncrementalStorage, using IIncrementalService
@@ -176,25 +180,6 @@
     }
 
     /**
-     * Iterates through path parents to find the base dir of an Incremental Storage.
-     *
-     * @param file Target file to search storage for.
-     * @return Absolute path which is a bind-mount point of Incremental File System.
-     */
-    @Nullable
-    private Path getStoragePathForFile(File file) {
-        File currentPath = new File(file.getParent());
-        while (currentPath.getParent() != null) {
-            IncrementalStorage storage = openStorage(currentPath.getAbsolutePath());
-            if (storage != null) {
-                return currentPath.toPath();
-            }
-            currentPath = new File(currentPath.getParent());
-        }
-        return null;
-    }
-
-    /**
      * Set up an app's code path. The expected outcome of this method is:
      * 1) The actual apk directory under /data/incremental is bind-mounted to the parent directory
      * of {@code afterCodeFile}.
@@ -212,29 +197,27 @@
      */
     public void renameCodePath(File beforeCodeFile, File afterCodeFile)
             throws IllegalArgumentException, IOException {
-        final String beforeCodePath = beforeCodeFile.getAbsolutePath();
-        final String afterCodePathParent = afterCodeFile.getParentFile().getAbsolutePath();
-        if (!isIncrementalPath(beforeCodePath)) {
-            throw new IllegalArgumentException("Not an Incremental path: " + beforeCodePath);
-        }
-        final String afterCodePathName = afterCodeFile.getName();
-        final Path apkStoragePath = Paths.get(beforeCodePath);
-        if (apkStoragePath == null || apkStoragePath.toAbsolutePath() == null) {
-            throw new IOException("Invalid source storage path for: " + beforeCodePath);
-        }
-        final IncrementalStorage apkStorage =
-                openStorage(apkStoragePath.toAbsolutePath().toString());
+        final File beforeCodeAbsolute = beforeCodeFile.getAbsoluteFile();
+        final IncrementalStorage apkStorage = openStorage(beforeCodeAbsolute.toString());
         if (apkStorage == null) {
-            throw new IOException("Failed to retrieve storage from Incremental Service.");
+            throw new IllegalArgumentException("Not an Incremental path: " + beforeCodeAbsolute);
         }
-        final IncrementalStorage linkedApkStorage = createStorage(afterCodePathParent, apkStorage,
-                IncrementalManager.CREATE_MODE_CREATE
-                        | IncrementalManager.CREATE_MODE_PERMANENT_BIND);
+        final String targetStorageDir = afterCodeFile.getAbsoluteFile().getParent();
+        final IncrementalStorage linkedApkStorage =
+                createStorage(targetStorageDir, apkStorage,
+                        IncrementalManager.CREATE_MODE_CREATE
+                                | IncrementalManager.CREATE_MODE_PERMANENT_BIND);
         if (linkedApkStorage == null) {
-            throw new IOException("Failed to create linked storage at dir: " + afterCodePathParent);
+            throw new IOException("Failed to create linked storage at dir: " + targetStorageDir);
         }
-        linkFiles(apkStorage, beforeCodeFile, "", linkedApkStorage, afterCodePathName);
-        apkStorage.unBind(beforeCodePath);
+        try {
+            final String afterCodePathName = afterCodeFile.getName();
+            linkFiles(apkStorage, beforeCodeAbsolute, "", linkedApkStorage, afterCodePathName);
+            apkStorage.unBind(beforeCodeAbsolute.toString());
+        } catch (Exception e) {
+            linkedApkStorage.unBind(targetStorageDir);
+            throw e;
+        }
     }
 
     /**
@@ -252,22 +235,27 @@
     private void linkFiles(IncrementalStorage sourceStorage, File sourceAbsolutePath,
             String sourceRelativePath, IncrementalStorage targetStorage,
             String targetRelativePath) throws IOException {
-        targetStorage.makeDirectory(targetRelativePath);
-        final File[] entryList = sourceAbsolutePath.listFiles();
-        for (int i = 0; i < entryList.length; i++) {
-            final File entry = entryList[i];
-            final String entryName = entryList[i].getName();
-            final String sourceEntryRelativePath =
-                    sourceRelativePath.isEmpty() ? entryName : sourceRelativePath + "/" + entryName;
-            final String targetEntryRelativePath = targetRelativePath + "/" + entryName;
-            if (entry.isFile()) {
-                sourceStorage.makeLink(
-                        sourceEntryRelativePath, targetStorage, targetEntryRelativePath);
-            } else if (entry.isDirectory()) {
-                linkFiles(sourceStorage, entry, sourceEntryRelativePath, targetStorage,
-                        targetEntryRelativePath);
+        final Path sourceBase = sourceAbsolutePath.toPath().resolve(sourceRelativePath);
+        final Path targetRelative = Paths.get(targetRelativePath);
+        Files.walkFileTree(sourceAbsolutePath.toPath(), new SimpleFileVisitor<Path>() {
+            @Override
+            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs)
+                    throws IOException {
+                final Path relativeDir = sourceBase.relativize(dir);
+                targetStorage.makeDirectory(targetRelative.resolve(relativeDir).toString());
+                return FileVisitResult.CONTINUE;
             }
-        }
+
+            @Override
+            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs)
+                    throws IOException {
+                final Path relativeFile = sourceBase.relativize(file);
+                sourceStorage.makeLink(
+                        file.toAbsolutePath().toString(), targetStorage,
+                        targetRelative.resolve(relativeFile).toString());
+                return FileVisitResult.CONTINUE;
+            }
+        });
     }
 
     /**
diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java
index df4ead1..28639b3 100644
--- a/core/java/android/text/TextUtils.java
+++ b/core/java/android/text/TextUtils.java
@@ -748,7 +748,7 @@
             int parcelableFlags) {
         if (cs instanceof Spanned) {
             p.writeInt(0);
-            p.writeString(cs.toString());
+            p.writeString8(cs.toString());
 
             Spanned sp = (Spanned) cs;
             Object[] os = sp.getSpans(0, cs.length(), Object.class);
@@ -785,9 +785,9 @@
         } else {
             p.writeInt(1);
             if (cs != null) {
-                p.writeString(cs.toString());
+                p.writeString8(cs.toString());
             } else {
-                p.writeString(null);
+                p.writeString8(null);
             }
         }
     }
@@ -807,7 +807,7 @@
         public CharSequence createFromParcel(Parcel p) {
             int kind = p.readInt();
 
-            String string = p.readString();
+            String string = p.readString8();
             if (string == null) {
                 return null;
             }
diff --git a/core/java/android/view/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java
index bdc8ba8..df89130 100644
--- a/core/java/android/view/ImeInsetsSourceConsumer.java
+++ b/core/java/android/view/ImeInsetsSourceConsumer.java
@@ -153,7 +153,7 @@
 
     @Override
     protected boolean isRequestedVisibleAwaitingControl() {
-        return mIsRequestedVisibleAwaitingControl;
+        return mIsRequestedVisibleAwaitingControl || isRequestedVisible();
     }
 
     private boolean isDummyOrEmptyEditor(EditorInfo info) {
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 7f6c0d2..7016c5c 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -1213,7 +1213,7 @@
 
         BitmapReflectionAction(Parcel in) {
             viewId = in.readInt();
-            methodName = in.readString();
+            methodName = in.readString8();
             bitmapId = in.readInt();
             bitmap = mBitmapCache.getBitmapForId(bitmapId);
         }
@@ -1221,7 +1221,7 @@
         @Override
         public void writeToParcel(Parcel dest, int flags) {
             dest.writeInt(viewId);
-            dest.writeString(methodName);
+            dest.writeString8(methodName);
             dest.writeInt(bitmapId);
         }
 
@@ -1282,7 +1282,7 @@
 
         ReflectionAction(Parcel in) {
             this.viewId = in.readInt();
-            this.methodName = in.readString();
+            this.methodName = in.readString8();
             this.type = in.readInt();
             //noinspection ConstantIfStatement
             if (false) {
@@ -1318,7 +1318,7 @@
                     this.value = (char)in.readInt();
                     break;
                 case STRING:
-                    this.value = in.readString();
+                    this.value = in.readString8();
                     break;
                 case CHAR_SEQUENCE:
                     this.value = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
@@ -1347,7 +1347,7 @@
 
         public void writeToParcel(Parcel out, int flags) {
             out.writeInt(this.viewId);
-            out.writeString(this.methodName);
+            out.writeString8(this.methodName);
             out.writeInt(this.type);
             //noinspection ConstantIfStatement
             if (false) {
@@ -1383,7 +1383,7 @@
                     out.writeInt((int)((Character)this.value).charValue());
                     break;
                 case STRING:
-                    out.writeString((String)this.value);
+                    out.writeString8((String)this.value);
                     break;
                 case CHAR_SEQUENCE:
                     TextUtils.writeToParcel((CharSequence)this.value, out, flags);
diff --git a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java
index b1e356d..bcb32fb 100644
--- a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java
+++ b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java
@@ -437,6 +437,9 @@
         resetViewVisibilitiesForWorkProfileEmptyState(emptyStateView);
         emptyStateView.setVisibility(View.VISIBLE);
 
+        View container = emptyStateView.findViewById(R.id.resolver_empty_state_container);
+        setupContainerPadding(container);
+
         TextView title = emptyStateView.findViewById(R.id.resolver_empty_state_title);
         title.setText(titleRes);
 
@@ -463,6 +466,12 @@
         activeListAdapter.markTabLoaded();
     }
 
+    /**
+     * Sets up the padding of the view containing the empty state screens.
+     * <p>This method is meant to be overridden so that subclasses can customize the padding.
+     */
+    protected void setupContainerPadding(View container) {}
+
     private void showConsumerUserNoAppsAvailableEmptyState(ResolverListAdapter activeListAdapter) {
         ProfileDescriptor descriptor = getItem(
                 userHandleToPageIndex(activeListAdapter.getUserHandle()));
diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java
index 970bab9..2fc75a7 100644
--- a/core/java/com/android/internal/app/ChooserActivity.java
+++ b/core/java/com/android/internal/app/ChooserActivity.java
@@ -2885,6 +2885,13 @@
         return METRICS_CATEGORY_CHOOSER;
     }
 
+    @Override
+    protected void onProfileTabSelected() {
+        ChooserGridAdapter currentRootAdapter =
+                mChooserMultiProfilePagerAdapter.getCurrentRootAdapter();
+        currentRootAdapter.updateDirectShareExpansion();
+    }
+
     /**
      * Adapter for all types of items and targets in ShareSheet.
      * Note that ranked sections like Direct Share - while appearing grid-like - are handled on the
@@ -3357,15 +3364,7 @@
         }
 
         public void handleScroll(View v, int y, int oldy) {
-            // Only expand direct share area if there is a minimum number of shortcuts,
-            // which will help reduce the amount of visible shuffling due to older-style
-            // direct share targets.
-            int orientation = getResources().getConfiguration().orientation;
-            boolean canExpandDirectShare =
-                    mChooserListAdapter.getNumShortcutResults() > getMaxTargetsPerRow()
-                    && orientation == Configuration.ORIENTATION_PORTRAIT
-                    && !isInMultiWindowMode();
-
+            boolean canExpandDirectShare = canExpandDirectShare();
             if (mDirectShareViewHolder != null && canExpandDirectShare) {
                 mDirectShareViewHolder.handleScroll(
                         mChooserMultiProfilePagerAdapter.getActiveAdapterView(), y, oldy,
@@ -3373,6 +3372,18 @@
             }
         }
 
+        /**
+         * Only expand direct share area if there is a minimum number of shortcuts,
+         * which will help reduce the amount of visible shuffling due to older-style
+         * direct share targets.
+         */
+        private boolean canExpandDirectShare() {
+            int orientation = getResources().getConfiguration().orientation;
+            return mChooserListAdapter.getNumShortcutResults() > getMaxTargetsPerRow()
+                    && orientation == Configuration.ORIENTATION_PORTRAIT
+                    && !isInMultiWindowMode();
+        }
+
         public ChooserListAdapter getListAdapter() {
             return mChooserListAdapter;
         }
@@ -3380,6 +3391,19 @@
         boolean shouldCellSpan(int position) {
             return getItemViewType(position) == VIEW_TYPE_NORMAL;
         }
+
+        void updateDirectShareExpansion() {
+            if (mDirectShareViewHolder == null || !canExpandDirectShare()) {
+                return;
+            }
+            RecyclerView activeAdapterView =
+                    mChooserMultiProfilePagerAdapter.getActiveAdapterView();
+            if (mResolverDrawerLayout.isCollapsed()) {
+                mDirectShareViewHolder.collapse(activeAdapterView);
+            } else {
+                mDirectShareViewHolder.expand(activeAdapterView);
+            }
+        }
     }
 
     /**
@@ -3577,6 +3601,20 @@
             newHeight = Math.max(newHeight, mDirectShareMinHeight);
             yDiff = newHeight - prevHeight;
 
+            updateDirectShareRowHeight(view, yDiff, newHeight);
+        }
+
+        void expand(RecyclerView view) {
+            updateDirectShareRowHeight(view, mDirectShareMaxHeight - mDirectShareCurrHeight,
+                    mDirectShareMaxHeight);
+        }
+
+        void collapse(RecyclerView view) {
+            updateDirectShareRowHeight(view, mDirectShareMinHeight - mDirectShareCurrHeight,
+                    mDirectShareMinHeight);
+        }
+
+        private void updateDirectShareRowHeight(RecyclerView view, int yDiff, int newHeight) {
             if (view == null || view.getChildCount() == 0 || yDiff == 0) {
                 return;
             }
diff --git a/core/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java
index 3eb0923..2c48925 100644
--- a/core/java/com/android/internal/app/IntentForwarderActivity.java
+++ b/core/java/com/android/internal/app/IntentForwarderActivity.java
@@ -50,6 +50,9 @@
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
 
 /**
  * This is used in conjunction with
@@ -74,11 +77,13 @@
     private Injector mInjector;
 
     private MetricsLogger mMetricsLogger;
+    protected ExecutorService mExecutorService;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         mInjector = createInjector();
+        mExecutorService = Executors.newSingleThreadExecutor();
 
         Intent intentReceived = getIntent();
         String className = intentReceived.getComponent().getClassName();
@@ -118,30 +123,9 @@
                 mInjector.getIPackageManager(), getContentResolver());
         if (newIntent != null) {
             newIntent.prepareToLeaveUser(callingUserId);
-
-            final ResolveInfo ri = mInjector.resolveActivityAsUser(newIntent, MATCH_DEFAULT_ONLY,
-                    targetUserId);
-            try {
-                startActivityAsCaller(newIntent, null, null, false, targetUserId);
-            } catch (RuntimeException e) {
-                int launchedFromUid = -1;
-                String launchedFromPackage = "?";
-                try {
-                    launchedFromUid = ActivityTaskManager.getService().getLaunchedFromUid(
-                            getActivityToken());
-                    launchedFromPackage = ActivityTaskManager.getService().getLaunchedFromPackage(
-                            getActivityToken());
-                } catch (RemoteException ignored) {
-                }
-
-                Slog.wtf(TAG, "Unable to launch as UID " + launchedFromUid + " package "
-                        + launchedFromPackage + ", while running in "
-                        + ActivityThread.currentProcessName(), e);
-            }
-
-            if (shouldShowDisclosure(ri, intentReceived)) {
-                mInjector.showToast(userMessageId, Toast.LENGTH_LONG);
-            }
+            maybeShowDisclosureAsync(intentReceived, newIntent, targetUserId, userMessageId);
+            CompletableFuture.runAsync(() -> startActivityAsCaller(
+                    newIntent, targetUserId), mExecutorService);
         } else {
             Slog.wtf(TAG, "the intent: " + intentReceived + " cannot be forwarded from user "
                     + callingUserId + " to user " + targetUserId);
@@ -149,6 +133,44 @@
         finish();
     }
 
+    private void maybeShowDisclosureAsync(
+            Intent intentReceived, Intent newIntent, int userId, int messageId) {
+        final CompletableFuture<ResolveInfo> resolveInfoFuture =
+                mInjector.resolveActivityAsUser(newIntent, MATCH_DEFAULT_ONLY, userId);
+        resolveInfoFuture.thenAcceptAsync(ri -> {
+            if (shouldShowDisclosure(ri, intentReceived)) {
+                mInjector.showToast(messageId, Toast.LENGTH_LONG);
+            }
+        }, getApplicationContext().getMainExecutor());
+    }
+
+    private void startActivityAsCaller(Intent newIntent, int userId) {
+        try {
+            startActivityAsCaller(
+                    newIntent,
+                    /* options= */ null,
+                    /* permissionToken= */ null,
+                    /* ignoreTargetSecurity= */ false,
+                    userId);
+        } catch (RuntimeException e) {
+            int launchedFromUid = -1;
+            String launchedFromPackage = "?";
+            try {
+                launchedFromUid = ActivityTaskManager.getService().getLaunchedFromUid(
+                        getActivityToken());
+                launchedFromPackage = ActivityTaskManager.getService()
+                        .getLaunchedFromPackage(getActivityToken());
+            } catch (RemoteException ignored) {
+            }
+
+            Slog.wtf(TAG, "Unable to launch as UID " + launchedFromUid + " package "
+                    + launchedFromPackage + ", while running in "
+                    + ActivityThread.currentProcessName(), e);
+        } finally {
+            mExecutorService.shutdown();
+        }
+    }
+
     private void launchChooserActivityWithCorrectTab(Intent intentReceived, String className) {
         // When showing the sharesheet, instead of forwarding to the other profile,
         // we launch the sharesheet in the current user and select the other tab.
@@ -322,8 +344,11 @@
         }
 
         @Override
-        public ResolveInfo resolveActivityAsUser(Intent intent, int flags, int userId) {
-            return getPackageManager().resolveActivityAsUser(intent, flags, userId);
+        @Nullable
+        public CompletableFuture<ResolveInfo> resolveActivityAsUser(
+                Intent intent, int flags, int userId) {
+            return CompletableFuture.supplyAsync(
+                    () -> getPackageManager().resolveActivityAsUser(intent, flags, userId));
         }
 
         @Override
@@ -339,7 +364,7 @@
 
         PackageManager getPackageManager();
 
-        ResolveInfo resolveActivityAsUser(Intent intent, int flags, int userId);
+        CompletableFuture<ResolveInfo> resolveActivityAsUser(Intent intent, int flags, int userId);
 
         void showToast(@StringRes int messageId, int duration);
     }
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index 1bc982c..00faa3b 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -146,6 +146,7 @@
 
     private static final String TAG = "ResolverActivity";
     private static final boolean DEBUG = false;
+    private static final String LAST_SHOWN_TAB_KEY = "last_shown_tab_key";
 
     private boolean mRegistered;
 
@@ -844,9 +845,19 @@
     }
 
     @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        ViewPager viewPager = findViewById(R.id.profile_pager);
+        outState.putInt(LAST_SHOWN_TAB_KEY, viewPager.getCurrentItem());
+    }
+
+    @Override
     protected void onRestoreInstanceState(Bundle savedInstanceState) {
         super.onRestoreInstanceState(savedInstanceState);
         resetButtonBar();
+        ViewPager viewPager = findViewById(R.id.profile_pager);
+        viewPager.setCurrentItem(savedInstanceState.getInt(LAST_SHOWN_TAB_KEY));
+        mMultiProfilePagerAdapter.clearInactiveProfileCache();
     }
 
     private boolean isHttpSchemeAndViewAction(Intent intent) {
@@ -1585,6 +1596,7 @@
         TabHost tabHost = findViewById(R.id.profile_tabhost);
         tabHost.setup();
         ViewPager viewPager = findViewById(R.id.profile_pager);
+        viewPager.setSaveEnabled(false);
         TabHost.TabSpec tabSpec = tabHost.newTabSpec(TAB_TAG_PERSONAL)
                 .setContent(R.id.profile_pager)
                 .setIndicator(getString(R.string.resolver_personal_tab));
@@ -1610,6 +1622,7 @@
             }
             setupViewVisibilities();
             maybeLogProfileChange();
+            onProfileTabSelected();
             DevicePolicyEventLogger
                     .createEvent(DevicePolicyEnums.RESOLVER_SWITCH_TABS)
                     .setInt(viewPager.getCurrentItem())
@@ -1628,6 +1641,12 @@
         findViewById(R.id.resolver_tab_divider).setVisibility(View.VISIBLE);
     }
 
+    /**
+     * Callback called when user changes the profile tab.
+     * <p>This method is intended to be overridden by subclasses.
+     */
+    protected void onProfileTabSelected() { }
+
     private void resetCheckedItem() {
         if (!isIntentPicker()) {
             return;
@@ -1745,22 +1764,33 @@
             return;
         }
         final ViewGroup buttonLayout = findViewById(R.id.button_bar);
-        if (buttonLayout != null) {
-            buttonLayout.setVisibility(View.VISIBLE);
-
-            if (!useLayoutWithDefault()) {
-                int inset = mSystemWindowInsets != null ? mSystemWindowInsets.bottom : 0;
-                buttonLayout.setPadding(buttonLayout.getPaddingLeft(), buttonLayout.getPaddingTop(),
-                        buttonLayout.getPaddingRight(), getResources().getDimensionPixelSize(
-                                R.dimen.resolver_button_bar_spacing) + inset);
-            }
-            mOnceButton = (Button) buttonLayout.findViewById(R.id.button_once);
-            mAlwaysButton = (Button) buttonLayout.findViewById(R.id.button_always);
-
-            resetAlwaysOrOnceButtonBar();
-        } else {
+        if (buttonLayout == null) {
             Log.e(TAG, "Layout unexpectedly does not have a button bar");
+            return;
         }
+        ResolverListAdapter activeListAdapter =
+                mMultiProfilePagerAdapter.getActiveListAdapter();
+        View buttonBarDivider = findViewById(R.id.resolver_button_bar_divider);
+        if (activeListAdapter.isTabLoaded()
+                && mMultiProfilePagerAdapter.shouldShowEmptyStateScreen(activeListAdapter)) {
+            buttonLayout.setVisibility(View.INVISIBLE);
+            buttonBarDivider.setVisibility(View.INVISIBLE);
+            return;
+        }
+
+        buttonBarDivider.setVisibility(View.VISIBLE);
+        buttonLayout.setVisibility(View.VISIBLE);
+
+        if (!useLayoutWithDefault()) {
+            int inset = mSystemWindowInsets != null ? mSystemWindowInsets.bottom : 0;
+            buttonLayout.setPadding(buttonLayout.getPaddingLeft(), buttonLayout.getPaddingTop(),
+                    buttonLayout.getPaddingRight(), getResources().getDimensionPixelSize(
+                            R.dimen.resolver_button_bar_spacing) + inset);
+        }
+        mOnceButton = (Button) buttonLayout.findViewById(R.id.button_once);
+        mAlwaysButton = (Button) buttonLayout.findViewById(R.id.button_always);
+
+        resetAlwaysOrOnceButtonBar();
     }
 
     private void resetAlwaysOrOnceButtonBar() {
diff --git a/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java
index ad31d8b..885d1bb 100644
--- a/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java
+++ b/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java
@@ -213,6 +213,12 @@
                 /* subtitleRes */ 0);
     }
 
+    @Override
+    protected void setupContainerPadding(View container) {
+        container.setPadding(container.getPaddingLeft(), container.getPaddingTop(),
+                container.getPaddingRight(), /* bottom */ 0);
+    }
+
     class ResolverProfileDescriptor extends ProfileDescriptor {
         private ResolverListAdapter resolverListAdapter;
         final ListView listView;
diff --git a/core/java/com/android/internal/os/BatteryStatsImpl.java b/core/java/com/android/internal/os/BatteryStatsImpl.java
index 43bd4a6..6432475 100644
--- a/core/java/com/android/internal/os/BatteryStatsImpl.java
+++ b/core/java/com/android/internal/os/BatteryStatsImpl.java
@@ -6093,7 +6093,8 @@
         return array;
     }
 
-    public void noteNetworkInterfaceTypeLocked(String iface, int networkType) {
+    /** @hide */
+    public void noteNetworkInterfaceType(String iface, int networkType) {
         if (TextUtils.isEmpty(iface)) return;
 
         synchronized (mModemNetworkLock) {
diff --git a/core/java/com/android/internal/policy/GestureNavigationSettingsObserver.java b/core/java/com/android/internal/policy/GestureNavigationSettingsObserver.java
index ebfea450..56a6db9 100644
--- a/core/java/com/android/internal/policy/GestureNavigationSettingsObserver.java
+++ b/core/java/com/android/internal/policy/GestureNavigationSettingsObserver.java
@@ -46,6 +46,9 @@
         r.registerContentObserver(
                 Settings.Secure.getUriFor(Settings.Secure.BACK_GESTURE_INSET_SCALE_RIGHT),
                 false, this, UserHandle.USER_ALL);
+        r.registerContentObserver(
+                Settings.Secure.getUriFor(Settings.Secure.USER_SETUP_COMPLETE),
+                false, this, UserHandle.USER_ALL);
     }
 
     public void unregister() {
@@ -68,6 +71,11 @@
         return getSensitivity(userRes, Settings.Secure.BACK_GESTURE_INSET_SCALE_RIGHT);
     }
 
+    public boolean areNavigationButtonForcedVisible() {
+        return Settings.Secure.getIntForUser(mContext.getContentResolver(),
+                Settings.Secure.USER_SETUP_COMPLETE, 0, UserHandle.USER_CURRENT) == 0;
+    }
+
     private int getSensitivity(Resources userRes, String side) {
         final int inset = userRes.getDimensionPixelSize(
                 com.android.internal.R.dimen.config_backGestureInset);
diff --git a/core/java/com/android/internal/widget/ResolverDrawerLayout.java b/core/java/com/android/internal/widget/ResolverDrawerLayout.java
index 31527e8..fb2ecf3 100644
--- a/core/java/com/android/internal/widget/ResolverDrawerLayout.java
+++ b/core/java/com/android/internal/widget/ResolverDrawerLayout.java
@@ -1084,6 +1084,7 @@
     protected Parcelable onSaveInstanceState() {
         final SavedState ss = new SavedState(super.onSaveInstanceState());
         ss.open = mCollapsibleHeight > 0 && mCollapseOffset == 0;
+        ss.mCollapsibleHeightReserved = mCollapsibleHeightReserved;
         return ss;
     }
 
@@ -1092,6 +1093,7 @@
         final SavedState ss = (SavedState) state;
         super.onRestoreInstanceState(ss.getSuperState());
         mOpenOnLayout = ss.open;
+        mCollapsibleHeightReserved = ss.mCollapsibleHeightReserved;
     }
 
     public static class LayoutParams extends MarginLayoutParams {
@@ -1142,6 +1144,7 @@
 
     static class SavedState extends BaseSavedState {
         boolean open;
+        private int mCollapsibleHeightReserved;
 
         SavedState(Parcelable superState) {
             super(superState);
@@ -1150,12 +1153,14 @@
         private SavedState(Parcel in) {
             super(in);
             open = in.readInt() != 0;
+            mCollapsibleHeightReserved = in.readInt();
         }
 
         @Override
         public void writeToParcel(Parcel out, int flags) {
             super.writeToParcel(out, flags);
             out.writeInt(open ? 1 : 0);
+            out.writeInt(mCollapsibleHeightReserved);
         }
 
         public static final Parcelable.Creator<SavedState> CREATOR =
diff --git a/core/jni/android_os_Parcel.cpp b/core/jni/android_os_Parcel.cpp
index 483b455..e7a2fb4 100644
--- a/core/jni/android_os_Parcel.cpp
+++ b/core/jni/android_os_Parcel.cpp
@@ -273,7 +273,28 @@
     }
 }
 
-static void android_os_Parcel_writeString(JNIEnv* env, jclass clazz, jlong nativePtr, jstring val)
+static void android_os_Parcel_writeString8(JNIEnv* env, jclass clazz, jlong nativePtr, jstring val)
+{
+    Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
+    if (parcel != NULL) {
+        status_t err = NO_MEMORY;
+        if (val) {
+            const size_t len = env->GetStringUTFLength(val);
+            const char* str = env->GetStringUTFChars(val, 0);
+            if (str) {
+                err = parcel->writeString8(str, len);
+                env->ReleaseStringUTFChars(val, str);
+            }
+        } else {
+            err = parcel->writeString8(NULL, 0);
+        }
+        if (err != NO_ERROR) {
+            signalExceptionForError(env, clazz, err);
+        }
+    }
+}
+
+static void android_os_Parcel_writeString16(JNIEnv* env, jclass clazz, jlong nativePtr, jstring val)
 {
     Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
     if (parcel != NULL) {
@@ -444,7 +465,21 @@
     return 0;
 }
 
-static jstring android_os_Parcel_readString(JNIEnv* env, jclass clazz, jlong nativePtr)
+static jstring android_os_Parcel_readString8(JNIEnv* env, jclass clazz, jlong nativePtr)
+{
+    Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
+    if (parcel != NULL) {
+        size_t len;
+        const char* str = parcel->readString8Inplace(&len);
+        if (str) {
+            return env->NewStringUTF(str);
+        }
+        return NULL;
+    }
+    return NULL;
+}
+
+static jstring android_os_Parcel_readString16(JNIEnv* env, jclass clazz, jlong nativePtr)
 {
     Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
     if (parcel != NULL) {
@@ -722,7 +757,9 @@
     // @FastNative
     {"nativeWriteDouble",         "(JD)V", (void*)android_os_Parcel_writeDouble},
     // @FastNative
-    {"nativeWriteString",         "(JLjava/lang/String;)V", (void*)android_os_Parcel_writeString},
+    {"nativeWriteString8",        "(JLjava/lang/String;)V", (void*)android_os_Parcel_writeString8},
+    // @FastNative
+    {"nativeWriteString16",       "(JLjava/lang/String;)V", (void*)android_os_Parcel_writeString16},
     // @FastNative
     {"nativeWriteStrongBinder",   "(JLandroid/os/IBinder;)V", (void*)android_os_Parcel_writeStrongBinder},
     // @FastNative
@@ -740,7 +777,9 @@
     // @CriticalNative
     {"nativeReadDouble",          "(J)D", (void*)android_os_Parcel_readDouble},
     // @FastNative
-    {"nativeReadString",          "(J)Ljava/lang/String;", (void*)android_os_Parcel_readString},
+    {"nativeReadString8",         "(J)Ljava/lang/String;", (void*)android_os_Parcel_readString8},
+    // @FastNative
+    {"nativeReadString16",        "(J)Ljava/lang/String;", (void*)android_os_Parcel_readString16},
     // @FastNative
     {"nativeReadStrongBinder",    "(J)Landroid/os/IBinder;", (void*)android_os_Parcel_readStrongBinder},
     // @FastNative
diff --git a/core/proto/android/app/settings_enums.proto b/core/proto/android/app/settings_enums.proto
index 3e007e4..997829e 100644
--- a/core/proto/android/app/settings_enums.proto
+++ b/core/proto/android/app/settings_enums.proto
@@ -2523,16 +2523,10 @@
     // OS: R
     PANEL_ADD_WIFI_NETWORKS = 1809;
 
-    // OPEN: Settings > Accessibility > Enable accessibility service > Show tutorial dialog
+    // OPEN: Settings > Accessibility > Enable the feature or shortcut > Show tutorial dialog
     // CATEGORY: SETTINGS
     // OS: R
-    DIALOG_TOGGLE_SCREEN_ACCESSIBILITY_BUTTON = 1810;
-
-    // OPEN: Settings > Accessibility > Enable accessibility service > Show tutorial dialog in
-    // gesture mode
-    // CATEGORY: SETTINGS
-    // OS: R
-    DIALOG_TOGGLE_SCREEN_GESTURE_NAVIGATION = 1811;
+    DIALOG_ACCESSIBILITY_TUTORIAL = 1810;
 
     // OPEN: Settings > Accessibility > Edit shortcut dialog
     // CATEGORY: SETTINGS
diff --git a/core/res/res/layout/resolver_empty_states.xml b/core/res/res/layout/resolver_empty_states.xml
index 25615d2..fe11769 100644
--- a/core/res/res/layout/resolver_empty_states.xml
+++ b/core/res/res/layout/resolver_empty_states.xml
@@ -24,6 +24,7 @@
     android:paddingStart="24dp"
     android:paddingEnd="24dp">
     <RelativeLayout
+        android:id="@+id/resolver_empty_state_container"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:paddingTop="48dp"
diff --git a/core/res/res/layout/resolver_list.xml b/core/res/res/layout/resolver_list.xml
index b754e0c..76ecefc 100644
--- a/core/res/res/layout/resolver_list.xml
+++ b/core/res/res/layout/resolver_list.xml
@@ -112,59 +112,61 @@
             </FrameLayout>
         </LinearLayout>
     </TabHost>
-
-    <View
-        android:layout_alwaysShow="true"
-        android:layout_width="match_parent"
-        android:layout_height="1dp"
-        android:background="?attr/colorBackgroundFloating"
-        android:foreground="?attr/dividerVertical" />
-
     <LinearLayout
-        android:id="@+id/button_bar"
-        android:visibility="gone"
-        style="?attr/buttonBarStyle"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:layout_ignoreOffset="true"
         android:layout_alwaysShow="true"
-        android:layout_hasNestedScrollIndicator="true"
-        android:gravity="end|center_vertical"
-        android:orientation="horizontal"
-        android:layoutDirection="locale"
-        android:measureWithLargestChild="true"
-        android:background="?attr/colorBackgroundFloating"
-        android:paddingTop="@dimen/resolver_button_bar_spacing"
-        android:paddingBottom="@dimen/resolver_button_bar_spacing"
-        android:paddingStart="@dimen/resolver_edge_margin"
-        android:paddingEnd="@dimen/resolver_small_margin"
-        android:elevation="@dimen/resolver_elevation">
-
-        <Button
-            android:id="@+id/button_once"
-            android:layout_width="wrap_content"
-            android:layout_gravity="start"
-            android:maxLines="2"
-            style="?attr/buttonBarButtonStyle"
-            android:fontFamily="@android:string/config_headlineFontFamilyMedium"
+        android:orientation="vertical"
+        android:background="?attr/colorBackgroundFloating">
+        <View
+            android:id="@+id/resolver_button_bar_divider"
+            android:layout_width="match_parent"
+            android:layout_height="1dp"
+            android:background="?attr/colorBackgroundFloating"
+            android:foreground="?attr/dividerVertical" />
+        <LinearLayout
+            android:id="@+id/button_bar"
+            android:visibility="gone"
+            style="?attr/buttonBarStyle"
+            android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:textAllCaps="false"
-            android:enabled="false"
-            android:text="@string/activity_resolver_use_once"
-            android:onClick="onButtonClick" />
+            android:layout_ignoreOffset="true"
+            android:layout_hasNestedScrollIndicator="true"
+            android:gravity="end|center_vertical"
+            android:orientation="horizontal"
+            android:layoutDirection="locale"
+            android:measureWithLargestChild="true"
+            android:paddingTop="@dimen/resolver_button_bar_spacing"
+            android:paddingBottom="@dimen/resolver_button_bar_spacing"
+            android:paddingStart="@dimen/resolver_edge_margin"
+            android:paddingEnd="@dimen/resolver_small_margin"
+            android:elevation="@dimen/resolver_elevation">
 
-        <Button
-            android:id="@+id/button_always"
-            android:layout_width="wrap_content"
-            android:layout_gravity="end"
-            android:maxLines="2"
-            style="?attr/buttonBarButtonStyle"
-            android:fontFamily="@android:string/config_headlineFontFamilyMedium"
-            android:textAllCaps="false"
-            android:layout_height="wrap_content"
-            android:enabled="false"
-            android:text="@string/activity_resolver_use_always"
-            android:onClick="onButtonClick" />
+            <Button
+                android:id="@+id/button_once"
+                android:layout_width="wrap_content"
+                android:layout_gravity="start"
+                android:maxLines="2"
+                style="?attr/buttonBarButtonStyle"
+                android:fontFamily="@android:string/config_headlineFontFamilyMedium"
+                android:layout_height="wrap_content"
+                android:textAllCaps="false"
+                android:enabled="false"
+                android:text="@string/activity_resolver_use_once"
+                android:onClick="onButtonClick" />
+
+            <Button
+                android:id="@+id/button_always"
+                android:layout_width="wrap_content"
+                android:layout_gravity="end"
+                android:maxLines="2"
+                style="?attr/buttonBarButtonStyle"
+                android:fontFamily="@android:string/config_headlineFontFamilyMedium"
+                android:textAllCaps="false"
+                android:layout_height="wrap_content"
+                android:enabled="false"
+                android:text="@string/activity_resolver_use_always"
+                android:onClick="onButtonClick" />
+        </LinearLayout>
     </LinearLayout>
-
 </com.android.internal.widget.ResolverDrawerLayout>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 1a98553..6fac46e 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -1908,7 +1908,7 @@
     <!-- The name of the package that will hold the call screening role by default. -->
     <string name="config_defaultCallScreening" translatable="false"></string>
     <!-- The name of the package that will hold the system gallery role. -->
-    <string name="config_systemGallery" translatable="false">com.android.gallery</string>
+    <string name="config_systemGallery" translatable="false">com.android.gallery3d</string>
 
     <!-- The name of the package that will be allowed to change its components' label/icon. -->
     <string name="config_overrideComponentUiPackage" translatable="false"></string>
@@ -3496,10 +3496,9 @@
 
     <!-- Do not translate. Mcc codes whose existence trigger the presence of emergency
          affordances-->
-    <integer-array name="config_emergency_mcc_codes" translatable="false">
-        <item>404</item>
-        <item>405</item>
-    </integer-array>
+    <string-array name="config_emergency_iso_country_codes" translatable="false">
+        <item>in</item>
+    </string-array>
 
     <!-- Package name for the device provisioning package. -->
     <string name="config_deviceProvisioningPackage"></string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 05c00ce..4efd7ff 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -3085,7 +3085,7 @@
   <java-symbol type="string" name="global_action_emergency" />
   <java-symbol type="string" name="config_emergency_call_number" />
   <java-symbol type="string" name="config_emergency_dialer_package" />
-  <java-symbol type="array" name="config_emergency_mcc_codes" />
+  <java-symbol type="array" name="config_emergency_iso_country_codes" />
 
   <java-symbol type="string" name="config_dozeDoubleTapSensorType" />
   <java-symbol type="string" name="config_dozeTapSensorType" />
@@ -3921,6 +3921,8 @@
   <java-symbol type="id" name="resolver_empty_state_button" />
   <java-symbol type="id" name="resolver_empty_state_progress" />
   <java-symbol type="id" name="resolver_tab_divider" />
+  <java-symbol type="id" name="resolver_button_bar_divider" />
+  <java-symbol type="id" name="resolver_empty_state_container" />
   <java-symbol type="string" name="resolver_cant_share_with_work_apps" />
   <java-symbol type="string" name="resolver_cant_share_with_work_apps_explanation" />
   <java-symbol type="string" name="resolver_cant_share_with_personal_apps" />
diff --git a/core/tests/benchmarks/src/android/os/ParcelStringBenchmark.java b/core/tests/benchmarks/src/android/os/ParcelStringBenchmark.java
new file mode 100644
index 0000000..daa90c2
--- /dev/null
+++ b/core/tests/benchmarks/src/android/os/ParcelStringBenchmark.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+import com.google.caliper.AfterExperiment;
+import com.google.caliper.BeforeExperiment;
+import com.google.caliper.Param;
+
+public class ParcelStringBenchmark {
+
+    @Param({"com.example.typical_package_name", "從不喜歡孤單一個 - 蘇永康/吳雨霏"})
+    String mValue;
+
+    private Parcel mParcel;
+
+    @BeforeExperiment
+    protected void setUp() {
+        mParcel = Parcel.obtain();
+    }
+
+    @AfterExperiment
+    protected void tearDown() {
+        mParcel.recycle();
+        mParcel = null;
+    }
+
+    public void timeWriteString8(int reps) {
+        for (int i = 0; i < reps; i++) {
+            mParcel.setDataPosition(0);
+            mParcel.writeString8(mValue);
+        }
+    }
+
+    public void timeReadString8(int reps) {
+        mParcel.writeString8(mValue);
+
+        for (int i = 0; i < reps; i++) {
+            mParcel.setDataPosition(0);
+            mParcel.readString8();
+        }
+    }
+
+    public void timeWriteString16(int reps) {
+        for (int i = 0; i < reps; i++) {
+            mParcel.setDataPosition(0);
+            mParcel.writeString16(mValue);
+        }
+    }
+
+    public void timeReadString16(int reps) {
+        mParcel.writeString16(mValue);
+
+        for (int i = 0; i < reps; i++) {
+            mParcel.setDataPosition(0);
+            mParcel.readString16();
+        }
+    }
+}
diff --git a/core/tests/coretests/src/android/os/ParcelTest.java b/core/tests/coretests/src/android/os/ParcelTest.java
index 46873b9..dcb3e2f 100644
--- a/core/tests/coretests/src/android/os/ParcelTest.java
+++ b/core/tests/coretests/src/android/os/ParcelTest.java
@@ -87,4 +87,27 @@
 
         p.recycle();
     }
+
+    /**
+     * Verify that writing/reading UTF-8 and UTF-16 strings works well.
+     */
+    @Test
+    public void testStrings() {
+        final String[] strings = {
+                null, "", "abc\0def", "com.example.typical_package_name",
+                "從不喜歡孤單一個 - 蘇永康/吳雨霏", "example"
+        };
+
+        final Parcel p = Parcel.obtain();
+        for (String string : strings) {
+            p.writeString8(string);
+            p.writeString16(string);
+        }
+
+        p.setDataPosition(0);
+        for (String string : strings) {
+            assertEquals(string, p.readString8());
+            assertEquals(string, p.readString16());
+        }
+    }
 }
diff --git a/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java b/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java
index 5424b6f..43590ba 100644
--- a/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java
+++ b/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java
@@ -68,6 +68,8 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
 
 @RunWith(AndroidJUnit4.class)
 public class IntentForwarderActivityTest {
@@ -633,6 +635,11 @@
         public void onCreate(@Nullable Bundle savedInstanceState) {
             getIntent().setComponent(sComponentName);
             super.onCreate(savedInstanceState);
+            try {
+                mExecutorService.awaitTermination(/* timeout= */ 30, TimeUnit.SECONDS);
+            } catch (InterruptedException e) {
+                e.printStackTrace();
+            }
         }
 
         @Override
@@ -671,7 +678,8 @@
         }
 
         @Override
-        public ResolveInfo resolveActivityAsUser(Intent intent, int flags, int userId) {
+        public CompletableFuture<ResolveInfo> resolveActivityAsUser(
+                Intent intent, int flags, int userId) {
             ActivityInfo activityInfo = new ActivityInfo();
             activityInfo.packageName = sPackageName;
             activityInfo.name = sActivityName;
@@ -680,7 +688,7 @@
             ResolveInfo resolveInfo = new ResolveInfo();
             resolveInfo.activityInfo = activityInfo;
 
-            return resolveInfo;
+            return CompletableFuture.completedFuture(resolveInfo);
         }
 
         @Override
diff --git a/identity/java/android/security/identity/IdentityCredential.java b/identity/java/android/security/identity/IdentityCredential.java
index 1db2f63..b351b3d 100644
--- a/identity/java/android/security/identity/IdentityCredential.java
+++ b/identity/java/android/security/identity/IdentityCredential.java
@@ -95,9 +95,7 @@
     /**
      * Sets whether to allow using an authentication key which use count has been exceeded if no
      * other key is available. This must be called prior to calling
-     * {@link #getEntries(byte[], Map, byte[], byte[])} or using a
-     * {@link android.hardware.biometrics.BiometricPrompt.CryptoObject} which references this
-     * object.
+     * {@link #getEntries(byte[], Map, byte[], byte[])}.
      *
      * By default this is set to true.
      *
@@ -123,13 +121,14 @@
      * entries.
      *
      * <p>It is the responsibility of the calling application to know if authentication is needed
-     * and use e.g. {@link android.hardware.biometrics.BiometricPrompt}) to make the user
+     * and use e.g. {@link android.hardware.biometrics.BiometricPrompt} to make the user
      * authenticate using a {@link android.hardware.biometrics.BiometricPrompt.CryptoObject} which
      * references this object. If needed, this must be done before calling
      * {@link #getEntries(byte[], Map, byte[], byte[])}.
      *
-     * <p>If this method returns successfully (i.e. without throwing an exception), it must not be
-     * called again on this instance.
+     * <p>It is permissible to call this method multiple times using the same instance but if this
+     * is done, the {@code sessionTranscript} parameter must be identical for each call. If this is
+     * not the case, the {@link SessionTranscriptMismatchException} exception is thrown.
      *
      * <p>If not {@code null} the {@code requestMessage} parameter must contain data for the request
      * from the verifier. The content can be defined in the way appropriate for the credential, byt
@@ -141,6 +140,9 @@
      *     the example below.</li>
      * </ul>
      *
+     * <p>If these requirements are not met the {@link InvalidRequestMessageException} exception
+     * is thrown.
+     *
      * <p>Here's an example of CBOR which conforms to this requirement:
      * <pre>
      *   ItemsRequest = {
@@ -149,6 +151,8 @@
      *     ? "RequestInfo" : {* tstr => any} ; Additional info the reader wants to provide
      *   }
      *
+     *   DocType = tstr
+     *
      *   NameSpaces = {
      *     + NameSpace => DataElements    ; Requested data elements for each NameSpace
      *   }
@@ -172,16 +176,18 @@
      *     EReaderKeyBytes
      *   ]
      *
-     *   DeviceEngagementBytes = #6.24(bstr .cbor DeviceEngagement)
-     *   EReaderKeyBytes = #6.24(bstr .cbor EReaderKey.Pub)
+     *   DeviceEngagementBytes = #6.24(bstr .cbor DeviceEngagement)  ; Bytes of DeviceEngagement
+     *   EReaderKeyBytes = #6.24(bstr .cbor EReaderKey.Pub)  ; Bytes of EReaderKey.pub
+     *
+     *   EReaderKey.Pub = COSE_Key    ; Ephemeral public key provided by reader
      * </pre>
      *
-     * <p>If the SessionTranscript is not empty, a COSE_Key structure for the public part
-     * of the key-pair previously generated by {@link #createEphemeralKeyPair()} must appear
-     * somewhere in {@code DeviceEngagement} and the X and Y coordinates must both be present
+     * <p>where a {@code COSE_Key} structure for the public part of the key-pair previously
+     * generated by {@link #createEphemeralKeyPair()} must appear somewhere in
+     * {@code DeviceEngagement} and the X and Y coordinates must both be present
      * in uncompressed form.
      *
-     * <p>If {@code readerAuth} is not {@code null} it must be the bytes of a COSE_Sign1
+     * <p>If {@code readerAuth} is not {@code null} it must be the bytes of a {@code COSE_Sign1}
      * structure as defined in RFC 8152. For the payload nil shall be used and the
      * detached payload is the ReaderAuthentication CBOR described below.
      * <pre>
@@ -194,20 +200,23 @@
      *     ItemsRequestBytes = #6.24(bstr .cbor ItemsRequest)   ; Bytes of ItemsRequest
      * </pre>
      *
-     * <p>The public key corresponding to the key used to made signature, can be
-     * found in the {@code x5chain} unprotected header element of the COSE_Sign1
-     * structure (as as described in 'draft-ietf-cose-x509-04'). There will be at
-     * least one certificate in said element and there may be more (and if so,
+     * <p>where {@code ItemsRequestBytes} are the bytes in the {@code requestMessage} parameter.
+     *
+     * <p>The public key corresponding to the key used to make the signature, can be found in the
+     * {@code x5chain} unprotected header element of the {@code COSE_Sign1} structure (as as
+     * described in
+     * <a href="https://tools.ietf.org/html/draft-ietf-cose-x509-04">draft-ietf-cose-x509-04</a>).
+     * There will be at least one certificate in said element and there may be more (and if so,
      * each certificate must be signed by its successor).
      *
-     * <p>Data elements protected by reader authentication is returned if, and only if, they are
+     * <p>Data elements protected by reader authentication are returned if, and only if, they are
      * mentioned in {@code requestMessage}, {@code requestMessage} is signed by the top-most
-     * certificate in {@code readerCertificateChain}, and the data element is configured
-     * with an {@link AccessControlProfile} with a {@link X509Certificate} in
-     * {@code readerCertificateChain}.
+     * certificate in the reader's certificate chain, and the data element is configured
+     * with an {@link AccessControlProfile} configured with an X.509 certificate which appears
+     * in the certificate chain.
      *
      * <p>Note that only items referenced in {@code entriesToRequest} are returned - the
-     * {@code requestMessage} parameter is only used to for enforcing reader authentication.
+     * {@code requestMessage} parameter is used only for enforcing reader authentication.
      *
      * <p>The reason for having {@code requestMessage} and {@code entriesToRequest} as separate
      * parameters is that the former represents a request from the remote verifier device
@@ -219,13 +228,12 @@
      * @param entriesToRequest       The entries to request, organized as a map of namespace
      *                               names with each value being a collection of data elements
      *                               in the given namespace.
-     * @param readerSignature        COSE_Sign1 structure as described above or {@code null}
-     *                               if reader authentication is not being used.
+     * @param readerSignature        A {@code COSE_Sign1} structure as described above or
+     *                               {@code null} if reader authentication is not being used.
      * @return A {@link ResultData} object containing entry data organized by namespace and a
      *         cryptographically authenticated representation of the same data.
      * @throws SessionTranscriptMismatchException     Thrown when trying use multiple different
-     *                                                session transcripts in the same presentation
-     *                                                session.
+     *                                                session transcripts.
      * @throws NoAuthenticationKeyAvailableException  if authentication keys were never
      *                                                provisioned, the method
      *                                             {@link #setAvailableAuthenticationKeys(int, int)}
@@ -255,8 +263,8 @@
      * Sets the number of dynamic authentication keys the {@code IdentityCredential} will maintain,
      * and the number of times each should be used.
      *
-     * <p>{@code IdentityCredential}s will select the least-used dynamic authentication key each
-     * time {@link #getEntries(byte[], Map, byte[], byte[])} is called. {@code IdentityCredential}s
+     * <p>The Identity Credential system will select the least-used dynamic authentication key each
+     * time {@link #getEntries(byte[], Map, byte[], byte[])} is called. Identity Credentials
      * for which this method has not been called behave as though it had been called wit
      * {@code keyCount} 0 and {@code maxUsesPerKey} 1.
      *
@@ -274,9 +282,10 @@
      * <p>When there aren't enough certified dynamic authentication keys, either because the key
      * count has been increased or because one or more keys have reached their usage count, this
      * method will generate replacement keys and certificates and return them for issuer
-     * certification. The issuer certificates and associated static authentication data must then
-     * be provided back to the {@code IdentityCredential} using
-     * {@link #storeStaticAuthenticationData(X509Certificate, byte[])}.
+     * certification.  The issuer certificates and associated static authentication data must then
+     * be provided back to the Identity Credential using
+     * {@link #storeStaticAuthenticationData(X509Certificate, byte[])}.  The private part of
+     * each authentication key never leaves secure hardware.
      *
      * <p>Each X.509 certificate is signed by CredentialKey. The certificate chain for CredentialKey
      * can be obtained using the {@link #getCredentialKeyCertificateChain()} method.
diff --git a/identity/java/android/security/identity/IdentityCredentialStore.java b/identity/java/android/security/identity/IdentityCredentialStore.java
index a1dfc77..4f834d2 100644
--- a/identity/java/android/security/identity/IdentityCredentialStore.java
+++ b/identity/java/android/security/identity/IdentityCredentialStore.java
@@ -78,17 +78,21 @@
 
     /**
      * Specifies that the cipher suite that will be used to secure communications between the reader
-     * is:
+     * and the prover is using the following primitives
      *
      * <ul>
-     * <li>ECDHE with HKDF-SHA-256 for key agreement.</li>
-     * <li>AES-256 with GCM block mode for authenticated encryption (nonces are incremented by one
-     * for every message).</li>
-     * <li>ECDSA with SHA-256 for signing (used for signing session transcripts to defeat
-     * man-in-the-middle attacks), signing keys are not ephemeral. See {@link IdentityCredential}
-     * for details on reader and prover signing keys.</li>
+     * <li>ECKA-DH (Elliptic Curve Key Agreement Algorithm - Diffie-Hellman, see BSI TR-03111).</li>
+     *
+     * <li>HKDF-SHA-256 (see RFC 5869).</li>
+     *
+     * <li>AES-256-GCM (see NIST SP 800-38D).</li>
+     *
+     * <li>HMAC-SHA-256 (see RFC 2104).</li>
      * </ul>
      *
+     * <p>The exact way these primitives are combined to derive the session key is specified in
+     * section 9.2.1.4 of ISO/IEC 18013-5 (see description of cipher suite '1').<p>
+     *
      * <p>
      * At present this is the only supported cipher suite.
      */
@@ -135,9 +139,20 @@
     /**
      * Creates a new credential.
      *
+     * <p>When a credential is created, a cryptographic key-pair - CredentialKey - is created which
+     * is used to authenticate the store to the Issuing Authority.  The private part of this
+     * key-pair never leaves secure hardware and the public part can be obtained using
+     * {@link WritableIdentityCredential#getCredentialKeyCertificateChain(byte[])} on the
+     * returned object.
+     *
+     * <p>In addition, all of the Credential data content is imported and a certificate for the
+     * CredentialKey and a signature produced with the CredentialKey are created.  These latter
+     * values may be checked by an issuing authority to verify that the data was imported into
+     * secure hardware and that it was imported unmodified.
+     *
      * @param credentialName The name used to identify the credential.
      * @param docType        The document type for the credential.
-     * @return A @{link WritableIdentityCredential} that can be used to create a new credential.
+     * @return A {@link WritableIdentityCredential} that can be used to create a new credential.
      * @throws AlreadyPersonalizedException if a credential with the given name already exists.
      * @throws DocTypeNotSupportedException if the given document type isn't supported by the store.
      */
@@ -148,6 +163,10 @@
     /**
      * Retrieve a named credential.
      *
+     * <p>The cipher suite used to communicate with the remote verifier must also be specified.
+     * Currently only a single cipher-suite is supported. Support for other cipher suites may be
+     * added in a future version of this API.
+     *
      * @param credentialName the name of the credential to retrieve.
      * @param cipherSuite    the cipher suite to use for communicating with the verifier.
      * @return The named credential, or null if not found.
diff --git a/identity/java/android/security/identity/ResultData.java b/identity/java/android/security/identity/ResultData.java
index 13552d6..37de2c4 100644
--- a/identity/java/android/security/identity/ResultData.java
+++ b/identity/java/android/security/identity/ResultData.java
@@ -34,23 +34,23 @@
     /** Value was successfully retrieved. */
     public static final int STATUS_OK = 0;
 
-    /** Requested entry does not exist. */
+    /** The entry does not exist. */
     public static final int STATUS_NO_SUCH_ENTRY = 1;
 
-    /** Requested entry was not requested. */
+    /** The entry was not requested. */
     public static final int STATUS_NOT_REQUESTED = 2;
 
-    /** Requested entry wasn't in the request message. */
+    /** The entry wasn't in the request message. */
     public static final int STATUS_NOT_IN_REQUEST_MESSAGE = 3;
 
-    /** The requested entry was not retrieved because user authentication wasn't performed. */
+    /** The entry was not retrieved because user authentication failed. */
     public static final int STATUS_USER_AUTHENTICATION_FAILED = 4;
 
-    /** The requested entry was not retrieved because reader authentication wasn't performed. */
+    /** The entry was not retrieved because reader authentication failed. */
     public static final int STATUS_READER_AUTHENTICATION_FAILED = 5;
 
     /**
-     * The requested entry was not retrieved because it was configured without any access
+     * The entry was not retrieved because it was configured without any access
      * control profile.
      */
     public static final int STATUS_NO_ACCESS_CONTROL_PROFILES = 6;
@@ -88,11 +88,10 @@
      *
      *   DeviceEngagementBytes = #6.24(bstr .cbor DeviceEngagement)
      *   EReaderKeyBytes = #6.24(bstr .cbor EReaderKey.Pub)
-     *
      *   DeviceNameSpacesBytes = #6.24(bstr .cbor DeviceNameSpaces)
      * </pre>
      *
-     * where
+     * <p>where
      *
      * <pre>
      *   DeviceNameSpaces = {
@@ -116,15 +115,16 @@
     public abstract @NonNull byte[] getAuthenticatedData();
 
     /**
-     * Returns a message authentication code over the data returned by
-     * {@link #getAuthenticatedData}, to prove to the reader that the data is from a trusted
-     * credential.
+     * Returns a message authentication code over the {@code DeviceAuthentication} CBOR
+     * specified in {@link #getAuthenticatedData()}, to prove to the reader that the data
+     * is from a trusted credential.
      *
      * <p>The MAC proves to the reader that the data is from a trusted credential. This code is
      * produced by using the key agreement and key derivation function from the ciphersuite
      * with the authentication private key and the reader ephemeral public key to compute a
      * shared message authentication code (MAC) key, then using the MAC function from the
-     * ciphersuite to compute a MAC of the authenticated data.
+     * ciphersuite to compute a MAC of the authenticated data. See section 9.2.3.5 of
+     * ISO/IEC 18013-5 for details of this operation.
      *
      * <p>If the {@code sessionTranscript} parameter passed to
      * {@link IdentityCredential#getEntries(byte[], Map, byte[], byte[])} was {@code null}
@@ -157,7 +157,7 @@
     /**
      * Get the names of all entries.
      *
-     * This includes the name of entries that wasn't successfully retrieved.
+     * <p>This includes the name of entries that wasn't successfully retrieved.
      *
      * @param namespaceName the namespace name to get entries for.
      * @return A collection of names or {@code null} if there are no entries for the given
@@ -168,7 +168,7 @@
     /**
      * Get the names of all entries that was successfully retrieved.
      *
-     * This only return entries for which {@link #getStatus(String, String)} will return
+     * <p>This only return entries for which {@link #getStatus(String, String)} will return
      * {@link #STATUS_OK}.
      *
      * @param namespaceName the namespace name to get entries for.
@@ -181,16 +181,15 @@
     /**
      * Gets the status of an entry.
      *
-     * This returns {@link #STATUS_OK} if the value was retrieved, {@link #STATUS_NO_SUCH_ENTRY}
+     * <p>This returns {@link #STATUS_OK} if the value was retrieved, {@link #STATUS_NO_SUCH_ENTRY}
      * if the given entry wasn't retrieved, {@link #STATUS_NOT_REQUESTED} if it wasn't requested,
      * {@link #STATUS_NOT_IN_REQUEST_MESSAGE} if the request message was set but the entry wasn't
-     * present in the request message,
-     * {@link #STATUS_USER_AUTHENTICATION_FAILED} if the value
+     * present in the request message, {@link #STATUS_USER_AUTHENTICATION_FAILED} if the value
      * wasn't retrieved because the necessary user authentication wasn't performed,
-     * {@link #STATUS_READER_AUTHENTICATION_FAILED} if the supplied reader certificate chain
-     * didn't match the set of certificates the entry was provisioned with, or
-     * {@link #STATUS_NO_ACCESS_CONTROL_PROFILES} if the entry was configured without any
-     * access control profiles.
+     * {@link #STATUS_READER_AUTHENTICATION_FAILED} if the supplied reader certificate chain didn't
+     * match the set of certificates the entry was provisioned with, or
+     * {@link #STATUS_NO_ACCESS_CONTROL_PROFILES} if the entry was configured without any access
+     * control profiles.
      *
      * @param namespaceName the namespace name of the entry.
      * @param name the name of the entry to get the value for.
@@ -201,7 +200,7 @@
     /**
      * Gets the raw CBOR data for the value of an entry.
      *
-     * This should only be called on an entry for which the {@link #getStatus(String, String)}
+     * <p>This should only be called on an entry for which the {@link #getStatus(String, String)}
      * method returns {@link #STATUS_OK}.
      *
      * @param namespaceName the namespace name of the entry.
diff --git a/identity/java/android/security/identity/WritableIdentityCredential.java b/identity/java/android/security/identity/WritableIdentityCredential.java
index e2a389b..c7aa328 100644
--- a/identity/java/android/security/identity/WritableIdentityCredential.java
+++ b/identity/java/android/security/identity/WritableIdentityCredential.java
@@ -41,15 +41,16 @@
      * <a href="https://source.android.com/security/keystore/attestation">Android Keystore</a>
      * attestation extension which describes the key and the security hardware in which it lives.
      *
-     * <p>Additionally, the attestation extension will contain the tag TODO_IC_KEY which indicates
-     * it is an Identity Credential key (which can only sign/MAC very specific messages) and not
-     * an Android Keystore key (which can be used to sign/MAC anything).
+     * <p>Additionally, the attestation extension will contain the tag Tag::IDENTITY_CREDENTIAL_KEY
+     * which indicates it is an Identity Credential key (which can only sign/MAC very specific
+     * messages) and not an Android Keystore key (which can be used to sign/MAC anything).
      *
      * <p>The issuer <b>MUST</b> carefully examine this certificate chain including (but not
-     * limited to) checking that the root certificate is well-known, the tag TODO_IC_KEY is
-     * present, the passed in challenge is present, the device has verified boot enabled, that each
-     * certificate in the chain is signed by its successor, that none of the certificates have been
-     * revoked and so on.
+     * limited to) checking that the root certificate is well-known, the tag
+     * Tag::IDENTITY_CREDENTIAL_KEY present, the passed in challenge is present, the tag
+     * Tag::ATTESTATION_APPLICATION_ID is set to the expected Android application, the device
+     * has verified boot enabled, each certificate in the chain is signed by its successor,
+     * none of the certificates have been revoked, and so on.
      *
      * <p>It is not strictly necessary to use this method to provision a credential if the issuing
      * authority doesn't care about the nature of the security hardware. If called, however, this
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 3e1f72d..8ea6883 100755
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -2713,6 +2713,32 @@
     }
 
     /**
+     * @hide
+     */
+    public static String audioFocusToString(int focus) {
+        switch (focus) {
+            case AUDIOFOCUS_NONE:
+                return "AUDIOFOCUS_NONE";
+            case AUDIOFOCUS_GAIN:
+                return "AUDIOFOCUS_GAIN";
+            case AUDIOFOCUS_GAIN_TRANSIENT:
+                return "AUDIOFOCUS_GAIN_TRANSIENT";
+            case AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:
+                return "AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK";
+            case AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE:
+                return "AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE";
+            case AUDIOFOCUS_LOSS:
+                return "AUDIOFOCUS_LOSS";
+            case AUDIOFOCUS_LOSS_TRANSIENT:
+                return "AUDIOFOCUS_LOSS_TRANSIENT";
+            case AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: // Note CAN_DUCK not MAY_DUCK.
+                return "AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK";
+            default:
+                return "AUDIO_FOCUS_UNKNOWN(" + focus + ")";
+        }
+    }
+
+    /**
      * Used to indicate no audio focus has been gained or lost, or requested.
      */
     public static final int AUDIOFOCUS_NONE = 0;
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index c11762b..373f6e1 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -214,6 +214,175 @@
         }
     }
 
+    /**
+     * @hide
+     * Convert a native audio format integer constant to a string.
+     */
+    public static String audioFormatToString(int audioFormat) {
+        switch (audioFormat) {
+            case /* AUDIO_FORMAT_INVALID         */ 0xFFFFFFFF:
+                return "AUDIO_FORMAT_INVALID";
+            case /* AUDIO_FORMAT_DEFAULT         */ 0:
+                return "AUDIO_FORMAT_DEFAULT";
+            case /* AUDIO_FORMAT_MP3             */ 0x01000000:
+                return "AUDIO_FORMAT_MP3";
+            case /* AUDIO_FORMAT_AMR_NB          */ 0x02000000:
+                return "AUDIO_FORMAT_AMR_NB";
+            case /* AUDIO_FORMAT_AMR_WB          */ 0x03000000:
+                return "AUDIO_FORMAT_AMR_WB";
+            case /* AUDIO_FORMAT_AAC             */ 0x04000000:
+                return "AUDIO_FORMAT_AAC";
+            case /* AUDIO_FORMAT_HE_AAC_V1       */ 0x05000000:
+                return "AUDIO_FORMAT_HE_AAC_V1";
+            case /* AUDIO_FORMAT_HE_AAC_V2       */ 0x06000000:
+                return "AUDIO_FORMAT_HE_AAC_V2";
+            case /* AUDIO_FORMAT_VORBIS          */ 0x07000000:
+                return "AUDIO_FORMAT_VORBIS";
+            case /* AUDIO_FORMAT_OPUS            */ 0x08000000:
+                return "AUDIO_FORMAT_OPUS";
+            case /* AUDIO_FORMAT_AC3             */ 0x09000000:
+                return "AUDIO_FORMAT_AC3";
+            case /* AUDIO_FORMAT_E_AC3           */ 0x0A000000:
+                return "AUDIO_FORMAT_E_AC3";
+            case /* AUDIO_FORMAT_DTS             */ 0x0B000000:
+                return "AUDIO_FORMAT_DTS";
+            case /* AUDIO_FORMAT_DTS_HD          */ 0x0C000000:
+                return "AUDIO_FORMAT_DTS_HD";
+            case /* AUDIO_FORMAT_IEC61937        */ 0x0D000000:
+                return "AUDIO_FORMAT_IEC61937";
+            case /* AUDIO_FORMAT_DOLBY_TRUEHD    */ 0x0E000000:
+                return "AUDIO_FORMAT_DOLBY_TRUEHD";
+            case /* AUDIO_FORMAT_EVRC            */ 0x10000000:
+                return "AUDIO_FORMAT_EVRC";
+            case /* AUDIO_FORMAT_EVRCB           */ 0x11000000:
+                return "AUDIO_FORMAT_EVRCB";
+            case /* AUDIO_FORMAT_EVRCWB          */ 0x12000000:
+                return "AUDIO_FORMAT_EVRCWB";
+            case /* AUDIO_FORMAT_EVRCNW          */ 0x13000000:
+                return "AUDIO_FORMAT_EVRCNW";
+            case /* AUDIO_FORMAT_AAC_ADIF        */ 0x14000000:
+                return "AUDIO_FORMAT_AAC_ADIF";
+            case /* AUDIO_FORMAT_WMA             */ 0x15000000:
+                return "AUDIO_FORMAT_WMA";
+            case /* AUDIO_FORMAT_WMA_PRO         */ 0x16000000:
+                return "AUDIO_FORMAT_WMA_PRO";
+            case /* AUDIO_FORMAT_AMR_WB_PLUS     */ 0x17000000:
+                return "AUDIO_FORMAT_AMR_WB_PLUS";
+            case /* AUDIO_FORMAT_MP2             */ 0x18000000:
+                return "AUDIO_FORMAT_MP2";
+            case /* AUDIO_FORMAT_QCELP           */ 0x19000000:
+                return "AUDIO_FORMAT_QCELP";
+            case /* AUDIO_FORMAT_DSD             */ 0x1A000000:
+                return "AUDIO_FORMAT_DSD";
+            case /* AUDIO_FORMAT_FLAC            */ 0x1B000000:
+                return "AUDIO_FORMAT_FLAC";
+            case /* AUDIO_FORMAT_ALAC            */ 0x1C000000:
+                return "AUDIO_FORMAT_ALAC";
+            case /* AUDIO_FORMAT_APE             */ 0x1D000000:
+                return "AUDIO_FORMAT_APE";
+            case /* AUDIO_FORMAT_AAC_ADTS        */ 0x1E000000:
+                return "AUDIO_FORMAT_AAC_ADTS";
+            case /* AUDIO_FORMAT_SBC             */ 0x1F000000:
+                return "AUDIO_FORMAT_SBC";
+            case /* AUDIO_FORMAT_APTX            */ 0x20000000:
+                return "AUDIO_FORMAT_APTX";
+            case /* AUDIO_FORMAT_APTX_HD         */ 0x21000000:
+                return "AUDIO_FORMAT_APTX_HD";
+            case /* AUDIO_FORMAT_AC4             */ 0x22000000:
+                return "AUDIO_FORMAT_AC4";
+            case /* AUDIO_FORMAT_LDAC            */ 0x23000000:
+                return "AUDIO_FORMAT_LDAC";
+            case /* AUDIO_FORMAT_MAT             */ 0x24000000:
+                return "AUDIO_FORMAT_MAT";
+            case /* AUDIO_FORMAT_AAC_LATM        */ 0x25000000:
+                return "AUDIO_FORMAT_AAC_LATM";
+            case /* AUDIO_FORMAT_CELT            */ 0x26000000:
+                return "AUDIO_FORMAT_CELT";
+            case /* AUDIO_FORMAT_APTX_ADAPTIVE   */ 0x27000000:
+                return "AUDIO_FORMAT_APTX_ADAPTIVE";
+            case /* AUDIO_FORMAT_LHDC            */ 0x28000000:
+                return "AUDIO_FORMAT_LHDC";
+            case /* AUDIO_FORMAT_LHDC_LL         */ 0x29000000:
+                return "AUDIO_FORMAT_LHDC_LL";
+            case /* AUDIO_FORMAT_APTX_TWSP       */ 0x2A000000:
+                return "AUDIO_FORMAT_APTX_TWSP";
+
+            /* Aliases */
+            case /* AUDIO_FORMAT_PCM_16_BIT        */ 0x1:
+                return "AUDIO_FORMAT_PCM_16_BIT";        // (PCM | PCM_SUB_16_BIT)
+            case /* AUDIO_FORMAT_PCM_8_BIT         */ 0x2:
+                return "AUDIO_FORMAT_PCM_8_BIT";        // (PCM | PCM_SUB_8_BIT)
+            case /* AUDIO_FORMAT_PCM_32_BIT        */ 0x3:
+                return "AUDIO_FORMAT_PCM_32_BIT";        // (PCM | PCM_SUB_32_BIT)
+            case /* AUDIO_FORMAT_PCM_8_24_BIT      */ 0x4:
+                return "AUDIO_FORMAT_PCM_8_24_BIT";        // (PCM | PCM_SUB_8_24_BIT)
+            case /* AUDIO_FORMAT_PCM_FLOAT         */ 0x5:
+                return "AUDIO_FORMAT_PCM_FLOAT";        // (PCM | PCM_SUB_FLOAT)
+            case /* AUDIO_FORMAT_PCM_24_BIT_PACKED */ 0x6:
+                return "AUDIO_FORMAT_PCM_24_BIT_PACKED";        // (PCM | PCM_SUB_24_BIT_PACKED)
+            case /* AUDIO_FORMAT_AAC_MAIN          */ 0x4000001:
+                return "AUDIO_FORMAT_AAC_MAIN";  // (AAC | AAC_SUB_MAIN)
+            case /* AUDIO_FORMAT_AAC_LC            */ 0x4000002:
+                return "AUDIO_FORMAT_AAC_LC";  // (AAC | AAC_SUB_LC)
+            case /* AUDIO_FORMAT_AAC_SSR           */ 0x4000004:
+                return "AUDIO_FORMAT_AAC_SSR";  // (AAC | AAC_SUB_SSR)
+            case /* AUDIO_FORMAT_AAC_LTP           */ 0x4000008:
+                return "AUDIO_FORMAT_AAC_LTP";  // (AAC | AAC_SUB_LTP)
+            case /* AUDIO_FORMAT_AAC_HE_V1         */ 0x4000010:
+                return "AUDIO_FORMAT_AAC_HE_V1";  // (AAC | AAC_SUB_HE_V1)
+            case /* AUDIO_FORMAT_AAC_SCALABLE      */ 0x4000020:
+                return "AUDIO_FORMAT_AAC_SCALABLE";  // (AAC | AAC_SUB_SCALABLE)
+            case /* AUDIO_FORMAT_AAC_ERLC          */ 0x4000040:
+                return "AUDIO_FORMAT_AAC_ERLC";  // (AAC | AAC_SUB_ERLC)
+            case /* AUDIO_FORMAT_AAC_LD            */ 0x4000080:
+                return "AUDIO_FORMAT_AAC_LD";  // (AAC | AAC_SUB_LD)
+            case /* AUDIO_FORMAT_AAC_HE_V2         */ 0x4000100:
+                return "AUDIO_FORMAT_AAC_HE_V2";  // (AAC | AAC_SUB_HE_V2)
+            case /* AUDIO_FORMAT_AAC_ELD           */ 0x4000200:
+                return "AUDIO_FORMAT_AAC_ELD";  // (AAC | AAC_SUB_ELD)
+            case /* AUDIO_FORMAT_AAC_XHE           */ 0x4000300:
+                return "AUDIO_FORMAT_AAC_XHE";  // (AAC | AAC_SUB_XHE)
+            case /* AUDIO_FORMAT_AAC_ADTS_MAIN     */ 0x1e000001:
+                return "AUDIO_FORMAT_AAC_ADTS_MAIN"; // (AAC_ADTS | AAC_SUB_MAIN)
+            case /* AUDIO_FORMAT_AAC_ADTS_LC       */ 0x1e000002:
+                return "AUDIO_FORMAT_AAC_ADTS_LC"; // (AAC_ADTS | AAC_SUB_LC)
+            case /* AUDIO_FORMAT_AAC_ADTS_SSR      */ 0x1e000004:
+                return "AUDIO_FORMAT_AAC_ADTS_SSR"; // (AAC_ADTS | AAC_SUB_SSR)
+            case /* AUDIO_FORMAT_AAC_ADTS_LTP      */ 0x1e000008:
+                return "AUDIO_FORMAT_AAC_ADTS_LTP"; // (AAC_ADTS | AAC_SUB_LTP)
+            case /* AUDIO_FORMAT_AAC_ADTS_HE_V1    */ 0x1e000010:
+                return "AUDIO_FORMAT_AAC_ADTS_HE_V1"; // (AAC_ADTS | AAC_SUB_HE_V1)
+            case /* AUDIO_FORMAT_AAC_ADTS_SCALABLE */ 0x1e000020:
+                return "AUDIO_FORMAT_AAC_ADTS_SCALABLE"; // (AAC_ADTS | AAC_SUB_SCALABLE)
+            case /* AUDIO_FORMAT_AAC_ADTS_ERLC     */ 0x1e000040:
+                return "AUDIO_FORMAT_AAC_ADTS_ERLC"; // (AAC_ADTS | AAC_SUB_ERLC)
+            case /* AUDIO_FORMAT_AAC_ADTS_LD       */ 0x1e000080:
+                return "AUDIO_FORMAT_AAC_ADTS_LD"; // (AAC_ADTS | AAC_SUB_LD)
+            case /* AUDIO_FORMAT_AAC_ADTS_HE_V2    */ 0x1e000100:
+                return "AUDIO_FORMAT_AAC_ADTS_HE_V2"; // (AAC_ADTS | AAC_SUB_HE_V2)
+            case /* AUDIO_FORMAT_AAC_ADTS_ELD      */ 0x1e000200:
+                return "AUDIO_FORMAT_AAC_ADTS_ELD"; // (AAC_ADTS | AAC_SUB_ELD)
+            case /* AUDIO_FORMAT_AAC_ADTS_XHE      */ 0x1e000300:
+                return "AUDIO_FORMAT_AAC_ADTS_XHE"; // (AAC_ADTS | AAC_SUB_XHE)
+            case /* AUDIO_FORMAT_AAC_LATM_LC       */ 0x25000002:
+                return "AUDIO_FORMAT_AAC_LATM_LC"; // (AAC_LATM | AAC_SUB_LC)
+            case /* AUDIO_FORMAT_AAC_LATM_HE_V1    */ 0x25000010:
+                return "AUDIO_FORMAT_AAC_LATM_HE_V1"; // (AAC_LATM | AAC_SUB_HE_V1)
+            case /* AUDIO_FORMAT_AAC_LATM_HE_V2    */ 0x25000100:
+                return "AUDIO_FORMAT_AAC_LATM_HE_V2"; // (AAC_LATM | AAC_SUB_HE_V2)
+            case /* AUDIO_FORMAT_E_AC3_JOC         */ 0xA000001:
+                return "AUDIO_FORMAT_E_AC3_JOC";  // (E_AC3 | E_AC3_SUB_JOC)
+            case /* AUDIO_FORMAT_MAT_1_0           */ 0x24000001:
+                return "AUDIO_FORMAT_MAT_1_0"; // (MAT | MAT_SUB_1_0)
+            case /* AUDIO_FORMAT_MAT_2_0           */ 0x24000002:
+                return "AUDIO_FORMAT_MAT_2_0"; // (MAT | MAT_SUB_2_0)
+            case /* AUDIO_FORMAT_MAT_2_1           */ 0x24000003:
+                return "AUDIO_FORMAT_MAT_2_1"; // (MAT | MAT_SUB_2_1)
+            default:
+                return "AUDIO_FORMAT_(" + audioFormat + ")";
+        }
+    }
+
     /* Routing bits for the former setRouting/getRouting API */
     /** @hide @deprecated */
     @Deprecated public static final int ROUTE_EARPIECE          = (1 << 0);
diff --git a/media/java/android/media/MediaCas.java b/media/java/android/media/MediaCas.java
index 97d32d8..c652628 100644
--- a/media/java/android/media/MediaCas.java
+++ b/media/java/android/media/MediaCas.java
@@ -388,7 +388,7 @@
             @Override
             public void onReclaimResources() {
                 synchronized (mSessionMap) {
-                    mSessionMap.forEach((casSession, sessionResourceId) -> casSession.close());
+                    mSessionMap.forEach((casSession, sessionResourceHandle) -> casSession.close());
                 }
                 mEventHandler.sendMessage(mEventHandler.obtainMessage(
                         EventHandler.MSG_CAS_RESOURCE_LOST));
@@ -868,7 +868,7 @@
         }
     }
 
-    private int getSessionResourceId() throws MediaCasException {
+    private int getSessionResourceHandle() throws MediaCasException {
         validateInternalStates();
 
         int[] sessionResourceHandle = new int[1];
@@ -881,14 +881,14 @@
                     "insufficient resource to Open Session");
             }
         }
-        return  sessionResourceHandle[0];
+        return sessionResourceHandle[0];
     }
 
-    private void addSessionToResourceMap(Session session, int sessionResourceId) {
+    private void addSessionToResourceMap(Session session, int sessionResourceHandle) {
 
-        if (sessionResourceId != -1) {
+        if (sessionResourceHandle != TunerResourceManager.INVALID_RESOURCE_HANDLE) {
             synchronized (mSessionMap) {
-                mSessionMap.put(session, sessionResourceId);
+                mSessionMap.put(session, sessionResourceHandle);
             }
         }
     }
@@ -918,13 +918,13 @@
      * @throws MediaCasStateException for CAS-specific state exceptions.
      */
     public Session openSession() throws MediaCasException {
-        int sessionResourceId = getSessionResourceId();
+        int sessionResourceHandle = getSessionResourceHandle();
 
         try {
             OpenSessionCallback cb = new OpenSessionCallback();
             mICas.openSession(cb);
             MediaCasException.throwExceptionIfNeeded(cb.mStatus);
-            addSessionToResourceMap(cb.mSession, sessionResourceId);
+            addSessionToResourceMap(cb.mSession, sessionResourceHandle);
             return cb.mSession;
         } catch (RemoteException e) {
             cleanupAndRethrowIllegalState();
@@ -952,7 +952,7 @@
     @Nullable
     public Session openSession(@SessionUsage int sessionUsage, @ScramblingMode int scramblingMode)
             throws MediaCasException {
-        int sessionResourceId = getSessionResourceId();
+        int sessionResourceHandle = getSessionResourceHandle();
 
         if (mICasV12 == null) {
             Log.d(TAG, "Open Session with scrambling mode is only supported by cas@1.2+ interface");
@@ -963,7 +963,7 @@
             OpenSession_1_2_Callback cb = new OpenSession_1_2_Callback();
             mICasV12.openSession_1_2(sessionUsage, scramblingMode, cb);
             MediaCasException.throwExceptionIfNeeded(cb.mStatus);
-            addSessionToResourceMap(cb.mSession, sessionResourceId);
+            addSessionToResourceMap(cb.mSession, sessionResourceHandle);
             return cb.mSession;
         } catch (RemoteException e) {
             cleanupAndRethrowIllegalState();
diff --git a/media/java/android/media/MediaMetrics.java b/media/java/android/media/MediaMetrics.java
index 540955f..f6f482d 100644
--- a/media/java/android/media/MediaMetrics.java
+++ b/media/java/android/media/MediaMetrics.java
@@ -38,6 +38,117 @@
 public class MediaMetrics {
     public static final String TAG = "MediaMetrics";
 
+    public static final String SEPARATOR = ".";
+
+    /**
+     * A list of established MediaMetrics names that can be used for Items.
+     */
+    public static class Name {
+        public static final String AUDIO = "audio";
+        public static final String AUDIO_BLUETOOTH = AUDIO + SEPARATOR + "bluetooth";
+        public static final String AUDIO_DEVICE = AUDIO + SEPARATOR + "device";
+        public static final String AUDIO_FOCUS = AUDIO + SEPARATOR + "focus";
+        public static final String AUDIO_FORCE_USE = AUDIO + SEPARATOR + "forceUse";
+        public static final String AUDIO_MIC = AUDIO + SEPARATOR + "mic";
+        public static final String AUDIO_SERVICE = AUDIO + SEPARATOR + "service";
+        public static final String AUDIO_VOLUME = AUDIO + SEPARATOR + "volume";
+        public static final String AUDIO_VOLUME_EVENT = AUDIO_VOLUME + SEPARATOR + "event";
+    }
+
+    /**
+     * A list of established string values.
+     */
+    public static class Value {
+        public static final String CONNECT = "connect";
+        public static final String CONNECTED = "connected";
+        public static final String DISCONNECT = "disconnect";
+        public static final String DISCONNECTED = "disconnected";
+        public static final String DOWN = "down";
+        public static final String MUTE = "mute";
+        public static final String NO = "no";
+        public static final String OFF = "off";
+        public static final String ON = "on";
+        public static final String UNMUTE = "unmute";
+        public static final String UP = "up";
+        public static final String YES = "yes";
+    }
+
+    /**
+     * A list of standard property keys for consistent use and type.
+     */
+    public static class Property {
+        // A use for Bluetooth or USB device addresses
+        public static final Key<String> ADDRESS = createKey("address", String.class);
+        // A string representing the Audio Attributes
+        public static final Key<String> ATTRIBUTES = createKey("attributes", String.class);
+
+        // The calling package responsible for the state change
+        public static final Key<String> CALLING_PACKAGE =
+                createKey("callingPackage", String.class);
+
+        // The client name
+        public static final Key<String> CLIENT_NAME = createKey("clientName", String.class);
+
+        // The device type
+        public static final Key<Integer> DELAY_MS = createKey("delayMs", Integer.class);
+
+        // The device type
+        public static final Key<String> DEVICE = createKey("device", String.class);
+
+        // For volume changes, up or down
+        public static final Key<String> DIRECTION = createKey("direction", String.class);
+
+        // A reason for early return or error
+        public static final Key<String> EARLY_RETURN =
+                createKey("earlyReturn", String.class);
+        // ENCODING_ ... string to match AudioFormat encoding
+        public static final Key<String> ENCODING = createKey("encoding", String.class);
+
+        public static final Key<String> EVENT = createKey("event#", String.class);
+
+        // event generated is external (yes, no)
+        public static final Key<String> EXTERNAL = createKey("external", String.class);
+
+        public static final Key<Integer> FLAGS = createKey("flags", Integer.class);
+        public static final Key<String> FOCUS_CHANGE_HINT =
+                createKey("focusChangeHint", String.class);
+        public static final Key<String> FORCE_USE_DUE_TO =
+                createKey("forceUseDueTo", String.class);
+        public static final Key<String> FORCE_USE_MODE =
+                createKey("forceUseMode", String.class);
+        public static final Key<Double> GAIN_DB =
+                createKey("gainDb", Double.class);
+        public static final Key<String> GROUP =
+                createKey("group", String.class);
+        // For volume
+        public static final Key<Integer> INDEX = createKey("index", Integer.class);
+        public static final Key<Integer> MAX_INDEX = createKey("maxIndex", Integer.class);
+        public static final Key<Integer> MIN_INDEX = createKey("minIndex", Integer.class);
+        public static final Key<String> MODE =
+                createKey("mode", String.class); // audio_mode
+        public static final Key<String> MUTE =
+                createKey("mute", String.class); // microphone, on or off.
+
+        // Bluetooth or Usb device name
+        public static final Key<String> NAME =
+                createKey("name", String.class);
+
+        // Number of observers
+        public static final Key<Integer> OBSERVERS =
+                createKey("observers", Integer.class);
+
+        public static final Key<String> REQUEST =
+                createKey("request", String.class);
+
+        // For Bluetooth
+        public static final Key<String> SCO_AUDIO_MODE =
+                createKey("scoAudioMode", String.class);
+        public static final Key<Integer> SDK = createKey("sdk", Integer.class);
+        public static final Key<String> STATE = createKey("state", String.class);
+        public static final Key<Integer> STATUS = createKey("status", Integer.class);
+        public static final Key<String> STREAM_TYPE = createKey("streamType", String.class);
+    }
+
     /**
      * The TYPE constants below should match those in native MediaMetricsItem.h
      */
diff --git a/media/java/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl b/media/java/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl
index 7077cd1..487b444 100644
--- a/media/java/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl
+++ b/media/java/android/media/tv/tunerresourcemanager/ITunerResourceManager.aidl
@@ -218,11 +218,11 @@
      * <p><strong>Note:</strong> {@link #updateCasInfo(int, int)} must be called before this request.
      *
      * @param request {@link CasSessionRequest} information of the current request.
-     * @param sessionResourceId a one-element array to return the granted cas session id.
+     * @param casSessionHandle a one-element array to return the granted cas session handle.
      *
      * @return true if there is CAS session granted.
      */
-    boolean requestCasSession(in CasSessionRequest request, out int[] sessionResourceId);
+    boolean requestCasSession(in CasSessionRequest request, out int[] casSessionHandle);
 
     /*
      * This API is used by the Tuner framework to request an available Lnb from the TunerHAL.
@@ -276,7 +276,7 @@
      *
      * <p>Client must call this whenever it releases a descrambler.
      *
-     * @param demuxHandle the handle of the released Tuner Descrambler.
+     * @param descramblerHandle the handle of the released Tuner Descrambler.
      * @param clientId the id of the client that is releasing the descrambler.
      */
     void releaseDescrambler(in int descramblerHandle, int clientId);
@@ -288,10 +288,10 @@
      *
      * <p><strong>Note:</strong> {@link #updateCasInfo(int, int)} must be called before this release.
      *
-     * @param sessionResourceId the id of the released CAS session.
+     * @param casSessionHandle the handle of the released CAS session.
      * @param clientId the id of the client that is releasing the cas session.
      */
-    void releaseCasSession(in int sessionResourceId, int clientId);
+    void releaseCasSession(in int casSessionHandle, int clientId);
 
     /*
      * Notifies the TRM that the Lnb with the given handle was released.
diff --git a/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java b/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java
index b4dcc5d..be102d8 100644
--- a/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java
+++ b/media/java/android/media/tv/tunerresourcemanager/TunerResourceManager.java
@@ -362,17 +362,16 @@
      * request.
      *
      * @param request {@link CasSessionRequest} information of the current request.
-     * @param sessionResourceId a one-element array to return the granted cas session id.
-     *                          If no CAS granted, this will return
-     *                          {@link #INVALID_CAS_SESSION_RESOURCE_ID}.
+     * @param casSessionHandle a one-element array to return the granted cas session handel.
+     *                         If no CAS granted, this will return {@link #INVALID_RESOURCE_HANDLE}.
      *
      * @return true if there is CAS session granted.
      */
     public boolean requestCasSession(@NonNull CasSessionRequest request,
-                @NonNull int[] sessionResourceId) {
+                @NonNull int[] casSessionHandle) {
         boolean result = false;
         try {
-            result = mService.requestCasSession(request, sessionResourceId);
+            result = mService.requestCasSession(request, casSessionHandle);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -471,12 +470,12 @@
      * <p><strong>Note:</strong> {@link #updateCasInfo(int, int)} must be called before this
      * release.
      *
-     * @param sessionResourceId the id of the released CAS session.
+     * @param casSessionHandle the handle of the released CAS session.
      * @param clientId the id of the client that is releasing the cas session.
      */
-    public void releaseCasSession(int sessionResourceId, int clientId) {
+    public void releaseCasSession(int casSessionHandle, int clientId) {
         try {
-            mService.releaseCasSession(sessionResourceId, clientId);
+            mService.releaseCasSession(casSessionHandle, clientId);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/media/java/android/mtp/MtpDatabase.java b/media/java/android/mtp/MtpDatabase.java
index e6962a1..aba74e5 100755
--- a/media/java/android/mtp/MtpDatabase.java
+++ b/media/java/android/mtp/MtpDatabase.java
@@ -46,6 +46,7 @@
 import android.view.WindowManager;
 
 import com.android.internal.annotations.VisibleForNative;
+import com.android.internal.annotations.VisibleForTesting;
 
 import dalvik.system.CloseGuard;
 
@@ -408,7 +409,8 @@
     }
 
     @VisibleForNative
-    private int beginSendObject(String path, int format, int parent, int storageId) {
+    @VisibleForTesting
+    public int beginSendObject(String path, int format, int parent, int storageId) {
         MtpStorageManager.MtpObject parentObj =
                 parent == 0 ? mManager.getStorageRoot(storageId) : mManager.getObject(parent);
         if (parentObj == null) {
@@ -452,7 +454,8 @@
     }
 
     @VisibleForNative
-    private int getNumObjects(int storageID, int format, int parent) {
+    @VisibleForTesting
+    public int getNumObjects(int storageID, int format, int parent) {
         List<MtpStorageManager.MtpObject> objs = mManager.getObjects(parent,
                 format, storageID);
         if (objs == null) {
@@ -830,7 +833,8 @@
     }
 
     @VisibleForNative
-    private boolean getThumbnailInfo(int handle, long[] outLongs) {
+    @VisibleForTesting
+    public boolean getThumbnailInfo(int handle, long[] outLongs) {
         MtpStorageManager.MtpObject obj = mManager.getObject(handle);
         if (obj == null) {
             return false;
@@ -866,7 +870,8 @@
     }
 
     @VisibleForNative
-    private byte[] getThumbnailData(int handle) {
+    @VisibleForTesting
+    public byte[] getThumbnailData(int handle) {
         MtpStorageManager.MtpObject obj = mManager.getObject(handle);
         if (obj == null) {
             return null;
diff --git a/media/java/android/mtp/MtpStorage.java b/media/java/android/mtp/MtpStorage.java
index ba75263..88c32a3 100644
--- a/media/java/android/mtp/MtpStorage.java
+++ b/media/java/android/mtp/MtpStorage.java
@@ -36,7 +36,7 @@
 
     public MtpStorage(StorageVolume volume, int storageId) {
         mStorageId = storageId;
-        mPath = volume.getInternalPath();
+        mPath = volume.getPath();
         mDescription = volume.getDescription(null);
         mRemovable = volume.isRemovable();
         mMaxFileSize = volume.getMaxFileSize();
diff --git a/media/tests/MtpTests/res/raw/test_bad_thumb.jpg b/media/tests/MtpTests/res/raw/test_bad_thumb.jpg
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/media/tests/MtpTests/res/raw/test_bad_thumb.jpg
diff --git a/media/tests/MtpTests/src/android/mtp/MtpDatabaseTest.java b/media/tests/MtpTests/src/android/mtp/MtpDatabaseTest.java
new file mode 100644
index 0000000..e2e8ff4
--- /dev/null
+++ b/media/tests/MtpTests/src/android/mtp/MtpDatabaseTest.java
@@ -0,0 +1,316 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.mtp;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.os.Build;
+import android.os.FileUtils;
+import android.os.UserHandle;
+import android.os.storage.StorageManager;
+import android.os.storage.StorageVolume;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.internal.util.Preconditions;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * Tests for MtpDatabase functionality.
+ */
+@RunWith(AndroidJUnit4.class)
+public class MtpDatabaseTest {
+    private static final String TAG = MtpDatabaseTest.class.getSimpleName();
+
+    private final Context mContext = InstrumentationRegistry.getContext();
+
+    private static final File mBaseDir = InstrumentationRegistry.getContext().getExternalCacheDir();
+    private static final String MAIN_STORAGE_DIR = mBaseDir.getPath() + "/" + TAG + "/";
+    private static final String TEST_DIRNAME = "/TestIs";
+
+    private static final int MAIN_STORAGE_ID = 0x10001;
+    private static final int SCND_STORAGE_ID = 0x20001;
+    private static final String MAIN_STORAGE_ID_STR = Integer.toHexString(MAIN_STORAGE_ID);
+    private static final String SCND_STORAGE_ID_STR = Integer.toHexString(SCND_STORAGE_ID);
+
+    private static final File mMainStorageDir = new File(MAIN_STORAGE_DIR);
+
+    private static ServerHolder mServerHolder;
+    private MtpDatabase mMtpDatabase;
+
+    private static void logMethodName() {
+        Log.d(TAG, Thread.currentThread().getStackTrace()[3].getMethodName());
+    }
+
+    private static File createNewDir(File parent, String name) {
+        File ret = new File(parent, name);
+        if (!ret.mkdir())
+            throw new AssertionError(
+                    "Failed to create file: name=" + name + ", " + parent.getPath());
+        return ret;
+    }
+
+    private static void writeNewFile(File newFile) {
+        try {
+            new FileOutputStream(newFile).write(new byte[] {0, 0, 0});
+        } catch (IOException e) {
+            Assert.fail();
+        }
+    }
+
+    private static void writeNewFileFromByte(File newFile, byte[] byteData) {
+        try {
+            new FileOutputStream(newFile).write(byteData);
+        } catch (IOException e) {
+            Assert.fail();
+        }
+    }
+
+    private static class ServerHolder {
+        @NonNull final MtpServer server;
+        @NonNull final MtpDatabase database;
+
+        ServerHolder(@NonNull MtpServer server, @NonNull MtpDatabase database) {
+            Preconditions.checkNotNull(server);
+            Preconditions.checkNotNull(database);
+            this.server = server;
+            this.database = database;
+        }
+
+        void close() {
+            this.database.setServer(null);
+        }
+    }
+
+    private class OnServerTerminated implements Runnable {
+        @Override
+        public void run() {
+            if (mServerHolder == null) {
+                Log.e(TAG, "mServerHolder is unexpectedly null.");
+                return;
+            }
+            mServerHolder.close();
+            mServerHolder = null;
+        }
+    }
+
+    @Before
+    public void setUp() {
+        FileUtils.deleteContentsAndDir(mMainStorageDir);
+        Assert.assertTrue(mMainStorageDir.mkdir());
+
+        StorageVolume mainStorage = new StorageVolume(MAIN_STORAGE_ID_STR,
+                mMainStorageDir, mMainStorageDir, "Primary Storage",
+				true, false, true, false, -1, UserHandle.CURRENT, "", "");
+
+        final StorageVolume primary = mainStorage;
+
+        mMtpDatabase = new MtpDatabase(mContext, null);
+
+        final MtpServer server =
+                new MtpServer(mMtpDatabase, null, false,
+                        new OnServerTerminated(), Build.MANUFACTURER,
+                        Build.MODEL, "1.0");
+        mMtpDatabase.setServer(server);
+        mServerHolder = new ServerHolder(server, mMtpDatabase);
+
+        mMtpDatabase.addStorage(mainStorage);
+    }
+
+    @After
+    public void tearDown() {
+        FileUtils.deleteContentsAndDir(mMainStorageDir);
+    }
+
+    private File stageFile(int resId, File file) throws IOException {
+        try (InputStream source = mContext.getResources().openRawResource(resId);
+                OutputStream target = new FileOutputStream(file)) {
+            android.os.FileUtils.copy(source, target);
+        }
+        return file;
+    }
+
+    /**
+     * Refer to BitmapUtilTests, but keep here,
+	 * so as to be aware of the behavior or interface change there
+     */
+    private void assertBitmapSize(int expectedWidth, int expectedHeight, Bitmap bitmap) {
+        Assert.assertTrue(
+                "Abnormal bitmap.width: " + bitmap.getWidth(), bitmap.getWidth() >= expectedWidth);
+        Assert.assertTrue(
+                "Abnormal bitmap.height: " + bitmap.getHeight(),
+                bitmap.getHeight() >= expectedHeight);
+    }
+
+    private byte[] createJpegRawData(int sourceWidth, int sourceHeight) throws IOException {
+        return createRawData(Bitmap.CompressFormat.JPEG, sourceWidth, sourceHeight);
+    }
+
+    private byte[] createPngRawData(int sourceWidth, int sourceHeight) throws IOException {
+        return createRawData(Bitmap.CompressFormat.PNG, sourceWidth, sourceHeight);
+    }
+
+    private byte[] createRawData(Bitmap.CompressFormat format, int sourceWidth, int sourceHeight)
+            throws IOException {
+        // Create a temp bitmap as our source
+        Bitmap b = Bitmap.createBitmap(sourceWidth, sourceHeight, Bitmap.Config.ARGB_8888);
+        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+        b.compress(format, 50, outputStream);
+        final byte[] data = outputStream.toByteArray();
+        outputStream.close();
+        return data;
+    }
+
+    /**
+     * Decodes the bitmap with the given sample size
+     */
+    public static Bitmap decodeBitmapFromBytes(byte[] bytes, int sampleSize) {
+        final BitmapFactory.Options options;
+        if (sampleSize <= 1) {
+            options = null;
+        } else {
+            options = new BitmapFactory.Options();
+            options.inSampleSize = sampleSize;
+        }
+        return BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);
+    }
+
+    private void testThumbnail(int fileHandle, File imgFile, boolean isGoodThumb)
+            throws IOException {
+        boolean isValidThumb;
+        byte[] byteArray;
+        long[] outLongs = new long[3];
+
+        isValidThumb = mMtpDatabase.getThumbnailInfo(fileHandle, outLongs);
+        Assert.assertTrue(isValidThumb);
+
+        byteArray = mMtpDatabase.getThumbnailData(fileHandle);
+
+        if (isGoodThumb) {
+            Assert.assertNotNull("Fail to generate thumbnail:" + imgFile.getPath(), byteArray);
+
+            Bitmap testBitmap = decodeBitmapFromBytes(byteArray, 4);
+            assertBitmapSize(32, 16, testBitmap);
+        } else Assert.assertNull("Bad image should return null:" + imgFile.getPath(), byteArray);
+    }
+
+    @Test
+    @SmallTest
+    public void testMtpDatabaseThumbnail() throws IOException {
+        int baseHandle;
+        int handleJpgBadThumb, handleJpgNoThumb, handleJpgBad;
+        int handlePng1, handlePngBad;
+        final String baseTestDirStr = mMainStorageDir.getPath() + TEST_DIRNAME;
+
+        logMethodName();
+
+        Log.d(TAG, "testMtpDatabaseThumbnail: Generate and insert tested files.");
+
+        baseHandle = mMtpDatabase.beginSendObject(baseTestDirStr,
+                MtpConstants.FORMAT_ASSOCIATION, 0, MAIN_STORAGE_ID);
+
+        File baseDir = new File(baseTestDirStr);
+        baseDir.mkdirs();
+
+        final File jpgfileBadThumb = new File(baseDir, "jpgfileBadThumb.jpg");
+        final File jpgFileNoThumb = new File(baseDir, "jpgFileNoThumb.jpg");
+        final File jpgfileBad = new File(baseDir, "jpgfileBad.jpg");
+        final File pngFile1 = new File(baseDir, "pngFile1.png");
+        final File pngFileBad = new File(baseDir, "pngFileBad.png");
+
+        handleJpgBadThumb = mMtpDatabase.beginSendObject(jpgfileBadThumb.getPath(),
+                MtpConstants.FORMAT_EXIF_JPEG, baseHandle, MAIN_STORAGE_ID);
+        stageFile(R.raw.test_bad_thumb, jpgfileBadThumb);
+
+        handleJpgNoThumb = mMtpDatabase.beginSendObject(jpgFileNoThumb.getPath(),
+                MtpConstants.FORMAT_EXIF_JPEG, baseHandle, MAIN_STORAGE_ID);
+        writeNewFileFromByte(jpgFileNoThumb, createJpegRawData(128, 64));
+
+        handleJpgBad = mMtpDatabase.beginSendObject(jpgfileBad.getPath(),
+                MtpConstants.FORMAT_EXIF_JPEG, baseHandle, MAIN_STORAGE_ID);
+        writeNewFile(jpgfileBad);
+
+        handlePng1 = mMtpDatabase.beginSendObject(pngFile1.getPath(),
+                MtpConstants.FORMAT_PNG, baseHandle, MAIN_STORAGE_ID);
+        writeNewFileFromByte(pngFile1, createPngRawData(128, 64));
+
+        handlePngBad = mMtpDatabase.beginSendObject(pngFileBad.getPath(),
+                MtpConstants.FORMAT_PNG, baseHandle, MAIN_STORAGE_ID);
+        writeNewFile(pngFileBad);
+
+        Log.d(TAG, "testMtpDatabaseThumbnail: Test bad JPG");
+
+        testThumbnail(handleJpgBadThumb, jpgfileBadThumb, false);
+
+        testThumbnail(handleJpgNoThumb, jpgFileNoThumb, false);
+
+        testThumbnail(handleJpgBad, jpgfileBad, false);
+
+        Log.d(TAG, "testMtpDatabaseThumbnail: Test PNG");
+
+        testThumbnail(handlePng1, pngFile1, true);
+
+        Log.d(TAG, "testMtpDatabaseThumbnail: Test bad PNG");
+
+        testThumbnail(handlePngBad, pngFileBad, false);
+    }
+
+    @Test
+    @SmallTest
+    public void testMtpDatabaseExtStorage() throws IOException {
+        int numObj;
+        StorageVolume[] mVolumes;
+
+        logMethodName();
+
+        mVolumes = StorageManager.getVolumeList(UserHandle.myUserId(), 0);
+        // Currently it may need manual setup for 2nd storage on virtual device testing.
+        // Thus only run test when 2nd storage exists.
+        Assume.assumeTrue(
+                "Skip when 2nd storage not available, volume numbers = " + mVolumes.length,
+                mVolumes.length >= 2);
+
+        for (int ii = 0; ii < mVolumes.length; ii++) {
+            StorageVolume volume = mVolumes[ii];
+            // Skip Actual Main storage (Internal Storage),
+            // since we use manipulated path as testing Main storage
+            if (ii > 0)
+                mMtpDatabase.addStorage(volume);
+        }
+
+        numObj = mMtpDatabase.getNumObjects(SCND_STORAGE_ID, 0, 0xFFFFFFFF);
+        Assert.assertTrue(
+                "Fail to get objects in 2nd storage, object numbers = " + numObj, numObj >= 0);
+    }
+}
diff --git a/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml b/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml
index ce0d31c..a7347f2 100644
--- a/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml
+++ b/packages/CarSystemUI/res/layout/car_top_navigation_bar.xml
@@ -45,7 +45,7 @@
                 systemui:intent="intent:#Intent;action=android.car.intent.action.TOGGLE_HVAC_CONTROLS;end"
             />
 
-            <com.android.systemui.statusbar.hvac.AnimatedTemperatureView
+            <com.android.systemui.car.hvac.AnimatedTemperatureView
                 android:id="@+id/lefttext"
                 android:layout_width="wrap_content"
                 android:layout_height="match_parent"
@@ -127,7 +127,7 @@
                 systemui:intent="intent:#Intent;action=android.car.intent.action.TOGGLE_HVAC_CONTROLS;end"
             />
 
-            <com.android.systemui.statusbar.hvac.AnimatedTemperatureView
+            <com.android.systemui.car.hvac.AnimatedTemperatureView
                 android:id="@+id/righttext"
                 android:layout_width="wrap_content"
                 android:layout_height="match_parent"
diff --git a/packages/CarSystemUI/res/layout/car_top_navigation_bar_unprovisioned.xml b/packages/CarSystemUI/res/layout/car_top_navigation_bar_unprovisioned.xml
index a71567c..aa0a8c5 100644
--- a/packages/CarSystemUI/res/layout/car_top_navigation_bar_unprovisioned.xml
+++ b/packages/CarSystemUI/res/layout/car_top_navigation_bar_unprovisioned.xml
@@ -45,7 +45,7 @@
           systemui:intent="intent:#Intent;action=android.car.intent.action.TOGGLE_HVAC_CONTROLS;end"
           />
 
-      <com.android.systemui.statusbar.hvac.AnimatedTemperatureView
+      <com.android.systemui.car.hvac.AnimatedTemperatureView
           android:id="@+id/lefttext"
           android:layout_width="wrap_content"
           android:layout_height="match_parent"
@@ -123,7 +123,7 @@
           systemui:intent="intent:#Intent;action=android.car.intent.action.TOGGLE_HVAC_CONTROLS;end"
           />
 
-      <com.android.systemui.statusbar.hvac.AnimatedTemperatureView
+      <com.android.systemui.car.hvac.AnimatedTemperatureView
           android:id="@+id/righttext"
           android:layout_width="wrap_content"
           android:layout_height="match_parent"
diff --git a/packages/CarSystemUI/res/values/config.xml b/packages/CarSystemUI/res/values/config.xml
index e6fb501..52d13c3 100644
--- a/packages/CarSystemUI/res/values/config.xml
+++ b/packages/CarSystemUI/res/values/config.xml
@@ -86,29 +86,30 @@
     <string-array name="config_systemUIServiceComponents" translatable="false">
         <item>com.android.systemui.util.NotificationChannels</item>
         <item>com.android.systemui.keyguard.KeyguardViewMediator</item>
-        <item>com.android.systemui.recents.Recents</item>
-        <item>com.android.systemui.volume.VolumeUI</item>
-        <item>com.android.systemui.stackdivider.Divider</item>
+<!--        <item>com.android.systemui.recents.Recents</item>-->
+<!--        <item>com.android.systemui.volume.VolumeUI</item>-->
+<!--        <item>com.android.systemui.stackdivider.Divider</item>-->
 <!--        <item>com.android.systemui.statusbar.phone.StatusBar</item>-->
         <item>com.android.systemui.usb.StorageNotification</item>
         <item>com.android.systemui.power.PowerUI</item>
         <item>com.android.systemui.media.RingtonePlayer</item>
-        <item>com.android.systemui.keyboard.KeyboardUI</item>
-        <item>com.android.systemui.pip.PipUI</item>
-        <item>com.android.systemui.shortcut.ShortcutKeyDispatcher</item>
+<!--        <item>com.android.systemui.keyboard.KeyboardUI</item>-->
+<!--        <item>com.android.systemui.pip.PipUI</item>-->
+<!--        <item>com.android.systemui.shortcut.ShortcutKeyDispatcher</item>-->
         <item>@string/config_systemUIVendorServiceComponent</item>
         <item>com.android.systemui.util.leak.GarbageMonitor$Service</item>
-        <item>com.android.systemui.LatencyTester</item>
-        <item>com.android.systemui.globalactions.GlobalActionsComponent</item>
+<!--        <item>com.android.systemui.LatencyTester</item>-->
+<!--        <item>com.android.systemui.globalactions.GlobalActionsComponent</item>-->
         <item>com.android.systemui.ScreenDecorations</item>
         <item>com.android.systemui.biometrics.AuthController</item>
-        <item>com.android.systemui.SliceBroadcastRelayHandler</item>
+<!--        <item>com.android.systemui.SliceBroadcastRelayHandler</item>-->
         <item>com.android.systemui.SizeCompatModeActivityController</item>
-        <item>com.android.systemui.statusbar.notification.InstantAppNotifier</item>
+<!--        <item>com.android.systemui.statusbar.notification.InstantAppNotifier</item>-->
         <item>com.android.systemui.theme.ThemeOverlayController</item>
         <item>com.android.systemui.navigationbar.car.CarNavigationBar</item>
         <item>com.android.systemui.toast.ToastUI</item>
-        <item>com.android.systemui.voicerecognition.car.ConnectedDeviceVoiceRecognitionNotifier</item>
+        <item>com.android.systemui.car.voicerecognition.ConnectedDeviceVoiceRecognitionNotifier</item>
         <item>com.android.systemui.window.SystemUIOverlayWindowManager</item>
+        <item>com.android.systemui.car.volume.VolumeUI</item>
     </string-array>
 </resources>
diff --git a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIBinder.java b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIBinder.java
index 59fa9d0..91e222c 100644
--- a/packages/CarSystemUI/src/com/android/systemui/CarSystemUIBinder.java
+++ b/packages/CarSystemUI/src/com/android/systemui/CarSystemUIBinder.java
@@ -19,6 +19,8 @@
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.bubbles.dagger.BubbleModule;
 import com.android.systemui.car.notification.CarNotificationModule;
+import com.android.systemui.car.voicerecognition.ConnectedDeviceVoiceRecognitionNotifier;
+import com.android.systemui.car.volume.VolumeUI;
 import com.android.systemui.globalactions.GlobalActionsComponent;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.keyguard.dagger.KeyguardModule;
@@ -38,8 +40,6 @@
 import com.android.systemui.theme.ThemeOverlayController;
 import com.android.systemui.toast.ToastUI;
 import com.android.systemui.util.leak.GarbageMonitor;
-import com.android.systemui.voicerecognition.car.ConnectedDeviceVoiceRecognitionNotifier;
-import com.android.systemui.volume.VolumeUI;
 import com.android.systemui.window.OverlayWindowModule;
 import com.android.systemui.window.SystemUIOverlayWindowManager;
 
@@ -65,7 +65,7 @@
     @ClassKey(Divider.class)
     public abstract SystemUI bindDivider(Divider sysui);
 
-    /** */
+    /** Inject Car Navigation Bar. */
     @Binds
     @IntoMap
     @ClassKey(CarNavigationBar.class)
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/AnimatedTemperatureView.java b/packages/CarSystemUI/src/com/android/systemui/car/hvac/AnimatedTemperatureView.java
similarity index 97%
rename from packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/AnimatedTemperatureView.java
rename to packages/CarSystemUI/src/com/android/systemui/car/hvac/AnimatedTemperatureView.java
index 908aaad..a729431 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/AnimatedTemperatureView.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/hvac/AnimatedTemperatureView.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2018 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.hvac;
+package com.android.systemui.car.hvac;
 
 import android.animation.ObjectAnimator;
 import android.annotation.SuppressLint;
@@ -35,7 +35,6 @@
 import android.widget.TextView;
 
 import com.android.systemui.R;
-import com.android.systemui.navigationbar.car.hvac.TemperatureView;
 
 /**
  * Simple text display of HVAC properties, It is designed to show mTemperature and is configured in
diff --git a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/hvac/HvacController.java b/packages/CarSystemUI/src/com/android/systemui/car/hvac/HvacController.java
similarity index 98%
rename from packages/CarSystemUI/src/com/android/systemui/navigationbar/car/hvac/HvacController.java
rename to packages/CarSystemUI/src/com/android/systemui/car/hvac/HvacController.java
index fd9c488..af8ddb6 100644
--- a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/hvac/HvacController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/hvac/HvacController.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.navigationbar.car.hvac;
+package com.android.systemui.car.hvac;
 
 import static android.car.VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL;
 import static android.car.VehiclePropertyIds.HVAC_TEMPERATURE_DISPLAY_UNITS;
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureBackgroundAnimator.java b/packages/CarSystemUI/src/com/android/systemui/car/hvac/TemperatureBackgroundAnimator.java
similarity index 96%
rename from packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureBackgroundAnimator.java
rename to packages/CarSystemUI/src/com/android/systemui/car/hvac/TemperatureBackgroundAnimator.java
index 3c6d623..a4c4573 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureBackgroundAnimator.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/hvac/TemperatureBackgroundAnimator.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2018 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,15 +11,15 @@
  * 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.hvac;
+package com.android.systemui.car.hvac;
 
-import static com.android.systemui.statusbar.hvac.AnimatedTemperatureView.isHorizontal;
-import static com.android.systemui.statusbar.hvac.AnimatedTemperatureView.isLeft;
-import static com.android.systemui.statusbar.hvac.AnimatedTemperatureView.isTop;
-import static com.android.systemui.statusbar.hvac.AnimatedTemperatureView.isVertical;
+import static com.android.systemui.car.hvac.AnimatedTemperatureView.isHorizontal;
+import static com.android.systemui.car.hvac.AnimatedTemperatureView.isLeft;
+import static com.android.systemui.car.hvac.AnimatedTemperatureView.isTop;
+import static com.android.systemui.car.hvac.AnimatedTemperatureView.isVertical;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureColorStore.java b/packages/CarSystemUI/src/com/android/systemui/car/hvac/TemperatureColorStore.java
similarity index 97%
rename from packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureColorStore.java
rename to packages/CarSystemUI/src/com/android/systemui/car/hvac/TemperatureColorStore.java
index a40ffaf..9a7b0b9 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureColorStore.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/hvac/TemperatureColorStore.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2018 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.hvac;
+package com.android.systemui.car.hvac;
 
 import android.graphics.Color;
 
diff --git a/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureTextAnimator.java b/packages/CarSystemUI/src/com/android/systemui/car/hvac/TemperatureTextAnimator.java
similarity index 95%
rename from packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureTextAnimator.java
rename to packages/CarSystemUI/src/com/android/systemui/car/hvac/TemperatureTextAnimator.java
index 8ee5ef6..74d9704 100644
--- a/packages/CarSystemUI/src/com/android/systemui/statusbar/hvac/TemperatureTextAnimator.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/hvac/TemperatureTextAnimator.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2018 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,13 +11,13 @@
  * 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.hvac;
+package com.android.systemui.car.hvac;
 
-import static com.android.systemui.statusbar.hvac.AnimatedTemperatureView.isHorizontal;
-import static com.android.systemui.statusbar.hvac.AnimatedTemperatureView.isLeft;
+import static com.android.systemui.car.hvac.AnimatedTemperatureView.isHorizontal;
+import static com.android.systemui.car.hvac.AnimatedTemperatureView.isLeft;
 
 import android.annotation.NonNull;
 import android.view.animation.AccelerateDecelerateInterpolator;
diff --git a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/hvac/TemperatureTextView.java b/packages/CarSystemUI/src/com/android/systemui/car/hvac/TemperatureTextView.java
similarity index 95%
rename from packages/CarSystemUI/src/com/android/systemui/navigationbar/car/hvac/TemperatureTextView.java
rename to packages/CarSystemUI/src/com/android/systemui/car/hvac/TemperatureTextView.java
index ad4fcd9..521a665 100644
--- a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/hvac/TemperatureTextView.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/hvac/TemperatureTextView.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.navigationbar.car.hvac;
+package com.android.systemui.car.hvac;
 
 import android.content.Context;
 import android.content.res.TypedArray;
diff --git a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/hvac/TemperatureView.java b/packages/CarSystemUI/src/com/android/systemui/car/hvac/TemperatureView.java
similarity index 93%
rename from packages/CarSystemUI/src/com/android/systemui/navigationbar/car/hvac/TemperatureView.java
rename to packages/CarSystemUI/src/com/android/systemui/car/hvac/TemperatureView.java
index 963f318..6b903fa 100644
--- a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/hvac/TemperatureView.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/hvac/TemperatureView.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.navigationbar.car.hvac;
+package com.android.systemui.car.hvac;
 
 /**
  * Interface for Views that display temperature HVAC properties
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java b/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java
index 60ee19e..4fde309 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/keyguard/CarKeyguardViewController.java
@@ -205,6 +205,9 @@
 
     @Override
     public void onCancelClicked() {
+        getOverlayViewGlobalStateController().setWindowFocusable(/* focusable= */ false);
+        getOverlayViewGlobalStateController().setWindowNeedsInput(/* needsInput= */ false);
+
         mBouncer.hide(/* destroyView= */ true);
         mKeyguardCancelClickedListener.onCancelClicked();
     }
@@ -226,7 +229,8 @@
 
     @Override
     public void setNeedsInput(boolean needsInput) {
-        getLayout().setFocusable(needsInput);
+        getOverlayViewGlobalStateController().setWindowFocusable(needsInput);
+        getOverlayViewGlobalStateController().setWindowNeedsInput(needsInput);
     }
 
     /**
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainer.java b/packages/CarSystemUI/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainer.java
index 53e5d9f..20fcca0 100644
--- a/packages/CarSystemUI/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainer.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainer.java
@@ -30,7 +30,6 @@
 import com.android.car.notification.headsup.CarHeadsUpNotificationContainer;
 import com.android.systemui.car.CarDeviceProvisionedController;
 import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.statusbar.car.CarStatusBar;
 
 import javax.inject.Inject;
 import javax.inject.Singleton;
@@ -43,7 +42,7 @@
 @Singleton
 public class CarHeadsUpNotificationSystemContainer implements CarHeadsUpNotificationContainer {
     private final CarDeviceProvisionedController mCarDeviceProvisionedController;
-    private final Lazy<CarStatusBar> mCarStatusBarLazy;
+    private final Lazy<NotificationPanelViewController> mNotificationPanelViewControllerLazy;
 
     private final ViewGroup mWindow;
     private final FrameLayout mHeadsUpContentFrame;
@@ -55,10 +54,9 @@
             @Main Resources resources,
             CarDeviceProvisionedController deviceProvisionedController,
             WindowManager windowManager,
-            // TODO: Remove dependency on CarStatusBar
-            Lazy<CarStatusBar> carStatusBarLazy) {
+            Lazy<NotificationPanelViewController> notificationPanelViewControllerLazy) {
         mCarDeviceProvisionedController = deviceProvisionedController;
-        mCarStatusBarLazy = carStatusBarLazy;
+        mNotificationPanelViewControllerLazy = notificationPanelViewControllerLazy;
 
         boolean showOnBottom = resources.getBoolean(R.bool.config_showHeadsUpNotificationOnBottom);
 
@@ -87,7 +85,8 @@
 
     private void animateShow() {
         if ((mEnableHeadsUpNotificationWhenNotificationShadeOpen
-                || !mCarStatusBarLazy.get().isPanelExpanded()) && isCurrentUserSetup()) {
+                || !mNotificationPanelViewControllerLazy.get().isPanelExpanded())
+                && mCarDeviceProvisionedController.isCurrentUserFullySetup()) {
             mWindow.setVisibility(View.VISIBLE);
         }
     }
@@ -114,9 +113,4 @@
     public boolean isVisible() {
         return mWindow.getVisibility() == View.VISIBLE;
     }
-
-    private boolean isCurrentUserSetup() {
-        return mCarDeviceProvisionedController.isCurrentUserSetup()
-                && !mCarDeviceProvisionedController.isCurrentUserSetupInProgress();
-    }
 }
diff --git a/packages/CarSystemUI/src/com/android/systemui/sideloaded/car/CarSideLoadedAppDetector.java b/packages/CarSystemUI/src/com/android/systemui/car/sideloaded/CarSideLoadedAppDetector.java
similarity index 98%
rename from packages/CarSystemUI/src/com/android/systemui/sideloaded/car/CarSideLoadedAppDetector.java
rename to packages/CarSystemUI/src/com/android/systemui/car/sideloaded/CarSideLoadedAppDetector.java
index c0dbb58..f145b14 100644
--- a/packages/CarSystemUI/src/com/android/systemui/sideloaded/car/CarSideLoadedAppDetector.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/sideloaded/CarSideLoadedAppDetector.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.sideloaded.car;
+package com.android.systemui.car.sideloaded;
 
 import android.annotation.NonNull;
 import android.app.ActivityManager;
diff --git a/packages/CarSystemUI/src/com/android/systemui/voicerecognition/car/ConnectedDeviceVoiceRecognitionNotifier.java b/packages/CarSystemUI/src/com/android/systemui/car/voicerecognition/ConnectedDeviceVoiceRecognitionNotifier.java
similarity index 98%
rename from packages/CarSystemUI/src/com/android/systemui/voicerecognition/car/ConnectedDeviceVoiceRecognitionNotifier.java
rename to packages/CarSystemUI/src/com/android/systemui/car/voicerecognition/ConnectedDeviceVoiceRecognitionNotifier.java
index 2f79f96..c054d20 100644
--- a/packages/CarSystemUI/src/com/android/systemui/voicerecognition/car/ConnectedDeviceVoiceRecognitionNotifier.java
+++ b/packages/CarSystemUI/src/com/android/systemui/car/voicerecognition/ConnectedDeviceVoiceRecognitionNotifier.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.voicerecognition.car;
+package com.android.systemui.car.voicerecognition;
 
 import android.bluetooth.BluetoothHeadsetClient;
 import android.content.BroadcastReceiver;
diff --git a/packages/CarSystemUI/src/com/android/systemui/car/volume/VolumeUI.java b/packages/CarSystemUI/src/com/android/systemui/car/volume/VolumeUI.java
new file mode 100644
index 0000000..2bdb85f
--- /dev/null
+++ b/packages/CarSystemUI/src/com/android/systemui/car/volume/VolumeUI.java
@@ -0,0 +1,125 @@
+/*
+ * 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.car.volume;
+
+import android.car.Car;
+import android.car.media.CarAudioManager;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.os.Handler;
+import android.util.Log;
+
+import com.android.systemui.R;
+import com.android.systemui.SystemUI;
+import com.android.systemui.car.CarServiceProvider;
+import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.volume.VolumeDialogComponent;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import dagger.Lazy;
+
+/** The entry point for controlling the volume ui in cars. */
+@Singleton
+public class VolumeUI extends SystemUI {
+
+    private static final String TAG = "VolumeUI";
+    private final Resources mResources;
+    private final Handler mMainHandler;
+    private final CarServiceProvider mCarServiceProvider;
+    private final Lazy<VolumeDialogComponent> mVolumeDialogComponentLazy;
+
+    private final CarAudioManager.CarVolumeCallback mVolumeChangeCallback =
+            new CarAudioManager.CarVolumeCallback() {
+                @Override
+                public void onGroupVolumeChanged(int zoneId, int groupId, int flags) {
+                    if (mVolumeDialogComponent == null) {
+                        mMainHandler.post(() -> {
+                            mVolumeDialogComponent = mVolumeDialogComponentLazy.get();
+                            mVolumeDialogComponent.register();
+                        });
+                        mCarAudioManager.unregisterCarVolumeCallback(mVolumeChangeCallback);
+                    }
+                }
+
+                @Override
+                public void onMasterMuteChanged(int zoneId, int flags) {
+                    // ignored
+                }
+            };
+
+    private boolean mEnabled;
+    private CarAudioManager mCarAudioManager;
+    private VolumeDialogComponent mVolumeDialogComponent;
+
+    @Inject
+    public VolumeUI(
+            Context context,
+            @Main Resources resources,
+            @Main Handler mainHandler,
+            CarServiceProvider carServiceProvider,
+            Lazy<VolumeDialogComponent> volumeDialogComponentLazy
+    ) {
+        super(context);
+        mResources = resources;
+        mMainHandler = mainHandler;
+        mCarServiceProvider = carServiceProvider;
+        mVolumeDialogComponentLazy = volumeDialogComponentLazy;
+    }
+
+    @Override
+    public void start() {
+        boolean enableVolumeUi = mResources.getBoolean(R.bool.enable_volume_ui);
+        mEnabled = enableVolumeUi;
+        if (!mEnabled) return;
+
+        mCarServiceProvider.addListener(car -> {
+            if (mCarAudioManager != null) {
+                return;
+            }
+
+            mCarAudioManager = (CarAudioManager) car.getCarManager(Car.AUDIO_SERVICE);
+            Log.d(TAG, "Registering mVolumeChangeCallback.");
+            // This volume call back is never unregistered because CarStatusBar is
+            // never destroyed.
+            mCarAudioManager.registerCarVolumeCallback(mVolumeChangeCallback);
+        });
+    }
+
+    @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        if (!mEnabled) return;
+        if (mVolumeDialogComponent != null) {
+            mVolumeDialogComponent.onConfigurationChanged(newConfig);
+        }
+    }
+
+    @Override
+    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        pw.print("mEnabled="); pw.println(mEnabled);
+        if (!mEnabled) return;
+        if (mVolumeDialogComponent != null) {
+            mVolumeDialogComponent.dump(fd, pw, args);
+        }
+    }
+}
diff --git a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java
index 9fdfc0f..4e315c6 100644
--- a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java
+++ b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBar.java
@@ -51,8 +51,6 @@
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.statusbar.AutoHideUiElement;
 import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.NavigationBarController;
-import com.android.systemui.statusbar.SuperStatusBarViewFactory;
 import com.android.systemui.statusbar.phone.AutoHideController;
 import com.android.systemui.statusbar.phone.BarTransitions;
 import com.android.systemui.statusbar.phone.PhoneStatusBarPolicy;
@@ -79,8 +77,6 @@
     private final ButtonSelectionStateListener mButtonSelectionStateListener;
     private final Handler mMainHandler;
     private final Lazy<KeyguardStateController> mKeyguardStateControllerLazy;
-    private final Lazy<NavigationBarController> mNavigationBarControllerLazy;
-    private final SuperStatusBarViewFactory mSuperStatusBarViewFactory;
     private final ButtonSelectionStateController mButtonSelectionStateController;
     private final PhoneStatusBarPolicy mIconPolicy;
     private final StatusBarIconController mIconController;
@@ -126,8 +122,6 @@
             ButtonSelectionStateListener buttonSelectionStateListener,
             @Main Handler mainHandler,
             Lazy<KeyguardStateController> keyguardStateControllerLazy,
-            Lazy<NavigationBarController> navigationBarControllerLazy,
-            SuperStatusBarViewFactory superStatusBarViewFactory,
             ButtonSelectionStateController buttonSelectionStateController,
             PhoneStatusBarPolicy iconPolicy,
             StatusBarIconController iconController
@@ -142,8 +136,6 @@
         mButtonSelectionStateListener = buttonSelectionStateListener;
         mMainHandler = mainHandler;
         mKeyguardStateControllerLazy = keyguardStateControllerLazy;
-        mNavigationBarControllerLazy = navigationBarControllerLazy;
-        mSuperStatusBarViewFactory = superStatusBarViewFactory;
         mButtonSelectionStateController = buttonSelectionStateController;
         mIconPolicy = iconPolicy;
         mIconController = iconController;
@@ -319,11 +311,6 @@
                     result.mImeWindowVis, result.mImeBackDisposition,
                     result.mShowImeSwitcher);
         }
-
-        // There has been a car customized nav bar on the default display, so just create nav bars
-        // on external displays.
-        mNavigationBarControllerLazy.get().createNavigationBars(/* includeDefaultDisplay= */ false,
-                result);
     }
 
     private void buildNavBarWindows() {
diff --git a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBarController.java b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBarController.java
index 37a8225..8f3ae1a 100644
--- a/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBarController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/navigationbar/car/CarNavigationBarController.java
@@ -24,7 +24,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.systemui.R;
-import com.android.systemui.navigationbar.car.hvac.HvacController;
+import com.android.systemui.car.hvac.HvacController;
 
 import javax.inject.Inject;
 import javax.inject.Singleton;
diff --git a/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeDialogComponent.java b/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeDialogComponent.java
index 5a34436..35ab322 100644
--- a/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeDialogComponent.java
+++ b/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeDialogComponent.java
@@ -45,6 +45,7 @@
     @Override
     protected VolumeDialog createDefault() {
         mCarVolumeDialog = new CarVolumeDialogImpl(mContext);
+        mCarVolumeDialog.show(Events.SHOW_REASON_VOLUME_CHANGED);
         return mCarVolumeDialog;
     }
 }
diff --git a/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java b/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java
index 873d7d7..2218eb5 100644
--- a/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java
+++ b/packages/CarSystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java
@@ -234,6 +234,20 @@
         cleanupAudioManager();
     }
 
+    /**
+     * Reveals volume dialog.
+     */
+    public void show(int reason) {
+        mHandler.obtainMessage(H.SHOW, reason).sendToTarget();
+    }
+
+    /**
+     * Hides volume dialog.
+     */
+    public void dismiss(int reason) {
+        mHandler.obtainMessage(H.DISMISS, reason).sendToTarget();
+    }
+
     private void initDialog() {
         loadAudioUsageItems();
         mCarVolumeLineItems.clear();
diff --git a/packages/CarSystemUI/src/com/android/systemui/window/OverlayViewGlobalStateController.java b/packages/CarSystemUI/src/com/android/systemui/window/OverlayViewGlobalStateController.java
index 402d742..5fe03f1 100644
--- a/packages/CarSystemUI/src/com/android/systemui/window/OverlayViewGlobalStateController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/window/OverlayViewGlobalStateController.java
@@ -123,6 +123,12 @@
         mSystemUIOverlayWindowController.setWindowFocusable(focusable);
     }
 
+    /** Sets the {@link android.view.WindowManager.LayoutParams#FLAG_ALT_FOCUSABLE_IM} flag of the
+     * sysui overlay window */
+    public void setWindowNeedsInput(boolean needsInput) {
+        mSystemUIOverlayWindowController.setWindowNeedsInput(needsInput);
+    }
+
     /** Returns {@code true} if the window is focusable. */
     public boolean isWindowFocusable() {
         return mSystemUIOverlayWindowController.isWindowFocusable();
diff --git a/packages/CarSystemUI/src/com/android/systemui/window/SystemUIOverlayWindowController.java b/packages/CarSystemUI/src/com/android/systemui/window/SystemUIOverlayWindowController.java
index 04d69ea..5df5d6e 100644
--- a/packages/CarSystemUI/src/com/android/systemui/window/SystemUIOverlayWindowController.java
+++ b/packages/CarSystemUI/src/com/android/systemui/window/SystemUIOverlayWindowController.java
@@ -132,6 +132,16 @@
         updateWindow();
     }
 
+    /** Sets the window to enable IME. */
+    public void setWindowNeedsInput(boolean needsInput) {
+        if (needsInput) {
+            mLpChanged.flags &= ~WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+        } else {
+            mLpChanged.flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
+        }
+        updateWindow();
+    }
+
     /** Returns {@code true} if the window is visible */
     public boolean isWindowVisible() {
         return mVisible;
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/hvac/HvacControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/hvac/HvacControllerTest.java
similarity index 97%
rename from packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/hvac/HvacControllerTest.java
rename to packages/CarSystemUI/tests/src/com/android/systemui/car/hvac/HvacControllerTest.java
index a71d1db..7996170 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/hvac/HvacControllerTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/hvac/HvacControllerTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.navigationbar.car.hvac;
+package com.android.systemui.car.hvac;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainerTest.java
index 05b8e6a..6ac72a6 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainerTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/notification/CarHeadsUpNotificationSystemContainerTest.java
@@ -31,7 +31,6 @@
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.car.CarDeviceProvisionedController;
-import com.android.systemui.statusbar.car.CarStatusBar;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -48,7 +47,7 @@
     @Mock
     private CarDeviceProvisionedController mCarDeviceProvisionedController;
     @Mock
-    private CarStatusBar mCarStatusBar;
+    private NotificationPanelViewController mNotificationPanelViewController;
     @Mock
     private WindowManager mWindowManager;
 
@@ -61,7 +60,7 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
 
-        when(mCarStatusBar.isPanelExpanded()).thenReturn(false);
+        when(mNotificationPanelViewController.isPanelExpanded()).thenReturn(false);
         when(mCarDeviceProvisionedController.isCurrentUserSetup()).thenReturn(true);
         when(mCarDeviceProvisionedController.isCurrentUserSetupInProgress()).thenReturn(false);
 
@@ -72,14 +71,14 @@
 
         mDefaultController = new CarHeadsUpNotificationSystemContainer(mContext,
                 testableResources.getResources(), mCarDeviceProvisionedController, mWindowManager,
-                () -> mCarStatusBar);
+                () -> mNotificationPanelViewController);
 
         testableResources.addOverride(
                 R.bool.config_enableHeadsUpNotificationWhenNotificationShadeOpen, true);
 
         mOverrideEnabledController = new CarHeadsUpNotificationSystemContainer(mContext,
                 testableResources.getResources(), mCarDeviceProvisionedController, mWindowManager,
-                () -> mCarStatusBar);
+                () -> mNotificationPanelViewController);
     }
 
     @Test
@@ -120,14 +119,14 @@
 
     @Test
     public void testDisplayNotification_notificationPanelExpanded_isInvisible() {
-        when(mCarStatusBar.isPanelExpanded()).thenReturn(true);
+        when(mNotificationPanelViewController.isPanelExpanded()).thenReturn(true);
         mDefaultController.displayNotification(mNotificationView);
         assertThat(mDefaultController.isVisible()).isFalse();
     }
 
     @Test
     public void testDisplayNotification_notificationPanelExpandedEnabledHUNWhenOpen_isVisible() {
-        when(mCarStatusBar.isPanelExpanded()).thenReturn(true);
+        when(mNotificationPanelViewController.isPanelExpanded()).thenReturn(true);
         mOverrideEnabledController.displayNotification(mNotificationView);
         assertThat(mOverrideEnabledController.isVisible()).isTrue();
     }
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/sideloaded/car/CarSideLoadedAppDetectorTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/sideloaded/CarSideLoadedAppDetectorTest.java
similarity index 99%
rename from packages/CarSystemUI/tests/src/com/android/systemui/sideloaded/car/CarSideLoadedAppDetectorTest.java
rename to packages/CarSystemUI/tests/src/com/android/systemui/car/sideloaded/CarSideLoadedAppDetectorTest.java
index aebb0e0..80f3d1ee 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/sideloaded/car/CarSideLoadedAppDetectorTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/sideloaded/CarSideLoadedAppDetectorTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.sideloaded.car;
+package com.android.systemui.car.sideloaded;
 
 import static com.google.common.truth.Truth.assertThat;
 
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/voicerecognition/car/ConnectedDeviceVoiceRecognitionNotifierTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/car/voicerecognition/ConnectedDeviceVoiceRecognitionNotifierTest.java
similarity index 93%
rename from packages/CarSystemUI/tests/src/com/android/systemui/voicerecognition/car/ConnectedDeviceVoiceRecognitionNotifierTest.java
rename to packages/CarSystemUI/tests/src/com/android/systemui/car/voicerecognition/ConnectedDeviceVoiceRecognitionNotifierTest.java
index 38b47d0..eca51e3 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/voicerecognition/car/ConnectedDeviceVoiceRecognitionNotifierTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/car/voicerecognition/ConnectedDeviceVoiceRecognitionNotifierTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,10 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.systemui.voicerecognition.car;
+package com.android.systemui.car.voicerecognition;
 
-import static com.android.systemui.voicerecognition.car.ConnectedDeviceVoiceRecognitionNotifier.INVALID_VALUE;
-import static com.android.systemui.voicerecognition.car.ConnectedDeviceVoiceRecognitionNotifier.VOICE_RECOGNITION_STARTED;
+import static com.android.systemui.car.voicerecognition.ConnectedDeviceVoiceRecognitionNotifier.INVALID_VALUE;
+import static com.android.systemui.car.voicerecognition.ConnectedDeviceVoiceRecognitionNotifier.VOICE_RECOGNITION_STARTED;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.never;
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarControllerTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarControllerTest.java
index bbcd0d4..28c69c7 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarControllerTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarControllerTest.java
@@ -31,7 +31,7 @@
 
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
-import com.android.systemui.navigationbar.car.hvac.HvacController;
+import com.android.systemui.car.hvac.HvacController;
 import com.android.systemui.plugins.DarkIconDispatcher;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
 
diff --git a/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarTest.java b/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarTest.java
index 3ecb29f..f789d38 100644
--- a/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarTest.java
+++ b/packages/CarSystemUI/tests/src/com/android/systemui/navigationbar/car/CarNavigationBarTest.java
@@ -37,8 +37,6 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.car.CarDeviceProvisionedController;
 import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.NavigationBarController;
-import com.android.systemui.statusbar.SuperStatusBarViewFactory;
 import com.android.systemui.statusbar.phone.AutoHideController;
 import com.android.systemui.statusbar.phone.PhoneStatusBarPolicy;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
@@ -73,10 +71,6 @@
     @Mock
     private KeyguardStateController mKeyguardStateController;
     @Mock
-    private NavigationBarController mNavigationBarController;
-    @Mock
-    private SuperStatusBarViewFactory mSuperStatusBarViewFactory;
-    @Mock
     private ButtonSelectionStateController mButtonSelectionStateController;
     @Mock
     private PhoneStatusBarPolicy mIconPolicy;
@@ -91,9 +85,8 @@
         mCarNavigationBar = new CarNavigationBar(mContext, mTestableResources.getResources(),
                 mCarNavigationBarController, mWindowManager, mDeviceProvisionedController,
                 new CommandQueue(mContext), mAutoHideController, mButtonSelectionStateListener,
-                mHandler, () -> mKeyguardStateController, () -> mNavigationBarController,
-                mSuperStatusBarViewFactory, mButtonSelectionStateController, mIconPolicy,
-                mIconController);
+                mHandler, () -> mKeyguardStateController, mButtonSelectionStateController,
+                mIconPolicy, mIconController);
     }
 
     @Test
diff --git a/packages/CtsShim/build/Android.bp b/packages/CtsShim/build/Android.bp
index 8aac1c9..be79010 100644
--- a/packages/CtsShim/build/Android.bp
+++ b/packages/CtsShim/build/Android.bp
@@ -70,6 +70,7 @@
     // v2/v3 signature.
     use_embedded_native_libs: true,
     apex_available: [
+        "//apex_available:platform",
         "com.android.apex.cts.shim.v1",
         "com.android.apex.cts.shim.v2",
         "com.android.apex.cts.shim.v2_no_hashtree",
@@ -141,6 +142,7 @@
 
     manifest: "shim/AndroidManifest.xml",
     apex_available: [
+        "//apex_available:platform",
         "com.android.apex.cts.shim.v1",
         "com.android.apex.cts.shim.v2",
         "com.android.apex.cts.shim.v2_no_hashtree",
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index 1007d83..7baaf49 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1080,14 +1080,14 @@
     <string name="power_suggestion_battery_run_out">Battery may run out by <xliff:g id="time" example="12 PM">%1$s</xliff:g></string>
 
     <!-- [CHAR_LIMIT=60] label for estimated remaining duration of battery when under a certain amount -->
-    <string name="power_remaining_less_than_duration_only">Less than <xliff:g id="threshold">%1$s</xliff:g> remaining</string>
+    <string name="power_remaining_less_than_duration_only">Less than <xliff:g id="threshold">%1$s</xliff:g> left</string>
     <!-- [CHAR_LIMIT=60] label for estimated remaining duration of battery when under a certain amount with the percentage -->
-    <string name="power_remaining_less_than_duration">Less than <xliff:g id="threshold">%1$s</xliff:g> remaining (<xliff:g id="level">%2$s</xliff:g>)</string>
+    <string name="power_remaining_less_than_duration">Less than <xliff:g id="threshold">%1$s</xliff:g> left (<xliff:g id="level">%2$s</xliff:g>)</string>
 
     <!-- Used to let users know that they have more than some amount of battery life remaining with percentage. ex: 75% - more than 1 day remaining [CHAR LIMIT = 80] -->
-    <string name="power_remaining_more_than_subtext">More than <xliff:g id="time_remaining">%1$s</xliff:g> remaining (<xliff:g id="level">%2$s</xliff:g>)</string>
+    <string name="power_remaining_more_than_subtext">More than <xliff:g id="time_remaining">%1$s</xliff:g> left (<xliff:g id="level">%2$s</xliff:g>)</string>
     <!-- Used to let users know that they have more than some amount of battery life remaining. ex: more than 1 day remaining [CHAR LIMIT = 40] -->
-    <string name="power_remaining_only_more_than_subtext">More than <xliff:g id="time_remaining">%1$s</xliff:g> remaining</string>
+    <string name="power_remaining_only_more_than_subtext">More than <xliff:g id="time_remaining">%1$s</xliff:g> left</string>
 
     <!-- [CHAR_LIMIT=50] Short label for imminent shutdown warning of device -->
     <string name="power_remaining_duration_only_shutdown_imminent" product="default">Phone may shut down soon</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java b/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java
index 653c8ad..deccde5 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java
@@ -33,6 +33,7 @@
 import android.util.Slog;
 import android.view.LayoutInflater;
 import android.view.View;
+import android.view.ViewGroup;
 import android.widget.CompoundButton;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
@@ -395,8 +396,12 @@
             button1.setAlpha(button1.isEnabled() ? 1f : .5f);
             button2.setAlpha(button2.isEnabled() ? 1f : .5f);
         } else {
-            button1.setVisibility(View.GONE);
-            button2.setVisibility(View.GONE);
+            if (button1 != null) {
+                ((ViewGroup) row).removeView(button1);
+            }
+            if (button2 != null) {
+                ((ViewGroup) row).removeView(button2);
+            }
         }
     }
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java
index 342d127a..4b779ac 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/utils/PowerUtilTest.java
@@ -137,9 +137,9 @@
                 true /* basedOnUsage */);
 
         // shortened string should not have percentage
-        assertThat(info).isEqualTo("Less than 15 min remaining");
+        assertThat(info).isEqualTo("Less than 15 min left");
         // Add percentage to string when provided
-        assertThat(info2).isEqualTo("Less than 15 min remaining (10%)");
+        assertThat(info2).isEqualTo("Less than 15 min left (10%)");
     }
 
     @Test
@@ -171,9 +171,9 @@
                 true /* basedOnUsage */);
 
         // shortened string should not have percentage
-        assertThat(info).isEqualTo("More than 2 days remaining");
+        assertThat(info).isEqualTo("More than 2 days left");
         // Add percentage to string when provided
-        assertThat(info2).isEqualTo("More than 2 days remaining (10%)");
+        assertThat(info2).isEqualTo("More than 2 days left (10%)");
     }
 
     @Test
@@ -181,7 +181,7 @@
         String info = PowerUtil.getBatteryTipStringFormatted(mContext,
                 THREE_DAYS_MILLIS);
 
-        assertThat(info).isEqualTo("More than 3 days remaining");
+        assertThat(info).isEqualTo("More than 3 days left");
     }
 
     @Test
diff --git a/packages/SystemUI/res/drawable/bubble_manage_menu_row.xml b/packages/SystemUI/res/drawable/bubble_manage_menu_row.xml
new file mode 100644
index 0000000..a793680
--- /dev/null
+++ b/packages/SystemUI/res/drawable/bubble_manage_menu_row.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true">
+        <ripple android:color="#99999999" />
+    </item>
+</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/screenshot_cancel.xml b/packages/SystemUI/res/drawable/screenshot_cancel.xml
index be3c598..f0dfd21 100644
--- a/packages/SystemUI/res/drawable/screenshot_cancel.xml
+++ b/packages/SystemUI/res/drawable/screenshot_cancel.xml
@@ -20,9 +20,9 @@
         android:viewportWidth="48.0"
         android:viewportHeight="48.0">
     <path
-        android:pathData="M24,24m-16,0a16,16 0,1 1,32 0a16,16 0,1 1,-32 0"
-        android:fillColor="@android:color/white"/>
+        android:fillColor="@color/global_screenshot_dismiss_background"
+        android:pathData="M24,24m-16,0a16,16 0,1 1,32 0a16,16 0,1 1,-32 0"/>
     <path
-        android:fillColor="@color/GM2_grey_500"
+        android:fillColor="@color/global_screenshot_dismiss_foreground"
         android:pathData="M31,18.41L29.59,17 24,22.59 18.41,17 17,18.41 22.59,24 17,29.59 18.41,31 24,25.41 29.59,31 31,29.59 25.41,24z"/>
 </vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/bubble_manage_menu.xml b/packages/SystemUI/res/layout/bubble_manage_menu.xml
new file mode 100644
index 0000000..129282d
--- /dev/null
+++ b/packages/SystemUI/res/layout/bubble_manage_menu.xml
@@ -0,0 +1,99 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:background="@drawable/rounded_bg_full"
+    android:elevation="@dimen/bubble_manage_menu_elevation"
+    android:orientation="vertical">
+
+    <LinearLayout
+        android:id="@+id/bubble_manage_menu_dismiss_container"
+        android:background="@drawable/bubble_manage_menu_row"
+        android:layout_width="match_parent"
+        android:layout_height="48dp"
+        android:gravity="center_vertical"
+        android:paddingStart="16dp"
+        android:paddingEnd="16dp"
+        android:orientation="horizontal">
+
+        <ImageView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:src="@drawable/ic_remove_no_shadow"
+            android:tint="@color/global_actions_text"/>
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="16dp"
+            android:textAppearance="@*android:style/TextAppearance.DeviceDefault"
+            android:text="@string/bubble_dismiss_text" />
+
+    </LinearLayout>
+
+    <LinearLayout
+        android:id="@+id/bubble_manage_menu_dont_bubble_container"
+        android:background="@drawable/bubble_manage_menu_row"
+        android:layout_width="match_parent"
+        android:layout_height="48dp"
+        android:gravity="center_vertical"
+        android:paddingStart="16dp"
+        android:paddingEnd="16dp"
+        android:orientation="horizontal">
+
+        <ImageView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:src="@drawable/ic_stop_bubble"
+            android:tint="@color/global_actions_text"/>
+
+        <TextView
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="16dp"
+            android:textAppearance="@*android:style/TextAppearance.DeviceDefault"
+            android:text="@string/bubbles_dont_bubble_conversation" />
+
+    </LinearLayout>
+
+    <LinearLayout
+        android:id="@+id/bubble_manage_menu_settings_container"
+        android:background="@drawable/bubble_manage_menu_row"
+        android:layout_width="match_parent"
+        android:layout_height="48dp"
+        android:gravity="center_vertical"
+        android:paddingStart="16dp"
+        android:paddingEnd="16dp"
+        android:orientation="horizontal">
+
+        <ImageView
+            android:id="@+id/bubble_manage_menu_settings_icon"
+            android:layout_width="24dp"
+            android:layout_height="24dp"
+            android:src="@drawable/ic_remove_no_shadow"/>
+
+        <TextView
+            android:id="@+id/bubble_manage_menu_settings_name"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginStart="16dp"
+            android:textAppearance="@*android:style/TextAppearance.DeviceDefault" />
+
+    </LinearLayout>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/controls_management.xml b/packages/SystemUI/res/layout/controls_management.xml
index 6da96d1..835e54e 100644
--- a/packages/SystemUI/res/layout/controls_management.xml
+++ b/packages/SystemUI/res/layout/controls_management.xml
@@ -26,41 +26,15 @@
     android:paddingStart="@dimen/controls_management_side_padding"
     android:paddingEnd="@dimen/controls_management_side_padding" >
 
-    <LinearLayout
-        android:orientation="horizontal"
+
+    <TextView
+        android:id="@+id/title"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
-        android:focusable="false"
-        android:clickable="false"
-        android:gravity="center_vertical">
+        android:textAppearance="?android:attr/textAppearanceLarge"
+        android:textSize="@dimen/controls_title_size"
+        android:textAlignment="center" />
 
-        <FrameLayout
-            android:id="@+id/icon_frame"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:gravity="start|center_vertical"
-            android:minWidth="56dp"
-            android:visibility="gone"
-            android:paddingTop="@dimen/controls_app_icon_frame_top_padding"
-            android:paddingBottom="@dimen/controls_app_icon_frame_bottom_padding"
-            android:paddingEnd="@dimen/controls_app_icon_frame_side_padding"
-            android:paddingStart="@dimen/controls_app_icon_frame_side_padding" >
-
-            <ImageView
-                android:id="@android:id/icon"
-                android:layout_width="@dimen/controls_app_icon_size"
-                android:layout_height="@dimen/controls_app_icon_size" />
-        </FrameLayout>
-
-        <TextView
-            android:id="@+id/title"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:textAppearance="?android:attr/textAppearanceLarge"
-            android:textSize="@dimen/controls_title_size"
-            android:textAlignment="center" />
-
-    </LinearLayout>
 
 
     <TextView
diff --git a/packages/SystemUI/res/layout/global_screenshot.xml b/packages/SystemUI/res/layout/global_screenshot.xml
index d506e7e..db109fe 100644
--- a/packages/SystemUI/res/layout/global_screenshot.xml
+++ b/packages/SystemUI/res/layout/global_screenshot.xml
@@ -68,6 +68,7 @@
         android:visibility="gone"
         android:contentDescription="@string/screenshot_dismiss_ui_description">
         <ImageView
+            android:id="@+id/global_screenshot_dismiss_image"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
             android:layout_margin="@dimen/screenshot_dismiss_button_margin"
diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml
index 93aa270..2d51011 100644
--- a/packages/SystemUI/res/values-night/colors.xml
+++ b/packages/SystemUI/res/values-night/colors.xml
@@ -72,9 +72,14 @@
     <color name="global_actions_alert_text">@color/GM2_red_300</color>
 
     <!-- Global screenshot actions -->
-    <color name="global_screenshot_button_background">@color/GM2_grey_900</color>
+    <color name="global_screenshot_button_background">@color/GM2_grey_800</color>
     <color name="global_screenshot_button_ripple">#42FFFFFF</color>
-    <color name="global_screenshot_button_text">@color/GM2_blue_300</color>
+    <color name="global_screenshot_button_text">#FFFFFF</color>
+    <color name="global_screenshot_button_border">@color/GM2_grey_600</color>
+    <color name="global_screenshot_button_icon">@color/GM2_blue_300</color>
+    <color name="global_screenshot_dismiss_background">@color/GM2_grey_800</color>
+    <color name="global_screenshot_dismiss_foreground">#FFFFFF</color>
+
 
     <!-- Biometric dialog colors -->
     <color name="biometric_dialog_gray">#ff888888</color>
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 4482cda..8b6b5f6 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -198,6 +198,8 @@
     <color name="global_screenshot_button_border">@color/GM2_grey_300</color>
     <color name="global_screenshot_button_ripple">#1f000000</color>
     <color name="global_screenshot_button_icon">@color/GM2_blue_500</color>
+    <color name="global_screenshot_dismiss_background">#FFFFFF</color>
+    <color name="global_screenshot_dismiss_foreground">@color/GM2_grey_500</color>
 
     <!-- GM2 colors -->
     <color name="GM2_grey_50">#F8F9FA</color>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 7cefa0c..179f8b8 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1200,6 +1200,7 @@
          snap to the dismiss target. -->
     <dimen name="bubble_dismiss_target_padding_x">40dp</dimen>
     <dimen name="bubble_dismiss_target_padding_y">20dp</dimen>
+    <dimen name="bubble_manage_menu_elevation">4dp</dimen>
 
     <dimen name="dismiss_circle_size">52dp</dimen>
     <dimen name="dismiss_target_x_size">24dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index cb20e7a..d639ed0 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2629,6 +2629,8 @@
     <string name="bubbles_user_education_manage">Tap Manage to turn off bubbles from this app</string>
     <!-- Button text for dismissing the bubble "manage" button tool tip  [CHAR LIMIT=20]-->
     <string name="bubbles_user_education_got_it">Got it</string>
+    <!-- Label for the button that takes the user to the notification settings for the given app. -->
+    <string name="bubbles_app_settings"><xliff:g id="notification_title" example="Android Messages">%1$s</xliff:g> settings</string>
 
     <!-- Notification content text when the system navigation mode changes as a result of changing the default launcher [CHAR LIMIT=NONE] -->
     <string name="notification_content_system_nav_changed">System navigation updated. To make changes, go to Settings.</string>
diff --git a/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java b/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
index 409ae3f..c92174a 100644
--- a/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
+++ b/packages/SystemUI/src/com/android/keyguard/PasswordTextView.java
@@ -164,7 +164,9 @@
                 currentDrawPosition = getPaddingLeft();
             }
         } else {
-            currentDrawPosition = getWidth() / 2 - totalDrawingWidth / 2;
+            float maxRight = getWidth() - getPaddingRight() - totalDrawingWidth;
+            float center = getWidth() / 2f - totalDrawingWidth / 2f;
+            currentDrawPosition = center > 0 ? center : maxRight;
         }
         int length = mTextChars.size();
         Rect bounds = getCharBounds();
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
index 71f2bc0..38bfffb 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java
@@ -90,6 +90,7 @@
     }
 
     private FlyoutMessage mFlyoutMessage;
+    private Drawable mBadgedAppIcon;
     private Bitmap mBadgedImage;
     private int mDotColor;
     private Path mDotPath;
@@ -133,6 +134,10 @@
         return mBadgedImage;
     }
 
+    public Drawable getBadgedAppIcon() {
+        return mBadgedAppIcon;
+    }
+
     @Override
     public int getDotColor() {
         return mDotColor;
@@ -239,6 +244,7 @@
         mAppName = info.appName;
         mFlyoutMessage = info.flyoutMessage;
 
+        mBadgedAppIcon = info.badgedAppIcon;
         mBadgedImage = info.badgedBubbleImage;
         mDotColor = info.dotColor;
         mDotPath = info.dotPath;
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index c6883c8..e488cf2 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -605,6 +605,9 @@
             if (mExpandListener != null) {
                 mStackView.setExpandListener(mExpandListener);
             }
+
+            mStackView.setUnbubbleConversationCallback(notificationEntry ->
+                    onUserChangedBubble(notificationEntry, false /* shouldBubble */));
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
index 3524696..bb23655 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java
@@ -43,7 +43,6 @@
 import android.graphics.Rect;
 import android.graphics.drawable.ShapeDrawable;
 import android.os.RemoteException;
-import android.service.notification.StatusBarNotification;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.View;
@@ -55,14 +54,13 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.recents.TriangleShape;
-import com.android.systemui.shared.system.SysUiStatsLog;
 import com.android.systemui.statusbar.AlphaOptimizedButton;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 
 /**
  * Container for the expanded bubble view, handles rendering the caret and settings icon.
  */
-public class BubbleExpandedView extends LinearLayout implements View.OnClickListener {
+public class BubbleExpandedView extends LinearLayout {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleExpandedView" : TAG_BUBBLES;
 
     private enum ActivityViewStatus {
@@ -100,9 +98,6 @@
     private int mPointerWidth;
     private int mPointerHeight;
     private ShapeDrawable mPointerDrawable;
-    private Rect mTempRect = new Rect();
-    private int[] mTempLoc = new int[2];
-    private int mExpandedViewTouchSlop;
 
     @Nullable private Bubble mBubble;
 
@@ -224,7 +219,6 @@
         mMinHeight = res.getDimensionPixelSize(R.dimen.bubble_expanded_default_height);
         mOverflowHeight = res.getDimensionPixelSize(R.dimen.bubble_overflow_height);
         mPointerMargin = res.getDimensionPixelSize(R.dimen.bubble_pointer_margin);
-        mExpandedViewTouchSlop = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_slop);
     }
 
     @Override
@@ -239,7 +233,6 @@
         mPointerWidth = res.getDimensionPixelSize(R.dimen.bubble_pointer_width);
         mPointerHeight = res.getDimensionPixelSize(R.dimen.bubble_pointer_height);
 
-
         mPointerDrawable = new ShapeDrawable(TriangleShape.create(
                 mPointerWidth, mPointerHeight, true /* pointUp */));
         mPointerView.setBackground(mPointerDrawable);
@@ -248,7 +241,6 @@
         mSettingsIconHeight = getContext().getResources().getDimensionPixelSize(
                 R.dimen.bubble_manage_button_height);
         mSettingsIcon = findViewById(R.id.settings_button);
-        mSettingsIcon.setOnClickListener(this);
 
         mActivityView = new ActivityView(mContext, null /* attrs */, 0 /* defStyle */,
                 true /* singleTaskInstance */);
@@ -289,6 +281,19 @@
         return mBubble != null ? mBubble.getEntry() : null;
     }
 
+    void setManageClickListener(OnClickListener manageClickListener) {
+        findViewById(R.id.settings_button).setOnClickListener(manageClickListener);
+    }
+
+    /**
+     * Updates the ActivityView's obscured touchable region. This calls onLocationChanged, which
+     * results in a call to {@link BubbleStackView#subtractObscuredTouchableRegion}. This is useful
+     * if a view has been added or removed from on top of the ActivityView, such as the manage menu.
+     */
+    void updateObscuredTouchableRegion() {
+        mActivityView.onLocationChanged();
+    }
+
     void applyThemeAttrs() {
         final TypedArray ta = mContext.obtainStyledAttributes(
                 new int[] {
@@ -473,51 +478,6 @@
     }
 
     /**
-     * Whether the provided x, y values (in raw coordinates) are in a touchable area of the
-     * expanded view.
-     *
-     * The touchable areas are the ActivityView (plus some slop around it) and the manage button.
-     */
-    boolean intersectingTouchableContent(int rawX, int rawY) {
-        mTempRect.setEmpty();
-        if (mActivityView != null) {
-            mTempLoc = mActivityView.getLocationOnScreen();
-            mTempRect.set(mTempLoc[0] - mExpandedViewTouchSlop,
-                    mTempLoc[1] - mExpandedViewTouchSlop,
-                    mTempLoc[0] + mActivityView.getWidth() + mExpandedViewTouchSlop,
-                    mTempLoc[1] + mActivityView.getHeight() + mExpandedViewTouchSlop);
-        }
-        if (mTempRect.contains(rawX, rawY)) {
-            return true;
-        }
-        mTempLoc = mSettingsIcon.getLocationOnScreen();
-        mTempRect.set(mTempLoc[0],
-                mTempLoc[1],
-                mTempLoc[0] + mSettingsIcon.getWidth(),
-                mTempLoc[1] + mSettingsIcon.getHeight());
-        if (mTempRect.contains(rawX, rawY)) {
-            return true;
-        }
-        return false;
-    }
-
-    @Override
-    public void onClick(View view) {
-        if (mBubble == null) {
-            return;
-        }
-        int id = view.getId();
-        if (id == R.id.settings_button) {
-            Intent intent = mBubble.getSettingsIntent();
-            mStackView.collapseStack(() -> {
-                mContext.startActivityAsUser(intent, mBubble.getEntry().getSbn().getUser());
-                logBubbleClickEvent(mBubble,
-                        SysUiStatsLog.BUBBLE_UICHANGED__ACTION__HEADER_GO_TO_SETTINGS);
-            });
-        }
-    }
-
-    /**
      * Update appearance of the expanded view being displayed.
      */
     public void updateView() {
@@ -547,10 +507,8 @@
      * Position of the manage button displayed in the expanded view. Used for placing user
      * education about the manage button.
      */
-    public Rect getManageButtonLocationOnScreen() {
-        mTempLoc = mSettingsIcon.getLocationOnScreen();
-        return new Rect(mTempLoc[0], mTempLoc[1], mTempLoc[0] + mSettingsIcon.getWidth(),
-                mTempLoc[1] + mSettingsIcon.getHeight());
+    public void getManageButtonBoundsOnScreen(Rect rect) {
+        mSettingsIcon.getBoundsOnScreen(rect);
     }
 
     /**
@@ -611,26 +569,4 @@
         }
         return INVALID_DISPLAY;
     }
-
-    /**
-     * Logs bubble UI click event.
-     *
-     * @param bubble the bubble notification entry that user is interacting with.
-     * @param action the user interaction enum.
-     */
-    private void logBubbleClickEvent(Bubble bubble, int action) {
-        StatusBarNotification notification = bubble.getEntry().getSbn();
-        SysUiStatsLog.write(SysUiStatsLog.BUBBLE_UI_CHANGED,
-                notification.getPackageName(),
-                notification.getNotification().getChannelId(),
-                notification.getId(),
-                mStackView.getBubbleIndex(mStackView.getExpandedBubble()),
-                mStackView.getBubbleCount(),
-                action,
-                mStackView.getNormalizedXPosition(),
-                mStackView.getNormalizedYPosition(),
-                bubble.showInShade(),
-                bubble.isOngoing(),
-                false /* isAppForeground (unused) */);
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
index 0aabdff..c906931 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java
@@ -33,12 +33,14 @@
 import android.annotation.SuppressLint;
 import android.app.Notification;
 import android.content.Context;
+import android.content.Intent;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Color;
 import android.graphics.ColorMatrix;
 import android.graphics.ColorMatrixColorFilter;
+import android.graphics.Outline;
 import android.graphics.Paint;
 import android.graphics.Point;
 import android.graphics.PointF;
@@ -47,6 +49,7 @@
 import android.graphics.Region;
 import android.os.Bundle;
 import android.os.Vibrator;
+import android.service.notification.StatusBarNotification;
 import android.util.Log;
 import android.view.Choreographer;
 import android.view.DisplayCutout;
@@ -55,6 +58,7 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.ViewOutlineProvider;
 import android.view.ViewTreeObserver;
 import android.view.WindowInsets;
 import android.view.WindowManager;
@@ -62,6 +66,7 @@
 import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
 import android.view.animation.AccelerateDecelerateInterpolator;
 import android.widget.FrameLayout;
+import android.widget.ImageView;
 import android.widget.TextView;
 
 import androidx.annotation.MainThread;
@@ -83,6 +88,7 @@
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.shared.system.SysUiStatsLog;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.phone.NotificationShadeWindowController;
 import com.android.systemui.util.DismissCircleView;
 import com.android.systemui.util.FloatingContentCoordinator;
@@ -97,6 +103,7 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.function.Consumer;
 
 /**
  * Renders bubbles in a stack and handles animating expanded and collapsed states.
@@ -224,7 +231,7 @@
     private int mPointerHeight;
     private int mStatusBarHeight;
     private int mImeOffset;
-    private BubbleViewProvider mExpandedBubble;
+    @Nullable private BubbleViewProvider mExpandedBubble;
     private boolean mIsExpanded;
 
     /** Whether the stack is currently on the left side of the screen, or animating there. */
@@ -244,6 +251,10 @@
     }
 
     private BubbleController.BubbleExpandListener mExpandListener;
+
+    /** Callback to run when we want to unbubble the given notification's conversation. */
+    private Consumer<NotificationEntry> mUnbubbleConversationCallback;
+
     private SysUiState mSysUiState;
 
     private boolean mViewUpdatedRequested = false;
@@ -255,9 +266,7 @@
 
     private LayoutInflater mInflater;
 
-    // Used for determining view / touch intersection
-    int[] mTempLoc = new int[2];
-    RectF mTempRect = new RectF();
+    private Rect mTempRect = new Rect();
 
     private final List<Rect> mSystemGestureExclusionRects = Collections.singletonList(new Rect());
 
@@ -471,6 +480,11 @@
                 return true;
             }
 
+            // If the manage menu is visible, just hide it.
+            if (mShowingManage) {
+                showManageMenu(false /* show */);
+            }
+
             if (mBubbleData.isExpanded()) {
                 maybeShowManageEducation(false /* show */);
 
@@ -627,6 +641,13 @@
     private BubbleManageEducationView mManageEducationView;
     private boolean mAnimatingManageEducationAway;
 
+    private ViewGroup mManageMenu;
+    private ImageView mManageSettingsIcon;
+    private TextView mManageSettingsText;
+    private boolean mShowingManage = false;
+    private PhysicsAnimator.SpringConfig mManageSpringConfig = new PhysicsAnimator.SpringConfig(
+            SpringForce.STIFFNESS_MEDIUM, SpringForce.DAMPING_RATIO_LOW_BOUNCY);
+
     @SuppressLint("ClickableViewAccessibility")
     public BubbleStackView(Context context, BubbleData data,
             @Nullable SurfaceSynchronizer synchronizer,
@@ -689,6 +710,8 @@
         mExpandedViewContainer.setClipChildren(false);
         addView(mExpandedViewContainer);
 
+        setUpManageMenu();
+
         setUpFlyout();
         mFlyoutTransitionSpring.setSpring(new SpringForce()
                 .setStiffness(SpringForce.STIFFNESS_LOW)
@@ -838,7 +861,9 @@
         // ActivityViews, etc.) were touched. Collapse the stack if it's expanded.
         setOnTouchListener((view, ev) -> {
             if (ev.getAction() == MotionEvent.ACTION_DOWN) {
-                if (mBubbleData.isExpanded()) {
+                if (mShowingManage) {
+                    showManageMenu(false /* show */);
+                } else if (mBubbleData.isExpanded()) {
                     mBubbleData.setExpanded(false);
                 }
             }
@@ -847,6 +872,66 @@
         });
     }
 
+    private void setUpManageMenu() {
+        if (mManageMenu != null) {
+            removeView(mManageMenu);
+        }
+
+        mManageMenu = (ViewGroup) LayoutInflater.from(getContext()).inflate(
+                R.layout.bubble_manage_menu, this, false);
+        mManageMenu.setVisibility(View.INVISIBLE);
+
+        PhysicsAnimator.getInstance(mManageMenu).setDefaultSpringConfig(mManageSpringConfig);
+
+        final TypedArray ta = mContext.obtainStyledAttributes(
+                new int[] {android.R.attr.dialogCornerRadius});
+        final int menuCornerRadius = ta.getDimensionPixelSize(0, 0);
+        ta.recycle();
+
+        mManageMenu.setOutlineProvider(new ViewOutlineProvider() {
+            @Override
+            public void getOutline(View view, Outline outline) {
+                outline.setRoundRect(0, 0, view.getWidth(), view.getHeight(), menuCornerRadius);
+            }
+        });
+        mManageMenu.setClipToOutline(true);
+
+        mManageMenu.findViewById(R.id.bubble_manage_menu_dismiss_container).setOnClickListener(
+                view -> {
+                    showManageMenu(false /* show */);
+                    dismissBubbleIfExists(mBubbleData.getSelectedBubble());
+                });
+
+        mManageMenu.findViewById(R.id.bubble_manage_menu_dont_bubble_container).setOnClickListener(
+                view -> {
+                    showManageMenu(false /* show */);
+                    final Bubble bubble = mBubbleData.getSelectedBubble();
+                    if (bubble != null && mBubbleData.hasBubbleWithKey(bubble.getKey())) {
+                        mUnbubbleConversationCallback.accept(bubble.getEntry());
+                    }
+                });
+
+        mManageMenu.findViewById(R.id.bubble_manage_menu_settings_container).setOnClickListener(
+                view -> {
+                    showManageMenu(false /* show */);
+                    final Bubble bubble = mBubbleData.getSelectedBubble();
+                    if (bubble != null && mBubbleData.hasBubbleWithKey(bubble.getKey())) {
+                        final Intent intent = bubble.getSettingsIntent();
+                        collapseStack(() -> {
+                            mContext.startActivityAsUser(
+                                    intent, bubble.getEntry().getSbn().getUser());
+                            logBubbleClickEvent(
+                                    bubble,
+                                    SysUiStatsLog.BUBBLE_UICHANGED__ACTION__HEADER_GO_TO_SETTINGS);
+                        });
+                    }
+                });
+
+        mManageSettingsIcon = mManageMenu.findViewById(R.id.bubble_manage_menu_settings_icon);
+        mManageSettingsText = mManageMenu.findViewById(R.id.bubble_manage_menu_settings_name);
+        addView(mManageMenu);
+    }
+
     private void setUpUserEducation() {
         if (mUserEducationView != null) {
             removeView(mUserEducationView);
@@ -934,6 +1019,7 @@
         setUpFlyout();
         setUpOverflow();
         setUpUserEducation();
+        setUpManageMenu();
     }
 
     /** Respond to the phone being rotated by repositioning the stack and hiding any flyouts. */
@@ -960,6 +1046,9 @@
                 Math.max(0f, Math.min(1f, mVerticalPosPercentBeforeRotation));
         addOnLayoutChangeListener(mOrientationChangedListener);
         hideFlyoutImmediate();
+
+        mManageMenu.setVisibility(View.INVISIBLE);
+        mShowingManage = false;
     }
 
     @Override
@@ -1100,6 +1189,12 @@
         mExpandListener = listener;
     }
 
+    /** Sets the function to call to un-bubble the given conversation. */
+    public void setUnbubbleConversationCallback(
+            Consumer<NotificationEntry> unbubbleConversationCallback) {
+        mUnbubbleConversationCallback = unbubbleConversationCallback;
+    }
+
     /**
      * Whether the stack of bubbles is expanded or not.
      */
@@ -1361,15 +1456,14 @@
             mManageEducationView.setAlpha(0);
             mManageEducationView.setVisibility(VISIBLE);
             mManageEducationView.post(() -> {
-                final Rect position =
-                        mExpandedBubble.getExpandedView().getManageButtonLocationOnScreen();
+                mExpandedBubble.getExpandedView().getManageButtonBoundsOnScreen(mTempRect);
                 final int viewHeight = mManageEducationView.getManageViewHeight();
                 final int inset = getResources().getDimensionPixelSize(
                         R.dimen.bubbles_manage_education_top_inset);
                 mManageEducationView.bringToFront();
-                mManageEducationView.setManageViewPosition(position.left,
-                        position.top - viewHeight + inset);
-                mManageEducationView.setPointerPosition(position.centerX() - position.left);
+                mManageEducationView.setManageViewPosition(mTempRect.left,
+                        mTempRect.top - viewHeight + inset);
+                mManageEducationView.setPointerPosition(mTempRect.centerX() - mTempRect.left);
                 mManageEducationView.animate()
                         .setDuration(ANIMATE_STACK_USER_EDUCATION_DURATION)
                         .setInterpolator(FAST_OUT_SLOW_IN).alpha(1);
@@ -1443,6 +1537,9 @@
     }
 
     private void animateCollapse() {
+        // Hide the menu if it's visible.
+        showManageMenu(false);
+
         mIsExpanded = false;
         final BubbleViewProvider previouslySelected = mExpandedBubble;
         beforeExpandedViewAnimation();
@@ -1570,9 +1667,9 @@
      */
     @Override
     public void subtractObscuredTouchableRegion(Region touchableRegion, View view) {
-        // If the notification shade is expanded, we shouldn't let the ActivityView steal any touch
-        // events from any location.
-        if (mNotificationShadeWindowController.getPanelExpanded()) {
+        // If the notification shade is expanded, or the manage menu is open, we shouldn't let the
+        // ActivityView steal any touch events from any location.
+        if (mNotificationShadeWindowController.getPanelExpanded() || mShowingManage) {
             touchableRegion.setEmpty();
         }
     }
@@ -1658,17 +1755,20 @@
     private void dismissMagnetizedObject() {
         if (mIsExpanded) {
             final View draggedOutBubbleView = (View) mMagnetizedObject.getUnderlyingObject();
-            final Bubble draggedOutBubble = mBubbleData.getBubbleWithView(draggedOutBubbleView);
+            dismissBubbleIfExists(mBubbleData.getBubbleWithView(draggedOutBubbleView));
 
-            if (mBubbleData.hasBubbleWithKey(draggedOutBubble.getKey())) {
-                mBubbleData.notificationEntryRemoved(
-                        draggedOutBubble.getEntry(), BubbleController.DISMISS_USER_GESTURE);
-            }
         } else {
             mBubbleData.dismissAll(BubbleController.DISMISS_USER_GESTURE);
         }
     }
 
+    private void dismissBubbleIfExists(@Nullable Bubble bubble) {
+        if (bubble != null && mBubbleData.hasBubbleWithKey(bubble.getKey())) {
+            mBubbleData.notificationEntryRemoved(
+                    bubble.getEntry(), BubbleController.DISMISS_USER_GESTURE);
+        }
+    }
+
     /** Prepares and starts the desaturate/darken animation on the bubble stack. */
     private void animateDesaturateAndDarken(View targetView, boolean desaturateAndDarken) {
         mDesaturateAndDarkenTargetView = targetView;
@@ -1912,6 +2012,63 @@
         invalidate();
     }
 
+    private void showManageMenu(boolean show) {
+        mShowingManage = show;
+
+        // This should not happen, since the manage menu is only visible when there's an expanded
+        // bubble. If we end up in this state, just hide the menu immediately.
+        if (mExpandedBubble == null || mExpandedBubble.getExpandedView() == null) {
+            mManageMenu.setVisibility(View.INVISIBLE);
+            return;
+        }
+
+        // If available, update the manage menu's settings option with the expanded bubble's app
+        // name and icon.
+        if (show && mBubbleData.hasBubbleWithKey(mExpandedBubble.getKey())) {
+            final Bubble bubble = mBubbleData.getBubbleWithKey(mExpandedBubble.getKey());
+            mManageSettingsIcon.setImageDrawable(bubble.getBadgedAppIcon());
+            mManageSettingsText.setText(getResources().getString(
+                    R.string.bubbles_app_settings, bubble.getAppName()));
+        }
+
+        mExpandedBubble.getExpandedView().getManageButtonBoundsOnScreen(mTempRect);
+
+        // When the menu is open, it should be at these coordinates. This will make the menu's
+        // bottom left corner match up with the button's bottom left corner.
+        final float targetX = mTempRect.left;
+        final float targetY = mTempRect.bottom - mManageMenu.getHeight();
+
+        if (show) {
+            mManageMenu.setScaleX(0.5f);
+            mManageMenu.setScaleY(0.5f);
+            mManageMenu.setTranslationX(targetX - mManageMenu.getWidth() / 4);
+            mManageMenu.setTranslationY(targetY + mManageMenu.getHeight() / 4);
+            mManageMenu.setAlpha(0f);
+
+            PhysicsAnimator.getInstance(mManageMenu)
+                    .spring(DynamicAnimation.ALPHA, 1f)
+                    .spring(DynamicAnimation.SCALE_X, 1f)
+                    .spring(DynamicAnimation.SCALE_Y, 1f)
+                    .spring(DynamicAnimation.TRANSLATION_X, targetX)
+                    .spring(DynamicAnimation.TRANSLATION_Y, targetY)
+                    .start();
+
+            mManageMenu.setVisibility(View.VISIBLE);
+        } else {
+            PhysicsAnimator.getInstance(mManageMenu)
+                    .spring(DynamicAnimation.ALPHA, 0f)
+                    .spring(DynamicAnimation.SCALE_X, 0.5f)
+                    .spring(DynamicAnimation.SCALE_Y, 0.5f)
+                    .spring(DynamicAnimation.TRANSLATION_X, targetX - mManageMenu.getWidth() / 4)
+                    .spring(DynamicAnimation.TRANSLATION_Y, targetY + mManageMenu.getHeight() / 4)
+                    .withEndActions(() -> mManageMenu.setVisibility(View.INVISIBLE))
+                    .start();
+        }
+
+        // Update the AV's obscured touchable region for the new menu visibility state.
+        mExpandedBubble.getExpandedView().updateObscuredTouchableRegion();
+    }
+
     private void updateExpandedBubble() {
         if (DEBUG_BUBBLE_STACK_VIEW) {
             Log.d(TAG, "updateExpandedBubble()");
@@ -1921,6 +2078,7 @@
                 && mExpandedBubble.getExpandedView() != null) {
             BubbleExpandedView bev = mExpandedBubble.getExpandedView();
             mExpandedViewContainer.addView(bev);
+            bev.setManageClickListener((view) -> showManageMenu(!mShowingManage));
             bev.populateExpandedView();
             mExpandedViewContainer.setVisibility(VISIBLE);
             mExpandedViewContainer.setAlpha(1.0f);
@@ -2089,4 +2247,26 @@
         }
         return bubbles;
     }
+
+    /**
+     * Logs bubble UI click event.
+     *
+     * @param bubble the bubble notification entry that user is interacting with.
+     * @param action the user interaction enum.
+     */
+    private void logBubbleClickEvent(Bubble bubble, int action) {
+        StatusBarNotification notification = bubble.getEntry().getSbn();
+        SysUiStatsLog.write(SysUiStatsLog.BUBBLE_UI_CHANGED,
+                notification.getPackageName(),
+                notification.getNotification().getChannelId(),
+                notification.getId(),
+                getBubbleIndex(getExpandedBubble()),
+                getBubbleCount(),
+                action,
+                getNormalizedXPosition(),
+                getNormalizedYPosition(),
+                bubble.showInShade(),
+                bubble.isOngoing(),
+                false /* isAppForeground (unused) */);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java
index c96f9a4..8a57a73 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewInfoTask.java
@@ -116,6 +116,7 @@
         ShortcutInfo shortcutInfo;
         String appName;
         Bitmap badgedBubbleImage;
+        Drawable badgedAppIcon;
         int dotColor;
         Path dotPath;
         Bubble.FlyoutMessage flyoutMessage;
@@ -176,6 +177,7 @@
             }
 
             BitmapInfo badgeBitmapInfo = iconFactory.getBadgeBitmap(badgedIcon);
+            info.badgedAppIcon = badgedIcon;
             info.badgedBubbleImage = iconFactory.getBubbleBitmap(bubbleDrawable,
                     badgeBitmapInfo).icon;
 
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java
index ef84c73..ca3e2e2 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleViewProvider.java
@@ -20,15 +20,17 @@
 import android.graphics.Path;
 import android.view.View;
 
+import androidx.annotation.Nullable;
+
 /**
  * Interface to represent actual Bubbles and UI elements that act like bubbles, like BubbleOverflow.
  */
 interface BubbleViewProvider {
-    BubbleExpandedView getExpandedView();
+    @Nullable BubbleExpandedView getExpandedView();
 
     void setContentVisibility(boolean visible);
 
-    View getIconView();
+    @Nullable View getIconView();
 
     void logUIEvent(int bubbleCount, int action, float normalX, float normalY, int index);
 
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt
index 7e8fec7..e84f439 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsBindingControllerImpl.kt
@@ -31,6 +31,7 @@
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.util.concurrency.DelayableExecutor
 import dagger.Lazy
+import java.util.concurrent.atomic.AtomicBoolean
 import javax.inject.Inject
 import javax.inject.Singleton
 
@@ -284,13 +285,19 @@
         val requestLimit: Long
     ) : IControlsSubscriber.Stub() {
         val loadedControls = ArrayList<Control>()
-        private var isTerminated = false
+        private var isTerminated = AtomicBoolean(false)
         private var _loadCancelInternal: (() -> Unit)? = null
         private lateinit var subscription: IControlsSubscription
 
+        /**
+         * Potentially cancel a subscriber. The subscriber may also have terminated, in which case
+         * the request is ignored.
+         */
         fun loadCancel() = Runnable {
-            Log.d(TAG, "Cancel load requested")
-            _loadCancelInternal?.invoke()
+            _loadCancelInternal?.let {
+                Log.d(TAG, "Canceling loadSubscribtion")
+                it.invoke()
+            }
         }
 
         override fun onSubscribe(token: IBinder, subs: IControlsSubscription) {
@@ -301,7 +308,7 @@
 
         override fun onNext(token: IBinder, c: Control) {
             backgroundExecutor.execute {
-                if (isTerminated) return@execute
+                if (isTerminated.get()) return@execute
 
                 loadedControls.add(c)
 
@@ -325,13 +332,15 @@
         }
 
         private fun maybeTerminateAndRun(postTerminateFn: Runnable) {
-            if (isTerminated) return
+            if (isTerminated.get()) return
 
-            isTerminated = true
             _loadCancelInternal = {}
             currentProvider?.cancelLoadTimeout()
 
-            backgroundExecutor.execute(postTerminateFn)
+            backgroundExecutor.execute {
+                isTerminated.compareAndSet(false, true)
+                postTerminateFn.run()
+            }
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
index 79a5b0a..bc97c10 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsController.kt
@@ -52,19 +52,17 @@
      * Load all available [Control] for a given service.
      *
      * @param componentName the [ComponentName] of the [ControlsProviderService] to load from
-     * @param dataCallback a callback in which to retrieve the result.
+     * @param dataCallback a callback in which to retrieve the result
+     * @param cancelWrapper a callback to receive a [Runnable] that can be run to cancel the
+     *                      request
      */
     fun loadForComponent(
         componentName: ComponentName,
-        dataCallback: Consumer<LoadData>
+        dataCallback: Consumer<LoadData>,
+        cancelWrapper: Consumer<Runnable>
     )
 
     /**
-     * Cancels a pending load call
-     */
-    fun cancelLoad()
-
-    /**
      * Request to subscribe for favorited controls per structure
      *
      * @param structureInfo structure to limit the subscription to
diff --git a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
index 5626a5d..8e88756 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/controller/ControlsControllerImpl.kt
@@ -77,8 +77,6 @@
 
     private var userChanging: Boolean = true
 
-    private var loadCanceller: Runnable? = null
-
     private var seedingInProgress = false
     private val seedingCallbacks = mutableListOf<Consumer<Boolean>>()
 
@@ -276,28 +274,29 @@
 
     override fun loadForComponent(
         componentName: ComponentName,
-        dataCallback: Consumer<ControlsController.LoadData>
+        dataCallback: Consumer<ControlsController.LoadData>,
+        cancelWrapper: Consumer<Runnable>
     ) {
         if (!confirmAvailability()) {
             if (userChanging) {
                 // Try again later, userChanging should not last forever. If so, we have bigger
                 // problems. This will return a runnable that allows to cancel the delayed version,
                 // it will not be able to cancel the load if
-                loadCanceller = executor.executeDelayed(
-                        { loadForComponent(componentName, dataCallback) },
-                        USER_CHANGE_RETRY_DELAY,
-                        TimeUnit.MILLISECONDS
+                executor.executeDelayed(
+                    { loadForComponent(componentName, dataCallback, cancelWrapper) },
+                    USER_CHANGE_RETRY_DELAY,
+                    TimeUnit.MILLISECONDS
                 )
-            } else {
-                dataCallback.accept(createLoadDataObject(emptyList(), emptyList(), true))
             }
-            return
+
+            dataCallback.accept(createLoadDataObject(emptyList(), emptyList(), true))
         }
-        loadCanceller = bindingController.bindAndLoad(
+
+        cancelWrapper.accept(
+            bindingController.bindAndLoad(
                 componentName,
                 object : ControlsBindingController.LoadCallback {
                     override fun accept(controls: List<Control>) {
-                        loadCanceller = null
                         executor.execute {
                             val favoritesForComponentKeys = Favorites
                                 .getControlsForComponent(componentName).map { it.controlId }
@@ -333,7 +332,6 @@
                     }
 
                     override fun error(message: String) {
-                        loadCanceller = null
                         executor.execute {
                             val controls = Favorites.getStructuresForComponent(componentName)
                                     .flatMap { st ->
@@ -348,6 +346,7 @@
                         }
                     }
                 }
+            )
         )
     }
 
@@ -432,12 +431,6 @@
         seedingCallbacks.clear()
     }
 
-    override fun cancelLoad() {
-        loadCanceller?.let {
-            executor.execute(it)
-        }
-    }
-
     private fun createRemovedStatus(
         componentName: ComponentName,
         controlInfo: ControlInfo,
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
index 273e47c..640c90d 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsEditingActivity.kt
@@ -32,6 +32,7 @@
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.controls.controller.ControlsControllerImpl
 import com.android.systemui.controls.controller.StructureInfo
+import com.android.systemui.globalactions.GlobalActionsComponent
 import com.android.systemui.settings.CurrentUserTracker
 import com.android.systemui.util.LifecycleActivity
 import javax.inject.Inject
@@ -41,7 +42,8 @@
  */
 class ControlsEditingActivity @Inject constructor(
     private val controller: ControlsControllerImpl,
-    broadcastDispatcher: BroadcastDispatcher
+    broadcastDispatcher: BroadcastDispatcher,
+    private val globalActionsComponent: GlobalActionsComponent
 ) : LifecycleActivity() {
 
     companion object {
@@ -86,12 +88,20 @@
         bindViews()
 
         bindButtons()
+    }
 
+    override fun onStart() {
+        super.onStart()
         setUpList()
 
         currentUserTracker.startTracking()
     }
 
+    override fun onStop() {
+        super.onStop()
+        currentUserTracker.stopTracking()
+    }
+
     private fun bindViews() {
         setContentView(R.layout.controls_management)
 
@@ -129,12 +139,21 @@
             }
         }
 
+        val rootView = requireViewById<ViewGroup>(R.id.controls_management_root)
         saveButton = requireViewById<Button>(R.id.done).apply {
             isEnabled = false
             setText(R.string.save)
             setOnClickListener {
                 saveFavorites()
-                finishAffinity()
+                ControlsAnimations.exitAnimation(
+                    rootView,
+                    object : Runnable {
+                        override fun run() {
+                            finish()
+                        }
+                    }
+                ).start()
+                globalActionsComponent.handleShowGlobalActionsMenu()
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
index 4a34705..9a2ccb5 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsFavoritingActivity.kt
@@ -20,7 +20,6 @@
 import android.content.ComponentName
 import android.content.Intent
 import android.content.res.Configuration
-import android.graphics.drawable.Drawable
 import android.os.Bundle
 import android.text.TextUtils
 import android.view.Gravity
@@ -29,7 +28,6 @@
 import android.view.ViewStub
 import android.widget.Button
 import android.widget.FrameLayout
-import android.widget.ImageView
 import android.widget.TextView
 import androidx.viewpager2.widget.ViewPager2
 import com.android.systemui.Prefs
@@ -40,6 +38,7 @@
 import com.android.systemui.controls.controller.ControlsControllerImpl
 import com.android.systemui.controls.controller.StructureInfo
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.globalactions.GlobalActionsComponent
 import com.android.systemui.settings.CurrentUserTracker
 import com.android.systemui.util.LifecycleActivity
 import java.text.Collator
@@ -51,7 +50,8 @@
     @Main private val executor: Executor,
     private val controller: ControlsControllerImpl,
     private val listingController: ControlsListingController,
-    broadcastDispatcher: BroadcastDispatcher
+    broadcastDispatcher: BroadcastDispatcher,
+    private val globalActionsComponent: GlobalActionsComponent
 ) : LifecycleActivity() {
 
     companion object {
@@ -74,14 +74,15 @@
     private lateinit var structurePager: ViewPager2
     private lateinit var statusText: TextView
     private lateinit var titleView: TextView
-    private lateinit var iconView: ImageView
-    private lateinit var iconFrame: View
     private lateinit var pageIndicator: ManagementPageIndicator
     private var mTooltipManager: TooltipManager? = null
     private lateinit var doneButton: View
+    private lateinit var otherAppsButton: View
     private var listOfStructures = emptyList<StructureContainer>()
 
     private lateinit var comparator: Comparator<StructureContainer>
+    private var cancelLoadRunnable: Runnable? = null
+    private var isPagerLoaded = false
 
     private val currentUserTracker = object : CurrentUserTracker(broadcastDispatcher) {
         private val startingUser = controller.currentUserId
@@ -95,17 +96,10 @@
     }
 
     private val listingCallback = object : ControlsListingController.ControlsListingCallback {
-        private var icon: Drawable? = null
 
         override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) {
-            val newIcon = serviceInfos.firstOrNull { it.componentName == component }?.loadIcon()
-            if (icon == newIcon) return
-            icon = newIcon
-            executor.execute {
-                if (icon != null) {
-                    iconView.setImageDrawable(icon)
-                }
-                iconFrame.visibility = if (icon != null) View.VISIBLE else View.GONE
+            if (serviceInfos.size > 1) {
+                otherAppsButton.visibility = View.VISIBLE
             }
         }
     }
@@ -124,14 +118,6 @@
         component = intent.getParcelableExtra<ComponentName>(Intent.EXTRA_COMPONENT_NAME)
 
         bindViews()
-
-        setUpPager()
-
-        loadControls()
-
-        listingController.addCallback(listingCallback)
-
-        currentUserTracker.startTracking()
     }
 
     private val controlsModelCallback = object : ControlsModel.ControlsModelCallback {
@@ -180,7 +166,7 @@
                     ControlsAnimations.enterAnimation(pageIndicator).start()
                     ControlsAnimations.enterAnimation(structurePager).start()
                 }
-            })
+            }, Consumer { runnable -> cancelLoadRunnable = runnable })
         }
     }
 
@@ -275,8 +261,6 @@
         }
         requireViewById<TextView>(R.id.subtitle).text =
                 resources.getText(R.string.controls_favorite_subtitle)
-        iconView = requireViewById(com.android.internal.R.id.icon)
-        iconFrame = requireViewById(R.id.icon_frame)
         structurePager = requireViewById<ViewPager2>(R.id.structure_pager)
         structurePager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
             override fun onPageSelected(position: Int) {
@@ -288,8 +272,7 @@
     }
 
     private fun bindButtons() {
-        requireViewById<Button>(R.id.other_apps).apply {
-            visibility = View.VISIBLE
+        otherAppsButton = requireViewById<Button>(R.id.other_apps).apply {
             setOnClickListener {
                 val i = Intent()
                 i.setComponent(
@@ -299,6 +282,7 @@
             }
         }
 
+        val rootView = requireViewById<ViewGroup>(R.id.controls_management_root)
         doneButton = requireViewById<Button>(R.id.done).apply {
             isEnabled = false
             setOnClickListener {
@@ -309,7 +293,16 @@
                         StructureInfo(component!!, it.structureName, favoritesForStorage)
                     )
                 }
-                finishAffinity()
+
+                ControlsAnimations.exitAnimation(
+                    rootView,
+                    object : Runnable {
+                        override fun run() {
+                            finish()
+                        }
+                    }
+                ).start()
+                globalActionsComponent.handleShowGlobalActionsMenu()
             }
         }
     }
@@ -319,15 +312,39 @@
         mTooltipManager?.hide(false)
     }
 
+    override fun onStart() {
+        super.onStart()
+
+        listingController.addCallback(listingCallback)
+        currentUserTracker.startTracking()
+    }
+
+    override fun onResume() {
+        super.onResume()
+
+        // only do once, to make sure that any user changes do not get replaces if resume is called
+        // more than once
+        if (!isPagerLoaded) {
+            setUpPager()
+            loadControls()
+            isPagerLoaded = true
+        }
+    }
+
+    override fun onStop() {
+        super.onStop()
+
+        listingController.removeCallback(listingCallback)
+        currentUserTracker.stopTracking()
+    }
+
     override fun onConfigurationChanged(newConfig: Configuration) {
         super.onConfigurationChanged(newConfig)
         mTooltipManager?.hide(false)
     }
 
     override fun onDestroy() {
-        currentUserTracker.stopTracking()
-        listingController.removeCallback(listingCallback)
-        controller.cancelLoad()
+        cancelLoadRunnable?.run()
         super.onDestroy()
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
index 59a8b32..0044854 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsProviderSelectorActivity.kt
@@ -21,6 +21,7 @@
 import android.content.Intent
 import android.os.Bundle
 import android.view.LayoutInflater
+import android.view.View
 import android.view.ViewGroup
 import android.view.ViewStub
 import android.widget.Button
@@ -84,8 +85,27 @@
         }
 
         recyclerView = requireViewById(R.id.list)
-        recyclerView.alpha = 0.0f
         recyclerView.layoutManager = LinearLayoutManager(applicationContext)
+
+        requireViewById<TextView>(R.id.title).apply {
+            text = resources.getText(R.string.controls_providers_title)
+        }
+
+        requireViewById<Button>(R.id.other_apps).apply {
+            visibility = View.VISIBLE
+            setText(com.android.internal.R.string.cancel)
+            setOnClickListener {
+                this@ControlsProviderSelectorActivity.finishAffinity()
+            }
+        }
+        requireViewById<View>(R.id.done).visibility = View.GONE
+    }
+
+    override fun onStart() {
+        super.onStart()
+        currentUserTracker.startTracking()
+
+        recyclerView.alpha = 0.0f
         recyclerView.adapter = AppAdapter(
                 backExecutor,
                 executor,
@@ -105,16 +125,11 @@
                 }
             })
         }
+    }
 
-        requireViewById<TextView>(R.id.title).apply {
-            text = resources.getText(R.string.controls_providers_title)
-        }
-
-        requireViewById<Button>(R.id.done).setOnClickListener {
-            this@ControlsProviderSelectorActivity.finishAffinity()
-        }
-
-        currentUserTracker.startTracking()
+    override fun onStop() {
+        super.onStop()
+        currentUserTracker.stopTracking()
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java
index 88f96a8..8c572fe 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java
@@ -24,7 +24,6 @@
 import androidx.annotation.Nullable;
 
 import com.android.keyguard.KeyguardViewController;
-import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.dock.DockManagerImpl;
 import com.android.systemui.plugins.qs.QSFactory;
@@ -34,7 +33,6 @@
 import com.android.systemui.qs.tileimpl.QSFactoryImpl;
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.RecentsImplementation;
-import com.android.systemui.settings.CurrentUserContextTracker;
 import com.android.systemui.stackdivider.DividerModule;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
@@ -138,15 +136,4 @@
     @Binds
     abstract KeyguardViewController bindKeyguardViewController(
             StatusBarKeyguardViewManager statusBarKeyguardViewManager);
-
-    @Singleton
-    @Provides
-    static CurrentUserContextTracker provideCurrentUserContextTracker(
-            Context context,
-            BroadcastDispatcher broadcastDispatcher) {
-        CurrentUserContextTracker tracker =
-                new CurrentUserContextTracker(context, broadcastDispatcher);
-        tracker.initialize();
-        return tracker;
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index 2e9ce12..90cd13f 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -29,6 +29,7 @@
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.recents.Recents;
+import com.android.systemui.settings.dagger.SettingsModule;
 import com.android.systemui.stackdivider.Divider;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.notification.collection.inflation.NotificationRowBinder;
@@ -61,6 +62,7 @@
             ConcurrencyModule.class,
             LogModule.class,
             PeopleHubModule.class,
+            SettingsModule.class
         },
         subcomponents = {StatusBarComponent.class,
                 NotificationRowComponent.class,
diff --git a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
index 649fe89..a24fede 100644
--- a/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/globalactions/GlobalActionsDialog.java
@@ -129,7 +129,6 @@
 import com.android.systemui.util.EmergencyDialerConstants;
 import com.android.systemui.util.RingerModeTracker;
 import com.android.systemui.util.leak.RotationUtils;
-import com.android.systemui.volume.SystemUIInterpolators.LogAccelerateInterpolator;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -482,7 +481,8 @@
      */
     @VisibleForTesting
     protected int getMaxShownPowerItems() {
-        if (shouldUseControlsLayout()) {
+        // TODO: Overflow disabled on keyguard while we solve for touch blocking issues.
+        if (shouldUseControlsLayout() && !mKeyguardShowing) {
             return mResources.getInteger(com.android.systemui.R.integer.power_menu_max_columns);
         } else {
             return Integer.MAX_VALUE;
@@ -1898,6 +1898,7 @@
 
         private ControlsUiController mControlsUiController;
         private ViewGroup mControlsView;
+        private ViewGroup mContainer;
 
         ActionsDialog(Context context, MyAdapter adapter, MyOverflowAdapter overflowAdapter,
                 GlobalActionsPanelPlugin.PanelViewController plugin,
@@ -2050,6 +2051,11 @@
             });
             mGlobalActionsLayout.setRotationListener(this::onRotate);
             mGlobalActionsLayout.setAdapter(mAdapter);
+            mContainer = findViewById(com.android.systemui.R.id.global_actions_container);
+            // Some legacy dialog layouts don't have the outer container
+            if (mContainer == null) {
+                mContainer = mGlobalActionsLayout;
+            }
 
             mOverflowPopup = createPowerOverflowPopup();
 
@@ -2176,10 +2182,10 @@
             mHadTopUi = mNotificationShadeWindowController.getForceHasTopUi();
             mNotificationShadeWindowController.setForceHasTopUi(true);
             mBackgroundDrawable.setAlpha(0);
-            mGlobalActionsLayout.setTranslationX(mGlobalActionsLayout.getAnimationOffsetX());
-            mGlobalActionsLayout.setTranslationY(mGlobalActionsLayout.getAnimationOffsetY());
-            mGlobalActionsLayout.setAlpha(0);
-            mGlobalActionsLayout.animate()
+            mContainer.setTranslationX(mGlobalActionsLayout.getAnimationOffsetX());
+            mContainer.setTranslationY(mGlobalActionsLayout.getAnimationOffsetY());
+            mContainer.setAlpha(0);
+            mContainer.animate()
                     .alpha(1)
                     .translationX(0)
                     .translationY(0)
@@ -2211,16 +2217,16 @@
         @Override
         public void dismiss() {
             dismissWithAnimation(() -> {
-                mGlobalActionsLayout.setTranslationX(0);
-                mGlobalActionsLayout.setTranslationY(0);
-                mGlobalActionsLayout.setAlpha(1);
-                mGlobalActionsLayout.animate()
+                mContainer.setTranslationX(0);
+                mContainer.setTranslationY(0);
+                mContainer.setAlpha(1);
+                mContainer.animate()
                         .alpha(0)
                         .translationX(mGlobalActionsLayout.getAnimationOffsetX())
                         .translationY(mGlobalActionsLayout.getAnimationOffsetY())
-                        .setDuration(550)
+                        .setDuration(450)
                         .withEndAction(this::completeDismiss)
-                        .setInterpolator(new LogAccelerateInterpolator())
+                        .setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
                         .setUpdateListener(animation -> {
                             float animatedValue = 1f - animation.getAnimatedFraction();
                             int alpha = (int) (animatedValue * mScrimAlpha * 255);
@@ -2234,7 +2240,7 @@
 
         private void dismissForControlsActivity() {
             dismissWithAnimation(() -> {
-                ViewGroup root = (ViewGroup) mGlobalActionsLayout.getRootView();
+                ViewGroup root = (ViewGroup) mGlobalActionsLayout.getParent();
                 ControlsAnimations.exitAnimation(root, this::completeDismiss).start();
             });
         }
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 14e3e93..123cf78 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -61,6 +61,18 @@
         return buffer;
     }
 
+    /** Provides a logging buffer for all logs related to the data layer of notifications. */
+    @Provides
+    @Singleton
+    @NotifInteractionLog
+    public static LogBuffer provideNotifInteractionLogBuffer(
+            LogcatEchoTracker echoTracker,
+            DumpManager dumpManager) {
+        LogBuffer buffer = new LogBuffer("NotifInteractionLog", 50, 10, echoTracker);
+        buffer.attach(dumpManager);
+        return buffer;
+    }
+
     /** Provides a logging buffer for all logs related to Quick Settings. */
     @Provides
     @Singleton
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/NotifInteractionLog.java b/packages/SystemUI/src/com/android/systemui/log/dagger/NotifInteractionLog.java
new file mode 100644
index 0000000..20fc6ff
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/NotifInteractionLog.java
@@ -0,0 +1,36 @@
+/*
+ * 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.log.dagger;
+
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+import com.android.systemui.log.LogBuffer;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+
+import javax.inject.Qualifier;
+
+/**
+ * A {@link LogBuffer} for messages related to the user interacting with notifications (e.g.
+ * clicking on them).
+ */
+@Qualifier
+@Documented
+@Retention(RUNTIME)
+public @interface NotifInteractionLog {
+}
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
index f25de6a..233d24b 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java
@@ -26,14 +26,18 @@
 import android.content.pm.ResolveInfo;
 import android.content.res.ColorStateList;
 import android.graphics.Bitmap;
+import android.graphics.ImageDecoder;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.Icon;
 import android.graphics.drawable.RippleDrawable;
 import android.media.MediaDescription;
 import android.media.MediaMetadata;
+import android.media.ThumbnailUtils;
 import android.media.session.MediaController;
 import android.media.session.MediaSession;
 import android.media.session.PlaybackState;
+import android.net.Uri;
 import android.service.media.MediaBrowserService;
 import android.util.Log;
 import android.view.LayoutInflater;
@@ -59,6 +63,7 @@
 import com.android.systemui.qs.QSMediaBrowser;
 import com.android.systemui.util.Assert;
 
+import java.io.IOException;
 import java.util.List;
 import java.util.concurrent.Executor;
 
@@ -99,6 +104,13 @@
             com.android.internal.R.id.action4
     };
 
+    // URI fields to try loading album art from
+    private static final String[] ART_URIS = {
+            MediaMetadata.METADATA_KEY_ALBUM_ART_URI,
+            MediaMetadata.METADATA_KEY_ART_URI,
+            MediaMetadata.METADATA_KEY_DISPLAY_ICON_URI
+    };
+
     private final MediaController.Callback mSessionCallback = new MediaController.Callback() {
         @Override
         public void onSessionDestroyed() {
@@ -205,14 +217,16 @@
      * Update the media panel view for the given media session
      * @param token
      * @param iconDrawable
+     * @param largeIcon
      * @param iconColor
      * @param bgColor
      * @param contentIntent
      * @param appNameString
      * @param key
      */
-    public void setMediaSession(MediaSession.Token token, Drawable iconDrawable, int iconColor,
-            int bgColor, PendingIntent contentIntent, String appNameString, String key) {
+    public void setMediaSession(MediaSession.Token token, Drawable iconDrawable, Icon largeIcon,
+            int iconColor, int bgColor, PendingIntent contentIntent, String appNameString,
+            String key) {
         // Ensure that component names are updated if token has changed
         if (mToken == null || !mToken.equals(token)) {
             mToken = token;
@@ -303,7 +317,7 @@
         ImageView albumView = mMediaNotifView.findViewById(R.id.album_art);
         if (albumView != null) {
             // Resize art in a background thread
-            mBackgroundExecutor.execute(() -> processAlbumArt(mediaMetadata, albumView));
+            mBackgroundExecutor.execute(() -> processAlbumArt(mediaMetadata, largeIcon, albumView));
         }
 
         // Song name
@@ -396,30 +410,82 @@
      * @param albumView view to hold the album art
      */
     protected void processAlbumArt(MediaDescription description, ImageView albumView) {
-        Bitmap albumArt = description.getIconBitmap();
-        //TODO check other fields (b/151054111, b/152067055)
+        Bitmap albumArt = null;
+
+        // First try loading from URI
+        albumArt = loadBitmapFromUri(description.getIconUri());
+
+        // Then check bitmap
+        if (albumArt == null) {
+            albumArt = description.getIconBitmap();
+        }
+
         processAlbumArtInternal(albumArt, albumView);
     }
 
     /**
      * Process album art for layout
      * @param metadata media metadata
+     * @param largeIcon from notification, checked as a fallback if metadata does not have art
      * @param albumView view to hold the album art
      */
-    private void processAlbumArt(MediaMetadata metadata, ImageView albumView) {
-        Bitmap albumArt = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
-        //TODO check other fields (b/151054111, b/152067055)
+    private void processAlbumArt(MediaMetadata metadata, Icon largeIcon, ImageView albumView) {
+        Bitmap albumArt = null;
+
+        // First look in URI fields
+        for (String field : ART_URIS) {
+            String uriString = metadata.getString(field);
+            if (uriString != null) {
+                albumArt = loadBitmapFromUri(Uri.parse(uriString));
+                if (albumArt != null) {
+                    Log.d(TAG, "loaded art from " + field);
+                    break;
+                }
+            }
+        }
+
+        // Then check bitmap field
+        if (albumArt == null) {
+            albumArt = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
+        }
+
+        // Finally try the notification's largeIcon
+        if (albumArt == null && largeIcon != null) {
+            albumArt = largeIcon.getBitmap();
+        }
+
         processAlbumArtInternal(albumArt, albumView);
     }
 
-    private void processAlbumArtInternal(Bitmap albumArt, ImageView albumView) {
-        float radius = mContext.getResources().getDimension(R.dimen.qs_media_corner_radius);
+    /**
+     * Load a bitmap from a URI
+     * @param uri
+     * @return bitmap, or null if couldn't be loaded
+     */
+    private Bitmap loadBitmapFromUri(Uri uri) {
+        ImageDecoder.Source source = ImageDecoder.createSource(mContext.getContentResolver(), uri);
+        try {
+            return ImageDecoder.decodeBitmap(source);
+        } catch (IOException e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    /**
+     * Resize and crop the image if provided and update the control view
+     * @param albumArt Bitmap of art to display, or null to hide view
+     * @param albumView View that will hold the art
+     */
+    private void processAlbumArtInternal(@Nullable Bitmap albumArt, ImageView albumView) {
+        // Resize
         RoundedBitmapDrawable roundedDrawable = null;
         if (albumArt != null) {
+            float radius = mContext.getResources().getDimension(R.dimen.qs_media_corner_radius);
             Bitmap original = albumArt.copy(Bitmap.Config.ARGB_8888, true);
             int albumSize = (int) mContext.getResources().getDimension(
                     R.dimen.qs_media_album_size);
-            Bitmap scaled = Bitmap.createScaledBitmap(original, albumSize, albumSize, false);
+            Bitmap scaled = ThumbnailUtils.extractThumbnail(original, albumSize, albumSize);
             roundedDrawable = RoundedBitmapDrawableFactory.create(mContext.getResources(), scaled);
             roundedDrawable.setCornerRadius(radius);
         } else {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java b/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java
index 0f06566..9e574a1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSMediaPlayer.java
@@ -23,6 +23,7 @@
 import android.content.pm.PackageManager;
 import android.content.res.ColorStateList;
 import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
 import android.media.MediaDescription;
 import android.media.session.MediaController;
 import android.media.session.MediaSession;
@@ -115,8 +116,8 @@
         }
 
         // Set what we can normally
-        super.setMediaSession(token, icon, iconColor, bgColor, contentIntent, appName.toString(),
-                null);
+        super.setMediaSession(token, icon, null, iconColor, bgColor, contentIntent,
+                appName.toString(), null);
 
         // Then add info from MediaDescription
         ImageView albumView = mMediaNotifView.findViewById(R.id.album_art);
@@ -149,6 +150,7 @@
      * Update media panel view for the given media session
      * @param token token for this media session
      * @param icon app notification icon
+     * @param largeIcon notification's largeIcon, used as a fallback for album art
      * @param iconColor foreground color (for text, icons)
      * @param bgColor background color
      * @param actionsContainer a LinearLayout containing the media action buttons
@@ -156,11 +158,12 @@
      * @param appName Application title
      * @param key original notification's key
      */
-    public void setMediaSession(MediaSession.Token token, Drawable icon, int iconColor,
-            int bgColor, View actionsContainer, PendingIntent contentIntent, String appName,
-            String key) {
+    public void setMediaSession(MediaSession.Token token, Drawable icon, Icon largeIcon,
+            int iconColor, int bgColor, View actionsContainer, PendingIntent contentIntent,
+            String appName, String key) {
 
-        super.setMediaSession(token, icon, iconColor, bgColor, contentIntent, appName, key);
+        super.setMediaSession(token, icon, largeIcon, iconColor, bgColor, contentIntent, appName,
+                key);
 
         // Media controls
         if (actionsContainer != null) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
index 1eb5778..1252008 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSPanel.java
@@ -32,6 +32,7 @@
 import android.content.res.Resources;
 import android.graphics.Color;
 import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
 import android.media.MediaDescription;
 import android.media.session.MediaSession;
 import android.metrics.LogMaker;
@@ -225,14 +226,16 @@
      * Add or update a player for the associated media session
      * @param token
      * @param icon
+     * @param largeIcon
      * @param iconColor
      * @param bgColor
      * @param actionsContainer
      * @param notif
      * @param key
      */
-    public void addMediaSession(MediaSession.Token token, Drawable icon, int iconColor, int bgColor,
-            View actionsContainer, StatusBarNotification notif, String key) {
+    public void addMediaSession(MediaSession.Token token, Drawable icon, Icon largeIcon,
+            int iconColor, int bgColor, View actionsContainer, StatusBarNotification notif,
+            String key) {
         if (!useQsMediaPlayer(mContext)) {
             // Shouldn't happen, but just in case
             Log.e(TAG, "Tried to add media session without player!");
@@ -296,7 +299,7 @@
         Log.d(TAG, "setting player session");
         String appName = Notification.Builder.recoverBuilder(getContext(), notif.getNotification())
                 .loadHeaderAppName();
-        player.setMediaSession(token, icon, iconColor, bgColor, actionsContainer,
+        player.setMediaSession(token, icon, largeIcon, iconColor, bgColor, actionsContainer,
                 notif.getNotification().contentIntent, appName, key);
 
         if (mMediaPlayers.size() > 0) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java
index 7ba7c5f..89b36da 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSMediaPlayer.java
@@ -19,6 +19,7 @@
 import android.app.PendingIntent;
 import android.content.Context;
 import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
 import android.media.session.MediaController;
 import android.media.session.MediaSession;
 import android.view.View;
@@ -58,6 +59,7 @@
      * Update media panel view for the given media session
      * @param token token for this media session
      * @param icon app notification icon
+     * @param largeIcon notification's largeIcon, used as a fallback for album art
      * @param iconColor foreground color (for text, icons)
      * @param bgColor background color
      * @param actionsContainer a LinearLayout containing the media action buttons
@@ -66,8 +68,9 @@
      * @param contentIntent Intent to send when user taps on the view
      * @param key original notification's key
      */
-    public void setMediaSession(MediaSession.Token token, Drawable icon, int iconColor, int bgColor,
-            View actionsContainer, int[] actionsToShow, PendingIntent contentIntent, String key) {
+    public void setMediaSession(MediaSession.Token token, Drawable icon, Icon largeIcon,
+            int iconColor, int bgColor, View actionsContainer, int[] actionsToShow,
+            PendingIntent contentIntent, String key) {
         // Only update if this is a different session and currently playing
         String oldPackage = "";
         if (getController() != null) {
@@ -82,7 +85,7 @@
             return;
         }
 
-        super.setMediaSession(token, icon, iconColor, bgColor, contentIntent, null, key);
+        super.setMediaSession(token, icon, largeIcon, iconColor, bgColor, contentIntent, null, key);
 
         LinearLayout parentActionsLayout = (LinearLayout) actionsContainer;
         int i = 0;
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
index 1efe663..70454d4 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/GlobalScreenshot.java
@@ -36,6 +36,7 @@
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.Insets;
@@ -184,9 +185,11 @@
     private final LinearLayout mActionsView;
     private final ImageView mBackgroundProtection;
     private final FrameLayout mDismissButton;
+    private final ImageView mDismissImage;
 
     private Bitmap mScreenBitmap;
     private Animator mScreenshotAnimation;
+    private boolean mInDarkMode = false;
 
     private float mScreenshotOffsetXPx;
     private float mScreenshotOffsetYPx;
@@ -250,6 +253,7 @@
             mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_EXPLICIT_DISMISSAL);
             clearScreenshot("dismiss_button");
         });
+        mDismissImage = mDismissButton.findViewById(R.id.global_screenshot_dismiss_image);
 
         mScreenshotFlash = mScreenshotLayout.findViewById(R.id.global_screenshot_flash);
         mScreenshotSelectorView = mScreenshotLayout.findViewById(R.id.global_screenshot_selector);
@@ -356,6 +360,8 @@
         mScreenBitmap.setHasAlpha(false);
         mScreenBitmap.prepareToDraw();
 
+        updateDarkTheme();
+
         mWindowManager.addView(mScreenshotLayout, mWindowLayoutParams);
         mScreenshotLayout.getViewTreeObserver().addOnComputeInternalInsetsListener(this);
 
@@ -434,7 +440,7 @@
      * Clears current screenshot
      */
     private void clearScreenshot(String reason) {
-        Log.e(TAG, "clearing screenshot: " + reason);
+        Log.v(TAG, "clearing screenshot: " + reason);
         if (mScreenshotLayout.isAttachedToWindow()) {
             mWindowManager.removeView(mScreenshotLayout);
         }
@@ -454,6 +460,41 @@
     }
 
     /**
+     * Update assets (called when the dark theme status changes). We only need to update the dismiss
+     * button and the actions container background, since the buttons are re-inflated on demand.
+     */
+    private void reloadAssets() {
+        mDismissImage.setImageDrawable(mContext.getDrawable(R.drawable.screenshot_cancel));
+        mActionsContainer.setBackground(
+                mContext.getDrawable(R.drawable.action_chip_container_background));
+
+    }
+
+    /**
+     * Checks the current dark theme status and updates if it has changed.
+     */
+    private void updateDarkTheme() {
+        int currentNightMode = mContext.getResources().getConfiguration().uiMode
+                & Configuration.UI_MODE_NIGHT_MASK;
+        switch (currentNightMode) {
+            case Configuration.UI_MODE_NIGHT_NO:
+                // Night mode is not active, we're using the light theme
+                if (mInDarkMode) {
+                    mInDarkMode = false;
+                    reloadAssets();
+                }
+                break;
+            case Configuration.UI_MODE_NIGHT_YES:
+                // Night mode is active, we're using dark theme
+                if (!mInDarkMode) {
+                    mInDarkMode = true;
+                    reloadAssets();
+                }
+                break;
+        }
+    }
+
+    /**
      * Starts the animation after taking the screenshot
      */
     private void startAnimation(final Consumer<Uri> finisher, int w, int h,
@@ -660,7 +701,7 @@
                     R.layout.global_screenshot_action_chip, mActionsView, false);
             Toast scrollNotImplemented = Toast.makeText(
                     mContext, "Not implemented", Toast.LENGTH_SHORT);
-            scrollChip.setText("Extend"); // TODO : add resource and translate
+            scrollChip.setText("Extend"); // TODO: add resource and translate
             scrollChip.setIcon(
                     Icon.createWithResource(mContext, R.drawable.ic_arrow_downward), true);
             scrollChip.setOnClickListener(v -> {
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
index a6e083b..f68cb74 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotService.java
@@ -132,7 +132,7 @@
     @Override
     public boolean onUnbind(Intent intent) {
         if (mScreenshot != null) mScreenshot.stopScreenshot();
-        // TODO (mkephart) remove once notifications flow is fully deprecated
+        // TODO remove once notifications flow is fully deprecated
         if (mScreenshotLegacy != null) mScreenshotLegacy.stopScreenshot();
         return true;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserContextTracker.kt b/packages/SystemUI/src/com/android/systemui/settings/CurrentUserContextTracker.kt
index 4de978c..825a7f3 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/CurrentUserContextTracker.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/CurrentUserContextTracker.kt
@@ -22,14 +22,13 @@
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.util.Assert
 import java.lang.IllegalStateException
-import javax.inject.Inject
-import javax.inject.Singleton
 
 /**
  * Tracks a reference to the context for the current user
+ *
+ * Constructor is injected at SettingsModule
  */
-@Singleton
-class CurrentUserContextTracker @Inject constructor(
+class CurrentUserContextTracker internal constructor(
     private val sysuiContext: Context,
     broadcastDispatcher: BroadcastDispatcher
 ) {
diff --git a/packages/SystemUI/src/com/android/systemui/settings/dagger/SettingsModule.java b/packages/SystemUI/src/com/android/systemui/settings/dagger/SettingsModule.java
new file mode 100644
index 0000000..2c5c3ce
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/settings/dagger/SettingsModule.java
@@ -0,0 +1,48 @@
+/*
+ * 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.settings.dagger;
+
+import android.content.Context;
+
+import com.android.systemui.broadcast.BroadcastDispatcher;
+import com.android.systemui.settings.CurrentUserContextTracker;
+
+import javax.inject.Singleton;
+
+import dagger.Module;
+import dagger.Provides;
+
+/**
+ * Dagger Module for classes found within the com.android.systemui.settings package.
+ */
+@Module
+public interface SettingsModule {
+
+    /**
+     * Provides and initializes a CurrentUserContextTracker
+     */
+    @Singleton
+    @Provides
+    static CurrentUserContextTracker provideCurrentUserContextTracker(
+            Context context,
+            BroadcastDispatcher broadcastDispatcher) {
+        CurrentUserContextTracker tracker =
+                new CurrentUserContextTracker(context, broadcastDispatcher);
+        tracker.initialize();
+        return tracker;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java
index 1b752452..8c24c54 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NavigationBarController.java
@@ -44,6 +44,7 @@
 import com.android.systemui.statusbar.phone.LightBarController;
 import com.android.systemui.statusbar.phone.NavigationBarFragment;
 import com.android.systemui.statusbar.phone.NavigationBarView;
+import com.android.systemui.statusbar.phone.NavigationModeController;
 import com.android.systemui.statusbar.policy.BatteryController;
 
 import javax.inject.Inject;
@@ -139,7 +140,8 @@
                     ? Dependency.get(LightBarController.class)
                     : new LightBarController(context,
                             Dependency.get(DarkIconDispatcher.class),
-                            Dependency.get(BatteryController.class));
+                            Dependency.get(BatteryController.class),
+                            Dependency.get(NavigationModeController.class));
             navBar.setLightBarController(lightBarController);
 
             // TODO(b/118592525): to support multi-display, we start to add something which is
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
index 4e6df0a..d364689 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClicker.java
@@ -23,11 +23,14 @@
 
 import com.android.systemui.DejankUtils;
 import com.android.systemui.bubbles.BubbleController;
+import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.phone.StatusBar;
 
 import java.util.Optional;
 
+import javax.inject.Inject;
+
 /**
  * Click handler for generic clicks on notifications. Clicks on specific areas (expansion caret,
  * app ops icon, etc) are handled elsewhere.
@@ -35,15 +38,19 @@
 public final class NotificationClicker implements View.OnClickListener {
     private static final String TAG = "NotificationClicker";
 
-    private final Optional<StatusBar> mStatusBar;
     private final BubbleController mBubbleController;
+    private final NotificationClickerLogger mLogger;
+    private final Optional<StatusBar> mStatusBar;
     private final NotificationActivityStarter mNotificationActivityStarter;
 
-    public NotificationClicker(Optional<StatusBar> statusBar,
+    private NotificationClicker(
             BubbleController bubbleController,
+            NotificationClickerLogger logger,
+            Optional<StatusBar> statusBar,
             NotificationActivityStarter notificationActivityStarter) {
-        mStatusBar = statusBar;
         mBubbleController = bubbleController;
+        mLogger = logger;
+        mStatusBar = statusBar;
         mNotificationActivityStarter = notificationActivityStarter;
     }
 
@@ -58,25 +65,26 @@
                 SystemClock.uptimeMillis(), v, "NOTIFICATION_CLICK"));
 
         final ExpandableNotificationRow row = (ExpandableNotificationRow) v;
-        final StatusBarNotification sbn = row.getEntry().getSbn();
-        if (sbn == null) {
-            Log.e(TAG, "NotificationClicker called on an unclickable notification,");
-            return;
-        }
+        final NotificationEntry entry = row.getEntry();
+        mLogger.logOnClick(entry);
 
         // Check if the notification is displaying the menu, if so slide notification back
         if (isMenuVisible(row)) {
+            mLogger.logMenuVisible(entry);
             row.animateTranslateNotification(0);
             return;
         } else if (row.isChildInGroup() && isMenuVisible(row.getNotificationParent())) {
+            mLogger.logParentMenuVisible(entry);
             row.getNotificationParent().animateTranslateNotification(0);
             return;
         } else if (row.isSummaryWithChildren() && row.areChildrenExpanded()) {
             // We never want to open the app directly if the user clicks in between
             // the notifications.
+            mLogger.logChildrenExpanded(entry);
             return;
         } else if (row.areGutsExposed()) {
             // ignore click if guts are exposed
+            mLogger.logGutsExposed(entry);
             return;
         }
 
@@ -88,7 +96,7 @@
             mBubbleController.collapseStack();
         }
 
-        mNotificationActivityStarter.onNotificationClicked(sbn, row);
+        mNotificationActivityStarter.onNotificationClicked(entry.getSbn(), row);
     }
 
     private boolean isMenuVisible(ExpandableNotificationRow row) {
@@ -107,4 +115,30 @@
             row.setOnClickListener(null);
         }
     }
+
+    /** Daggerized builder for NotificationClicker. */
+    public static class Builder {
+        private final BubbleController mBubbleController;
+        private final NotificationClickerLogger mLogger;
+
+        @Inject
+        public Builder(
+                BubbleController bubbleController,
+                NotificationClickerLogger logger) {
+            mBubbleController = bubbleController;
+            mLogger = logger;
+        }
+
+        /** Builds an instance. */
+        public NotificationClicker build(
+                Optional<StatusBar> statusBar,
+                NotificationActivityStarter notificationActivityStarter
+        ) {
+            return new NotificationClicker(
+                    mBubbleController,
+                    mLogger,
+                    statusBar,
+                    notificationActivityStarter);
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt
new file mode 100644
index 0000000..fbf033b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationClickerLogger.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification
+
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel
+import com.android.systemui.log.dagger.NotifInteractionLog
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import javax.inject.Inject
+
+class NotificationClickerLogger @Inject constructor(
+    @NotifInteractionLog private val buffer: LogBuffer
+) {
+    fun logOnClick(entry: NotificationEntry) {
+        buffer.log(TAG, LogLevel.DEBUG, {
+            str1 = entry.key
+            str2 = entry.ranking.channel.id
+        }, {
+            "CLICK $str1 (channel=$str2)"
+        })
+    }
+
+    fun logMenuVisible(entry: NotificationEntry) {
+        buffer.log(TAG, LogLevel.DEBUG, {
+            str1 = entry.key
+        }, {
+            "Ignoring click on $str1; menu is visible"
+        })
+    }
+
+    fun logParentMenuVisible(entry: NotificationEntry) {
+        buffer.log(TAG, LogLevel.DEBUG, {
+            str1 = entry.key
+        }, {
+            "Ignoring click on $str1; parent menu is visible"
+        })
+    }
+
+    fun logChildrenExpanded(entry: NotificationEntry) {
+        buffer.log(TAG, LogLevel.DEBUG, {
+            str1 = entry.key
+        }, {
+            "Ignoring click on $str1; children are expanded"
+        })
+    }
+
+    fun logGutsExposed(entry: NotificationEntry) {
+        buffer.log(TAG, LogLevel.DEBUG, {
+            str1 = entry.key
+        }, {
+            "Ignoring click on $str1; guts are exposed"
+        })
+    }
+}
+
+private const val TAG = "NotificationClicker"
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 7f2f898..c975404 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
@@ -17,7 +17,6 @@
 package com.android.systemui.statusbar.notification.init
 
 import android.service.notification.StatusBarNotification
-import com.android.systemui.bubbles.BubbleController
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption
 import com.android.systemui.statusbar.FeatureFlags
 import com.android.systemui.statusbar.NotificationListener
@@ -62,12 +61,12 @@
     private val deviceProvisionedController: DeviceProvisionedController,
     private val notificationRowBinder: NotificationRowBinderImpl,
     private val remoteInputUriController: RemoteInputUriController,
-    private val bubbleController: BubbleController,
     private val groupManager: NotificationGroupManager,
     private val groupAlertTransferHelper: NotificationGroupAlertTransferHelper,
     private val headsUpManager: HeadsUpManager,
     private val headsUpController: HeadsUpController,
-    private val headsUpViewBinder: HeadsUpViewBinder
+    private val headsUpViewBinder: HeadsUpViewBinder,
+    private val clickerBuilder: NotificationClicker.Builder
 ) : NotificationsController {
 
     override fun initialize(
@@ -87,10 +86,7 @@
         listController.bind()
 
         notificationRowBinder.setNotificationClicker(
-                NotificationClicker(
-                        Optional.of(statusBar),
-                        bubbleController,
-                        notificationActivityStarter))
+                clickerBuilder.build(Optional.of(statusBar), notificationActivityStarter))
         notificationRowBinder.setUpWithPresenter(
                 presenter,
                 listContainer,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java
index 796f22c..b96cff8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/wrapper/NotificationMediaTemplateViewWrapper.java
@@ -191,6 +191,7 @@
             Drawable iconDrawable = notif.getSmallIcon().loadDrawable(mContext);
             panel.getMediaPlayer().setMediaSession(token,
                     iconDrawable,
+                    notif.getLargeIcon(),
                     tintColor,
                     mBackgroundColor,
                     mActions,
@@ -201,6 +202,7 @@
                     com.android.systemui.R.id.quick_settings_panel);
             bigPanel.addMediaSession(token,
                     iconDrawable,
+                    notif.getLargeIcon(),
                     tintColor,
                     mBackgroundColor,
                     mActions,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
index 7d422e3..f9119c7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/EdgeBackGestureHandler.java
@@ -53,11 +53,13 @@
 import com.android.internal.policy.GestureNavigationSettingsObserver;
 import com.android.systemui.Dependency;
 import com.android.systemui.R;
+import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.bubbles.BubbleController;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.plugins.NavigationEdgeBackPlugin;
 import com.android.systemui.plugins.PluginListener;
 import com.android.systemui.recents.OverviewProxyService;
+import com.android.systemui.settings.CurrentUserTracker;
 import com.android.systemui.shared.plugins.PluginManager;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
 import com.android.systemui.shared.system.QuickStepContract;
@@ -74,7 +76,7 @@
 /**
  * Utility class to handle edge swipes for back gesture
  */
-public class EdgeBackGestureHandler implements DisplayListener,
+public class EdgeBackGestureHandler extends CurrentUserTracker implements DisplayListener,
         PluginListener<NavigationEdgeBackPlugin>, ProtoTraceable<SystemUiTraceProto> {
 
     private static final String TAG = "EdgeBackGestureHandler";
@@ -165,6 +167,7 @@
     private boolean mIsGesturalModeEnabled;
     private boolean mIsEnabled;
     private boolean mIsNavBarShownTransiently;
+    private boolean mIsBackGestureAllowed;
 
     private InputMonitor mInputMonitor;
     private InputEventReceiver mInputEventReceiver;
@@ -200,7 +203,7 @@
 
     public EdgeBackGestureHandler(Context context, OverviewProxyService overviewProxyService,
             SysUiState sysUiFlagContainer, PluginManager pluginManager) {
-        final Resources res = context.getResources();
+        super(Dependency.get(BroadcastDispatcher.class));
         mContext = context;
         mDisplayId = context.getDisplayId();
         mMainExecutor = context.getMainExecutor();
@@ -216,20 +219,30 @@
                 ViewConfiguration.getLongPressTimeout());
 
         mGestureNavigationSettingsObserver = new GestureNavigationSettingsObserver(
-                mContext.getMainThreadHandler(), mContext, () -> updateCurrentUserResources(res));
+                mContext.getMainThreadHandler(), mContext, this::updateCurrentUserResources);
 
-        updateCurrentUserResources(res);
+        updateCurrentUserResources();
         sysUiFlagContainer.addCallback(sysUiFlags -> mSysUiFlags = sysUiFlags);
     }
 
-    public void updateCurrentUserResources(Resources res) {
+    public void updateCurrentUserResources() {
+        Resources res = Dependency.get(NavigationModeController.class).getCurrentUserContext()
+                .getResources();
         mEdgeWidthLeft = mGestureNavigationSettingsObserver.getLeftSensitivity(res);
         mEdgeWidthRight = mGestureNavigationSettingsObserver.getRightSensitivity(res);
+        mIsBackGestureAllowed =
+                !mGestureNavigationSettingsObserver.areNavigationButtonForcedVisible();
 
         mBottomGestureHeight = res.getDimensionPixelSize(
                 com.android.internal.R.dimen.navigation_bar_gesture_height);
     }
 
+    @Override
+    public void onUserSwitched(int newUserId) {
+        updateIsEnabled();
+        updateCurrentUserResources();
+    }
+
     /**
      * @see NavigationBarView#onAttachedToWindow()
      */
@@ -243,6 +256,7 @@
                 Settings.Global.getUriFor(FIXED_ROTATION_TRANSFORM_SETTING_NAME),
                 false /* notifyForDescendants */, mFixedRotationObserver, UserHandle.USER_ALL);
         updateIsEnabled();
+        startTracking();
     }
 
     /**
@@ -255,6 +269,7 @@
         }
         mContext.getContentResolver().unregisterContentObserver(mFixedRotationObserver);
         updateIsEnabled();
+        stopTracking();
     }
 
     private void setRotationCallbacks(boolean enable) {
@@ -269,10 +284,13 @@
         }
     }
 
-    public void onNavigationModeChanged(int mode, Context currentUserContext) {
+    /**
+     * @see NavigationModeController.ModeChangedListener#onNavigationModeChanged
+     */
+    public void onNavigationModeChanged(int mode) {
         mIsGesturalModeEnabled = QuickStepContract.isGesturalMode(mode);
         updateIsEnabled();
-        updateCurrentUserResources(currentUserContext.getResources());
+        updateCurrentUserResources();
     }
 
     public void onNavBarTransientStateChanged(boolean isTransient) {
@@ -363,6 +381,10 @@
         updateDisplaySize();
     }
 
+    public boolean isHandlingGestures() {
+        return mIsEnabled && mIsBackGestureAllowed;
+    }
+
     private WindowManager.LayoutParams createLayoutParams() {
         Resources resources = mContext.getResources();
         WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
@@ -469,9 +491,9 @@
             mIsOnLeftEdge = ev.getX() <= mEdgeWidthLeft + mLeftInset;
             mLogGesture = false;
             mInRejectedExclusion = false;
-            mAllowGesture = !QuickStepContract.isBackGestureDisabled(mSysUiFlags)
-                    && isWithinTouchRegion((int) ev.getX(), (int) ev.getY())
-                    && !mDisabledForQuickstep;
+            mAllowGesture = !mDisabledForQuickstep && mIsBackGestureAllowed
+                    && !QuickStepContract.isBackGestureDisabled(mSysUiFlags)
+                    && isWithinTouchRegion((int) ev.getX(), (int) ev.getY());
             if (mAllowGesture) {
                 mEdgeBackPlugin.setIsLeftPanel(mIsOnLeftEdge);
                 mEdgeBackPlugin.onMotionEvent(ev);
@@ -599,6 +621,7 @@
     public void dump(PrintWriter pw) {
         pw.println("EdgeBackGestureHandler:");
         pw.println("  mIsEnabled=" + mIsEnabled);
+        pw.println("  mIsBackGestureAllowed=" + mIsBackGestureAllowed);
         pw.println("  mAllowGesture=" + mAllowGesture);
         pw.println("  mDisabledForQuickstep=" + mDisabledForQuickstep);
         pw.println("  mInRejectedExclusion" + mInRejectedExclusion);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
index d35e1e1..3e5eb5f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightBarController.java
@@ -33,6 +33,7 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
 import com.android.systemui.plugins.DarkIconDispatcher;
+import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.statusbar.policy.BatteryController;
 
 import java.io.FileDescriptor;
@@ -58,6 +59,7 @@
     private AppearanceRegion[] mAppearanceRegions = new AppearanceRegion[0];
     private int mStatusBarMode;
     private int mNavigationBarMode;
+    private int mNavigationMode;
     private final Color mDarkModeColor;
 
     /**
@@ -84,11 +86,14 @@
 
     @Inject
     public LightBarController(Context ctx, DarkIconDispatcher darkIconDispatcher,
-            BatteryController batteryController) {
+            BatteryController batteryController, NavigationModeController navModeController) {
         mDarkModeColor = Color.valueOf(ctx.getColor(R.color.dark_mode_icon_color_single_tone));
         mStatusBarIconController = (SysuiDarkIconDispatcher) darkIconDispatcher;
         mBatteryController = batteryController;
         mBatteryController.addCallback(this);
+        mNavigationMode = navModeController.addListener((mode) -> {
+            mNavigationMode = mode;
+        });
     }
 
     public void setNavigationBar(LightBarTransitionsController navigationBar) {
@@ -234,7 +239,8 @@
     }
 
     private void updateNavigation() {
-        if (mNavigationBarController != null) {
+        if (mNavigationBarController != null
+                && !QuickStepContract.isGesturalMode(mNavigationMode)) {
             mNavigationBarController.setIconsDark(mNavigationLight, animateChange());
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
index 84aecd4..2978772 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationBarView.java
@@ -123,7 +123,7 @@
     private KeyButtonDrawable mRecentIcon;
     private KeyButtonDrawable mDockedIcon;
 
-    private final EdgeBackGestureHandler mEdgeBackGestureHandler;
+    private EdgeBackGestureHandler mEdgeBackGestureHandler;
     private final DeadZone mDeadZone;
     private boolean mDeadZoneConsuming = false;
     private final NavigationBarTransitions mBarTransitions;
@@ -244,7 +244,7 @@
     private final OnComputeInternalInsetsListener mOnComputeInternalInsetsListener = info -> {
         // When the nav bar is in 2-button or 3-button mode, or when IME is visible in fully
         // gestural mode, the entire nav bar should be touchable.
-        if (!isGesturalMode(mNavBarMode) || mImeVisible) {
+        if (!mEdgeBackGestureHandler.isHandlingGestures() || mImeVisible) {
             info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_FRAME);
             return;
         }
@@ -296,8 +296,6 @@
                 R.style.RotateButtonCCWStart90,
                 isGesturalMode ? mFloatingRotationButton : rotateSuggestionButton);
 
-        final ContextualButton backButton = new ContextualButton(R.id.back, 0);
-
         mConfiguration = new Configuration();
         mTmpLastConfiguration = new Configuration();
         mConfiguration.updateFrom(context.getResources().getConfiguration());
@@ -305,7 +303,7 @@
         mScreenPinningNotify = new ScreenPinningNotify(mContext);
         mBarTransitions = new NavigationBarTransitions(this, Dependency.get(CommandQueue.class));
 
-        mButtonDispatchers.put(R.id.back, backButton);
+        mButtonDispatchers.put(R.id.back, new ButtonDispatcher(R.id.back));
         mButtonDispatchers.put(R.id.home, new ButtonDispatcher(R.id.home));
         mButtonDispatchers.put(R.id.home_handle, new ButtonDispatcher(R.id.home_handle));
         mButtonDispatchers.put(R.id.recent_apps, new ButtonDispatcher(R.id.recent_apps));
@@ -659,7 +657,7 @@
         boolean disableHomeHandle = disableRecent
                 && ((mDisabledFlags & View.STATUS_BAR_DISABLE_HOME) != 0);
 
-        boolean disableBack = !useAltBack && (isGesturalMode(mNavBarMode)
+        boolean disableBack = !useAltBack && (mEdgeBackGestureHandler.isHandlingGestures()
                 || ((mDisabledFlags & View.STATUS_BAR_DISABLE_BACK) != 0));
 
         // When screen pinning, don't hide back and home when connected service or back and
@@ -686,9 +684,9 @@
             }
         }
 
-        getBackButton().setVisibility(disableBack      ? View.INVISIBLE : View.VISIBLE);
-        getHomeButton().setVisibility(disableHome      ? View.INVISIBLE : View.VISIBLE);
-        getRecentsButton().setVisibility(disableRecent ? View.INVISIBLE : View.VISIBLE);
+        getBackButton().setVisibility(disableBack       ? View.INVISIBLE : View.VISIBLE);
+        getHomeButton().setVisibility(disableHome       ? View.INVISIBLE : View.VISIBLE);
+        getRecentsButton().setVisibility(disableRecent  ? View.INVISIBLE : View.VISIBLE);
         getHomeHandle().setVisibility(disableHomeHandle ? View.INVISIBLE : View.VISIBLE);
     }
 
@@ -838,10 +836,9 @@
 
     @Override
     public void onNavigationModeChanged(int mode) {
-        Context curUserCtx = Dependency.get(NavigationModeController.class).getCurrentUserContext();
         mNavBarMode = mode;
         mBarTransitions.onNavigationModeChanged(mNavBarMode);
-        mEdgeBackGestureHandler.onNavigationModeChanged(mNavBarMode, curUserCtx);
+        mEdgeBackGestureHandler.onNavigationModeChanged(mNavBarMode);
         mRecentsOnboarding.onNavigationModeChanged(mNavBarMode);
         getRotateSuggestionButton().onNavigationModeChanged(mNavBarMode);
 
@@ -864,6 +861,7 @@
 
     @Override
     public void onFinishInflate() {
+        super.onFinishInflate();
         mNavigationInflaterView = findViewById(R.id.navigation_inflater);
         mNavigationInflaterView.setButtonDispatchers(mButtonDispatchers);
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java
index d24ccf3..6061b1e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NavigationModeController.java
@@ -17,9 +17,6 @@
 package com.android.systemui.statusbar.phone;
 
 import static android.content.Intent.ACTION_OVERLAY_CHANGED;
-import static android.content.Intent.ACTION_PREFERRED_ACTIVITY_CHANGED;
-import static android.os.UserHandle.USER_CURRENT;
-import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY;
 
@@ -38,17 +35,14 @@
 import android.provider.Settings;
 import android.provider.Settings.Secure;
 import android.util.Log;
-import android.util.SparseBooleanArray;
 
 import com.android.systemui.Dumpable;
 import com.android.systemui.dagger.qualifiers.UiBackground;
 import com.android.systemui.shared.system.ActivityManagerWrapper;
-import com.android.systemui.statusbar.policy.DeviceProvisionedController;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
@@ -70,104 +64,34 @@
     private final Context mContext;
     private Context mCurrentUserContext;
     private final IOverlayManager mOverlayManager;
-    private final DeviceProvisionedController mDeviceProvisionedController;
     private final Executor mUiBgExecutor;
 
-    private SparseBooleanArray mRestoreGesturalNavBarMode = new SparseBooleanArray();
-
     private ArrayList<ModeChangedListener> mListeners = new ArrayList<>();
 
     private BroadcastReceiver mReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
-            switch (intent.getAction()) {
-                case ACTION_OVERLAY_CHANGED:
                     if (DEBUG) {
                         Log.d(TAG, "ACTION_OVERLAY_CHANGED");
                     }
                     updateCurrentInteractionMode(true /* notify */);
-                    break;
-            }
         }
     };
 
-    private final DeviceProvisionedController.DeviceProvisionedListener mDeviceProvisionedCallback =
-            new DeviceProvisionedController.DeviceProvisionedListener() {
-                @Override
-                public void onDeviceProvisionedChanged() {
-                    if (DEBUG) {
-                        Log.d(TAG, "onDeviceProvisionedChanged: "
-                                + mDeviceProvisionedController.isDeviceProvisioned());
-                    }
-                    // Once the device has been provisioned, check if we can restore gestural nav
-                    restoreGesturalNavOverlayIfNecessary();
-                }
-
-                @Override
-                public void onUserSetupChanged() {
-                    if (DEBUG) {
-                        Log.d(TAG, "onUserSetupChanged: "
-                                + mDeviceProvisionedController.isCurrentUserSetup());
-                    }
-                    // Once the user has been setup, check if we can restore gestural nav
-                    restoreGesturalNavOverlayIfNecessary();
-                }
-
-                @Override
-                public void onUserSwitched() {
-                    if (DEBUG) {
-                        Log.d(TAG, "onUserSwitched: "
-                                + ActivityManagerWrapper.getInstance().getCurrentUserId());
-                    }
-
-                    // Update the nav mode for the current user
-                    updateCurrentInteractionMode(true /* notify */);
-
-                    // When switching users, defer enabling the gestural nav overlay until the user
-                    // is all set up
-                    deferGesturalNavOverlayIfNecessary();
-                }
-            };
-
     @Inject
-    public NavigationModeController(Context context,
-            DeviceProvisionedController deviceProvisionedController,
-            @UiBackground Executor uiBgExecutor) {
+    public NavigationModeController(Context context, @UiBackground Executor uiBgExecutor) {
         mContext = context;
         mCurrentUserContext = context;
         mOverlayManager = IOverlayManager.Stub.asInterface(
                 ServiceManager.getService(Context.OVERLAY_SERVICE));
         mUiBgExecutor = uiBgExecutor;
-        mDeviceProvisionedController = deviceProvisionedController;
-        mDeviceProvisionedController.addCallback(mDeviceProvisionedCallback);
 
         IntentFilter overlayFilter = new IntentFilter(ACTION_OVERLAY_CHANGED);
         overlayFilter.addDataScheme("package");
         overlayFilter.addDataSchemeSpecificPart("android", PatternMatcher.PATTERN_LITERAL);
         mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, overlayFilter, null, null);
 
-        IntentFilter preferredActivityFilter = new IntentFilter(ACTION_PREFERRED_ACTIVITY_CHANGED);
-        mContext.registerReceiverAsUser(mReceiver, UserHandle.ALL, preferredActivityFilter, null,
-                null);
-
         updateCurrentInteractionMode(false /* notify */);
-
-        // Check if we need to defer enabling gestural nav
-        deferGesturalNavOverlayIfNecessary();
-    }
-
-    private boolean setGestureModeOverlayForMainLauncher() {
-        if (getCurrentInteractionMode(mCurrentUserContext) == NAV_BAR_MODE_GESTURAL) {
-            // Already in gesture mode
-            return true;
-        }
-
-        Log.d(TAG, "Switching system navigation to full-gesture mode:"
-                + " contextUser="
-                + mCurrentUserContext.getUserId());
-
-        setModeOverlay(NAV_BAR_MODE_GESTURAL_OVERLAY, USER_CURRENT);
-        return true;
     }
 
     public void updateCurrentInteractionMode(boolean notify) {
@@ -176,10 +100,9 @@
         if (mode == NAV_BAR_MODE_GESTURAL) {
             switchToDefaultGestureNavOverlayIfNecessary();
         }
-        mUiBgExecutor.execute(() -> {
+        mUiBgExecutor.execute(() ->
             Settings.Secure.putString(mCurrentUserContext.getContentResolver(),
-                    Secure.NAVIGATION_MODE, String.valueOf(mode));
-        });
+                    Secure.NAVIGATION_MODE, String.valueOf(mode)));
         if (DEBUG) {
             Log.e(TAG, "updateCurrentInteractionMode: mode=" + mode);
             dumpAssetPaths(mCurrentUserContext);
@@ -230,61 +153,11 @@
         }
     }
 
-    private void deferGesturalNavOverlayIfNecessary() {
-        final int userId = mDeviceProvisionedController.getCurrentUser();
-        mRestoreGesturalNavBarMode.put(userId, false);
-        if (mDeviceProvisionedController.isDeviceProvisioned()
-                && mDeviceProvisionedController.isCurrentUserSetup()) {
-            // User is already setup and device is provisioned, nothing to do
-            if (DEBUG) {
-                Log.d(TAG, "deferGesturalNavOverlayIfNecessary: device is provisioned and user is "
-                        + "setup");
-            }
-            return;
-        }
-
-        ArrayList<String> defaultOverlays = new ArrayList<>();
-        try {
-            defaultOverlays.addAll(Arrays.asList(mOverlayManager.getDefaultOverlayPackages()));
-        } catch (RemoteException e) {
-            Log.e(TAG, "deferGesturalNavOverlayIfNecessary: failed to fetch default overlays");
-        }
-        if (!defaultOverlays.contains(NAV_BAR_MODE_GESTURAL_OVERLAY)) {
-            // No default gesture nav overlay
-            if (DEBUG) {
-                Log.d(TAG, "deferGesturalNavOverlayIfNecessary: no default gestural overlay, "
-                        + "default=" + defaultOverlays);
-            }
-            return;
-        }
-
-        // If the default is gestural, force-enable three button mode until the device is
-        // provisioned
-        setModeOverlay(NAV_BAR_MODE_3BUTTON_OVERLAY, USER_CURRENT);
-        mRestoreGesturalNavBarMode.put(userId, true);
-
-        if (DEBUG) {
-            Log.d(TAG, "deferGesturalNavOverlayIfNecessary: setting to 3 button mode");
-        }
-    }
-
-    private void restoreGesturalNavOverlayIfNecessary() {
-        if (DEBUG) {
-            Log.d(TAG, "restoreGesturalNavOverlayIfNecessary: needs restore="
-                    + mRestoreGesturalNavBarMode);
-        }
-        final int userId = mDeviceProvisionedController.getCurrentUser();
-        if (mRestoreGesturalNavBarMode.get(userId)) {
-            // Restore the gestural state if necessary
-            setGestureModeOverlayForMainLauncher();
-            mRestoreGesturalNavBarMode.put(userId, false);
-        }
-    }
-
     private void switchToDefaultGestureNavOverlayIfNecessary() {
         final int userId = mCurrentUserContext.getUserId();
         try {
-            final IOverlayManager om = mOverlayManager;
+            final IOverlayManager om = IOverlayManager.Stub.asInterface(
+                    ServiceManager.getService(Context.OVERLAY_SERVICE));
             final OverlayInfo info = om.getOverlayInfo(NAV_BAR_MODE_GESTURAL_OVERLAY, userId);
             if (info != null && !info.isEnabled()) {
                 // Enable the default gesture nav overlay, and move the back gesture inset scale to
@@ -309,20 +182,6 @@
         }
     }
 
-    public void setModeOverlay(String overlayPkg, int userId) {
-        mUiBgExecutor.execute(() -> {
-            try {
-                mOverlayManager.setEnabledExclusiveInCategory(overlayPkg, userId);
-                if (DEBUG) {
-                    Log.d(TAG, "setModeOverlay: overlayPackage=" + overlayPkg
-                            + " userId=" + userId);
-                }
-            } catch (SecurityException | IllegalStateException | RemoteException e) {
-                Log.e(TAG, "Failed to enable overlay " + overlayPkg + " for user " + userId);
-            }
-        });
-    }
-
     @Override
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("NavigationModeController:");
@@ -334,11 +193,6 @@
             defaultOverlays = "failed_to_fetch";
         }
         pw.println("  defaultOverlays=" + defaultOverlays);
-        pw.println("  restoreGesturalNavMode:");
-        for (int i = 0; i < mRestoreGesturalNavBarMode.size(); i++) {
-            pw.println("    userId=" + mRestoreGesturalNavBarMode.keyAt(i)
-                    + " shouldRestore=" + mRestoreGesturalNavBarMode.valueAt(i));
-        }
         dumpAssetPaths(mCurrentUserContext);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RegionSamplingHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/RegionSamplingHelper.java
index 1a6b415..bf52a7a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/RegionSamplingHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/RegionSamplingHelper.java
@@ -148,11 +148,6 @@
         updateSamplingRect();
     }
 
-    private void postUpdateSamplingListener() {
-        mHandler.removeCallbacks(mUpdateSamplingListener);
-        mHandler.post(mUpdateSamplingListener);
-    }
-
     private void updateSamplingListener() {
         boolean isSamplingEnabled = mSamplingEnabled
                 && !mSamplingRequestBounds.isEmpty()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index 53fa263..fbe3e9b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -40,7 +40,6 @@
 import android.service.notification.StatusBarNotification;
 import android.text.TextUtils;
 import android.util.EventLog;
-import android.util.Log;
 import android.view.RemoteAnimationAdapter;
 import android.view.View;
 
@@ -91,92 +90,119 @@
  */
 public class StatusBarNotificationActivityStarter implements NotificationActivityStarter {
 
-    private static final String TAG = "NotifActivityStarter";
-    protected static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+    private final Context mContext;
 
-    private final Lazy<AssistManager> mAssistManagerLazy;
-    private final NotificationGroupManager mGroupManager;
-    private final StatusBarRemoteInputCallback mStatusBarRemoteInputCallback;
-    private final NotificationRemoteInputManager mRemoteInputManager;
-    private final NotificationLockscreenUserManager mLockscreenUserManager;
-    private final ShadeController mShadeController;
-    private final StatusBar mStatusBar;
-    private final KeyguardStateController mKeyguardStateController;
-    private final ActivityStarter mActivityStarter;
+    private final CommandQueue mCommandQueue;
+    private final Handler mMainThreadHandler;
+    private final Handler mBackgroundHandler;
+    private final Executor mUiBgExecutor;
+
     private final NotificationEntryManager mEntryManager;
     private final NotifPipeline mNotifPipeline;
     private final NotifCollection mNotifCollection;
-    private final FeatureFlags mFeatureFlags;
-    private final StatusBarStateController mStatusBarStateController;
-    private final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
-    private final MetricsLogger mMetricsLogger;
-    private final Context mContext;
-    private final NotificationPanelViewController mNotificationPanel;
-    private final NotificationPresenter mPresenter;
-    private final LockPatternUtils mLockPatternUtils;
     private final HeadsUpManagerPhone mHeadsUpManager;
+    private final ActivityStarter mActivityStarter;
+    private final IStatusBarService mBarService;
+    private final StatusBarStateController mStatusBarStateController;
     private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     private final KeyguardManager mKeyguardManager;
-    private final ActivityLaunchAnimator mActivityLaunchAnimator;
-    private final IStatusBarService mBarService;
-    private final CommandQueue mCommandQueue;
     private final IDreamManager mDreamManager;
-    private final Handler mMainThreadHandler;
-    private final Handler mBackgroundHandler;
-    private final ActivityIntentHelper mActivityIntentHelper;
     private final BubbleController mBubbleController;
-    private final Executor mUiBgExecutor;
+    private final Lazy<AssistManager> mAssistManagerLazy;
+    private final NotificationRemoteInputManager mRemoteInputManager;
+    private final NotificationGroupManager mGroupManager;
+    private final NotificationLockscreenUserManager mLockscreenUserManager;
+    private final ShadeController mShadeController;
+    private final KeyguardStateController mKeyguardStateController;
+    private final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
+    private final LockPatternUtils mLockPatternUtils;
+    private final StatusBarRemoteInputCallback mStatusBarRemoteInputCallback;
+    private final ActivityIntentHelper mActivityIntentHelper;
+
+    private final FeatureFlags mFeatureFlags;
+    private final MetricsLogger mMetricsLogger;
+    private final StatusBarNotificationActivityStarterLogger mLogger;
+
+    private final StatusBar mStatusBar;
+    private final NotificationPresenter mPresenter;
+    private final NotificationPanelViewController mNotificationPanel;
+    private final ActivityLaunchAnimator mActivityLaunchAnimator;
 
     private boolean mIsCollapsingToShowActivityOverLockscreen;
 
-    private StatusBarNotificationActivityStarter(Context context, CommandQueue commandQueue,
-            Lazy<AssistManager> assistManagerLazy, NotificationPanelViewController panel,
-            NotificationPresenter presenter, NotificationEntryManager entryManager,
-            HeadsUpManagerPhone headsUpManager, ActivityStarter activityStarter,
-            ActivityLaunchAnimator activityLaunchAnimator, IStatusBarService statusBarService,
+    private StatusBarNotificationActivityStarter(
+            Context context,
+            CommandQueue commandQueue,
+            Handler mainThreadHandler,
+            Handler backgroundHandler,
+            Executor uiBgExecutor,
+            NotificationEntryManager entryManager,
+            NotifPipeline notifPipeline,
+            NotifCollection notifCollection,
+            HeadsUpManagerPhone headsUpManager,
+            ActivityStarter activityStarter,
+            IStatusBarService statusBarService,
             StatusBarStateController statusBarStateController,
             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
             KeyguardManager keyguardManager,
-            IDreamManager dreamManager, NotificationRemoteInputManager remoteInputManager,
-            StatusBarRemoteInputCallback remoteInputCallback, NotificationGroupManager groupManager,
+            IDreamManager dreamManager,
+            BubbleController bubbleController,
+            Lazy<AssistManager> assistManagerLazy,
+            NotificationRemoteInputManager remoteInputManager,
+            NotificationGroupManager groupManager,
             NotificationLockscreenUserManager lockscreenUserManager,
-            ShadeController shadeController, StatusBar statusBar,
+            ShadeController shadeController,
             KeyguardStateController keyguardStateController,
             NotificationInterruptStateProvider notificationInterruptStateProvider,
-            MetricsLogger metricsLogger, LockPatternUtils lockPatternUtils,
-            Handler mainThreadHandler, Handler backgroundHandler, Executor uiBgExecutor,
-            ActivityIntentHelper activityIntentHelper, BubbleController bubbleController,
-            FeatureFlags featureFlags, NotifPipeline notifPipeline,
-            NotifCollection notifCollection) {
+            LockPatternUtils lockPatternUtils,
+            StatusBarRemoteInputCallback remoteInputCallback,
+            ActivityIntentHelper activityIntentHelper,
+
+            FeatureFlags featureFlags,
+            MetricsLogger metricsLogger,
+            StatusBarNotificationActivityStarterLogger logger,
+
+            StatusBar statusBar,
+            NotificationPresenter presenter,
+            NotificationPanelViewController panel,
+            ActivityLaunchAnimator activityLaunchAnimator) {
         mContext = context;
-        mNotificationPanel = panel;
-        mPresenter = presenter;
-        mHeadsUpManager = headsUpManager;
-        mActivityLaunchAnimator = activityLaunchAnimator;
-        mBarService = statusBarService;
         mCommandQueue = commandQueue;
+        mMainThreadHandler = mainThreadHandler;
+        mBackgroundHandler = backgroundHandler;
+        mUiBgExecutor = uiBgExecutor;
+        mEntryManager = entryManager;
+        mNotifPipeline = notifPipeline;
+        mNotifCollection = notifCollection;
+        mHeadsUpManager = headsUpManager;
+        mActivityStarter = activityStarter;
+        mBarService = statusBarService;
+        mStatusBarStateController = statusBarStateController;
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
         mKeyguardManager = keyguardManager;
         mDreamManager = dreamManager;
+        mBubbleController = bubbleController;
+        mAssistManagerLazy = assistManagerLazy;
         mRemoteInputManager = remoteInputManager;
+        mGroupManager = groupManager;
         mLockscreenUserManager = lockscreenUserManager;
         mShadeController = shadeController;
+        mKeyguardStateController = keyguardStateController;
+        mNotificationInterruptStateProvider = notificationInterruptStateProvider;
+        mLockPatternUtils = lockPatternUtils;
+        mStatusBarRemoteInputCallback = remoteInputCallback;
+        mActivityIntentHelper = activityIntentHelper;
+
+        mFeatureFlags = featureFlags;
+        mMetricsLogger = metricsLogger;
+        mLogger = logger;
+
         // TODO: use KeyguardStateController#isOccluded to remove this dependency
         mStatusBar = statusBar;
-        mKeyguardStateController = keyguardStateController;
-        mActivityStarter = activityStarter;
-        mEntryManager = entryManager;
-        mStatusBarStateController = statusBarStateController;
-        mNotificationInterruptStateProvider = notificationInterruptStateProvider;
-        mMetricsLogger = metricsLogger;
-        mAssistManagerLazy = assistManagerLazy;
-        mGroupManager = groupManager;
-        mLockPatternUtils = lockPatternUtils;
-        mBackgroundHandler = backgroundHandler;
-        mUiBgExecutor = uiBgExecutor;
-        mFeatureFlags = featureFlags;
-        mNotifPipeline = notifPipeline;
-        mNotifCollection = notifCollection;
+        mPresenter = presenter;
+        mNotificationPanel = panel;
+        mActivityLaunchAnimator = activityLaunchAnimator;
+
         if (!mFeatureFlags.isNewNotifPipelineRenderingEnabled()) {
             mEntryManager.addNotificationEntryListener(new NotificationEntryListener() {
                 @Override
@@ -192,11 +218,6 @@
                 }
             });
         }
-
-        mStatusBarRemoteInputCallback = remoteInputCallback;
-        mMainThreadHandler = mainThreadHandler;
-        mActivityIntentHelper = activityIntentHelper;
-        mBubbleController = bubbleController;
     }
 
     /**
@@ -207,6 +228,8 @@
      */
     @Override
     public void onNotificationClicked(StatusBarNotification sbn, ExpandableNotificationRow row) {
+        mLogger.logStartingActivityFromClick(sbn.getKey());
+
         RemoteInputController controller = mRemoteInputManager.getController();
         if (controller.isRemoteInputActive(row.getEntry())
                 && !TextUtils.isEmpty(row.getActiveRemoteInputText())) {
@@ -225,7 +248,7 @@
         // The only valid case is Bubble notifications. Guard against other cases
         // entering here.
         if (intent == null && !isBubble) {
-            Log.e(TAG, "onNotificationClicked called for non-clickable notification!");
+            mLogger.logNonClickableNotification(sbn.getKey());
             return;
         }
 
@@ -258,6 +281,8 @@
             boolean isActivityIntent,
             boolean wasOccluded,
             boolean showOverLockscreen) {
+        mLogger.logHandleClickAfterKeyguardDismissed(sbn.getKey());
+
         // TODO: Some of this code may be able to move to NotificationEntryManager.
         if (mHeadsUpManager != null && mHeadsUpManager.isAlerting(sbn.getKey())) {
             // Release the HUN notification to the shade.
@@ -304,6 +329,8 @@
             boolean isActivityIntent,
             boolean wasOccluded,
             NotificationEntry parentToCancelFinal) {
+        mLogger.logHandleClickAfterPanelCollapsed(sbn.getKey());
+
         String notificationKey = sbn.getKey();
         try {
             // The intent we are sending is for the application, which
@@ -343,9 +370,11 @@
                     remoteInputText.toString());
         }
         if (isBubble) {
+            mLogger.logExpandingBubble(notificationKey);
             expandBubbleStackOnMainThread(notificationKey);
         } else {
-            startNotificationIntent(intent, fillInIntent, row, wasOccluded, isActivityIntent);
+            startNotificationIntent(
+                    intent, fillInIntent, entry, row, wasOccluded, isActivityIntent);
         }
         if (isActivityIntent || isBubble) {
             mAssistManagerLazy.get().hideAssist();
@@ -392,10 +421,16 @@
         }
     }
 
-    private void startNotificationIntent(PendingIntent intent, Intent fillInIntent,
-            View row, boolean wasOccluded, boolean isActivityIntent) {
+    private void startNotificationIntent(
+            PendingIntent intent,
+            Intent fillInIntent,
+            NotificationEntry entry,
+            View row,
+            boolean wasOccluded,
+            boolean isActivityIntent) {
         RemoteAnimationAdapter adapter = mActivityLaunchAnimator.getLaunchAnimation(row,
                 wasOccluded);
+        mLogger.logStartNotificationIntent(entry.getKey(), intent);
         try {
             if (adapter != null) {
                 ActivityTaskManager.getService()
@@ -408,7 +443,7 @@
         } catch (RemoteException | PendingIntent.CanceledException e) {
             // the stack trace isn't very helpful here.
             // Just log the exception message.
-            Log.w(TAG, "Sending contentIntent failed: " + e);
+            mLogger.logSendingIntentFailed(e);
             // TODO: Dismiss Keyguard.
         }
     }
@@ -438,13 +473,9 @@
     private void handleFullScreenIntent(NotificationEntry entry) {
         if (mNotificationInterruptStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry)) {
             if (shouldSuppressFullScreenIntent(entry)) {
-                if (DEBUG) {
-                    Log.d(TAG, "No Fullscreen intent: suppressed by DND: " + entry.getKey());
-                }
+                mLogger.logFullScreenIntentSuppressedByDnD(entry.getKey());
             } else if (entry.getImportance() < NotificationManager.IMPORTANCE_HIGH) {
-                if (DEBUG) {
-                    Log.d(TAG, "No Fullscreen intent: not important enough: " + entry.getKey());
-                }
+                mLogger.logFullScreenIntentNotImportantEnough(entry.getKey());
             } else {
                 // Stop screensaver if the notification has a fullscreen intent.
                 // (like an incoming phone call)
@@ -457,13 +488,13 @@
                 });
 
                 // not immersive & a fullscreen alert should be shown
-                if (DEBUG) {
-                    Log.d(TAG, "Notification has fullScreenIntent; sending fullScreenIntent");
-                }
+                final PendingIntent fullscreenIntent =
+                        entry.getSbn().getNotification().fullScreenIntent;
+                mLogger.logSendingFullScreenIntent(entry.getKey(), fullscreenIntent);
                 try {
                     EventLog.writeEvent(EventLogTags.SYSUI_FULLSCREEN_NOTIFICATION,
                             entry.getKey());
-                    entry.getSbn().getNotification().fullScreenIntent.send();
+                    fullscreenIntent.send();
                     entry.notifyFullScreenIntentLaunched();
                     mMetricsLogger.count("note_fullscreen", 1);
                 } catch (PendingIntent.CanceledException e) {
@@ -578,9 +609,10 @@
     public static class Builder {
         private final Context mContext;
         private final CommandQueue mCommandQueue;
-        private final Lazy<AssistManager> mAssistManagerLazy;
+        private final Handler mMainThreadHandler;
+        private final Handler mBackgroundHandler;
+        private final Executor mUiBgExecutor;
         private final NotificationEntryManager mEntryManager;
-        private final FeatureFlags mFeatureFlags;
         private final NotifPipeline mNotifPipeline;
         private final NotifCollection mNotifCollection;
         private final HeadsUpManagerPhone mHeadsUpManager;
@@ -590,30 +622,37 @@
         private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
         private final KeyguardManager mKeyguardManager;
         private final IDreamManager mDreamManager;
+        private final BubbleController mBubbleController;
+        private final Lazy<AssistManager> mAssistManagerLazy;
         private final NotificationRemoteInputManager mRemoteInputManager;
-        private final StatusBarRemoteInputCallback mRemoteInputCallback;
         private final NotificationGroupManager mGroupManager;
         private final NotificationLockscreenUserManager mLockscreenUserManager;
-        private final KeyguardStateController mKeyguardStateController;
-        private final MetricsLogger mMetricsLogger;
-        private final LockPatternUtils mLockPatternUtils;
-        private final Handler mMainThreadHandler;
-        private final Handler mBackgroundHandler;
-        private final Executor mUiBgExecutor;
-        private final ActivityIntentHelper mActivityIntentHelper;
-        private final BubbleController mBubbleController;
-        private NotificationPanelViewController mNotificationPanelViewController;
-        private NotificationInterruptStateProvider mNotificationInterruptStateProvider;
         private final ShadeController mShadeController;
-        private NotificationPresenter mNotificationPresenter;
-        private ActivityLaunchAnimator mActivityLaunchAnimator;
+        private final KeyguardStateController mKeyguardStateController;
+        private final NotificationInterruptStateProvider mNotificationInterruptStateProvider;
+        private final LockPatternUtils mLockPatternUtils;
+        private final StatusBarRemoteInputCallback mRemoteInputCallback;
+        private final ActivityIntentHelper mActivityIntentHelper;
+
+        private final FeatureFlags mFeatureFlags;
+        private final MetricsLogger mMetricsLogger;
+        private final StatusBarNotificationActivityStarterLogger mLogger;
+
         private StatusBar mStatusBar;
+        private NotificationPresenter mNotificationPresenter;
+        private NotificationPanelViewController mNotificationPanelViewController;
+        private ActivityLaunchAnimator mActivityLaunchAnimator;
 
         @Inject
-        public Builder(Context context,
+        public Builder(
+                Context context,
                 CommandQueue commandQueue,
-                Lazy<AssistManager> assistManagerLazy,
+                @Main Handler mainThreadHandler,
+                @Background Handler backgroundHandler,
+                @UiBackground Executor uiBgExecutor,
                 NotificationEntryManager entryManager,
+                NotifPipeline notifPipeline,
+                NotifCollection notifCollection,
                 HeadsUpManagerPhone headsUpManager,
                 ActivityStarter activityStarter,
                 IStatusBarService statusBarService,
@@ -621,27 +660,30 @@
                 StatusBarKeyguardViewManager statusBarKeyguardViewManager,
                 KeyguardManager keyguardManager,
                 IDreamManager dreamManager,
+                BubbleController bubbleController,
+                Lazy<AssistManager> assistManagerLazy,
                 NotificationRemoteInputManager remoteInputManager,
-                StatusBarRemoteInputCallback remoteInputCallback,
                 NotificationGroupManager groupManager,
                 NotificationLockscreenUserManager lockscreenUserManager,
+                ShadeController shadeController,
                 KeyguardStateController keyguardStateController,
                 NotificationInterruptStateProvider notificationInterruptStateProvider,
-                MetricsLogger metricsLogger,
                 LockPatternUtils lockPatternUtils,
-                @Main Handler mainThreadHandler,
-                @Background Handler backgroundHandler,
-                @UiBackground Executor uiBgExecutor,
+                StatusBarRemoteInputCallback remoteInputCallback,
                 ActivityIntentHelper activityIntentHelper,
-                BubbleController bubbleController,
-                ShadeController shadeController,
+
                 FeatureFlags featureFlags,
-                NotifPipeline notifPipeline,
-                NotifCollection notifCollection) {
+                MetricsLogger metricsLogger,
+                StatusBarNotificationActivityStarterLogger logger) {
+
             mContext = context;
             mCommandQueue = commandQueue;
-            mAssistManagerLazy = assistManagerLazy;
+            mMainThreadHandler = mainThreadHandler;
+            mBackgroundHandler = backgroundHandler;
+            mUiBgExecutor = uiBgExecutor;
             mEntryManager = entryManager;
+            mNotifPipeline = notifPipeline;
+            mNotifCollection = notifCollection;
             mHeadsUpManager = headsUpManager;
             mActivityStarter = activityStarter;
             mStatusBarService = statusBarService;
@@ -649,23 +691,21 @@
             mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
             mKeyguardManager = keyguardManager;
             mDreamManager = dreamManager;
+            mBubbleController = bubbleController;
+            mAssistManagerLazy = assistManagerLazy;
             mRemoteInputManager = remoteInputManager;
-            mRemoteInputCallback = remoteInputCallback;
             mGroupManager = groupManager;
             mLockscreenUserManager = lockscreenUserManager;
+            mShadeController = shadeController;
             mKeyguardStateController = keyguardStateController;
             mNotificationInterruptStateProvider = notificationInterruptStateProvider;
-            mMetricsLogger = metricsLogger;
             mLockPatternUtils = lockPatternUtils;
-            mMainThreadHandler = mainThreadHandler;
-            mBackgroundHandler = backgroundHandler;
-            mUiBgExecutor = uiBgExecutor;
+            mRemoteInputCallback = remoteInputCallback;
             mActivityIntentHelper = activityIntentHelper;
-            mBubbleController = bubbleController;
-            mShadeController = shadeController;
+
             mFeatureFlags = featureFlags;
-            mNotifPipeline = notifPipeline;
-            mNotifCollection = notifCollection;
+            mMetricsLogger = metricsLogger;
+            mLogger = logger;
         }
 
         /** Sets the status bar to use as {@link StatusBar}. */
@@ -692,37 +732,42 @@
         }
 
         public StatusBarNotificationActivityStarter build() {
-            return new StatusBarNotificationActivityStarter(mContext,
-                    mCommandQueue, mAssistManagerLazy,
-                    mNotificationPanelViewController,
-                    mNotificationPresenter,
+            return new StatusBarNotificationActivityStarter(
+                    mContext,
+                    mCommandQueue,
+                    mMainThreadHandler,
+                    mBackgroundHandler,
+                    mUiBgExecutor,
                     mEntryManager,
+                    mNotifPipeline,
+                    mNotifCollection,
                     mHeadsUpManager,
                     mActivityStarter,
-                    mActivityLaunchAnimator,
                     mStatusBarService,
                     mStatusBarStateController,
                     mStatusBarKeyguardViewManager,
                     mKeyguardManager,
                     mDreamManager,
+                    mBubbleController,
+                    mAssistManagerLazy,
                     mRemoteInputManager,
-                    mRemoteInputCallback,
                     mGroupManager,
                     mLockscreenUserManager,
                     mShadeController,
-                    mStatusBar,
                     mKeyguardStateController,
                     mNotificationInterruptStateProvider,
-                    mMetricsLogger,
                     mLockPatternUtils,
-                    mMainThreadHandler,
-                    mBackgroundHandler,
-                    mUiBgExecutor,
+                    mRemoteInputCallback,
                     mActivityIntentHelper,
-                    mBubbleController,
+
                     mFeatureFlags,
-                    mNotifPipeline,
-                    mNotifCollection);
+                    mMetricsLogger,
+                    mLogger,
+
+                    mStatusBar,
+                    mNotificationPresenter,
+                    mNotificationPanelViewController,
+                    mActivityLaunchAnimator);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt
new file mode 100644
index 0000000..d118747
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt
@@ -0,0 +1,114 @@
+/*
+ * 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.phone
+
+import android.app.PendingIntent
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.LogLevel.DEBUG
+import com.android.systemui.log.LogLevel.ERROR
+import com.android.systemui.log.LogLevel.INFO
+import com.android.systemui.log.LogLevel.WARNING
+import com.android.systemui.log.dagger.NotifInteractionLog
+import javax.inject.Inject
+
+class StatusBarNotificationActivityStarterLogger @Inject constructor(
+    @NotifInteractionLog private val buffer: LogBuffer
+) {
+    fun logStartingActivityFromClick(key: String) {
+        buffer.log(TAG, DEBUG, {
+            str1 = key
+        }, {
+            "(1/4) onNotificationClicked: $str1"
+        })
+    }
+
+    fun logHandleClickAfterKeyguardDismissed(key: String) {
+        buffer.log(TAG, DEBUG, {
+            str1 = key
+        }, {
+            "(2/4) handleNotificationClickAfterKeyguardDismissed: $str1"
+        })
+    }
+
+    fun logHandleClickAfterPanelCollapsed(key: String) {
+        buffer.log(TAG, DEBUG, {
+            str1 = key
+        }, {
+            "(3/4) handleNotificationClickAfterPanelCollapsed: $str1"
+        })
+    }
+
+    fun logStartNotificationIntent(key: String, pendingIntent: PendingIntent) {
+        buffer.log(TAG, INFO, {
+            str1 = key
+            str2 = pendingIntent.intent.toString()
+        }, {
+            "(4/4) Starting $str2 for notification $str1"
+        })
+    }
+
+    fun logExpandingBubble(key: String) {
+        buffer.log(TAG, DEBUG, {
+            str1 = key
+        }, {
+            "Expanding bubble for $str1 (rather than firing intent)"
+        })
+    }
+
+    fun logSendingIntentFailed(e: Exception) {
+        buffer.log(TAG, WARNING, {
+            str1 = e.toString()
+        }, {
+            "Sending contentIntentFailed: $str1"
+        })
+    }
+
+    fun logNonClickableNotification(key: String) {
+        buffer.log(TAG, ERROR, {
+            str1 = key
+        }, {
+            "onNotificationClicked called for non-clickable notification! $str1"
+        })
+    }
+
+    fun logFullScreenIntentSuppressedByDnD(key: String) {
+        buffer.log(TAG, DEBUG, {
+            str1 = key
+        }, {
+            "No Fullscreen intent: suppressed by DND: $str1"
+        })
+    }
+
+    fun logFullScreenIntentNotImportantEnough(key: String) {
+        buffer.log(TAG, DEBUG, {
+            str1 = key
+        }, {
+            "No Fullscreen intent: not important enough: $str1"
+        })
+    }
+
+    fun logSendingFullScreenIntent(key: String, pendingIntent: PendingIntent) {
+        buffer.log(TAG, INFO, {
+            str1 = key
+            str2 = pendingIntent.intent.toString()
+        }, {
+            "Notification $str1 has fullScreenIntent; sending fullScreenIntent $str2"
+        })
+    }
+}
+
+private const val TAG = "NotifActivityStarter"
diff --git a/packages/SystemUI/src/com/android/systemui/util/RelativeTouchListener.kt b/packages/SystemUI/src/com/android/systemui/util/RelativeTouchListener.kt
index d65b285..8880df9 100644
--- a/packages/SystemUI/src/com/android/systemui/util/RelativeTouchListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/RelativeTouchListener.kt
@@ -115,7 +115,9 @@
 
                 performedLongClick = false
                 handler.postDelayed({
-                    performedLongClick = v.performLongClick()
+                    if (v.isLongClickable) {
+                        performedLongClick = v.performLongClick()
+                    }
                 }, ViewConfiguration.getLongPressTimeout().toLong())
             }
 
diff --git a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt
index 8625d63..db08d64 100644
--- a/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/animation/PhysicsAnimator.kt
@@ -61,14 +61,17 @@
 
 /**
  * Default spring configuration to use for animations where stiffness and/or damping ratio
- * were not provided.
+ * were not provided, and a default spring was not set via [PhysicsAnimator.setDefaultSpringConfig].
  */
-private val defaultSpring = PhysicsAnimator.SpringConfig(
+private val globalDefaultSpring = PhysicsAnimator.SpringConfig(
         SpringForce.STIFFNESS_MEDIUM,
         SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY)
 
-/** Default fling configuration to use for animations where friction was not provided. */
-private val defaultFling = PhysicsAnimator.FlingConfig(
+/**
+ * Default fling configuration to use for animations where friction was not provided, and a default
+ * fling config was not set via [PhysicsAnimator.setDefaultFlingConfig].
+ */
+private val globalDefaultFling = PhysicsAnimator.FlingConfig(
         friction = 1f, min = -Float.MAX_VALUE, max = Float.MAX_VALUE)
 
 /** Whether to log helpful debug information about animations. */
@@ -111,6 +114,12 @@
     /** End actions to run when all animations have completed.  */
     private val endActions = ArrayList<EndAction>()
 
+    /** SpringConfig to use by default for properties whose springs were not provided. */
+    private var defaultSpring: SpringConfig = globalDefaultSpring
+
+    /** FlingConfig to use by default for properties whose fling configs were not provided. */
+    private var defaultFling: FlingConfig = globalDefaultFling
+
     /**
      * Internal listeners that respond to DynamicAnimations updating and ending, and dispatch to
      * the listeners provided via [addUpdateListener] and [addEndListener]. This allows us to add
@@ -204,6 +213,19 @@
     }
 
     /**
+     * Springs a property to a given value using the provided configuration options, and a start
+     * velocity of 0f.
+     *
+     * @see spring
+     */
+    fun spring(
+        property: FloatPropertyCompat<in T>,
+        toPosition: Float
+    ): PhysicsAnimator<T> {
+        return spring(property, toPosition, 0f)
+    }
+
+    /**
      * Flings a property using the given start velocity, using a [FlingAnimation] configured using
      * the provided configuration settings.
      *
@@ -392,6 +414,14 @@
         return this
     }
 
+    fun setDefaultSpringConfig(defaultSpring: SpringConfig) {
+        this.defaultSpring = defaultSpring
+    }
+
+    fun setDefaultFlingConfig(defaultFling: FlingConfig) {
+        this.defaultFling = defaultFling
+    }
+
     /** Starts the animations! */
     fun start() {
         startAction()
@@ -752,7 +782,7 @@
     ) {
 
         constructor() :
-                this(defaultSpring.stiffness, defaultSpring.dampingRatio)
+                this(globalDefaultSpring.stiffness, globalDefaultSpring.dampingRatio)
 
         constructor(stiffness: Float, dampingRatio: Float) :
                 this(stiffness = stiffness, dampingRatio = dampingRatio, startVelocity = 0f)
@@ -782,10 +812,10 @@
         internal var startVelocity: Float
     ) {
 
-        constructor() : this(defaultFling.friction)
+        constructor() : this(globalDefaultFling.friction)
 
         constructor(friction: Float) :
-                this(friction, defaultFling.min, defaultFling.max)
+                this(friction, globalDefaultFling.min, globalDefaultFling.max)
 
         constructor(friction: Float, min: Float, max: Float) :
                 this(friction, min, max, startVelocity = 0f)
diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java
index cc6d607..8acfbf2 100644
--- a/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java
+++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/ConcurrencyModule.java
@@ -137,6 +137,36 @@
     }
 
     /**
+     * Provide a Background-Thread Executor by default.
+     */
+    @Provides
+    @Singleton
+    public static RepeatableExecutor provideRepeatableExecutor(@Background DelayableExecutor exec) {
+        return new RepeatableExecutorImpl(exec);
+    }
+
+    /**
+     * Provide a Background-Thread Executor.
+     */
+    @Provides
+    @Singleton
+    @Background
+    public static RepeatableExecutor provideBackgroundRepeatableExecutor(
+            @Background DelayableExecutor exec) {
+        return new RepeatableExecutorImpl(exec);
+    }
+
+    /**
+     * Provide a Main-Thread Executor.
+     */
+    @Provides
+    @Singleton
+    @Main
+    public static RepeatableExecutor provideMainRepeatableExecutor(@Main DelayableExecutor exec) {
+        return new RepeatableExecutorImpl(exec);
+    }
+
+    /**
      * Provide an Executor specifically for running UI operations on a separate thread.
      *
      * Keep submitted runnables short and to the point, just as with any other UI code.
diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/RepeatableExecutor.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/RepeatableExecutor.java
new file mode 100644
index 0000000..aefdc99
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/RepeatableExecutor.java
@@ -0,0 +1,54 @@
+/*
+ * 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.util.concurrency;
+
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A sub-class of {@link Executor} that allows scheduling commands to execute periodically.
+ */
+public interface RepeatableExecutor extends Executor {
+
+    /**
+     * Execute supplied Runnable on the Executors thread after initial delay, and subsequently with
+     * the given delay between the termination of one execution and the commencement of the next.
+     *
+     * Each invocation of the supplied Runnable will be scheduled after the previous invocation
+     * completes. For example, if you schedule the Runnable with a 60 second delay, and the Runnable
+     * itself takes 1 second, the effective delay will be 61 seconds between each invocation.
+     *
+     * See {@link java.util.concurrent.ScheduledExecutorService#scheduleRepeatedly(Runnable,
+     * long, long)}
+     *
+     * @return A Runnable that, when run, removes the supplied argument from the Executor queue.
+     */
+    default Runnable executeRepeatedly(Runnable r, long initialDelayMillis, long delayMillis) {
+        return executeRepeatedly(r, initialDelayMillis, delayMillis, TimeUnit.MILLISECONDS);
+    }
+
+    /**
+     * Execute supplied Runnable on the Executors thread after initial delay, and subsequently with
+     * the given delay between the termination of one execution and the commencement of the next..
+     *
+     * See {@link java.util.concurrent.ScheduledExecutorService#scheduleRepeatedly(Runnable,
+     * long, long)}
+     *
+     * @return A Runnable that, when run, removes the supplied argument from the Executor queue.
+     */
+    Runnable executeRepeatedly(Runnable r, long initialDelay, long delay, TimeUnit unit);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/util/concurrency/RepeatableExecutorImpl.java b/packages/SystemUI/src/com/android/systemui/util/concurrency/RepeatableExecutorImpl.java
new file mode 100644
index 0000000..c03e10e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/concurrency/RepeatableExecutorImpl.java
@@ -0,0 +1,84 @@
+/*
+ * 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.util.concurrency;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Implementation of {@link RepeatableExecutor} for SystemUI.
+ */
+class RepeatableExecutorImpl implements RepeatableExecutor {
+
+    private final DelayableExecutor mExecutor;
+
+    RepeatableExecutorImpl(DelayableExecutor executor) {
+        mExecutor = executor;
+    }
+
+    @Override
+    public void execute(Runnable command) {
+        mExecutor.execute(command);
+    }
+
+    @Override
+    public Runnable executeRepeatedly(Runnable r, long initDelay, long delay, TimeUnit unit) {
+        ExecutionToken token = new ExecutionToken(r, delay, unit);
+        token.start(initDelay, unit);
+        return token::cancel;
+    }
+
+    private class ExecutionToken implements Runnable {
+        private final Runnable mCommand;
+        private final long mDelay;
+        private final TimeUnit mUnit;
+        private final Object mLock = new Object();
+        private Runnable mCancel;
+
+        ExecutionToken(Runnable r, long delay, TimeUnit unit) {
+            mCommand = r;
+            mDelay = delay;
+            mUnit = unit;
+        }
+
+        @Override
+        public void run() {
+            mCommand.run();
+            synchronized (mLock) {
+                if (mCancel != null) {
+                    mCancel = mExecutor.executeDelayed(this, mDelay, mUnit);
+                }
+            }
+        }
+
+        /** Starts execution that will repeat the command until {@link cancel}. */
+        public void start(long startDelay, TimeUnit unit) {
+            synchronized (mLock) {
+                mCancel = mExecutor.executeDelayed(this, startDelay, unit);
+            }
+        }
+
+        /** Cancel repeated execution of command. */
+        public void cancel() {
+            synchronized (mLock) {
+                if (mCancel != null) {
+                    mCancel.run();
+                    mCancel = null;
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
index f6ee46b..e3f25c6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/controller/ControlsControllerImplTest.kt
@@ -89,6 +89,7 @@
     @Captor
     private lateinit var controlLoadCallbackCaptor:
             ArgumentCaptor<ControlsBindingController.LoadCallback>
+
     @Captor
     private lateinit var broadcastReceiverCaptor: ArgumentCaptor<BroadcastReceiver>
     @Captor
@@ -97,6 +98,7 @@
 
     private lateinit var delayableExecutor: FakeExecutor
     private lateinit var controller: ControlsControllerImpl
+    private lateinit var canceller: DidRunRunnable
 
     companion object {
         fun <T> capture(argumentCaptor: ArgumentCaptor<T>): T = argumentCaptor.capture()
@@ -146,6 +148,9 @@
             }
         }
 
+        canceller = DidRunRunnable()
+        `when`(bindingController.bindAndLoad(any(), any())).thenReturn(canceller)
+
         controller = ControlsControllerImpl(
                 wrapper,
                 delayableExecutor,
@@ -266,7 +271,7 @@
 
             assertTrue(favorites.isEmpty())
             assertFalse(data.errorOnLoad)
-        })
+        }, Consumer {})
 
         verify(bindingController).bindAndLoad(eq(TEST_COMPONENT),
                 capture(controlLoadCallbackCaptor))
@@ -301,7 +306,7 @@
             assertEquals(1, favorites.size)
             assertEquals(TEST_CONTROL_ID, favorites[0])
             assertFalse(data.errorOnLoad)
-        })
+        }, Consumer {})
 
         verify(bindingController).bindAndLoad(eq(TEST_COMPONENT),
                 capture(controlLoadCallbackCaptor))
@@ -332,7 +337,7 @@
             assertEquals(1, favorites.size)
             assertEquals(TEST_CONTROL_ID, favorites[0])
             assertFalse(data.errorOnLoad)
-        })
+        }, Consumer {})
 
         verify(bindingController).bindAndLoad(eq(TEST_COMPONENT),
                 capture(controlLoadCallbackCaptor))
@@ -363,7 +368,7 @@
             assertEquals(1, favorites.size)
             assertEquals(TEST_CONTROL_ID, favorites[0])
             assertTrue(data.errorOnLoad)
-        })
+        }, Consumer {})
 
         verify(bindingController).bindAndLoad(eq(TEST_COMPONENT),
                 capture(controlLoadCallbackCaptor))
@@ -377,22 +382,15 @@
 
     @Test
     fun testCancelLoad() {
-        val canceller = object : Runnable {
-            var ran = false
-            override fun run() {
-                ran = true
-            }
-        }
-        `when`(bindingController.bindAndLoad(any(), any())).thenReturn(canceller)
-
         var loaded = false
+        var cancelRunnable: Runnable? = null
         controller.replaceFavoritesForStructure(TEST_STRUCTURE_INFO)
         delayableExecutor.runAllReady()
         controller.loadForComponent(TEST_COMPONENT, Consumer {
             loaded = true
-        })
+        }, Consumer { runnable -> cancelRunnable = runnable })
 
-        controller.cancelLoad()
+        cancelRunnable?.run()
         delayableExecutor.runAllReady()
 
         assertFalse(loaded)
@@ -400,61 +398,47 @@
     }
 
     @Test
-    fun testCancelLoad_noCancelAfterSuccessfulLoad() {
-        val canceller = object : Runnable {
-            var ran = false
-            override fun run() {
-                ran = true
-            }
-        }
-        `when`(bindingController.bindAndLoad(any(), any())).thenReturn(canceller)
-
+    fun testCancelLoad_afterSuccessfulLoad() {
         var loaded = false
+        var cancelRunnable: Runnable? = null
         controller.replaceFavoritesForStructure(TEST_STRUCTURE_INFO)
         delayableExecutor.runAllReady()
         controller.loadForComponent(TEST_COMPONENT, Consumer {
             loaded = true
-        })
+        }, Consumer { runnable -> cancelRunnable = runnable })
 
         verify(bindingController).bindAndLoad(eq(TEST_COMPONENT),
             capture(controlLoadCallbackCaptor))
 
         controlLoadCallbackCaptor.value.accept(emptyList())
 
-        controller.cancelLoad()
+        cancelRunnable?.run()
         delayableExecutor.runAllReady()
 
         assertTrue(loaded)
-        assertFalse(canceller.ran)
+        assertTrue(canceller.ran)
     }
 
     @Test
-    fun testCancelLoad_noCancelAfterErrorLoad() {
-        val canceller = object : Runnable {
-            var ran = false
-            override fun run() {
-                ran = true
-            }
-        }
-        `when`(bindingController.bindAndLoad(any(), any())).thenReturn(canceller)
-
+    fun testCancelLoad_afterErrorLoad() {
         var loaded = false
+        var cancelRunnable: Runnable? = null
         controller.replaceFavoritesForStructure(TEST_STRUCTURE_INFO)
         delayableExecutor.runAllReady()
         controller.loadForComponent(TEST_COMPONENT, Consumer {
             loaded = true
-        })
+        }, Consumer { runnable -> cancelRunnable = runnable })
 
         verify(bindingController).bindAndLoad(eq(TEST_COMPONENT),
             capture(controlLoadCallbackCaptor))
 
         controlLoadCallbackCaptor.value.error("")
 
-        controller.cancelLoad()
+        cancelRunnable?.run()
         delayableExecutor.runAllReady()
 
         assertTrue(loaded)
-        assertFalse(canceller.ran)
+        assertTrue(canceller.ran)
     }
 
     @Test
@@ -465,7 +449,7 @@
         val newControlInfo = TEST_CONTROL_INFO.copy(controlTitle = TEST_CONTROL_TITLE_2)
         val control = statelessBuilderFromInfo(newControlInfo).build()
 
-        controller.loadForComponent(TEST_COMPONENT, Consumer {})
+        controller.loadForComponent(TEST_COMPONENT, Consumer {}, Consumer {})
 
         verify(bindingController).bindAndLoad(eq(TEST_COMPONENT),
                 capture(controlLoadCallbackCaptor))
@@ -963,3 +947,10 @@
         assertTrue(controller.getFavoritesForStructure(TEST_COMPONENT_2, TEST_STRUCTURE).isEmpty())
     }
 }
+
+private class DidRunRunnable() : Runnable {
+    var ran = false
+    override fun run() {
+        ran = true
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
index 8ab660c..3e46907 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightBarControllerTest.java
@@ -56,7 +56,7 @@
         when(mStatusBarIconController.getTransitionsController()).thenReturn(
                 mLightBarTransitionsController);
         mLightBarController = new LightBarController(mContext, mStatusBarIconController,
-                mock(BatteryController.class));
+                mock(BatteryController.class), mock(NavigationModeController.class));
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarTransitionsTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarTransitionsTest.java
index 27a5002..14c6e9f9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarTransitionsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NavigationBarTransitionsTest.java
@@ -17,6 +17,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
@@ -50,9 +51,11 @@
         mDependency.injectMockDependency(IWindowManager.class);
         mDependency.injectMockDependency(AssistManager.class);
         mDependency.injectMockDependency(OverviewProxyService.class);
-        mDependency.injectMockDependency(NavigationModeController.class);
         mDependency.injectMockDependency(StatusBarStateController.class);
         mDependency.injectMockDependency(KeyguardStateController.class);
+        doReturn(mContext)
+                .when(mDependency.injectMockDependency(NavigationModeController.class))
+                .getCurrentUserContext();
 
         NavigationBarView navBar = spy(new NavigationBarView(mContext, null));
         when(navBar.getCurrentView()).thenReturn(navBar);
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 dd28687..1afe132 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
@@ -176,25 +176,43 @@
         when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE);
         when(mFeatureFlags.isNewNotifPipelineRenderingEnabled()).thenReturn(false);
 
-        mNotificationActivityStarter = (new StatusBarNotificationActivityStarter.Builder(
-                getContext(), mock(CommandQueue.class), () -> mAssistManager,
-                mEntryManager, mock(HeadsUpManagerPhone.class),
-                mActivityStarter, mStatusBarService,
-                mock(StatusBarStateController.class), mStatusBarKeyguardViewManager,
-                mock(KeyguardManager.class),
-                mock(IDreamManager.class), mRemoteInputManager,
-                mock(StatusBarRemoteInputCallback.class), mock(NotificationGroupManager.class),
-                mock(NotificationLockscreenUserManager.class),
-                mKeyguardStateController,
-                mock(NotificationInterruptStateProvider.class), mock(MetricsLogger.class),
-                mock(LockPatternUtils.class), mHandler, mHandler, mUiBgExecutor,
-                mActivityIntentHelper, mBubbleController, mShadeController, mFeatureFlags,
-                mNotifPipeline, mNotifCollection)
+        mNotificationActivityStarter =
+                new StatusBarNotificationActivityStarter.Builder(
+                        getContext(),
+                        mock(CommandQueue.class),
+                        mHandler,
+                        mHandler,
+                        mUiBgExecutor,
+                        mEntryManager,
+                        mNotifPipeline,
+                        mNotifCollection,
+                        mock(HeadsUpManagerPhone.class),
+                        mActivityStarter,
+                        mStatusBarService,
+                        mock(StatusBarStateController.class),
+                        mStatusBarKeyguardViewManager,
+                        mock(KeyguardManager.class),
+                        mock(IDreamManager.class),
+                        mBubbleController,
+                        () -> mAssistManager,
+                        mRemoteInputManager,
+                        mock(NotificationGroupManager.class),
+                        mock(NotificationLockscreenUserManager.class),
+                        mShadeController,
+                        mKeyguardStateController,
+                        mock(NotificationInterruptStateProvider.class),
+                        mock(LockPatternUtils.class),
+                        mock(StatusBarRemoteInputCallback.class),
+                        mActivityIntentHelper,
+
+                        mFeatureFlags,
+                        mock(MetricsLogger.class),
+                        mock(StatusBarNotificationActivityStarterLogger.class))
                 .setStatusBar(mStatusBar)
-                .setNotificationPanelViewController(mock(NotificationPanelViewController.class))
                 .setNotificationPresenter(mock(NotificationPresenter.class))
-                .setActivityLaunchAnimator(mock(ActivityLaunchAnimator.class)))
-        .build();
+                .setNotificationPanelViewController(mock(NotificationPanelViewController.class))
+                .setActivityLaunchAnimator(mock(ActivityLaunchAnimator.class))
+                .build();
 
         // set up dismissKeyguardThenExecute to synchronously invoke the OnDismissAction arg
         doAnswer(mCallOnDismiss).when(mActivityStarter).dismissKeyguardThenExecute(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/RepeatableExecutorTest.java b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/RepeatableExecutorTest.java
new file mode 100644
index 0000000..00f37ae
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/util/concurrency/RepeatableExecutorTest.java
@@ -0,0 +1,162 @@
+/*
+ * 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.util.concurrency;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.util.time.FakeSystemClock;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidTestingRunner.class)
+public class RepeatableExecutorTest extends SysuiTestCase {
+
+    private static final int DELAY = 100;
+
+    private FakeSystemClock mFakeClock;
+    private FakeExecutor mFakeExecutor;
+    private RepeatableExecutor mExecutor;
+    private CountingTask mCountingTask;
+
+    @Before
+    public void setUp() throws Exception {
+        mFakeClock = new FakeSystemClock();
+        mFakeExecutor = new FakeExecutor(mFakeClock);
+        mCountingTask = new CountingTask();
+        mExecutor = new RepeatableExecutorImpl(mFakeExecutor);
+    }
+
+    /**
+     * Test FakeExecutor that receives non-delayed items to execute.
+     */
+    @Test
+    public void testExecute() {
+        mExecutor.execute(mCountingTask);
+        mFakeExecutor.runAllReady();
+        assertThat(mCountingTask.getCount()).isEqualTo(1);
+    }
+
+    @Test
+    public void testRepeats() {
+        // GIVEN that a command is queued to repeat
+        mExecutor.executeRepeatedly(mCountingTask, DELAY, DELAY);
+        // WHEN The clock advances and the task is run
+        mFakeExecutor.advanceClockToNext();
+        mFakeExecutor.runAllReady();
+        // THEN another task is queued
+        assertThat(mCountingTask.getCount()).isEqualTo(1);
+        assertThat(mFakeExecutor.numPending()).isEqualTo(1);
+    }
+
+    @Test
+    public void testNoExecutionBeforeStartDelay() {
+        // WHEN a command is queued with a start delay
+        mExecutor.executeRepeatedly(mCountingTask, 2 * DELAY, DELAY);
+        mFakeExecutor.runAllReady();
+        // THEN then it doesn't run immediately
+        assertThat(mCountingTask.getCount()).isEqualTo(0);
+        assertThat(mFakeExecutor.numPending()).isEqualTo(1);
+    }
+
+    @Test
+    public void testExecuteAfterStartDelay() {
+        // GIVEN that a command is queued to repeat with a longer start delay
+        mExecutor.executeRepeatedly(mCountingTask, 2 * DELAY, DELAY);
+        // WHEN the clock advances the start delay
+        mFakeClock.advanceTime(2 * DELAY);
+        mFakeExecutor.runAllReady();
+        // THEN the command has run and another task is queued
+        assertThat(mCountingTask.getCount()).isEqualTo(1);
+        assertThat(mFakeExecutor.numPending()).isEqualTo(1);
+    }
+
+    @Test
+    public void testExecuteWithZeroStartDelay() {
+        // WHEN a command is queued with no start delay
+        mExecutor.executeRepeatedly(mCountingTask, 0L, DELAY);
+        mFakeExecutor.runAllReady();
+        // THEN the command has run and another task is queued
+        assertThat(mCountingTask.getCount()).isEqualTo(1);
+        assertThat(mFakeExecutor.numPending()).isEqualTo(1);
+    }
+
+    @Test
+    public void testAdvanceTimeTwice() {
+        // GIVEN that a command is queued to repeat
+        mExecutor.executeRepeatedly(mCountingTask, DELAY, DELAY);
+        // WHEN the clock advances the time DELAY twice
+        mFakeClock.advanceTime(DELAY);
+        mFakeExecutor.runAllReady();
+        mFakeClock.advanceTime(DELAY);
+        mFakeExecutor.runAllReady();
+        // THEN the command has run twice and another task is queued
+        assertThat(mCountingTask.getCount()).isEqualTo(2);
+        assertThat(mFakeExecutor.numPending()).isEqualTo(1);
+    }
+
+    @Test
+    public void testCancel() {
+        // GIVEN that a scheduled command has been cancelled
+        Runnable cancel = mExecutor.executeRepeatedly(mCountingTask, DELAY, DELAY);
+        cancel.run();
+        // WHEN the clock advances the time DELAY
+        mFakeClock.advanceTime(DELAY);
+        mFakeExecutor.runAllReady();
+        // THEN the comamnd has not run and no further tasks are queued
+        assertThat(mCountingTask.getCount()).isEqualTo(0);
+        assertThat(mFakeExecutor.numPending()).isEqualTo(0);
+    }
+
+    @Test
+    public void testCancelAfterStart() {
+        // GIVEN that a command has reapeated a few times
+        Runnable cancel = mExecutor.executeRepeatedly(mCountingTask, DELAY, DELAY);
+        mFakeClock.advanceTime(DELAY);
+        mFakeExecutor.runAllReady();
+        // WHEN cancelled and time advances
+        cancel.run();
+        // THEN the command has only run the first time
+        assertThat(mCountingTask.getCount()).isEqualTo(1);
+        assertThat(mFakeExecutor.numPending()).isEqualTo(0);
+    }
+
+    /**
+     * Runnable used for testing that counts the number of times run() is invoked.
+     */
+    private static class CountingTask implements Runnable {
+
+        private int mRunCount;
+
+        @Override
+        public void run() {
+            mRunCount++;
+        }
+
+        /** Gets the run count. */
+        public int getCount() {
+            return mRunCount;
+        }
+    }
+}
diff --git a/packages/Tethering/res/values-mcc310-mnc004/strings.xml b/packages/Tethering/res/values-mcc310-mnc004/strings.xml
index 9dadd49..ce9ff60 100644
--- a/packages/Tethering/res/values-mcc310-mnc004/strings.xml
+++ b/packages/Tethering/res/values-mcc310-mnc004/strings.xml
@@ -25,6 +25,4 @@
     <string name="upstream_roaming_notification_title">Hotspot or tethering is on</string>
     <!-- String for cellular roaming notification message [CHAR LIMIT=500] -->
     <string name="upstream_roaming_notification_message">Additional charges may apply while roaming</string>
-    <!-- String for cellular roaming notification continue button [CHAR LIMIT=200] -->
-    <string name="upstream_roaming_notification_continue_button">Continue</string>
 </resources>
diff --git a/packages/Tethering/res/values-mcc310-mnc120/strings.xml b/packages/Tethering/res/values-mcc310-mnc120/strings.xml
deleted file mode 100644
index 618df90..0000000
--- a/packages/Tethering/res/values-mcc310-mnc120/strings.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2020 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- String for tethered notification title with client number info. -->
-    <plurals name="tethered_notification_title_with_client_number">
-        <item quantity="one"><xliff:g>%1$d</xliff:g> device connected.</item>
-        <item quantity="other"><xliff:g>%1$d</xliff:g> devices connected.</item>
-    </plurals>
-</resources>
\ No newline at end of file
diff --git a/packages/Tethering/res/values-mcc311-mnc480/strings.xml b/packages/Tethering/res/values-mcc311-mnc480/strings.xml
index 9dadd49..ce9ff60 100644
--- a/packages/Tethering/res/values-mcc311-mnc480/strings.xml
+++ b/packages/Tethering/res/values-mcc311-mnc480/strings.xml
@@ -25,6 +25,4 @@
     <string name="upstream_roaming_notification_title">Hotspot or tethering is on</string>
     <!-- String for cellular roaming notification message [CHAR LIMIT=500] -->
     <string name="upstream_roaming_notification_message">Additional charges may apply while roaming</string>
-    <!-- String for cellular roaming notification continue button [CHAR LIMIT=200] -->
-    <string name="upstream_roaming_notification_continue_button">Continue</string>
 </resources>
diff --git a/packages/Tethering/res/values-mcc311-mnc490/strings.xml b/packages/Tethering/res/values-mcc311-mnc490/strings.xml
deleted file mode 100644
index 618df90..0000000
--- a/packages/Tethering/res/values-mcc311-mnc490/strings.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2020 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- String for tethered notification title with client number info. -->
-    <plurals name="tethered_notification_title_with_client_number">
-        <item quantity="one"><xliff:g>%1$d</xliff:g> device connected.</item>
-        <item quantity="other"><xliff:g>%1$d</xliff:g> devices connected.</item>
-    </plurals>
-</resources>
\ No newline at end of file
diff --git a/packages/Tethering/res/values-mcc312-mnc530/strings.xml b/packages/Tethering/res/values-mcc312-mnc530/strings.xml
deleted file mode 100644
index 618df90..0000000
--- a/packages/Tethering/res/values-mcc312-mnc530/strings.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2020 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- String for tethered notification title with client number info. -->
-    <plurals name="tethered_notification_title_with_client_number">
-        <item quantity="one"><xliff:g>%1$d</xliff:g> device connected.</item>
-        <item quantity="other"><xliff:g>%1$d</xliff:g> devices connected.</item>
-    </plurals>
-</resources>
\ No newline at end of file
diff --git a/packages/Tethering/res/values/config.xml b/packages/Tethering/res/values/config.xml
index 66fbefc..83c99d2 100644
--- a/packages/Tethering/res/values/config.xml
+++ b/packages/Tethering/res/values/config.xml
@@ -158,51 +158,6 @@
     <!-- ComponentName of the service used to run no ui tether provisioning. -->
     <string translatable="false" name="config_wifi_tether_enable">com.android.settings/.wifi.tether.TetherService</string>
 
-    <!-- Enable tethering notification -->
-    <!-- Icons for showing tether enable notification.
-         Each item should have two elements and be separated with ";".
-
-         The first element is downstream types which is one of tethering. This element has to be
-         made by WIFI, USB, BT, and OR'd with the others. Use "|" to combine multiple downstream
-         types and use "," to separate each combinations. Such as
-
-             USB|BT,WIFI|USB|BT
-
-         The second element is icon for the item. This element has to be composed by
-         <package name>:drawable/<resource name>. Such as
-
-             1. com.android.networkstack.tethering:drawable/stat_sys_tether_general
-             2. android:drawable/xxx
-
-         So the entire string of each item would be
-
-             USB|BT,WIFI|USB|BT;com.android.networkstack.tethering:drawable/stat_sys_tether_general
-
-         NOTE: One config can be separated into two or more for readability. Such as
-
-               WIFI|USB,WIFI|BT,USB|BT,WIFI|USB|BT;android:drawable/xxx
-
-               can be separated into
-
-               WIFI|USB;android:drawable/xxx
-               WIFI|BT;android:drawable/xxx
-               USB|BT;android:drawable/xxx
-               WIFI|USB|BT;android:drawable/xxx
-
-         Notification will not show if the downstream type isn't listed in array.
-         Empty array means disable notifications. -->
-    <!-- In AOSP, hotspot is configured to no notification by default. Because status bar has showed
-         an icon on the right side already -->
-    <string-array translatable="false" name="tethering_notification_icons">
-        <item>USB;com.android.networkstack.tethering:drawable/stat_sys_tether_usb</item>
-        <item>BT;com.android.networkstack.tethering:drawable/stat_sys_tether_bluetooth</item>
-        <item>WIFI|USB,WIFI|BT,USB|BT,WIFI|USB|BT;com.android.networkstack.tethering:drawable/stat_sys_tether_general</item>
-    </string-array>
-    <!-- String for tether enable notification title. -->
-    <string name="tethering_notification_title">@string/tethered_notification_title</string>
-    <!-- String for tether enable notification message. -->
-    <string name="tethering_notification_message">@string/tethered_notification_message</string>
-
     <!-- No upstream notification is shown when there is a downstream but no upstream that is able
          to do the tethering. -->
     <!-- Delay(millisecond) to show no upstream notification after there's no Backhaul. Set delay to
diff --git a/packages/Tethering/res/values/overlayable.xml b/packages/Tethering/res/values/overlayable.xml
index bbba3f3..16ae8ad 100644
--- a/packages/Tethering/res/values/overlayable.xml
+++ b/packages/Tethering/res/values/overlayable.xml
@@ -32,44 +32,6 @@
             <item type="string" name="config_mobile_hotspot_provision_response"/>
             <item type="integer" name="config_mobile_hotspot_provision_check_period"/>
             <item type="string" name="config_wifi_tether_enable"/>
-            <!-- Configuration values for TetheringNotificationUpdater -->
-            <!-- Icons for showing tether enable notification.
-            Each item should have two elements and be separated with ";".
-
-            The first element is downstream types which is one of tethering. This element has to be
-            made by WIFI, USB, BT, and OR'd with the others. Use "|" to combine multiple downstream
-            types and use "," to separate each combinations. Such as
-
-            USB|BT,WIFI|USB|BT
-
-            The second element is icon for the item. This element has to be composed by
-            <package name>:drawable/<resource name>. Such as
-
-            1. com.android.networkstack.tethering:drawable/stat_sys_tether_general
-            2. android:drawable/xxx
-
-            So the entire string of each item would be
-
-            USB|BT,WIFI|USB|BT;com.android.networkstack.tethering:drawable/stat_sys_tether_general
-
-            NOTE: One config can be separated into two or more for readability. Such as
-
-                  WIFI|USB,WIFI|BT,USB|BT,WIFI|USB|BT;android:drawable/xxx
-
-                  can be separated into
-
-                  WIFI|USB;android:drawable/xxx
-                  WIFI|BT;android:drawable/xxx
-                  USB|BT;android:drawable/xxx
-                  WIFI|USB|BT;android:drawable/xxx
-
-            Notification will not show if the downstream type isn't listed in array.
-            Empty array means disable notifications. -->
-            <item type="array" name="tethering_notification_icons"/>
-            <!-- String for tether enable notification title. -->
-            <item type="string" name="tethering_notification_title"/>
-            <!-- String for tether enable notification message. -->
-            <item type="string" name="tethering_notification_message"/>
             <!-- Params from config.xml that can be overlaid -->
         </policy>
     </overlayable>
diff --git a/packages/Tethering/res/values/strings.xml b/packages/Tethering/res/values/strings.xml
index 4fa60d4..d63c7c5 100644
--- a/packages/Tethering/res/values/strings.xml
+++ b/packages/Tethering/res/values/strings.xml
@@ -19,9 +19,6 @@
     <string name="tethered_notification_title">Tethering or hotspot active</string>
     <!-- String for tethered notification message [CHAR LIMIT=200] -->
     <string name="tethered_notification_message">Tap to set up.</string>
-    <!-- String for tethered notification title with client number info. -->
-    <plurals name="tethered_notification_title_with_client_number">
-    </plurals>
 
     <!-- This notification is shown when tethering has been disabled on a user's device.
     The device is managed by the user's employer. Tethering can't be turned on unless the
@@ -47,6 +44,4 @@
     <string name="upstream_roaming_notification_title"></string>
     <!-- String for cellular roaming notification message [CHAR LIMIT=500] -->
     <string name="upstream_roaming_notification_message"></string>
-    <!-- String for cellular roaming notification continue button [CHAR LIMIT=200] -->
-    <string name="upstream_roaming_notification_continue_button"></string>
 </resources>
diff --git a/packages/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java b/packages/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java
index 85a23fb..55344fc 100644
--- a/packages/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java
+++ b/packages/Tethering/src/com/android/networkstack/tethering/OffloadHardwareInterface.java
@@ -142,7 +142,7 @@
     public boolean initOffloadConfig() {
         IOffloadConfig offloadConfig;
         try {
-            offloadConfig = IOffloadConfig.getService();
+            offloadConfig = IOffloadConfig.getService(true /*retry*/);
         } catch (RemoteException e) {
             mLog.e("getIOffloadConfig error " + e);
             return false;
diff --git a/packages/Tethering/src/com/android/networkstack/tethering/TetheringNotificationUpdater.java b/packages/Tethering/src/com/android/networkstack/tethering/TetheringNotificationUpdater.java
index f490cc4..7fd6b61 100644
--- a/packages/Tethering/src/com/android/networkstack/tethering/TetheringNotificationUpdater.java
+++ b/packages/Tethering/src/com/android/networkstack/tethering/TetheringNotificationUpdater.java
@@ -17,9 +17,6 @@
 package com.android.networkstack.tethering;
 
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING;
-import static android.net.TetheringManager.TETHERING_BLUETOOTH;
-import static android.net.TetheringManager.TETHERING_USB;
-import static android.net.TetheringManager.TETHERING_WIFI;
 import static android.text.TextUtils.isEmpty;
 
 import android.app.Notification;
@@ -39,10 +36,8 @@
 import android.provider.Settings;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
-import android.util.Log;
 import android.util.SparseArray;
 
-import androidx.annotation.ArrayRes;
 import androidx.annotation.DrawableRes;
 import androidx.annotation.IntDef;
 import androidx.annotation.IntRange;
@@ -77,9 +72,6 @@
     private static final boolean NO_NOTIFY = false;
     @VisibleForTesting
     static final int EVENT_SHOW_NO_UPSTREAM = 1;
-    // Id to update and cancel enable notification. Must be unique within the tethering app.
-    @VisibleForTesting
-    static final int ENABLE_NOTIFICATION_ID = 1000;
     // Id to update and cancel restricted notification. Must be unique within the tethering app.
     @VisibleForTesting
     static final int RESTRICTED_NOTIFICATION_ID = 1001;
@@ -120,7 +112,6 @@
 
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(value = {
-            ENABLE_NOTIFICATION_ID,
             RESTRICTED_NOTIFICATION_ID,
             NO_UPSTREAM_NOTIFICATION_ID,
             ROAMING_NOTIFICATION_ID
@@ -223,7 +214,6 @@
         final boolean tetheringActiveChanged =
                 (downstreamTypes == DOWNSTREAM_NONE) != (mDownstreamTypesMask == DOWNSTREAM_NONE);
         final boolean subIdChanged = subId != mActiveDataSubId;
-        final boolean downstreamChanged = downstreamTypes != mDownstreamTypesMask;
         final boolean upstreamChanged = noUpstream != mNoUpstream;
         final boolean roamingChanged = isRoaming != mRoaming;
         final boolean updateAll = tetheringActiveChanged || subIdChanged;
@@ -232,19 +222,10 @@
         mNoUpstream = noUpstream;
         mRoaming = isRoaming;
 
-        if (updateAll || downstreamChanged) updateEnableNotification();
         if (updateAll || upstreamChanged) updateNoUpstreamNotification();
         if (updateAll || roamingChanged) updateRoamingNotification();
     }
 
-    private void updateEnableNotification() {
-        final boolean tetheringInactive = mDownstreamTypesMask == DOWNSTREAM_NONE;
-
-        if (tetheringInactive || setupNotification() == NO_NOTIFY) {
-            clearNotification(ENABLE_NOTIFICATION_ID);
-        }
-    }
-
     private void updateNoUpstreamNotification() {
         final boolean tetheringInactive = mDownstreamTypesMask == DOWNSTREAM_NONE;
 
@@ -310,64 +291,6 @@
                 NO_UPSTREAM_NOTIFICATION_ID, null /* pendingIntent */, action);
     }
 
-    /**
-     * Returns the downstream types mask which convert from given string.
-     *
-     * @param types This string has to be made by "WIFI", "USB", "BT", and OR'd with the others.
-     *
-     * @return downstream types mask value.
-     */
-    @VisibleForTesting
-    @IntRange(from = 0, to = 7)
-    int getDownstreamTypesMask(@NonNull final String types) {
-        int downstreamTypesMask = DOWNSTREAM_NONE;
-        final String[] downstreams = types.split("\\|");
-        for (String downstream : downstreams) {
-            if (USB_DOWNSTREAM.equals(downstream.trim())) {
-                downstreamTypesMask |= (1 << TETHERING_USB);
-            } else if (WIFI_DOWNSTREAM.equals(downstream.trim())) {
-                downstreamTypesMask |= (1 << TETHERING_WIFI);
-            } else if (BLUETOOTH_DOWNSTREAM.equals(downstream.trim())) {
-                downstreamTypesMask |= (1 << TETHERING_BLUETOOTH);
-            }
-        }
-        return downstreamTypesMask;
-    }
-
-    /**
-     * Returns the icons {@link android.util.SparseArray} which get from given string-array resource
-     * id.
-     *
-     * @param id String-array resource id
-     *
-     * @return {@link android.util.SparseArray} with downstream types and icon id info.
-     */
-    @NonNull
-    @VisibleForTesting
-    SparseArray<Integer> getIcons(@ArrayRes int id, @NonNull Resources res) {
-        final String[] array = res.getStringArray(id);
-        final SparseArray<Integer> icons = new SparseArray<>();
-        for (String config : array) {
-            if (isEmpty(config)) continue;
-
-            final String[] elements = config.split(";");
-            if (elements.length != 2) {
-                Log.wtf(TAG,
-                        "Unexpected format in Tethering notification configuration : " + config);
-                continue;
-            }
-
-            final String[] types = elements[0].split(",");
-            for (String type : types) {
-                int mask = getDownstreamTypesMask(type);
-                if (mask == DOWNSTREAM_NONE) continue;
-                icons.put(mask, res.getIdentifier(
-                        elements[1].trim(), null /* defType */, null /* defPackage */));
-            }
-        }
-        return icons;
-    }
-
     private boolean setupRoamingNotification() {
         final Resources res = getResourcesForSubId(mContext, mActiveDataSubId);
         final boolean upstreamRoamingNotification =
@@ -403,29 +326,6 @@
         return NOTIFY_DONE;
     }
 
-    private boolean setupNotification() {
-        final Resources res = getResourcesForSubId(mContext, mActiveDataSubId);
-        final SparseArray<Integer> downstreamIcons =
-                getIcons(R.array.tethering_notification_icons, res);
-
-        final int iconId = downstreamIcons.get(mDownstreamTypesMask, NO_ICON_ID);
-        if (iconId == NO_ICON_ID) return NO_NOTIFY;
-
-        final String title = res.getString(R.string.tethering_notification_title);
-        final String message = res.getString(R.string.tethering_notification_message);
-        if (isEmpty(title) || isEmpty(message)) return NO_NOTIFY;
-
-        final PendingIntent pi = PendingIntent.getActivity(
-                mContext.createContextAsUser(UserHandle.CURRENT, 0 /* flags */),
-                0 /* requestCode */,
-                new Intent(Settings.ACTION_TETHER_SETTINGS),
-                Intent.FLAG_ACTIVITY_NEW_TASK,
-                null /* options */);
-
-        showNotification(iconId, title, message, ENABLE_NOTIFICATION_ID, pi, new Action[0]);
-        return NOTIFY_DONE;
-    }
-
     private void showNotification(@DrawableRes final int iconId, @NonNull final String title,
             @NonNull final String message, @NotificationId final int id, @Nullable PendingIntent pi,
             @NonNull final Action... actions) {
diff --git a/packages/Tethering/tests/integration/Android.bp b/packages/Tethering/tests/integration/Android.bp
index 6b751af..3305ed0 100644
--- a/packages/Tethering/tests/integration/Android.bp
+++ b/packages/Tethering/tests/integration/Android.bp
@@ -69,6 +69,7 @@
     test_config: "AndroidTest_Coverage.xml",
     defaults: ["libnetworkstackutilsjni_deps"],
     static_libs: [
+        "NetworkStaticLibTestsLib",
         "NetworkStackTestsLib",
         "TetheringTestsLib",
         "TetheringIntegrationTestsLib",
diff --git a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringNotificationUpdaterTest.kt b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringNotificationUpdaterTest.kt
index 04f31a7..745468f 100644
--- a/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringNotificationUpdaterTest.kt
+++ b/packages/Tethering/tests/unit/src/com/android/networkstack/tethering/TetheringNotificationUpdaterTest.kt
@@ -20,8 +20,6 @@
 import android.app.NotificationManager
 import android.content.Context
 import android.content.res.Resources
-import android.net.ConnectivityManager.TETHERING_BLUETOOTH
-import android.net.ConnectivityManager.TETHERING_USB
 import android.net.ConnectivityManager.TETHERING_WIFI
 import android.os.Handler
 import android.os.HandlerThread
@@ -29,14 +27,12 @@
 import android.net.NetworkCapabilities
 import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING
 import android.os.UserHandle
-import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
 import android.telephony.TelephonyManager
 import androidx.test.filters.SmallTest
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.runner.AndroidJUnit4
 import com.android.internal.util.test.BroadcastInterceptingContext
 import com.android.networkstack.tethering.TetheringNotificationUpdater.DOWNSTREAM_NONE
-import com.android.networkstack.tethering.TetheringNotificationUpdater.ENABLE_NOTIFICATION_ID
 import com.android.networkstack.tethering.TetheringNotificationUpdater.EVENT_SHOW_NO_UPSTREAM
 import com.android.networkstack.tethering.TetheringNotificationUpdater.NO_UPSTREAM_NOTIFICATION_ID
 import com.android.networkstack.tethering.TetheringNotificationUpdater.RESTRICTED_NOTIFICATION_ID
@@ -63,17 +59,9 @@
 import org.mockito.MockitoAnnotations
 
 const val TEST_SUBID = 1
-const val WIFI_ICON_ID = 1
-const val USB_ICON_ID = 2
-const val BT_ICON_ID = 3
-const val GENERAL_ICON_ID = 4
 const val WIFI_MASK = 1 shl TETHERING_WIFI
-const val USB_MASK = 1 shl TETHERING_USB
-const val BT_MASK = 1 shl TETHERING_BLUETOOTH
-const val TITLE = "Tethering active"
-const val MESSAGE = "Tap here to set up."
-const val TEST_TITLE = "Hotspot active"
-const val TEST_MESSAGE = "Tap to set up hotspot."
+const val TEST_DISALLOW_TITLE = "Tether function is disallowed"
+const val TEST_DISALLOW_MESSAGE = "Please contact your admin"
 const val TEST_NO_UPSTREAM_TITLE = "Hotspot has no internet access"
 const val TEST_NO_UPSTREAM_MESSAGE = "Device cannot connect to internet."
 const val TEST_NO_UPSTREAM_BUTTON = "Turn off hotspot"
@@ -88,7 +76,6 @@
     @Mock private lateinit var mockContext: Context
     @Mock private lateinit var notificationManager: NotificationManager
     @Mock private lateinit var telephonyManager: TelephonyManager
-    @Mock private lateinit var defaultResources: Resources
     @Mock private lateinit var testResources: Resources
 
     // lateinit for these classes under test, as they should be reset to a different instance for
@@ -97,11 +84,6 @@
     private lateinit var notificationUpdater: TetheringNotificationUpdater
     private lateinit var fakeTetheringThread: HandlerThread
 
-    private val ENABLE_ICON_CONFIGS = arrayOf(
-            "USB;android.test:drawable/usb", "BT;android.test:drawable/bluetooth",
-            "WIFI|BT;android.test:drawable/general", "WIFI|USB;android.test:drawable/general",
-            "USB|BT;android.test:drawable/general", "WIFI|USB|BT;android.test:drawable/general")
-
     private val ROAMING_CAPABILITIES = NetworkCapabilities()
     private val HOME_CAPABILITIES = NetworkCapabilities().addCapability(NET_CAPABILITY_NOT_ROAMING)
     private val NOTIFICATION_ICON_ID = R.drawable.stat_sys_tether_general
@@ -117,29 +99,19 @@
 
     private inner class WrappedNotificationUpdater(c: Context, looper: Looper)
         : TetheringNotificationUpdater(c, looper) {
-        override fun getResourcesForSubId(context: Context, subId: Int) =
-                when (subId) {
-                    TEST_SUBID -> testResources
-                    INVALID_SUBSCRIPTION_ID -> defaultResources
-                    else -> super.getResourcesForSubId(context, subId)
-                }
+        override fun getResourcesForSubId(c: Context, subId: Int) =
+                if (subId == TEST_SUBID) testResources else super.getResourcesForSubId(c, subId)
     }
 
     private fun setupResources() {
-        doReturn(ENABLE_ICON_CONFIGS).`when`(defaultResources)
-                .getStringArray(R.array.tethering_notification_icons)
-        doReturn(arrayOf("WIFI;android.test:drawable/wifi")).`when`(testResources)
-                .getStringArray(R.array.tethering_notification_icons)
         doReturn(5).`when`(testResources)
                 .getInteger(R.integer.delay_to_show_no_upstream_after_no_backhaul)
         doReturn(true).`when`(testResources)
                 .getBoolean(R.bool.config_upstream_roaming_notification)
-        doReturn(TITLE).`when`(defaultResources).getString(R.string.tethering_notification_title)
-        doReturn(MESSAGE).`when`(defaultResources)
-                .getString(R.string.tethering_notification_message)
-        doReturn(TEST_TITLE).`when`(testResources).getString(R.string.tethering_notification_title)
-        doReturn(TEST_MESSAGE).`when`(testResources)
-                .getString(R.string.tethering_notification_message)
+        doReturn(TEST_DISALLOW_TITLE).`when`(testResources)
+                .getString(R.string.disable_tether_notification_title)
+        doReturn(TEST_DISALLOW_MESSAGE).`when`(testResources)
+                .getString(R.string.disable_tether_notification_message)
         doReturn(TEST_NO_UPSTREAM_TITLE).`when`(testResources)
                 .getString(R.string.no_upstream_notification_title)
         doReturn(TEST_NO_UPSTREAM_MESSAGE).`when`(testResources)
@@ -150,14 +122,6 @@
                 .getString(R.string.upstream_roaming_notification_title)
         doReturn(TEST_ROAMING_MESSAGE).`when`(testResources)
                 .getString(R.string.upstream_roaming_notification_message)
-        doReturn(USB_ICON_ID).`when`(defaultResources)
-                .getIdentifier(eq("android.test:drawable/usb"), any(), any())
-        doReturn(BT_ICON_ID).`when`(defaultResources)
-                .getIdentifier(eq("android.test:drawable/bluetooth"), any(), any())
-        doReturn(GENERAL_ICON_ID).`when`(defaultResources)
-                .getIdentifier(eq("android.test:drawable/general"), any(), any())
-        doReturn(WIFI_ICON_ID).`when`(testResources)
-                .getIdentifier(eq("android.test:drawable/wifi"), any(), any())
     }
 
     @Before
@@ -206,119 +170,27 @@
     }
 
     @Test
-    fun testNotificationWithDownstreamChanged() {
-        // Wifi downstream. No notification.
-        notificationUpdater.onDownstreamChanged(WIFI_MASK)
-        verifyNotificationCancelled(listOf(ENABLE_NOTIFICATION_ID))
-
-        // Same downstream changed. Nothing happened.
-        notificationUpdater.onDownstreamChanged(WIFI_MASK)
-        verifyZeroInteractions(notificationManager)
-
-        // Wifi and usb downstreams. Show enable notification
-        notificationUpdater.onDownstreamChanged(WIFI_MASK or USB_MASK)
-        verifyNotification(GENERAL_ICON_ID, TITLE, MESSAGE, ENABLE_NOTIFICATION_ID)
-
-        // Usb downstream. Still show enable notification.
-        notificationUpdater.onDownstreamChanged(USB_MASK)
-        verifyNotification(USB_ICON_ID, TITLE, MESSAGE, ENABLE_NOTIFICATION_ID)
-
-        // No downstream. No notification.
-        notificationUpdater.onDownstreamChanged(DOWNSTREAM_NONE)
-        verifyNotificationCancelled(listOf(ENABLE_NOTIFICATION_ID, NO_UPSTREAM_NOTIFICATION_ID,
-                ROAMING_NOTIFICATION_ID))
-    }
-
-    @Test
-    fun testNotificationWithActiveDataSubscriptionIdChanged() {
-        // Usb downstream. Showed enable notification with default resource.
-        notificationUpdater.onDownstreamChanged(USB_MASK)
-        verifyNotification(USB_ICON_ID, TITLE, MESSAGE, ENABLE_NOTIFICATION_ID)
-
-        // Same subId changed. Nothing happened.
-        notificationUpdater.onActiveDataSubscriptionIdChanged(INVALID_SUBSCRIPTION_ID)
-        verifyZeroInteractions(notificationManager)
-
-        // Set test sub id. Clear notification with test resource.
+    fun testRestrictedNotification() {
+        // Set test sub id.
         notificationUpdater.onActiveDataSubscriptionIdChanged(TEST_SUBID)
-        verifyNotificationCancelled(listOf(ENABLE_NOTIFICATION_ID, NO_UPSTREAM_NOTIFICATION_ID,
-                ROAMING_NOTIFICATION_ID))
-
-        // Wifi downstream. Show enable notification with test resource.
-        notificationUpdater.onDownstreamChanged(WIFI_MASK)
-        verifyNotification(WIFI_ICON_ID, TEST_TITLE, TEST_MESSAGE, ENABLE_NOTIFICATION_ID)
-
-        // No downstream. No notification.
-        notificationUpdater.onDownstreamChanged(DOWNSTREAM_NONE)
-        verifyNotificationCancelled(listOf(ENABLE_NOTIFICATION_ID, NO_UPSTREAM_NOTIFICATION_ID,
-                ROAMING_NOTIFICATION_ID))
-    }
-
-    private fun assertIconNumbers(number: Int, configs: Array<String?>) {
-        doReturn(configs).`when`(defaultResources)
-                .getStringArray(R.array.tethering_notification_icons)
-        assertEquals(number, notificationUpdater.getIcons(
-                R.array.tethering_notification_icons, defaultResources).size())
-    }
-
-    @Test
-    fun testGetIcons() {
-        assertIconNumbers(0, arrayOfNulls<String>(0))
-        assertIconNumbers(0, arrayOf(null, ""))
-        assertIconNumbers(3, arrayOf(
-                // These configurations are invalid with wrong strings or symbols.
-                ";", ",", "|", "|,;", "WIFI", "1;2", " U SB; ", "bt;", "WIFI;USB;BT", "WIFI|USB|BT",
-                "WIFI,BT,USB", " WIFI| |  | USB, test:drawable/test",
-                // This configuration is valid with two downstream types (USB, BT).
-                "USB|,,,,,|BT;drawable/test ",
-                // This configuration is valid with one downstream types (WIFI).
-                "     WIFI     ; android.test:drawable/xxx "))
-    }
-
-    @Test
-    fun testGetDownstreamTypesMask() {
-        assertEquals(DOWNSTREAM_NONE, notificationUpdater.getDownstreamTypesMask(""))
-        assertEquals(DOWNSTREAM_NONE, notificationUpdater.getDownstreamTypesMask("1"))
-        assertEquals(DOWNSTREAM_NONE, notificationUpdater.getDownstreamTypesMask("WIFI_P2P"))
-        assertEquals(DOWNSTREAM_NONE, notificationUpdater.getDownstreamTypesMask("usb"))
-        assertEquals(WIFI_MASK, notificationUpdater.getDownstreamTypesMask(" WIFI "))
-        assertEquals(USB_MASK, notificationUpdater.getDownstreamTypesMask("USB | B T"))
-        assertEquals(BT_MASK, notificationUpdater.getDownstreamTypesMask(" WIFI: | BT"))
-        assertEquals(WIFI_MASK or USB_MASK,
-                notificationUpdater.getDownstreamTypesMask("1|2|USB|WIFI|BLUETOOTH||"))
-    }
-
-    @Test
-    fun testSetupRestrictedNotification() {
-        val title = context.resources.getString(R.string.disable_tether_notification_title)
-        val message = context.resources.getString(R.string.disable_tether_notification_message)
-        val disallowTitle = "Tether function is disallowed"
-        val disallowMessage = "Please contact your admin"
-        doReturn(title).`when`(defaultResources)
-                .getString(R.string.disable_tether_notification_title)
-        doReturn(message).`when`(defaultResources)
-                .getString(R.string.disable_tether_notification_message)
-        doReturn(disallowTitle).`when`(testResources)
-                .getString(R.string.disable_tether_notification_title)
-        doReturn(disallowMessage).`when`(testResources)
-                .getString(R.string.disable_tether_notification_message)
+        verifyNotificationCancelled(listOf(NO_UPSTREAM_NOTIFICATION_ID, ROAMING_NOTIFICATION_ID))
 
         // User restrictions on. Show restricted notification.
         notificationUpdater.notifyTetheringDisabledByRestriction()
-        verifyNotification(NOTIFICATION_ICON_ID, title, message, RESTRICTED_NOTIFICATION_ID)
+        verifyNotification(NOTIFICATION_ICON_ID, TEST_DISALLOW_TITLE, TEST_DISALLOW_MESSAGE,
+                RESTRICTED_NOTIFICATION_ID)
 
         // User restrictions off. Clear notification.
         notificationUpdater.tetheringRestrictionLifted()
         verifyNotificationCancelled(listOf(RESTRICTED_NOTIFICATION_ID))
 
-        // Set test sub id. No notification.
-        notificationUpdater.onActiveDataSubscriptionIdChanged(TEST_SUBID)
-        verifyNotificationCancelled(listOf(ENABLE_NOTIFICATION_ID, NO_UPSTREAM_NOTIFICATION_ID,
-                ROAMING_NOTIFICATION_ID))
+        // No downstream.
+        notificationUpdater.onDownstreamChanged(DOWNSTREAM_NONE)
+        verifyZeroInteractions(notificationManager)
 
-        // User restrictions on again. Show restricted notification with test resource.
+        // User restrictions on again. Show restricted notification.
         notificationUpdater.notifyTetheringDisabledByRestriction()
-        verifyNotification(NOTIFICATION_ICON_ID, disallowTitle, disallowMessage,
+        verifyNotification(NOTIFICATION_ICON_ID, TEST_DISALLOW_TITLE, TEST_DISALLOW_MESSAGE,
                 RESTRICTED_NOTIFICATION_ID)
     }
 
@@ -356,15 +228,14 @@
     }
 
     @Test
-    fun testNotificationWithUpstreamCapabilitiesChanged_NoUpstream() {
-        // Set test sub id. No notification.
+    fun testNoUpstreamNotification() {
+        // Set test sub id.
         notificationUpdater.onActiveDataSubscriptionIdChanged(TEST_SUBID)
-        verifyNotificationCancelled(listOf(ENABLE_NOTIFICATION_ID, NO_UPSTREAM_NOTIFICATION_ID,
-                ROAMING_NOTIFICATION_ID))
+        verifyNotificationCancelled(listOf(NO_UPSTREAM_NOTIFICATION_ID, ROAMING_NOTIFICATION_ID))
 
-        // Wifi downstream. Show enable notification with test resource.
+        // Wifi downstream.
         notificationUpdater.onDownstreamChanged(WIFI_MASK)
-        verifyNotification(WIFI_ICON_ID, TEST_TITLE, TEST_MESSAGE, ENABLE_NOTIFICATION_ID)
+        verifyNotificationCancelled(listOf(NO_UPSTREAM_NOTIFICATION_ID, ROAMING_NOTIFICATION_ID))
 
         // There is no upstream. Show no upstream notification.
         notificationUpdater.onUpstreamCapabilitiesChanged(null)
@@ -386,15 +257,14 @@
         verifyNotification(NOTIFICATION_ICON_ID, TEST_NO_UPSTREAM_TITLE, TEST_NO_UPSTREAM_MESSAGE,
                 NO_UPSTREAM_NOTIFICATION_ID)
 
-        // No downstream. No notification.
+        // No downstream.
         notificationUpdater.onDownstreamChanged(DOWNSTREAM_NONE)
-        verifyNotificationCancelled(listOf(ENABLE_NOTIFICATION_ID, NO_UPSTREAM_NOTIFICATION_ID,
-                ROAMING_NOTIFICATION_ID))
+        verifyNotificationCancelled(listOf(NO_UPSTREAM_NOTIFICATION_ID, ROAMING_NOTIFICATION_ID))
 
-        // Put up enable notification with wifi downstream and home capabilities.
+        // Wifi downstream and home capabilities.
         notificationUpdater.onDownstreamChanged(WIFI_MASK)
         notificationUpdater.onUpstreamCapabilitiesChanged(HOME_CAPABILITIES)
-        verifyNotification(WIFI_ICON_ID, TEST_TITLE, TEST_MESSAGE, ENABLE_NOTIFICATION_ID)
+        verifyNotificationCancelled(listOf(NO_UPSTREAM_NOTIFICATION_ID, ROAMING_NOTIFICATION_ID))
 
         // Set R.integer.delay_to_show_no_upstream_after_no_backhaul to -1 and change to no upstream
         // again. Don't put up no upstream notification.
@@ -429,15 +299,14 @@
     }
 
     @Test
-    fun testNotificationWithUpstreamCapabilitiesChanged_Roaming() {
-        // Set test sub id. Clear notification.
+    fun testRoamingNotification() {
+        // Set test sub id.
         notificationUpdater.onActiveDataSubscriptionIdChanged(TEST_SUBID)
-        verifyNotificationCancelled(listOf(ENABLE_NOTIFICATION_ID, NO_UPSTREAM_NOTIFICATION_ID,
-                ROAMING_NOTIFICATION_ID))
+        verifyNotificationCancelled(listOf(NO_UPSTREAM_NOTIFICATION_ID, ROAMING_NOTIFICATION_ID))
 
-        // Wifi downstream. Show enable notification with test resource.
+        // Wifi downstream.
         notificationUpdater.onDownstreamChanged(WIFI_MASK)
-        verifyNotification(WIFI_ICON_ID, TEST_TITLE, TEST_MESSAGE, ENABLE_NOTIFICATION_ID)
+        verifyNotificationCancelled(listOf(NO_UPSTREAM_NOTIFICATION_ID, ROAMING_NOTIFICATION_ID))
 
         // Upstream capabilities changed to roaming state. Show roaming notification.
         notificationUpdater.onUpstreamCapabilitiesChanged(ROAMING_CAPABILITIES)
@@ -464,14 +333,16 @@
         verifyNotification(NOTIFICATION_ICON_ID, TEST_NO_UPSTREAM_TITLE, TEST_NO_UPSTREAM_MESSAGE,
                 NO_UPSTREAM_NOTIFICATION_ID)
 
-        // No downstream. No notification.
+        // No downstream.
         notificationUpdater.onDownstreamChanged(DOWNSTREAM_NONE)
-        verifyNotificationCancelled(listOf(ENABLE_NOTIFICATION_ID, NO_UPSTREAM_NOTIFICATION_ID,
-                ROAMING_NOTIFICATION_ID))
+        verifyNotificationCancelled(listOf(NO_UPSTREAM_NOTIFICATION_ID, ROAMING_NOTIFICATION_ID))
 
-        // Wifi downstream again. Show enable notification with test resource.
+        // Wifi downstream again.
         notificationUpdater.onDownstreamChanged(WIFI_MASK)
-        verifyNotification(WIFI_ICON_ID, TEST_TITLE, TEST_MESSAGE, ENABLE_NOTIFICATION_ID)
+        notificationUpdater.handler.waitForDelayedMessage(EVENT_SHOW_NO_UPSTREAM, TIMEOUT_MS)
+        verifyNotificationCancelled(listOf(ROAMING_NOTIFICATION_ID), false)
+        verifyNotification(NOTIFICATION_ICON_ID, TEST_NO_UPSTREAM_TITLE, TEST_NO_UPSTREAM_MESSAGE,
+                NO_UPSTREAM_NOTIFICATION_ID)
 
         // Set R.bool.config_upstream_roaming_notification to false and change upstream
         // network to roaming state again. No roaming notification.
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 132b692..da9bdf3 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -1023,8 +1023,7 @@
         try {
             mMainHandler.post(PooledLambda.obtainRunnable((nonArg) -> {
                 final ScreenshotGraphicBuffer screenshotBuffer = LocalServices
-                        .getService(DisplayManagerInternal.class)
-                        .screenshotWithoutSecureLayer(displayId);
+                        .getService(DisplayManagerInternal.class).userScreenshot(displayId);
                 if (screenshotBuffer != null) {
                     sendScreenshotSuccess(screenshotBuffer, callback);
                 } else {
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index b27c5d5..35089d6 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -117,6 +117,7 @@
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.function.Consumer;
+import java.util.function.Function;
 
 /**
  * A session for a given activity.
@@ -3079,19 +3080,13 @@
         final boolean isWhitelisted = mService
                 .isWhitelistedForAugmentedAutofillLocked(mComponentName);
 
-        final String historyItem =
-                "aug:id=" + id + " u=" + uid + " m=" + mode
-                + " a=" + ComponentName.flattenToShortString(mComponentName)
-                + " f=" + mCurrentViewId
-                + " s=" + remoteService.getComponentName()
-                + " w=" + isWhitelisted;
-        mService.getMaster().logRequestLocked(historyItem);
-
         if (!isWhitelisted) {
             if (sVerbose) {
                 Slog.v(TAG, "triggerAugmentedAutofillLocked(): "
                         + ComponentName.flattenToShortString(mComponentName) + " not whitelisted ");
             }
+            logAugmentedAutofillRequestLocked(mode, remoteService.getComponentName(),
+                    mCurrentViewId, isWhitelisted, /*isInline*/null);
             return null;
         }
 
@@ -3116,24 +3111,27 @@
 
         final AutofillId focusedId = mCurrentViewId;
 
+        final Function<InlineSuggestionsResponse, Boolean> inlineSuggestionsResponseCallback =
+                response -> {
+                    synchronized (mLock) {
+                        return mInlineSessionController.onInlineSuggestionsResponseLocked(
+                                focusedId, response);
+                    }
+                };
         final Consumer<InlineSuggestionsRequest> requestAugmentedAutofill =
                 (inlineSuggestionsRequest) -> {
-                    remoteService.onRequestAutofillLocked(id, mClient, taskId, mComponentName,
-                            AutofillId.withoutSession(focusedId),
-                            currentValue, inlineSuggestionsRequest,
-                            /*inlineSuggestionsCallback=*/
-                            response -> {
-                                synchronized (mLock) {
-                                    return mInlineSessionController
-                                            .onInlineSuggestionsResponseLocked(
-                                                    focusedId, response);
-                                }
-                            },
-                            /*onErrorCallback=*/ () -> {
-                                synchronized (mLock) {
-                                    cancelAugmentedAutofillLocked();
-                                }
-                            }, mService.getRemoteInlineSuggestionRenderServiceLocked());
+                    synchronized (mLock) {
+                        logAugmentedAutofillRequestLocked(mode, remoteService.getComponentName(),
+                                focusedId, isWhitelisted, inlineSuggestionsRequest != null);
+                        remoteService.onRequestAutofillLocked(id, mClient, taskId, mComponentName,
+                                AutofillId.withoutSession(focusedId), currentValue,
+                                inlineSuggestionsRequest, inlineSuggestionsResponseCallback,
+                                /*onErrorCallback=*/ () -> {
+                                    synchronized (mLock) {
+                                        cancelAugmentedAutofillLocked();
+                                    }
+                                }, mService.getRemoteInlineSuggestionRenderServiceLocked());
+                    }
                 };
 
         // When the inline suggestion render service is available, there are 2 cases when
@@ -3150,9 +3148,11 @@
             if (sDebug) Slog.d(TAG, "Create inline request for augmented autofill");
             remoteRenderService.getInlineSuggestionsRendererInfo(new RemoteCallback(
                     (extras) -> {
-                        mInlineSessionController.onCreateInlineSuggestionsRequestLocked(
-                                focusedId, /*requestConsumer=*/ requestAugmentedAutofill,
-                                extras);
+                        synchronized (mLock) {
+                            mInlineSessionController.onCreateInlineSuggestionsRequestLocked(
+                                    focusedId, /*requestConsumer=*/ requestAugmentedAutofill,
+                                    extras);
+                        }
                     }, mHandler));
         } else {
             requestAugmentedAutofill.accept(
@@ -3165,6 +3165,20 @@
     }
 
     @GuardedBy("mLock")
+    private void logAugmentedAutofillRequestLocked(int mode,
+            ComponentName augmentedRemoteServiceName, AutofillId focusedId, boolean isWhitelisted,
+            Boolean isInline) {
+        final String historyItem =
+                "aug:id=" + id + " u=" + uid + " m=" + mode
+                        + " a=" + ComponentName.flattenToShortString(mComponentName)
+                        + " f=" + focusedId
+                        + " s=" + augmentedRemoteServiceName
+                        + " w=" + isWhitelisted
+                        + " i=" + isInline;
+        mService.getMaster().logRequestLocked(historyItem);
+    }
+
+    @GuardedBy("mLock")
     private void cancelAugmentedAutofillLocked() {
         final RemoteAugmentedAutofillService remoteService = mService
                 .getRemoteAugmentedAutofillServiceLocked();
diff --git a/services/core/java/com/android/server/AlarmManagerService.java b/services/core/java/com/android/server/AlarmManagerService.java
index ce65110..4009caf 100644
--- a/services/core/java/com/android/server/AlarmManagerService.java
+++ b/services/core/java/com/android/server/AlarmManagerService.java
@@ -603,8 +603,7 @@
             }
 
             pw.print(KEY_APP_STANDBY_RESTRICTED_QUOTA); pw.print("=");
-            TimeUtils.formatDuration(APP_STANDBY_RESTRICTED_QUOTA, pw);
-            pw.println();
+            pw.println(APP_STANDBY_RESTRICTED_QUOTA);
 
             pw.print(KEY_APP_STANDBY_RESTRICTED_WINDOW); pw.print("=");
             TimeUtils.formatDuration(APP_STANDBY_RESTRICTED_WINDOW, pw);
diff --git a/services/core/java/com/android/server/TelephonyRegistry.java b/services/core/java/com/android/server/TelephonyRegistry.java
index 2bbf278..97a5cfe 100644
--- a/services/core/java/com/android/server/TelephonyRegistry.java
+++ b/services/core/java/com/android/server/TelephonyRegistry.java
@@ -1146,7 +1146,7 @@
                     && registrationLimit >= 1
                     && numRecordsForPid >= registrationLimit) {
                 String errorMsg = "Pid " + callingPid + " has exceeded the number of permissible"
-                        + "registered listeners. Ignoring request to add.";
+                        + " registered listeners. Ignoring request to add.";
                 loge(errorMsg);
                 if (mConfigurationProvider
                         .isRegistrationLimitEnabledInPlatformCompat(callingUid)) {
@@ -1157,7 +1157,7 @@
                 // Log the warning independently of the dynamically set limit -- apps shouldn't be
                 // doing this regardless of whether we're throwing them an exception for it.
                 Rlog.w(TAG, "Pid " + callingPid + " has exceeded half the number of permissible"
-                        + "registered listeners. Now at " + numRecordsForPid);
+                        + " registered listeners. Now at " + numRecordsForPid);
             }
 
             r = new Record();
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 21760cd..419389f 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -119,7 +119,6 @@
 import java.io.StringWriter;
 import java.util.ArrayList;
 import java.util.Comparator;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
 import java.util.function.Predicate;
@@ -1753,8 +1752,8 @@
     private void updateServiceForegroundLocked(ProcessRecord proc, boolean oomAdj) {
         boolean anyForeground = false;
         int fgServiceTypes = 0;
-        for (int i = proc.services.size() - 1; i >= 0; i--) {
-            ServiceRecord sr = proc.services.valueAt(i);
+        for (int i = proc.numberOfRunningServices() - 1; i >= 0; i--) {
+            ServiceRecord sr = proc.getRunningServiceAt(i);
             if (sr.isForeground || sr.fgRequired) {
                 anyForeground = true;
                 fgServiceTypes |= sr.foregroundServiceType;
@@ -1765,8 +1764,8 @@
 
     private void updateWhitelistManagerLocked(ProcessRecord proc) {
         proc.whitelistManager = false;
-        for (int i=proc.services.size()-1; i>=0; i--) {
-            ServiceRecord sr = proc.services.valueAt(i);
+        for (int i = proc.numberOfRunningServices() - 1; i >= 0; i--) {
+            ServiceRecord sr = proc.getRunningServiceAt(i);
             if (sr.whitelistManager) {
                 proc.whitelistManager = true;
                 break;
@@ -1802,8 +1801,8 @@
         }
 
         boolean anyClientActivities = false;
-        for (int i=proc.services.size()-1; i>=0 && !anyClientActivities; i--) {
-            ServiceRecord sr = proc.services.valueAt(i);
+        for (int i = proc.numberOfRunningServices() - 1; i >= 0 && !anyClientActivities; i--) {
+            ServiceRecord sr = proc.getRunningServiceAt(i);
             ArrayMap<IBinder, ArrayList<ConnectionRecord>> connections = sr.getConnections();
             for (int conni = connections.size() - 1; conni >= 0 && !anyClientActivities; conni--) {
                 ArrayList<ConnectionRecord> clist = connections.valueAt(conni);
@@ -2995,7 +2994,7 @@
         r.setProcess(app);
         r.restartTime = r.lastActivity = SystemClock.uptimeMillis();
 
-        final boolean newService = app.services.add(r);
+        final boolean newService = app.startService(r);
         bumpServiceExecutingLocked(r, execInFg, "create");
         mAm.updateLruProcessLocked(app, false, null);
         updateServiceForegroundLocked(r.app, /* oomAdj= */ false);
@@ -3036,7 +3035,7 @@
 
                 // Cleanup.
                 if (newService) {
-                    app.services.remove(r);
+                    app.stopService(r);
                     r.setProcess(null);
                 }
 
@@ -3362,7 +3361,7 @@
             synchronized (r.stats.getBatteryStats()) {
                 r.stats.stopLaunchedLocked();
             }
-            r.app.services.remove(r);
+            r.app.stopService(r);
             r.app.updateBoundClientUids();
             if (r.whitelistManager) {
                 updateWhitelistManagerLocked(r.app);
@@ -3652,7 +3651,7 @@
             }
             if (finishing) {
                 if (r.app != null && !r.app.isPersistent()) {
-                    r.app.services.remove(r);
+                    r.app.stopService(r);
                     r.app.updateBoundClientUids();
                     if (r.whitelistManager) {
                         updateWhitelistManagerLocked(r.app);
@@ -3748,7 +3747,7 @@
                 didSomething = true;
                 Slog.i(TAG, "  Force stopping service " + service);
                 if (service.app != null && !service.app.isPersistent()) {
-                    service.app.services.remove(service);
+                    service.app.stopService(service);
                     service.app.updateBoundClientUids();
                     if (service.whitelistManager) {
                         updateWhitelistManagerLocked(service.app);
@@ -3861,24 +3860,22 @@
         if (false) {
             // XXX we are letting the client link to the service for
             // death notifications.
-            if (app.services.size() > 0) {
-                Iterator<ServiceRecord> it = app.services.iterator();
-                while (it.hasNext()) {
-                    ServiceRecord r = it.next();
-                    ArrayMap<IBinder, ArrayList<ConnectionRecord>> connections = r.getConnections();
-                    for (int conni=connections.size()-1; conni>=0; conni--) {
-                        ArrayList<ConnectionRecord> cl = connections.valueAt(conni);
-                        for (int i=0; i<cl.size(); i++) {
-                            ConnectionRecord c = cl.get(i);
-                            if (c.binding.client != app) {
-                                try {
-                                    //c.conn.connected(r.className, null);
-                                } catch (Exception e) {
-                                    // todo: this should be asynchronous!
-                                    Slog.w(TAG, "Exception thrown disconnected servce "
-                                          + r.shortInstanceName
-                                          + " from app " + app.processName, e);
-                                }
+            int numberOfRunningServices = app.numberOfRunningServices();
+            for (int sIndex = 0; sIndex < numberOfRunningServices; sIndex++) {
+                ServiceRecord r = app.getRunningServiceAt(sIndex);
+                ArrayMap<IBinder, ArrayList<ConnectionRecord>> connections = r.getConnections();
+                for (int conni = connections.size() - 1; conni >= 0; conni--) {
+                    ArrayList<ConnectionRecord> cl = connections.valueAt(conni);
+                    for (int i = 0; i < cl.size(); i++) {
+                        ConnectionRecord c = cl.get(i);
+                        if (c.binding.client != app) {
+                            try {
+                                //c.conn.connected(r.className, null);
+                            } catch (Exception e) {
+                                // todo: this should be asynchronous!
+                                Slog.w(TAG, "Exception thrown disconnected servce "
+                                        + r.shortInstanceName
+                                        + " from app " + app.processName, e);
                             }
                         }
                     }
@@ -3897,13 +3894,13 @@
         app.whitelistManager = false;
 
         // Clear app state from services.
-        for (int i = app.services.size() - 1; i >= 0; i--) {
-            ServiceRecord sr = app.services.valueAt(i);
+        for (int i = app.numberOfRunningServices() - 1; i >= 0; i--) {
+            ServiceRecord sr = app.getRunningServiceAt(i);
             synchronized (sr.stats.getBatteryStats()) {
                 sr.stats.stopLaunchedLocked();
             }
             if (sr.app != app && sr.app != null && !sr.app.isPersistent()) {
-                sr.app.services.remove(sr);
+                sr.app.stopService(sr);
                 sr.app.updateBoundClientUids();
             }
             sr.setProcess(null);
@@ -3962,13 +3959,13 @@
         ServiceMap smap = getServiceMapLocked(app.userId);
 
         // Now do remaining service cleanup.
-        for (int i=app.services.size()-1; i>=0; i--) {
-            ServiceRecord sr = app.services.valueAt(i);
+        for (int i = app.numberOfRunningServices() - 1; i >= 0; i--) {
+            ServiceRecord sr = app.getRunningServiceAt(i);
 
             // Unless the process is persistent, this process record is going away,
             // so make sure the service is cleaned out of it.
             if (!app.isPersistent()) {
-                app.services.removeAt(i);
+                app.stopService(sr);
                 app.updateBoundClientUids();
             }
 
@@ -4018,7 +4015,7 @@
         }
 
         if (!allowRestart) {
-            app.services.clear();
+            app.stopAllServices();
             app.clearBoundClientUids();
 
             // Make sure there are no more restarting services for this process.
@@ -4920,8 +4917,8 @@
             if (pr.uid != uid) {
                 continue;
             }
-            for (int j = pr.services.size() - 1; j >= 0; j--) {
-                ServiceRecord r = pr.services.valueAt(j);
+            for (int j = pr.numberOfRunningServices() - 1; j >= 0; j--) {
+                ServiceRecord r = pr.getRunningServiceAt(j);
                 if (!r.isForeground) {
                     continue;
                 }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 3fdf541..9b2743c 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -18172,7 +18172,7 @@
         for (int i = mProcessList.mRemovedProcesses.size() - 1; i >= 0; i--) {
             final ProcessRecord app = mProcessList.mRemovedProcesses.get(i);
             if (!app.hasActivitiesOrRecentTasks()
-                    && app.curReceivers.isEmpty() && app.services.size() == 0) {
+                    && app.curReceivers.isEmpty() && app.numberOfRunningServices() == 0) {
                 Slog.i(
                     TAG, "Exiting empty application process "
                     + app.toShortString() + " ("
diff --git a/services/core/java/com/android/server/am/AppErrors.java b/services/core/java/com/android/server/am/AppErrors.java
index b1fc029..50d2cab 100644
--- a/services/core/java/com/android/server/am/AppErrors.java
+++ b/services/core/java/com/android/server/am/AppErrors.java
@@ -702,10 +702,10 @@
         }
 
         // Bump up the crash count of any services currently running in the proc.
-        for (int i = app.services.size() - 1; i >= 0; i--) {
+        for (int i = app.numberOfRunningServices() - 1; i >= 0; i--) {
             // Any services running in the application need to be placed
             // back in the pending list.
-            ServiceRecord sr = app.services.valueAt(i);
+            ServiceRecord sr = app.getRunningServiceAt(i);
             // If the service was restarted a while ago, then reset crash count, else increment it.
             if (now > sr.restartTime + ProcessList.MIN_CRASH_INTERVAL) {
                 sr.crashCount = 1;
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index dbad562..b647818 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -1013,9 +1013,7 @@
     @Override
     public void noteNetworkInterfaceType(String iface, int networkType) {
         enforceCallingPermission();
-        synchronized (mStats) {
-            mStats.noteNetworkInterfaceTypeLocked(iface, networkType);
-        }
+        mStats.noteNetworkInterfaceType(iface, networkType);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 2d6ef81..ad85853 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -837,7 +837,8 @@
                         break;
                 }
 
-                if (app.isolated && app.services.size() <= 0 && app.isolatedEntryPoint == null) {
+                if (app.isolated && app.numberOfRunningServices() <= 0
+                        && app.isolatedEntryPoint == null) {
                     // If this is an isolated process, there are no services
                     // running in it, and it's not a special process with a
                     // custom entry point, then the process is no longer
@@ -1446,12 +1447,12 @@
         }
 
         int capabilityFromFGS = 0; // capability from foreground service.
-        for (int is = app.services.size() - 1;
+        for (int is = app.numberOfRunningServices() - 1;
                 is >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ
                         || schedGroup == ProcessList.SCHED_GROUP_BACKGROUND
                         || procState > PROCESS_STATE_TOP);
                 is--) {
-            ServiceRecord s = app.services.valueAt(is);
+            ServiceRecord s = app.getRunningServiceAt(is);
             if (s.startRequested) {
                 app.hasStartedServices = true;
                 if (procState > PROCESS_STATE_SERVICE) {
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 61ebc36..a1ec07c 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -261,9 +261,9 @@
     // Controller for error dialogs
     private final ErrorDialogController mDialogController = new ErrorDialogController();
     // Controller for driving the process state on the window manager side.
-    final private WindowProcessController mWindowProcessController;
+    private final WindowProcessController mWindowProcessController;
     // all ServiceRecord running in this process
-    final ArraySet<ServiceRecord> services = new ArraySet<>();
+    private final ArraySet<ServiceRecord> mServices = new ArraySet<>();
     // services that are currently executing code (need to remain foreground).
     final ArraySet<ServiceRecord> executingServices = new ArraySet<>();
     // All ConnectionRecord this process holds
@@ -577,10 +577,10 @@
             pw.println(Arrays.toString(isolatedEntryPointArgs));
         }
         mWindowProcessController.dump(pw, prefix);
-        if (services.size() > 0) {
+        if (mServices.size() > 0) {
             pw.print(prefix); pw.println("Services:");
-            for (int i=0; i<services.size(); i++) {
-                pw.print(prefix); pw.print("  - "); pw.println(services.valueAt(i));
+            for (int i = 0; i < mServices.size(); i++) {
+                pw.print(prefix); pw.print("  - "); pw.println(mServices.valueAt(i));
             }
         }
         if (executingServices.size() > 0) {
@@ -735,6 +735,60 @@
         }
     }
 
+    /**
+     * Records a service as running in the process. Note that this method does not actually start
+     * the service, but records the service as started for bookkeeping.
+     *
+     * @return true if the service was added, false otherwise.
+     */
+    boolean startService(ServiceRecord record) {
+        if (record == null) {
+            return false;
+        }
+        boolean added = mServices.add(record);
+        if (added && record.serviceInfo != null) {
+            mWindowProcessController.onServiceStarted(record.serviceInfo);
+        }
+        return added;
+    }
+
+    /**
+     * Records a service as stopped. Note that like {@link #startService(ServiceRecord)} this method
+     * does not actually stop the service, but records the service as stopped for bookkeeping.
+     *
+     * @return true if the service was removed, false otherwise.
+     */
+    boolean stopService(ServiceRecord record) {
+        return mServices.remove(record);
+    }
+
+    /**
+     * The same as calling {@link #stopService(ServiceRecord)} on all current running services.
+     */
+    void stopAllServices() {
+        mServices.clear();
+    }
+
+    /**
+     * Returns the number of services added with {@link #startService(ServiceRecord)} and not yet
+     * removed by a call to {@link #stopService(ServiceRecord)} or {@link #stopAllServices()}.
+     *
+     * @see #startService(ServiceRecord)
+     * @see #stopService(ServiceRecord)
+     */
+    int numberOfRunningServices() {
+        return mServices.size();
+    }
+
+    /**
+     * Returns the service at the specified {@code index}.
+     *
+     * @see #numberOfRunningServices()
+     */
+    ServiceRecord getRunningServiceAt(int index) {
+        return mServices.valueAt(index);
+    }
+
     void setCached(boolean cached) {
         if (mCached != cached) {
             mCached = cached;
@@ -768,9 +822,9 @@
             return true;
         }
 
-        final int servicesSize = services.size();
+        final int servicesSize = mServices.size();
         for (int i = 0; i < servicesSize; i++) {
-            ServiceRecord r = services.valueAt(i);
+            ServiceRecord r = mServices.valueAt(i);
             if (r.isForeground) {
                 return true;
             }
@@ -1289,16 +1343,16 @@
     }
 
     void updateBoundClientUids() {
-        if (services.isEmpty()) {
+        if (mServices.isEmpty()) {
             clearBoundClientUids();
             return;
         }
         // grab a set of clientUids of all connections of all services
         ArraySet<Integer> boundClientUids = new ArraySet<>();
-        final int K = services.size();
-        for (int j = 0; j < K; j++) {
+        final int serviceCount = mServices.size();
+        for (int j = 0; j < serviceCount; j++) {
             ArrayMap<IBinder, ArrayList<ConnectionRecord>> conns =
-                    services.valueAt(j).getConnections();
+                    mServices.valueAt(j).getConnections();
             final int N = conns.size();
             for (int conni = 0; conni < N; conni++) {
                 ArrayList<ConnectionRecord> c = conns.valueAt(conni);
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index c2c79d3..032ad63 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -30,6 +30,7 @@
 import android.media.AudioSystem;
 import android.media.IAudioRoutesObserver;
 import android.media.IStrategyPreferredDeviceDispatcher;
+import android.media.MediaMetrics;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
@@ -668,6 +669,13 @@
         }
         AudioService.sForceUseLogger.log(
                 new AudioServiceEvents.ForceUseEvent(useCase, config, eventSource));
+        new MediaMetrics.Item(MediaMetrics.Name.AUDIO_FORCE_USE + MediaMetrics.SEPARATOR
+                + AudioSystem.forceUseUsageToString(useCase))
+                .set(MediaMetrics.Property.EVENT, "onSetForceUse")
+                .set(MediaMetrics.Property.FORCE_USE_DUE_TO, eventSource)
+                .set(MediaMetrics.Property.FORCE_USE_MODE,
+                        AudioSystem.forceUseConfigToString(config))
+                .record();
         AudioSystem.setForceUse(useCase, config);
     }
 
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index c17ed3e..3e97a1e 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -33,6 +33,7 @@
 import android.media.AudioSystem;
 import android.media.IAudioRoutesObserver;
 import android.media.IStrategyPreferredDeviceDispatcher;
+import android.media.MediaMetrics;
 import android.os.Binder;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
@@ -64,10 +65,69 @@
     // lock to synchronize all access to mConnectedDevices and mApmConnectedDevices
     private final Object mDevicesLock = new Object();
 
+    //Audio Analytics ids.
+    private static final String mMetricsId = "audio.device.";
+
     // List of connected devices
     // Key for map created from DeviceInfo.makeDeviceListKey()
     @GuardedBy("mDevicesLock")
-    private final LinkedHashMap<String, DeviceInfo> mConnectedDevices = new LinkedHashMap<>();
+    private final LinkedHashMap<String, DeviceInfo> mConnectedDevices = new LinkedHashMap<>() {
+        @Override
+        public DeviceInfo put(String key, DeviceInfo value) {
+            final DeviceInfo result = super.put(key, value);
+            record("put", true /* connected */, key, value);
+            return result;
+        }
+
+        @Override
+        public DeviceInfo putIfAbsent(String key, DeviceInfo value) {
+            final DeviceInfo result = super.putIfAbsent(key, value);
+            if (result == null) {
+                record("putIfAbsent", true /* connected */, key, value);
+            }
+            return result;
+        }
+
+        @Override
+        public DeviceInfo remove(Object key) {
+            final DeviceInfo result = super.remove(key);
+            if (result != null) {
+                record("remove", false /* connected */, (String) key, result);
+            }
+            return result;
+        }
+
+        @Override
+        public boolean remove(Object key, Object value) {
+            final boolean result = super.remove(key, value);
+            if (result) {
+                record("remove", false /* connected */, (String) key, (DeviceInfo) value);
+            }
+            return result;
+        }
+
+        // Not overridden
+        // clear
+        // compute
+        // computeIfAbsent
+        // computeIfPresent
+        // merge
+        // putAll
+        // replace
+        // replaceAll
+        private void record(String event, boolean connected, String key, DeviceInfo value) {
+            // DeviceInfo - int mDeviceType;
+            // DeviceInfo - int mDeviceCodecFormat;
+            new MediaMetrics.Item(MediaMetrics.Name.AUDIO_DEVICE
+                    + MediaMetrics.SEPARATOR + AudioSystem.getDeviceName(value.mDeviceType))
+                    .set(MediaMetrics.Property.ADDRESS, value.mDeviceAddress)
+                    .set(MediaMetrics.Property.EVENT, event)
+                    .set(MediaMetrics.Property.NAME, value.mDeviceName)
+                    .set(MediaMetrics.Property.STATE, connected
+                            ? MediaMetrics.Value.CONNECTED : MediaMetrics.Value.DISCONNECTED)
+                    .record();
+        }
+    };
 
     // List of devices actually connected to AudioPolicy (through AudioSystem), only one
     // by device type, which is used as the key, value is the DeviceInfo generated key.
@@ -236,6 +296,16 @@
                         + " codec=" + a2dpCodec
                         + " vol=" + a2dpVolume));
 
+        new MediaMetrics.Item(mMetricsId + "a2dp")
+                .set(MediaMetrics.Property.ADDRESS, address)
+                .set(MediaMetrics.Property.ENCODING, AudioSystem.audioFormatToString(a2dpCodec))
+                .set(MediaMetrics.Property.EVENT, "onSetA2dpSinkConnectionState")
+                .set(MediaMetrics.Property.INDEX, a2dpVolume)
+                .set(MediaMetrics.Property.STATE,
+                        state == BluetoothProfile.STATE_CONNECTED
+                        ? MediaMetrics.Value.CONNECTED : MediaMetrics.Value.DISCONNECTED)
+                .record();
+
         synchronized (mDevicesLock) {
             final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
                     btDevice.getAddress());
@@ -284,6 +354,15 @@
             final DeviceInfo di = mConnectedDevices.get(key);
             boolean isConnected = di != null;
 
+            new MediaMetrics.Item(mMetricsId + "onSetA2dpSourceConnectionState")
+                    .set(MediaMetrics.Property.ADDRESS, address)
+                    .set(MediaMetrics.Property.DEVICE,
+                            AudioSystem.getDeviceName(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP))
+                    .set(MediaMetrics.Property.STATE,
+                            state == BluetoothProfile.STATE_CONNECTED
+                            ? MediaMetrics.Value.CONNECTED : MediaMetrics.Value.DISCONNECTED)
+                    .record();
+
             if (isConnected && state != BluetoothProfile.STATE_CONNECTED) {
                 makeA2dpSrcUnavailable(address);
             } else if (!isConnected && state == BluetoothProfile.STATE_CONNECTED) {
@@ -301,6 +380,17 @@
         AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
                 "onSetHearingAidConnectionState addr=" + address));
 
+        new MediaMetrics.Item(mMetricsId + "onSetHearingAidConnectionState")
+                .set(MediaMetrics.Property.ADDRESS, address)
+                .set(MediaMetrics.Property.DEVICE,
+                        AudioSystem.getDeviceName(AudioSystem.DEVICE_IN_BLUETOOTH_A2DP))
+                .set(MediaMetrics.Property.STATE,
+                        state == BluetoothProfile.STATE_CONNECTED
+                                ? MediaMetrics.Value.CONNECTED : MediaMetrics.Value.DISCONNECTED)
+                .set(MediaMetrics.Property.STREAM_TYPE,
+                        AudioSystem.streamToString(streamType))
+                .record();
+
         synchronized (mDevicesLock) {
             final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID,
                     btDevice.getAddress());
@@ -317,10 +407,15 @@
     }
 
     @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
-    /*package*/ void onBluetoothA2dpActiveDeviceChange(
+        /*package*/ void onBluetoothA2dpActiveDeviceChange(
             @NonNull BtHelper.BluetoothA2dpDeviceInfo btInfo, int event) {
+        MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId
+                + "onBluetoothA2dpActiveDeviceChange")
+                .set(MediaMetrics.Property.EVENT, BtHelper.a2dpDeviceEventToString(event));
+
         final BluetoothDevice btDevice = btInfo.getBtDevice();
         if (btDevice == null) {
+            mmi.set(MediaMetrics.Property.EARLY_RETURN, "btDevice null").record();
             return;
         }
         if (AudioService.DEBUG_DEVICES) {
@@ -341,6 +436,8 @@
             if (mDeviceBroker.hasScheduledA2dpSinkConnectionState(btDevice)) {
                 AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
                         "A2dp config change ignored (scheduled connection change)"));
+                mmi.set(MediaMetrics.Property.EARLY_RETURN, "A2dp config change ignored")
+                        .record();
                 return;
             }
             final String key = DeviceInfo.makeDeviceListKey(
@@ -348,9 +445,16 @@
             final DeviceInfo di = mConnectedDevices.get(key);
             if (di == null) {
                 Log.e(TAG, "invalid null DeviceInfo in onBluetoothA2dpActiveDeviceChange");
+                mmi.set(MediaMetrics.Property.EARLY_RETURN, "null DeviceInfo").record();
                 return;
             }
 
+            mmi.set(MediaMetrics.Property.ADDRESS, address)
+                    .set(MediaMetrics.Property.ENCODING,
+                            AudioSystem.audioFormatToString(a2dpCodec))
+                    .set(MediaMetrics.Property.INDEX, a2dpVolume)
+                    .set(MediaMetrics.Property.NAME, di.mDeviceName);
+
             if (event == BtHelper.EVENT_ACTIVE_DEVICE_CHANGE) {
                 // Device is connected
                 if (a2dpVolume != -1) {
@@ -388,6 +492,7 @@
                                 + address + " codec=" + a2dpCodec).printLog(TAG));
             }
         }
+        mmi.record();
     }
 
     /*package*/ void onMakeA2dpDeviceUnavailableNow(String address, int a2dpCodec) {
@@ -399,6 +504,9 @@
     /*package*/ void onReportNewRoutes() {
         int n = mRoutesObservers.beginBroadcast();
         if (n > 0) {
+            new MediaMetrics.Item(mMetricsId + "onReportNewRoutes")
+                    .set(MediaMetrics.Property.OBSERVERS, n)
+                    .record();
             AudioRoutesInfo routes;
             synchronized (mCurAudioRoutes) {
                 routes = new AudioRoutesInfo(mCurAudioRoutes);
@@ -428,6 +536,13 @@
                             AudioDeviceInventory.WiredDeviceConnectionState wdcs) {
         AudioService.sDeviceLogger.log(new AudioServiceEvents.WiredDevConnectEvent(wdcs));
 
+        MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId
+                + "onSetWiredDeviceConnectionState")
+                .set(MediaMetrics.Property.ADDRESS, wdcs.mAddress)
+                .set(MediaMetrics.Property.DEVICE, AudioSystem.getDeviceName(wdcs.mType))
+                .set(MediaMetrics.Property.STATE,
+                        wdcs.mState == AudioService.CONNECTION_STATE_DISCONNECTED
+                                ? MediaMetrics.Value.DISCONNECTED : MediaMetrics.Value.CONNECTED);
         synchronized (mDevicesLock) {
             if ((wdcs.mState == AudioService.CONNECTION_STATE_DISCONNECTED)
                     && DEVICE_OVERRIDE_A2DP_ROUTE_ON_PLUG_SET.contains(wdcs.mType)) {
@@ -438,6 +553,8 @@
             if (!handleDeviceConnection(wdcs.mState == AudioService.CONNECTION_STATE_CONNECTED,
                     wdcs.mType, wdcs.mAddress, wdcs.mName)) {
                 // change of connection state failed, bailout
+                mmi.set(MediaMetrics.Property.EARLY_RETURN, "change of connection state failed")
+                        .record();
                 return;
             }
             if (wdcs.mState != AudioService.CONNECTION_STATE_DISCONNECTED) {
@@ -453,15 +570,20 @@
             sendDeviceConnectionIntent(wdcs.mType, wdcs.mState, wdcs.mAddress, wdcs.mName);
             updateAudioRoutes(wdcs.mType, wdcs.mState);
         }
+        mmi.record();
     }
 
     /*package*/ void onToggleHdmi() {
+        MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId + "onToggleHdmi")
+                .set(MediaMetrics.Property.DEVICE,
+                        AudioSystem.getDeviceName(AudioSystem.DEVICE_OUT_HDMI));
         synchronized (mDevicesLock) {
             // Is HDMI connected?
             final String key = DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HDMI, "");
             final DeviceInfo di = mConnectedDevices.get(key);
             if (di == null) {
                 Log.e(TAG, "invalid null DeviceInfo in onToggleHdmi");
+                mmi.set(MediaMetrics.Property.EARLY_RETURN, "invalid null DeviceInfo").record();
                 return;
             }
             // Toggle HDMI to retrigger broadcast with proper formats.
@@ -472,6 +594,7 @@
                     AudioSystem.DEVICE_STATE_AVAILABLE, "", "",
                     "android"); // reconnect
         }
+        mmi.record();
     }
 
     /*package*/ void onSaveSetPreferredDevice(int strategy, @NonNull AudioDeviceAttributes device) {
@@ -535,6 +658,12 @@
                     + Integer.toHexString(device) + " address:" + address
                     + " name:" + deviceName + ")");
         }
+        MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId + "handleDeviceConnection")
+                .set(MediaMetrics.Property.ADDRESS, address)
+                .set(MediaMetrics.Property.DEVICE, AudioSystem.getDeviceName(device))
+                .set(MediaMetrics.Property.MODE, connect
+                        ? MediaMetrics.Value.CONNECT : MediaMetrics.Value.DISCONNECT)
+                .set(MediaMetrics.Property.NAME, deviceName);
         synchronized (mDevicesLock) {
             final String deviceKey = DeviceInfo.makeDeviceListKey(device, address);
             if (AudioService.DEBUG_DEVICES) {
@@ -550,13 +679,18 @@
                         AudioSystem.DEVICE_STATE_AVAILABLE, address, deviceName,
                         AudioSystem.AUDIO_FORMAT_DEFAULT);
                 if (res != AudioSystem.AUDIO_STATUS_OK) {
-                    Slog.e(TAG, "not connecting device 0x" + Integer.toHexString(device)
-                            + " due to command error " + res);
+                    final String reason = "not connecting device 0x" + Integer.toHexString(device)
+                            + " due to command error " + res;
+                    Slog.e(TAG, reason);
+                    mmi.set(MediaMetrics.Property.EARLY_RETURN, reason)
+                            .set(MediaMetrics.Property.STATE, MediaMetrics.Value.DISCONNECTED)
+                            .record();
                     return false;
                 }
                 mConnectedDevices.put(deviceKey, new DeviceInfo(
                         device, deviceName, address, AudioSystem.AUDIO_FORMAT_DEFAULT));
                 mDeviceBroker.postAccessoryPlugMediaUnmute(device);
+                mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.CONNECTED).record();
                 return true;
             } else if (!connect && isConnected) {
                 mAudioSystem.setDeviceConnectionState(device,
@@ -564,11 +698,13 @@
                         AudioSystem.AUDIO_FORMAT_DEFAULT);
                 // always remove even if disconnection failed
                 mConnectedDevices.remove(deviceKey);
+                mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.CONNECTED).record();
                 return true;
             }
             Log.w(TAG, "handleDeviceConnection() failed, deviceKey=" + deviceKey
                     + ", deviceSpec=" + di + ", connect=" + connect);
         }
+        mmi.set(MediaMetrics.Property.STATE, MediaMetrics.Value.DISCONNECTED).record();
         return false;
     }
 
@@ -582,6 +718,8 @@
                     toRemove.add(deviceInfo.mDeviceAddress);
                 }
             });
+            new MediaMetrics.Item(mMetricsId + "disconnectA2dp")
+                    .record();
             if (toRemove.size() > 0) {
                 final int delay = checkSendBecomingNoisyIntentInt(
                         AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
@@ -602,6 +740,8 @@
                     toRemove.add(deviceInfo.mDeviceAddress);
                 }
             });
+            new MediaMetrics.Item(mMetricsId + "disconnectA2dpSink")
+                    .record();
             toRemove.stream().forEach(deviceAddress -> makeA2dpSrcUnavailable(deviceAddress));
         }
     }
@@ -615,6 +755,8 @@
                     toRemove.add(deviceInfo.mDeviceAddress);
                 }
             });
+            new MediaMetrics.Item(mMetricsId + "disconnectHearingAid")
+                    .record();
             if (toRemove.size() > 0) {
                 final int delay = checkSendBecomingNoisyIntentInt(
                         AudioSystem.DEVICE_OUT_HEARING_AID, 0, AudioSystem.DEVICE_NONE);
@@ -743,6 +885,8 @@
         final int res = mAudioSystem.setDeviceConnectionState(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP,
                 AudioSystem.DEVICE_STATE_AVAILABLE, address, name, a2dpCodec);
 
+        // TODO: log in MediaMetrics once distinction between connection failure and
+        // double connection is made.
         if (res != AudioSystem.AUDIO_STATUS_OK) {
             AudioService.sDeviceLogger.log(new AudioEventLogger.StringEvent(
                     "APM failed to make available A2DP device addr=" + address
@@ -771,7 +915,12 @@
 
     @GuardedBy("mDevicesLock")
     private void makeA2dpDeviceUnavailableNow(String address, int a2dpCodec) {
+        MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId + "a2dp." + address)
+                .set(MediaMetrics.Property.ENCODING, AudioSystem.audioFormatToString(a2dpCodec))
+                .set(MediaMetrics.Property.EVENT, "makeA2dpDeviceUnavailableNow");
+
         if (address == null) {
+            mmi.set(MediaMetrics.Property.EARLY_RETURN, "address null").record();
             return;
         }
         final String deviceToRemoveKey =
@@ -783,6 +932,9 @@
             // removing A2DP device not currently used by AudioPolicy, log but don't act on it
             AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent(
                     "A2DP device " + address + " made unavailable, was not used")).printLog(TAG));
+            mmi.set(MediaMetrics.Property.EARLY_RETURN,
+                    "A2DP device made unavailable, was not used")
+                    .record();
             return;
         }
 
@@ -804,6 +956,7 @@
         mApmConnectedDevices.remove(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
         // Remove A2DP routes as well
         setCurrentAudioRouteNameIfPossible(null);
+        mmi.record();
     }
 
     @GuardedBy("mDevicesLock")
@@ -862,6 +1015,14 @@
         mDeviceBroker.postApplyVolumeOnDevice(streamType,
                 AudioSystem.DEVICE_OUT_HEARING_AID, "makeHearingAidDeviceAvailable");
         setCurrentAudioRouteNameIfPossible(name);
+        new MediaMetrics.Item(mMetricsId + "makeHearingAidDeviceAvailable")
+                .set(MediaMetrics.Property.ADDRESS, address != null ? address : "")
+                .set(MediaMetrics.Property.DEVICE,
+                        AudioSystem.getDeviceName(AudioSystem.DEVICE_OUT_HEARING_AID))
+                .set(MediaMetrics.Property.NAME, name)
+                .set(MediaMetrics.Property.STREAM_TYPE,
+                        AudioSystem.streamToString(streamType))
+                .record();
     }
 
     @GuardedBy("mDevicesLock")
@@ -873,6 +1034,11 @@
                 DeviceInfo.makeDeviceListKey(AudioSystem.DEVICE_OUT_HEARING_AID, address));
         // Remove Hearing Aid routes as well
         setCurrentAudioRouteNameIfPossible(null);
+        new MediaMetrics.Item(mMetricsId + "makeHearingAidDeviceUnavailable")
+                .set(MediaMetrics.Property.ADDRESS, address != null ? address : "")
+                .set(MediaMetrics.Property.DEVICE,
+                        AudioSystem.getDeviceName(AudioSystem.DEVICE_OUT_HEARING_AID))
+                .record();
     }
 
     @GuardedBy("mDevicesLock")
@@ -919,10 +1085,18 @@
     @GuardedBy("mDevicesLock")
     private int checkSendBecomingNoisyIntentInt(int device,
             @AudioService.ConnectionState int state, int musicDevice) {
+        MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId
+                + "checkSendBecomingNoisyIntentInt")
+                .set(MediaMetrics.Property.DEVICE, AudioSystem.getDeviceName(device))
+                .set(MediaMetrics.Property.STATE,
+                        state == AudioService.CONNECTION_STATE_CONNECTED
+                                ? MediaMetrics.Value.CONNECTED : MediaMetrics.Value.DISCONNECTED);
         if (state != AudioService.CONNECTION_STATE_DISCONNECTED) {
+            mmi.set(MediaMetrics.Property.DELAY_MS, 0).record(); // OK to return
             return 0;
         }
         if (!BECOMING_NOISY_INTENT_DEVICES_SET.contains(device)) {
+            mmi.set(MediaMetrics.Property.DELAY_MS, 0).record(); // OK to return
             return 0;
         }
         int delay = 0;
@@ -950,12 +1124,14 @@
                 // the pausing of some apps that are playing remotely
                 AudioService.sDeviceLogger.log((new AudioEventLogger.StringEvent(
                         "dropping ACTION_AUDIO_BECOMING_NOISY")).printLog(TAG));
+                mmi.set(MediaMetrics.Property.DELAY_MS, 0).record(); // OK to return
                 return 0;
             }
             mDeviceBroker.postBroadcastBecomingNoisy();
             delay = AudioService.BECOMING_NOISY_DELAY_MS;
         }
 
+        mmi.set(MediaMetrics.Property.DELAY_MS, delay).record();
         return delay;
     }
 
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index f840f2d..7cac376 100755
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -88,6 +88,7 @@
 import android.media.IVolumeController;
 import android.media.MediaExtractor;
 import android.media.MediaFormat;
+import android.media.MediaMetrics;
 import android.media.PlayerBase;
 import android.media.VolumePolicy;
 import android.media.audiofx.AudioEffect;
@@ -1880,6 +1881,16 @@
         synchronized (mExtVolumeControllerLock) {
             extVolCtlr = mExtVolumeController;
         }
+        new MediaMetrics.Item(mMetricsId + "adjustSuggestedStreamVolume")
+                .setUid(Binder.getCallingUid())
+                .set(MediaMetrics.Property.CALLING_PACKAGE, callingPackage)
+                .set(MediaMetrics.Property.CLIENT_NAME, caller)
+                .set(MediaMetrics.Property.DIRECTION, direction > 0
+                        ? MediaMetrics.Value.UP : MediaMetrics.Value.DOWN)
+                .set(MediaMetrics.Property.EXTERNAL, extVolCtlr != null
+                        ? MediaMetrics.Value.YES : MediaMetrics.Value.NO)
+                .set(MediaMetrics.Property.FLAGS, flags)
+                .record();
         if (extVolCtlr != null) {
             sendMsg(mAudioHandler, MSG_NOTIFY_VOL_EVENT, SENDMSG_QUEUE,
                     direction, 0 /*ignored*/,
@@ -3141,21 +3152,32 @@
         if (uid == android.os.Process.SYSTEM_UID) {
             uid = UserHandle.getUid(userId, UserHandle.getAppId(uid));
         }
+        MediaMetrics.Item mmi = new MediaMetrics.Item(MediaMetrics.Name.AUDIO_MIC)
+                .setUid(uid)
+                .set(MediaMetrics.Property.CALLING_PACKAGE, callingPackage)
+                .set(MediaMetrics.Property.EVENT, "setMicrophoneMute")
+                .set(MediaMetrics.Property.REQUEST, on
+                        ? MediaMetrics.Value.MUTE : MediaMetrics.Value.UNMUTE);
+
         // If OP_MUTE_MICROPHONE is set, disallow unmuting.
         if (!on && mAppOps.noteOp(AppOpsManager.OP_MUTE_MICROPHONE, uid, callingPackage)
                 != AppOpsManager.MODE_ALLOWED) {
+            mmi.set(MediaMetrics.Property.EARLY_RETURN, "disallow unmuting").record();
             return;
         }
         if (!checkAudioSettingsPermission("setMicrophoneMute()")) {
+            mmi.set(MediaMetrics.Property.EARLY_RETURN, "!checkAudioSettingsPermission").record();
             return;
         }
         if (userId != UserHandle.getCallingUserId() &&
                 mContext.checkCallingOrSelfPermission(
                 android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
                 != PackageManager.PERMISSION_GRANTED) {
+            mmi.set(MediaMetrics.Property.EARLY_RETURN, "permission").record();
             return;
         }
         mMicMuteFromApi = on;
+        mmi.record(); // record now, the no caller check will set the mute state.
         setMicrophoneMuteNoCallerCheck(userId);
     }
 
@@ -3167,6 +3189,12 @@
             return;
         }
         mMicMuteFromSwitch = on;
+        new MediaMetrics.Item(MediaMetrics.Name.AUDIO_MIC)
+                .setUid(userId)
+                .set(MediaMetrics.Property.EVENT, "setMicrophoneMuteFromSwitch")
+                .set(MediaMetrics.Property.REQUEST, on
+                        ? MediaMetrics.Value.MUTE : MediaMetrics.Value.UNMUTE)
+                .record();
         setMicrophoneMuteNoCallerCheck(userId);
     }
 
@@ -3207,6 +3235,17 @@
                 Log.e(TAG, "Error changing mic mute state to " + muted + " current:"
                         + mMicMuteFromSystemCached);
             }
+
+            new MediaMetrics.Item(MediaMetrics.Name.AUDIO_MIC)
+                    .setUid(userId)
+                    .set(MediaMetrics.Property.EVENT, "setMicrophoneMuteNoCallerCheck")
+                    .set(MediaMetrics.Property.MUTE, mMicMuteFromSystemCached
+                            ? MediaMetrics.Value.ON : MediaMetrics.Value.OFF)
+                    .set(MediaMetrics.Property.REQUEST, muted
+                            ? MediaMetrics.Value.MUTE : MediaMetrics.Value.UNMUTE)
+                    .set(MediaMetrics.Property.STATUS, ret)
+                    .record();
+
             try {
                 // send the intent even if there was a failure to change the actual mute state:
                 // the AudioManager.setMicrophoneMute API doesn't have a return value to
@@ -3941,10 +3980,20 @@
         }
 
         // for logging only
+        final int uid = Binder.getCallingUid();
+        final int pid = Binder.getCallingPid();
         final String eventSource = new StringBuilder("setSpeakerphoneOn(").append(on)
-                .append(") from u/pid:").append(Binder.getCallingUid()).append("/")
-                .append(Binder.getCallingPid()).toString();
+                .append(") from u/pid:").append(uid).append("/")
+                .append(pid).toString();
         final boolean stateChanged = mDeviceBroker.setSpeakerphoneOn(on, eventSource);
+        new MediaMetrics.Item(MediaMetrics.Name.AUDIO_DEVICE
+                + MediaMetrics.SEPARATOR + "setSpeakerphoneOn")
+                .setUid(uid)
+                .setPid(pid)
+                .set(MediaMetrics.Property.STATE, on
+                        ? MediaMetrics.Value.ON : MediaMetrics.Value.OFF)
+                .record();
+
         if (stateChanged) {
             final long ident = Binder.clearCallingIdentity();
             try {
@@ -3975,9 +4024,19 @@
         }
 
         // for logging only
+        final int uid = Binder.getCallingUid();
+        final int pid = Binder.getCallingPid();
         final String eventSource = new StringBuilder("setBluetoothScoOn(").append(on)
-                .append(") from u/pid:").append(Binder.getCallingUid()).append("/")
-                .append(Binder.getCallingPid()).toString();
+                .append(") from u/pid:").append(uid).append("/").append(pid).toString();
+
+        //bt sco
+        new MediaMetrics.Item(MediaMetrics.Name.AUDIO_DEVICE
+                + MediaMetrics.SEPARATOR + "setBluetoothScoOn")
+                .setUid(uid)
+                .setPid(pid)
+                .set(MediaMetrics.Property.STATE, on
+                        ? MediaMetrics.Value.ON : MediaMetrics.Value.OFF)
+                .record();
 
         mDeviceBroker.setBluetoothScoOn(on, eventSource);
     }
@@ -3993,9 +4052,20 @@
     /** @see AudioManager#setBluetoothA2dpOn(boolean) */
     public void setBluetoothA2dpOn(boolean on) {
         // for logging only
+        final int uid = Binder.getCallingUid();
+        final int pid = Binder.getCallingPid();
         final String eventSource = new StringBuilder("setBluetoothA2dpOn(").append(on)
-                .append(") from u/pid:").append(Binder.getCallingUid()).append("/")
-                .append(Binder.getCallingPid()).toString();
+                .append(") from u/pid:").append(uid).append("/")
+                .append(pid).toString();
+
+        new MediaMetrics.Item(MediaMetrics.Name.AUDIO_DEVICE
+                + MediaMetrics.SEPARATOR + "setBluetoothA2dpOn")
+                .setUid(uid)
+                .setPid(pid)
+                .set(MediaMetrics.Property.STATE, on
+                        ? MediaMetrics.Value.ON : MediaMetrics.Value.OFF)
+                .record();
+
         mDeviceBroker.setBluetoothA2dpOn_Async(on, eventSource);
     }
 
@@ -4006,31 +4076,59 @@
 
     /** @see AudioManager#startBluetoothSco() */
     public void startBluetoothSco(IBinder cb, int targetSdkVersion) {
+        final int uid = Binder.getCallingUid();
+        final int pid = Binder.getCallingPid();
         final int scoAudioMode =
                 (targetSdkVersion < Build.VERSION_CODES.JELLY_BEAN_MR2) ?
                         BtHelper.SCO_MODE_VIRTUAL_CALL : BtHelper.SCO_MODE_UNDEFINED;
         final String eventSource = new StringBuilder("startBluetoothSco()")
-                .append(") from u/pid:").append(Binder.getCallingUid()).append("/")
-                .append(Binder.getCallingPid()).toString();
+                .append(") from u/pid:").append(uid).append("/")
+                .append(pid).toString();
+
+        new MediaMetrics.Item(MediaMetrics.Name.AUDIO_BLUETOOTH)
+                .setUid(uid)
+                .setPid(pid)
+                .set(MediaMetrics.Property.EVENT, "startBluetoothSco")
+                .set(MediaMetrics.Property.SCO_AUDIO_MODE,
+                        BtHelper.scoAudioModeToString(scoAudioMode))
+                .record();
         startBluetoothScoInt(cb, scoAudioMode, eventSource);
+
     }
 
     /** @see AudioManager#startBluetoothScoVirtualCall() */
     public void startBluetoothScoVirtualCall(IBinder cb) {
+        final int uid = Binder.getCallingUid();
+        final int pid = Binder.getCallingPid();
         final String eventSource = new StringBuilder("startBluetoothScoVirtualCall()")
-                .append(") from u/pid:").append(Binder.getCallingUid()).append("/")
-                .append(Binder.getCallingPid()).toString();
+                .append(") from u/pid:").append(uid).append("/")
+                .append(pid).toString();
+
+        new MediaMetrics.Item(MediaMetrics.Name.AUDIO_BLUETOOTH)
+                .setUid(uid)
+                .setPid(pid)
+                .set(MediaMetrics.Property.EVENT, "startBluetoothScoVirtualCall")
+                .set(MediaMetrics.Property.SCO_AUDIO_MODE,
+                        BtHelper.scoAudioModeToString(BtHelper.SCO_MODE_VIRTUAL_CALL))
+                .record();
         startBluetoothScoInt(cb, BtHelper.SCO_MODE_VIRTUAL_CALL, eventSource);
     }
 
     void startBluetoothScoInt(IBinder cb, int scoAudioMode, @NonNull String eventSource) {
+        MediaMetrics.Item mmi = new MediaMetrics.Item(MediaMetrics.Name.AUDIO_BLUETOOTH)
+                .set(MediaMetrics.Property.EVENT, "startBluetoothScoInt")
+                .set(MediaMetrics.Property.SCO_AUDIO_MODE,
+                        BtHelper.scoAudioModeToString(scoAudioMode));
+
         if (!checkAudioSettingsPermission("startBluetoothSco()") ||
                 !mSystemReady) {
+            mmi.set(MediaMetrics.Property.EARLY_RETURN, "permission or systemReady").record();
             return;
         }
         synchronized (mDeviceBroker.mSetModeLock) {
             mDeviceBroker.startBluetoothScoForClient_Sync(cb, scoAudioMode, eventSource);
         }
+        mmi.record();
     }
 
     /** @see AudioManager#stopBluetoothSco() */
@@ -4039,12 +4137,21 @@
                 !mSystemReady) {
             return;
         }
+        final int uid = Binder.getCallingUid();
+        final int pid = Binder.getCallingPid();
         final String eventSource =  new StringBuilder("stopBluetoothSco()")
-                .append(") from u/pid:").append(Binder.getCallingUid()).append("/")
-                .append(Binder.getCallingPid()).toString();
+                .append(") from u/pid:").append(uid).append("/")
+                .append(pid).toString();
         synchronized (mDeviceBroker.mSetModeLock) {
             mDeviceBroker.stopBluetoothScoForClient_Sync(cb, eventSource);
         }
+        new MediaMetrics.Item(MediaMetrics.Name.AUDIO_BLUETOOTH)
+                .setUid(uid)
+                .setPid(pid)
+                .set(MediaMetrics.Property.EVENT, "stopBluetoothSco")
+                .set(MediaMetrics.Property.SCO_AUDIO_MODE,
+                        BtHelper.scoAudioModeToString(BtHelper.SCO_MODE_UNDEFINED))
+                .record();
     }
 
 
@@ -4806,6 +4913,14 @@
                 && state != CONNECTION_STATE_DISCONNECTED) {
             throw new IllegalArgumentException("Invalid state " + state);
         }
+        new MediaMetrics.Item(mMetricsId + "setWiredDeviceConnectionState")
+                .set(MediaMetrics.Property.ADDRESS, address)
+                .set(MediaMetrics.Property.CLIENT_NAME, caller)
+                .set(MediaMetrics.Property.DEVICE, AudioSystem.getDeviceName(type))
+                .set(MediaMetrics.Property.NAME, name)
+                .set(MediaMetrics.Property.STATE,
+                        state == CONNECTION_STATE_CONNECTED ? "connected" : "disconnected")
+                .record();
         mDeviceBroker.setWiredDeviceConnectionState(type, state, address, name, caller);
     }
 
@@ -5266,7 +5381,32 @@
         private String mVolumeIndexSettingName;
         private int mObservedDevices;
 
-        private final SparseIntArray mIndexMap = new SparseIntArray(8);
+        private final SparseIntArray mIndexMap = new SparseIntArray(8) {
+            @Override
+            public void put(int key, int value) {
+                super.put(key, value);
+                record("put", key, value);
+            }
+            @Override
+            public void setValueAt(int index, int value) {
+                super.setValueAt(index, value);
+                record("setValueAt", keyAt(index), value);
+            }
+
+            // Record all changes in the VolumeStreamState
+            private void record(String event, int key, int value) {
+                final String device = key == AudioSystem.DEVICE_OUT_DEFAULT ? "default"
+                        : AudioSystem.getOutputDeviceName(key);
+                new MediaMetrics.Item(MediaMetrics.Name.AUDIO_VOLUME + MediaMetrics.SEPARATOR
+                        + AudioSystem.streamToString(mStreamType)
+                        + "." + device)
+                        .set(MediaMetrics.Property.EVENT, event)
+                        .set(MediaMetrics.Property.INDEX, value)
+                        .set(MediaMetrics.Property.MIN_INDEX, mIndexMin)
+                        .set(MediaMetrics.Property.MAX_INDEX, mIndexMax)
+                        .record();
+            }
+        };
         private final Intent mVolumeChanged;
         private final Intent mStreamDevicesChanged;
 
@@ -5949,6 +6089,13 @@
                                 + eventSource);
                         break;
                     }
+                    new MediaMetrics.Item(MediaMetrics.Name.AUDIO_FORCE_USE
+                            + MediaMetrics.SEPARATOR + AudioSystem.forceUseUsageToString(useCase))
+                            .set(MediaMetrics.Property.EVENT, "setForceUse")
+                            .set(MediaMetrics.Property.FORCE_USE_DUE_TO, eventSource)
+                            .set(MediaMetrics.Property.FORCE_USE_MODE,
+                                    AudioSystem.forceUseConfigToString(config))
+                            .record();
                     sForceUseLogger.log(
                             new AudioServiceEvents.ForceUseEvent(useCase, config, eventSource));
                     AudioSystem.setForceUse(useCase, config);
@@ -6450,23 +6597,42 @@
     public int requestAudioFocus(AudioAttributes aa, int durationHint, IBinder cb,
             IAudioFocusDispatcher fd, String clientId, String callingPackageName, int flags,
             IAudioPolicyCallback pcb, int sdk) {
+        final int uid = Binder.getCallingUid();
+        MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId + "focus")
+                .setUid(uid)
+                //.putInt("durationHint", durationHint)
+                .set(MediaMetrics.Property.CALLING_PACKAGE, callingPackageName)
+                .set(MediaMetrics.Property.CLIENT_NAME, clientId)
+                .set(MediaMetrics.Property.EVENT, "requestAudioFocus")
+                .set(MediaMetrics.Property.FLAGS, flags);
+
         // permission checks
         if (aa != null && !isValidAudioAttributesUsage(aa)) {
-            Log.w(TAG, "Request using unsupported usage.");
+            final String reason = "Request using unsupported usage";
+            Log.w(TAG, reason);
+            mmi.set(MediaMetrics.Property.EARLY_RETURN, reason)
+                    .record();
             return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
         }
         if ((flags & AudioManager.AUDIOFOCUS_FLAG_LOCK) == AudioManager.AUDIOFOCUS_FLAG_LOCK) {
             if (AudioSystem.IN_VOICE_COMM_FOCUS_ID.equals(clientId)) {
                 if (PackageManager.PERMISSION_GRANTED != mContext.checkCallingOrSelfPermission(
                             android.Manifest.permission.MODIFY_PHONE_STATE)) {
-                    Log.e(TAG, "Invalid permission to (un)lock audio focus", new Exception());
+                    final String reason = "Invalid permission to (un)lock audio focus";
+                    Log.e(TAG, reason, new Exception());
+                    mmi.set(MediaMetrics.Property.EARLY_RETURN, reason)
+                            .record();
                     return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
                 }
             } else {
                 // only a registered audio policy can be used to lock focus
                 synchronized (mAudioPolicies) {
                     if (!mAudioPolicies.containsKey(pcb.asBinder())) {
-                        Log.e(TAG, "Invalid unregistered AudioPolicy to (un)lock audio focus");
+                        final String reason =
+                                "Invalid unregistered AudioPolicy to (un)lock audio focus";
+                        Log.e(TAG, reason);
+                        mmi.set(MediaMetrics.Property.EARLY_RETURN, reason)
+                                .record();
                         return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
                     }
                 }
@@ -6474,25 +6640,40 @@
         }
 
         if (callingPackageName == null || clientId == null || aa == null) {
-            Log.e(TAG, "Invalid null parameter to request audio focus");
+            final String reason = "Invalid null parameter to request audio focus";
+            Log.e(TAG, reason);
+            mmi.set(MediaMetrics.Property.EARLY_RETURN, reason)
+                    .record();
             return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
         }
-
+        mmi.record();
         return mMediaFocusControl.requestAudioFocus(aa, durationHint, cb, fd,
                 clientId, callingPackageName, flags, sdk,
-                forceFocusDuckingForAccessibility(aa, durationHint, Binder.getCallingUid()));
+                forceFocusDuckingForAccessibility(aa, durationHint, uid));
     }
 
     public int abandonAudioFocus(IAudioFocusDispatcher fd, String clientId, AudioAttributes aa,
             String callingPackageName) {
+        MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId + "focus")
+                .set(MediaMetrics.Property.CALLING_PACKAGE, callingPackageName)
+                .set(MediaMetrics.Property.CLIENT_NAME, clientId)
+                .set(MediaMetrics.Property.EVENT, "abandonAudioFocus");
+
         if (aa != null && !isValidAudioAttributesUsage(aa)) {
             Log.w(TAG, "Request using unsupported usage.");
+            mmi.set(MediaMetrics.Property.EARLY_RETURN, "unsupported usage").record();
+
             return AudioManager.AUDIOFOCUS_REQUEST_FAILED;
         }
+        mmi.record();
         return mMediaFocusControl.abandonAudioFocus(fd, clientId, aa, callingPackageName);
     }
 
     public void unregisterAudioFocusClient(String clientId) {
+        new MediaMetrics.Item(mMetricsId + "focus")
+                .set(MediaMetrics.Property.CLIENT_NAME, clientId)
+                .set(MediaMetrics.Property.EVENT, "unregisterAudioFocusClient")
+                .record();
         mMediaFocusControl.unregisterAudioFocusClient(clientId);
     }
 
@@ -7066,6 +7247,12 @@
         }
     }
 
+    /**
+     * Audio Analytics ids.
+     */
+    private static final String mMetricsId = MediaMetrics.Name.AUDIO_SERVICE
+            + MediaMetrics.SEPARATOR;
+
     private static String safeMediaVolumeStateToString(int state) {
         switch(state) {
             case SAFE_MEDIA_VOLUME_NOT_CONFIGURED: return "SAFE_MEDIA_VOLUME_NOT_CONFIGURED";
diff --git a/services/core/java/com/android/server/audio/AudioServiceEvents.java b/services/core/java/com/android/server/audio/AudioServiceEvents.java
index add620e..5913567 100644
--- a/services/core/java/com/android/server/audio/AudioServiceEvents.java
+++ b/services/core/java/com/android/server/audio/AudioServiceEvents.java
@@ -19,6 +19,7 @@
 import android.media.AudioAttributes;
 import android.media.AudioManager;
 import android.media.AudioSystem;
+import android.media.MediaMetrics;
 
 import com.android.server.audio.AudioDeviceInventory.WiredDeviceConnectionState;
 
@@ -120,6 +121,7 @@
             mCaller = caller;
             mGroupName = null;
             mAudioAttributes = null;
+            logMetricEvent();
         }
 
         /** used for VOL_SET_HEARING_AID_VOL*/
@@ -132,6 +134,7 @@
             mCaller = null;
             mGroupName = null;
             mAudioAttributes = null;
+            logMetricEvent();
         }
 
         /** used for VOL_SET_AVRCP_VOL */
@@ -144,6 +147,7 @@
             mCaller = null;
             mGroupName = null;
             mAudioAttributes = null;
+            logMetricEvent();
         }
 
         /** used for VOL_VOICE_ACTIVITY_HEARING_AID */
@@ -156,6 +160,7 @@
             mCaller = null;
             mGroupName = null;
             mAudioAttributes = null;
+            logMetricEvent();
         }
 
         /** used for VOL_MODE_CHANGE_HEARING_AID */
@@ -168,6 +173,7 @@
             mCaller = null;
             mGroupName = null;
             mAudioAttributes = null;
+            logMetricEvent();
         }
 
         /** used for VOL_SET_GROUP_VOL */
@@ -179,6 +185,102 @@
             mCaller = caller;
             mGroupName = group;
             mAudioAttributes = aa;
+            logMetricEvent();
+        }
+
+
+        /**
+         * Audio Analytics unique Id.
+         */
+        private static final String mMetricsId = MediaMetrics.Name.AUDIO_VOLUME_EVENT;
+
+        /**
+         * Log mediametrics event
+         */
+        private void logMetricEvent() {
+            switch (mOp) {
+                case VOL_ADJUST_SUGG_VOL:
+                case VOL_ADJUST_VOL_UID:
+                case VOL_ADJUST_STREAM_VOL: {
+                    String eventName;
+                    switch (mOp) {
+                        case VOL_ADJUST_SUGG_VOL:
+                            eventName = "adjustSuggestedStreamVolume";
+                            break;
+                        case VOL_ADJUST_STREAM_VOL:
+                            eventName = "adjustStreamVolume";
+                            break;
+                        case VOL_ADJUST_VOL_UID:
+                            eventName = "adjustStreamVolumeForUid";
+                            break;
+                        default:
+                            return; // not possible, just return here
+                    }
+                    new MediaMetrics.Item(mMetricsId)
+                            .set(MediaMetrics.Property.CALLING_PACKAGE, mCaller)
+                            .set(MediaMetrics.Property.DIRECTION, mVal1 > 0 ? "up" : "down")
+                            .set(MediaMetrics.Property.EVENT, eventName)
+                            .set(MediaMetrics.Property.FLAGS, mVal2)
+                            .set(MediaMetrics.Property.STREAM_TYPE,
+                                    AudioSystem.streamToString(mStream))
+                            .record();
+                    return;
+                }
+                case VOL_SET_STREAM_VOL:
+                    new MediaMetrics.Item(mMetricsId)
+                            .set(MediaMetrics.Property.CALLING_PACKAGE, mCaller)
+                            .set(MediaMetrics.Property.EVENT, "setStreamVolume")
+                            .set(MediaMetrics.Property.FLAGS, mVal2)
+                            .set(MediaMetrics.Property.INDEX, mVal1)
+                            .set(MediaMetrics.Property.STREAM_TYPE,
+                                    AudioSystem.streamToString(mStream))
+                            .record();
+                    return;
+                case VOL_SET_HEARING_AID_VOL:
+                    new MediaMetrics.Item(mMetricsId)
+                            .set(MediaMetrics.Property.EVENT, "setHearingAidVolume")
+                            .set(MediaMetrics.Property.GAIN_DB, (double) mVal2)
+                            .set(MediaMetrics.Property.INDEX, mVal1)
+                            .record();
+                    return;
+                case VOL_SET_AVRCP_VOL:
+                    new MediaMetrics.Item(mMetricsId)
+                            .set(MediaMetrics.Property.EVENT, "setAvrcpVolume")
+                            .set(MediaMetrics.Property.INDEX, mVal1)
+                            .record();
+                    return;
+                case VOL_VOICE_ACTIVITY_HEARING_AID:
+                    new MediaMetrics.Item(mMetricsId)
+                            .set(MediaMetrics.Property.EVENT, "voiceActivityHearingAid")
+                            .set(MediaMetrics.Property.INDEX, mVal1)
+                            .set(MediaMetrics.Property.STATE,
+                                    mVal2 == 1 ? "active" : "inactive")
+                            .set(MediaMetrics.Property.STREAM_TYPE,
+                                    AudioSystem.streamToString(mStream))
+                            .record();
+                    return;
+                case VOL_MODE_CHANGE_HEARING_AID:
+                    new MediaMetrics.Item(mMetricsId)
+                            .set(MediaMetrics.Property.EVENT, "modeChangeHearingAid")
+                            .set(MediaMetrics.Property.INDEX, mVal1)
+                            .set(MediaMetrics.Property.MODE, AudioSystem.modeToString(mVal2))
+                            .set(MediaMetrics.Property.STREAM_TYPE,
+                                    AudioSystem.streamToString(mStream))
+                            .record();
+                    return;
+                case VOL_SET_GROUP_VOL:
+                    new MediaMetrics.Item(mMetricsId)
+                            .set(MediaMetrics.Property.ATTRIBUTES, mAudioAttributes.toString())
+                            .set(MediaMetrics.Property.CALLING_PACKAGE, mCaller)
+                            .set(MediaMetrics.Property.EVENT, "setVolumeIndexForAttributes")
+                            .set(MediaMetrics.Property.FLAGS, mVal2)
+                            .set(MediaMetrics.Property.GROUP, mGroupName)
+                            .set(MediaMetrics.Property.INDEX, mVal1)
+                            .record();
+                    return;
+                default:
+                    return;
+            }
         }
 
         @Override
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index 93d1bed..accb90c 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -113,6 +113,24 @@
 
     private static final int BT_HEARING_AID_GAIN_MIN = -128;
 
+    /**
+     * Returns a string representation of the scoAudioMode.
+     */
+    public static String scoAudioModeToString(int scoAudioMode) {
+        switch (scoAudioMode) {
+            case SCO_MODE_UNDEFINED:
+                return "SCO_MODE_UNDEFINED";
+            case SCO_MODE_VIRTUAL_CALL:
+                return "SCO_MODE_VIRTUAL_CALL";
+            case SCO_MODE_RAW:
+                return "SCO_MODE_RAW";
+            case SCO_MODE_VR:
+                return "SCO_MODE_VR";
+            default:
+                return "SCO_MODE_(" + scoAudioMode + ")";
+        }
+    }
+
     //----------------------------------------------------------------------
     /*package*/ static class BluetoothA2dpDeviceInfo {
         private final @NonNull BluetoothDevice mBtDevice;
@@ -965,4 +983,27 @@
                 return AudioSystem.AUDIO_FORMAT_DEFAULT;
         }
     }
+
+    /**
+     * Returns the String equivalent of the btCodecType.
+     *
+     * This uses an "ENCODING_" prefix for consistency with Audio;
+     * we could alternately use the "SOURCE_CODEC_TYPE_" prefix from Bluetooth.
+     */
+    public static String bluetoothCodecToEncodingString(int btCodecType) {
+        switch (btCodecType) {
+            case BluetoothCodecConfig.SOURCE_CODEC_TYPE_SBC:
+                return "ENCODING_SBC";
+            case BluetoothCodecConfig.SOURCE_CODEC_TYPE_AAC:
+                return "ENCODING_AAC";
+            case BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX:
+                return "ENCODING_APTX";
+            case BluetoothCodecConfig.SOURCE_CODEC_TYPE_APTX_HD:
+                return "ENCODING_APTX_HD";
+            case BluetoothCodecConfig.SOURCE_CODEC_TYPE_LDAC:
+                return "ENCODING_LDAC";
+            default:
+                return "ENCODING_BT_CODEC_TYPE(" + btCodecType + ")";
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/audio/MediaFocusControl.java b/services/core/java/com/android/server/audio/MediaFocusControl.java
index bfab991..26281b7 100755
--- a/services/core/java/com/android/server/audio/MediaFocusControl.java
+++ b/services/core/java/com/android/server/audio/MediaFocusControl.java
@@ -25,6 +25,7 @@
 import android.media.AudioManager;
 import android.media.AudioSystem;
 import android.media.IAudioFocusDispatcher;
+import android.media.MediaMetrics;
 import android.media.audiopolicy.IAudioPolicyCallback;
 import android.os.Binder;
 import android.os.Build;
@@ -144,6 +145,8 @@
     private static final AudioEventLogger mEventLogger = new AudioEventLogger(50,
             "focus commands as seen by MediaFocusControl");
 
+    private static final String mMetricsId = MediaMetrics.Name.AUDIO_FOCUS;
+
     /*package*/ void noFocusForSuspendedApp(@NonNull String packageName, int uid) {
         synchronized (mAudioFocusLock) {
             final Iterator<FocusRequester> stackIterator = mFocusStack.iterator();
@@ -818,6 +821,17 @@
     protected int requestAudioFocus(@NonNull AudioAttributes aa, int focusChangeHint, IBinder cb,
             IAudioFocusDispatcher fd, @NonNull String clientId, @NonNull String callingPackageName,
             int flags, int sdk, boolean forceDuck) {
+        new MediaMetrics.Item(mMetricsId)
+                .setUid(Binder.getCallingUid())
+                .set(MediaMetrics.Property.CALLING_PACKAGE, callingPackageName)
+                .set(MediaMetrics.Property.CLIENT_NAME, clientId)
+                .set(MediaMetrics.Property.EVENT, "requestAudioFocus")
+                .set(MediaMetrics.Property.FLAGS, flags)
+                .set(MediaMetrics.Property.FOCUS_CHANGE_HINT,
+                        AudioManager.audioFocusToString(focusChangeHint))
+                //.set(MediaMetrics.Property.SDK, sdk)
+                .record();
+
         mEventLogger.log((new AudioEventLogger.StringEvent(
                 "requestAudioFocus() from uid/pid " + Binder.getCallingUid()
                     + "/" + Binder.getCallingPid()
@@ -982,6 +996,13 @@
      * */
     protected int abandonAudioFocus(IAudioFocusDispatcher fl, String clientId, AudioAttributes aa,
             String callingPackageName) {
+        new MediaMetrics.Item(mMetricsId)
+                .setUid(Binder.getCallingUid())
+                .set(MediaMetrics.Property.CALLING_PACKAGE, callingPackageName)
+                .set(MediaMetrics.Property.CLIENT_NAME, clientId)
+                .set(MediaMetrics.Property.EVENT, "abandonAudioFocus")
+                .record();
+
         // AudioAttributes are currently ignored, to be used for zones / a11y
         mEventLogger.log((new AudioEventLogger.StringEvent(
                 "abandonAudioFocus() from uid/pid " + Binder.getCallingUid()
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index f4d7f9a..36d69c9 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -89,10 +89,8 @@
     private final BrightnessMappingStrategy mBrightnessMapper;
 
     // The minimum and maximum screen brightnesses.
-    private final int mScreenBrightnessRangeMinimum;
-    private final int mScreenBrightnessRangeMaximum;
-    private final float mScreenBrightnessRangeMinimumFloat;
-    private final float mScreenBrightnessRangeMaximumFloat;
+    private final float mScreenBrightnessRangeMinimum;
+    private final float mScreenBrightnessRangeMaximum;
 
     // How much to scale doze brightness by (should be (0, 1.0]).
     private final float mDozeScaleFactor;
@@ -156,7 +154,6 @@
     // The screen brightness threshold at which to brighten or darken the screen.
     private float mScreenBrighteningThreshold;
     private float mScreenDarkeningThreshold;
-
     // The most recent light sample.
     private float mLastObservedLux;
 
@@ -177,8 +174,9 @@
     // We preserve this value even when we stop using the light sensor so
     // that we can quickly revert to the previous auto-brightness level
     // while the light sensor warms up.
-    // Use -1 if there is no current auto-brightness value available.
-    private int mScreenAutoBrightness = PowerManager.BRIGHTNESS_INVALID;
+    // Use PowerManager.BRIGHTNESS_INVALID_FLOAT if there is no current auto-brightness value
+    // available.
+    private float mScreenAutoBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
 
     // The current display policy. This is useful, for example,  for knowing when we're dozing,
     // where the light sensor may not be available.
@@ -188,7 +186,7 @@
     // for the initial state of the sample.
     private boolean mBrightnessAdjustmentSamplePending;
     private float mBrightnessAdjustmentSampleOldLux;
-    private int mBrightnessAdjustmentSampleOldBrightness;
+    private float mBrightnessAdjustmentSampleOldBrightness;
 
     // When the short term model is invalidated, we don't necessarily reset it (i.e. clear the
     // user's adjustment) immediately, but wait for a drastic enough change in the ambient light.
@@ -243,13 +241,8 @@
         mCallbacks = callbacks;
         mSensorManager = sensorManager;
         mBrightnessMapper = mapper;
-        mScreenBrightnessRangeMinimum =
-                BrightnessSynchronizer.brightnessFloatToInt(mContext, brightnessMin);
-        mScreenBrightnessRangeMaximum =
-                com.android.internal.BrightnessSynchronizer.brightnessFloatToInt(
-                        mContext, brightnessMax);
-        mScreenBrightnessRangeMinimumFloat = brightnessMin;
-        mScreenBrightnessRangeMaximumFloat = brightnessMax;
+        mScreenBrightnessRangeMinimum = brightnessMin;
+        mScreenBrightnessRangeMaximum = brightnessMax;
         mLightSensorWarmUpTimeConfig = lightSensorWarmUpTime;
         mDozeScaleFactor = dozeScaleFactor;
         mNormalLightSensorRate = lightSensorRate;
@@ -299,12 +292,12 @@
         return true;
     }
 
-    public int getAutomaticScreenBrightness() {
+    public float getAutomaticScreenBrightness() {
         if (!mAmbientLuxValid) {
-            return -1;
+            return PowerManager.BRIGHTNESS_INVALID_FLOAT;
         }
         if (mDisplayPolicy == DisplayPowerRequest.POLICY_DOZE) {
-            return Math.round(mScreenAutoBrightness * mDozeScaleFactor);
+            return mScreenAutoBrightness * mDozeScaleFactor;
         }
         return mScreenAutoBrightness;
     }
@@ -385,7 +378,7 @@
 
     private boolean setScreenBrightnessByUser(float brightness) {
         if (!mAmbientLuxValid) {
-            // If we don't have a valid ambient lux then we don't have a valid brightness anyways,
+            // If we don't have a valid ambient lux then we don't have a valid brightness anyway,
             // and we can't use this data to add a new control point to the short-term model.
             return false;
         }
@@ -486,7 +479,7 @@
         } else if (mLightSensorEnabled) {
             mLightSensorEnabled = false;
             mAmbientLuxValid = !mResetAmbientLuxAfterWarmUpConfig;
-            mScreenAutoBrightness = PowerManager.BRIGHTNESS_INVALID;
+            mScreenAutoBrightness = PowerManager.BRIGHTNESS_INVALID_FLOAT;
             mRecentLightSamples = 0;
             mAmbientLightRingBuffer.clear();
             mCurrentLightSensorRate = -1;
@@ -735,29 +728,28 @@
 
         float value = mBrightnessMapper.getBrightness(mAmbientLux, mForegroundAppPackageName,
                 mForegroundAppCategory);
-        int newScreenAutoBrightness = BrightnessSynchronizer.brightnessFloatToInt(
-                mContext, clampScreenBrightnessFloat(value));
+        float newScreenAutoBrightness = clampScreenBrightness(value);
         // If screenAutoBrightness is set, we should have screen{Brightening,Darkening}Threshold,
         // in which case we ignore the new screen brightness if it doesn't differ enough from the
         // previous one.
-        if (mScreenAutoBrightness != -1
+        if (!Float.isNaN(mScreenAutoBrightness)
                 && !isManuallySet
                 && newScreenAutoBrightness > mScreenDarkeningThreshold
                 && newScreenAutoBrightness < mScreenBrighteningThreshold) {
             if (mLoggingEnabled) {
-                Slog.d(TAG, "ignoring newScreenAutoBrightness: " + mScreenDarkeningThreshold
-                        + " < " + newScreenAutoBrightness + " < " + mScreenBrighteningThreshold);
+                Slog.d(TAG, "ignoring newScreenAutoBrightness: "
+                        + mScreenDarkeningThreshold + " < " + newScreenAutoBrightness
+                        + " < " + mScreenBrighteningThreshold);
             }
             return;
         }
-
-        if (mScreenAutoBrightness != newScreenAutoBrightness) {
+        if (!BrightnessSynchronizer.floatEquals(mScreenAutoBrightness,
+                newScreenAutoBrightness)) {
             if (mLoggingEnabled) {
-                Slog.d(TAG, "updateAutoBrightness: " +
-                        "mScreenAutoBrightness=" + mScreenAutoBrightness + ", " +
-                        "newScreenAutoBrightness=" + newScreenAutoBrightness);
+                Slog.d(TAG, "updateAutoBrightness: "
+                        + "mScreenAutoBrightness=" + mScreenAutoBrightness + ", "
+                        + "newScreenAutoBrightness=" + newScreenAutoBrightness);
             }
-
             mScreenAutoBrightness = newScreenAutoBrightness;
             mScreenBrighteningThreshold = clampScreenBrightness(
                     mScreenBrightnessThresholds.getBrighteningThreshold(newScreenAutoBrightness));
@@ -770,19 +762,12 @@
         }
     }
 
-    // Clamps values with float range [1.0-255.0]
-    // TODO(brightnessfloat): convert everything that uses this to float system
+    // Clamps values with float range [0.0-1.0]
     private float clampScreenBrightness(float value) {
         return MathUtils.constrain(value,
                 mScreenBrightnessRangeMinimum, mScreenBrightnessRangeMaximum);
     }
 
-    // Clamps values with float range [0.0-1.0]
-    private float clampScreenBrightnessFloat(float value) {
-        return MathUtils.constrain(value,
-                mScreenBrightnessRangeMinimumFloat, mScreenBrightnessRangeMaximumFloat);
-    }
-
     private void prepareBrightnessAdjustmentSample() {
         if (!mBrightnessAdjustmentSamplePending) {
             mBrightnessAdjustmentSamplePending = true;
@@ -806,12 +791,13 @@
     private void collectBrightnessAdjustmentSample() {
         if (mBrightnessAdjustmentSamplePending) {
             mBrightnessAdjustmentSamplePending = false;
-            if (mAmbientLuxValid && mScreenAutoBrightness >= 0) {
+            if (mAmbientLuxValid && (mScreenAutoBrightness >= PowerManager.BRIGHTNESS_MIN
+                    || mScreenAutoBrightness == PowerManager.BRIGHTNESS_OFF_FLOAT)) {
                 if (mLoggingEnabled) {
-                    Slog.d(TAG, "Auto-brightness adjustment changed by user: " +
-                            "lux=" + mAmbientLux + ", " +
-                            "brightness=" + mScreenAutoBrightness + ", " +
-                            "ring=" + mAmbientLightRingBuffer);
+                    Slog.d(TAG, "Auto-brightness adjustment changed by user: "
+                            + "lux=" + mAmbientLux + ", "
+                            + "brightness=" + mScreenAutoBrightness + ", "
+                            + "ring=" + mAmbientLightRingBuffer);
                 }
 
                 EventLog.writeEvent(EventLogTags.AUTO_BRIGHTNESS_ADJ,
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index b4f7cdb..02d499f 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -28,6 +28,8 @@
 import static android.hardware.display.DisplayViewport.VIEWPORT_EXTERNAL;
 import static android.hardware.display.DisplayViewport.VIEWPORT_INTERNAL;
 import static android.hardware.display.DisplayViewport.VIEWPORT_VIRTUAL;
+import static android.view.Surface.ROTATION_270;
+import static android.view.Surface.ROTATION_90;
 
 import android.Manifest;
 import android.annotation.NonNull;
@@ -1363,8 +1365,7 @@
         return null;
     }
 
-    private SurfaceControl.ScreenshotGraphicBuffer screenshotInternal(int displayId,
-            boolean captureSecureLayer) {
+    private SurfaceControl.ScreenshotGraphicBuffer systemScreenshotInternal(int displayId) {
         synchronized (mSyncRoot) {
             final IBinder token = getDisplayToken(displayId);
             if (token == null) {
@@ -1376,15 +1377,42 @@
             }
 
             final DisplayInfo displayInfo = logicalDisplay.getDisplayInfoLocked();
-            if (captureSecureLayer) {
-                return SurfaceControl.screenshotToBufferWithSecureLayersUnsafe(token, new Rect(),
-                        displayInfo.getNaturalWidth(), displayInfo.getNaturalHeight(),
-                        false /* useIdentityTransform */, 0 /* rotation */);
-            } else {
-                return SurfaceControl.screenshotToBuffer(token, new Rect(),
-                        displayInfo.getNaturalWidth(), displayInfo.getNaturalHeight(),
-                        false /* useIdentityTransform */, 0 /* rotation */);
+            return SurfaceControl.screenshotToBufferWithSecureLayersUnsafe(token, new Rect(),
+                    displayInfo.getNaturalWidth(), displayInfo.getNaturalHeight(),
+                    false /* useIdentityTransform */, 0 /* rotation */);
+        }
+    }
+
+    private SurfaceControl.ScreenshotGraphicBuffer userScreenshotInternal(int displayId) {
+        synchronized (mSyncRoot) {
+            final IBinder token = getDisplayToken(displayId);
+            if (token == null) {
+                return null;
             }
+            final LogicalDisplay logicalDisplay = mLogicalDisplays.get(displayId);
+            if (logicalDisplay == null) {
+                return null;
+            }
+
+            final DisplayInfo displayInfo = logicalDisplay.getDisplayInfoLocked();
+            // Takes screenshot based on current device orientation.
+            final Display display = DisplayManagerGlobal.getInstance()
+                    .getRealDisplay(displayId);
+            if (display == null) {
+                return null;
+            }
+            final Point displaySize = new Point();
+            display.getRealSize(displaySize);
+
+            int rotation = displayInfo.rotation;
+            // TODO (b/153382624) : This workaround solution would be removed after
+            // SurfaceFlinger fixes the inconsistency with rotation direction issue.
+            if (rotation == ROTATION_90 || rotation == ROTATION_270) {
+                rotation = (rotation == ROTATION_90) ? ROTATION_270 : ROTATION_90;
+            }
+
+            return SurfaceControl.screenshotToBuffer(token, new Rect(), displaySize.x,
+                    displaySize.y, false /* useIdentityTransform */, rotation /* rotation */);
         }
     }
 
@@ -2502,13 +2530,13 @@
         }
 
         @Override
-        public SurfaceControl.ScreenshotGraphicBuffer screenshot(int displayId) {
-            return screenshotInternal(displayId, true);
+        public SurfaceControl.ScreenshotGraphicBuffer systemScreenshot(int displayId) {
+            return systemScreenshotInternal(displayId);
         }
 
         @Override
-        public SurfaceControl.ScreenshotGraphicBuffer screenshotWithoutSecureLayer(int displayId) {
-            return screenshotInternal(displayId, false);
+        public SurfaceControl.ScreenshotGraphicBuffer userScreenshot(int displayId) {
+            return userScreenshotInternal(displayId);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 48e30bf..bafeb77 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -948,8 +948,7 @@
         if (Float.isNaN(brightnessState)) {
             float newAutoBrightnessAdjustment = autoBrightnessAdjustment;
             if (autoBrightnessEnabled) {
-                brightnessState = BrightnessSynchronizer.brightnessIntToFloat(
-                mContext, mAutomaticBrightnessController.getAutomaticScreenBrightness());
+                brightnessState = mAutomaticBrightnessController.getAutomaticScreenBrightness();
                 newAutoBrightnessAdjustment =
                         mAutomaticBrightnessController.getAutomaticScreenBrightnessAdjustment();
             }
diff --git a/services/core/java/com/android/server/display/HysteresisLevels.java b/services/core/java/com/android/server/display/HysteresisLevels.java
index f0a505d..2b56569 100644
--- a/services/core/java/com/android/server/display/HysteresisLevels.java
+++ b/services/core/java/com/android/server/display/HysteresisLevels.java
@@ -64,8 +64,8 @@
      * Return the brightening hysteresis threshold for the given value level.
      */
     public float getBrighteningThreshold(float value) {
-        float brightConstant = getReferenceLevel(value, mBrighteningThresholds);
-        float brightThreshold = value * (1.0f + brightConstant);
+        final float brightConstant = getReferenceLevel(value, mBrighteningThresholds);
+        final float brightThreshold = value * (1.0f + brightConstant);
         if (DEBUG) {
             Slog.d(TAG, "bright hysteresis constant=" + brightConstant + ", threshold="
                     + brightThreshold + ", value=" + value);
@@ -77,8 +77,8 @@
      * Return the darkening hysteresis threshold for the given value level.
      */
     public float getDarkeningThreshold(float value) {
-        float darkConstant = getReferenceLevel(value, mDarkeningThresholds);
-        float darkThreshold = value * (1.0f - darkConstant);
+        final float darkConstant = getReferenceLevel(value, mDarkeningThresholds);
+        final float darkThreshold = value * (1.0f - darkConstant);
         if (DEBUG) {
             Slog.d(TAG, "dark hysteresis constant=: " + darkConstant + ", threshold="
                     + darkThreshold + ", value=" + value);
diff --git a/services/core/java/com/android/server/emergency/EmergencyAffordanceService.java b/services/core/java/com/android/server/emergency/EmergencyAffordanceService.java
index 1cf27ff..cc7915c 100644
--- a/services/core/java/com/android/server/emergency/EmergencyAffordanceService.java
+++ b/services/core/java/com/android/server/emergency/EmergencyAffordanceService.java
@@ -20,90 +20,80 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.os.Binder;
+import android.os.Build;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Message;
 import android.provider.Settings;
-import android.telephony.CellInfo;
-import android.telephony.CellInfoGsm;
-import android.telephony.CellInfoLte;
-import android.telephony.CellInfoWcdma;
-import android.telephony.CellLocation;
-import android.telephony.PhoneStateListener;
 import android.telephony.SubscriptionInfo;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.Slog;
 
+import com.android.internal.util.DumpUtils;
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.SystemService;
 
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 
 /**
- * A service that listens to connectivity and SIM card changes and determines if the emergency mode
- * should be enabled
+ * A service that listens to connectivity and SIM card changes and determines if the emergency
+ * affordance should be enabled.
  */
 public class EmergencyAffordanceService extends SystemService {
 
     private static final String TAG = "EmergencyAffordanceService";
+    private static final boolean DBG = false;
 
-    private static final int NUM_SCANS_UNTIL_ABORT = 4;
+    private static final String SERVICE_NAME = "emergency_affordance";
 
     private static final int INITIALIZE_STATE = 1;
-    private static final int CELL_INFO_STATE_CHANGED = 2;
-    private static final int SUBSCRIPTION_CHANGED = 3;
-
     /**
-     * Global setting, whether the last scan of the sim cards reveal that a sim was inserted that
-     * requires the emergency affordance. The value is a boolean (1 or 0).
-     * @hide
+     * @param arg1 slot Index
+     * @param arg2 0
+     * @param obj ISO country code
      */
-    private static final String EMERGENCY_SIM_INSERTED_SETTING = "emergency_sim_inserted_before";
+    private static final int NETWORK_COUNTRY_CHANGED = 2;
+    private static final int SUBSCRIPTION_CHANGED = 3;
+    private static final int UPDATE_AIRPLANE_MODE_STATUS = 4;
+
+    // Global Settings to override emergency affordance country ISO for debugging.
+    // Available only on debug build. The value is a country ISO string in lower case (eg. "us").
+    private static final String EMERGENCY_AFFORDANCE_OVERRIDE_ISO =
+            "emergency_affordance_override_iso";
 
     private final Context mContext;
-    private final ArrayList<Integer> mEmergencyCallMccNumbers;
-
-    private final Object mLock = new Object();
-
-    private TelephonyManager mTelephonyManager;
+    // Country ISOs that require affordance
+    private final ArrayList<String> mEmergencyCallCountryIsos;
     private SubscriptionManager mSubscriptionManager;
-    private boolean mEmergencyAffordanceNeeded;
+    private TelephonyManager mTelephonyManager;
     private MyHandler mHandler;
-    private int mScansCompleted;
-    private PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
-        @Override
-        public void onCellInfoChanged(List<CellInfo> cellInfo) {
-            if (!isEmergencyAffordanceNeeded()) {
-                requestCellScan();
-            }
-        }
-
-        @Override
-        public void onCellLocationChanged(CellLocation location) {
-            if (!isEmergencyAffordanceNeeded()) {
-                requestCellScan();
-            }
-        }
-    };
-    private BroadcastReceiver mAirplaneModeReceiver = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (Settings.Global.getInt(context.getContentResolver(),
-                    Settings.Global.AIRPLANE_MODE_ON, 0) == 0) {
-                startScanning();
-                requestCellScan();
-            }
-        }
-    };
-    private boolean mSimNeedsEmergencyAffordance;
-    private boolean mNetworkNeedsEmergencyAffordance;
+    private boolean mAnySimNeedsEmergencyAffordance;
+    private boolean mAnyNetworkNeedsEmergencyAffordance;
+    private boolean mEmergencyAffordanceNeeded;
+    private boolean mAirplaneModeEnabled;
     private boolean mVoiceCapable;
 
-    private void requestCellScan() {
-        mHandler.obtainMessage(CELL_INFO_STATE_CHANGED).sendToTarget();
-    }
+    private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (TelephonyManager.ACTION_NETWORK_COUNTRY_CHANGED.equals(intent.getAction())) {
+                String countryCode = intent.getStringExtra(TelephonyManager.EXTRA_NETWORK_COUNTRY);
+                int slotId = intent.getIntExtra(SubscriptionManager.EXTRA_SLOT_INDEX,
+                        SubscriptionManager.INVALID_SIM_SLOT_INDEX);
+                mHandler.obtainMessage(
+                        NETWORK_COUNTRY_CHANGED, slotId, 0, countryCode).sendToTarget();
+            } else if (Intent.ACTION_AIRPLANE_MODE_CHANGED.equals(intent.getAction())) {
+                mHandler.obtainMessage(UPDATE_AIRPLANE_MODE_STATUS).sendToTarget();
+            }
+        }
+    };
 
     private SubscriptionManager.OnSubscriptionsChangedListener mSubscriptionChangedListener
             = new SubscriptionManager.OnSubscriptionsChangedListener() {
@@ -116,207 +106,200 @@
     public EmergencyAffordanceService(Context context) {
         super(context);
         mContext = context;
-        int[] numbers = context.getResources().getIntArray(
-                com.android.internal.R.array.config_emergency_mcc_codes);
-        mEmergencyCallMccNumbers = new ArrayList<>(numbers.length);
-        for (int i = 0; i < numbers.length; i++) {
-            mEmergencyCallMccNumbers.add(numbers[i]);
+        String[] isos = context.getResources().getStringArray(
+                com.android.internal.R.array.config_emergency_iso_country_codes);
+        mEmergencyCallCountryIsos = new ArrayList<>(isos.length);
+        for (String iso : isos) {
+            mEmergencyCallCountryIsos.add(iso);
         }
-    }
 
-    private void updateEmergencyAffordanceNeeded() {
-        synchronized (mLock) {
-            mEmergencyAffordanceNeeded = mVoiceCapable && (mSimNeedsEmergencyAffordance ||
-                    mNetworkNeedsEmergencyAffordance);
-            Settings.Global.putInt(mContext.getContentResolver(),
-                    Settings.Global.EMERGENCY_AFFORDANCE_NEEDED,
-                    mEmergencyAffordanceNeeded ? 1 : 0);
-            if (mEmergencyAffordanceNeeded) {
-                stopScanning();
+        if (Build.IS_DEBUGGABLE) {
+            String overrideIso = Settings.Global.getString(
+                    mContext.getContentResolver(), EMERGENCY_AFFORDANCE_OVERRIDE_ISO);
+            if (!TextUtils.isEmpty(overrideIso)) {
+                if (DBG) Slog.d(TAG, "Override ISO to " + overrideIso);
+                mEmergencyCallCountryIsos.clear();
+                mEmergencyCallCountryIsos.add(overrideIso);
             }
         }
     }
 
-    private void stopScanning() {
-        synchronized (mLock) {
-            mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
-            mScansCompleted = 0;
-        }
-    }
-
-    private boolean isEmergencyAffordanceNeeded() {
-        synchronized (mLock) {
-            return mEmergencyAffordanceNeeded;
-        }
-    }
-
     @Override
     public void onStart() {
+        if (DBG) Slog.i(TAG, "onStart");
+        publishBinderService(SERVICE_NAME, new BinderService());
     }
 
     @Override
     public void onBootPhase(int phase) {
         if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
-            mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
-            mVoiceCapable = mTelephonyManager.isVoiceCapable();
-            if (!mVoiceCapable) {
-                updateEmergencyAffordanceNeeded();
-                return;
-            }
-            mSubscriptionManager = SubscriptionManager.from(mContext);
-            HandlerThread thread = new HandlerThread(TAG);
-            thread.start();
-            mHandler = new MyHandler(thread.getLooper());
-            mHandler.obtainMessage(INITIALIZE_STATE).sendToTarget();
-            startScanning();
-            IntentFilter filter = new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED);
-            mContext.registerReceiver(mAirplaneModeReceiver, filter);
-            mSubscriptionManager.addOnSubscriptionsChangedListener(mSubscriptionChangedListener);
+            if (DBG) Slog.i(TAG, "onBootPhase");
+            handleThirdPartyBootPhase();
         }
     }
 
-    private void startScanning() {
-        mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_CELL_INFO
-                | PhoneStateListener.LISTEN_CELL_LOCATION);
-    }
-
     /** Handler to do the heavier work on */
     private class MyHandler extends Handler {
-
         public MyHandler(Looper l) {
             super(l);
         }
 
         @Override
         public void handleMessage(Message msg) {
+            if (DBG) Slog.d(TAG, "handleMessage: " + msg.what);
             switch (msg.what) {
                 case INITIALIZE_STATE:
                     handleInitializeState();
                     break;
-                case CELL_INFO_STATE_CHANGED:
-                    handleUpdateCellInfo();
+                case NETWORK_COUNTRY_CHANGED:
+                    final String countryIso = (String) msg.obj;
+                    final int slotId = msg.arg1;
+                    handleNetworkCountryChanged(countryIso, slotId);
                     break;
                 case SUBSCRIPTION_CHANGED:
                     handleUpdateSimSubscriptionInfo();
                     break;
+                case UPDATE_AIRPLANE_MODE_STATUS:
+                    handleUpdateAirplaneModeStatus();
+                    break;
+                default:
+                    Slog.e(TAG, "Unexpected message received: " + msg.what);
             }
         }
     }
 
     private void handleInitializeState() {
-        if (handleUpdateSimSubscriptionInfo()) {
-            return;
-        }
-        if (handleUpdateCellInfo()) {
-            return;
-        }
+        if (DBG) Slog.d(TAG, "handleInitializeState");
+        handleUpdateAirplaneModeStatus();
+        handleUpdateSimSubscriptionInfo();
+        updateNetworkCountry();
         updateEmergencyAffordanceNeeded();
     }
 
-    private boolean handleUpdateSimSubscriptionInfo() {
-        boolean neededBefore = simNeededAffordanceBefore();
-        boolean neededNow = neededBefore;
+    private void handleThirdPartyBootPhase() {
+        if (DBG) Slog.d(TAG, "handleThirdPartyBootPhase");
+        mTelephonyManager = mContext.getSystemService(TelephonyManager.class);
+        mVoiceCapable = mTelephonyManager.isVoiceCapable();
+        if (!mVoiceCapable) {
+            updateEmergencyAffordanceNeeded();
+            return;
+        }
+
+        HandlerThread thread = new HandlerThread(TAG);
+        thread.start();
+        mHandler = new MyHandler(thread.getLooper());
+
+        mSubscriptionManager = SubscriptionManager.from(mContext);
+        mSubscriptionManager.addOnSubscriptionsChangedListener(mSubscriptionChangedListener);
+
+        IntentFilter filter = new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+        filter.addAction(TelephonyManager.ACTION_NETWORK_COUNTRY_CHANGED);
+        mContext.registerReceiver(mBroadcastReceiver, filter);
+
+        mHandler.obtainMessage(INITIALIZE_STATE).sendToTarget();
+    }
+
+    private void handleUpdateAirplaneModeStatus() {
+        mAirplaneModeEnabled = Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.AIRPLANE_MODE_ON, 0) == 1;
+        if (DBG) Slog.d(TAG, "APM status updated to " + mAirplaneModeEnabled);
+    }
+
+    private void handleUpdateSimSubscriptionInfo() {
         List<SubscriptionInfo> activeSubscriptionInfoList =
                 mSubscriptionManager.getActiveSubscriptionInfoList();
+        if (DBG) Slog.d(TAG, "handleUpdateSimSubscriptionInfo: " + activeSubscriptionInfoList);
         if (activeSubscriptionInfoList == null) {
-            setSimNeedsEmergencyAffordance(neededNow);
-            return neededNow;
+            return;
         }
+
+        boolean needsAffordance = false;
         for (SubscriptionInfo info : activeSubscriptionInfoList) {
-            int mcc = info.getMcc();
-            if (mccRequiresEmergencyAffordance(mcc)) {
-                neededNow = true;
+            if (isoRequiresEmergencyAffordance(info.getCountryIso())) {
+                needsAffordance = true;
                 break;
-            } else if (mcc != 0 && mcc != Integer.MAX_VALUE){
-                // a Sim with a different mcc code was found
-                neededNow = false;
-            }
-            String simOperator = mTelephonyManager
-                    .createForSubscriptionId(info.getSubscriptionId()).getSimOperator();
-            mcc = 0;
-            if (simOperator != null && simOperator.length() >= 3) {
-                mcc = Integer.parseInt(simOperator.substring(0, 3));
-            }
-            if (mcc != 0) {
-                if (mccRequiresEmergencyAffordance(mcc)) {
-                    neededNow = true;
-                    break;
-                } else {
-                    // a Sim with a different mcc code was found
-                    neededNow = false;
-                }
             }
         }
-        setSimNeedsEmergencyAffordance(neededNow);
-        return neededNow;
+
+        mAnySimNeedsEmergencyAffordance = needsAffordance;
+        updateEmergencyAffordanceNeeded();
     }
 
-    private void setSimNeedsEmergencyAffordance(boolean simNeedsEmergencyAffordance) {
-        if (simNeededAffordanceBefore() != simNeedsEmergencyAffordance) {
+    private void handleNetworkCountryChanged(String countryIso, int slotId) {
+        if (DBG) {
+            Slog.d(TAG, "handleNetworkCountryChanged: countryIso=" + countryIso
+                    + ", slotId=" + slotId);
+        }
+
+        if (TextUtils.isEmpty(countryIso) && mAirplaneModeEnabled) {
+            Slog.w(TAG, "Ignore empty countryIso report when APM is on.");
+            return;
+        }
+
+        updateNetworkCountry();
+
+        updateEmergencyAffordanceNeeded();
+    }
+
+    private void updateNetworkCountry() {
+        boolean needsAffordance = false;
+
+        final int activeModems = mTelephonyManager.getActiveModemCount();
+        for (int i = 0; i < activeModems; i++) {
+            String countryIso = mTelephonyManager.getNetworkCountryIso(i);
+            if (DBG) Slog.d(TAG, "UpdateNetworkCountry: slotId=" + i + " countryIso=" + countryIso);
+            if (isoRequiresEmergencyAffordance(countryIso)) {
+                needsAffordance = true;
+                break;
+            }
+        }
+
+        mAnyNetworkNeedsEmergencyAffordance = needsAffordance;
+
+        updateEmergencyAffordanceNeeded();
+    }
+
+    private boolean isoRequiresEmergencyAffordance(String iso) {
+        return mEmergencyCallCountryIsos.contains(iso);
+    }
+
+    private void updateEmergencyAffordanceNeeded() {
+        if (DBG) {
+            Slog.d(TAG, "updateEmergencyAffordanceNeeded: mEmergencyAffordanceNeeded="
+                    + mEmergencyAffordanceNeeded + ", mVoiceCapable=" + mVoiceCapable
+                    + ", mAnySimNeedsEmergencyAffordance=" + mAnySimNeedsEmergencyAffordance
+                    + ", mAnyNetworkNeedsEmergencyAffordance="
+                    + mAnyNetworkNeedsEmergencyAffordance);
+        }
+        boolean lastAffordanceNeeded = mEmergencyAffordanceNeeded;
+
+        mEmergencyAffordanceNeeded = mVoiceCapable
+                && (mAnySimNeedsEmergencyAffordance || mAnyNetworkNeedsEmergencyAffordance);
+
+        if (lastAffordanceNeeded != mEmergencyAffordanceNeeded) {
             Settings.Global.putInt(mContext.getContentResolver(),
-                    EMERGENCY_SIM_INSERTED_SETTING,
-                    simNeedsEmergencyAffordance ? 1 : 0);
-        }
-        if (simNeedsEmergencyAffordance != mSimNeedsEmergencyAffordance) {
-            mSimNeedsEmergencyAffordance = simNeedsEmergencyAffordance;
-            updateEmergencyAffordanceNeeded();
+                    Settings.Global.EMERGENCY_AFFORDANCE_NEEDED,
+                    mEmergencyAffordanceNeeded ? 1 : 0);
         }
     }
 
-    private boolean simNeededAffordanceBefore() {
-        return Settings.Global.getInt(mContext.getContentResolver(),
-                EMERGENCY_SIM_INSERTED_SETTING, 0) != 0;
+    private void dumpInternal(IndentingPrintWriter ipw) {
+        ipw.println("EmergencyAffordanceService (dumpsys emergency_affordance) state:\n");
+        ipw.println("mEmergencyAffordanceNeeded=" + mEmergencyAffordanceNeeded);
+        ipw.println("mVoiceCapable=" + mVoiceCapable);
+        ipw.println("mAnySimNeedsEmergencyAffordance=" + mAnySimNeedsEmergencyAffordance);
+        ipw.println("mAnyNetworkNeedsEmergencyAffordance=" + mAnyNetworkNeedsEmergencyAffordance);
+        ipw.println("mEmergencyCallCountryIsos=" + String.join(",", mEmergencyCallCountryIsos));
     }
 
-    private boolean handleUpdateCellInfo() {
-        List<CellInfo> cellInfos = mTelephonyManager.getAllCellInfo();
-        if (cellInfos == null) {
-            return false;
-        }
-        boolean stopScanningAfterScan = false;
-        for (CellInfo cellInfo : cellInfos) {
-            int mcc = 0;
-            if (cellInfo instanceof CellInfoGsm) {
-                mcc = ((CellInfoGsm) cellInfo).getCellIdentity().getMcc();
-            } else if (cellInfo instanceof CellInfoLte) {
-                mcc = ((CellInfoLte) cellInfo).getCellIdentity().getMcc();
-            } else if (cellInfo instanceof CellInfoWcdma) {
-                mcc = ((CellInfoWcdma) cellInfo).getCellIdentity().getMcc();
+    private final class BinderService extends Binder {
+        @Override
+        protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+            if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) {
+                return;
             }
-            if (mccRequiresEmergencyAffordance(mcc)) {
-                setNetworkNeedsEmergencyAffordance(true);
-                return true;
-            } else if (mcc != 0 && mcc != Integer.MAX_VALUE) {
-                // we found an mcc that isn't in the list, abort
-                stopScanningAfterScan = true;
-            }
-        }
-        if (stopScanningAfterScan) {
-            stopScanning();
-        } else {
-            onCellScanFinishedUnsuccessful();
-        }
-        setNetworkNeedsEmergencyAffordance(false);
-        return false;
-    }
 
-    private void setNetworkNeedsEmergencyAffordance(boolean needsAffordance) {
-        synchronized (mLock) {
-            mNetworkNeedsEmergencyAffordance = needsAffordance;
-            updateEmergencyAffordanceNeeded();
+            dumpInternal(new IndentingPrintWriter(pw, "  "));
         }
     }
-
-    private void onCellScanFinishedUnsuccessful() {
-        synchronized (mLock) {
-            mScansCompleted++;
-            if (mScansCompleted >= NUM_SCANS_UNTIL_ABORT) {
-                stopScanning();
-            }
-        }
-    }
-
-    private boolean mccRequiresEmergencyAffordance(int mcc) {
-        return mEmergencyCallMccNumbers.contains(mcc);
-    }
 }
diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java
index d432fc8..3e6d6f5 100644
--- a/services/core/java/com/android/server/notification/PreferencesHelper.java
+++ b/services/core/java/com/android/server/notification/PreferencesHelper.java
@@ -819,7 +819,9 @@
             }
             if (fromTargetApp) {
                 channel.setLockscreenVisibility(r.visibility);
-                channel.setAllowBubbles(existing != null && existing.canBubble());
+                channel.setAllowBubbles(existing != null
+                        ? existing.getAllowBubbles()
+                        : NotificationChannel.DEFAULT_ALLOW_BUBBLE);
             }
             clearLockedFieldsLocked(channel);
             channel.setImportanceLockedByOEM(r.oemLockedImportance);
@@ -1704,7 +1706,7 @@
         if (original.canShowBadge() != update.canShowBadge()) {
             update.lockFields(NotificationChannel.USER_LOCKED_SHOW_BADGE);
         }
-        if (original.canBubble() != update.canBubble()) {
+        if (original.getAllowBubbles() != update.getAllowBubbles()) {
             update.lockFields(NotificationChannel.USER_LOCKED_ALLOW_BUBBLE);
         }
     }
diff --git a/services/core/java/com/android/server/notification/ShortcutHelper.java b/services/core/java/com/android/server/notification/ShortcutHelper.java
index 1d48438..96da649 100644
--- a/services/core/java/com/android/server/notification/ShortcutHelper.java
+++ b/services/core/java/com/android/server/notification/ShortcutHelper.java
@@ -21,7 +21,6 @@
 import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED;
 
 import android.annotation.NonNull;
-import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.LauncherApps;
 import android.content.pm.ShortcutInfo;
@@ -41,9 +40,18 @@
 /**
  * Helper for querying shortcuts.
  */
-class ShortcutHelper {
+public class ShortcutHelper {
     private static final String TAG = "ShortcutHelper";
 
+    private static final IntentFilter SHARING_FILTER = new IntentFilter();
+    static {
+        try {
+            SHARING_FILTER.addDataType("*/*");
+        } catch (IntentFilter.MalformedMimeTypeException e) {
+            Slog.e(TAG, "Bad mime type", e);
+        }
+    }
+
     /**
      * Listener to call when a shortcut we're tracking has been removed.
      */
@@ -54,7 +62,6 @@
     private LauncherApps mLauncherAppsService;
     private ShortcutListener mShortcutListener;
     private ShortcutServiceInternal mShortcutServiceInternal;
-    private IntentFilter mSharingFilter;
 
     // Key: packageName Value: <shortcutId, notifId>
     private HashMap<String, HashMap<String, String>> mActiveShortcutBubbles = new HashMap<>();
@@ -122,12 +129,6 @@
             ShortcutServiceInternal shortcutServiceInternal) {
         mLauncherAppsService = launcherApps;
         mShortcutListener = listener;
-        mSharingFilter = new IntentFilter();
-        try {
-            mSharingFilter.addDataType("*/*");
-        } catch (IntentFilter.MalformedMimeTypeException e) {
-            Slog.e(TAG, "Bad mime type", e);
-        }
         mShortcutServiceInternal = shortcutServiceInternal;
     }
 
@@ -142,7 +143,21 @@
     }
 
     /**
-     * Only returns shortcut info if it's found and if it's {@link ShortcutInfo#isLongLived()}.
+     * Returns whether the given shortcut info is a conversation shortcut.
+     */
+    public static boolean isConversationShortcut(
+            ShortcutInfo shortcutInfo, ShortcutServiceInternal mShortcutServiceInternal,
+            int callingUserId) {
+        if (shortcutInfo == null || !shortcutInfo.isLongLived() || !shortcutInfo.isEnabled()) {
+            return false;
+        }
+        return mShortcutServiceInternal.isSharingShortcut(callingUserId, "android",
+                shortcutInfo.getPackage(), shortcutInfo.getId(), shortcutInfo.getUserId(),
+                SHARING_FILTER);
+    }
+
+    /**
+     * Only returns shortcut info if it's found and if it's a conversation shortcut.
      */
     ShortcutInfo getValidShortcutInfo(String shortcutId, String packageName, UserHandle user) {
         if (mLauncherAppsService == null) {
@@ -161,11 +176,7 @@
             ShortcutInfo info = shortcuts != null && shortcuts.size() > 0
                     ? shortcuts.get(0)
                     : null;
-            if (info == null || !info.isLongLived() || !info.isEnabled()) {
-                return null;
-            }
-            if (mShortcutServiceInternal.isSharingShortcut(user.getIdentifier(),
-                    "android", packageName, shortcutId, user.getIdentifier(), mSharingFilter)) {
+            if (isConversationShortcut(info, mShortcutServiceInternal, user.getIdentifier())) {
                 return info;
             }
             return null;
diff --git a/services/core/java/com/android/server/pm/AppsFilter.java b/services/core/java/com/android/server/pm/AppsFilter.java
index 7c47cf0..d36d038 100644
--- a/services/core/java/com/android/server/pm/AppsFilter.java
+++ b/services/core/java/com/android/server/pm/AppsFilter.java
@@ -612,7 +612,7 @@
                     }
                     final int insert = ~loc;
                     System.arraycopy(appIds, insert, buffer, 0, whitelistSize - insert);
-                    appIds[insert] = existingUid;
+                    appIds[insert] = existingAppId;
                     System.arraycopy(buffer, 0, appIds, insert + 1, whitelistSize - insert);
                     whitelistSize++;
                 }
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index c716fce..4561d2e 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -104,6 +104,7 @@
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.FastXmlSerializer;
 import com.android.internal.util.FrameworkStatsLog;
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
 import com.android.internal.util.XmlUtils;
 import com.android.internal.widget.LockPatternUtils;
@@ -4516,6 +4517,8 @@
             switch(cmd) {
                 case "list":
                     return runList(pw, shell);
+                case "list-missing-system-packages":
+                    return runListMissingSystemPackages(pw, shell);
                 default:
                     return shell.handleDefaultCommands(cmd);
             }
@@ -4582,6 +4585,30 @@
         }
     }
 
+    private int runListMissingSystemPackages(PrintWriter pw, Shell shell) {
+        boolean verbose = false;
+        boolean force = false;
+        String opt;
+        while ((opt = shell.getNextOption()) != null) {
+            switch (opt) {
+                case "-v":
+                    verbose = true;
+                    break;
+                case "--force":
+                    force = true;
+                    break;
+                default:
+                    pw.println("Invalid option: " + opt);
+                    return -1;
+            }
+        }
+
+        try (IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ")) {
+            mSystemPackageInstaller.dumpMissingSystemPackages(ipw, force, verbose);
+        }
+        return 0;
+    }
+
     @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (!DumpUtils.checkDumpPermission(mContext, LOG_TAG, pw)) return;
@@ -5154,6 +5181,9 @@
             pw.println("");
             pw.println("  list [-v] [-all]");
             pw.println("    Prints all users on the system.");
+            pw.println("  list-missing-system-packages [-v] [--force]");
+            pw.println("    Prints all system packages that were not explicitly configured to be "
+                    + "installed.");
         }
     }
 
diff --git a/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
index 85c2306..cd1087f 100644
--- a/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
+++ b/services/core/java/com/android/server/pm/UserSystemPackageInstaller.java
@@ -22,15 +22,16 @@
 import android.annotation.UserIdInt;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
-import android.content.pm.PackageParser;
 import android.content.res.Resources;
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.Pair;
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.LocalServices;
 import com.android.server.SystemConfig;
 import com.android.server.pm.parsing.pkg.AndroidPackage;
@@ -38,7 +39,9 @@
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
@@ -263,37 +266,85 @@
         if (!isLogMode(mode) && !isEnforceMode(mode)) {
             return;
         }
-        Slog.v(TAG,  "Checking that all system packages are whitelisted.");
-        final Set<String> allWhitelistedPackages = getWhitelistedSystemPackages();
-        PackageManagerInternal pmInt = LocalServices.getService(PackageManagerInternal.class);
-
-        // Check whether all whitelisted packages are indeed on the system.
-        for (String pkgName : allWhitelistedPackages) {
-            AndroidPackage pkg = pmInt.getPackage(pkgName);
-            if (pkg == null) {
-                Slog.w(TAG, pkgName + " is whitelisted but not present.");
-            } else if (!pkg.isSystem()) {
-                Slog.w(TAG, pkgName + " is whitelisted and present but not a system package.");
-            }
-        }
-
-        // Check whether all system packages are indeed whitelisted.
-        if (isImplicitWhitelistMode(mode) && !isLogMode(mode)) {
+        final List<Pair<Boolean, String>> warnings = checkSystemPackagesWhitelistWarnings(mode);
+        final int size = warnings.size();
+        if (size == 0) {
+            Slog.v(TAG, "checkWhitelistedSystemPackages(mode=" + mode + "): no warnings");
             return;
         }
-        final boolean doWtf = isEnforceMode(mode);
-        pmInt.forEachPackage(pkg -> {
-            if (pkg.isSystem() && !allWhitelistedPackages.contains(pkg.getManifestPackageName())) {
-                final String msg = "System package " + pkg.getManifestPackageName()
-                        + " is not whitelisted using 'install-in-user-type' in SystemConfig "
-                        + "for any user types!";
+
+        if (isImplicitWhitelistMode(mode) && !isLogMode(mode)) {
+            // Only shows whether all whitelisted packages are indeed on the system.
+            for (int i = 0; i < size; i++) {
+                final Pair<Boolean, String> pair = warnings.get(i);
+                final boolean isSevere = pair.first;
+                if (!isSevere) {
+                    final String msg = pair.second;
+                    Slog.w(TAG, msg);
+                }
+            }
+            return;
+        }
+
+        Slog.v(TAG, "checkWhitelistedSystemPackages(mode=" + mode + "): " + size + " warnings");
+        boolean doWtf = !isImplicitWhitelistMode(mode);
+        for (int i = 0; i < size; i++) {
+            final Pair<Boolean, String> pair = warnings.get(i);
+            final boolean isSevere = pair.first;
+            final String msg = pair.second;
+            if (isSevere) {
                 if (doWtf) {
                     Slog.wtf(TAG, msg);
                 } else {
                     Slog.e(TAG, msg);
                 }
+            } else {
+                Slog.w(TAG, msg);
+            }
+        }
+    }
+
+    // TODO: method below was created to refactor the one-time logging logic so it can be used on
+    // dump / cmd as well. It could to be further refactored (for example, creating a new
+    // structure for the warnings so it doesn't need a Pair).
+    /**
+     * Gets warnings for system user whitelisting.
+     *
+     * @return list of warnings, where {@code Pair.first} is the severity ({@code true} for WTF,
+     * {@code false} for WARN) and {@code Pair.second} the message.
+     */
+    @NonNull
+    private List<Pair<Boolean, String>> checkSystemPackagesWhitelistWarnings(
+            @PackageWhitelistMode int mode) {
+        final Set<String> allWhitelistedPackages = getWhitelistedSystemPackages();
+        final List<Pair<Boolean, String>> warnings = new ArrayList<>();
+        final PackageManagerInternal pmInt = LocalServices.getService(PackageManagerInternal.class);
+
+        // Check whether all whitelisted packages are indeed on the system.
+        final String notPresentFmt = "%s is whitelisted but not present.";
+        final String notSystemFmt = "%s is whitelisted and present but not a system package.";
+        for (String pkgName : allWhitelistedPackages) {
+            final AndroidPackage pkg = pmInt.getPackage(pkgName);
+            if (pkg == null) {
+                warnings.add(new Pair<>(false, String.format(notPresentFmt, pkgName)));
+            } else if (!pkg.isSystem()) {
+                warnings.add(new Pair<>(false, String.format(notSystemFmt, pkgName)));
+            }
+        }
+
+        // Check whether all system packages are indeed whitelisted.
+        final String logMessageFmt = "System package %s is not whitelisted using "
+                + "'install-in-user-type' in SystemConfig for any user types!";
+        final boolean isSevere = isEnforceMode(mode);
+        pmInt.forEachPackage(pkg -> {
+            if (!pkg.isSystem()) return;
+            final String pkgName = pkg.getManifestPackageName();
+            if (!allWhitelistedPackages.contains(pkgName)) {
+                warnings.add(new Pair<>(isSevere, String.format(logMessageFmt, pkgName)));
             }
         });
+
+        return warnings;
     }
 
     /** Whether to only install system packages in new users for which they are whitelisted. */
@@ -602,32 +653,45 @@
     }
 
     void dump(PrintWriter pw) {
-        final String prefix = "    ";
-        final String prefix2 = prefix + prefix;
+        try (IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "    ")) {
+            dumpIndented(ipw);
+        }
+    }
+
+    private void dumpIndented(IndentingPrintWriter pw) {
         final int mode = getWhitelistMode();
         pw.println("Whitelisted packages per user type");
-        pw.print(prefix); pw.print("Mode: ");
+
+        pw.increaseIndent();
+        pw.print("Mode: ");
         pw.print(mode);
         pw.print(isEnforceMode(mode) ? " (enforced)" : "");
         pw.print(isLogMode(mode) ? " (logged)" : "");
         pw.print(isImplicitWhitelistMode(mode) ? " (implicit)" : "");
         pw.print(isIgnoreOtaMode(mode) ? " (ignore OTAs)" : "");
         pw.println();
+        pw.decreaseIndent();
 
-        pw.print(prefix); pw.println("Legend");
+        pw.increaseIndent();
+        pw.println("Legend");
+        pw.increaseIndent();
         for (int idx = 0; idx < mUserTypes.length; idx++) {
-            pw.print(prefix2); pw.println(idx + " -> " + mUserTypes[idx]);
+            pw.println(idx + " -> " + mUserTypes[idx]);
         }
+        pw.decreaseIndent(); pw.decreaseIndent();
 
+        pw.increaseIndent();
         final int size = mWhitelistedPackagesForUserTypes.size();
         if (size == 0) {
-            pw.print(prefix); pw.println("No packages");
+            pw.println("No packages");
+            pw.decreaseIndent();
             return;
         }
-        pw.print(prefix); pw.print(size); pw.println(" packages:");
+        pw.print(size); pw.println(" packages:");
+        pw.increaseIndent();
         for (int pkgIdx = 0; pkgIdx < size; pkgIdx++) {
             final String pkgName = mWhitelistedPackagesForUserTypes.keyAt(pkgIdx);
-            pw.print(prefix2); pw.print(pkgName); pw.print(": ");
+            pw.print(pkgName); pw.print(": ");
             final long userTypesBitSet = mWhitelistedPackagesForUserTypes.valueAt(pkgIdx);
             for (int idx = 0; idx < mUserTypes.length; idx++) {
                 if ((userTypesBitSet & (1 << idx)) != 0) {
@@ -636,5 +700,40 @@
             }
             pw.println();
         }
+        pw.decreaseIndent(); pw.decreaseIndent();
+
+        pw.increaseIndent();
+        dumpMissingSystemPackages(pw, /* force= */ true, /* verbose= */ true);
+        pw.decreaseIndent();
+    }
+
+    void dumpMissingSystemPackages(IndentingPrintWriter pw, boolean force, boolean verbose) {
+        final int mode = getWhitelistMode();
+        final boolean show = force || (isEnforceMode(mode) && !isImplicitWhitelistMode(mode));
+        if (!show) return;
+
+        final List<Pair<Boolean, String>> warnings = checkSystemPackagesWhitelistWarnings(mode);
+        final int size = warnings.size();
+
+        if (size == 0) {
+            if (verbose) {
+                pw.println("All system packages are accounted for");
+            }
+            return;
+        }
+
+        if (verbose) {
+            pw.print(size); pw.println(" warnings for system user:");
+            pw.increaseIndent();
+        }
+        for (int i = 0; i < size; i++) {
+            final Pair<Boolean, String> pair = warnings.get(i);
+            final String lvl = pair.first ? "WTF" : "WARN";
+            final String msg = pair.second;
+            pw.print(lvl); pw.print(": "); pw.println(msg);
+        }
+        if (verbose) {
+            pw.decreaseIndent();
+        }
     }
 }
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
index 780b234..4a1a6a7 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
@@ -31,6 +31,7 @@
 import android.content.pm.parsing.component.ParsedInstrumentation;
 import android.content.pm.parsing.component.ParsedProvider;
 import android.content.pm.parsing.component.ParsedService;
+import android.os.incremental.IncrementalManager;
 import android.text.TextUtils;
 
 import com.android.internal.content.NativeLibraryHelper;
@@ -141,8 +142,15 @@
 
     public static boolean canHaveOatDir(AndroidPackage pkg, boolean isUpdatedSystemApp) {
         // The following app types CANNOT have oat directory
-        // - non-updated system apps
-        return !pkg.isSystem() || isUpdatedSystemApp;
+        // - non-updated system apps,
+        // - incrementally installed apps.
+        if (pkg.isSystem() && !isUpdatedSystemApp) {
+            return false;
+        }
+        if (IncrementalManager.isIncrementalPath(pkg.getCodePath())) {
+            return false;
+        }
+        return true;
     }
 
     public static boolean hasComponentClassName(AndroidPackage pkg, String className) {
diff --git a/services/core/java/com/android/server/power/AttentionDetector.java b/services/core/java/com/android/server/power/AttentionDetector.java
index 1ab6ade..b69c450 100644
--- a/services/core/java/com/android/server/power/AttentionDetector.java
+++ b/services/core/java/com/android/server/power/AttentionDetector.java
@@ -17,7 +17,6 @@
 package com.android.server.power;
 
 import static android.provider.DeviceConfig.NAMESPACE_ATTENTION_MANAGER_SERVICE;
-import static android.provider.Settings.Secure.ADAPTIVE_SLEEP;
 
 import android.Manifest;
 import android.app.ActivityManager;
@@ -192,9 +191,6 @@
         }
 
         if (!isAttentionServiceSupported() || !serviceHasSufficientPermissions()) {
-            // Turns off adaptive sleep in settings for all users if attention service is not
-            // available. The setting itself should also be grayed out in this case.
-            Settings.Secure.putInt(mContentResolver, ADAPTIVE_SLEEP, 0);
             return nextScreenDimming;
         }
 
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Enforcer.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Enforcer.java
index d98ad74..a18b690 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Enforcer.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerHw2Enforcer.java
@@ -23,7 +23,6 @@
 import android.hardware.soundtrigger.V2_3.RecognitionConfig;
 import android.os.IHwBinder;
 import android.os.RemoteException;
-import android.os.SystemProperties;
 import android.util.Log;
 
 import java.util.HashMap;
@@ -49,130 +48,83 @@
 
     @Override
     public Properties getProperties() {
-        try {
-            return mUnderlying.getProperties();
-        } catch (RuntimeException e) {
-            throw handleException(e);
-        }
+        return mUnderlying.getProperties();
     }
 
     @Override
     public int loadSoundModel(ISoundTriggerHw.SoundModel soundModel, Callback callback,
             int cookie) {
-        try {
-            int handle = mUnderlying.loadSoundModel(soundModel, new CallbackEnforcer(callback),
-                    cookie);
-            synchronized (mModelStates) {
-                mModelStates.put(handle, false);
-            }
-            return handle;
-        } catch (RuntimeException e) {
-            throw handleException(e);
+        int handle = mUnderlying.loadSoundModel(soundModel, new CallbackEnforcer(callback), cookie);
+        synchronized (mModelStates) {
+            mModelStates.put(handle, false);
         }
+        return handle;
     }
 
     @Override
     public int loadPhraseSoundModel(ISoundTriggerHw.PhraseSoundModel soundModel, Callback callback,
             int cookie) {
-        try {
-            int handle = mUnderlying.loadPhraseSoundModel(soundModel,
-                    new CallbackEnforcer(callback),
-                    cookie);
-            synchronized (mModelStates) {
-                mModelStates.put(handle, false);
-            }
-            return handle;
-        } catch (RuntimeException e) {
-            throw handleException(e);
+        int handle = mUnderlying.loadPhraseSoundModel(soundModel, new CallbackEnforcer(callback),
+                cookie);
+        synchronized (mModelStates) {
+            mModelStates.put(handle, false);
         }
+        return handle;
     }
 
     @Override
     public void unloadSoundModel(int modelHandle) {
-        try {
-            mUnderlying.unloadSoundModel(modelHandle);
-            synchronized (mModelStates) {
-                mModelStates.remove(modelHandle);
-            }
-        } catch (RuntimeException e) {
-            throw handleException(e);
+        mUnderlying.unloadSoundModel(modelHandle);
+        synchronized (mModelStates) {
+            mModelStates.remove(modelHandle);
         }
     }
 
     @Override
     public void stopRecognition(int modelHandle) {
-        try {
-            mUnderlying.stopRecognition(modelHandle);
-            synchronized (mModelStates) {
-                mModelStates.replace(modelHandle, false);
-            }
-        } catch (RuntimeException e) {
-            throw handleException(e);
+        mUnderlying.stopRecognition(modelHandle);
+        synchronized (mModelStates) {
+            mModelStates.replace(modelHandle, false);
         }
     }
 
     @Override
     public void stopAllRecognitions() {
-        try {
-            mUnderlying.stopAllRecognitions();
-            synchronized (mModelStates) {
-                for (Map.Entry<Integer, Boolean> entry : mModelStates.entrySet()) {
-                    entry.setValue(false);
-                }
+        mUnderlying.stopAllRecognitions();
+        synchronized (mModelStates) {
+            for (Map.Entry<Integer, Boolean> entry : mModelStates.entrySet()) {
+                entry.setValue(false);
             }
-        } catch (RuntimeException e) {
-            throw handleException(e);
         }
     }
 
     @Override
     public void startRecognition(int modelHandle, RecognitionConfig config, Callback callback,
             int cookie) {
-        try {
-            mUnderlying.startRecognition(modelHandle, config, new CallbackEnforcer(callback),
-                    cookie);
-            synchronized (mModelStates) {
-                mModelStates.replace(modelHandle, true);
-            }
-        } catch (RuntimeException e) {
-            throw handleException(e);
+        mUnderlying.startRecognition(modelHandle, config, new CallbackEnforcer(callback), cookie);
+        synchronized (mModelStates) {
+            mModelStates.replace(modelHandle, true);
         }
     }
 
     @Override
     public void getModelState(int modelHandle) {
-        try {
-            mUnderlying.getModelState(modelHandle);
-        } catch (RuntimeException e) {
-            throw handleException(e);
-        }
+        mUnderlying.getModelState(modelHandle);
     }
 
     @Override
     public int getModelParameter(int modelHandle, int param) {
-        try {
-            return mUnderlying.getModelParameter(modelHandle, param);
-        } catch (RuntimeException e) {
-            throw handleException(e);
-        }
+        return mUnderlying.getModelParameter(modelHandle, param);
     }
 
     @Override
     public void setModelParameter(int modelHandle, int param, int value) {
-        try {
-            mUnderlying.setModelParameter(modelHandle, param, value);
-        } catch (RuntimeException e) {
-            throw handleException(e);
-        }
+        mUnderlying.setModelParameter(modelHandle, param, value);
     }
 
     @Override
     public ModelParameterRange queryParameter(int modelHandle, int param) {
-        try {
-            return mUnderlying.queryParameter(modelHandle, param);
-        } catch (RuntimeException e) {
-            throw handleException(e);
-        }
+        return mUnderlying.queryParameter(modelHandle, param);
     }
 
     @Override
@@ -190,17 +142,6 @@
         return mUnderlying.interfaceDescriptor();
     }
 
-    private static RuntimeException handleException(RuntimeException e) {
-        Log.e(TAG, "Exception caught from HAL, rebooting HAL");
-        rebootHal();
-        throw e;
-    }
-
-    private static void rebootHal() {
-        // This property needs to be defined in an init.rc script and trigger a HAL reboot.
-        SystemProperties.set("sys.audio.restart.hal", "1");
-    }
-
     private class CallbackEnforcer implements Callback {
         private final Callback mUnderlying;
 
@@ -216,8 +157,6 @@
             synchronized (mModelStates) {
                 if (!mModelStates.getOrDefault(model, false)) {
                     Log.wtfStack(TAG, "Unexpected recognition event for model: " + model);
-                    rebootHal();
-                    return;
                 }
                 if (event.header.status
                         != android.media.soundtrigger_middleware.RecognitionStatus.FORCED) {
@@ -234,8 +173,6 @@
             synchronized (mModelStates) {
                 if (!mModelStates.getOrDefault(model, false)) {
                     Log.wtfStack(TAG, "Unexpected recognition event for model: " + model);
-                    rebootHal();
-                    return;
                 }
                 if (event.common.header.status
                         != android.media.soundtrigger_middleware.RecognitionStatus.FORCED) {
diff --git a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java
index 4b464d2..635cb61 100644
--- a/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java
+++ b/services/core/java/com/android/server/soundtrigger_middleware/SoundTriggerMiddlewareService.java
@@ -27,7 +27,9 @@
 import android.media.soundtrigger_middleware.RecognitionConfig;
 import android.media.soundtrigger_middleware.SoundModel;
 import android.media.soundtrigger_middleware.SoundTriggerModuleDescriptor;
+import android.os.Parcel;
 import android.os.RemoteException;
+import android.os.SystemProperties;
 import android.util.Log;
 
 import com.android.server.SystemService;
@@ -99,6 +101,28 @@
         }
     }
 
+    @Override
+    public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+            throws RemoteException {
+        try {
+            return super.onTransact(code, data, reply, flags);
+        } catch (InternalServerError e) {
+            if (e.getCause() instanceof HalException) {
+                // We recover from any sort of HAL failure by rebooting the HAL process.
+                // This will likely reboot more than just the sound trigger HAL.
+                // The rest of the system should be able to tolerate that.
+                rebootHal();
+            }
+            throw e;
+        }
+    }
+
+    private static void rebootHal() {
+        Log.i(TAG, "Rebooting the sound trigger HAL");
+        // This property needs to be defined in an init.rc script and trigger a HAL reboot.
+        SystemProperties.set("sys.audio.restart.hal", "1");
+    }
+
     private final static class ModuleService extends ISoundTriggerModule.Stub {
         private final ISoundTriggerModule mDelegate;
 
@@ -158,6 +182,22 @@
         public void detach() throws RemoteException {
             mDelegate.detach();
         }
+
+        @Override
+        public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+                throws RemoteException {
+            try {
+                return super.onTransact(code, data, reply, flags);
+            } catch (InternalServerError e) {
+                if (e.getCause() instanceof HalException) {
+                    // We recover from any sort of HAL failure by rebooting the HAL process.
+                    // This will likely reboot more than just the sound trigger HAL.
+                    // The rest of the system should be able to tolerate that.
+                    rebootHal();
+                }
+                throw e;
+            }
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/CasResource.java b/services/core/java/com/android/server/tv/tunerresourcemanager/CasResource.java
new file mode 100644
index 0000000..54ad1d2
--- /dev/null
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/CasResource.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright 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.tv.tunerresourcemanager;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A Cas resource object used by the Tuner Resource Manager to record the cas
+ * information.
+ *
+ * @hide
+ */
+public final class CasResource {
+
+    private final int mSystemId;
+
+    private int mMaxSessionNum;
+
+    private int mAvailableSessionNum;
+
+    /**
+     * The owner clients' ids when part of the Cas is occupied.
+     */
+    private Map<Integer, Integer> mOwnerClientIdsToSessionNum = new HashMap<>();
+
+    private CasResource(Builder builder) {
+        this.mSystemId = builder.mSystemId;
+        this.mMaxSessionNum = builder.mMaxSessionNum;
+        this.mAvailableSessionNum = builder.mMaxSessionNum;
+    }
+
+    public int getSystemId() {
+        return mSystemId;
+    }
+
+    public int getMaxSessionNum() {
+        return mMaxSessionNum;
+    }
+
+    public int getUsedSessionNum() {
+        return (mMaxSessionNum - mAvailableSessionNum);
+    }
+
+    public boolean isFullyUsed() {
+        return mAvailableSessionNum == 0;
+    }
+
+    /**
+     * Update max session number.
+     *
+     * @param maxSessionNum the new max session num.
+     */
+    public void updateMaxSessionNum(int maxSessionNum) {
+        mAvailableSessionNum = Math.max(
+                0, mAvailableSessionNum + (maxSessionNum - mMaxSessionNum));
+        mMaxSessionNum = maxSessionNum;
+    }
+
+    /**
+     * Set an owner for the cas
+     *
+     * @param ownerId the client id of the owner.
+     */
+    public void setOwner(int ownerId) {
+        int sessionNum = mOwnerClientIdsToSessionNum.get(ownerId) == null
+                ? 1 : (mOwnerClientIdsToSessionNum.get(ownerId) + 1);
+        mOwnerClientIdsToSessionNum.put(ownerId, sessionNum);
+        mAvailableSessionNum--;
+    }
+
+    /**
+     * Remove an owner of the Cas.
+     *
+     * @param ownerId the removing client id of the owner.
+     */
+    public void removeOwner(int ownerId) {
+        mAvailableSessionNum += mOwnerClientIdsToSessionNum.get(ownerId);
+        mOwnerClientIdsToSessionNum.remove(ownerId);
+    }
+
+    public Set<Integer> getOwnerClientIds() {
+        return mOwnerClientIdsToSessionNum.keySet();
+    }
+
+    @Override
+    public String toString() {
+        return "CasResource[systemId=" + this.mSystemId
+                + ", isFullyUsed=" + (this.mAvailableSessionNum == 0)
+                + ", maxSessionNum=" + this.mMaxSessionNum
+                + ", ownerClients=" + ownersMapToString() + "]";
+    }
+
+    /**
+     * Builder class for {@link CasResource}.
+     */
+    public static class Builder {
+
+        private int mSystemId;
+        private int mMaxSessionNum;
+
+        Builder(int systemId) {
+            this.mSystemId = systemId;
+        }
+
+        /**
+         * Builder for {@link CasResource}.
+         *
+         * @param maxSessionNum the max session num the current Cas has.
+         */
+        public Builder maxSessionNum(int maxSessionNum) {
+            this.mMaxSessionNum = maxSessionNum;
+            return this;
+        }
+
+        /**
+         * Build a {@link CasResource}.
+         *
+         * @return {@link CasResource}.
+         */
+        public CasResource build() {
+            CasResource cas = new CasResource(this);
+            return cas;
+        }
+    }
+
+    private String ownersMapToString() {
+        StringBuilder string = new StringBuilder("{");
+        for (int clienId : mOwnerClientIdsToSessionNum.keySet()) {
+            string.append(" clientId=")
+                  .append(clienId)
+                  .append(", owns session num=")
+                  .append(mOwnerClientIdsToSessionNum.get(clienId))
+                  .append(",");
+        }
+        return string.append("}").toString();
+    }
+}
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java b/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java
index 4cdc172..2b0fe8a 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/ClientProfile.java
@@ -27,6 +27,7 @@
 public final class ClientProfile {
 
     public static final int INVALID_GROUP_ID = -1;
+    public static final int INVALID_RESOURCE_ID = -1;
 
     /**
      * Client id sent to the client when registering with
@@ -56,7 +57,6 @@
      * also lose their resources.
      */
     private int mGroupId = INVALID_GROUP_ID;
-
     /**
      * Optional nice value for TRM to reduce client’s priority.
      */
@@ -73,6 +73,11 @@
     private Set<Integer> mUsingLnbIds = new HashSet<>();
 
     /**
+     * List of the Cas system ids that are used by the current client.
+     */
+    private int mUsingCasSystemId = INVALID_RESOURCE_ID;
+
+    /**
      * Optional arbitrary priority value given by the client.
      *
      * <p>This value can override the default priorotiy calculated from
@@ -172,11 +177,32 @@
     }
 
     /**
+     * Set when the client starts to use a Cas system.
+     *
+     * @param casSystemId cas being used.
+     */
+    public void useCas(int casSystemId) {
+        mUsingCasSystemId = casSystemId;
+    }
+
+    public int getInUseCasSystemId() {
+        return mUsingCasSystemId;
+    }
+
+    /**
+     * Called when the client released a Cas System.
+     */
+    public void releaseCas() {
+        mUsingCasSystemId = INVALID_RESOURCE_ID;
+    }
+
+    /**
      * Called to reclaim all the resources being used by the current client.
      */
     public void reclaimAllResources() {
         mUsingFrontendIds.clear();
         mUsingLnbIds.clear();
+        mUsingCasSystemId = INVALID_RESOURCE_ID;
     }
 
     @Override
diff --git a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
index 7231813..2f70840 100644
--- a/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
+++ b/services/core/java/com/android/server/tv/tunerresourcemanager/TunerResourceManagerService.java
@@ -65,6 +65,8 @@
     private Map<Integer, FrontendResource> mFrontendResources = new HashMap<>();
     // Map of the current available lnb resources
     private Map<Integer, LnbResource> mLnbResources = new HashMap<>();
+    // Map of the current available Cas resources
+    private Map<Integer, CasResource> mCasResources = new HashMap<>();
 
     @GuardedBy("mLock")
     private Map<Integer, ResourcesReclaimListenerRecord> mListeners = new HashMap<>();
@@ -158,10 +160,8 @@
         @Override
         public void updateCasInfo(int casSystemId, int maxSessionNum) {
             enforceTrmAccessPermission("updateCasInfo");
-            if (DEBUG) {
-                Slog.d(TAG,
-                        "updateCasInfo(casSystemId=" + casSystemId
-                                + ", maxSessionNum=" + maxSessionNum + ")");
+            synchronized (mLock) {
+                updateCasInfoInternal(casSystemId, maxSessionNum);
             }
         }
 
@@ -185,11 +185,11 @@
                 throw new RemoteException("frontendHandle can't be null");
             }
             synchronized (mLock) {
-                try {
-                    return requestFrontendInternal(request, frontendHandle);
-                } catch (RemoteException e) {
-                    throw e.rethrowFromSystemServer();
+                if (!checkClientExists(request.getClientId())) {
+                    throw new RemoteException("Request frontend from unregistered client:"
+                            + request.getClientId());
                 }
+                return requestFrontendInternal(request, frontendHandle);
             }
         }
 
@@ -211,32 +211,45 @@
                 throw new RemoteException("demuxHandle can't be null");
             }
             synchronized (mLock) {
+                if (!checkClientExists(request.getClientId())) {
+                    throw new RemoteException("Request demux from unregistered client:"
+                            + request.getClientId());
+                }
                 return requestDemuxInternal(request, demuxHandle);
             }
         }
 
         @Override
         public boolean requestDescrambler(@NonNull TunerDescramblerRequest request,
-                    @NonNull int[] descrambleHandle) throws RemoteException {
+                    @NonNull int[] descramblerHandle) throws RemoteException {
             enforceDescramblerAccessPermission("requestDescrambler");
             enforceTrmAccessPermission("requestDescrambler");
-            if (descrambleHandle == null) {
-                throw new RemoteException("descrambleHandle can't be null");
+            if (descramblerHandle == null) {
+                throw new RemoteException("descramblerHandle can't be null");
             }
             synchronized (mLock) {
-                return requestDescramblerInternal(request, descrambleHandle);
+                if (!checkClientExists(request.getClientId())) {
+                    throw new RemoteException("Request descrambler from unregistered client:"
+                            + request.getClientId());
+                }
+                return requestDescramblerInternal(request, descramblerHandle);
             }
         }
 
         @Override
-        public boolean requestCasSession(
-                @NonNull CasSessionRequest request, @NonNull int[] sessionResourceHandle) {
+        public boolean requestCasSession(@NonNull CasSessionRequest request,
+                @NonNull int[] casSessionHandle) throws RemoteException {
             enforceTrmAccessPermission("requestCasSession");
-            if (DEBUG) {
-                Slog.d(TAG, "requestCasSession(request=" + request + ")");
+            if (casSessionHandle == null) {
+                throw new RemoteException("casSessionHandle can't be null");
             }
-
-            return true;
+            synchronized (mLock) {
+                if (!checkClientExists(request.getClientId())) {
+                    throw new RemoteException("Request cas from unregistered client:"
+                            + request.getClientId());
+                }
+                return requestCasSessionInternal(request, casSessionHandle);
+            }
         }
 
         @Override
@@ -248,11 +261,11 @@
                 throw new RemoteException("lnbHandle can't be null");
             }
             synchronized (mLock) {
-                try {
-                    return requestLnbInternal(request, lnbHandle);
-                } catch (RemoteException e) {
-                    throw e.rethrowFromSystemServer();
+                if (!checkClientExists(request.getClientId())) {
+                    throw new RemoteException("Request lnb from unregistered client:"
+                            + request.getClientId());
                 }
+                return requestLnbInternal(request, lnbHandle);
             }
         }
 
@@ -264,15 +277,20 @@
                     frontendHandle)) {
                 throw new RemoteException("frontendHandle can't be invalid");
             }
-            int frontendId = getResourceIdFromHandle(frontendHandle);
-            FrontendResource fe = getFrontendResource(frontendId);
-            if (fe == null) {
-                throw new RemoteException("Releasing frontend does not exist.");
-            }
-            if (fe.getOwnerClientId() != clientId) {
-                throw new RemoteException("Client is not the current owner of the releasing fe.");
-            }
             synchronized (mLock) {
+                if (!checkClientExists(clientId)) {
+                    throw new RemoteException("Release frontend from unregistered client:"
+                            + clientId);
+                }
+                int frontendId = getResourceIdFromHandle(frontendHandle);
+                FrontendResource fe = getFrontendResource(frontendId);
+                if (fe == null) {
+                    throw new RemoteException("Releasing frontend does not exist.");
+                }
+                if (fe.getOwnerClientId() != clientId) {
+                    throw new RemoteException(
+                            "Client is not the current owner of the releasing fe.");
+                }
                 releaseFrontendInternal(fe);
             }
         }
@@ -296,10 +314,26 @@
         }
 
         @Override
-        public void releaseCasSession(int sessionResourceId, int clientId) {
+        public void releaseCasSession(int casSessionHandle, int clientId) throws RemoteException {
             enforceTrmAccessPermission("releaseCasSession");
-            if (DEBUG) {
-                Slog.d(TAG, "releaseCasSession(sessionResourceId=" + sessionResourceId + ")");
+            if (!validateResourceHandle(
+                    TunerResourceManager.TUNER_RESOURCE_TYPE_CAS_SESSION, casSessionHandle)) {
+                throw new RemoteException("casSessionHandle can't be invalid");
+            }
+            synchronized (mLock) {
+                if (!checkClientExists(clientId)) {
+                    throw new RemoteException("Release cas from unregistered client:" + clientId);
+                }
+                int casSystemId = getClientProfile(clientId).getInUseCasSystemId();
+                CasResource cas = getCasResource(casSystemId);
+                if (cas == null) {
+                    throw new RemoteException("Releasing cas does not exist.");
+                }
+                if (!cas.getOwnerClientIds().contains(clientId)) {
+                    throw new RemoteException(
+                            "Client is not the current owner of the releasing cas.");
+                }
+                releaseCasSessionInternal(cas, clientId);
             }
         }
 
@@ -310,6 +344,9 @@
             if (!validateResourceHandle(TunerResourceManager.TUNER_RESOURCE_TYPE_LNB, lnbHandle)) {
                 throw new RemoteException("lnbHandle can't be invalid");
             }
+            if (!checkClientExists(clientId)) {
+                throw new RemoteException("Release lnb from unregistered client:" + clientId);
+            }
             int lnbId = getResourceIdFromHandle(lnbHandle);
             LnbResource lnb = getLnbResource(lnbId);
             if (lnb == null) {
@@ -465,17 +502,42 @@
     }
 
     @VisibleForTesting
-    protected boolean requestFrontendInternal(TunerFrontendRequest request, int[] frontendHandle)
-            throws RemoteException {
+    protected void updateCasInfoInternal(int casSystemId, int maxSessionNum) {
+        if (DEBUG) {
+            Slog.d(TAG,
+                    "updateCasInfo(casSystemId=" + casSystemId
+                            + ", maxSessionNum=" + maxSessionNum + ")");
+        }
+        // If maxSessionNum is 0, removing the Cas Resource.
+        if (maxSessionNum == 0) {
+            removeCasResource(casSystemId);
+            return;
+        }
+        // If the Cas exists, updates the Cas Resource accordingly.
+        CasResource cas = getCasResource(casSystemId);
+        if (cas != null) {
+            if (cas.getUsedSessionNum() > maxSessionNum) {
+                // Sort and release the short number of Cas resources.
+                int releasingCasResourceNum = cas.getUsedSessionNum() - maxSessionNum;
+                releaseLowerPriorityClientCasResources(releasingCasResourceNum);
+            }
+            cas.updateMaxSessionNum(maxSessionNum);
+            return;
+        }
+        // Add the new Cas Resource.
+        cas = new CasResource.Builder(casSystemId)
+                             .maxSessionNum(maxSessionNum)
+                             .build();
+        addCasResource(cas);
+    }
+
+    @VisibleForTesting
+    protected boolean requestFrontendInternal(TunerFrontendRequest request, int[] frontendHandle) {
         if (DEBUG) {
             Slog.d(TAG, "requestFrontend(request=" + request + ")");
         }
 
         frontendHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE;
-        if (!checkClientExists(request.getClientId())) {
-            Slog.e(TAG, "Request frontend from unregistered client:" + request.getClientId());
-            return false;
-        }
         ClientProfile requestClient = getClientProfile(request.getClientId());
         int grantingFrontendId = -1;
         int inUseLowestPriorityFrId = -1;
@@ -496,7 +558,7 @@
                 } else if (grantingFrontendId < 0) {
                     // Record the frontend id with the lowest client priority among all the
                     // in use frontends when no available frontend has been found.
-                    int priority = getOwnerClientPriority(fr);
+                    int priority = getOwnerClientPriority(fr.getOwnerClientId());
                     if (currentLowestPriority > priority) {
                         inUseLowestPriorityFrId = fr.getId();
                         currentLowestPriority = priority;
@@ -530,17 +592,12 @@
     }
 
     @VisibleForTesting
-    protected boolean requestLnbInternal(TunerLnbRequest request, int[] lnbHandle)
-            throws RemoteException {
+    protected boolean requestLnbInternal(TunerLnbRequest request, int[] lnbHandle) {
         if (DEBUG) {
             Slog.d(TAG, "requestLnb(request=" + request + ")");
         }
 
         lnbHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE;
-        if (!checkClientExists(request.getClientId())) {
-            Slog.e(TAG, "Request lnb from unregistered client:" + request.getClientId());
-            return false;
-        }
         ClientProfile requestClient = getClientProfile(request.getClientId());
         int grantingLnbId = -1;
         int inUseLowestPriorityLnbId = -1;
@@ -554,7 +611,7 @@
             } else {
                 // Record the lnb id with the lowest client priority among all the
                 // in use lnb when no available lnb has been found.
-                int priority = getOwnerClientPriority(lnb);
+                int priority = getOwnerClientPriority(lnb.getOwnerClientId());
                 if (currentLowestPriority > priority) {
                     inUseLowestPriorityLnbId = lnb.getId();
                     currentLowestPriority = priority;
@@ -588,7 +645,55 @@
     }
 
     @VisibleForTesting
-    void releaseFrontendInternal(FrontendResource fe) {
+    protected boolean requestCasSessionInternal(CasSessionRequest request, int[] casSessionHandle) {
+        if (DEBUG) {
+            Slog.d(TAG, "requestCasSession(request=" + request + ")");
+        }
+        CasResource cas = getCasResource(request.getCasSystemId());
+        // Unregistered Cas System is treated as having unlimited sessions.
+        if (cas == null) {
+            cas = new CasResource.Builder(request.getCasSystemId())
+                                 .maxSessionNum(Integer.MAX_VALUE)
+                                 .build();
+            addCasResource(cas);
+        }
+        casSessionHandle[0] = TunerResourceManager.INVALID_RESOURCE_HANDLE;
+        ClientProfile requestClient = getClientProfile(request.getClientId());
+        int lowestPriorityOwnerId = -1;
+        // Priority max value is 1000
+        int currentLowestPriority = MAX_CLIENT_PRIORITY + 1;
+        if (!cas.isFullyUsed()) {
+            casSessionHandle[0] = generateResourceHandle(
+                    TunerResourceManager.TUNER_RESOURCE_TYPE_CAS_SESSION, cas.getSystemId());
+            updateCasClientMappingOnNewGrant(request.getCasSystemId(), request.getClientId());
+            return true;
+        }
+        for (int ownerId : cas.getOwnerClientIds()) {
+            // Record the client id with lowest priority that is using the current Cas system.
+            int priority = getOwnerClientPriority(ownerId);
+            if (currentLowestPriority > priority) {
+                lowestPriorityOwnerId = ownerId;
+                currentLowestPriority = priority;
+            }
+        }
+
+        // When all the Cas sessions are occupied, reclaim the lowest priority client if the
+        // request client has higher priority.
+        if (lowestPriorityOwnerId > -1 && (requestClient.getPriority() > currentLowestPriority)) {
+            if (!reclaimResource(lowestPriorityOwnerId,
+                    TunerResourceManager.TUNER_RESOURCE_TYPE_CAS_SESSION)) {
+                return false;
+            }
+            casSessionHandle[0] = generateResourceHandle(
+                    TunerResourceManager.TUNER_RESOURCE_TYPE_CAS_SESSION, cas.getSystemId());
+            updateCasClientMappingOnNewGrant(request.getCasSystemId(), request.getClientId());
+            return true;
+        }
+        return false;
+    }
+
+    @VisibleForTesting
+    protected void releaseFrontendInternal(FrontendResource fe) {
         if (DEBUG) {
             Slog.d(TAG, "releaseFrontend(id=" + fe.getId() + ")");
         }
@@ -596,7 +701,7 @@
     }
 
     @VisibleForTesting
-    void releaseLnbInternal(LnbResource lnb) {
+    protected void releaseLnbInternal(LnbResource lnb) {
         if (DEBUG) {
             Slog.d(TAG, "releaseLnb(lnbId=" + lnb.getId() + ")");
         }
@@ -604,7 +709,15 @@
     }
 
     @VisibleForTesting
-    boolean requestDemuxInternal(TunerDemuxRequest request, int[] demuxHandle) {
+    protected void releaseCasSessionInternal(CasResource cas, int ownerClientId) {
+        if (DEBUG) {
+            Slog.d(TAG, "releaseCasSession(sessionResourceId=" + cas.getSystemId() + ")");
+        }
+        updateCasClientMappingOnRelease(cas, ownerClientId);
+    }
+
+    @VisibleForTesting
+    protected boolean requestDemuxInternal(TunerDemuxRequest request, int[] demuxHandle) {
         if (DEBUG) {
             Slog.d(TAG, "requestDemux(request=" + request + ")");
         }
@@ -614,7 +727,8 @@
     }
 
     @VisibleForTesting
-    boolean requestDescramblerInternal(TunerDescramblerRequest request, int[] descramblerHandle) {
+    protected boolean requestDescramblerInternal(
+            TunerDescramblerRequest request, int[] descramblerHandle) {
         if (DEBUG) {
             Slog.d(TAG, "requestDescrambler(request=" + request + ")");
         }
@@ -742,14 +856,28 @@
         ownerProfile.releaseLnb(releasingLnb.getId());
     }
 
+    private void updateCasClientMappingOnNewGrant(int grantingId, int ownerClientId) {
+        CasResource grantingCas = getCasResource(grantingId);
+        ClientProfile ownerProfile = getClientProfile(ownerClientId);
+        grantingCas.setOwner(ownerClientId);
+        ownerProfile.useCas(grantingId);
+    }
+
+    private void updateCasClientMappingOnRelease(
+            @NonNull CasResource releasingCas, int ownerClientId) {
+        ClientProfile ownerProfile = getClientProfile(ownerClientId);
+        releasingCas.removeOwner(ownerClientId);
+        ownerProfile.releaseCas();
+    }
+
     /**
      * Get the owner client's priority from the resource id.
      *
-     * @param resource a in use tuner resource.
+     * @param clientId the owner client id.
      * @return the priority of the owner client of the resource.
      */
-    private int getOwnerClientPriority(TunerResourceBasic resource) {
-        return getClientProfile(resource.getOwnerClientId()).getPriority();
+    private int getOwnerClientPriority(int clientId) {
+        return getClientProfile(clientId).getPriority();
     }
 
     @VisibleForTesting
@@ -783,6 +911,9 @@
 
     private void removeFrontendResource(int removingId) {
         FrontendResource fe = getFrontendResource(removingId);
+        if (fe == null) {
+            return;
+        }
         if (fe.isInUse()) {
             releaseFrontendInternal(fe);
         }
@@ -811,6 +942,9 @@
 
     private void removeLnbResource(int removingId) {
         LnbResource lnb = getLnbResource(removingId);
+        if (lnb == null) {
+            return;
+        }
         if (lnb.isInUse()) {
             releaseLnbInternal(lnb);
         }
@@ -819,6 +953,39 @@
 
     @VisibleForTesting
     @Nullable
+    protected CasResource getCasResource(int systemId) {
+        return mCasResources.get(systemId);
+    }
+
+    @VisibleForTesting
+    protected Map<Integer, CasResource> getCasResources() {
+        return mCasResources;
+    }
+
+    private void addCasResource(CasResource newCas) {
+        // Update resource list and available id list
+        mCasResources.put(newCas.getSystemId(), newCas);
+    }
+
+    private void removeCasResource(int removingId) {
+        CasResource cas = getCasResource(removingId);
+        if (cas == null) {
+            return;
+        }
+        for (int ownerId : cas.getOwnerClientIds()) {
+            getClientProfile(ownerId).releaseCas();
+        }
+        mCasResources.remove(removingId);
+    }
+
+    private void releaseLowerPriorityClientCasResources(int releasingCasResourceNum) {
+        // TODO: Sort with a treemap
+
+        // select the first num client to release
+    }
+
+    @VisibleForTesting
+    @Nullable
     protected ClientProfile getClientProfile(int clientId) {
         return mClientProfiles.get(clientId);
     }
@@ -830,12 +997,7 @@
     }
 
     private void removeClientProfile(int clientId) {
-        for (int id : getClientProfile(clientId).getInUseFrontendIds()) {
-            getFrontendResource(id).removeOwner();
-            for (int groupMemberId : getFrontendResource(id).getExclusiveGroupMemberFeIds()) {
-                getFrontendResource(groupMemberId).removeOwner();
-            }
-        }
+        reclaimingResourcesFromClient(getClientProfile(clientId));
         mClientProfiles.remove(clientId);
         mListeners.remove(clientId);
     }
@@ -847,6 +1009,9 @@
         for (Integer lnbId : profile.getInUseLnbIds()) {
             getLnbResource(lnbId).removeOwner();
         }
+        if (profile.getInUseCasSystemId() != ClientProfile.INVALID_RESOURCE_ID) {
+            getCasResource(profile.getInUseCasSystemId()).removeOwner(profile.getId());
+        }
         profile.reclaimAllResources();
     }
 
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index abccf99..9d63029 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -66,6 +66,7 @@
 import com.android.server.wm.WindowManagerInternal.MagnificationCallbacks;
 import com.android.server.wm.WindowManagerInternal.WindowsForAccessibilityCallback;
 
+import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
@@ -341,6 +342,16 @@
         windowState.getTransformationMatrix(sTempFloats, outMatrix);
     }
 
+    void dump(PrintWriter pw, String prefix) {
+        for (int i = 0; i < mDisplayMagnifiers.size(); i++) {
+            final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.valueAt(i);
+            if (displayMagnifier != null) {
+                displayMagnifier.dump(pw, prefix
+                        + "Magnification display# " + mDisplayMagnifiers.keyAt(i));
+            }
+        }
+    }
+
     /**
      * This class encapsulates the functionality related to display magnification.
      */
@@ -551,6 +562,10 @@
             mMagnifedViewport.drawWindowIfNeededLocked(t);
         }
 
+        void dump(PrintWriter pw, String prefix) {
+            mMagnifedViewport.dump(pw, prefix);
+        }
+
         private final class MagnifiedViewport {
 
             private final SparseArray<WindowState> mTempWindowStates =
@@ -820,6 +835,10 @@
                 }, false /* traverseTopToBottom */ );
             }
 
+            void dump(PrintWriter pw, String prefix) {
+                mWindow.dump(pw, prefix);
+            }
+
             private final class ViewportWindow {
                 private static final String SURFACE_TITLE = "Magnification Overlay";
 
@@ -985,6 +1004,14 @@
                     mSurface.release();
                 }
 
+                void dump(PrintWriter pw, String prefix) {
+                    pw.println(prefix
+                            + " mBounds= " + mBounds
+                            + " mDirtyRect= " + mDirtyRect
+                            + " mWidth= " + mSurfaceControl.getWidth()
+                            + " mHeight= " + mSurfaceControl.getHeight());
+                }
+
                 private final class AnimationController extends Handler {
                     private static final String PROPERTY_NAME_ALPHA = "alpha";
 
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 2648c86..729f549 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -3137,7 +3137,6 @@
         commitVisibility(false /* visible */, true /* performLayout */);
 
         getDisplayContent().mOpeningApps.remove(this);
-        getDisplayContent().mChangingContainers.remove(this);
         getDisplayContent().mUnknownAppVisibilityController.appRemovedOrHidden(this);
         mWmService.mTaskSnapshotController.onAppRemoved(this);
         mStackSupervisor.getActivityMetricsLogger().notifyActivityRemoved(this);
@@ -6130,19 +6129,8 @@
 
     @Override
     void cancelAnimation() {
-        cancelAnimationOnly();
-        clearThumbnail();
-        mSurfaceFreezer.unfreeze(getPendingTransaction());
-    }
-
-    /**
-     * This only cancels the animation. It doesn't do other teardown like cleaning-up thumbnail
-     * or interim leashes.
-     * <p>
-     * Used when canceling in preparation for starting a new animation.
-     */
-    void cancelAnimationOnly() {
         super.cancelAnimation();
+        clearThumbnail();
     }
 
     @VisibleForTesting
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 0754a34..8edc84f 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -168,7 +168,6 @@
 
     // The task display area to launch the activity onto, barring any strong reason to do otherwise.
     private TaskDisplayArea mPreferredTaskDisplayArea;
-    // The windowing mode to apply to the root task, if possible
     private int mPreferredWindowingMode;
 
     private Task mInTask;
@@ -1560,9 +1559,6 @@
         if (!mAvoidMoveToFront && mDoResume) {
             mTargetStack.getStack().moveToFront("reuseOrNewTask", targetTask);
             if (mOptions != null) {
-                if (mPreferredWindowingMode != WINDOWING_MODE_UNDEFINED) {
-                    mTargetStack.setWindowingMode(mPreferredWindowingMode);
-                }
                 if (mOptions.getTaskAlwaysOnTop()) {
                     mTargetStack.setAlwaysOnTop(true);
                 }
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 736bc9f..02f6a69 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -5400,14 +5400,11 @@
         final boolean inputMethodExists = !(config.keyboard == Configuration.KEYBOARD_NOKEYS
                 && config.touchscreen == Configuration.TOUCHSCREEN_NOTOUCH
                 && config.navigation == Configuration.NAVIGATION_NONAV);
-        int modeType = config.uiMode & Configuration.UI_MODE_TYPE_MASK;
-        final boolean uiModeSupportsDialogs = (modeType != Configuration.UI_MODE_TYPE_CAR
-                && !(modeType == Configuration.UI_MODE_TYPE_WATCH && Build.IS_USER)
-                && modeType != Configuration.UI_MODE_TYPE_TELEVISION
-                && modeType != Configuration.UI_MODE_TYPE_VR_HEADSET);
         final boolean hideDialogsSet = Settings.Global.getInt(mContext.getContentResolver(),
                 HIDE_ERROR_DIALOGS, 0) != 0;
-        mShowDialogs = inputMethodExists && uiModeSupportsDialogs && !hideDialogsSet;
+        mShowDialogs = inputMethodExists
+                && ActivityTaskManager.currentUiModeSupportsErrorDialogs(mContext)
+                && !hideDialogsSet;
     }
 
     private void updateFontScaleIfNeeded(@UserIdInt int userId) {
@@ -6607,7 +6604,6 @@
                     }
                     return;
                 }
-                process.mIsImeProcess = true;
                 process.registerDisplayConfigurationListener(displayContent);
             }
         }
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 601f8df..8aace21 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -261,6 +261,8 @@
     @Px
     private int mRightGestureInset;
 
+    private boolean mNavButtonForcedVisible;
+
     StatusBarManagerInternal getStatusBarManagerInternal() {
         synchronized (mServiceAcquireLock) {
             if (mStatusBarManagerInternal == null) {
@@ -1046,12 +1048,14 @@
                             // calculate inset.
                             if (navigationBarPosition(displayFrames.mDisplayWidth,
                                     displayFrames.mDisplayHeight,
-                                    displayFrames.mRotation) == NAV_BAR_BOTTOM) {
+                                    displayFrames.mRotation) == NAV_BAR_BOTTOM
+                                    && !mNavButtonForcedVisible) {
+
                                 sTmpRect.set(displayFrames.mUnrestricted);
                                 sTmpRect.intersectUnchecked(displayFrames.mDisplayCutoutSafe);
                                 inOutFrame.top = sTmpRect.bottom
                                         - getNavigationBarHeight(displayFrames.mRotation,
-                                                mDisplayContent.getConfiguration().uiMode);
+                                        mDisplayContent.getConfiguration().uiMode);
                             }
                         },
 
@@ -2810,6 +2814,8 @@
         mNavBarOpacityMode = res.getInteger(R.integer.config_navBarOpacityMode);
         mLeftGestureInset = mGestureNavigationSettingsObserver.getLeftSensitivity(res);
         mRightGestureInset = mGestureNavigationSettingsObserver.getRightSensitivity(res);
+        mNavButtonForcedVisible =
+                mGestureNavigationSettingsObserver.areNavigationButtonForcedVisible();
         mNavigationBarLetsThroughTaps = res.getBoolean(R.bool.config_navBarTapThrough);
         mNavigationBarAlwaysShowOnSideGesture =
                 res.getBoolean(R.bool.config_navBarAlwaysShowOnSideEdgeGesture);
diff --git a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
index 9d44cad..86e0818 100644
--- a/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
+++ b/services/core/java/com/android/server/wm/ScreenRotationAnimation.java
@@ -212,7 +212,7 @@
             final Surface surface = mService.mSurfaceFactory.get();
             surface.copyFrom(mScreenshotLayer);
             SurfaceControl.ScreenshotGraphicBuffer gb =
-                    mService.mDisplayManagerInternal.screenshot(displayId);
+                    mService.mDisplayManagerInternal.systemScreenshot(displayId);
             if (gb != null) {
                 Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
                         "ScreenRotationAnimation#getMedianBorderLuma");
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index df5cfee..b12eb70 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -1992,7 +1992,8 @@
         if (mWmService.mDisableTransitionAnimation
                 || !isVisible()
                 || getDisplayContent().mAppTransition.isTransitionSet()
-                || getSurfaceControl() == null) {
+                || getSurfaceControl() == null
+                || !isLeafTask()) {
             return false;
         }
         // Only do an animation into and out-of freeform mode for now. Other mode
@@ -2897,22 +2898,12 @@
         if (!isRootTask) {
             adjustBoundsForDisplayChangeIfNeeded(dc);
         }
-        final DisplayContent prevDc = mDisplayContent;
         super.onDisplayChanged(dc);
         if (!isRootTask) {
             final int displayId = (dc != null) ? dc.getDisplayId() : INVALID_DISPLAY;
             mWmService.mAtmService.getTaskChangeNotificationController().notifyTaskDisplayChanged(
                     mTaskId, displayId);
         }
-        if (prevDc != null && prevDc.mChangingContainers.remove(this)) {
-            // This gets called *after* this has been reparented to the new display.
-            // That reparenting resulted in this window changing modes (eg. FREEFORM -> FULLSCREEN),
-            // so this token is now "frozen" while waiting for the animation to start on prevDc
-            // (which will be cancelled since the window is no-longer a child). However, since this
-            // is no longer a child of prevDc, this won't be notified of the cancelled animation,
-            // so we need to cancel the change transition here.
-            mSurfaceFreezer.unfreeze(getPendingTransaction());
-        }
     }
 
     boolean isResizeable(boolean checkSupportsPip) {
@@ -4435,19 +4426,20 @@
         // Let the old organizer know it has lost control.
         sendTaskVanished();
         mTaskOrganizer = organizer;
-        sendTaskAppeared();
-        onTaskOrganizerChanged();
-        return true;
-    }
 
-    void taskOrganizerUnregistered() {
-        mTaskOrganizer = null;
-        mTaskAppearedSent = false;
-        mLastTaskOrganizerWindowingMode = -1;
-        onTaskOrganizerChanged();
-        if (mCreatedByOrganizer) {
-            removeImmediately();
+        if (mTaskOrganizer != null) {
+            sendTaskAppeared();
+        } else {
+            // No longer managed by any organizer.
+            mTaskAppearedSent = false;
+            mLastTaskOrganizerWindowingMode = -1;
+            setForceHidden(FLAG_FORCE_HIDDEN_FOR_TASK_ORG, false /* set */);
+            if (mCreatedByOrganizer) {
+                removeImmediately();
+            }
         }
+
+        return true;
     }
 
     /**
@@ -4484,14 +4476,6 @@
         return result;
     }
 
-    private void onTaskOrganizerChanged() {
-        if (mTaskOrganizer == null) {
-            // If this task is no longer controlled by a task organizer, then reset the force hidden
-            // state
-            setForceHidden(FLAG_FORCE_HIDDEN_FOR_TASK_ORG, false /* set */);
-        }
-    }
-
     @Override
     void setSurfaceControl(SurfaceControl sc) {
         super.setSurfaceControl(sc);
diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java
index adc50bf..306c100 100644
--- a/services/core/java/com/android/server/wm/TaskOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java
@@ -218,18 +218,24 @@
         }
 
         void dispose() {
-            releaseTasks();
+            // Move organizer from managing specific windowing modes
             for (int i = mTaskOrganizersForWindowingMode.size() - 1; i >= 0; --i) {
                 mTaskOrganizersForWindowingMode.valueAt(i).remove(mOrganizer.getBinder());
             }
-        }
 
-        private void releaseTasks() {
-            for (int i = mOrganizedTasks.size() - 1; i >= 0; i--) {
-                final Task t = mOrganizedTasks.get(i);
-                removeTask(t);
-                t.taskOrganizerUnregistered();
+            // Update tasks currently managed by this organizer to the next one available if
+            // possible.
+            while (!mOrganizedTasks.isEmpty()) {
+                final Task t = mOrganizedTasks.get(0);
+                t.updateTaskOrganizerState(true /* forceUpdate */);
+                if (mOrganizedTasks.contains(t)) {
+                    removeTask(t);
+                }
             }
+
+            // Remove organizer state after removing tasks so we get a chance to send
+            // onTaskVanished.
+            mTaskOrganizerStates.remove(asBinder());
         }
 
         void unlinkDeath() {
@@ -313,16 +319,11 @@
                             new TaskOrganizerState(organizer, uid));
                 }
 
-                if (orgs.size() == 1) {
-                    // Only in the case where this is the root task organizer for the given
-                    // windowing mode, we add report all existing tasks in that mode to the new
-                    // task organizer.
-                    mService.mRootWindowContainer.forAllTasks((task) -> {
-                        if (task.getWindowingMode() == windowingMode) {
-                            task.updateTaskOrganizerState(true /* forceUpdate */);
-                        }
-                    });
-                }
+                mService.mRootWindowContainer.forAllTasks((task) -> {
+                    if (task.getWindowingMode() == windowingMode) {
+                        task.updateTaskOrganizerState(true /* forceUpdate */);
+                    }
+                });
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
@@ -335,7 +336,7 @@
         final long origId = Binder.clearCallingIdentity();
         try {
             synchronized (mGlobalLock) {
-                final TaskOrganizerState state = mTaskOrganizerStates.remove(organizer.asBinder());
+                final TaskOrganizerState state = mTaskOrganizerStates.get(organizer.asBinder());
                 if (state == null) {
                     return;
                 }
@@ -367,7 +368,9 @@
 
     void onTaskVanished(ITaskOrganizer organizer, Task task) {
         final TaskOrganizerState state = mTaskOrganizerStates.get(organizer.asBinder());
-        state.removeTask(task);
+        if (state != null) {
+            state.removeTask(task);
+        }
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/WallpaperWindowToken.java b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
index 203ca25..5f3c633 100644
--- a/services/core/java/com/android/server/wm/WallpaperWindowToken.java
+++ b/services/core/java/com/android/server/wm/WallpaperWindowToken.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
 
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYERS;
@@ -45,6 +46,7 @@
             DisplayContent dc, boolean ownerCanManageAppTokens) {
         super(service, token, TYPE_WALLPAPER, explicit, dc, ownerCanManageAppTokens);
         dc.mWallpaperController.addWallpaperToken(this);
+        setWindowingMode(WINDOWING_MODE_FULLSCREEN);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index f3e2992..591bc54 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -526,6 +526,11 @@
      */
     @CallSuper
     void removeImmediately() {
+        final DisplayContent dc = getDisplayContent();
+        if (dc != null) {
+            mSurfaceFreezer.unfreeze(getPendingTransaction());
+            dc.mChangingContainers.remove(this);
+        }
         while (!mChildren.isEmpty()) {
             final E child = mChildren.peekLast();
             child.removeImmediately();
@@ -718,6 +723,10 @@
      * @param dc The display this container is on after changes.
      */
     void onDisplayChanged(DisplayContent dc) {
+        if (mDisplayContent != null && mDisplayContent.mChangingContainers.remove(this)) {
+            // Cancel any change transition queued-up for this container on the old display.
+            mSurfaceFreezer.unfreeze(getPendingTransaction());
+        }
         mDisplayContent = dc;
         if (dc != null && dc != this) {
             dc.getPendingTransaction().merge(mPendingTransaction);
@@ -2033,6 +2042,7 @@
 
     void cancelAnimation() {
         mSurfaceAnimator.cancelAnimation();
+        mSurfaceFreezer.unfreeze(getPendingTransaction());
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 51095ee..fbdea01 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -6111,15 +6111,6 @@
                 pw.print("  mInputMethodInputTarget in display# "); pw.print(displayId);
                 pw.print(' '); pw.println(inputMethodInputTarget);
             }
-            if (mAccessibilityController != null) {
-                final Region magnificationRegion = new Region();
-                mAccessibilityController.getMagnificationRegionLocked(displayId,
-                        magnificationRegion);
-                pw.print("  mMagnificationRegion in display# ");
-                pw.print(displayId);
-                pw.print(' ');
-                pw.println(magnificationRegion);
-            }
         });
         pw.print("  mInTouchMode="); pw.println(mInTouchMode);
         pw.print("  mLastDisplayFreezeDuration=");
@@ -6135,6 +6126,9 @@
 
         mInputManagerCallback.dump(pw, "  ");
         mTaskSnapshotController.dump(pw, "  ");
+        if (mAccessibilityController != null) {
+            mAccessibilityController.dump(pw, "  ");
+        }
 
         if (dumpAll) {
             final WindowState imeWindow = mRoot.getCurrentInputMethodWindow();
@@ -7656,15 +7650,8 @@
                     Slog.w(TAG, "Cannot find window which accessibility connection is added to");
                     return;
                 }
-                try (SurfaceControl.Transaction t = new SurfaceControl.Transaction()) {
-                    t.setMetadata(
-                            state.mSurfaceControl,
-                            SurfaceControl.METADATA_ACCESSIBILITY_ID,
-                            accessibilityWindowId);
-                    t.apply();
-                } finally {
-                    SurfaceControl.closeTransaction();
-                }
+                mTransaction.setMetadata(state.mSurfaceControl,
+                        SurfaceControl.METADATA_ACCESSIBILITY_ID, accessibilityWindowId).apply();
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java
index 194ed3e..41bd707 100644
--- a/services/core/java/com/android/server/wm/WindowProcessController.java
+++ b/services/core/java/com/android/server/wm/WindowProcessController.java
@@ -40,6 +40,7 @@
 import static com.android.server.wm.ActivityTaskManagerService.KEY_DISPATCHING_TIMEOUT_MS;
 import static com.android.server.wm.ActivityTaskManagerService.RELAUNCH_REASON_NONE;
 
+import android.Manifest;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityThread;
@@ -50,6 +51,7 @@
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.ServiceInfo;
 import android.content.res.Configuration;
 import android.os.Build;
 import android.os.Message;
@@ -165,7 +167,8 @@
     // Thread currently set for VR scheduling
     int mVrThreadTid;
 
-    boolean mIsImeProcess;
+    // Whether this process has ever started a service with the BIND_INPUT_METHOD permission.
+    private volatile boolean mHasImeService;
 
     // all activities running in the process
     private final ArrayList<ActivityRecord> mActivities = new ArrayList<>();
@@ -187,6 +190,8 @@
     // Registered display id as a listener to override config change
     private int mDisplayId;
     private ActivityRecord mConfigActivityRecord;
+    // Whether the activity config override is allowed for this process.
+    private volatile boolean mIsActivityConfigOverrideAllowed = true;
     /**
      * Activities that hosts some UI drawn by the current process. The activities live
      * in another process. This is used to check if the process is currently showing anything
@@ -201,9 +206,6 @@
     /** Whether our process is currently running a {@link IRemoteAnimationRunner} */
     private boolean mRunningRemoteAnimation;
 
-    /** Whether this process is owned by the System UI package. */
-    final boolean mIsSysUiPackage;
-
     public WindowProcessController(@NonNull ActivityTaskManagerService atm, ApplicationInfo info,
             String name, int uid, int userId, Object owner, WindowProcessListener listener) {
         mInfo = info;
@@ -215,8 +217,13 @@
         mAtm = atm;
         mDisplayId = INVALID_DISPLAY;
 
-        mIsSysUiPackage = info.packageName.equals(
+        boolean isSysUiPackage = info.packageName.equals(
                 mAtm.getSysUiServiceComponentLocked().getPackageName());
+        if (isSysUiPackage || mUid == Process.SYSTEM_UID) {
+            // This is a system owned process and should not use an activity config.
+            // TODO(b/151161907): Remove after support for display-independent (raw) SysUi configs.
+            mIsActivityConfigOverrideAllowed = false;
+        }
 
         onConfigurationChanged(atm.getGlobalConfiguration());
     }
@@ -1095,9 +1102,7 @@
      * always track the configuration of the non-finishing activity last added to the process.
      */
     private void updateActivityConfigurationListener() {
-        if (mIsSysUiPackage || mUid == Process.SYSTEM_UID) {
-            // This is a system owned process and should not use an activity config.
-            // TODO(b/151161907): Remove after support for display-independent (raw) SysUi configs.
+        if (!mIsActivityConfigOverrideAllowed) {
             return;
         }
 
@@ -1132,7 +1137,7 @@
         final Configuration config = getConfiguration();
         if (mLastReportedConfiguration.diff(config) == 0) {
             // Nothing changed.
-            if (Build.IS_DEBUGGABLE && mIsImeProcess) {
+            if (Build.IS_DEBUGGABLE && mHasImeService) {
                 // TODO (b/135719017): Temporary log for debugging IME service.
                 Slog.w(TAG_CONFIGURATION, "Current config: " + config
                         + " unchanged for IME proc " + mName);
@@ -1156,7 +1161,7 @@
 
     private void dispatchConfigurationChange(Configuration config) {
         if (mThread == null) {
-            if (Build.IS_DEBUGGABLE && mIsImeProcess) {
+            if (Build.IS_DEBUGGABLE && mHasImeService) {
                 // TODO (b/135719017): Temporary log for debugging IME service.
                 Slog.w(TAG_CONFIGURATION, "Unable to send config for IME proc " + mName
                         + ": no app thread");
@@ -1166,7 +1171,7 @@
         if (DEBUG_CONFIGURATION) {
             Slog.v(TAG_CONFIGURATION, "Sending to proc " + mName + " new config " + config);
         }
-        if (Build.IS_DEBUGGABLE && mIsImeProcess) {
+        if (Build.IS_DEBUGGABLE && mHasImeService) {
             // TODO (b/135719017): Temporary log for debugging IME service.
             Slog.v(TAG_CONFIGURATION, "Sending to IME proc " + mName + " new config " + config);
         }
@@ -1286,6 +1291,35 @@
         }
     }
 
+    /**
+     * Called to notify {@link WindowProcessController} of a started service.
+     *
+     * @param serviceInfo information describing the started service.
+     */
+    public void onServiceStarted(ServiceInfo serviceInfo) {
+        String permission = serviceInfo.permission;
+        if (permission == null) {
+            return;
+        }
+
+        // TODO: Audit remaining services for disabling activity override (Wallpaper, Dream, etc).
+        switch (permission) {
+            case Manifest.permission.BIND_INPUT_METHOD:
+                mHasImeService = true;
+                // Fall-through
+            case Manifest.permission.BIND_ACCESSIBILITY_SERVICE:
+            case Manifest.permission.BIND_VOICE_INTERACTION:
+                // We want to avoid overriding the config of these services with that of the
+                // activity as it could lead to incorrect display metrics. For ex, IME services
+                // expect their config to match the config of the display with the IME window
+                // showing.
+                mIsActivityConfigOverrideAllowed = false;
+                break;
+            default:
+                break;
+        }
+    }
+
     @HotPath(caller = HotPath.OOM_ADJUSTMENT)
     public void onTopProcChanged() {
         synchronized (mAtm.mGlobalLockWithoutBoost) {
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 9957707..a2930ab 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -1476,6 +1476,7 @@
         if (dumpAll) {
             pw.print(prefix); pw.print("mDrawState="); pw.print(drawStateToString());
             pw.print(prefix); pw.print(" mLastHidden="); pw.println(mLastHidden);
+            pw.print(prefix); pw.print("mEnterAnimationPending=" + mEnterAnimationPending);
             pw.print(prefix); pw.print("mSystemDecorRect="); mSystemDecorRect.printShortString(pw);
             pw.print(" mLastClipRect="); mLastClipRect.printShortString(pw);
 
diff --git a/services/core/jni/com_android_server_tv_TvUinputBridge.cpp b/services/core/jni/com_android_server_tv_TvUinputBridge.cpp
index 6e2e2c5..99deab4 100644
--- a/services/core/jni/com_android_server_tv_TvUinputBridge.cpp
+++ b/services/core/jni/com_android_server_tv_TvUinputBridge.cpp
@@ -106,7 +106,7 @@
 static const GamepadAxis* getGamepadAxis(int32_t androidAxisCode) {
     std::unordered_map<int32_t, int>::iterator it =
             gamepadAndroidAxisToIndexMap.find(androidAxisCode);
-    if (it == gamepadAndroidToLinuxKeyMap.end()) {
+    if (it == gamepadAndroidAxisToIndexMap.end()) {
         return nullptr;
     }
     return &GAMEPAD_AXES[it->second];
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 22b0b62..5d5e424 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -161,8 +161,11 @@
 import android.content.ComponentName;
 import android.content.ContentValues;
 import android.content.Context;
+import android.content.IIntentReceiver;
+import android.content.IIntentSender;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.IntentSender;
 import android.content.PermissionChecker;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
@@ -171,6 +174,7 @@
 import android.content.pm.IPackageDataObserver;
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageInfo;
+import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.PackageManagerInternal;
@@ -399,11 +403,14 @@
 
     private static final long EXPIRATION_GRACE_PERIOD_MS = 5 * MS_PER_DAY; // 5 days, in ms
     private static final long MANAGED_PROFILE_MAXIMUM_TIME_OFF_THRESHOLD = 3 * MS_PER_DAY;
+    /** When to warn the user about the approaching work profile off deadline: 1 day before */
+    private static final long MANAGED_PROFILE_OFF_WARNING_PERIOD = 1 * MS_PER_DAY;
 
     private static final String ACTION_EXPIRED_PASSWORD_NOTIFICATION =
             "com.android.server.ACTION_EXPIRED_PASSWORD_NOTIFICATION";
 
-    private static final String ACTION_PROFILE_OFF_DEADLINE =
+    @VisibleForTesting
+    static final String ACTION_PROFILE_OFF_DEADLINE =
             "com.android.server.ACTION_PROFILE_OFF_DEADLINE";
 
     private static final String ATTR_PERMISSION_PROVIDER = "permission-provider";
@@ -645,6 +652,13 @@
 
     private static final boolean ENABLE_LOCK_GUARD = true;
 
+    /** Profile off deadline is not set or more than MANAGED_PROFILE_OFF_WARNING_PERIOD away. */
+    private static final int PROFILE_OFF_DEADLINE_DEFAULT = 0;
+    /** Profile off deadline is closer than MANAGED_PROFILE_OFF_WARNING_PERIOD. */
+    private static final int PROFILE_OFF_DEADLINE_WARNING = 1;
+    /** Profile off deadline reached, notify the user that personal apps blocked. */
+    private static final int PROFILE_OFF_DEADLINE_REACHED = 2;
+
     interface Stats {
         int LOCK_GUARD_GUARD = 0;
 
@@ -922,11 +936,12 @@
                     mUserData.remove(userHandle);
                 }
                 handlePackagesChanged(null /* check all admins */, userHandle);
+                updatePersonalAppsSuspensionOnUserStart(userHandle);
             } else if (Intent.ACTION_USER_STOPPED.equals(action)) {
                 sendDeviceOwnerUserCommand(DeviceAdminReceiver.ACTION_USER_STOPPED, userHandle);
                 if (isManagedProfile(userHandle)) {
                     Slog.d(LOG_TAG, "Managed profile was stopped");
-                    updatePersonalAppSuspension(userHandle, false /* profileIsOn */);
+                    updatePersonalAppsSuspension(userHandle, false /* unlocked */);
                 }
             } else if (Intent.ACTION_USER_SWITCHED.equals(action)) {
                 sendDeviceOwnerUserCommand(DeviceAdminReceiver.ACTION_USER_SWITCHED, userHandle);
@@ -936,7 +951,7 @@
                 }
                 if (isManagedProfile(userHandle)) {
                     Slog.d(LOG_TAG, "Managed profile became unlocked");
-                    updatePersonalAppSuspension(userHandle, true /* profileIsOn */);
+                    updatePersonalAppsSuspension(userHandle, true /* unlocked */);
                 }
             } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(action)) {
                 handlePackagesChanged(null /* check all admins */, userHandle);
@@ -963,7 +978,7 @@
                 Slog.i(LOG_TAG, "Profile off deadline alarm was triggered");
                 final int userId = getManagedUserId(UserHandle.USER_SYSTEM);
                 if (userId >= 0) {
-                    updatePersonalAppSuspension(userId, mUserManager.isUserUnlocked(userId));
+                    updatePersonalAppsSuspension(userId, mUserManager.isUserUnlocked(userId));
                 } else {
                     Slog.wtf(LOG_TAG, "Got deadline alarm for nonexistent profile");
                 }
@@ -2482,6 +2497,16 @@
         public void runCryptoSelfTest() {
             CryptoTestHelper.runAndLogSelfTest();
         }
+
+        public String[] getPersonalAppsForSuspension(int userId) {
+            return new PersonalAppsSuspensionHelper(
+                    mContext.createContextAsUser(UserHandle.of(userId), 0 /* flags */))
+                    .getPersonalAppsForSuspension();
+        }
+
+        public long systemCurrentTimeMillis() {
+            return System.currentTimeMillis();
+        }
     }
 
     /**
@@ -2704,6 +2729,8 @@
         clearDeviceOwnerLocked(doAdmin, doUserId);
         Slog.i(LOG_TAG, "Removing admin artifacts...");
         removeAdminArtifacts(doAdminReceiver, doUserId);
+        Slog.i(LOG_TAG, "Uninstalling the DO...");
+        uninstallOrDisablePackage(doAdminComponent.getPackageName(), doUserId);
         Slog.i(LOG_TAG, "Migration complete.");
 
         // Note: KeyChain keys are not removed and will remain accessible for the apps that have
@@ -2715,6 +2742,47 @@
                 .write();
     }
 
+    private void uninstallOrDisablePackage(String packageName, int userHandle) {
+        final ApplicationInfo appInfo;
+        try {
+            appInfo = mIPackageManager.getApplicationInfo(
+                    packageName, MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE, userHandle);
+        } catch (RemoteException e) {
+            // Shouldn't happen.
+            return;
+        }
+        if (appInfo == null) {
+            Slog.wtf(LOG_TAG, "Failed to get package info for " + packageName);
+            return;
+        }
+        if ((appInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
+            Slog.i(LOG_TAG, String.format(
+                    "Package %s is pre-installed, marking disabled until used", packageName));
+            mContext.getPackageManager().setApplicationEnabledSetting(packageName,
+                    PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED, 0 /* flags */);
+            return;
+        }
+
+        final IIntentSender.Stub mLocalSender = new IIntentSender.Stub() {
+            @Override
+            public void send(int code, Intent intent, String resolvedType, IBinder whitelistToken,
+                    IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) {
+                final int status = intent.getIntExtra(
+                        PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE);
+                if (status == PackageInstaller.STATUS_SUCCESS) {
+                    Slog.i(LOG_TAG, String.format(
+                            "Package %s uninstalled for user %d", packageName, userHandle));
+                } else {
+                    Slog.e(LOG_TAG, String.format(
+                            "Failed to uninstall %s; status: %d", packageName, status));
+                }
+            }
+        };
+
+        final PackageInstaller pi = mInjector.getPackageManager(userHandle).getPackageInstaller();
+        pi.uninstall(packageName, 0 /* flags */, new IntentSender((IIntentSender) mLocalSender));
+    }
+
     private void moveDoPoliciesToProfileParentAdmin(ActiveAdmin doAdmin, ActiveAdmin parentAdmin) {
         // The following policies can be already controlled via parent instance, skip if so.
         if (parentAdmin.mPasswordPolicy.quality == PASSWORD_QUALITY_UNSPECIFIED) {
@@ -3998,10 +4066,6 @@
                     applyManagedProfileRestrictionIfDeviceOwnerLocked();
                 }
                 maybeStartSecurityLogMonitorOnActivityManagerReady();
-                final int userId = getManagedUserId(UserHandle.USER_SYSTEM);
-                if (userId >= 0) {
-                    updatePersonalAppSuspension(userId, false /* running */);
-                }
                 break;
             case SystemService.PHASE_BOOT_COMPLETED:
                 ensureDeviceOwnerUserStarted(); // TODO Consider better place to do this.
@@ -4009,6 +4073,16 @@
         }
     }
 
+    private void updatePersonalAppsSuspensionOnUserStart(int userHandle) {
+        final int profileUserHandle = getManagedUserId(userHandle);
+        if (profileUserHandle >= 0) {
+            // Given that the parent user has just started, profile should be locked.
+            updatePersonalAppsSuspension(profileUserHandle, false /* unlocked */);
+        } else {
+            suspendPersonalAppsInternal(userHandle, false);
+        }
+    }
+
     private void onLockSettingsReady() {
         getUserData(UserHandle.USER_SYSTEM);
         loadOwners();
@@ -15846,11 +15920,8 @@
             }
         }
 
-        final int suspendedState = suspended
-                ? PERSONAL_APPS_SUSPENDED_EXPLICITLY
-                : PERSONAL_APPS_NOT_SUSPENDED;
-        mInjector.binderWithCleanCallingIdentity(
-                () -> applyPersonalAppsSuspension(callingUserId, suspendedState));
+        mInjector.binderWithCleanCallingIdentity(() -> updatePersonalAppsSuspension(
+                callingUserId, mUserManager.isUserUnlocked(callingUserId)));
 
         DevicePolicyEventLogger
                 .createEvent(DevicePolicyEnums.SET_PERSONAL_APPS_SUSPENDED)
@@ -15860,44 +15931,54 @@
     }
 
     /**
-     * Checks whether there is a policy that requires personal apps to be suspended and if so,
-     * applies it.
-     * @param running whether the profile is currently considered running.
+     * Checks whether personal apps should be suspended according to the policy and applies the
+     * change if needed.
+     *
+     * @param unlocked whether the profile is currently running unlocked.
      */
-    private void updatePersonalAppSuspension(int profileUserId, boolean running) {
-        final int suspensionState;
+    private void updatePersonalAppsSuspension(int profileUserId, boolean unlocked) {
+        final boolean suspended;
         synchronized (getLockObject()) {
             final ActiveAdmin profileOwner = getProfileOwnerAdminLocked(profileUserId);
             if (profileOwner != null) {
-                final boolean deadlineReached =
-                        updateProfileOffDeadlineLocked(profileUserId, profileOwner, running);
-                suspensionState = makeSuspensionReasons(
-                        profileOwner.mSuspendPersonalApps, deadlineReached);
-                Slog.d(LOG_TAG,
-                        String.format("New personal apps suspension state: %d", suspensionState));
+                final int deadlineState =
+                        updateProfileOffDeadlineLocked(profileUserId, profileOwner, unlocked);
+                suspended = profileOwner.mSuspendPersonalApps
+                        || deadlineState == PROFILE_OFF_DEADLINE_REACHED;
+                Slog.d(LOG_TAG, String.format("Personal apps suspended: %b, deadline state: %d",
+                            suspended, deadlineState));
+                updateProfileOffDeadlineNotificationLocked(profileUserId, profileOwner,
+                        unlocked ? PROFILE_OFF_DEADLINE_DEFAULT : deadlineState);
             } else {
-                suspensionState = PERSONAL_APPS_NOT_SUSPENDED;
+                suspended = false;
             }
         }
 
-        applyPersonalAppsSuspension(profileUserId, suspensionState);
+        final int parentUserId = getProfileParentId(profileUserId);
+        suspendPersonalAppsInternal(parentUserId, suspended);
     }
 
     /**
      * Checks work profile time off policy, scheduling personal apps suspension via alarm if
      * necessary.
-     * @return whether the apps should be suspended based on maximum time off policy.
+     * @return profile deadline state
      */
-    private boolean updateProfileOffDeadlineLocked(
+    private int updateProfileOffDeadlineLocked(
             int profileUserId, ActiveAdmin profileOwner, boolean unlocked) {
-        final long now = System.currentTimeMillis();
+        final long now = mInjector.systemCurrentTimeMillis();
         if (profileOwner.mProfileOffDeadline != 0 && now > profileOwner.mProfileOffDeadline) {
             // Profile off deadline is already reached.
             Slog.i(LOG_TAG, "Profile off deadline has been reached.");
-            return true;
+            return PROFILE_OFF_DEADLINE_REACHED;
         }
         boolean shouldSaveSettings = false;
-        if (profileOwner.mProfileOffDeadline != 0
+        if (profileOwner.mSuspendPersonalApps) {
+            // When explicit suspension is active, deadline shouldn't be set.
+            if (profileOwner.mProfileOffDeadline != 0) {
+                profileOwner.mProfileOffDeadline = 0;
+                shouldSaveSettings = true;
+            }
+        } else if (profileOwner.mProfileOffDeadline != 0
                 && (profileOwner.mProfileMaximumTimeOffMillis == 0 || unlocked)) {
             // There is a deadline but either there is no policy or the profile is unlocked -> clear
             // the deadline.
@@ -15913,52 +15994,51 @@
             shouldSaveSettings = true;
         }
 
-        updateProfileOffAlarm(profileOwner.mProfileOffDeadline);
-
         if (shouldSaveSettings) {
             saveSettingsLocked(profileUserId);
         }
-        return false;
-    }
 
-    private void updateProfileOffAlarm(long profileOffDeadline) {
+        final long alarmTime;
+        final int deadlineState;
+        if (profileOwner.mProfileOffDeadline == 0) {
+            alarmTime = 0;
+            deadlineState = PROFILE_OFF_DEADLINE_DEFAULT;
+        } else if (profileOwner.mProfileOffDeadline - now < MANAGED_PROFILE_OFF_WARNING_PERIOD) {
+            // The deadline is close, upon the alarm personal apps should be suspended.
+            alarmTime = profileOwner.mProfileOffDeadline;
+            deadlineState = PROFILE_OFF_DEADLINE_WARNING;
+        } else {
+            // The deadline is quite far, upon the alarm we should warn the user first, so the
+            // alarm is scheduled earlier than the actual deadline.
+            alarmTime = profileOwner.mProfileOffDeadline - MANAGED_PROFILE_OFF_WARNING_PERIOD;
+            deadlineState = PROFILE_OFF_DEADLINE_DEFAULT;
+        }
+
         final AlarmManager am = mInjector.getAlarmManager();
         final PendingIntent pi = mInjector.pendingIntentGetBroadcast(
                 mContext, REQUEST_PROFILE_OFF_DEADLINE, new Intent(ACTION_PROFILE_OFF_DEADLINE),
                 PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_UPDATE_CURRENT);
-        am.cancel(pi);
-        if (profileOffDeadline != 0) {
-            Slog.i(LOG_TAG, "Profile off deadline alarm is set.");
-            am.set(AlarmManager.RTC, profileOffDeadline, pi);
-        } else {
+
+        if (alarmTime == 0) {
             Slog.i(LOG_TAG, "Profile off deadline alarm is removed.");
-        }
-    }
-
-    private void applyPersonalAppsSuspension(
-            int profileUserId, @PersonalAppsSuspensionReason int suspensionState) {
-        final boolean suspended = getUserData(UserHandle.USER_SYSTEM).mAppsSuspended;
-        final boolean shouldSuspend = suspensionState != PERSONAL_APPS_NOT_SUSPENDED;
-        if (suspended != shouldSuspend) {
-            suspendPersonalAppsInternal(shouldSuspend, UserHandle.USER_SYSTEM);
-        }
-
-        if (suspensionState == PERSONAL_APPS_SUSPENDED_PROFILE_TIMEOUT) {
-            sendPersonalAppsSuspendedNotification(profileUserId);
+            am.cancel(pi);
         } else {
-            clearPersonalAppsSuspendedNotification();
+            Slog.i(LOG_TAG, "Profile off deadline alarm is set.");
+            am.set(AlarmManager.RTC, alarmTime, pi);
         }
+
+        return deadlineState;
     }
 
-    private void suspendPersonalAppsInternal(boolean suspended, int userId) {
+    private void suspendPersonalAppsInternal(int userId, boolean suspended) {
+        if (getUserData(userId).mAppsSuspended == suspended) {
+            return;
+        }
         Slog.i(LOG_TAG, String.format("%s personal apps for user %d",
                 suspended ? "Suspending" : "Unsuspending", userId));
         mInjector.binderWithCleanCallingIdentity(() -> {
             try {
-                final String[] appsToSuspend =
-                        new PersonalAppsSuspensionHelper(
-                                mContext.createContextAsUser(UserHandle.of(userId), 0 /* flags */))
-                                .getPersonalAppsForSuspension();
+                final String[] appsToSuspend = mInjector.getPersonalAppsForSuspension(userId);
                 final String[] failedPackages = mIPackageManager.setPackagesSuspendedAsUser(
                         appsToSuspend, suspended, null, null, null, PLATFORM_PACKAGE_NAME, userId);
                 if (!ArrayUtils.isEmpty(failedPackages)) {
@@ -15977,37 +16057,38 @@
         }
     }
 
-    private void clearPersonalAppsSuspendedNotification() {
-        mInjector.binderWithCleanCallingIdentity(() ->
-                mInjector.getNotificationManager().cancel(
-                        SystemMessage.NOTE_PERSONAL_APPS_SUSPENDED));
-    }
+    private void updateProfileOffDeadlineNotificationLocked(int profileUserId,
+            @Nullable ActiveAdmin profileOwner, int notificationState) {
 
-    private void sendPersonalAppsSuspendedNotification(int userId) {
-        final String profileOwnerPackageName;
-        final long maxTimeOffDays;
-        synchronized (getLockObject()) {
-            profileOwnerPackageName = mOwners.getProfileOwnerComponent(userId).getPackageName();
-            final ActiveAdmin poAdmin = getProfileOwnerAdminLocked(userId);
-            maxTimeOffDays = TimeUnit.MILLISECONDS.toDays(poAdmin.mProfileMaximumTimeOffMillis);
+        if (notificationState == PROFILE_OFF_DEADLINE_DEFAULT) {
+            mInjector.getNotificationManager().cancel(SystemMessage.NOTE_PERSONAL_APPS_SUSPENDED);
+            return;
         }
 
+        final String profileOwnerPackageName = profileOwner.info.getPackageName();
+        final long maxTimeOffDays =
+                TimeUnit.MILLISECONDS.toDays(profileOwner.mProfileMaximumTimeOffMillis);
+
         final Intent intent = new Intent(DevicePolicyManager.ACTION_CHECK_POLICY_COMPLIANCE);
         intent.setPackage(profileOwnerPackageName);
 
         final PendingIntent pendingIntent = mInjector.pendingIntentGetActivityAsUser(mContext,
-                0 /* requestCode */, intent, PendingIntent.FLAG_UPDATE_CURRENT, null /* options */,
-                UserHandle.of(userId));
+                0 /* requestCode */, intent, PendingIntent.FLAG_UPDATE_CURRENT,
+                null /* options */, UserHandle.of(profileUserId));
+
+        // TODO(b/149075510): Only the first of the notifications should be dismissible.
+        final String title = mContext.getString(
+                notificationState == PROFILE_OFF_DEADLINE_WARNING
+                ? R.string.personal_apps_suspended_tomorrow_title
+                : R.string.personal_apps_suspended_title);
 
         final Notification notification =
                 new Notification.Builder(mContext, SystemNotificationChannels.DEVICE_ADMIN)
                         .setSmallIcon(android.R.drawable.stat_sys_warning)
                         .setOngoing(true)
-                        .setContentTitle(
-                                mContext.getString(
-                                        R.string.personal_apps_suspended_title))
+                        .setContentTitle(title)
                         .setContentText(mContext.getString(
-                                R.string.personal_apps_suspended_text, maxTimeOffDays))
+                            R.string.personal_apps_suspended_text, maxTimeOffDays))
                         .setColor(mContext.getColor(R.color.system_notification_accent_color))
                         .setContentIntent(pendingIntent)
                         .build();
@@ -16039,7 +16120,7 @@
         }
 
         mInjector.binderWithCleanCallingIdentity(
-                () -> updatePersonalAppSuspension(userId, mUserManager.isUserUnlocked()));
+                () -> updatePersonalAppsSuspension(userId, mUserManager.isUserUnlocked()));
 
         DevicePolicyEventLogger
                 .createEvent(DevicePolicyEnums.SET_MANAGED_PROFILE_MAXIMUM_TIME_OFF)
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java b/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java
index d9db17e..da716ea 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PersonalAppsSuspensionHelper.java
@@ -51,6 +51,10 @@
 public class PersonalAppsSuspensionHelper {
     private static final String LOG_TAG = DevicePolicyManagerService.LOG_TAG;
 
+    // Flags to get all packages even if the user is still locked.
+    private static final int PACKAGE_QUERY_FLAGS =
+            PackageManager.MATCH_DIRECT_BOOT_AWARE | PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
+
     private final Context mContext;
     private final PackageManager mPackageManager;
 
@@ -67,7 +71,7 @@
      */
     String[] getPersonalAppsForSuspension() {
         final List<PackageInfo> installedPackageInfos =
-                mPackageManager.getInstalledPackages(0 /* flags */);
+                mPackageManager.getInstalledPackages(PACKAGE_QUERY_FLAGS);
         final Set<String> result = new ArraySet<>();
         for (final PackageInfo packageInfo : installedPackageInfos) {
             final ApplicationInfo info = packageInfo.applicationInfo;
@@ -97,7 +101,7 @@
         final Intent intent = new Intent(Intent.ACTION_MAIN);
         intent.addCategory(Intent.CATEGORY_HOME);
         final List<ResolveInfo> matchingActivities =
-                mPackageManager.queryIntentActivities(intent, 0);
+                mPackageManager.queryIntentActivities(intent, PACKAGE_QUERY_FLAGS);
         for (final ResolveInfo resolveInfo : matchingActivities) {
             if (resolveInfo.activityInfo == null
                     || TextUtils.isEmpty(resolveInfo.activityInfo.packageName)) {
@@ -107,7 +111,7 @@
             final String packageName = resolveInfo.activityInfo.packageName;
             try {
                 final ApplicationInfo applicationInfo =
-                        mPackageManager.getApplicationInfo(packageName, 0);
+                        mPackageManager.getApplicationInfo(packageName, PACKAGE_QUERY_FLAGS);
                 if (applicationInfo.isSystemApp() || applicationInfo.isUpdatedSystemApp()) {
                     result.add(packageName);
                 }
@@ -147,7 +151,8 @@
     private String getSettingsPackageName() {
         final Intent intent = new Intent(Settings.ACTION_SETTINGS);
         intent.addCategory(Intent.CATEGORY_DEFAULT);
-        final ResolveInfo resolveInfo = mPackageManager.resolveActivity(intent, /* flags= */ 0);
+        final ResolveInfo resolveInfo =
+                mPackageManager.resolveActivity(intent, PACKAGE_QUERY_FLAGS);
         if (resolveInfo != null) {
             return resolveInfo.activityInfo.packageName;
         }
@@ -164,7 +169,7 @@
         intentToResolve.addCategory(Intent.CATEGORY_LAUNCHER);
         intentToResolve.setPackage(packageName);
         final List<ResolveInfo> resolveInfos =
-                mPackageManager.queryIntentActivities(intentToResolve, /* flags= */ 0);
+                mPackageManager.queryIntentActivities(intentToResolve, PACKAGE_QUERY_FLAGS);
         return resolveInfos != null && !resolveInfos.isEmpty();
     }
 
diff --git a/services/incremental/Android.bp b/services/incremental/Android.bp
index b13d330..b8bd3b4 100644
--- a/services/incremental/Android.bp
+++ b/services/incremental/Android.bp
@@ -62,6 +62,7 @@
     srcs: [
         "incremental_service.c",
         "IncrementalService.cpp",
+        "IncrementalServiceValidation.cpp",
         "BinderIncrementalService.cpp",
         "path.cpp",
         "ServiceWrappers.cpp",
diff --git a/services/incremental/BinderIncrementalService.cpp b/services/incremental/BinderIncrementalService.cpp
index fc8c6fe..8476674 100644
--- a/services/incremental/BinderIncrementalService.cpp
+++ b/services/incremental/BinderIncrementalService.cpp
@@ -18,6 +18,7 @@
 
 #include <android-base/logging.h>
 #include <android-base/no_destructor.h>
+#include <android/os/IVold.h>
 #include <binder/IResultReceiver.h>
 #include <binder/PermissionCache.h>
 #include <incfs.h>
@@ -117,11 +118,12 @@
 }
 
 binder::Status BinderIncrementalService::createStorage(
-        const std::string& path, const DataLoaderParamsParcel& params,
+        const std::string& path, const content::pm::DataLoaderParamsParcel& params,
         const ::android::sp<::android::content::pm::IDataLoaderStatusListener>& listener,
         int32_t createMode, int32_t* _aidl_return) {
     *_aidl_return =
-            mImpl.createStorage(path, const_cast<DataLoaderParamsParcel&&>(params), listener,
+            mImpl.createStorage(path, const_cast<content::pm::DataLoaderParamsParcel&&>(params),
+                                listener,
                                 android::incremental::IncrementalService::CreateOptions(
                                         createMode));
     return ok();
@@ -238,11 +240,8 @@
 binder::Status BinderIncrementalService::getMetadataByPath(int32_t storageId,
                                                            const std::string& path,
                                                            std::vector<uint8_t>* _aidl_return) {
-    auto fid = mImpl.nodeFor(storageId, path);
-    if (fid != kIncFsInvalidFileId) {
-        auto metadata = mImpl.getMetadata(storageId, fid);
-        _aidl_return->assign(metadata.begin(), metadata.end());
-    }
+    auto metadata = mImpl.getMetadata(storageId, path);
+    _aidl_return->assign(metadata.begin(), metadata.end());
     return ok();
 }
 
diff --git a/services/incremental/IncrementalService.cpp b/services/incremental/IncrementalService.cpp
index f423119..0641872 100644
--- a/services/incremental/IncrementalService.cpp
+++ b/services/incremental/IncrementalService.cpp
@@ -18,32 +18,26 @@
 
 #include "IncrementalService.h"
 
-#include <android-base/file.h>
 #include <android-base/logging.h>
 #include <android-base/no_destructor.h>
 #include <android-base/properties.h>
 #include <android-base/stringprintf.h>
-#include <android-base/strings.h>
-#include <android/content/pm/IDataLoaderStatusListener.h>
-#include <android/os/IVold.h>
-#include <binder/BinderService.h>
+#include <binder/AppOpsManager.h>
 #include <binder/Nullable.h>
-#include <binder/ParcelFileDescriptor.h>
 #include <binder/Status.h>
 #include <sys/stat.h>
 #include <uuid/uuid.h>
 
 #include <charconv>
 #include <ctime>
-#include <filesystem>
 #include <iterator>
 #include <span>
 #include <type_traits>
 
+#include "IncrementalServiceValidation.h"
 #include "Metadata.pb.h"
 
 using namespace std::literals;
-using namespace android::content::pm;
 namespace fs = std::filesystem;
 
 constexpr const char* kDataUsageStats = "android.permission.LOADER_USAGE_STATS";
@@ -51,10 +45,13 @@
 
 namespace android::incremental {
 
+using content::pm::DataLoaderParamsParcel;
+using content::pm::FileSystemControlParcel;
+using content::pm::IDataLoader;
+
 namespace {
 
-using IncrementalFileSystemControlParcel =
-        ::android::os::incremental::IncrementalFileSystemControlParcel;
+using IncrementalFileSystemControlParcel = os::incremental::IncrementalFileSystemControlParcel;
 
 struct Constants {
     static constexpr auto backing = "backing_store"sv;
@@ -105,10 +102,13 @@
     if (path::isAbsolute(path)) {
         path.remove_prefix(1);
     }
+    if (path.size() > 16) {
+        path = path.substr(0, 16);
+    }
     std::string res(path);
-    std::replace(res.begin(), res.end(), '/', '_');
-    std::replace(res.begin(), res.end(), '@', '_');
-    return std::string(constants().mountKeyPrefix) + res;
+    std::replace_if(
+            res.begin(), res.end(), [](char c) { return c == '/' || c == '@'; }, '_');
+    return std::string(constants().mountKeyPrefix) += res;
 }
 
 static std::pair<std::string, std::string> makeMountDir(std::string_view incrementalDir,
@@ -125,8 +125,26 @@
     return {};
 }
 
+template <class Map>
+typename Map::const_iterator findParentPath(const Map& map, std::string_view path) {
+    const auto nextIt = map.upper_bound(path);
+    if (nextIt == map.begin()) {
+        return map.end();
+    }
+    const auto suspectIt = std::prev(nextIt);
+    if (!path::startsWith(path, suspectIt->first)) {
+        return map.end();
+    }
+    return suspectIt;
+}
+
+static base::unique_fd dup(base::borrowed_fd fd) {
+    const auto res = fcntl(fd.get(), F_DUPFD_CLOEXEC, 0);
+    return base::unique_fd(res);
+}
+
 template <class ProtoMessage, class Control>
-static ProtoMessage parseFromIncfs(const IncFsWrapper* incfs, Control&& control,
+static ProtoMessage parseFromIncfs(const IncFsWrapper* incfs, const Control& control,
                                    std::string_view path) {
     auto md = incfs->getMetadata(control, path);
     ProtoMessage message;
@@ -155,20 +173,18 @@
 }
 } // namespace
 
-const bool IncrementalService::sEnablePerfLogging =
-        android::base::GetBoolProperty("incremental.perflogging", false);
-
 IncrementalService::IncFsMount::~IncFsMount() {
     if (dataLoaderStub) {
         dataLoaderStub->cleanupResources();
         dataLoaderStub = {};
     }
+    control.close();
     LOG(INFO) << "Unmounting and cleaning up mount " << mountId << " with root '" << root << '\'';
     for (auto&& [target, _] : bindPoints) {
-        LOG(INFO) << "\tbind: " << target;
+        LOG(INFO) << "  bind: " << target;
         incrementalService.mVold->unmountIncFs(target);
     }
-    LOG(INFO) << "\troot: " << root;
+    LOG(INFO) << "  root: " << root;
     incrementalService.mVold->unmountIncFs(path::join(root, constants().mount));
     cleanupFilesystem(root);
 }
@@ -193,8 +209,18 @@
     return storages.end();
 }
 
-static std::unique_ptr<DIR, decltype(&::closedir)> openDir(const char* path) {
-    return {::opendir(path), ::closedir};
+template <class Func>
+static auto makeCleanup(Func&& f) {
+    auto deleter = [f = std::move(f)](auto) { f(); };
+    return std::unique_ptr<Func, decltype(deleter)>(&f, std::move(deleter));
+}
+
+static std::unique_ptr<DIR, decltype(&::closedir)> openDir(const char* dir) {
+    return {::opendir(dir), ::closedir};
+}
+
+static auto openDir(std::string_view dir) {
+    return openDir(path::c_str(dir));
 }
 
 static int rmDirContent(const char* path) {
@@ -206,7 +232,7 @@
         if (entry->d_name == "."sv || entry->d_name == ".."sv) {
             continue;
         }
-        auto fullPath = android::base::StringPrintf("%s/%s", path, entry->d_name);
+        auto fullPath = base::StringPrintf("%s/%s", path, entry->d_name);
         if (entry->d_type == DT_DIR) {
             if (const auto err = rmDirContent(fullPath.c_str()); err != 0) {
                 PLOG(WARNING) << "Failed to delete " << fullPath << " content";
@@ -256,7 +282,8 @@
         runJobProcessing();
     });
 
-    mountExistingImages();
+    const auto mountedRootNames = adoptMountedInstances();
+    mountExistingImages(mountedRootNames);
 }
 
 IncrementalService::~IncrementalService() {
@@ -268,15 +295,7 @@
     mJobProcessor.join();
 }
 
-inline const char* toString(TimePoint t) {
-    using SystemClock = std::chrono::system_clock;
-    time_t time = SystemClock::to_time_t(
-            SystemClock::now() +
-            std::chrono::duration_cast<SystemClock::duration>(t - Clock::now()));
-    return std::ctime(&time);
-}
-
-inline const char* toString(IncrementalService::BindKind kind) {
+static const char* toString(IncrementalService::BindKind kind) {
     switch (kind) {
         case IncrementalService::BindKind::Temporary:
             return "Temporary";
@@ -291,38 +310,48 @@
 
     std::unique_lock l(mLock);
 
-    dprintf(fd, "Mounts (%d):\n", int(mMounts.size()));
+    dprintf(fd, "Mounts (%d): {\n", int(mMounts.size()));
     for (auto&& [id, ifs] : mMounts) {
-        const IncFsMount& mnt = *ifs.get();
-        dprintf(fd, "\t[%d]:\n", id);
-        dprintf(fd, "\t\tmountId: %d\n", mnt.mountId);
-        dprintf(fd, "\t\troot: %s\n", mnt.root.c_str());
-        dprintf(fd, "\t\tnextStorageDirNo: %d\n", mnt.nextStorageDirNo.load());
-        if (mnt.dataLoaderStub) {
-            mnt.dataLoaderStub->onDump(fd);
-        }
-        dprintf(fd, "\t\tstorages (%d):\n", int(mnt.storages.size()));
-        for (auto&& [storageId, storage] : mnt.storages) {
-            dprintf(fd, "\t\t\t[%d] -> [%s]\n", storageId, storage.name.c_str());
-        }
+        const IncFsMount& mnt = *ifs;
+        dprintf(fd, "  [%d]: {\n", id);
+        if (id != mnt.mountId) {
+            dprintf(fd, "    reference to mountId: %d\n", mnt.mountId);
+        } else {
+            dprintf(fd, "    mountId: %d\n", mnt.mountId);
+            dprintf(fd, "    root: %s\n", mnt.root.c_str());
+            dprintf(fd, "    nextStorageDirNo: %d\n", mnt.nextStorageDirNo.load());
+            if (mnt.dataLoaderStub) {
+                mnt.dataLoaderStub->onDump(fd);
+            } else {
+                dprintf(fd, "    dataLoader: null\n");
+            }
+            dprintf(fd, "    storages (%d): {\n", int(mnt.storages.size()));
+            for (auto&& [storageId, storage] : mnt.storages) {
+                dprintf(fd, "      [%d] -> [%s]\n", storageId, storage.name.c_str());
+            }
+            dprintf(fd, "    }\n");
 
-        dprintf(fd, "\t\tbindPoints (%d):\n", int(mnt.bindPoints.size()));
-        for (auto&& [target, bind] : mnt.bindPoints) {
-            dprintf(fd, "\t\t\t[%s]->[%d]:\n", target.c_str(), bind.storage);
-            dprintf(fd, "\t\t\t\tsavedFilename: %s\n", bind.savedFilename.c_str());
-            dprintf(fd, "\t\t\t\tsourceDir: %s\n", bind.sourceDir.c_str());
-            dprintf(fd, "\t\t\t\tkind: %s\n", toString(bind.kind));
+            dprintf(fd, "    bindPoints (%d): {\n", int(mnt.bindPoints.size()));
+            for (auto&& [target, bind] : mnt.bindPoints) {
+                dprintf(fd, "      [%s]->[%d]:\n", target.c_str(), bind.storage);
+                dprintf(fd, "        savedFilename: %s\n", bind.savedFilename.c_str());
+                dprintf(fd, "        sourceDir: %s\n", bind.sourceDir.c_str());
+                dprintf(fd, "        kind: %s\n", toString(bind.kind));
+            }
+            dprintf(fd, "    }\n");
         }
+        dprintf(fd, "  }\n");
     }
-
-    dprintf(fd, "Sorted binds (%d):\n", int(mBindsByPath.size()));
+    dprintf(fd, "}\n");
+    dprintf(fd, "Sorted binds (%d): {\n", int(mBindsByPath.size()));
     for (auto&& [target, mountPairIt] : mBindsByPath) {
         const auto& bind = mountPairIt->second;
-        dprintf(fd, "\t\t[%s]->[%d]:\n", target.c_str(), bind.storage);
-        dprintf(fd, "\t\t\tsavedFilename: %s\n", bind.savedFilename.c_str());
-        dprintf(fd, "\t\t\tsourceDir: %s\n", bind.sourceDir.c_str());
-        dprintf(fd, "\t\t\tkind: %s\n", toString(bind.kind));
+        dprintf(fd, "    [%s]->[%d]:\n", target.c_str(), bind.storage);
+        dprintf(fd, "      savedFilename: %s\n", bind.savedFilename.c_str());
+        dprintf(fd, "      sourceDir: %s\n", bind.sourceDir.c_str());
+        dprintf(fd, "      kind: %s\n", toString(bind.kind));
     }
+    dprintf(fd, "}\n");
 }
 
 void IncrementalService::onSystemReady() {
@@ -471,9 +500,9 @@
         metadata::Mount m;
         m.mutable_storage()->set_id(ifs->mountId);
         m.mutable_loader()->set_type((int)dataLoaderParams.type);
-        m.mutable_loader()->set_package_name(dataLoaderParams.packageName);
-        m.mutable_loader()->set_class_name(dataLoaderParams.className);
-        m.mutable_loader()->set_arguments(dataLoaderParams.arguments);
+        m.mutable_loader()->set_allocated_package_name(&dataLoaderParams.packageName);
+        m.mutable_loader()->set_allocated_class_name(&dataLoaderParams.className);
+        m.mutable_loader()->set_allocated_arguments(&dataLoaderParams.arguments);
         const auto metadata = m.SerializeAsString();
         m.mutable_loader()->release_arguments();
         m.mutable_loader()->release_class_name();
@@ -528,7 +557,7 @@
     }
 
     std::unique_lock l(mLock);
-    const auto& ifs = getIfsLocked(linkedStorage);
+    auto ifs = getIfsLocked(linkedStorage);
     if (!ifs) {
         LOG(ERROR) << "Ifs unavailable";
         return kInvalidStorageId;
@@ -552,6 +581,8 @@
                                 bk, l);
         err < 0) {
         LOG(ERROR) << "bindMount failed with error: " << err;
+        (void)mIncFs->unlink(ifs->control, storageIt->second.name);
+        ifs->storages.erase(storageIt);
         return kInvalidStorageId;
     }
 
@@ -561,15 +592,7 @@
 
 IncrementalService::BindPathMap::const_iterator IncrementalService::findStorageLocked(
         std::string_view path) const {
-    auto bindPointIt = mBindsByPath.upper_bound(path);
-    if (bindPointIt == mBindsByPath.begin()) {
-        return mBindsByPath.end();
-    }
-    --bindPointIt;
-    if (!path::startsWith(path, bindPointIt->first)) {
-        return mBindsByPath.end();
-    }
-    return bindPointIt;
+    return findParentPath(mBindsByPath, path);
 }
 
 StorageId IncrementalService::findStorageId(std::string_view path) const {
@@ -611,13 +634,12 @@
 }
 
 binder::Status IncrementalService::applyStorageParams(IncFsMount& ifs, bool enableReadLogs) {
-    using unique_fd = ::android::base::unique_fd;
-    ::android::os::incremental::IncrementalFileSystemControlParcel control;
-    control.cmd.reset(unique_fd(dup(ifs.control.cmd())));
-    control.pendingReads.reset(unique_fd(dup(ifs.control.pendingReads())));
+    os::incremental::IncrementalFileSystemControlParcel control;
+    control.cmd.reset(dup(ifs.control.cmd()));
+    control.pendingReads.reset(dup(ifs.control.pendingReads()));
     auto logsFd = ifs.control.logs();
     if (logsFd >= 0) {
-        control.log.reset(unique_fd(dup(logsFd)));
+        control.log.reset(dup(logsFd));
     }
 
     std::lock_guard l(mMountOperationLock);
@@ -664,38 +686,6 @@
     return findStorageId(path::normalize(pathInMount));
 }
 
-FileId IncrementalService::nodeFor(StorageId storage, std::string_view subpath) const {
-    const auto ifs = getIfs(storage);
-    if (!ifs) {
-        return kIncFsInvalidFileId;
-    }
-    std::unique_lock l(ifs->lock);
-    auto storageIt = ifs->storages.find(storage);
-    if (storageIt == ifs->storages.end()) {
-        return kIncFsInvalidFileId;
-    }
-    if (subpath.empty() || subpath == "."sv) {
-        return kIncFsInvalidFileId;
-    }
-    auto path = path::join(ifs->root, constants().mount, storageIt->second.name, subpath);
-    l.unlock();
-    return mIncFs->getFileId(ifs->control, path);
-}
-
-std::pair<FileId, std::string_view> IncrementalService::parentAndNameFor(
-        StorageId storage, std::string_view subpath) const {
-    auto name = path::basename(subpath);
-    if (name.empty()) {
-        return {kIncFsInvalidFileId, {}};
-    }
-    auto dir = path::dirname(subpath);
-    if (dir.empty() || dir == "/"sv) {
-        return {kIncFsInvalidFileId, {}};
-    }
-    auto id = nodeFor(storage, dir);
-    return {id, name};
-}
-
 IncrementalService::IfsMountPtr IncrementalService::getIfs(StorageId storage) const {
     std::lock_guard l(mLock);
     return getIfsLocked(storage);
@@ -704,7 +694,7 @@
 const IncrementalService::IfsMountPtr& IncrementalService::getIfsLocked(StorageId storage) const {
     auto it = mMounts.find(storage);
     if (it == mMounts.end()) {
-        static const android::base::NoDestructor<IfsMountPtr> kEmpty{};
+        static const base::NoDestructor<IfsMountPtr> kEmpty{};
         return *kEmpty;
     }
     return it->second;
@@ -713,21 +703,25 @@
 int IncrementalService::bind(StorageId storage, std::string_view source, std::string_view target,
                              BindKind kind) {
     if (!isValidMountTarget(target)) {
+        LOG(ERROR) << __func__ << ": not a valid bind target " << target;
         return -EINVAL;
     }
 
     const auto ifs = getIfs(storage);
     if (!ifs) {
+        LOG(ERROR) << __func__ << ": no ifs object for storage " << storage;
         return -EINVAL;
     }
 
     std::unique_lock l(ifs->lock);
     const auto storageInfo = ifs->storages.find(storage);
     if (storageInfo == ifs->storages.end()) {
+        LOG(ERROR) << "no storage";
         return -EINVAL;
     }
-    std::string normSource = normalizePathToStorageLocked(storageInfo, source);
+    std::string normSource = normalizePathToStorageLocked(ifs, storageInfo, source);
     if (normSource.empty()) {
+        LOG(ERROR) << "invalid source path";
         return -EINVAL;
     }
     l.unlock();
@@ -779,27 +773,32 @@
 }
 
 std::string IncrementalService::normalizePathToStorageLocked(
-        IncFsMount::StorageMap::iterator storageIt, std::string_view path) {
-    std::string normPath;
-    if (path::isAbsolute(path)) {
-        normPath = path::normalize(path);
-        if (!path::startsWith(normPath, storageIt->second.name)) {
-            return {};
-        }
-    } else {
-        normPath = path::normalize(path::join(storageIt->second.name, path));
+        const IfsMountPtr& incfs, IncFsMount::StorageMap::iterator storageIt,
+        std::string_view path) const {
+    if (!path::isAbsolute(path)) {
+        return path::normalize(path::join(storageIt->second.name, path));
     }
-    return normPath;
+    auto normPath = path::normalize(path);
+    if (path::startsWith(normPath, storageIt->second.name)) {
+        return normPath;
+    }
+    // not that easy: need to find if any of the bind points match
+    const auto bindIt = findParentPath(incfs->bindPoints, normPath);
+    if (bindIt == incfs->bindPoints.end()) {
+        return {};
+    }
+    return path::join(bindIt->second.sourceDir, path::relativize(bindIt->first, normPath));
 }
 
 std::string IncrementalService::normalizePathToStorage(const IncrementalService::IfsMountPtr& ifs,
-                                                       StorageId storage, std::string_view path) {
+                                                       StorageId storage,
+                                                       std::string_view path) const {
     std::unique_lock l(ifs->lock);
     const auto storageInfo = ifs->storages.find(storage);
     if (storageInfo == ifs->storages.end()) {
         return {};
     }
-    return normalizePathToStorageLocked(storageInfo, path);
+    return normalizePathToStorageLocked(ifs, storageInfo, path);
 }
 
 int IncrementalService::makeFile(StorageId storage, std::string_view path, int mode, FileId id,
@@ -844,7 +843,8 @@
     auto err = mIncFs->makeDir(ifs->control, normPath, mode);
     if (err == -EEXIST) {
         return 0;
-    } else if (err != -ENOENT) {
+    }
+    if (err != -ENOENT) {
         return err;
     }
     if (auto err = makeDirs(storageId, path::dirname(normPath), mode)) {
@@ -855,17 +855,22 @@
 
 int IncrementalService::link(StorageId sourceStorageId, std::string_view oldPath,
                              StorageId destStorageId, std::string_view newPath) {
-    auto ifsSrc = getIfs(sourceStorageId);
-    auto ifsDest = sourceStorageId == destStorageId ? ifsSrc : getIfs(destStorageId);
-    if (ifsSrc && ifsSrc == ifsDest) {
-        std::string normOldPath = normalizePathToStorage(ifsSrc, sourceStorageId, oldPath);
-        std::string normNewPath = normalizePathToStorage(ifsDest, destStorageId, newPath);
-        if (normOldPath.empty() || normNewPath.empty()) {
-            return -EINVAL;
-        }
-        return mIncFs->link(ifsSrc->control, normOldPath, normNewPath);
+    std::unique_lock l(mLock);
+    auto ifsSrc = getIfsLocked(sourceStorageId);
+    if (!ifsSrc) {
+        return -EINVAL;
     }
-    return -EINVAL;
+    if (sourceStorageId != destStorageId && getIfsLocked(destStorageId) != ifsSrc) {
+        return -EINVAL;
+    }
+    l.unlock();
+    std::string normOldPath = normalizePathToStorage(ifsSrc, sourceStorageId, oldPath);
+    std::string normNewPath = normalizePathToStorage(ifsSrc, destStorageId, newPath);
+    if (normOldPath.empty() || normNewPath.empty()) {
+        LOG(ERROR) << "Invalid paths in link(): " << normOldPath << " | " << normNewPath;
+        return -EINVAL;
+    }
+    return mIncFs->link(ifsSrc->control, normOldPath, normNewPath);
 }
 
 int IncrementalService::unlink(StorageId storage, std::string_view path) {
@@ -881,10 +886,12 @@
                                      std::string&& target, BindKind kind,
                                      std::unique_lock<std::mutex>& mainLock) {
     if (!isValidMountTarget(target)) {
+        LOG(ERROR) << __func__ << ": invalid mount target " << target;
         return -EINVAL;
     }
 
     std::string mdFileName;
+    std::string metadataFullPath;
     if (kind != BindKind::Temporary) {
         metadata::BindPoint bp;
         bp.set_storage_id(storage);
@@ -894,17 +901,21 @@
         bp.release_dest_path();
         bp.release_source_subdir();
         mdFileName = makeBindMdName();
-        auto node =
-                mIncFs->makeFile(ifs.control, path::join(ifs.root, constants().mount, mdFileName),
-                                 0444, idFromMetadata(metadata),
-                                 {.metadata = {metadata.data(), (IncFsSize)metadata.size()}});
+        metadataFullPath = path::join(ifs.root, constants().mount, mdFileName);
+        auto node = mIncFs->makeFile(ifs.control, metadataFullPath, 0444, idFromMetadata(metadata),
+                                     {.metadata = {metadata.data(), (IncFsSize)metadata.size()}});
         if (node) {
+            LOG(ERROR) << __func__ << ": couldn't create a mount node " << mdFileName;
             return int(node);
         }
     }
 
-    return addBindMountWithMd(ifs, storage, std::move(mdFileName), std::move(source),
-                              std::move(target), kind, mainLock);
+    const auto res = addBindMountWithMd(ifs, storage, std::move(mdFileName), std::move(source),
+                                        std::move(target), kind, mainLock);
+    if (res) {
+        mIncFs->unlink(ifs.control, metadataFullPath);
+    }
+    return res;
 }
 
 int IncrementalService::addBindMountWithMd(IncrementalService::IncFsMount& ifs, StorageId storage,
@@ -929,12 +940,31 @@
         mainLock.lock();
     }
     std::lock_guard l(ifs.lock);
+    addBindMountRecordLocked(ifs, storage, std::move(metadataName), std::move(source),
+                             std::move(target), kind);
+    return 0;
+}
+
+void IncrementalService::addBindMountRecordLocked(IncFsMount& ifs, StorageId storage,
+                                                  std::string&& metadataName, std::string&& source,
+                                                  std::string&& target, BindKind kind) {
     const auto [it, _] =
             ifs.bindPoints.insert_or_assign(target,
                                             IncFsMount::Bind{storage, std::move(metadataName),
                                                              std::move(source), kind});
     mBindsByPath[std::move(target)] = it;
-    return 0;
+}
+
+RawMetadata IncrementalService::getMetadata(StorageId storage, std::string_view path) const {
+    const auto ifs = getIfs(storage);
+    if (!ifs) {
+        return {};
+    }
+    const auto normPath = normalizePathToStorage(ifs, storage, path);
+    if (normPath.empty()) {
+        return {};
+    }
+    return mIncFs->getMetadata(ifs->control, normPath);
 }
 
 RawMetadata IncrementalService::getMetadata(StorageId storage, FileId node) const {
@@ -945,47 +975,6 @@
     return mIncFs->getMetadata(ifs->control, node);
 }
 
-std::vector<std::string> IncrementalService::listFiles(StorageId storage) const {
-    const auto ifs = getIfs(storage);
-    if (!ifs) {
-        return {};
-    }
-
-    std::unique_lock l(ifs->lock);
-    auto subdirIt = ifs->storages.find(storage);
-    if (subdirIt == ifs->storages.end()) {
-        return {};
-    }
-    auto dir = path::join(ifs->root, constants().mount, subdirIt->second.name);
-    l.unlock();
-
-    const auto prefixSize = dir.size() + 1;
-    std::vector<std::string> todoDirs{std::move(dir)};
-    std::vector<std::string> result;
-    do {
-        auto currDir = std::move(todoDirs.back());
-        todoDirs.pop_back();
-
-        auto d =
-                std::unique_ptr<DIR, decltype(&::closedir)>(::opendir(currDir.c_str()), ::closedir);
-        while (auto e = ::readdir(d.get())) {
-            if (e->d_type == DT_REG) {
-                result.emplace_back(
-                        path::join(std::string_view(currDir).substr(prefixSize), e->d_name));
-                continue;
-            }
-            if (e->d_type == DT_DIR) {
-                if (e->d_name == "."sv || e->d_name == ".."sv) {
-                    continue;
-                }
-                todoDirs.emplace_back(path::join(currDir, e->d_name));
-                continue;
-            }
-        }
-    } while (!todoDirs.empty());
-    return result;
-}
-
 bool IncrementalService::startLoading(StorageId storage) const {
     DataLoaderStubPtr dataLoaderStub;
     {
@@ -1003,16 +992,216 @@
     return true;
 }
 
-void IncrementalService::mountExistingImages() {
-    for (const auto& entry : fs::directory_iterator(mIncrementalDir)) {
-        const auto path = entry.path().u8string();
-        const auto name = entry.path().filename().u8string();
-        if (!base::StartsWith(name, constants().mountKeyPrefix)) {
+std::unordered_set<std::string_view> IncrementalService::adoptMountedInstances() {
+    std::unordered_set<std::string_view> mountedRootNames;
+    mIncFs->listExistingMounts([this, &mountedRootNames](auto root, auto backingDir, auto binds) {
+        LOG(INFO) << "Existing mount: " << backingDir << "->" << root;
+        for (auto [source, target] : binds) {
+            LOG(INFO) << "  bind: '" << source << "'->'" << target << "'";
+            LOG(INFO) << "         " << path::join(root, source);
+        }
+
+        // Ensure it's a kind of a mount that's managed by IncrementalService
+        if (path::basename(root) != constants().mount ||
+            path::basename(backingDir) != constants().backing) {
+            return;
+        }
+        const auto expectedRoot = path::dirname(root);
+        if (path::dirname(backingDir) != expectedRoot) {
+            return;
+        }
+        if (path::dirname(expectedRoot) != mIncrementalDir) {
+            return;
+        }
+        if (!path::basename(expectedRoot).starts_with(constants().mountKeyPrefix)) {
+            return;
+        }
+
+        LOG(INFO) << "Looks like an IncrementalService-owned: " << expectedRoot;
+
+        // make sure we clean up the mount if it happens to be a bad one.
+        // Note: unmounting needs to run first, so the cleanup object is created _last_.
+        auto cleanupFiles = makeCleanup([&]() {
+            LOG(INFO) << "Failed to adopt existing mount, deleting files: " << expectedRoot;
+            IncFsMount::cleanupFilesystem(expectedRoot);
+        });
+        auto cleanupMounts = makeCleanup([&]() {
+            LOG(INFO) << "Failed to adopt existing mount, cleaning up: " << expectedRoot;
+            for (auto&& [_, target] : binds) {
+                mVold->unmountIncFs(std::string(target));
+            }
+            mVold->unmountIncFs(std::string(root));
+        });
+
+        auto control = mIncFs->openMount(root);
+        if (!control) {
+            LOG(INFO) << "failed to open mount " << root;
+            return;
+        }
+
+        auto mountRecord =
+                parseFromIncfs<metadata::Mount>(mIncFs.get(), control,
+                                                path::join(root, constants().infoMdName));
+        if (!mountRecord.has_loader() || !mountRecord.has_storage()) {
+            LOG(ERROR) << "Bad mount metadata in mount at " << expectedRoot;
+            return;
+        }
+
+        auto mountId = mountRecord.storage().id();
+        mNextId = std::max(mNextId, mountId + 1);
+
+        DataLoaderParamsParcel dataLoaderParams;
+        {
+            const auto& loader = mountRecord.loader();
+            dataLoaderParams.type = (content::pm::DataLoaderType)loader.type();
+            dataLoaderParams.packageName = loader.package_name();
+            dataLoaderParams.className = loader.class_name();
+            dataLoaderParams.arguments = loader.arguments();
+        }
+
+        auto ifs = std::make_shared<IncFsMount>(std::string(expectedRoot), mountId,
+                                                std::move(control), *this);
+        cleanupFiles.release(); // ifs will take care of that now
+
+        std::vector<std::pair<std::string, metadata::BindPoint>> permanentBindPoints;
+        auto d = openDir(root);
+        while (auto e = ::readdir(d.get())) {
+            if (e->d_type == DT_REG) {
+                auto name = std::string_view(e->d_name);
+                if (name.starts_with(constants().mountpointMdPrefix)) {
+                    permanentBindPoints
+                            .emplace_back(name,
+                                          parseFromIncfs<metadata::BindPoint>(mIncFs.get(),
+                                                                              ifs->control,
+                                                                              path::join(root,
+                                                                                         name)));
+                    if (permanentBindPoints.back().second.dest_path().empty() ||
+                        permanentBindPoints.back().second.source_subdir().empty()) {
+                        permanentBindPoints.pop_back();
+                        mIncFs->unlink(ifs->control, path::join(root, name));
+                    } else {
+                        LOG(INFO) << "Permanent bind record: '"
+                                  << permanentBindPoints.back().second.source_subdir() << "'->'"
+                                  << permanentBindPoints.back().second.dest_path() << "'";
+                    }
+                }
+            } else if (e->d_type == DT_DIR) {
+                if (e->d_name == "."sv || e->d_name == ".."sv) {
+                    continue;
+                }
+                auto name = std::string_view(e->d_name);
+                if (name.starts_with(constants().storagePrefix)) {
+                    int storageId;
+                    const auto res =
+                            std::from_chars(name.data() + constants().storagePrefix.size() + 1,
+                                            name.data() + name.size(), storageId);
+                    if (res.ec != std::errc{} || *res.ptr != '_') {
+                        LOG(WARNING) << "Ignoring storage with invalid name '" << name
+                                     << "' for mount " << expectedRoot;
+                        continue;
+                    }
+                    auto [_, inserted] = mMounts.try_emplace(storageId, ifs);
+                    if (!inserted) {
+                        LOG(WARNING) << "Ignoring storage with duplicate id " << storageId
+                                     << " for mount " << expectedRoot;
+                        continue;
+                    }
+                    ifs->storages.insert_or_assign(storageId,
+                                                   IncFsMount::Storage{path::join(root, name)});
+                    mNextId = std::max(mNextId, storageId + 1);
+                }
+            }
+        }
+
+        if (ifs->storages.empty()) {
+            LOG(WARNING) << "No valid storages in mount " << root;
+            return;
+        }
+
+        // now match the mounted directories with what we expect to have in the metadata
+        {
+            std::unique_lock l(mLock, std::defer_lock);
+            for (auto&& [metadataFile, bindRecord] : permanentBindPoints) {
+                auto mountedIt = std::find_if(binds.begin(), binds.end(),
+                                              [&, bindRecord = bindRecord](auto&& bind) {
+                                                  return bind.second == bindRecord.dest_path() &&
+                                                          path::join(root, bind.first) ==
+                                                          bindRecord.source_subdir();
+                                              });
+                if (mountedIt != binds.end()) {
+                    LOG(INFO) << "Matched permanent bound " << bindRecord.source_subdir()
+                              << " to mount " << mountedIt->first;
+                    addBindMountRecordLocked(*ifs, bindRecord.storage_id(), std::move(metadataFile),
+                                             std::move(*bindRecord.mutable_source_subdir()),
+                                             std::move(*bindRecord.mutable_dest_path()),
+                                             BindKind::Permanent);
+                    if (mountedIt != binds.end() - 1) {
+                        std::iter_swap(mountedIt, binds.end() - 1);
+                    }
+                    binds = binds.first(binds.size() - 1);
+                } else {
+                    LOG(INFO) << "Didn't match permanent bound " << bindRecord.source_subdir()
+                              << ", mounting";
+                    // doesn't exist - try mounting back
+                    if (addBindMountWithMd(*ifs, bindRecord.storage_id(), std::move(metadataFile),
+                                           std::move(*bindRecord.mutable_source_subdir()),
+                                           std::move(*bindRecord.mutable_dest_path()),
+                                           BindKind::Permanent, l)) {
+                        mIncFs->unlink(ifs->control, metadataFile);
+                    }
+                }
+            }
+        }
+
+        // if anything stays in |binds| those are probably temporary binds; system restarted since
+        // they were mounted - so let's unmount them all.
+        for (auto&& [source, target] : binds) {
+            if (source.empty()) {
+                continue;
+            }
+            mVold->unmountIncFs(std::string(target));
+        }
+        cleanupMounts.release(); // ifs now manages everything
+
+        if (ifs->bindPoints.empty()) {
+            LOG(WARNING) << "No valid bind points for mount " << expectedRoot;
+            deleteStorage(*ifs);
+            return;
+        }
+
+        prepareDataLoaderLocked(*ifs, std::move(dataLoaderParams));
+        CHECK(ifs->dataLoaderStub);
+
+        mountedRootNames.insert(path::basename(ifs->root));
+
+        // not locking here at all: we're still in the constructor, no other calls can happen
+        mMounts[ifs->mountId] = std::move(ifs);
+    });
+
+    return mountedRootNames;
+}
+
+void IncrementalService::mountExistingImages(
+        const std::unordered_set<std::string_view>& mountedRootNames) {
+    auto dir = openDir(mIncrementalDir);
+    if (!dir) {
+        PLOG(WARNING) << "Couldn't open the root incremental dir " << mIncrementalDir;
+        return;
+    }
+    while (auto entry = ::readdir(dir.get())) {
+        if (entry->d_type != DT_DIR) {
+            continue;
+        }
+        std::string_view name = entry->d_name;
+        if (!name.starts_with(constants().mountKeyPrefix)) {
+            continue;
+        }
+        if (mountedRootNames.find(name) != mountedRootNames.end()) {
             continue;
         }
         const auto root = path::join(mIncrementalDir, name);
         if (!mountExistingImage(root)) {
-            IncFsMount::cleanupFilesystem(path);
+            IncFsMount::cleanupFilesystem(root);
         }
     }
 }
@@ -1049,7 +1238,7 @@
     DataLoaderParamsParcel dataLoaderParams;
     {
         const auto& loader = mount.loader();
-        dataLoaderParams.type = (android::content::pm::DataLoaderType)loader.type();
+        dataLoaderParams.type = (content::pm::DataLoaderType)loader.type();
         dataLoaderParams.packageName = loader.package_name();
         dataLoaderParams.className = loader.class_name();
         dataLoaderParams.arguments = loader.arguments();
@@ -1059,7 +1248,7 @@
     CHECK(ifs->dataLoaderStub);
 
     std::vector<std::pair<std::string, metadata::BindPoint>> bindPoints;
-    auto d = openDir(path::c_str(mountTarget));
+    auto d = openDir(mountTarget);
     while (auto e = ::readdir(d.get())) {
         if (e->d_type == DT_REG) {
             auto name = std::string_view(e->d_name);
@@ -1109,12 +1298,14 @@
     }
 
     int bindCount = 0;
-    for (auto&& bp : bindPoints) {
+    {
         std::unique_lock l(mLock, std::defer_lock);
-        bindCount += !addBindMountWithMd(*ifs, bp.second.storage_id(), std::move(bp.first),
-                                         std::move(*bp.second.mutable_source_subdir()),
-                                         std::move(*bp.second.mutable_dest_path()),
-                                         BindKind::Permanent, l);
+        for (auto&& bp : bindPoints) {
+            bindCount += !addBindMountWithMd(*ifs, bp.second.storage_id(), std::move(bp.first),
+                                             std::move(*bp.second.mutable_source_subdir()),
+                                             std::move(*bp.second.mutable_dest_path()),
+                                             BindKind::Permanent, l);
+        }
     }
 
     if (bindCount == 0) {
@@ -1123,30 +1314,35 @@
         return false;
     }
 
+    // not locking here at all: we're still in the constructor, no other calls can happen
     mMounts[ifs->mountId] = std::move(ifs);
     return true;
 }
 
 IncrementalService::DataLoaderStubPtr IncrementalService::prepareDataLoader(
-        IncrementalService::IncFsMount& ifs, DataLoaderParamsParcel&& params,
+        IncFsMount& ifs, DataLoaderParamsParcel&& params,
         const DataLoaderStatusListener* externalListener) {
     std::unique_lock l(ifs.lock);
+    prepareDataLoaderLocked(ifs, std::move(params), externalListener);
+    return ifs.dataLoaderStub;
+}
+
+void IncrementalService::prepareDataLoaderLocked(IncFsMount& ifs, DataLoaderParamsParcel&& params,
+                                                 const DataLoaderStatusListener* externalListener) {
     if (ifs.dataLoaderStub) {
         LOG(INFO) << "Skipped data loader preparation because it already exists";
-        return ifs.dataLoaderStub;
+        return;
     }
 
     FileSystemControlParcel fsControlParcel;
     fsControlParcel.incremental = aidl::make_nullable<IncrementalFileSystemControlParcel>();
-    fsControlParcel.incremental->cmd.reset(base::unique_fd(::dup(ifs.control.cmd())));
-    fsControlParcel.incremental->pendingReads.reset(
-            base::unique_fd(::dup(ifs.control.pendingReads())));
-    fsControlParcel.incremental->log.reset(base::unique_fd(::dup(ifs.control.logs())));
+    fsControlParcel.incremental->cmd.reset(dup(ifs.control.cmd()));
+    fsControlParcel.incremental->pendingReads.reset(dup(ifs.control.pendingReads()));
+    fsControlParcel.incremental->log.reset(dup(ifs.control.logs()));
     fsControlParcel.service = new IncrementalServiceConnector(*this, ifs.mountId);
 
     ifs.dataLoaderStub = new DataLoaderStub(*this, ifs.mountId, std::move(params),
                                             std::move(fsControlParcel), externalListener);
-    return ifs.dataLoaderStub;
 }
 
 template <class Duration>
@@ -1208,7 +1404,7 @@
         const auto targetLibPathAbsolute = normalizePathToStorage(ifs, storage, targetLibPath);
         // If the extract file already exists, skip
         if (access(targetLibPathAbsolute.c_str(), F_OK) == 0) {
-            if (sEnablePerfLogging) {
+            if (perfLoggingEnabled()) {
                 LOG(INFO) << "incfs: Native lib file already exists: " << targetLibPath
                           << "; skipping extraction, spent "
                           << elapsedMcs(startFileTs, Clock::now()) << "mcs";
@@ -1235,7 +1431,7 @@
 
         // If it is a zero-byte file, skip data writing
         if (entry.uncompressed_length == 0) {
-            if (sEnablePerfLogging) {
+            if (perfLoggingEnabled()) {
                 LOG(INFO) << "incfs: Extracted " << libName
                           << "(0 bytes): " << elapsedMcs(startFileTs, makeFileTs) << "mcs";
             }
@@ -1248,7 +1444,7 @@
             extractZipFile(ifs.lock(), zipFile.get(), entry, libFileId, libPath, makeFileTs);
         });
 
-        if (sEnablePerfLogging) {
+        if (perfLoggingEnabled()) {
             auto prepareJobTs = Clock::now();
             LOG(INFO) << "incfs: Processed " << libName << ": "
                       << elapsedMcs(startFileTs, prepareJobTs)
@@ -1275,7 +1471,7 @@
         mJobCondition.notify_all();
     }
 
-    if (sEnablePerfLogging) {
+    if (perfLoggingEnabled()) {
         auto end = Clock::now();
         LOG(INFO) << "incfs: configureNativeBinaries complete in " << elapsedMcs(start, end)
                   << "mcs, make dirs: " << elapsedMcs(start, mkDirsTs)
@@ -1340,7 +1536,7 @@
         return;
     }
 
-    if (sEnablePerfLogging) {
+    if (perfLoggingEnabled()) {
         auto endFileTs = Clock::now();
         LOG(INFO) << "incfs: Extracted " << libName << "(" << entry.compressed_length << " -> "
                   << entry.uncompressed_length << " bytes): " << elapsedMcs(startedTs, endFileTs)
@@ -1356,7 +1552,7 @@
     struct WaitPrinter {
         const Clock::time_point startTs = Clock::now();
         ~WaitPrinter() noexcept {
-            if (sEnablePerfLogging) {
+            if (perfLoggingEnabled()) {
                 const auto endTs = Clock::now();
                 LOG(INFO) << "incfs: waitForNativeBinariesExtraction() complete in "
                           << elapsedMcs(startTs, endTs) << "mcs";
@@ -1381,6 +1577,11 @@
     return mRunning;
 }
 
+bool IncrementalService::perfLoggingEnabled() {
+    static const bool enabled = base::GetBoolProperty("incremental.perflogging", false);
+    return enabled;
+}
+
 void IncrementalService::runJobProcessing() {
     for (;;) {
         std::unique_lock lock(mJobMutex);
@@ -1492,12 +1693,17 @@
     return setTargetStatus(IDataLoaderStatusListener::DATA_LOADER_DESTROYED);
 }
 
-bool IncrementalService::DataLoaderStub::setTargetStatus(int status) {
+bool IncrementalService::DataLoaderStub::setTargetStatus(int newStatus) {
+    int oldStatus, curStatus;
     {
         std::unique_lock lock(mStatusMutex);
-        mTargetStatus = status;
+        oldStatus = mTargetStatus;
+        mTargetStatus = newStatus;
         mTargetStatusTs = Clock::now();
+        curStatus = mCurrentStatus;
     }
+    LOG(DEBUG) << "Target status update for DataLoader " << mId << ": " << oldStatus << " -> "
+               << newStatus << " (current " << curStatus << ")";
     return fsmStep();
 }
 
@@ -1596,14 +1802,20 @@
         return binder::Status::fromServiceSpecificError(-EPERM, "Mount ID mismatch.");
     }
 
+    int targetStatus, oldStatus;
     {
         std::unique_lock lock(mStatusMutex);
         if (mCurrentStatus == newStatus) {
             return binder::Status::ok();
         }
         mCurrentStatus = newStatus;
+        oldStatus = mCurrentStatus;
+        targetStatus = mTargetStatus;
     }
 
+    LOG(DEBUG) << "Current status update for DataLoader " << mId << ": " << oldStatus << " -> "
+               << newStatus << " (target " << targetStatus << ")";
+
     if (mListener) {
         mListener->onStatusChanged(mountId, newStatus);
     }
@@ -1616,17 +1828,19 @@
 }
 
 void IncrementalService::DataLoaderStub::onDump(int fd) {
-    dprintf(fd, "\t\tdataLoader:");
-    dprintf(fd, "\t\t\tcurrentStatus: %d\n", mCurrentStatus);
-    dprintf(fd, "\t\t\ttargetStatus: %d\n", mTargetStatus);
-    dprintf(fd, "\t\t\ttargetStatusTs: %lldmcs\n",
+    dprintf(fd, "    dataLoader: {\n");
+    dprintf(fd, "      currentStatus: %d\n", mCurrentStatus);
+    dprintf(fd, "      targetStatus: %d\n", mTargetStatus);
+    dprintf(fd, "      targetStatusTs: %lldmcs\n",
             (long long)(elapsedMcs(mTargetStatusTs, Clock::now())));
     const auto& params = mParams;
-    dprintf(fd, "\t\t\tdataLoaderParams:\n");
-    dprintf(fd, "\t\t\t\ttype: %s\n", toString(params.type).c_str());
-    dprintf(fd, "\t\t\t\tpackageName: %s\n", params.packageName.c_str());
-    dprintf(fd, "\t\t\t\tclassName: %s\n", params.className.c_str());
-    dprintf(fd, "\t\t\t\targuments: %s\n", params.arguments.c_str());
+    dprintf(fd, "      dataLoaderParams: {\n");
+    dprintf(fd, "        type: %s\n", toString(params.type).c_str());
+    dprintf(fd, "        packageName: %s\n", params.packageName.c_str());
+    dprintf(fd, "        className: %s\n", params.className.c_str());
+    dprintf(fd, "        arguments: %s\n", params.arguments.c_str());
+    dprintf(fd, "      }\n");
+    dprintf(fd, "    }\n");
 }
 
 void IncrementalService::AppOpsListener::opChanged(int32_t, const String16&) {
diff --git a/services/incremental/IncrementalService.h b/services/incremental/IncrementalService.h
index bd01d77..9d40baf 100644
--- a/services/incremental/IncrementalService.h
+++ b/services/incremental/IncrementalService.h
@@ -16,13 +16,13 @@
 
 #pragma once
 
-#include <android-base/strings.h>
-#include <android-base/unique_fd.h>
+#include <android/content/pm/BnDataLoaderStatusListener.h>
 #include <android/content/pm/DataLoaderParamsParcel.h>
-#include <binder/IServiceManager.h>
+#include <android/content/pm/IDataLoaderStatusListener.h>
+#include <android/os/incremental/BnIncrementalServiceConnector.h>
+#include <binder/IAppOpsCallback.h>
 #include <utils/String16.h>
 #include <utils/StrongPointer.h>
-#include <utils/Vector.h>
 #include <ziparchive/zip_archive.h>
 
 #include <atomic>
@@ -37,21 +37,14 @@
 #include <string_view>
 #include <thread>
 #include <unordered_map>
+#include <unordered_set>
 #include <utility>
 #include <vector>
 
 #include "ServiceWrappers.h"
-#include "android/content/pm/BnDataLoaderStatusListener.h"
-#include "android/os/incremental/BnIncrementalServiceConnector.h"
 #include "incfs.h"
 #include "path.h"
 
-using namespace android::os::incremental;
-
-namespace android::os {
-class IVold;
-}
-
 namespace android::incremental {
 
 using MountId = int;
@@ -101,17 +94,14 @@
 
     void onSystemReady();
 
-    StorageId createStorage(std::string_view mountPoint, DataLoaderParamsParcel&& dataLoaderParams,
+    StorageId createStorage(std::string_view mountPoint,
+                            content::pm::DataLoaderParamsParcel&& dataLoaderParams,
                             const DataLoaderStatusListener& dataLoaderStatusListener,
                             CreateOptions options = CreateOptions::Default);
     StorageId createLinkedStorage(std::string_view mountPoint, StorageId linkedStorage,
                                   CreateOptions options = CreateOptions::Default);
     StorageId openStorage(std::string_view path);
 
-    FileId nodeFor(StorageId storage, std::string_view path) const;
-    std::pair<FileId, std::string_view> parentAndNameFor(StorageId storage,
-                                                         std::string_view path) const;
-
     int bind(StorageId storage, std::string_view source, std::string_view target, BindKind kind);
     int unbind(StorageId storage, std::string_view target);
     void deleteStorage(StorageId storage);
@@ -131,9 +121,9 @@
         return false;
     }
 
+    RawMetadata getMetadata(StorageId storage, std::string_view path) const;
     RawMetadata getMetadata(StorageId storage, FileId node) const;
 
-    std::vector<std::string> listFiles(StorageId storage) const;
     bool startLoading(StorageId storage) const;
 
     bool configureNativeBinaries(StorageId storage, std::string_view apkFullPath,
@@ -151,7 +141,7 @@
         const std::string packageName;
     };
 
-    class IncrementalServiceConnector : public BnIncrementalServiceConnector {
+    class IncrementalServiceConnector : public os::incremental::BnIncrementalServiceConnector {
     public:
         IncrementalServiceConnector(IncrementalService& incrementalService, int32_t storage)
               : incrementalService(incrementalService), storage(storage) {}
@@ -163,14 +153,13 @@
     };
 
 private:
-    static const bool sEnablePerfLogging;
-
     struct IncFsMount;
 
-    class DataLoaderStub : public android::content::pm::BnDataLoaderStatusListener {
+    class DataLoaderStub : public content::pm::BnDataLoaderStatusListener {
     public:
-        DataLoaderStub(IncrementalService& service, MountId id, DataLoaderParamsParcel&& params,
-                       FileSystemControlParcel&& control,
+        DataLoaderStub(IncrementalService& service, MountId id,
+                       content::pm::DataLoaderParamsParcel&& params,
+                       content::pm::FileSystemControlParcel&& control,
                        const DataLoaderStatusListener* externalListener);
         ~DataLoaderStub();
         // Cleans up the internal state and invalidates DataLoaderStub. Any subsequent calls will
@@ -184,7 +173,7 @@
         void onDump(int fd);
 
         MountId id() const { return mId; }
-        const DataLoaderParamsParcel& params() const { return mParams; }
+        const content::pm::DataLoaderParamsParcel& params() const { return mParams; }
 
     private:
         binder::Status onStatusChanged(MountId mount, int newStatus) final;
@@ -202,14 +191,14 @@
 
         IncrementalService& mService;
         MountId mId = kInvalidStorageId;
-        DataLoaderParamsParcel mParams;
-        FileSystemControlParcel mControl;
+        content::pm::DataLoaderParamsParcel mParams;
+        content::pm::FileSystemControlParcel mControl;
         DataLoaderStatusListener mListener;
 
         std::mutex mStatusMutex;
         std::condition_variable mStatusCondition;
-        int mCurrentStatus = IDataLoaderStatusListener::DATA_LOADER_DESTROYED;
-        int mTargetStatus = IDataLoaderStatusListener::DATA_LOADER_DESTROYED;
+        int mCurrentStatus = content::pm::IDataLoaderStatusListener::DATA_LOADER_DESTROYED;
+        int mTargetStatus = content::pm::IDataLoaderStatusListener::DATA_LOADER_DESTROYED;
         TimePoint mTargetStatusTs = {};
     };
     using DataLoaderStubPtr = sp<DataLoaderStub>;
@@ -228,7 +217,7 @@
 
         using Control = incfs::UniqueControl;
 
-        using BindMap = std::map<std::string, Bind>;
+        using BindMap = std::map<std::string, Bind, path::PathLess>;
         using StorageMap = std::unordered_map<StorageId, Storage>;
 
         mutable std::mutex lock;
@@ -260,7 +249,10 @@
     using MountMap = std::unordered_map<MountId, IfsMountPtr>;
     using BindPathMap = std::map<std::string, IncFsMount::BindMap::iterator, path::PathLess>;
 
-    void mountExistingImages();
+    static bool perfLoggingEnabled();
+
+    std::unordered_set<std::string_view> adoptMountedInstances();
+    void mountExistingImages(const std::unordered_set<std::string_view>& mountedRootNames);
     bool mountExistingImage(std::string_view root);
 
     IfsMountPtr getIfs(StorageId storage) const;
@@ -273,8 +265,14 @@
                            std::string&& source, std::string&& target, BindKind kind,
                            std::unique_lock<std::mutex>& mainLock);
 
-    DataLoaderStubPtr prepareDataLoader(IncFsMount& ifs, DataLoaderParamsParcel&& params,
+    void addBindMountRecordLocked(IncFsMount& ifs, StorageId storage, std::string&& metadataName,
+                                  std::string&& source, std::string&& target, BindKind kind);
+
+    DataLoaderStubPtr prepareDataLoader(IncFsMount& ifs,
+                                        content::pm::DataLoaderParamsParcel&& params,
                                         const DataLoaderStatusListener* externalListener = nullptr);
+    void prepareDataLoaderLocked(IncFsMount& ifs, content::pm::DataLoaderParamsParcel&& params,
+                                 const DataLoaderStatusListener* externalListener = nullptr);
 
     BindPathMap::const_iterator findStorageLocked(std::string_view path) const;
     StorageId findStorageId(std::string_view path) const;
@@ -283,9 +281,10 @@
     void deleteStorageLocked(IncFsMount& ifs, std::unique_lock<std::mutex>&& ifsLock);
     MountMap::iterator getStorageSlotLocked();
     std::string normalizePathToStorage(const IfsMountPtr& incfs, StorageId storage,
-                                       std::string_view path);
-    std::string normalizePathToStorageLocked(IncFsMount::StorageMap::iterator storageIt,
-                                             std::string_view path);
+                                       std::string_view path) const;
+    std::string normalizePathToStorageLocked(const IfsMountPtr& incfs,
+                                             IncFsMount::StorageMap::iterator storageIt,
+                                             std::string_view path) const;
 
     binder::Status applyStorageParams(IncFsMount& ifs, bool enableReadLogs);
 
diff --git a/services/incremental/IncrementalServiceValidation.cpp b/services/incremental/IncrementalServiceValidation.cpp
new file mode 100644
index 0000000..abadbbf
--- /dev/null
+++ b/services/incremental/IncrementalServiceValidation.cpp
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "IncrementalServiceValidation.h"
+
+#include <android-base/stringprintf.h>
+#include <binder/IPCThreadState.h>
+#include <binder/PermissionCache.h>
+#include <binder/PermissionController.h>
+#include <errno.h>
+#include <utils/String16.h>
+
+namespace android::incremental {
+
+binder::Status Ok() {
+    return binder::Status::ok();
+}
+
+binder::Status Exception(uint32_t code, const std::string& msg) {
+    return binder::Status::fromExceptionCode(code, String8(msg.c_str()));
+}
+
+int fromBinderStatus(const binder::Status& status) {
+    return status.exceptionCode() == binder::Status::EX_SERVICE_SPECIFIC
+            ? status.serviceSpecificErrorCode() > 0
+                    ? -status.serviceSpecificErrorCode()
+                    : status.serviceSpecificErrorCode() == 0 ? -EFAULT
+                                                             : status.serviceSpecificErrorCode()
+            : -EIO;
+}
+
+binder::Status CheckPermissionForDataDelivery(const char* permission, const char* operation,
+                                              const char* package) {
+    using android::base::StringPrintf;
+
+    int32_t pid;
+    int32_t uid;
+
+    if (!PermissionCache::checkCallingPermission(String16(permission), &pid, &uid)) {
+        return Exception(binder::Status::EX_SECURITY,
+                         StringPrintf("UID %d / PID %d lacks permission %s", uid, pid, permission));
+    }
+
+    String16 packageName{package};
+
+    // Caller must also have op granted.
+    PermissionController pc;
+    if (auto packageUid = pc.getPackageUid(packageName, 0); packageUid != uid) {
+        return Exception(binder::Status::EX_SECURITY,
+                         StringPrintf("UID %d / PID %d does not own package %s", uid, pid,
+                                      package));
+    }
+    switch (auto result = pc.noteOp(String16(operation), uid, packageName); result) {
+        case PermissionController::MODE_ALLOWED:
+        case PermissionController::MODE_DEFAULT:
+            return binder::Status::ok();
+        default:
+            return Exception(binder::Status::EX_SECURITY,
+                             StringPrintf("UID %d / PID %d / package %s lacks app-op %s, error %d",
+                                          uid, pid, package, operation, result));
+    }
+}
+
+} // namespace android::incremental
diff --git a/services/incremental/IncrementalServiceValidation.h b/services/incremental/IncrementalServiceValidation.h
index 48894c6..0e50c4d 100644
--- a/services/incremental/IncrementalServiceValidation.h
+++ b/services/incremental/IncrementalServiceValidation.h
@@ -16,61 +16,17 @@
 
 #pragma once
 
-#include <android-base/stringprintf.h>
-#include <binder/IPCThreadState.h>
-#include <binder/PermissionCache.h>
-#include <binder/PermissionController.h>
 #include <binder/Status.h>
+#include <stdint.h>
+
+#include <string>
 
 namespace android::incremental {
 
-inline binder::Status Ok() {
-    return binder::Status::ok();
-}
-
-inline binder::Status Exception(uint32_t code, const std::string& msg) {
-    return binder::Status::fromExceptionCode(code, String8(msg.c_str()));
-}
-
-inline int fromBinderStatus(const binder::Status& status) {
-    return status.exceptionCode() == binder::Status::EX_SERVICE_SPECIFIC
-            ? status.serviceSpecificErrorCode() > 0 ? -status.serviceSpecificErrorCode()
-                                                    : status.serviceSpecificErrorCode() == 0
-                            ? -EFAULT
-                            : status.serviceSpecificErrorCode()
-            : -EIO;
-}
-
-inline binder::Status CheckPermissionForDataDelivery(const char* permission, const char* operation,
-                                                     const char* package) {
-    using android::base::StringPrintf;
-
-    int32_t pid;
-    int32_t uid;
-
-    if (!PermissionCache::checkCallingPermission(String16(permission), &pid, &uid)) {
-        return Exception(binder::Status::EX_SECURITY,
-                         StringPrintf("UID %d / PID %d lacks permission %s", uid, pid, permission));
-    }
-
-    String16 packageName{package};
-
-    // Caller must also have op granted.
-    PermissionController pc;
-    if (auto packageUid = pc.getPackageUid(packageName, 0); packageUid != uid) {
-        return Exception(binder::Status::EX_SECURITY,
-                         StringPrintf("UID %d / PID %d does not own package %s", uid, pid,
-                                      package));
-    }
-    switch (auto result = pc.noteOp(String16(operation), uid, packageName); result) {
-        case PermissionController::MODE_ALLOWED:
-        case PermissionController::MODE_DEFAULT:
-            return binder::Status::ok();
-        default:
-            return Exception(binder::Status::EX_SECURITY,
-                             StringPrintf("UID %d / PID %d / package %s lacks app-op %s, error %d",
-                                          uid, pid, package, operation, result));
-    }
-}
+binder::Status Ok();
+binder::Status Exception(uint32_t code, const std::string& msg);
+int fromBinderStatus(const binder::Status& status);
+binder::Status CheckPermissionForDataDelivery(const char* permission, const char* operation,
+                                              const char* package);
 
 } // namespace android::incremental
diff --git a/services/incremental/ServiceWrappers.cpp b/services/incremental/ServiceWrappers.cpp
index bf8e696..0b044c2 100644
--- a/services/incremental/ServiceWrappers.cpp
+++ b/services/incremental/ServiceWrappers.cpp
@@ -18,12 +18,18 @@
 
 #include "ServiceWrappers.h"
 
+#include <MountRegistry.h>
 #include <android-base/logging.h>
+#include <android/content/pm/IDataLoaderManager.h>
+#include <android/os/IVold.h>
+#include <binder/AppOpsManager.h>
 #include <utils/String16.h>
 
+#include "IncrementalServiceValidation.h"
+
 using namespace std::literals;
 
-namespace android::os::incremental {
+namespace android::incremental {
 
 static constexpr auto kVoldServiceName = "vold"sv;
 static constexpr auto kDataLoaderManagerName = "dataloader_manager"sv;
@@ -32,9 +38,9 @@
 public:
     RealVoldService(const sp<os::IVold> vold) : mInterface(std::move(vold)) {}
     ~RealVoldService() = default;
-    binder::Status mountIncFs(const std::string& backingPath, const std::string& targetDir,
-                              int32_t flags,
-                              IncrementalFileSystemControlParcel* _aidl_return) const final {
+    binder::Status mountIncFs(
+            const std::string& backingPath, const std::string& targetDir, int32_t flags,
+            os::incremental::IncrementalFileSystemControlParcel* _aidl_return) const final {
         return mInterface->mountIncFs(backingPath, targetDir, flags, _aidl_return);
     }
     binder::Status unmountIncFs(const std::string& dir) const final {
@@ -56,16 +62,18 @@
 
 class RealDataLoaderManager : public DataLoaderManagerWrapper {
 public:
-    RealDataLoaderManager(const sp<content::pm::IDataLoaderManager> manager)
-          : mInterface(manager) {}
+    RealDataLoaderManager(sp<content::pm::IDataLoaderManager> manager)
+          : mInterface(std::move(manager)) {}
     ~RealDataLoaderManager() = default;
-    binder::Status initializeDataLoader(MountId mountId, const DataLoaderParamsParcel& params,
-                                        const FileSystemControlParcel& control,
-                                        const sp<IDataLoaderStatusListener>& listener,
+    binder::Status initializeDataLoader(MountId mountId,
+                                        const content::pm::DataLoaderParamsParcel& params,
+                                        const content::pm::FileSystemControlParcel& control,
+                                        const sp<content::pm::IDataLoaderStatusListener>& listener,
                                         bool* _aidl_return) const final {
         return mInterface->initializeDataLoader(mountId, params, control, listener, _aidl_return);
     }
-    binder::Status getDataLoader(MountId mountId, sp<IDataLoader>* _aidl_return) const final {
+    binder::Status getDataLoader(MountId mountId,
+                                 sp<content::pm::IDataLoader>* _aidl_return) const final {
         return mInterface->getDataLoader(mountId, _aidl_return);
     }
     binder::Status destroyDataLoader(MountId mountId) const final {
@@ -109,21 +117,28 @@
 class RealIncFs : public IncFsWrapper {
 public:
     RealIncFs() = default;
-    ~RealIncFs() = default;
+    ~RealIncFs() final = default;
+    void listExistingMounts(const ExistingMountCallback& cb) const final {
+        for (auto mount : incfs::defaultMountRegistry().copyMounts()) {
+            auto binds = mount.binds(); // span() doesn't like rvalue containers, needs to save it.
+            cb(mount.root(), mount.backingDir(), binds);
+        }
+    }
+    Control openMount(std::string_view path) const final { return incfs::open(path); }
     Control createControl(IncFsFd cmd, IncFsFd pendingReads, IncFsFd logs) const final {
         return incfs::createControl(cmd, pendingReads, logs);
     }
     ErrorCode makeFile(const Control& control, std::string_view path, int mode, FileId id,
-                       NewFileParams params) const final {
+                       incfs::NewFileParams params) const final {
         return incfs::makeFile(control, path, mode, id, params);
     }
     ErrorCode makeDir(const Control& control, std::string_view path, int mode) const final {
         return incfs::makeDir(control, path, mode);
     }
-    RawMetadata getMetadata(const Control& control, FileId fileid) const final {
+    incfs::RawMetadata getMetadata(const Control& control, FileId fileid) const final {
         return incfs::getMetadata(control, fileid);
     }
-    RawMetadata getMetadata(const Control& control, std::string_view path) const final {
+    incfs::RawMetadata getMetadata(const Control& control, std::string_view path) const final {
         return incfs::getMetadata(control, path);
     }
     FileId getFileId(const Control& control, std::string_view path) const final {
@@ -138,8 +153,8 @@
     base::unique_fd openForSpecialOps(const Control& control, FileId id) const final {
         return base::unique_fd{incfs::openForSpecialOps(control, id).release()};
     }
-    ErrorCode writeBlocks(Span<const DataBlock> blocks) const final {
-        return incfs::writeBlocks(blocks);
+    ErrorCode writeBlocks(std::span<const incfs::DataBlock> blocks) const final {
+        return incfs::writeBlocks({blocks.data(), size_t(blocks.size())});
     }
 };
 
@@ -165,8 +180,9 @@
 }
 
 std::unique_ptr<DataLoaderManagerWrapper> RealServiceManager::getDataLoaderManager() {
-    sp<IDataLoaderManager> manager =
-            RealServiceManager::getRealService<IDataLoaderManager>(kDataLoaderManagerName);
+    sp<content::pm::IDataLoaderManager> manager =
+            RealServiceManager::getRealService<content::pm::IDataLoaderManager>(
+                    kDataLoaderManagerName);
     if (manager) {
         return std::make_unique<RealDataLoaderManager>(manager);
     }
@@ -239,4 +255,4 @@
     return getJavaVm(env);
 }
 
-} // namespace android::os::incremental
+} // namespace android::incremental
diff --git a/services/incremental/ServiceWrappers.h b/services/incremental/ServiceWrappers.h
index 142bf2e..39f8541 100644
--- a/services/incremental/ServiceWrappers.h
+++ b/services/incremental/ServiceWrappers.h
@@ -16,29 +16,23 @@
 
 #pragma once
 
-#include "IncrementalServiceValidation.h"
-
-#include <android-base/strings.h>
 #include <android-base/unique_fd.h>
 #include <android/content/pm/DataLoaderParamsParcel.h>
 #include <android/content/pm/FileSystemControlParcel.h>
 #include <android/content/pm/IDataLoader.h>
-#include <android/content/pm/IDataLoaderManager.h>
 #include <android/content/pm/IDataLoaderStatusListener.h>
-#include <android/os/IVold.h>
-#include <binder/AppOpsManager.h>
+#include <binder/IAppOpsCallback.h>
 #include <binder/IServiceManager.h>
+#include <binder/Status.h>
 #include <incfs.h>
 #include <jni.h>
 
 #include <memory>
+#include <span>
 #include <string>
 #include <string_view>
 
-using namespace android::incfs;
-using namespace android::content::pm;
-
-namespace android::os::incremental {
+namespace android::incremental {
 
 // --- Wrapper interfaces ---
 
@@ -47,42 +41,54 @@
 class VoldServiceWrapper {
 public:
     virtual ~VoldServiceWrapper() = default;
-    virtual binder::Status mountIncFs(const std::string& backingPath, const std::string& targetDir,
-                                      int32_t flags,
-                                      IncrementalFileSystemControlParcel* _aidl_return) const = 0;
+    virtual binder::Status mountIncFs(
+            const std::string& backingPath, const std::string& targetDir, int32_t flags,
+            os::incremental::IncrementalFileSystemControlParcel* result) const = 0;
     virtual binder::Status unmountIncFs(const std::string& dir) const = 0;
     virtual binder::Status bindMount(const std::string& sourceDir,
                                      const std::string& targetDir) const = 0;
-    virtual binder::Status setIncFsMountOptions(const ::android::os::incremental::IncrementalFileSystemControlParcel& control, bool enableReadLogs) const = 0;
+    virtual binder::Status setIncFsMountOptions(
+            const os::incremental::IncrementalFileSystemControlParcel& control,
+            bool enableReadLogs) const = 0;
 };
 
 class DataLoaderManagerWrapper {
 public:
     virtual ~DataLoaderManagerWrapper() = default;
-    virtual binder::Status initializeDataLoader(MountId mountId,
-                                                const DataLoaderParamsParcel& params,
-                                                const FileSystemControlParcel& control,
-                                                const sp<IDataLoaderStatusListener>& listener,
-                                                bool* _aidl_return) const = 0;
-    virtual binder::Status getDataLoader(MountId mountId, sp<IDataLoader>* _aidl_return) const = 0;
+    virtual binder::Status initializeDataLoader(
+            MountId mountId, const content::pm::DataLoaderParamsParcel& params,
+            const content::pm::FileSystemControlParcel& control,
+            const sp<content::pm::IDataLoaderStatusListener>& listener, bool* result) const = 0;
+    virtual binder::Status getDataLoader(MountId mountId,
+                                         sp<content::pm::IDataLoader>* result) const = 0;
     virtual binder::Status destroyDataLoader(MountId mountId) const = 0;
 };
 
 class IncFsWrapper {
 public:
+    using Control = incfs::Control;
+    using FileId = incfs::FileId;
+    using ErrorCode = incfs::ErrorCode;
+
+    using ExistingMountCallback =
+            std::function<void(std::string_view root, std::string_view backingDir,
+                               std::span<std::pair<std::string_view, std::string_view>> binds)>;
+
     virtual ~IncFsWrapper() = default;
+    virtual void listExistingMounts(const ExistingMountCallback& cb) const = 0;
+    virtual Control openMount(std::string_view path) const = 0;
     virtual Control createControl(IncFsFd cmd, IncFsFd pendingReads, IncFsFd logs) const = 0;
     virtual ErrorCode makeFile(const Control& control, std::string_view path, int mode, FileId id,
-                               NewFileParams params) const = 0;
+                               incfs::NewFileParams params) const = 0;
     virtual ErrorCode makeDir(const Control& control, std::string_view path, int mode) const = 0;
-    virtual RawMetadata getMetadata(const Control& control, FileId fileid) const = 0;
-    virtual RawMetadata getMetadata(const Control& control, std::string_view path) const = 0;
+    virtual incfs::RawMetadata getMetadata(const Control& control, FileId fileid) const = 0;
+    virtual incfs::RawMetadata getMetadata(const Control& control, std::string_view path) const = 0;
     virtual FileId getFileId(const Control& control, std::string_view path) const = 0;
     virtual ErrorCode link(const Control& control, std::string_view from,
                            std::string_view to) const = 0;
     virtual ErrorCode unlink(const Control& control, std::string_view path) const = 0;
     virtual base::unique_fd openForSpecialOps(const Control& control, FileId id) const = 0;
-    virtual ErrorCode writeBlocks(Span<const DataBlock> blocks) const = 0;
+    virtual ErrorCode writeBlocks(std::span<const incfs::DataBlock> blocks) const = 0;
 };
 
 class AppOpsManagerWrapper {
@@ -129,4 +135,4 @@
     JavaVM* const mJvm;
 };
 
-} // namespace android::os::incremental
+} // namespace android::incremental
diff --git a/services/incremental/path.cpp b/services/incremental/path.cpp
index 0d86f2a..338659d 100644
--- a/services/incremental/path.cpp
+++ b/services/incremental/path.cpp
@@ -44,7 +44,7 @@
                                         PathCharsLess());
 }
 
-static void preparePathComponent(std::string_view path, bool trimFront) {
+static void preparePathComponent(std::string_view& path, bool trimFront) {
     if (trimFront) {
         while (!path.empty() && path.front() == '/') {
             path.remove_prefix(1);
diff --git a/services/incremental/test/IncrementalServiceTest.cpp b/services/incremental/test/IncrementalServiceTest.cpp
index bfe92f4..8a06053 100644
--- a/services/incremental/test/IncrementalServiceTest.cpp
+++ b/services/incremental/test/IncrementalServiceTest.cpp
@@ -25,6 +25,7 @@
 #include <future>
 
 #include "IncrementalService.h"
+#include "IncrementalServiceValidation.h"
 #include "Metadata.pb.h"
 #include "ServiceWrappers.h"
 
@@ -262,6 +263,8 @@
 
 class MockIncFs : public IncFsWrapper {
 public:
+    MOCK_CONST_METHOD1(listExistingMounts, void(const ExistingMountCallback& cb));
+    MOCK_CONST_METHOD1(openMount, Control(std::string_view path));
     MOCK_CONST_METHOD3(createControl, Control(IncFsFd cmd, IncFsFd pendingReads, IncFsFd logs));
     MOCK_CONST_METHOD5(makeFile,
                        ErrorCode(const Control& control, std::string_view path, int mode, FileId id,
@@ -274,10 +277,13 @@
                        ErrorCode(const Control& control, std::string_view from, std::string_view to));
     MOCK_CONST_METHOD2(unlink, ErrorCode(const Control& control, std::string_view path));
     MOCK_CONST_METHOD2(openForSpecialOps, base::unique_fd(const Control& control, FileId id));
-    MOCK_CONST_METHOD1(writeBlocks, ErrorCode(Span<const DataBlock> blocks));
+    MOCK_CONST_METHOD1(writeBlocks, ErrorCode(std::span<const DataBlock> blocks));
+
+    MockIncFs() { ON_CALL(*this, listExistingMounts(_)).WillByDefault(Return()); }
 
     void makeFileFails() { ON_CALL(*this, makeFile(_, _, _, _, _)).WillByDefault(Return(-1)); }
     void makeFileSuccess() { ON_CALL(*this, makeFile(_, _, _, _, _)).WillByDefault(Return(0)); }
+
     RawMetadata getMountInfoMetadata(const Control& control, std::string_view path) {
         metadata::Mount m;
         m.mutable_storage()->set_id(100);
@@ -692,14 +698,14 @@
                                                IncrementalService::CreateOptions::CreateNew);
     std::string dir_path("test");
 
-    std::string tempPath(tempDir.path);
-    std::replace(tempPath.begin(), tempPath.end(), '/', '_');
-    std::string mount_dir = std::string(mRootDir.path) + "/MT_" + tempPath.substr(1);
-    std::string normalized_dir_path = mount_dir + "/mount/st_1_0/" + dir_path;
-
     // Expecting incfs to call makeDir on a path like:
-    // /data/local/tmp/TemporaryDir-06yixG/data_local_tmp_TemporaryDir-xwdFhT/mount/st_1_0/test
-    EXPECT_CALL(*mIncFs, makeDir(_, std::string_view(normalized_dir_path), _));
+    // <root>/*/mount/<storage>/test
+    EXPECT_CALL(*mIncFs,
+                makeDir(_, Truly([&](std::string_view arg) {
+                            return arg.starts_with(mRootDir.path) &&
+                                    arg.ends_with("/mount/st_1_0/" + dir_path);
+                        }),
+                        _));
     auto res = mIncrementalService->makeDir(storageId, dir_path, 0555);
     ASSERT_EQ(res, 0);
 }
@@ -717,29 +723,32 @@
     auto first = "first"sv;
     auto second = "second"sv;
     auto third = "third"sv;
-
-    std::string tempPath(tempDir.path);
-    std::replace(tempPath.begin(), tempPath.end(), '/', '_');
-    std::string mount_dir = std::string(mRootDir.path) + "/MT_" + tempPath.substr(1);
-
-    InSequence seq;
     auto parent_path = std::string(first) + "/" + std::string(second);
     auto dir_path = parent_path + "/" + std::string(third);
 
-    std::string normalized_first_path = mount_dir + "/mount/st_1_0/" + std::string(first);
-    std::string normalized_parent_path = mount_dir + "/mount/st_1_0/" + parent_path;
-    std::string normalized_dir_path = mount_dir + "/mount/st_1_0/" + dir_path;
+    auto checkArgFor = [&](std::string_view expected, std::string_view arg) {
+        return arg.starts_with(mRootDir.path) && arg.ends_with("/mount/st_1_0/"s += expected);
+    };
 
-    EXPECT_CALL(*mIncFs, makeDir(_, std::string_view(normalized_dir_path), _))
-            .WillOnce(Return(-ENOENT));
-    EXPECT_CALL(*mIncFs, makeDir(_, std::string_view(normalized_parent_path), _))
-            .WillOnce(Return(-ENOENT));
-    EXPECT_CALL(*mIncFs, makeDir(_, std::string_view(normalized_first_path), _))
-            .WillOnce(Return(0));
-    EXPECT_CALL(*mIncFs, makeDir(_, std::string_view(normalized_parent_path), _))
-            .WillOnce(Return(0));
-    EXPECT_CALL(*mIncFs, makeDir(_, std::string_view(normalized_dir_path), _)).WillOnce(Return(0));
-    auto res = mIncrementalService->makeDirs(storageId, normalized_dir_path, 0555);
+    {
+        InSequence seq;
+        EXPECT_CALL(*mIncFs,
+                    makeDir(_, Truly([&](auto arg) { return checkArgFor(dir_path, arg); }), _))
+                .WillOnce(Return(-ENOENT));
+        EXPECT_CALL(*mIncFs,
+                    makeDir(_, Truly([&](auto arg) { return checkArgFor(parent_path, arg); }), _))
+                .WillOnce(Return(-ENOENT));
+        EXPECT_CALL(*mIncFs,
+                    makeDir(_, Truly([&](auto arg) { return checkArgFor(first, arg); }), _))
+                .WillOnce(Return(0));
+        EXPECT_CALL(*mIncFs,
+                    makeDir(_, Truly([&](auto arg) { return checkArgFor(parent_path, arg); }), _))
+                .WillOnce(Return(0));
+        EXPECT_CALL(*mIncFs,
+                    makeDir(_, Truly([&](auto arg) { return checkArgFor(dir_path, arg); }), _))
+                .WillOnce(Return(0));
+    }
+    auto res = mIncrementalService->makeDirs(storageId, dir_path, 0555);
     ASSERT_EQ(res, 0);
 }
 } // namespace android::os::incremental
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 289d79d..0fc333f 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -2174,12 +2174,6 @@
         mPackageManagerService.systemReady();
         t.traceEnd();
 
-        if (mIncrementalServiceHandle != 0) {
-            t.traceBegin("MakeIncrementalServiceReady");
-            setIncrementalServiceSystemReady(mIncrementalServiceHandle);
-            t.traceEnd();
-        }
-
         t.traceBegin("MakeDisplayManagerServiceReady");
         try {
             // TODO: use boot phase and communicate these flags some other way
@@ -2449,6 +2443,12 @@
                 reportWtf("Notifying incident daemon running", e);
             }
             t.traceEnd();
+
+            if (mIncrementalServiceHandle != 0) {
+                t.traceBegin("MakeIncrementalServiceReady");
+                setIncrementalServiceSystemReady(mIncrementalServiceHandle);
+                t.traceEnd();
+            }
         }, t);
 
         t.traceEnd(); // startOtherServices
diff --git a/services/people/java/com/android/server/people/data/DataManager.java b/services/people/java/com/android/server/people/data/DataManager.java
index c87ece2..763e19b 100644
--- a/services/people/java/com/android/server/people/data/DataManager.java
+++ b/services/people/java/com/android/server/people/data/DataManager.java
@@ -67,6 +67,7 @@
 import com.android.internal.telephony.SmsApplication;
 import com.android.server.LocalServices;
 import com.android.server.notification.NotificationManagerInternal;
+import com.android.server.notification.ShortcutHelper;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -497,10 +498,6 @@
                 EventStore.CATEGORY_SHORTCUT_BASED, shortcutId);
     }
 
-    private boolean isPersonShortcut(@NonNull ShortcutInfo shortcutInfo) {
-        return shortcutInfo.getPersons() != null && shortcutInfo.getPersons().length != 0;
-    }
-
     @VisibleForTesting
     @WorkerThread
     void addOrUpdateConversationInfo(@NonNull ShortcutInfo shortcutInfo) {
@@ -712,7 +709,8 @@
                 @NonNull List<ShortcutInfo> shortcuts, @NonNull UserHandle user) {
             mInjector.getBackgroundExecutor().execute(() -> {
                 for (ShortcutInfo shortcut : shortcuts) {
-                    if (isPersonShortcut(shortcut)) {
+                    if (ShortcutHelper.isConversationShortcut(
+                            shortcut, mShortcutServiceInternal, user.getIdentifier())) {
                         addOrUpdateConversationInfo(shortcut);
                     }
                 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
index 7a175ca1..2983d58 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java
@@ -541,7 +541,7 @@
         doReturn(new ArrayMap<IBinder, ArrayList<ConnectionRecord>>()).when(s).getConnections();
         s.startRequested = true;
         s.lastActivity = SystemClock.uptimeMillis();
-        app.services.add(s);
+        app.startService(s);
         sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
         sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
 
@@ -585,7 +585,7 @@
         doReturn(new ArrayMap<IBinder, ArrayList<ConnectionRecord>>()).when(s).getConnections();
         s.startRequested = true;
         s.lastActivity = SystemClock.uptimeMillis();
-        app.services.add(s);
+        app.startService(s);
         sService.mWakefulness = PowerManagerInternal.WAKEFULNESS_AWAKE;
         sService.mOomAdjuster.updateOomAdjLocked(app, false, OomAdjuster.OOM_ADJ_REASON_NONE);
 
@@ -1593,7 +1593,7 @@
         s.app = app3;
         setFieldValue(ServiceRecord.class, s, "connections",
                 new ArrayMap<IBinder, ArrayList<ConnectionRecord>>());
-        app3.services.add(s);
+        app3.startService(s);
         doCallRealMethod().when(s).getConnections();
         s.startRequested = true;
         s.lastActivity = now;
@@ -1698,7 +1698,7 @@
             record.app = service;
             setFieldValue(ServiceRecord.class, record, "connections",
                     new ArrayMap<IBinder, ArrayList<ConnectionRecord>>());
-            service.services.add(record);
+            service.startService(record);
             doCallRealMethod().when(record).getConnections();
         }
         AppBindRecord binding = new AppBindRecord(record, null, client);
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
index 3e5c21c..cbe49eb 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerServiceTestable.java
@@ -124,6 +124,9 @@
         // Key is a pair of uri and userId
         private final Map<Pair<Uri, Integer>, ContentObserver> mContentObservers = new ArrayMap<>();
 
+        // Used as an override when set to nonzero.
+        private long mCurrentTimeMillis = 0;
+
         public MockInjector(MockSystemServices services, DpmMockContext context) {
             super(context);
             this.services = services;
@@ -470,5 +473,19 @@
 
         @Override
         public void runCryptoSelfTest() {}
+
+        @Override
+        public String[] getPersonalAppsForSuspension(int userId) {
+            return new String[]{};
+        }
+
+        public void setSystemCurrentTimeMillis(long value) {
+            mCurrentTimeMillis = value;
+        }
+
+        @Override
+        public long systemCurrentTimeMillis() {
+            return mCurrentTimeMillis != 0 ? mCurrentTimeMillis : System.currentTimeMillis();
+        }
     }
 }
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 57039e5..b042e77 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -44,6 +44,7 @@
 import static org.mockito.Matchers.isNull;
 import static org.mockito.Mockito.atLeast;
 import static org.mockito.Mockito.atMost;
+import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
@@ -62,6 +63,7 @@
 import android.app.Activity;
 import android.app.AppOpsManager;
 import android.app.Notification;
+import android.app.PendingIntent;
 import android.app.admin.DeviceAdminReceiver;
 import android.app.admin.DevicePolicyManager;
 import android.app.admin.DevicePolicyManagerInternal;
@@ -96,6 +98,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.R;
+import com.android.internal.messages.nano.SystemMessageProto;
 import com.android.internal.widget.LockscreenCredential;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
@@ -103,6 +106,7 @@
 
 import org.hamcrest.BaseMatcher;
 import org.hamcrest.Description;
+import org.hamcrest.Matcher;
 import org.mockito.Mockito;
 import org.mockito.internal.util.collections.Sets;
 import org.mockito.stubbing.Answer;
@@ -181,6 +185,20 @@
             "wQ==\n" +
             "-----END CERTIFICATE-----\n";
 
+    // Constants for testing setManagedProfileMaximumTimeOff:
+    // Profile maximum time off value
+    private static final long PROFILE_OFF_TIMEOUT = TimeUnit.DAYS.toMillis(5);
+    // Synthetic time at the beginning of test.
+    private static final long PROFILE_OFF_START = 1;
+    // Time when warning notification should be posted,
+    private static final long PROFILE_OFF_WARNING_TIME =
+            PROFILE_OFF_START + PROFILE_OFF_TIMEOUT - TimeUnit.DAYS.toMillis(1);
+    // Time when the apps should be suspended
+    private static final long PROFILE_OFF_DEADLINE = PROFILE_OFF_START + PROFILE_OFF_TIMEOUT;
+    // Notification titles for setManagedProfileMaximumTimeOff tests:
+    private static final String PROFILE_OFF_WARNING_TITLE = "suspended_tomorrow";
+    private static final String PROFILE_OFF_SUSPENDED_TITLE = "suspended";
+
     @Override
     protected void setUp() throws Exception {
         super.setUp();
@@ -1558,20 +1576,7 @@
         dpms.approveCaCert(fourCerts.getList().get(1), userId, true);
         // a notification should be shown saying that there are two certificates left to approve.
         verify(getServices().notificationManager, timeout(1000))
-                .notifyAsUser(anyString(), anyInt(), argThat(
-                        new BaseMatcher<Notification>() {
-                            @Override
-                            public boolean matches(Object item) {
-                                final Notification noti = (Notification) item;
-                                return TEST_STRING.equals(
-                                        noti.extras.getString(Notification.EXTRA_TITLE));
-                            }
-                            @Override
-                            public void describeTo(Description description) {
-                                description.appendText(
-                                        "Notification{title=\"" + TEST_STRING + "\"}");
-                            }
-                        }), eq(user));
+                .notifyAsUser(anyString(), anyInt(), argThat(hasTitle(TEST_STRING)), eq(user));
     }
 
     /**
@@ -6272,7 +6277,214 @@
         assertThat(dpm.getAccountTypesWithManagementDisabled()).isEmpty();
     }
 
-    // admin1 is the outgoing DPC, adminAnotherPakcage is the incoming one.
+    /**
+     * Tests the case when the user doesn't turn the profile on in time, verifies that the user is
+     * warned with a notification and then the apps get suspended.
+     */
+    public void testMaximumProfileTimeOff_profileOffTimeExceeded() throws Exception {
+        prepareMocksForSetMaximumProfileTimeOff();
+
+        mContext.binder.callingUid = DpmMockContext.CALLER_UID;
+        dpm.setManagedProfileMaximumTimeOff(admin1, PROFILE_OFF_TIMEOUT);
+
+        mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
+        // The profile is running, neither alarm nor notification should be posted.
+        verify(getServices().alarmManager, never())
+                .set(anyInt(), anyLong(), any(PendingIntent.class));
+        verify(getServices().notificationManager, never())
+                .notify(anyInt(), any(Notification.class));
+        // Apps shouldn't be suspended.
+        verifyZeroInteractions(getServices().ipackageManager);
+        clearInvocations(getServices().alarmManager);
+
+        sendUserStoppedBroadcastForProfile();
+
+        // Verify the alarm was scheduled for time when the warning should be shown.
+        verify(getServices().alarmManager, times(1))
+                .set(anyInt(), eq(PROFILE_OFF_WARNING_TIME), any());
+        // But still no notification should be posted at this point.
+        verify(getServices().notificationManager, never())
+                .notify(anyInt(), any(Notification.class));
+        // Apps shouldn't be suspended.
+        verifyZeroInteractions(getServices().ipackageManager);
+        clearInvocations(getServices().alarmManager);
+
+        // Pretend the alarm went off.
+        dpms.mMockInjector.setSystemCurrentTimeMillis(PROFILE_OFF_WARNING_TIME + 10);
+        sendProfileOffDeadlineAlarmBroadcast();
+
+        // Verify the alarm was scheduled for the actual deadline this time.
+        verify(getServices().alarmManager, times(1)).set(anyInt(), eq(PROFILE_OFF_DEADLINE), any());
+        // Now the user should see a warning notification.
+        verify(getServices().notificationManager, times(1))
+                .notify(anyInt(), argThat(hasTitle(PROFILE_OFF_WARNING_TITLE)));
+        // Apps shouldn't be suspended yet.
+        verifyZeroInteractions(getServices().ipackageManager);
+        clearInvocations(getServices().alarmManager);
+        clearInvocations(getServices().notificationManager);
+
+        // Pretend the alarm went off.
+        dpms.mMockInjector.setSystemCurrentTimeMillis(PROFILE_OFF_DEADLINE + 10);
+        sendProfileOffDeadlineAlarmBroadcast();
+
+        // Verify the alarm was not set.
+        verifyZeroInteractions(getServices().alarmManager);
+        // Now the user should see a notification about suspended apps.
+        verify(getServices().notificationManager, times(1))
+                .notify(anyInt(), argThat(hasTitle(PROFILE_OFF_SUSPENDED_TITLE)));
+        // Verify that the apps are suspended.
+        verify(getServices().ipackageManager, times(1)).setPackagesSuspendedAsUser(
+                any(), eq(true), any(), any(), any(), any(), anyInt());
+    }
+
+    /**
+     * Tests the case when the user turns the profile back on long before the deadline (> 1 day).
+     */
+    public void testMaximumProfileTimeOff_turnOnBeforeWarning() throws Exception {
+        prepareMocksForSetMaximumProfileTimeOff();
+
+        mContext.binder.callingUid = DpmMockContext.CALLER_UID;
+        dpm.setManagedProfileMaximumTimeOff(admin1, PROFILE_OFF_TIMEOUT);
+
+        mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
+        sendUserStoppedBroadcastForProfile();
+        clearInvocations(getServices().alarmManager);
+        sendUserUnlockedBroadcastForProfile();
+
+        // Verify that the alarm got discharged.
+        verify(getServices().alarmManager, times(1)).cancel((PendingIntent) null);
+    }
+
+    /**
+     * Tests the case when the user turns the profile back on after the warning notification.
+     */
+    public void testMaximumProfileTimeOff_turnOnAfterWarning() throws Exception {
+        prepareMocksForSetMaximumProfileTimeOff();
+
+        mContext.binder.callingUid = DpmMockContext.CALLER_UID;
+        dpm.setManagedProfileMaximumTimeOff(admin1, PROFILE_OFF_TIMEOUT);
+
+        mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
+        sendUserStoppedBroadcastForProfile();
+
+        // Pretend the alarm went off.
+        dpms.mMockInjector.setSystemCurrentTimeMillis(PROFILE_OFF_WARNING_TIME + 10);
+        sendProfileOffDeadlineAlarmBroadcast();
+
+        clearInvocations(getServices().alarmManager);
+        clearInvocations(getServices().notificationManager);
+        sendUserUnlockedBroadcastForProfile();
+
+        // Verify that the alarm got discharged.
+        verify(getServices().alarmManager, times(1)).cancel((PendingIntent) null);
+        // Verify that the notification is removed.
+        verify(getServices().notificationManager, times(1))
+                .cancel(eq(SystemMessageProto.SystemMessage.NOTE_PERSONAL_APPS_SUSPENDED));
+    }
+
+    /**
+     * Tests the case when the user turns the profile back on when the apps are already suspended.
+     */
+    public void testMaximumProfileTimeOff_turnOnAfterDeadline() throws Exception {
+        prepareMocksForSetMaximumProfileTimeOff();
+
+        mContext.binder.callingUid = DpmMockContext.CALLER_UID;
+        dpm.setManagedProfileMaximumTimeOff(admin1, PROFILE_OFF_TIMEOUT);
+
+        mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
+        sendUserStoppedBroadcastForProfile();
+
+        // Pretend the alarm went off after the deadline.
+        dpms.mMockInjector.setSystemCurrentTimeMillis(PROFILE_OFF_DEADLINE + 10);
+        sendProfileOffDeadlineAlarmBroadcast();
+
+        clearInvocations(getServices().alarmManager);
+        clearInvocations(getServices().notificationManager);
+        clearInvocations(getServices().ipackageManager);
+
+        sendUserUnlockedBroadcastForProfile();
+
+        // Verify that the notification is removed (at this point DPC should show it).
+        verify(getServices().notificationManager, times(1))
+                .cancel(eq(SystemMessageProto.SystemMessage.NOTE_PERSONAL_APPS_SUSPENDED));
+        // Verify that the apps are NOT unsuspeded.
+        verify(getServices().ipackageManager, never()).setPackagesSuspendedAsUser(
+                any(), eq(false), any(), any(), any(), any(), anyInt());
+    }
+
+    private void sendUserUnlockedBroadcastForProfile() throws Exception {
+        when(getServices().userManager.isUserUnlocked(eq(DpmMockContext.CALLER_USER_HANDLE)))
+                .thenReturn(true);
+        final Intent unlockedIntent = new Intent(Intent.ACTION_USER_UNLOCKED)
+                .putExtra(Intent.EXTRA_USER_HANDLE, DpmMockContext.CALLER_USER_HANDLE);
+        getServices().injectBroadcast(
+                mServiceContext, unlockedIntent, DpmMockContext.CALLER_USER_HANDLE);
+        flushTasks();
+    }
+
+
+    private void sendProfileOffDeadlineAlarmBroadcast() throws Exception {
+        final Intent deadlineAlarmIntent =
+                new Intent(DevicePolicyManagerService.ACTION_PROFILE_OFF_DEADLINE);
+        getServices().injectBroadcast(
+                mServiceContext, deadlineAlarmIntent, DpmMockContext.CALLER_USER_HANDLE);
+        flushTasks();
+    }
+
+    private void sendUserStoppedBroadcastForProfile() throws Exception {
+        when(getServices().userManager.isUserUnlocked(eq(DpmMockContext.CALLER_USER_HANDLE)))
+                .thenReturn(false);
+        final Intent stoppedIntent = new Intent(Intent.ACTION_USER_STOPPED)
+                .putExtra(Intent.EXTRA_USER_HANDLE, DpmMockContext.CALLER_USER_HANDLE);
+        getServices().injectBroadcast(mServiceContext, stoppedIntent,
+                DpmMockContext.CALLER_USER_HANDLE);
+        flushTasks();
+    }
+
+    private void prepareMocksForSetMaximumProfileTimeOff() throws Exception {
+        addManagedProfile(admin1, DpmMockContext.CALLER_UID, admin1);
+        configureProfileOwnerOfOrgOwnedDevice(admin1, DpmMockContext.CALLER_USER_HANDLE);
+
+        when(getServices().userManager.isUserUnlocked()).thenReturn(true);
+
+        // Pretend our admin handles CHECK_POLICY_COMPLIANCE intent.
+        final Intent intent = new Intent(DevicePolicyManager.ACTION_CHECK_POLICY_COMPLIANCE);
+        intent.setPackage(admin1.getPackageName());
+
+        doReturn(Collections.singletonList(new ResolveInfo()))
+                .when(getServices().packageManager).queryIntentActivitiesAsUser(
+                        any(Intent.class), anyInt(), eq(DpmMockContext.CALLER_USER_HANDLE));
+
+        dpms.mMockInjector.setSystemCurrentTimeMillis(PROFILE_OFF_START);
+        // To allow creation of Notification via Notification.Builder
+        mContext.applicationInfo = mRealTestContext.getApplicationInfo();
+
+        // Setup notification titles.
+        when(mServiceContext.resources
+                .getString(R.string.personal_apps_suspended_tomorrow_title))
+                .thenReturn(PROFILE_OFF_WARNING_TITLE);
+        when(mServiceContext.resources
+                .getString(R.string.personal_apps_suspended_title))
+                .thenReturn(PROFILE_OFF_SUSPENDED_TITLE);
+
+        clearInvocations(getServices().ipackageManager);
+    }
+
+    private static Matcher<Notification> hasTitle(String expected) {
+        return new BaseMatcher<Notification>() {
+            @Override
+            public boolean matches(Object item) {
+                final Notification notification = (Notification) item;
+                return expected.equals(notification.extras.getString(Notification.EXTRA_TITLE));
+            }
+            @Override
+            public void describeTo(Description description) {
+                description.appendText("Notification{title=\"" + expected + "\"}");
+            }
+        };
+    }
+
+    // admin1 is the outgoing DPC, adminAnotherPackage is the incoming one.
     private void assertDeviceOwnershipRevertedWithFakeTransferMetadata() throws Exception {
         writeFakeTransferMetadataFile(UserHandle.USER_SYSTEM,
                 TransferOwnershipMetadataManager.ADMIN_TYPE_DEVICE_OWNER);
@@ -6299,7 +6511,7 @@
         mServiceContext.binder.restoreCallingIdentity(ident);
     }
 
-    // admin1 is the outgoing DPC, adminAnotherPakcage is the incoming one.
+    // admin1 is the outgoing DPC, adminAnotherPackage is the incoming one.
     private void assertProfileOwnershipRevertedWithFakeTransferMetadata() throws Exception {
         writeFakeTransferMetadataFile(DpmMockContext.CALLER_USER_HANDLE,
                 TransferOwnershipMetadataManager.ADMIN_TYPE_PROFILE_OWNER);
diff --git a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
index 7b3417a..b69cc47 100644
--- a/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/AutomaticBrightnessControllerTest.java
@@ -107,9 +107,10 @@
                 eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
         SensorEventListener listener = listenerCaptor.getValue();
 
-        // Set up system to return 5 as a brightness value
+        // Set up system to return 0.02f as a brightness value
         float lux1 = 100.0f;
-        float normalizedBrightness1 = 0.0158f; //float equivalent of 5 out of 255
+        // Brightness as float (from 0.0f to 1.0f)
+        float normalizedBrightness1 = 0.02f;
         when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux1))
                 .thenReturn(lux1);
         when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux1))
@@ -120,14 +121,14 @@
         // This is the important bit: When the new brightness is set, make sure the new
         // brightening threshold is beyond the maximum brightness value...so that we can test that
         // our threshold clamping works.
-        when(mScreenBrightnessThresholds.getBrighteningThreshold(5)).thenReturn(1.0f);
+        when(mScreenBrightnessThresholds.getBrighteningThreshold(normalizedBrightness1))
+                .thenReturn(1.0f);
 
         // Send new sensor value and verify
         listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, (int) lux1));
-        assertEquals(5, controller.getAutomaticScreenBrightness());
+        assertEquals(normalizedBrightness1, controller.getAutomaticScreenBrightness(), 0.001f);
 
-
-        // Set up system to return 255 as a brightness value
+        // Set up system to return 0.0f (minimum possible brightness) as a brightness value
         float lux2 = 10.0f;
         float normalizedBrightness2 = 0.0f;
         when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux2))
@@ -139,7 +140,7 @@
 
         // Send new sensor value and verify
         listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, (int) lux2));
-        assertEquals(1, controller.getAutomaticScreenBrightness());
+        assertEquals(normalizedBrightness2, controller.getAutomaticScreenBrightness(), 0.001f);
     }
 
     @Test
@@ -153,9 +154,9 @@
                 eq(INITIAL_LIGHT_SENSOR_RATE * 1000), any(Handler.class));
         SensorEventListener listener = listenerCaptor.getValue();
 
-        // Set up system to return 250 as a brightness value
+        // Set up system to return 0.98f as a brightness value
         float lux1 = 100.0f;
-        float normalizedBrightness1 = 0.981f; //float equivalent of 250 out of 255
+        float normalizedBrightness1 = 0.98f;
         when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux1))
                 .thenReturn(lux1);
         when(mAmbientBrightnessThresholds.getDarkeningThreshold(lux1))
@@ -166,14 +167,15 @@
         // This is the important bit: When the new brightness is set, make sure the new
         // brightening threshold is beyond the maximum brightness value...so that we can test that
         // our threshold clamping works.
-        when(mScreenBrightnessThresholds.getBrighteningThreshold(250)).thenReturn(260.0f);
+        when(mScreenBrightnessThresholds.getBrighteningThreshold(normalizedBrightness1))
+                .thenReturn(1.1f);
 
         // Send new sensor value and verify
         listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, (int) lux1));
-        assertEquals(250, controller.getAutomaticScreenBrightness());
+        assertEquals(normalizedBrightness1, controller.getAutomaticScreenBrightness(), 0.001f);
 
 
-        // Set up system to return 255 as a brightness value
+        // Set up system to return 1.0f as a brightness value (brightness_max)
         float lux2 = 110.0f;
         float normalizedBrightness2 = 1.0f;
         when(mAmbientBrightnessThresholds.getBrighteningThreshold(lux2))
@@ -185,7 +187,7 @@
 
         // Send new sensor value and verify
         listener.onSensorChanged(TestUtils.createSensorEvent(lightSensor, (int) lux2));
-        assertEquals(255, controller.getAutomaticScreenBrightness());
+        assertEquals(normalizedBrightness2, controller.getAutomaticScreenBrightness(), 0.001f);
     }
 
     @Test
@@ -204,10 +206,10 @@
 
         // User sets brightness to 100
         controller.configure(true /* enable */, null /* configuration */,
-                100 /* brightness */, true /* userChangedBrightness */, 0 /* adjustment */,
+                0.5f /* brightness */, true /* userChangedBrightness */, 0 /* adjustment */,
                 false /* userChanged */, DisplayPowerRequest.POLICY_BRIGHT);
 
         // There should be a user data point added to the mapper.
-        verify(mBrightnessMappingStrategy).addUserDataPoint(1000f, 100);
+        verify(mBrightnessMappingStrategy).addUserDataPoint(1000f, 0.5f);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/emergency/EmergencyAffordanceServiceTest.java b/services/tests/servicestests/src/com/android/server/emergency/EmergencyAffordanceServiceTest.java
new file mode 100644
index 0000000..d438a0e
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/emergency/EmergencyAffordanceServiceTest.java
@@ -0,0 +1,486 @@
+/*
+ * 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.emergency;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.verify;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.telephony.SubscriptionInfo;
+import android.telephony.SubscriptionManager;
+import android.telephony.SubscriptionManager.OnSubscriptionsChangedListener;
+import android.telephony.TelephonyManager;
+import android.test.mock.MockContentResolver;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.internal.util.test.BroadcastInterceptingContext;
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.server.SystemService;
+
+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;
+import java.util.function.Supplier;
+
+/**
+ * Unit test for EmergencyAffordanceService (EAS for short) which determines when
+ * should we enable Emergency Affordance feature (EA for short).
+ *
+ * Please refer to https://source.android.com/devices/tech/connect/emergency-affordance
+ * to see the details of the feature.
+ */
+@RunWith(AndroidTestingRunner.class)
+@TestableLooper.RunWithLooper
+public class EmergencyAffordanceServiceTest {
+
+    // Default country ISO that should enable EA. Value comes from resource
+    // com.android.internal.R.array.config_emergency_iso_country_codes
+    private static final String EMERGENCY_ISO_CODE = "in";
+    // Randomly picked country ISO that should not enable EA.
+    private static final String NON_EMERGENCY_ISO_CODE = "us";
+
+    // Valid values for Settings.Global.EMERGENCY_AFFORDANCE_NEEDED
+    private static final int OFF = 0; // which means feature disabled
+    private static final int ON  = 1; // which means feature enabled
+
+    private static final int ACTIVE_MODEM_COUNT = 2;
+
+    @Mock private Resources mResources;
+    @Mock private SubscriptionManager mSubscriptionManager;
+    @Mock private TelephonyManager mTelephonyManager;
+
+    private TestContext mServiceContext;
+    private MockContentResolver mContentResolver;
+    private OnSubscriptionsChangedListener mSubscriptionChangedListener;
+    private EmergencyAffordanceService mService;
+
+    // Testable Context that mocks resources, content resolver and system services
+    private class TestContext extends BroadcastInterceptingContext {
+        TestContext(Context base) {
+            super(base);
+        }
+
+        @Override
+        public ContentResolver getContentResolver() {
+            return mContentResolver;
+        }
+
+        @Override
+        public Resources getResources() {
+            return mResources;
+        }
+
+        @Override
+        public Object getSystemService(String name) {
+            switch (name) {
+                case Context.TELEPHONY_SUBSCRIPTION_SERVICE:
+                    return mSubscriptionManager;
+                case Context.TELEPHONY_SERVICE:
+                    return mTelephonyManager;
+                default:
+                    return super.getSystemService(name);
+            }
+        }
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        doReturn(new String[] { EMERGENCY_ISO_CODE }).when(mResources)
+                .getStringArray(com.android.internal.R.array.config_emergency_iso_country_codes);
+
+        final Context context = InstrumentationRegistry.getContext();
+        mServiceContext = new TestContext(context);
+        mContentResolver = new MockContentResolver(mServiceContext);
+        mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
+
+        // Initialize feature off, to have constant starting
+        Settings.Global.putInt(mContentResolver, Settings.Global.EMERGENCY_AFFORDANCE_NEEDED, 0);
+        mService = new EmergencyAffordanceService(mServiceContext);
+    }
+
+    /**
+     * Verify if the device is not voice capable, the feature should be disabled.
+     */
+    @Test
+    public void testSettings_shouldBeOff_whenVoiceCapableIsFalse() throws Exception {
+        // Given: the device is not voice capable
+        // When:  setup device and boot service
+        setUpDevice(false /* withVoiceCapable */, true /* withEmergencyIsoInSim */,
+                true /* withEmergencyIsoInCell */);
+
+        // Then: EA setting will should be 0
+        verifyEmergencyAffordanceNeededSettings(OFF);
+    }
+
+    /**
+     * Verify the voice capable device is booted up without EA-enabled cell network, with
+     * no EA-enabled SIM installed, feature should be disabled.
+     */
+    @Test
+    public void testSettings_shouldBeOff_whenWithoutEAEanbledNetworkNorSim() throws Exception {
+        // Given: the device is voice capble, no EA-enable SIM, no EA-enabled Cell
+        setUpDevice(true /* withVoiceCapable */, false /* withEmergencyIsoInSim */,
+                false /* withEmergencyIsoInCell */);
+
+        // Then: EA setting will should be 0
+        verifyEmergencyAffordanceNeededSettings(OFF);
+    }
+
+    /**
+     * Verify the voice capable device is booted up with EA-enabled SIM installed, the
+     * feature should be enabled.
+     */
+    @Test
+    public void testSettings_shouldBeOn_whenBootUpWithEAEanbledSim() throws Exception {
+        // Given: the device is voice capble, with EA-enable SIM, no EA-enabled Cell
+        setUpDevice(true /* withVoiceCapable */, true /* withEmergencyIsoInSim */,
+                false /* withEmergencyIsoInCell */);
+
+        // Then: EA setting will immediately update to 1
+        verifyEmergencyAffordanceNeededSettings(ON);
+    }
+
+    /**
+     * Verify the voice capable device is booted up with EA-enabled Cell network, the
+     * feature should be enabled.
+     */
+    @Test
+    public void testSettings_shouldBeOn_whenBootUpWithEAEanbledCell() throws Exception {
+        // Given: the device is voice capble, with EA-enable SIM, with EA-enabled Cell
+        setUpDevice(true /* withVoiceCapable */, false /* withEmergencyIsoInSim */,
+                true /* withEmergencyIsoInCell */);
+
+        // Then: EA setting will immediately update to 1
+        verifyEmergencyAffordanceNeededSettings(ON);
+    }
+
+    /**
+     * Verify when device boot up with no EA-enabled SIM, but later install one,
+     * feature should be enabled.
+     */
+    @Test
+    public void testSettings_shouldBeOn_whenSubscriptionInfoChangedWithEmergencyIso()
+            throws Exception {
+        // Given: the device is voice capable, boot up with no EA-enabled SIM, no EA-enabled Cell
+        setUpDevice(true /* withVoiceCapable */, false/* withEmergencyIsoInSim */,
+                false /* withEmergencyIsoInCell */);
+
+        // When: Insert EA-enabled SIM and get notified
+        setUpSim(true /* withEmergencyIsoInSim */);
+        mSubscriptionChangedListener.onSubscriptionsChanged();
+
+        // Then: EA Setting will update to 1
+        verifyEmergencyAffordanceNeededSettings(ON);
+    }
+
+    /**
+     * Verify when feature was on, device re-insert with no EA-enabled SIMs,
+     * feature should be disabled.
+     */
+    @Test
+    public void testSettings_shouldBeOff_whenSubscriptionInfoChangedWithoutEmergencyIso()
+            throws Exception {
+        // Given: the device is voice capable, no EA-enabled Cell, with EA-enabled SIM
+        setUpDevice(true /* withVoiceCapable */, true /* withEmergencyIsoInSim */,
+                false /* withEmergencyIsoInCell */);
+
+        // When: All SIMs are replaced with EA-disabled ones.
+        setUpSim(false /* withEmergencyIsoInSim */);
+        mSubscriptionChangedListener.onSubscriptionsChanged();
+
+        // Then: EA Setting will update to 0
+        verifyEmergencyAffordanceNeededSettings(OFF);
+    }
+
+    /**
+     * Verify when device boot up with no EA-enabled Cell, but later move into one,
+     * feature should be enabled.
+     */
+    @Test
+    public void testSettings_shouldBeOn_whenCountryIsoChangedWithEmergencyIso()
+            throws Exception {
+        // Given: the device is voice capable, boot up with no EA-enabled SIM, no EA-enabled Cell
+        setUpDevice(true /* withVoiceCapable */, false/* withEmergencyIsoInSim */,
+                false /* withEmergencyIsoInCell */);
+
+        // When: device locale change to EA-enabled Cell and get notified
+        resetCell(true /* withEmergencyIsoInSim */);
+        sendBroadcastNetworkCountryChanged(EMERGENCY_COUNTRY_ISO);
+
+        // Then: EA Setting will update to 1
+        verifyEmergencyAffordanceNeededSettings(ON);
+    }
+
+    /**
+     * Verify when device boot up with  EA-enabled Cell, but later move out of it,
+     * feature should be enabled.
+     */
+    @Test
+    public void testSettings_shouldBeOff_whenCountryIsoChangedWithoutEmergencyIso()
+            throws Exception {
+        // Given: the device is voice capable, boot up with no EA-enabled SIM, with EA-enabled Cell
+        setUpDevice(true /* withVoiceCapable */, false/* withEmergencyIsoInSim */,
+                true /* withEmergencyIsoInCell */);
+
+        // When: device locale change to no EA-enabled Cell and get notified
+        resetCell(false /* withEmergencyIsoInSim */);
+        sendBroadcastNetworkCountryChanged(NON_EMERGENCY_COUNTRY_ISO);
+
+        // Then: EA Setting will update to 0
+        verifyEmergencyAffordanceNeededSettings(OFF);
+    }
+    /**
+     * Verify if device is not in EA-enabled Mobile Network without EA-enable SIM(s) installed,
+     * when receive SubscriptionInfo change, the feature should not be enabled.
+     */
+    @Test
+    public void testSettings_shouldBeOff_whenNoEmergencyIsoInCellNorSim() throws Exception {
+        // Given: the device is voice capable, no EA-enabled Cell, no EA-enabled SIM
+        setUpDevice(true /* withVoiceCapable */, false /* withEmergencyIsoInSim */,
+                false /* withEmergencyIsoInCell */);
+
+        // When: Subscription changed event received
+        mSubscriptionChangedListener.onSubscriptionsChanged();
+
+        // Then: EA Settings should be 0
+        verifyEmergencyAffordanceNeededSettings(OFF);
+    }
+
+    /**
+     * Verify while feature was on, when device receive empty country iso change, while APM is
+     * enabled, feature status should keep on.
+     */
+    @Test
+    public void testSettings_shouldOn_whenReceiveEmptyISOWithAPMEnabled() throws Exception {
+        // Given: the device is voice capable,  no EA-enabled SIM, with EA-enabled Cell
+        setUpDevice(true /* withVoiceCapable */, false /* withEmergencyIsoInSim */,
+                true /* withEmergencyIsoInCell */);
+
+        // Then: EA Settings will update to 1
+        verifyEmergencyAffordanceNeededSettings(ON);
+
+        // When: Airplane mode is enabled, and we receive EMPTY ISO in locale change
+        setAirplaneMode(true);
+        sendBroadcastNetworkCountryChanged(EMPTY_COUNTRY_ISO);
+
+        // Then: EA Settings will keep to 1
+        verifyEmergencyAffordanceNeededSettings(ON);
+    }
+
+    /**
+     * Verify while feature was on, when device receive empty country iso change, while APM is
+     * disabled, feature should be disabled.
+     */
+    @Test
+    public void testSettings_shouldOff_whenReceiveEmptyISOWithAPMDisabled() throws Exception {
+        // Given: the device is voice capable,  no EA-enabled SIM, with EA-enabled Cell
+        setUpDevice(true /* withVoiceCapable */, false /* withEmergencyIsoInSim */,
+                true /* withEmergencyIsoInCell */);
+
+        // Then: EA Settings will update to 1
+        verifyEmergencyAffordanceNeededSettings(ON);
+
+        // When: Airplane mode is disabled, and we receive valid empty ISO in locale change
+        setUpCell(false /* withEmergencyIsoInCell */);
+        setAirplaneMode(false);
+        sendBroadcastNetworkCountryChanged(EMPTY_COUNTRY_ISO);
+
+        // Then: EA Settings will keep to 0
+        verifyEmergencyAffordanceNeededSettings(OFF);
+    }
+
+    /**
+     * Verify when airplane mode is turn on and off in cell network with EA-enabled ISO,
+     * feature should keep enabled.
+     */
+    @Test
+    public void testSettings_shouldBeOn_whenAirplaneModeOnOffWithEmergencyIsoInCell()
+            throws Exception {
+        // Given: the device is voice capable,  no EA-enabled SIM, with EA-enabled Cell
+        setUpDevice(true /* withVoiceCapable */, false /* withEmergencyIsoInSim */,
+                true /* withEmergencyIsoInCell */);
+
+        // When: Device receive locale change with EA-enabled iso
+        sendBroadcastNetworkCountryChanged(EMERGENCY_COUNTRY_ISO);
+
+        // When: Airplane mode is disabled
+        setAirplaneMode(false);
+
+        // Then: EA Settings will keep with 1
+        verifyEmergencyAffordanceNeededSettings(ON);
+
+        // When: Airplane mode is enabled
+        setAirplaneMode(true);
+
+        // Then: EA Settings is still 1
+        verifyEmergencyAffordanceNeededSettings(ON);
+    }
+
+    /**
+     * Verify when airplane mode is turn on and off with EA-enabled ISO in SIM,
+     * feature should keep enabled.
+     */
+    @Test
+    public void testSettings_shouldBeOn_whenAirplaneModeOnOffWithEmergencyIsoInSim()
+            throws Exception {
+        // Given: the device is voice capable, no EA-enabled Cell Network, with EA-enabled SIM
+        setUpDevice(true /* withVoiceCapable */, true /* withEmergencyIsoInSim */,
+                false /* withEmergencyIsoInCell */);
+
+        // When: Airplane mode is disabled
+        setAirplaneMode(false);
+
+        // Then: EA Settings will keep with 1
+        verifyEmergencyAffordanceNeededSettings(ON);
+
+        // When: Airplane mode is enabled
+        setAirplaneMode(true);
+
+        // Then: EA Settings is still 1
+        verifyEmergencyAffordanceNeededSettings(ON);
+    }
+
+    // EAS reads voice capable during boot up and cache it. To test non voice capable device,
+    // we can not put this in setUp
+    private void setUpDevice(boolean withVoiceCapable, boolean withEmergencyIsoInSim,
+            boolean withEmergencyIsoInCell) throws Exception {
+        setUpVoiceCapable(withVoiceCapable);
+
+        setUpSim(withEmergencyIsoInSim);
+
+        setUpCell(withEmergencyIsoInCell);
+
+        // bypass onStart which is used to publish binder service and need sepolicy policy update
+        // mService.onStart();
+
+        mService.onBootPhase(SystemService.PHASE_THIRD_PARTY_APPS_CAN_START);
+
+        if (!withVoiceCapable) {
+            return;
+        }
+
+        captureSubscriptionChangeListener();
+    }
+
+    private void setUpVoiceCapable(boolean voiceCapable) {
+        doReturn(voiceCapable).when(mTelephonyManager).isVoiceCapable();
+    }
+
+    private static final Supplier<String> EMPTY_COUNTRY_ISO = () -> "";
+    private static final Supplier<String> EMERGENCY_COUNTRY_ISO = () -> EMERGENCY_ISO_CODE;
+    private static final Supplier<String> NON_EMERGENCY_COUNTRY_ISO = () -> NON_EMERGENCY_ISO_CODE;
+    private void sendBroadcastNetworkCountryChanged(Supplier<String> countryIso) {
+        Intent intent = new Intent(TelephonyManager.ACTION_NETWORK_COUNTRY_CHANGED);
+        intent.putExtra(TelephonyManager.EXTRA_NETWORK_COUNTRY, countryIso.get());
+        SubscriptionManager.putPhoneIdAndSubIdExtra(intent, 0);
+        mServiceContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+    }
+
+    private void setUpSim(boolean withEmergencyIsoInSim) {
+        List<SubscriptionInfo> subInfos = getSubscriptionInfoList(withEmergencyIsoInSim);
+        doReturn(subInfos).when(mSubscriptionManager).getActiveSubscriptionInfoList();
+    }
+
+    private void setUpCell(boolean withEmergencyIsoInCell) {
+        doReturn(ACTIVE_MODEM_COUNT).when(mTelephonyManager).getActiveModemCount();
+        doReturn(NON_EMERGENCY_ISO_CODE).when(mTelephonyManager).getNetworkCountryIso(0);
+        doReturn(withEmergencyIsoInCell ? EMERGENCY_ISO_CODE : NON_EMERGENCY_ISO_CODE)
+                .when(mTelephonyManager).getNetworkCountryIso(1);
+    }
+
+    private void resetCell(boolean withEmergencyIsoInCell) {
+        doReturn(withEmergencyIsoInCell ? EMERGENCY_ISO_CODE : NON_EMERGENCY_ISO_CODE)
+                .when(mTelephonyManager).getNetworkCountryIso(1);
+    }
+
+    private void captureSubscriptionChangeListener() {
+        final ArgumentCaptor<OnSubscriptionsChangedListener> subChangedListenerCaptor =
+                ArgumentCaptor.forClass(OnSubscriptionsChangedListener.class);
+        verify(mSubscriptionManager).addOnSubscriptionsChangedListener(
+                subChangedListenerCaptor.capture());
+        mSubscriptionChangedListener = subChangedListenerCaptor.getValue();
+    }
+
+    private void setAirplaneMode(boolean enabled) {
+        // Change the system settings
+        Settings.Global.putInt(mContentResolver, Settings.Global.AIRPLANE_MODE_ON,
+                enabled ? 1 : 0);
+
+        // Post the intent
+        final Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+        intent.putExtra("state", enabled);
+        mServiceContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+    }
+
+    private List<SubscriptionInfo> getSubscriptionInfoList(boolean withEmergencyIso) {
+        List<SubscriptionInfo> subInfos = new ArrayList<>(2);
+
+        // Test with Multiple SIMs. SIM1 is a non-EA SIM
+        // Only country iso is valuable, all other info are filled with dummy values
+        SubscriptionInfo subInfo = new SubscriptionInfo(1, "890126042XXXXXXXXXXX", 0, "T-mobile",
+                "T-mobile", 0, 255, "12345", 0, null,
+                "310", "226", NON_EMERGENCY_ISO_CODE, false, null, null);
+        subInfos.add(subInfo);
+
+        // SIM2 can configured to be non-EA or EA SIM according parameter withEmergencyIso
+        SubscriptionInfo subInfo2 = new SubscriptionInfo(1, "890126042XXXXXXXXXXX", 0, "Airtel",
+                "Aritel", 0, 255, "12345", 0, null, "310", "226",
+                withEmergencyIso ? EMERGENCY_ISO_CODE : NON_EMERGENCY_ISO_CODE,
+                false, null, null);
+        subInfos.add(subInfo2);
+
+        return subInfos;
+    }
+
+    // EAS has handler thread to perform heavy work, while FakeSettingProvider does not support
+    // ContentObserver. To make sure consistent result, we use a simple sleep & retry to wait for
+    // real work finished before verify result.
+    private static final int TIME_DELAY_BEFORE_VERIFY_IN_MS = 50;
+    private static final int RETRIES_BEFORE_VERIFY = 20;
+    private void verifyEmergencyAffordanceNeededSettings(int expected) throws Exception {
+        try {
+            int ct = 0;
+            int actual = -1;
+            while (ct++ < RETRIES_BEFORE_VERIFY
+                    && (actual = Settings.Global.getInt(mContentResolver,
+                    Settings.Global.EMERGENCY_AFFORDANCE_NEEDED)) != expected) {
+                Thread.sleep(TIME_DELAY_BEFORE_VERIFY_IN_MS);
+            }
+            assertEquals(expected, actual);
+        } catch (Settings.SettingNotFoundException e) {
+            fail("SettingNotFoundException thrown for Settings.Global.EMERGENCY_AFFORDANCE_NEEDED");
+        }
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
index 728e149..e16f314 100644
--- a/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/people/data/DataManagerTest.java
@@ -215,6 +215,8 @@
         mDataManager = new DataManager(mContext, mInjector);
         mDataManager.initialize();
 
+        when(mShortcutServiceInternal.isSharingShortcut(anyInt(), anyString(), anyString(),
+                anyString(), anyInt(), any())).thenReturn(true);
         verify(mShortcutServiceInternal).addShortcutChangeCallback(
                 mShortcutChangeCallbackCaptor.capture());
         mShortcutChangeCallback = mShortcutChangeCallbackCaptor.getValue();
diff --git a/services/tests/servicestests/src/com/android/server/power/AttentionDetectorTest.java b/services/tests/servicestests/src/com/android/server/power/AttentionDetectorTest.java
index c4289ef..e7e8aca 100644
--- a/services/tests/servicestests/src/com/android/server/power/AttentionDetectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/AttentionDetectorTest.java
@@ -158,17 +158,6 @@
     }
 
     @Test
-    public void testOnUserActivity_disablesSettingIfNotSufficientPermissions() {
-        when(mPackageManager.checkPermission(any(), any())).thenReturn(
-                PackageManager.PERMISSION_DENIED);
-
-        registerAttention();
-        boolean enabled = Settings.Secure.getIntForUser(getContext().getContentResolver(),
-                Settings.Secure.ADAPTIVE_SLEEP, 0, UserHandle.USER_CURRENT) == 1;
-        assertFalse(enabled);
-    }
-
-    @Test
     public void testOnUserActivity_doesntCrashIfNoAttentionService() {
         mAttentionManagerInternal = null;
         registerAttention();
diff --git a/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java
index 965304f..21af356 100644
--- a/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/tv/tunerresourcemanager/TunerResourceManagerServiceTest.java
@@ -26,6 +26,7 @@
 import android.media.tv.TvInputManager;
 import android.media.tv.TvInputService;
 import android.media.tv.tuner.frontend.FrontendSettings;
+import android.media.tv.tunerresourcemanager.CasSessionRequest;
 import android.media.tv.tunerresourcemanager.IResourcesReclaimListener;
 import android.media.tv.tunerresourcemanager.ResourceClientProfile;
 import android.media.tv.tunerresourcemanager.TunerDemuxRequest;
@@ -34,7 +35,6 @@
 import android.media.tv.tunerresourcemanager.TunerFrontendRequest;
 import android.media.tv.tunerresourcemanager.TunerLnbRequest;
 import android.media.tv.tunerresourcemanager.TunerResourceManager;
-import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.test.InstrumentationRegistry;
@@ -236,12 +236,8 @@
         TunerFrontendRequest request =
                 new TunerFrontendRequest(0 /*clientId*/, FrontendSettings.TYPE_DVBT);
         int[] frontendHandle = new int[1];
-        try {
-            assertThat(mTunerResourceManagerService
-                    .requestFrontendInternal(request, frontendHandle)).isFalse();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        assertThat(mTunerResourceManagerService
+                .requestFrontendInternal(request, frontendHandle)).isFalse();
         assertThat(mTunerResourceManagerService.getResourceIdFromHandle(frontendHandle[0]))
                 .isEqualTo(TunerResourceManager.INVALID_RESOURCE_HANDLE);
     }
@@ -264,12 +260,8 @@
         TunerFrontendRequest request =
                 new TunerFrontendRequest(clientId[0] /*clientId*/, FrontendSettings.TYPE_DVBT);
         int[] frontendHandle = new int[1];
-        try {
-            assertThat(mTunerResourceManagerService
-                    .requestFrontendInternal(request, frontendHandle)).isFalse();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        assertThat(mTunerResourceManagerService
+                .requestFrontendInternal(request, frontendHandle)).isFalse();
         assertThat(mTunerResourceManagerService.getResourceIdFromHandle(frontendHandle[0]))
                 .isEqualTo(TunerResourceManager.INVALID_RESOURCE_HANDLE);
     }
@@ -296,12 +288,8 @@
         TunerFrontendRequest request =
                 new TunerFrontendRequest(clientId[0] /*clientId*/, FrontendSettings.TYPE_DVBT);
         int[] frontendHandle = new int[1];
-        try {
-            assertThat(mTunerResourceManagerService
-                    .requestFrontendInternal(request, frontendHandle)).isTrue();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        assertThat(mTunerResourceManagerService
+                .requestFrontendInternal(request, frontendHandle)).isTrue();
         assertThat(mTunerResourceManagerService.getResourceIdFromHandle(frontendHandle[0]))
                 .isEqualTo(0);
     }
@@ -334,23 +322,15 @@
         int[] frontendHandle = new int[1];
         TunerFrontendRequest request =
                 new TunerFrontendRequest(clientId1[0] /*clientId*/, FrontendSettings.TYPE_DVBT);
-        try {
-            assertThat(mTunerResourceManagerService
-                    .requestFrontendInternal(request, frontendHandle)).isTrue();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        assertThat(mTunerResourceManagerService
+                .requestFrontendInternal(request, frontendHandle)).isTrue();
         assertThat(mTunerResourceManagerService.getResourceIdFromHandle(frontendHandle[0]))
                 .isEqualTo(infos[0].getId());
 
         request =
                 new TunerFrontendRequest(clientId0[0] /*clientId*/, FrontendSettings.TYPE_DVBT);
-        try {
-            assertThat(mTunerResourceManagerService
-                    .requestFrontendInternal(request, frontendHandle)).isTrue();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        assertThat(mTunerResourceManagerService
+                .requestFrontendInternal(request, frontendHandle)).isTrue();
         assertThat(mTunerResourceManagerService.getResourceIdFromHandle(frontendHandle[0]))
                 .isEqualTo(infos[1].getId());
         assertThat(mTunerResourceManagerService.getFrontendResource(infos[1].getId())
@@ -394,32 +374,20 @@
         TunerFrontendRequest request =
                 new TunerFrontendRequest(clientId0[0] /*clientId*/, FrontendSettings.TYPE_DVBT);
         int[] frontendHandle = new int[1];
-        try {
-            assertThat(mTunerResourceManagerService
-                    .requestFrontendInternal(request, frontendHandle)).isTrue();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        assertThat(mTunerResourceManagerService
+                .requestFrontendInternal(request, frontendHandle)).isTrue();
 
         request =
                 new TunerFrontendRequest(clientId1[0] /*clientId*/, FrontendSettings.TYPE_DVBT);
-        try {
-            assertThat(mTunerResourceManagerService
-                    .requestFrontendInternal(request, frontendHandle)).isFalse();
-            assertThat(listener.isRelaimed()).isFalse();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        assertThat(mTunerResourceManagerService
+                .requestFrontendInternal(request, frontendHandle)).isFalse();
+        assertThat(listener.isRelaimed()).isFalse();
 
         request =
                 new TunerFrontendRequest(clientId1[0] /*clientId*/, FrontendSettings.TYPE_DVBS);
-        try {
-            assertThat(mTunerResourceManagerService
-                    .requestFrontendInternal(request, frontendHandle)).isFalse();
-            assertThat(listener.isRelaimed()).isFalse();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        assertThat(mTunerResourceManagerService
+                .requestFrontendInternal(request, frontendHandle)).isFalse();
+        assertThat(listener.isRelaimed()).isFalse();
     }
 
     @Test
@@ -456,12 +424,8 @@
         TunerFrontendRequest request =
                 new TunerFrontendRequest(clientId0[0] /*clientId*/, FrontendSettings.TYPE_DVBT);
         int[] frontendHandle = new int[1];
-        try {
-            assertThat(mTunerResourceManagerService
-                    .requestFrontendInternal(request, frontendHandle)).isTrue();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        assertThat(mTunerResourceManagerService
+                .requestFrontendInternal(request, frontendHandle)).isTrue();
         assertThat(mTunerResourceManagerService.getResourceIdFromHandle(frontendHandle[0]))
                 .isEqualTo(infos[0].getId());
         assertThat(mTunerResourceManagerService.getClientProfile(clientId0[0])
@@ -470,12 +434,8 @@
 
         request =
                 new TunerFrontendRequest(clientId1[0] /*clientId*/, FrontendSettings.TYPE_DVBS);
-        try {
-            assertThat(mTunerResourceManagerService
-                    .requestFrontendInternal(request, frontendHandle)).isTrue();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        assertThat(mTunerResourceManagerService
+                .requestFrontendInternal(request, frontendHandle)).isTrue();
         assertThat(mTunerResourceManagerService.getResourceIdFromHandle(frontendHandle[0]))
                 .isEqualTo(infos[1].getId());
         assertThat(mTunerResourceManagerService.getFrontendResource(infos[0].getId())
@@ -511,12 +471,8 @@
         TunerFrontendRequest request =
                 new TunerFrontendRequest(clientId[0] /*clientId*/, FrontendSettings.TYPE_DVBT);
         int[] frontendHandle = new int[1];
-        try {
-            assertThat(mTunerResourceManagerService
-                    .requestFrontendInternal(request, frontendHandle)).isTrue();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        assertThat(mTunerResourceManagerService
+                .requestFrontendInternal(request, frontendHandle)).isTrue();
         int frontendId = mTunerResourceManagerService.getResourceIdFromHandle(frontendHandle[0]);
         assertThat(frontendId).isEqualTo(infos[0].getId());
         assertThat(mTunerResourceManagerService
@@ -534,6 +490,99 @@
     }
 
     @Test
+    public void requestCasTest_NoCasAvailable_RequestWithHigherPriority() {
+        // Register clients
+        ResourceClientProfile[] profiles = new ResourceClientProfile[2];
+        profiles[0] = new ResourceClientProfile("0" /*sessionId*/,
+                TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
+        profiles[1] = new ResourceClientProfile("1" /*sessionId*/,
+                TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
+        int[] clientPriorities = {100, 500};
+        int[] clientId0 = new int[1];
+        int[] clientId1 = new int[1];
+        TestResourcesReclaimListener listener = new TestResourcesReclaimListener();
+        mTunerResourceManagerService.registerClientProfileInternal(
+                profiles[0], listener, clientId0);
+        assertThat(clientId0[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
+        mTunerResourceManagerService.getClientProfile(clientId0[0])
+                .setPriority(clientPriorities[0]);
+        mTunerResourceManagerService.registerClientProfileInternal(
+                profiles[1], new TestResourcesReclaimListener(), clientId1);
+        assertThat(clientId1[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
+        mTunerResourceManagerService.getClientProfile(clientId1[0])
+                .setPriority(clientPriorities[1]);
+
+        // Init cas resources.
+        mTunerResourceManagerService.updateCasInfoInternal(1 /*casSystemId*/, 2 /*maxSessionNum*/);
+
+        CasSessionRequest request = new CasSessionRequest(clientId0[0], 1 /*casSystemId*/);
+        int[] casSessionHandle = new int[1];
+        // Request for 2 cas sessions.
+        assertThat(mTunerResourceManagerService
+                .requestCasSessionInternal(request, casSessionHandle)).isTrue();
+        assertThat(mTunerResourceManagerService
+                .requestCasSessionInternal(request, casSessionHandle)).isTrue();
+        assertThat(mTunerResourceManagerService.getResourceIdFromHandle(casSessionHandle[0]))
+                .isEqualTo(1);
+        assertThat(mTunerResourceManagerService.getClientProfile(clientId0[0])
+                .getInUseCasSystemId()).isEqualTo(1);
+        assertThat(mTunerResourceManagerService.getCasResource(1)
+                .getOwnerClientIds()).isEqualTo(new HashSet<Integer>(Arrays.asList(clientId0[0])));
+        assertThat(mTunerResourceManagerService.getCasResource(1).isFullyUsed()).isTrue();
+
+        request = new CasSessionRequest(clientId1[0], 1);
+        assertThat(mTunerResourceManagerService
+                .requestCasSessionInternal(request, casSessionHandle)).isTrue();
+        assertThat(mTunerResourceManagerService.getResourceIdFromHandle(casSessionHandle[0]))
+                .isEqualTo(1);
+        assertThat(mTunerResourceManagerService.getClientProfile(clientId1[0])
+                .getInUseCasSystemId()).isEqualTo(1);
+        assertThat(mTunerResourceManagerService.getClientProfile(clientId0[0])
+                .getInUseCasSystemId()).isEqualTo(ClientProfile.INVALID_RESOURCE_ID);
+        assertThat(mTunerResourceManagerService.getCasResource(1)
+                .getOwnerClientIds()).isEqualTo(new HashSet<Integer>(Arrays.asList(clientId1[0])));
+        assertThat(mTunerResourceManagerService.getCasResource(1).isFullyUsed()).isFalse();
+        assertThat(listener.isRelaimed()).isTrue();
+    }
+
+    @Test
+    public void releaseCasTest() {
+        // Register clients
+        ResourceClientProfile[] profiles = new ResourceClientProfile[1];
+        profiles[0] = new ResourceClientProfile("0" /*sessionId*/,
+                TvInputService.PRIORITY_HINT_USE_CASE_TYPE_PLAYBACK);
+        int[] clientId = new int[1];
+        TestResourcesReclaimListener listener = new TestResourcesReclaimListener();
+        mTunerResourceManagerService.registerClientProfileInternal(profiles[0], listener, clientId);
+        assertThat(clientId[0]).isNotEqualTo(TunerResourceManagerService.INVALID_CLIENT_ID);
+
+        // Init cas resources.
+        mTunerResourceManagerService.updateCasInfoInternal(1 /*casSystemId*/, 2 /*maxSessionNum*/);
+
+        CasSessionRequest request = new CasSessionRequest(clientId[0], 1 /*casSystemId*/);
+        int[] casSessionHandle = new int[1];
+        // Request for 1 cas sessions.
+        assertThat(mTunerResourceManagerService
+                .requestCasSessionInternal(request, casSessionHandle)).isTrue();
+        assertThat(mTunerResourceManagerService.getResourceIdFromHandle(casSessionHandle[0]))
+                .isEqualTo(1);
+        assertThat(mTunerResourceManagerService.getClientProfile(clientId[0])
+                .getInUseCasSystemId()).isEqualTo(1);
+        assertThat(mTunerResourceManagerService.getCasResource(1)
+                .getOwnerClientIds()).isEqualTo(new HashSet<Integer>(Arrays.asList(clientId[0])));
+        assertThat(mTunerResourceManagerService.getCasResource(1).isFullyUsed()).isFalse();
+
+        // Release cas
+        mTunerResourceManagerService.releaseCasSessionInternal(mTunerResourceManagerService
+                .getCasResource(1), clientId[0]);
+        assertThat(mTunerResourceManagerService.getClientProfile(clientId[0])
+                .getInUseCasSystemId()).isEqualTo(ClientProfile.INVALID_RESOURCE_ID);
+        assertThat(mTunerResourceManagerService.getCasResource(1).isFullyUsed()).isFalse();
+        assertThat(mTunerResourceManagerService.getCasResource(1)
+                .getOwnerClientIds()).isEmpty();
+    }
+
+    @Test
     public void requestLnbTest_NoLnbAvailable_RequestWithHigherPriority() {
         // Register clients
         ResourceClientProfile[] profiles = new ResourceClientProfile[2];
@@ -562,24 +611,16 @@
 
         TunerLnbRequest request = new TunerLnbRequest(clientId0[0]);
         int[] lnbHandle = new int[1];
-        try {
-            assertThat(mTunerResourceManagerService
-                    .requestLnbInternal(request, lnbHandle)).isTrue();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        assertThat(mTunerResourceManagerService
+                .requestLnbInternal(request, lnbHandle)).isTrue();
         assertThat(mTunerResourceManagerService.getResourceIdFromHandle(lnbHandle[0]))
                 .isEqualTo(lnbIds[0]);
         assertThat(mTunerResourceManagerService.getClientProfile(clientId0[0])
                 .getInUseLnbIds()).isEqualTo(new HashSet<Integer>(Arrays.asList(lnbIds[0])));
 
         request = new TunerLnbRequest(clientId1[0]);
-        try {
-            assertThat(mTunerResourceManagerService
-                    .requestLnbInternal(request, lnbHandle)).isTrue();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        assertThat(mTunerResourceManagerService
+                .requestLnbInternal(request, lnbHandle)).isTrue();
         assertThat(mTunerResourceManagerService.getResourceIdFromHandle(lnbHandle[0]))
                 .isEqualTo(lnbIds[0]);
         assertThat(mTunerResourceManagerService.getLnbResource(lnbIds[0])
@@ -608,12 +649,8 @@
 
         TunerLnbRequest request = new TunerLnbRequest(clientId[0]);
         int[] lnbHandle = new int[1];
-        try {
-            assertThat(mTunerResourceManagerService
-                    .requestLnbInternal(request, lnbHandle)).isTrue();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        assertThat(mTunerResourceManagerService
+                .requestLnbInternal(request, lnbHandle)).isTrue();
         int lnbId = mTunerResourceManagerService.getResourceIdFromHandle(lnbHandle[0]);
         assertThat(lnbId).isEqualTo(lnbIds[0]);
 
@@ -647,12 +684,8 @@
         TunerFrontendRequest request =
                 new TunerFrontendRequest(clientId[0] /*clientId*/, FrontendSettings.TYPE_DVBT);
         int[] frontendHandle = new int[1];
-        try {
-            assertThat(mTunerResourceManagerService
-                    .requestFrontendInternal(request, frontendHandle)).isTrue();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
+        assertThat(mTunerResourceManagerService
+                .requestFrontendInternal(request, frontendHandle)).isTrue();
         assertThat(mTunerResourceManagerService.getResourceIdFromHandle(frontendHandle[0]))
                 .isEqualTo(infos[0].getId());
         assertThat(mTunerResourceManagerService.getFrontendResource(infos[0].getId())
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 ecdd9e5..7a5e226 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -6074,6 +6074,9 @@
         // Pretend the shortcut exists
         List<ShortcutInfo> shortcutInfos = new ArrayList<>();
         ShortcutInfo info = mock(ShortcutInfo.class);
+        when(info.getPackage()).thenReturn(PKG);
+        when(info.getId()).thenReturn("someshortcutId");
+        when(info.getUserId()).thenReturn(USER_SYSTEM);
         when(info.isLongLived()).thenReturn(true);
         when(info.isEnabled()).thenReturn(true);
         shortcutInfos.add(info);
@@ -6137,6 +6140,9 @@
         // Pretend the shortcut exists
         List<ShortcutInfo> shortcutInfos = new ArrayList<>();
         ShortcutInfo info = mock(ShortcutInfo.class);
+        when(info.getPackage()).thenReturn(PKG);
+        when(info.getId()).thenReturn("someshortcutId");
+        when(info.getUserId()).thenReturn(USER_SYSTEM);
         when(info.isLongLived()).thenReturn(true);
         when(info.isEnabled()).thenReturn(true);
         shortcutInfos.add(info);
@@ -6483,6 +6489,9 @@
         when(mPreferencesHelper.getConversations(anyString(), anyInt())).thenReturn(convos);
 
         ShortcutInfo si = mock(ShortcutInfo.class);
+        when(si.getPackage()).thenReturn(PKG_P);
+        when(si.getId()).thenReturn("convo");
+        when(si.getUserId()).thenReturn(USER_SYSTEM);
         when(si.getShortLabel()).thenReturn("Hello");
         when(si.isLongLived()).thenReturn(true);
         when(si.isEnabled()).thenReturn(true);
@@ -6514,6 +6523,9 @@
         when(mPreferencesHelper.getConversations(anyString(), anyInt())).thenReturn(convos);
 
         ShortcutInfo si = mock(ShortcutInfo.class);
+        when(si.getPackage()).thenReturn(PKG_P);
+        when(si.getId()).thenReturn("convo");
+        when(si.getUserId()).thenReturn(USER_SYSTEM);
         when(si.getShortLabel()).thenReturn("Hello");
         when(si.isLongLived()).thenReturn(false);
         when(mLauncherApps.getShortcuts(any(), any())).thenReturn(Arrays.asList(si));
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ShortcutHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ShortcutHelperTest.java
index f7304bd..3095c87 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ShortcutHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ShortcutHelperTest.java
@@ -170,6 +170,9 @@
     @Test
     public void testGetValidShortcutInfo_notLongLived() {
         ShortcutInfo si = mock(ShortcutInfo.class);
+        when(si.getPackage()).thenReturn(PKG);
+        when(si.getId()).thenReturn(SHORTCUT_ID);
+        when(si.getUserId()).thenReturn(UserHandle.USER_SYSTEM);
         when(si.isLongLived()).thenReturn(false);
         when(si.isEnabled()).thenReturn(true);
         ArrayList<ShortcutInfo> shortcuts = new ArrayList<>();
@@ -184,6 +187,9 @@
     @Test
     public void testGetValidShortcutInfo_notSharingShortcut() {
         ShortcutInfo si = mock(ShortcutInfo.class);
+        when(si.getPackage()).thenReturn(PKG);
+        when(si.getId()).thenReturn(SHORTCUT_ID);
+        when(si.getUserId()).thenReturn(UserHandle.USER_SYSTEM);
         when(si.isLongLived()).thenReturn(true);
         when(si.isEnabled()).thenReturn(true);
         ArrayList<ShortcutInfo> shortcuts = new ArrayList<>();
@@ -198,6 +204,9 @@
     @Test
     public void testGetValidShortcutInfo_notEnabled() {
         ShortcutInfo si = mock(ShortcutInfo.class);
+        when(si.getPackage()).thenReturn(PKG);
+        when(si.getId()).thenReturn(SHORTCUT_ID);
+        when(si.getUserId()).thenReturn(UserHandle.USER_SYSTEM);
         when(si.isLongLived()).thenReturn(true);
         when(si.isEnabled()).thenReturn(false);
         ArrayList<ShortcutInfo> shortcuts = new ArrayList<>();
@@ -212,6 +221,9 @@
     @Test
     public void testGetValidShortcutInfo_isValid() {
         ShortcutInfo si = mock(ShortcutInfo.class);
+        when(si.getPackage()).thenReturn(PKG);
+        when(si.getId()).thenReturn(SHORTCUT_ID);
+        when(si.getUserId()).thenReturn(UserHandle.USER_SYSTEM);
         when(si.isLongLived()).thenReturn(true);
         when(si.isEnabled()).thenReturn(true);
         ArrayList<ShortcutInfo> shortcuts = new ArrayList<>();
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java
index 9b7ffd6..e8fab2b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppChangeTransitionTests.java
@@ -37,7 +37,6 @@
 import android.view.RemoteAnimationDefinition;
 import android.view.RemoteAnimationTarget;
 
-import androidx.test.filters.FlakyTest;
 import androidx.test.filters.SmallTest;
 
 import org.junit.Test;
@@ -97,7 +96,6 @@
     }
 
     @Test
-    @FlakyTest(bugId = 131005232)
     public void testModeChangeRemoteAnimatorNoSnapshot() {
         // setup currently defaults to no snapshot.
         setUpOnDisplay(mDisplayContent);
@@ -115,7 +113,6 @@
     }
 
     @Test
-    @FlakyTest(bugId = 131005232)
     public void testCancelPendingChangeOnRemove() {
         // setup currently defaults to no snapshot.
         setUpOnDisplay(mDisplayContent);
@@ -135,8 +132,7 @@
     }
 
     @Test
-    @FlakyTest(bugId = 131005232)
-    public void testNoChangeWhenMoveDisplay() {
+    public void testNoChangeOnOldDisplayWhenMoveDisplay() {
         mDisplayContent.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
         final DisplayContent dc1 = createNewDisplay(Display.STATE_ON);
         dc1.setWindowingMode(WINDOWING_MODE_FREEFORM);
@@ -151,9 +147,8 @@
 
         assertEquals(WINDOWING_MODE_FULLSCREEN, mTask.getWindowingMode());
 
-        // Make sure we're not waiting for a change animation (no leash)
-        assertFalse(mTask.isInChangeTransition());
-        assertNull(mActivity.mSurfaceFreezer.mSnapshot);
+        // Make sure the change transition is not the old display
+        assertFalse(dc1.mChangingContainers.contains(mTask));
 
         waitUntilHandlersIdle();
         mActivity.removeImmediately();
diff --git a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
index a4f1487..520ac19 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WallpaperControllerTests.java
@@ -16,6 +16,9 @@
 
 package com.android.server.wm;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.view.WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS;
 import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
@@ -251,6 +254,22 @@
         assertEquals(otherWindowInitialZoom, wallpaperWindow.mWallpaperZoomOut, .01f);
     }
 
+    /**
+     * Tests that the windowing mode of the wallpaper window must always be fullscreen.
+     */
+    @Test
+    public void testWallpaperTokenWindowingMode() {
+        final DisplayContent dc = mWm.mRoot.getDefaultDisplay();
+        final WallpaperWindowToken token = new WallpaperWindowToken(mWm, mock(IBinder.class),
+                true, dc, true /* ownerCanManageAppTokens */);
+
+        // The wallpaper should have requested override fullscreen windowing mode, so the
+        // configuration (windowing mode) propagation from display won't change it.
+        dc.setWindowingMode(WINDOWING_MODE_FREEFORM);
+        assertEquals(WINDOWING_MODE_FULLSCREEN, token.getWindowingMode());
+        dc.setWindowingMode(WINDOWING_MODE_UNDEFINED);
+        assertEquals(WINDOWING_MODE_FULLSCREEN, token.getWindowingMode());
+    }
 
     private WindowState createWallpaperTargetWindow(DisplayContent dc) {
         final ActivityRecord homeActivity = new ActivityTestsBase.ActivityBuilder(mWm.mAtmService)
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index f65328d..7d2e880 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -81,7 +81,7 @@
  * Test class for {@link ITaskOrganizer} and {@link android.window.ITaskOrganizerController}.
  *
  * Build/Install/Run:
- *  atest WmTests:TaskOrganizerTests
+ *  atest WmTests:WindowOrganizerTests
  */
 @SmallTest
 @Presubmit
@@ -264,15 +264,22 @@
         // newly entering the windowing mode.
         final ITaskOrganizer organizer2 = registerMockOrganizer(WINDOWING_MODE_MULTI_WINDOW);
         stack2.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
-        verify(organizer2).onTaskAppeared(any());
+        // One each for task and task2
+        verify(organizer2, times(2)).onTaskAppeared(any());
+        verify(organizer2, times(0)).onTaskVanished(any());
+        // One for task
+        verify(organizer).onTaskVanished(any());
         assertTrue(stack2.isOrganized());
 
         // 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);
+        verify(organizer, times(3)).onTaskAppeared(any());
+        verify(organizer2, times(2)).onTaskVanished(any());
 
         stack3.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
-        verify(organizer, times(2)).onTaskAppeared(any());
+        verify(organizer, times(4)).onTaskAppeared(any());
+        verify(organizer2, times(2)).onTaskVanished(any());
         assertTrue(stack3.isOrganized());
     }
 
@@ -902,12 +909,13 @@
         task.setHasBeenVisible(true);
         verify(organizer, times(1)).onTaskAppeared(any());
 
-        task.taskOrganizerUnregistered();
+        task.setTaskOrganizer(null);
+        verify(organizer, times(1)).onTaskVanished(any());
         task.setTaskOrganizer(organizer);
         verify(organizer, times(2)).onTaskAppeared(any());
 
         task.removeImmediately();
-        verify(organizer).onTaskVanished(any());
+        verify(organizer, times(2)).onTaskVanished(any());
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
index 07a6179..cdf8eb4 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowProcessControllerTests.java
@@ -28,9 +28,11 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.when;
 
+import android.Manifest;
 import android.app.IApplicationThread;
 import android.content.ComponentName;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.ServiceInfo;
 import android.content.res.Configuration;
 import android.platform.test.annotations.Presubmit;
 
@@ -200,6 +202,57 @@
         assertFalse(wpc.registeredForActivityConfigChanges());
     }
 
+    @Test
+    public void testActivityNotOverridingImeProcessConfig() {
+        ServiceInfo serviceInfo = new ServiceInfo();
+        serviceInfo.permission = Manifest.permission.BIND_INPUT_METHOD;
+        // Notify WPC that this process has started an IME service.
+        mWpc.onServiceStarted(serviceInfo);
+
+        final ActivityRecord activity = new ActivityBuilder(mService)
+                .setCreateTask(true)
+                .setUseProcess(mWpc)
+                .build();
+
+        mWpc.addActivityIfNeeded(activity);
+        // IME processes should not be registered for activity config changes.
+        assertFalse(mWpc.registeredForActivityConfigChanges());
+    }
+
+    @Test
+    public void testActivityNotOverridingAllyProcessConfig() {
+        ServiceInfo serviceInfo = new ServiceInfo();
+        serviceInfo.permission = Manifest.permission.BIND_ACCESSIBILITY_SERVICE;
+        // Notify WPC that this process has started an ally service.
+        mWpc.onServiceStarted(serviceInfo);
+
+        final ActivityRecord activity = new ActivityBuilder(mService)
+                .setCreateTask(true)
+                .setUseProcess(mWpc)
+                .build();
+
+        mWpc.addActivityIfNeeded(activity);
+        // Ally processes should not be registered for activity config changes.
+        assertFalse(mWpc.registeredForActivityConfigChanges());
+    }
+
+    @Test
+    public void testActivityNotOverridingVoiceInteractionProcessConfig() {
+        ServiceInfo serviceInfo = new ServiceInfo();
+        serviceInfo.permission = Manifest.permission.BIND_VOICE_INTERACTION;
+        // Notify WPC that this process has started an voice interaction service.
+        mWpc.onServiceStarted(serviceInfo);
+
+        final ActivityRecord activity = new ActivityBuilder(mService)
+                .setCreateTask(true)
+                .setUseProcess(mWpc)
+                .build();
+
+        mWpc.addActivityIfNeeded(activity);
+        // Voice interaction service processes should not be registered for activity config changes.
+        assertFalse(mWpc.registeredForActivityConfigChanges());
+    }
+
     private TestDisplayContent createTestDisplayContentInContainer() {
         return new TestDisplayContent.Builder(mService, 1000, 1500).build();
     }
diff --git a/telephony/java/android/telephony/ims/ImsRcsManager.java b/telephony/java/android/telephony/ims/ImsRcsManager.java
index 151fb59..ede67dd 100644
--- a/telephony/java/android/telephony/ims/ImsRcsManager.java
+++ b/telephony/java/android/telephony/ims/ImsRcsManager.java
@@ -49,7 +49,7 @@
  *
  * Use {@link ImsManager#getImsRcsManager(int)} to create an instance of this manager.
  */
-public class ImsRcsManager implements RegistrationManager {
+public class ImsRcsManager {
     private static final String TAG = "ImsRcsManager";
 
     /**
@@ -173,11 +173,11 @@
     /**
      * @hide
      */
-    @Override
+    // @Override add back to RegistrationManager interface once public.
     @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public void registerImsRegistrationCallback(
             @NonNull @CallbackExecutor Executor executor,
-            @NonNull RegistrationCallback c)
+            @NonNull RegistrationManager.RegistrationCallback c)
             throws ImsException {
         if (c == null) {
             throw new IllegalArgumentException("Must include a non-null RegistrationCallback.");
@@ -204,7 +204,7 @@
     /**
      * @hide
      */
-    @Override
+    // @Override add back to RegistrationManager interface once public.
     @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public void unregisterImsRegistrationCallback(
             @NonNull RegistrationManager.RegistrationCallback c) {
@@ -228,10 +228,10 @@
     /**
      * @hide
      */
-    @Override
+    // @Override add back to RegistrationManager interface once public.
     @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public void getRegistrationState(@NonNull @CallbackExecutor Executor executor,
-            @NonNull @ImsRegistrationState Consumer<Integer> stateCallback) {
+            @NonNull @RegistrationManager.ImsRegistrationState Consumer<Integer> stateCallback) {
         if (stateCallback == null) {
             throw new IllegalArgumentException("Must include a non-null stateCallback.");
         }
@@ -260,7 +260,6 @@
     /**
      * @hide
      */
-    @Override
     @RequiresPermission(Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
     public void getRegistrationTransportType(@NonNull @CallbackExecutor Executor executor,
             @NonNull @AccessNetworkConstants.TransportType
@@ -347,8 +346,7 @@
      * inactive subscription, it will result in a no-op.
      * @param c The RCS {@link AvailabilityCallback} to be removed.
      * @see #registerRcsAvailabilityCallback(Executor, AvailabilityCallback)
-     * @throws ImsException if the IMS service is not available when calling this method
-     * {@link ImsRcsController#unregisterRcsAvailabilityCallback()}.
+     * @throws ImsException if the IMS service is not available when calling this method.
      * See {@link ImsException#getCode()} for more information on the error codes.
      * @hide
      */
@@ -390,8 +388,7 @@
      * rather the subscription is capable of this service over IMS.
      * @see #isAvailable(int)
      * @see android.telephony.CarrierConfigManager#KEY_USE_RCS_PRESENCE_BOOL
-     * @throws ImsException if the IMS service is not available when calling this method
-     * {@link ImsRcsController#isCapable(int, int)}.
+     * @throws ImsException if the IMS service is not available when calling this method.
      * See {@link ImsException#getCode()} for more information on the error codes.
      * @hide
      */
@@ -424,9 +421,8 @@
      * @return true if the RCS capability is currently available for the associated subscription,
      * false otherwise. If the capability is available, IMS is registered and the service is
      * currently available over IMS.
-     * @see #isCapable(int)
-     * @throws ImsException if the IMS service is not available when calling this method
-     * {@link ImsRcsController#isAvailable(int, int)}.
+     * @see #isCapable(int, int)
+     * @throws ImsException if the IMS service is not available when calling this method.
      * See {@link ImsException#getCode()} for more information on the error codes.
      * @hide
      */