Merge "AOD padding animation should match slice" into pi-dev
diff --git a/api/current.txt b/api/current.txt
index 8703bf4..52b4741c 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -41426,6 +41426,7 @@
     method public void addNewIncomingCall(android.telecom.PhoneAccountHandle, android.os.Bundle);
     method public void cancelMissedCallsNotification();
     method public android.content.Intent createManageBlockedNumbersIntent();
+    method public boolean endCall();
     method public android.net.Uri getAdnUriForPhoneAccount(android.telecom.PhoneAccountHandle);
     method public java.util.List<android.telecom.PhoneAccountHandle> getCallCapablePhoneAccounts();
     method public java.lang.String getDefaultDialerPackage();
diff --git a/cmds/statsd/benchmark/metric_util.cpp b/cmds/statsd/benchmark/metric_util.cpp
index e6272ed..50ed18d 100644
--- a/cmds/statsd/benchmark/metric_util.cpp
+++ b/cmds/statsd/benchmark/metric_util.cpp
@@ -366,7 +366,7 @@
     sp<AlarmMonitor> periodicAlarmMonitor;
     sp<StatsLogProcessor> processor = new StatsLogProcessor(
         uidMap, anomalyAlarmMonitor, periodicAlarmMonitor, timeBaseSec * NS_PER_SEC,
-        [](const ConfigKey&){});
+        [](const ConfigKey&){return true;});
     processor->OnConfigUpdated(timeBaseSec * NS_PER_SEC, key, config);
     return processor;
 }
diff --git a/cmds/statsd/src/StatsLogProcessor.cpp b/cmds/statsd/src/StatsLogProcessor.cpp
index ed07acc..4d27948 100644
--- a/cmds/statsd/src/StatsLogProcessor.cpp
+++ b/cmds/statsd/src/StatsLogProcessor.cpp
@@ -75,7 +75,7 @@
                                      const sp<AlarmMonitor>& anomalyAlarmMonitor,
                                      const sp<AlarmMonitor>& periodicAlarmMonitor,
                                      const int64_t timeBaseNs,
-                                     const std::function<void(const ConfigKey&)>& sendBroadcast)
+                                     const std::function<bool(const ConfigKey&)>& sendBroadcast)
     : mUidMap(uidMap),
       mAnomalyAlarmMonitor(anomalyAlarmMonitor),
       mPeriodicAlarmMonitor(periodicAlarmMonitor),
@@ -465,12 +465,21 @@
     // We suspect that the byteSize() computation is expensive, so we set a rate limit.
     size_t totalBytes = metricsManager.byteSize();
     mLastByteSizeTimes[key] = timestampNs;
+    bool requestDump = false;
     if (totalBytes >
         StatsdStats::kMaxMetricsBytesPerConfig) {  // Too late. We need to start clearing data.
         metricsManager.dropData(timestampNs);
         StatsdStats::getInstance().noteDataDropped(key);
         VLOG("StatsD had to toss out metrics for %s", key.ToString().c_str());
-    } else if (totalBytes > StatsdStats::kBytesPerConfigTriggerGetData) {
+    } else if ((totalBytes > StatsdStats::kBytesPerConfigTriggerGetData) ||
+               (mOnDiskDataConfigs.find(key) != mOnDiskDataConfigs.end())) {
+        // Request to send a broadcast if:
+        // 1. in memory data > threshold   OR
+        // 2. config has old data report on disk.
+        requestDump = true;
+    }
+
+    if (requestDump) {
         // Send broadcast so that receivers can pull data.
         auto lastBroadcastTime = mLastBroadcastTimes.find(key);
         if (lastBroadcastTime != mLastBroadcastTimes.end()) {
@@ -479,10 +488,12 @@
                 return;
             }
         }
-        mLastBroadcastTimes[key] = timestampNs;
-        VLOG("StatsD requesting broadcast for %s", key.ToString().c_str());
-        mSendBroadcast(key);
-        StatsdStats::getInstance().noteBroadcastSent(key);
+        if (mSendBroadcast(key)) {
+            mOnDiskDataConfigs.erase(key);
+            VLOG("StatsD triggered data fetch for %s", key.ToString().c_str());
+            mLastBroadcastTimes[key] = timestampNs;
+            StatsdStats::getInstance().noteBroadcastSent(key);
+        }
     }
 }
 
@@ -505,6 +516,8 @@
         return;
     }
     proto.flush(fd.get());
+    // We were able to write the ConfigMetricsReport to disk, so we should trigger collection ASAP.
+    mOnDiskDataConfigs.insert(key);
 }
 
 void StatsLogProcessor::WriteDataToDiskLocked(const DumpReportReason dumpReportReason) {
@@ -533,6 +546,11 @@
     }
 }
 
+void StatsLogProcessor::noteOnDiskData(const ConfigKey& key) {
+    std::lock_guard<std::mutex> lock(mMetricsMutex);
+    mOnDiskDataConfigs.insert(key);
+}
+
 }  // namespace statsd
 }  // namespace os
 }  // namespace android
diff --git a/cmds/statsd/src/StatsLogProcessor.h b/cmds/statsd/src/StatsLogProcessor.h
index 8de0f41..d6fb8de 100644
--- a/cmds/statsd/src/StatsLogProcessor.h
+++ b/cmds/statsd/src/StatsLogProcessor.h
@@ -48,7 +48,7 @@
     StatsLogProcessor(const sp<UidMap>& uidMap, const sp<AlarmMonitor>& anomalyAlarmMonitor,
                       const sp<AlarmMonitor>& subscriberTriggerAlarmMonitor,
                       const int64_t timeBaseNs,
-                      const std::function<void(const ConfigKey&)>& sendBroadcast);
+                      const std::function<bool(const ConfigKey&)>& sendBroadcast);
     virtual ~StatsLogProcessor();
 
     void OnLogEvent(LogEvent* event, bool reconnectionStarts);
@@ -99,6 +99,9 @@
 #endif
     }
 
+    // Add a specific config key to the possible configs to dump ASAP.
+    void noteOnDiskData(const ConfigKey& key);
+
 private:
     // For testing only.
     inline sp<AlarmMonitor> getAnomalyAlarmMonitor() const {
@@ -118,6 +121,9 @@
     // Tracks when we last checked the bytes consumed for each config key.
     std::unordered_map<ConfigKey, long> mLastByteSizeTimes;
 
+    // Tracks which config keys has metric reports on disk
+    std::set<ConfigKey> mOnDiskDataConfigs;
+
     sp<UidMap> mUidMap;  // Reference to the UidMap to lookup app name and version for each uid.
 
     StatsPullerManager mStatsPullerManager;
@@ -159,7 +165,7 @@
 
     // Function used to send a broadcast so that receiver for the config key can call getData
     // to retrieve the stored data.
-    std::function<void(const ConfigKey& key)> mSendBroadcast;
+    std::function<bool(const ConfigKey& key)> mSendBroadcast;
 
     const int64_t mTimeBaseNs;
 
diff --git a/cmds/statsd/src/StatsService.cpp b/cmds/statsd/src/StatsService.cpp
index e823f68..acf3ad2 100644
--- a/cmds/statsd/src/StatsService.cpp
+++ b/cmds/statsd/src/StatsService.cpp
@@ -158,11 +158,14 @@
         auto receiver = mConfigManager->GetConfigReceiver(key);
         if (sc == nullptr) {
             VLOG("Could not find StatsCompanionService");
+            return false;
         } else if (receiver == nullptr) {
             VLOG("Statscompanion could not find a broadcast receiver for %s",
                  key.ToString().c_str());
+            return false;
         } else {
             sc->sendDataBroadcast(receiver, mProcessor->getLastReportTimeNs(key));
+            return true;
         }
     }
     );
@@ -948,6 +951,11 @@
     IPCThreadState* ipc = IPCThreadState::self();
     ConfigKey configKey(ipc->getCallingUid(), key);
     mConfigManager->SetConfigReceiver(configKey, intentSender);
+    if (StorageManager::hasConfigMetricsReport(configKey)) {
+        VLOG("StatsService::setDataFetchOperation marking configKey %s to dump reports on disk",
+             configKey.ToString().c_str());
+        mProcessor->noteOnDiskData(configKey);
+    }
     return Status::ok();
 }
 
@@ -988,6 +996,15 @@
     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::util::stats_write(util::APP_BREADCRUMB_REPORTED,
+                               IPCThreadState::self()->getCallingUid(), label,
+                               state);
+    return Status::ok();
+}
+
 void StatsService::binderDied(const wp <IBinder>& who) {
     ALOGW("statscompanion service died");
     StatsdStats::getInstance().noteSystemServerRestart(getWallClockSec());
diff --git a/cmds/statsd/src/StatsService.h b/cmds/statsd/src/StatsService.h
index 67fc770..b3a4776 100644
--- a/cmds/statsd/src/StatsService.h
+++ b/cmds/statsd/src/StatsService.h
@@ -139,6 +139,11 @@
     /** Inform statsCompanion that statsd is ready. */
     virtual void sayHiToStatsCompanion();
 
+    /**
+     * Binder call to get AppBreadcrumbReported atom.
+     */
+    virtual Status sendAppBreadcrumbAtom(int32_t label, int32_t state) override;
+
     /** IBinder::DeathRecipient */
     virtual void binderDied(const wp<IBinder>& who) override;
 
diff --git a/cmds/statsd/src/metrics/MetricsManager.cpp b/cmds/statsd/src/metrics/MetricsManager.cpp
index bf0f720..2d14b05 100644
--- a/cmds/statsd/src/metrics/MetricsManager.cpp
+++ b/cmds/statsd/src/metrics/MetricsManager.cpp
@@ -62,7 +62,7 @@
     : mConfigKey(key), mUidMap(uidMap),
       mTtlNs(config.has_ttl_in_seconds() ? config.ttl_in_seconds() * NS_PER_SEC : -1),
       mTtlEndNs(-1),
-      mLastReportTimeNs(timeBaseNs),
+      mLastReportTimeNs(currentTimeNs),
       mLastReportWallClockNs(getWallClockNs()) {
     // Init the ttl end timestamp.
     refreshTtl(timeBaseNs);
@@ -247,16 +247,6 @@
             return;
         }
 
-        // Label is 2nd from last field and must be from [0, 15].
-        long appHookLabel = event.GetLong(event.size()-1, &err);
-        if (err != NO_ERROR ) {
-            VLOG("APP_BREADCRUMB_REPORTED had error when parsing the label field");
-            return;
-        } else if (appHookLabel < 0 || appHookLabel > 15) {
-            VLOG("APP_BREADCRUMB_REPORTED does not have valid label %ld", appHookLabel);
-            return;
-        }
-
         // The state must be from 0,3. This part of code must be manually updated.
         long appHookState = event.GetLong(event.size(), &err);
         if (err != NO_ERROR ) {
diff --git a/cmds/statsd/src/metrics/MetricsManager.h b/cmds/statsd/src/metrics/MetricsManager.h
index 170d6a7..4f39df9e 100644
--- a/cmds/statsd/src/metrics/MetricsManager.h
+++ b/cmds/statsd/src/metrics/MetricsManager.h
@@ -79,7 +79,8 @@
         }
     };
 
-    // Returns the elapsed realtime when this metric manager last reported metrics.
+    // Returns the elapsed realtime when this metric manager last reported metrics. If this config
+    // has not yet dumped any reports, this is the time the metricsmanager was initialized.
     inline int64_t getLastReportTimeNs() const {
         return mLastReportTimeNs;
     };
diff --git a/cmds/statsd/src/storage/StorageManager.cpp b/cmds/statsd/src/storage/StorageManager.cpp
index ea8da14..1f81812 100644
--- a/cmds/statsd/src/storage/StorageManager.cpp
+++ b/cmds/statsd/src/storage/StorageManager.cpp
@@ -160,6 +160,34 @@
     }
 }
 
+bool StorageManager::hasConfigMetricsReport(const ConfigKey& key) {
+    unique_ptr<DIR, decltype(&closedir)> dir(opendir(STATS_DATA_DIR), closedir);
+    if (dir == NULL) {
+        VLOG("Path %s does not exist", STATS_DATA_DIR);
+        return false;
+    }
+
+    string suffix = StringPrintf("%d_%lld", key.GetUid(), (long long)key.GetId());
+
+    dirent* de;
+    while ((de = readdir(dir.get()))) {
+        char* name = de->d_name;
+        if (name[0] == '.') continue;
+
+        size_t nameLen = strlen(name);
+        size_t suffixLen = suffix.length();
+        if (suffixLen <= nameLen &&
+            strncmp(name + nameLen - suffixLen, suffix.c_str(), suffixLen) == 0) {
+            // Check again that the file name is parseable.
+            int64_t result[3];
+            parseFileName(name, result);
+            if (result[0] == -1) continue;
+            return true;
+        }
+    }
+    return false;
+}
+
 void StorageManager::appendConfigMetricsReport(const ConfigKey& key, ProtoOutputStream* proto) {
     unique_ptr<DIR, decltype(&closedir)> dir(opendir(STATS_DATA_DIR), closedir);
     if (dir == NULL) {
diff --git a/cmds/statsd/src/storage/StorageManager.h b/cmds/statsd/src/storage/StorageManager.h
index 8953be9..4840f3c 100644
--- a/cmds/statsd/src/storage/StorageManager.h
+++ b/cmds/statsd/src/storage/StorageManager.h
@@ -63,6 +63,11 @@
                               const std::function<void(const ConfigKey&)>& sendBroadcast);
 
     /**
+     * Returns true if there's at least one report on disk.
+     */
+    static bool hasConfigMetricsReport(const ConfigKey& key);
+
+    /**
      * Appends ConfigMetricsReport found on disk to the specific proto and
      * delete it.
      */
diff --git a/cmds/statsd/tests/StatsLogProcessor_test.cpp b/cmds/statsd/tests/StatsLogProcessor_test.cpp
index 9fdf7a3..de6e6e5 100644
--- a/cmds/statsd/tests/StatsLogProcessor_test.cpp
+++ b/cmds/statsd/tests/StatsLogProcessor_test.cpp
@@ -62,7 +62,7 @@
     sp<AlarmMonitor> periodicAlarmMonitor;
     // Construct the processor with a dummy sendBroadcast function that does nothing.
     StatsLogProcessor p(m, anomalyAlarmMonitor, periodicAlarmMonitor, 0,
-        [](const ConfigKey& key) {});
+        [](const ConfigKey& key) {return true;});
 
     MockMetricsManager mockMetricsManager;
 
@@ -81,7 +81,7 @@
     sp<AlarmMonitor> subscriberAlarmMonitor;
     int broadcastCount = 0;
     StatsLogProcessor p(m, anomalyAlarmMonitor, subscriberAlarmMonitor, 0,
-                        [&broadcastCount](const ConfigKey& key) { broadcastCount++; });
+                        [&broadcastCount](const ConfigKey& key) { broadcastCount++; return true;});
 
     MockMetricsManager mockMetricsManager;
 
@@ -107,7 +107,7 @@
     sp<AlarmMonitor> subscriberAlarmMonitor;
     int broadcastCount = 0;
     StatsLogProcessor p(m, anomalyAlarmMonitor, subscriberAlarmMonitor, 0,
-                        [&broadcastCount](const ConfigKey& key) { broadcastCount++; });
+                        [&broadcastCount](const ConfigKey& key) { broadcastCount++; return true;});
 
     MockMetricsManager mockMetricsManager;
 
@@ -131,7 +131,7 @@
     sp<AlarmMonitor> subscriberAlarmMonitor;
     int broadcastCount = 0;
     StatsLogProcessor p(m, anomalyAlarmMonitor, subscriberAlarmMonitor, 0,
-                        [&broadcastCount](const ConfigKey& key) { broadcastCount++; });
+                        [&broadcastCount](const ConfigKey& key) { broadcastCount++; return true;});
     ConfigKey key(3, 4);
     StatsdConfig config;
     config.add_allowed_log_source("AID_ROOT");
@@ -156,7 +156,7 @@
     sp<AlarmMonitor> subscriberAlarmMonitor;
     int broadcastCount = 0;
     StatsLogProcessor p(m, anomalyAlarmMonitor, subscriberAlarmMonitor, 0,
-                        [&broadcastCount](const ConfigKey& key) { broadcastCount++; });
+                        [&broadcastCount](const ConfigKey& key) { broadcastCount++; return true;});
     ConfigKey key(3, 4);
     StatsdConfig config;
     auto annotation = config.add_annotation();
@@ -185,7 +185,7 @@
     sp<AlarmMonitor> subscriberAlarmMonitor;
     int broadcastCount = 0;
     StatsLogProcessor p(m, anomalyAlarmMonitor, subscriberAlarmMonitor, 0,
-                        [&broadcastCount](const ConfigKey& key) { broadcastCount++; });
+                        [&broadcastCount](const ConfigKey& key) { broadcastCount++; return true;});
 
     LogEvent event1(0, 1 /*logd timestamp*/, 1001 /*elapsedRealtime*/);
     event1.init();
diff --git a/cmds/statsd/tests/UidMap_test.cpp b/cmds/statsd/tests/UidMap_test.cpp
index dde50c2..e23131d 100644
--- a/cmds/statsd/tests/UidMap_test.cpp
+++ b/cmds/statsd/tests/UidMap_test.cpp
@@ -44,7 +44,7 @@
     sp<AlarmMonitor> subscriberAlarmMonitor;
     // Construct the processor with a dummy sendBroadcast function that does nothing.
     StatsLogProcessor p(m, anomalyAlarmMonitor, subscriberAlarmMonitor, 0,
-        [](const ConfigKey& key) {});
+        [](const ConfigKey& key) {return true;});
     LogEvent addEvent(android::util::ISOLATED_UID_CHANGED, 1);
     addEvent.write(100);  // parent UID
     addEvent.write(101);  // isolated UID
diff --git a/cmds/statsd/tests/statsd_test_util.cpp b/cmds/statsd/tests/statsd_test_util.cpp
index 5903993..e0c98cb 100644
--- a/cmds/statsd/tests/statsd_test_util.cpp
+++ b/cmds/statsd/tests/statsd_test_util.cpp
@@ -459,7 +459,7 @@
         new AlarmMonitor(1,  [](const sp<IStatsCompanionService>&, int64_t){},
                 [](const sp<IStatsCompanionService>&){});
     sp<StatsLogProcessor> processor = new StatsLogProcessor(
-        uidMap, anomalyAlarmMonitor, periodicAlarmMonitor, timeBaseNs, [](const ConfigKey&){});
+        uidMap, anomalyAlarmMonitor, periodicAlarmMonitor, timeBaseNs, [](const ConfigKey&){return true;});
     processor->OnConfigUpdated(currentTimeNs, key, config);
     return processor;
 }
diff --git a/config/hiddenapi-light-greylist.txt b/config/hiddenapi-light-greylist.txt
index 29d7a21..1418012 100644
--- a/config/hiddenapi-light-greylist.txt
+++ b/config/hiddenapi-light-greylist.txt
@@ -571,8 +571,8 @@
 Landroid/bluetooth/BluetoothHeadset;->disconnectAudio()Z
 Landroid/bluetooth/BluetoothHeadset;->getActiveDevice()Landroid/bluetooth/BluetoothDevice;
 Landroid/bluetooth/BluetoothHeadset;->setActiveDevice(Landroid/bluetooth/BluetoothDevice;)Z
-Landroid/bluetooth/BluetoothHeadset;->startScoUsingVirtualVoiceCall(Landroid/bluetooth/BluetoothDevice;)Z
-Landroid/bluetooth/BluetoothHeadset;->stopScoUsingVirtualVoiceCall(Landroid/bluetooth/BluetoothDevice;)Z
+Landroid/bluetooth/BluetoothHeadset;->startScoUsingVirtualVoiceCall()Z
+Landroid/bluetooth/BluetoothHeadset;->stopScoUsingVirtualVoiceCall()Z
 Landroid/bluetooth/BluetoothHearingAid;->ACTION_ACTIVE_DEVICE_CHANGED:Ljava/lang/String;
 Landroid/bluetooth/BluetoothHearingAid;->getActiveDevices()Ljava/util/List;
 Landroid/bluetooth/BluetoothHearingAid;->setActiveDevice(Landroid/bluetooth/BluetoothDevice;)Z
diff --git a/core/java/android/bluetooth/BluetoothHeadset.java b/core/java/android/bluetooth/BluetoothHeadset.java
index a68f485..0c91a20 100644
--- a/core/java/android/bluetooth/BluetoothHeadset.java
+++ b/core/java/android/bluetooth/BluetoothHeadset.java
@@ -16,6 +16,7 @@
 
 package android.bluetooth;
 
+import android.Manifest;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
@@ -633,8 +634,9 @@
      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
      *
      * @param device Bluetooth headset
-     * @return false if there is no headset connected of if the connected headset doesn't support
-     * voice recognition or on error, true otherwise
+     * @return false if there is no headset connected, or the connected headset doesn't support
+     * voice recognition, or voice recognition is already started, or audio channel is occupied,
+     * or on error, true otherwise
      */
     public boolean startVoiceRecognition(BluetoothDevice device) {
         if (DBG) log("startVoiceRecognition()");
@@ -654,10 +656,15 @@
      * Stop Bluetooth Voice Recognition mode, and shut down the
      * Bluetooth audio path.
      *
+     * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
+     * If this function returns true, this intent will be broadcasted with
+     * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_DISCONNECTED}.
+     *
      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
      *
      * @param device Bluetooth headset
-     * @return false if there is no headset connected or on error, true otherwise
+     * @return false if there is no headset connected, or voice recognition has not started,
+     * or voice recognition has ended on this headset, or on error, true otherwise
      */
     public boolean stopVoiceRecognition(BluetoothDevice device) {
         if (DBG) log("stopVoiceRecognition()");
@@ -798,11 +805,12 @@
     }
 
     /**
-     * Check if Bluetooth SCO audio is connected.
+     * Check if at least one headset's SCO audio is connected or connecting
      *
      * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
      *
-     * @return true if SCO is connected, false otherwise or on error
+     * @return true if at least one device's SCO audio is connected or connecting, false otherwise
+     * or on error
      * @hide
      */
     public boolean isAudioOn() {
@@ -821,11 +829,21 @@
     }
 
     /**
-     * Initiates a connection of headset audio.
-     * It setup SCO channel with remote connected headset device.
+     * Initiates a connection of headset audio to the current active device
      *
-     * @return true if successful false if there was some error such as there is no connected
-     * headset
+     * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
+     * If this function returns true, this intent will be broadcasted with
+     * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_CONNECTING}.
+     *
+     * <p> {@link #EXTRA_STATE} will transition from
+     * {@link #STATE_AUDIO_CONNECTING} to {@link #STATE_AUDIO_CONNECTED} when
+     * audio connection is established and to {@link #STATE_AUDIO_DISCONNECTED}
+     * in case of failure to establish the audio connection.
+     *
+     * Note that this intent will not be sent if {@link BluetoothHeadset#isAudioOn()} is true
+     * before calling this method
+     *
+     * @return false if there was some error such as there is no active headset
      * @hide
      */
     public boolean connectAudio() {
@@ -844,11 +862,14 @@
     }
 
     /**
-     * Initiates a disconnection of headset audio.
-     * It tears down the SCO channel from remote headset device.
+     * Initiates a disconnection of HFP SCO audio.
+     * Tear down voice recognition or virtual voice call if any.
      *
-     * @return true if successful false if there was some error such as there is no connected SCO
-     * channel
+     * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
+     * If this function returns true, this intent will be broadcasted with
+     * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_DISCONNECTED}.
+     *
+     * @return false if audio is not connected, or on error, true otherwise
      * @hide
      */
     public boolean disconnectAudio() {
@@ -867,22 +888,33 @@
     }
 
     /**
-     * Initiates a SCO channel connection with the headset (if connected).
-     * Also initiates a virtual voice call for Handsfree devices as many devices
-     * do not accept SCO audio without a call.
-     * This API allows the handsfree device to be used for routing non-cellular
-     * call audio.
+     * Initiates a SCO channel connection as a virtual voice call to the current active device
+     * Active handsfree device will be notified of incoming call and connected call.
      *
-     * @param device Remote Bluetooth Device
-     * @return true if successful, false if there was some error.
+     * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
+     * If this function returns true, this intent will be broadcasted with
+     * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_CONNECTING}.
+     *
+     * <p> {@link #EXTRA_STATE} will transition from
+     * {@link #STATE_AUDIO_CONNECTING} to {@link #STATE_AUDIO_CONNECTED} when
+     * audio connection is established and to {@link #STATE_AUDIO_DISCONNECTED}
+     * in case of failure to establish the audio connection.
+     *
+     * @return true if successful, false if one of the following case applies
+     *  - SCO audio is not idle (connecting or connected)
+     *  - virtual call has already started
+     *  - there is no active device
+     *  - a Telecom managed call is going on
+     *  - binder is dead or Bluetooth is disabled or other error
      * @hide
      */
-    public boolean startScoUsingVirtualVoiceCall(BluetoothDevice device) {
+    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    public boolean startScoUsingVirtualVoiceCall() {
         if (DBG) log("startScoUsingVirtualVoiceCall()");
         final IBluetoothHeadset service = mService;
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        if (service != null && isEnabled()) {
             try {
-                return service.startScoUsingVirtualVoiceCall(device);
+                return service.startScoUsingVirtualVoiceCall();
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
@@ -894,19 +926,24 @@
     }
 
     /**
-     * Terminates an ongoing SCO connection and the associated virtual
-     * call.
+     * Terminates an ongoing SCO connection and the associated virtual call.
      *
-     * @param device Remote Bluetooth Device
-     * @return true if successful, false if there was some error.
+     * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
+     * If this function returns true, this intent will be broadcasted with
+     * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_DISCONNECTED}.
+     *
+     * @return true if successful, false if one of the following case applies
+     *  - virtual voice call is not started or has ended
+     *  - binder is dead or Bluetooth is disabled or other error
      * @hide
      */
-    public boolean stopScoUsingVirtualVoiceCall(BluetoothDevice device) {
+    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    public boolean stopScoUsingVirtualVoiceCall() {
         if (DBG) log("stopScoUsingVirtualVoiceCall()");
         final IBluetoothHeadset service = mService;
-        if (service != null && isEnabled() && isValidDevice(device)) {
+        if (service != null && isEnabled()) {
             try {
-                return service.stopScoUsingVirtualVoiceCall(device);
+                return service.stopScoUsingVirtualVoiceCall();
             } catch (RemoteException e) {
                 Log.e(TAG, e.toString());
             }
diff --git a/core/java/android/os/IStatsManager.aidl b/core/java/android/os/IStatsManager.aidl
index 8c256be..124f207 100644
--- a/core/java/android/os/IStatsManager.aidl
+++ b/core/java/android/os/IStatsManager.aidl
@@ -152,4 +152,10 @@
      * Requires Manifest.permission.DUMP.
      */
     void unsetBroadcastSubscriber(long configKey, long subscriberId, in String packageName);
+
+    /**
+     * 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);
 }
diff --git a/core/java/android/util/StatsLog.java b/core/java/android/util/StatsLog.java
index e8b4197..e3de307 100644
--- a/core/java/android/util/StatsLog.java
+++ b/core/java/android/util/StatsLog.java
@@ -16,59 +16,101 @@
 
 package android.util;
 
-import android.os.Process;
+import android.os.IStatsManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
 
 /**
  * StatsLog provides an API for developers to send events to statsd. The events can be used to
  * define custom metrics inside statsd.
  */
 public final class StatsLog extends StatsLogInternal {
-    private static final String TAG = "StatsManager";
+    private static final String TAG = "StatsLog";
+    private static final boolean DEBUG = false;
+
+    private static IStatsManager sService;
 
     private StatsLog() {}
 
     /**
      * Logs a start event.
      *
-     * @param label developer-chosen label that is from [0, 16).
+     * @param label developer-chosen label.
      * @return True if the log request was sent to statsd.
      */
     public static boolean logStart(int label) {
-        if (label >= 0 && label < 16) {
-            StatsLog.write(APP_BREADCRUMB_REPORTED, Process.myUid(),
-                    label, APP_BREADCRUMB_REPORTED__STATE__START);
-            return true;
+        synchronized (StatsLog.class) {
+            try {
+                IStatsManager service = getIStatsManagerLocked();
+                if (service == null) {
+                    if (DEBUG) Slog.d(TAG, "Failed to find statsd when logging start");
+                    return false;
+                }
+                service.sendAppBreadcrumbAtom(label,
+                        StatsLog.APP_BREADCRUMB_REPORTED__STATE__START);
+                return true;
+            } catch (RemoteException e) {
+                sService = null;
+                if (DEBUG) Slog.d(TAG, "Failed to connect to statsd when logging start");
+                return false;
+            }
         }
-        return false;
     }
 
     /**
      * Logs a stop event.
      *
-     * @param label developer-chosen label that is from [0, 16).
+     * @param label developer-chosen label.
      * @return True if the log request was sent to statsd.
      */
     public static boolean logStop(int label) {
-        if (label >= 0 && label < 16) {
-            StatsLog.write(APP_BREADCRUMB_REPORTED, Process.myUid(),
-                    label, APP_BREADCRUMB_REPORTED__STATE__STOP);
-            return true;
+        synchronized (StatsLog.class) {
+            try {
+                IStatsManager service = getIStatsManagerLocked();
+                if (service == null) {
+                    if (DEBUG) Slog.d(TAG, "Failed to find statsd when logging stop");
+                    return false;
+                }
+                service.sendAppBreadcrumbAtom(label, StatsLog.APP_BREADCRUMB_REPORTED__STATE__STOP);
+                return true;
+            } catch (RemoteException e) {
+                sService = null;
+                if (DEBUG) Slog.d(TAG, "Failed to connect to statsd when logging stop");
+                return false;
+            }
         }
-        return false;
     }
 
     /**
      * Logs an event that does not represent a start or stop boundary.
      *
-     * @param label developer-chosen label that is from [0, 16).
+     * @param label developer-chosen label.
      * @return True if the log request was sent to statsd.
      */
     public static boolean logEvent(int label) {
-        if (label >= 0 && label < 16) {
-            StatsLog.write(APP_BREADCRUMB_REPORTED, Process.myUid(), label,
-                    APP_BREADCRUMB_REPORTED__STATE__UNSPECIFIED);
-            return true;
+        synchronized (StatsLog.class) {
+            try {
+                IStatsManager service = getIStatsManagerLocked();
+                if (service == null) {
+                    if (DEBUG) Slog.d(TAG, "Failed to find statsd when logging event");
+                    return false;
+                }
+                service.sendAppBreadcrumbAtom(
+                        label, StatsLog.APP_BREADCRUMB_REPORTED__STATE__UNSPECIFIED);
+                return true;
+            } catch (RemoteException e) {
+                sService = null;
+                if (DEBUG) Slog.d(TAG, "Failed to connect to statsd when logging event");
+                return false;
+            }
         }
-        return false;
+    }
+
+    private static IStatsManager getIStatsManagerLocked() throws RemoteException {
+        if (sService != null) {
+            return sService;
+        }
+        sService = IStatsManager.Stub.asInterface(ServiceManager.getService("stats"));
+        return sService;
     }
 }
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 88300db..4c7dc11 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -1132,8 +1132,7 @@
             if (mSaveTriggerId != null && mSaveTriggerId.equals(id)) {
                 if (sDebug) Log.d(TAG, "triggering commit by click of " + id);
                 commitLocked();
-                mMetricsLogger.action(MetricsEvent.AUTOFILL_SAVE_EXPLICITLY_TRIGGERED,
-                        mContext.getPackageName());
+                mMetricsLogger.write(newLog(MetricsEvent.AUTOFILL_SAVE_EXPLICITLY_TRIGGERED));
             }
         }
     }
@@ -1893,14 +1892,19 @@
                 }
             }
 
-            final LogMaker log = new LogMaker(MetricsEvent.AUTOFILL_DATASET_APPLIED)
-                    .setPackageName(mContext.getPackageName())
+            mMetricsLogger.write(newLog(MetricsEvent.AUTOFILL_DATASET_APPLIED)
                     .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_VALUES, itemCount)
-                    .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_VIEWS_FILLED, numApplied);
-            mMetricsLogger.write(log);
+                    .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_VIEWS_FILLED, numApplied));
         }
     }
 
+    private LogMaker newLog(int category) {
+        return new LogMaker(category)
+                .setPackageName(mContext.getPackageName())
+                .addTaggedData(MetricsEvent.FIELD_AUTOFILL_COMPAT_MODE,
+                        isCompatibilityModeEnabledLocked() ? 1 : 0);
+    }
+
     /**
      *  Set the tracked views.
      *
diff --git a/core/java/com/android/internal/widget/MessagingGroup.java b/core/java/com/android/internal/widget/MessagingGroup.java
index 15b2718..b9a8864 100644
--- a/core/java/com/android/internal/widget/MessagingGroup.java
+++ b/core/java/com/android/internal/widget/MessagingGroup.java
@@ -147,7 +147,7 @@
             setAvatar(sender.getIcon());
         }
         mAvatarView.setVisibility(VISIBLE);
-        mSenderName.setVisibility(VISIBLE);
+        mSenderName.setVisibility(TextUtils.isEmpty(nameOverride) ? GONE : VISIBLE);
         mTextColor = getNormalTextColor();
         mSendingTextColor = calculateSendingTextColor();
     }
diff --git a/core/java/com/android/internal/widget/MessagingLayout.java b/core/java/com/android/internal/widget/MessagingLayout.java
index af9aae3..538ea11 100644
--- a/core/java/com/android/internal/widget/MessagingLayout.java
+++ b/core/java/com/android/internal/widget/MessagingLayout.java
@@ -436,10 +436,29 @@
     }
 
     private void updateHistoricMessageVisibility() {
-        for (int i = 0; i < mHistoricMessages.size(); i++) {
+        int numHistoric = mHistoricMessages.size();
+        for (int i = 0; i < numHistoric; i++) {
             MessagingMessage existing = mHistoricMessages.get(i);
             existing.setVisibility(mShowHistoricMessages ? VISIBLE : GONE);
         }
+        int numGroups = mGroups.size();
+        for (int i = 0; i < numGroups; i++) {
+            MessagingGroup group = mGroups.get(i);
+            int visibleChildren = 0;
+            List<MessagingMessage> messages = group.getMessages();
+            int numGroupMessages = messages.size();
+            for (int j = 0; j < numGroupMessages; j++) {
+                MessagingMessage message = messages.get(j);
+                if (message.getVisibility() != GONE) {
+                    visibleChildren++;
+                }
+            }
+            if (visibleChildren > 0 && group.getVisibility() == GONE) {
+                group.setVisibility(VISIBLE);
+            } else if (visibleChildren == 0 && group.getVisibility() != GONE)   {
+                group.setVisibility(GONE);
+            }
+        }
     }
 
     @Override
diff --git a/core/java/com/android/internal/widget/MessagingMessage.java b/core/java/com/android/internal/widget/MessagingMessage.java
index d2b670f..ffcb503 100644
--- a/core/java/com/android/internal/widget/MessagingMessage.java
+++ b/core/java/com/android/internal/widget/MessagingMessage.java
@@ -145,4 +145,6 @@
     MessagingMessageState getState();
 
     void setVisibility(int visibility);
+
+    int getVisibility();
 }
diff --git a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
index 79bb534..270527d5 100644
--- a/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaOpenGLPipeline.cpp
@@ -297,6 +297,26 @@
     GLuint mTexture = 0;
 };
 
+static bool isFP16Supported(const sk_sp<GrContext>& grContext) {
+    static std::once_flag sOnceFlag;
+    static bool supported = false;
+
+    std::call_once(sOnceFlag, [](const sk_sp<GrContext>& grContext) {
+        if (!grContext->caps()->isConfigTexturable(kRGBA_half_GrPixelConfig)) {
+            supported = false;
+            return;
+        }
+
+        sp<GraphicBuffer> buffer = new GraphicBuffer(1, 1, PIXEL_FORMAT_RGBA_FP16,
+                GraphicBuffer::USAGE_HW_TEXTURE | GraphicBuffer::USAGE_SW_WRITE_NEVER |
+                        GraphicBuffer::USAGE_SW_READ_NEVER, "tempFp16Buffer");
+        status_t error = buffer->initCheck();
+        supported = !error;
+    }, grContext);
+
+    return supported;
+}
+
 sk_sp<Bitmap> SkiaOpenGLPipeline::allocateHardwareBitmap(renderthread::RenderThread& renderThread,
                                                          SkBitmap& skBitmap) {
     renderThread.eglManager().initialize();
@@ -318,7 +338,7 @@
             type = GL_UNSIGNED_BYTE;
             break;
         case kRGBA_F16_SkColorType:
-            isSupported = grContext->caps()->isConfigTexturable(kRGBA_half_GrPixelConfig);
+            isSupported = isFP16Supported(grContext);
             if (isSupported) {
                 type = GL_HALF_FLOAT;
                 pixelFormat = PIXEL_FORMAT_RGBA_FP16;
diff --git a/media/java/android/media/AudioFocusRequest.java b/media/java/android/media/AudioFocusRequest.java
index fe89b89..b9731d1 100644
--- a/media/java/android/media/AudioFocusRequest.java
+++ b/media/java/android/media/AudioFocusRequest.java
@@ -414,7 +414,9 @@
          *   with {@link AudioManager#abandonAudioFocusRequest(AudioFocusRequest)}.
          *   Note that only focus changes (gains and losses) affecting the focus owner are reported,
          *   not gains and losses of other focus requesters in the system.<br>
-         *   Notifications are delivered on the main {@link Looper}.
+         *   Notifications are delivered on the {@link Looper} associated with the one of
+         *   the creation of the {@link AudioManager} used to request focus
+         *   (see {@link AudioManager#requestAudioFocus(AudioFocusRequest)}).
          * @param listener the listener receiving the focus change notifications.
          * @return this {@code Builder} instance.
          * @throws NullPointerException thrown when a null focus listener is used.
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 68463e1..566db94 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -4558,8 +4558,7 @@
     /**
      * The list of {@link AudioDeviceCallback} objects to receive add/remove notifications.
      */
-    private ArrayMap<AudioDeviceCallback, NativeEventHandlerDelegate>
-        mDeviceCallbacks =
+    private final ArrayMap<AudioDeviceCallback, NativeEventHandlerDelegate> mDeviceCallbacks =
             new ArrayMap<AudioDeviceCallback, NativeEventHandlerDelegate>();
 
     /**
@@ -4859,22 +4858,21 @@
                     calcListDeltas(mPreviousPorts, current_ports, GET_DEVICES_ALL);
             AudioDeviceInfo[] removed_devices =
                     calcListDeltas(current_ports, mPreviousPorts, GET_DEVICES_ALL);
-
             if (added_devices.length != 0 || removed_devices.length != 0) {
                 synchronized (mDeviceCallbacks) {
                     for (int i = 0; i < mDeviceCallbacks.size(); i++) {
                         handler = mDeviceCallbacks.valueAt(i).getHandler();
                         if (handler != null) {
-                            if (added_devices.length != 0) {
-                                handler.sendMessage(Message.obtain(handler,
-                                                                   MSG_DEVICES_DEVICES_ADDED,
-                                                                   added_devices));
-                            }
                             if (removed_devices.length != 0) {
                                 handler.sendMessage(Message.obtain(handler,
                                                                    MSG_DEVICES_DEVICES_REMOVED,
                                                                    removed_devices));
                             }
+                            if (added_devices.length != 0) {
+                                handler.sendMessage(Message.obtain(handler,
+                                                                   MSG_DEVICES_DEVICES_ADDED,
+                                                                   added_devices));
+                            }
                         }
                     }
                 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java b/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java
index b98f27e..f14def1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java
@@ -185,6 +185,7 @@
     @VisibleForTesting
     protected void bind(final Condition condition, final View row, final int rowId) {
         if (condition == null) throw new IllegalArgumentException("condition must not be null");
+
         final boolean enabled = condition.state == Condition.STATE_TRUE;
         final ConditionTag tag = row.getTag() != null ? (ConditionTag) row.getTag() :
                 new ConditionTag();
@@ -207,7 +208,6 @@
                     MetricsLogger.action(mContext,
                             MetricsProto.MetricsEvent.QS_DND_CONDITION_SELECT);
                     updateAlarmWarningText(tag.condition);
-                    announceConditionSelection(tag);
                 }
             }
         });
@@ -328,6 +328,7 @@
             boolean enabled, int rowId, Uri conditionId) {
         if (tag.lines == null) {
             tag.lines = row.findViewById(android.R.id.content);
+            tag.lines.setAccessibilityLiveRegion(View.ACCESSIBILITY_LIVE_REGION_POLITE);
         }
         if (tag.line1 == null) {
             tag.line1 = (TextView) row.findViewById(android.R.id.text1);
@@ -450,16 +451,6 @@
         bind(newCondition, row, rowId);
         updateAlarmWarningText(tag.condition);
         tag.rb.setChecked(true);
-        announceConditionSelection(tag);
-    }
-
-    private void announceConditionSelection(ConditionTag tag) {
-        // condition will always be priority-only
-        String modeText = mContext.getString(R.string.zen_interruption_level_priority);
-        if (tag.line1 != null) {
-            mZenRadioGroupContent.announceForAccessibility(mContext.getString(
-                    R.string.zen_mode_and_condition, modeText, tag.line1.getText()));
-        }
     }
 
     private void updateAlarmWarningText(Condition condition) {
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/ZenDurationDialog.java b/packages/SettingsLib/src/com/android/settingslib/notification/ZenDurationDialog.java
index 7369ba8..adf49aa 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/ZenDurationDialog.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/ZenDurationDialog.java
@@ -203,8 +203,14 @@
 
 
     private void setupUi(ConditionTag tag, View row) {
-        tag.lines = row.findViewById(android.R.id.content);
-        tag.line1 = (TextView) row.findViewById(android.R.id.text1);
+        if (tag.lines == null) {
+            tag.lines = row.findViewById(android.R.id.content);
+            tag.lines.setAccessibilityLiveRegion(View.ACCESSIBILITY_LIVE_REGION_POLITE);
+        }
+
+        if (tag.line1 == null) {
+            tag.line1 = (TextView) row.findViewById(android.R.id.text1);
+        }
 
         // text2 is not used in zen duration dialog
         row.findViewById(android.R.id.text2).setVisibility(View.GONE);
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index d53f1a0..88edd12 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -208,6 +208,9 @@
     <!-- to read and change hvac values in a car -->
     <uses-permission android:name="android.car.permission.CONTROL_CAR_CLIMATE" />
 
+    <!-- Permission necessary to change car audio volume through CarAudioManager -->
+    <uses-permission android:name="android.car.permission.CAR_CONTROL_AUDIO_VOLUME" />
+
     <application
         android:name=".SystemUIApplication"
         android:persistent="true"
@@ -419,7 +422,8 @@
             android:theme="@style/Theme.AlertDialogHost"
             android:finishOnCloseSystemDialogs="true"
             android:launchMode="singleTop"
-            android:excludeFromRecents="true" />
+            android:excludeFromRecents="true"
+            android:visibleToInstantApps="true"/>
 
         <!-- started from PipUI -->
         <activity
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml b/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml
index 9fdb00e..11bd98f 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_password_view.xml
@@ -49,6 +49,7 @@
          <EditText android:id="@+id/passwordEntry"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
+             android:contentDescription="@string/keyguard_accessibility_password"
              android:gravity="center_horizontal"
              android:singleLine="true"
              android:textStyle="normal"
diff --git a/packages/SystemUI/res-keyguard/values/strings.xml b/packages/SystemUI/res-keyguard/values/strings.xml
index ed63089..513d848 100644
--- a/packages/SystemUI/res-keyguard/values/strings.xml
+++ b/packages/SystemUI/res-keyguard/values/strings.xml
@@ -120,6 +120,9 @@
 
     <!-- Accessibility description of the PIN password view. [CHAR_LIMIT=none] -->
     <string name="keyguard_accessibility_pin_area">PIN area</string>
+    <!-- Accessibility description of the normal password view. [CHAR_LIMIT=none] -->
+    <string name="keyguard_accessibility_password">Device password</string>
+
     <!-- Accessibility description of the SIM PIN password view. [CHAR_LIMIT=none] -->
     <string name="keyguard_accessibility_sim_pin_area">SIM PIN area</string>
     <!-- Accessibility description of the SIM PUK password view. [CHAR_LIMIT=none] -->
diff --git a/packages/SystemUI/res/drawable/car_ic_notification_2.xml b/packages/SystemUI/res/drawable/car_ic_notification_2.xml
deleted file mode 100644
index c74ae15..0000000
--- a/packages/SystemUI/res/drawable/car_ic_notification_2.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2018 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License
-  -->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
-    android:width="32dp"
-    android:height="38dp"
-    android:viewportWidth="32"
-    android:viewportHeight="38" >
-  <group
-      android:translateX="-6"
-      android:translateY="-3">
-    <path
-        android:pathData="M26.6195649,6.98115478 C31.5083629,8.85235985 34.9817444,13.6069337 34.9817444,19.1767606 L34.9817444,27.9542254 L38,27.9542254 L38,34.2161972 L6,34.2161972 L6,27.9542254 L9.01825558,27.9542254 L9.01825558,19.1767606 C9.01825558,13.6069337 12.4916371,8.85235985 17.3804351,6.98115478 C17.723241,4.726863 19.6609451,3 22,3 C24.3390549,3 26.276759,4.726863 26.6195649,6.98115478 Z M17.326572,36.3035211 L26.673428,36.3035211 C26.673428,38.8973148 24.581063,41 22,41 C19.418937,41 17.326572,38.8973148 17.326572,36.3035211 Z"
-        android:strokeColor="#00000000"
-        android:fillType="evenOdd"
-        android:fillColor="@color/car_grey_50" />
-  </group>
-</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/attrs_car.xml b/packages/SystemUI/res/values/attrs_car.xml
index 99d2425..41e0786 100644
--- a/packages/SystemUI/res/values/attrs_car.xml
+++ b/packages/SystemUI/res/values/attrs_car.xml
@@ -63,4 +63,32 @@
         <attr name="hvacPropertyId" format="integer"/>
         <attr name="hvacTempFormat" format="string"/>
     </declare-styleable>
+
+    <declare-styleable name="carVolumeItems"/>
+    <declare-styleable name="carVolumeItems_item">
+        <!-- Align with AudioAttributes.USAGE_* -->
+        <attr name="usage">
+            <enum name="unknown" value="0"/>
+            <enum name="media" value="1"/>
+            <enum name="voice_communication" value="2"/>
+            <enum name="voice_communication_signalling" value="3"/>
+            <enum name="alarm" value="4"/>
+            <enum name="notification" value="5"/>
+            <enum name="notification_ringtone" value="6"/>
+            <enum name="notification_communication_request" value="7"/>
+            <enum name="notification_communication_instant" value="8"/>
+            <enum name="notification_communication_delayed" value="9"/>
+            <enum name="notification_event" value="10"/>
+            <enum name="assistance_accessibility" value="11"/>
+            <enum name="assistance_navigation_guidance" value="12"/>
+            <enum name="assistance_sonification" value="13"/>
+            <enum name="game" value="14"/>
+            <!-- hidden, do not use -->
+            <!-- enum name="virtual_source" value="15"/ -->
+            <enum name="assistant" value="16"/>
+        </attr>
+
+        <!-- Icon resource ids to render on UI -->
+        <attr name="icon" format="reference"/>
+    </declare-styleable>
 </resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 3a9b934..95ccc3c 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1363,6 +1363,13 @@
     <string name="volume_stream_content_description_vibrate_a11y">%1$s. Tap to set to vibrate.</string>
     <string name="volume_stream_content_description_mute_a11y">%1$s. Tap to mute.</string>
 
+    <!-- Hint for accessibility. For example: double tap to mute [CHAR_LIMIT=NONE] -->
+    <string name="volume_ringer_hint_mute">mute</string>
+    <!-- Hint for accessibility. For example: double tap to unmute [CHAR_LIMIT=NONE] -->
+    <string name="volume_ringer_hint_unmute">unmute</string>
+    <!-- Hint for accessibility. For example: double tap to vibrate [CHAR_LIMIT=NONE] -->
+    <string name="volume_ringer_hint_vibrate">vibrate</string>
+
     <string name="volume_dialog_title">%s volume controls</string>
 
     <string name="volume_dialog_ringer_guidance_ring">Calls and notifications will ring (<xliff:g id="volume level" example="56">%1$s</xliff:g>)</string>
diff --git a/packages/SystemUI/res/xml/car_volume_items.xml b/packages/SystemUI/res/xml/car_volume_items.xml
new file mode 100644
index 0000000..742dfdd
--- /dev/null
+++ b/packages/SystemUI/res/xml/car_volume_items.xml
@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ *
+ * Copyright 2018, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+*/
+-->
+
+<!--
+  Defines all possible items on car volume settings UI, keyed by usage.
+
+  This enables the CarSettings UI to associate VolumeGroups surfaced by
+  CarAudioManager.getVolumeGroupCount with renderable assets (ie: title, icon)
+  for presentation.
+
+  Order matters in this configuration. If one volume group contains multiple
+  audio usages, the first one appears in this file would be picked to be
+  presented on UI.
+
+  When overriding this configuration, please consult also the
+  car_volume_groups.xml, which is read by car audio service.
+-->
+<carVolumeItems xmlns:car="http://schemas.android.com/apk/res-auto">
+  <item car:usage="unknown"
+        car:icon="@drawable/car_ic_music"/>
+  <item car:usage="media"
+        car:icon="@drawable/car_ic_music"/>
+  <item car:usage="voice_communication"
+        car:icon="@*android:drawable/ic_audio_ring_notif"/>
+  <item car:usage="voice_communication_signalling"
+        car:icon="@*android:drawable/ic_audio_ring_notif"/>
+  <item car:usage="alarm"
+        car:icon="@*android:drawable/ic_audio_alarm"/>
+  <item car:usage="notification"
+        car:icon="@drawable/car_ic_notification"/>
+  <item car:usage="notification_ringtone"
+        car:icon="@*android:drawable/ic_audio_ring_notif"/>
+  <item car:usage="notification_communication_request"
+        car:icon="@drawable/car_ic_notification"/>
+  <item car:usage="notification_communication_instant"
+        car:icon="@drawable/car_ic_notification"/>
+  <item car:usage="notification_communication_delayed"
+        car:icon="@drawable/car_ic_notification"/>
+  <item car:usage="notification_event"
+        car:icon="@drawable/car_ic_notification"/>
+  <item car:usage="assistance_accessibility"
+        car:icon="@drawable/car_ic_notification"/>
+  <item car:usage="assistance_navigation_guidance"
+        car:icon="@drawable/car_ic_navigation"/>
+  <item car:usage="assistance_sonification"
+        car:icon="@drawable/car_ic_notification"/>
+  <item car:usage="game"
+        car:icon="@drawable/car_ic_music"/>
+  <item car:usage="assistant"
+        car:icon="@drawable/car_ic_music"/>
+</carVolumeItems>
+
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
index 30a17a1..f066e34 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSliceView.java
@@ -57,6 +57,7 @@
 
 import androidx.slice.Slice;
 import androidx.slice.SliceItem;
+import androidx.slice.SliceManager;
 import androidx.slice.core.SliceQuery;
 import androidx.slice.widget.ListContent;
 import androidx.slice.widget.RowContent;
@@ -390,6 +391,11 @@
         }
     }
 
+    public void refresh() {
+        Slice slice = SliceManager.getInstance(getContext()).bindSlice(mKeyguardSliceUri);
+        onChanged(slice);
+    }
+
     public static class Row extends LinearLayout {
 
         /**
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
index 454528e..f6b5d69 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardStatusView.java
@@ -287,7 +287,12 @@
         }
     }
 
-    public void refreshTime() {
+    public void dozeTimeTick() {
+        refreshTime();
+        mKeyguardSlice.refresh();
+    }
+
+    private void refreshTime() {
         mClockView.refresh();
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
index f867b34..c5e66f9 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardSliceProvider.java
@@ -79,7 +79,6 @@
     private DateFormat mDateFormat;
     private String mLastText;
     private boolean mRegistered;
-    private boolean mRegisteredEveryMinute;
     private String mNextAlarm;
     private NextAlarmController mNextAlarmController;
     protected AlarmManager mAlarmManager;
@@ -175,7 +174,7 @@
         mZenModeController = new ZenModeControllerImpl(getContext(), mHandler);
         mZenModeController.addCallback(this);
         mDatePattern = getContext().getString(R.string.system_ui_aod_date_pattern);
-        registerClockUpdate(false /* everyMinute */);
+        registerClockUpdate();
         updateClock();
         return true;
     }
@@ -214,22 +213,13 @@
     /**
      * Registers a broadcast receiver for clock updates, include date, time zone and manually
      * changing the date/time via the settings app.
-     *
-     * @param everyMinute {@code true} if you also want updates every minute.
      */
-    protected void registerClockUpdate(boolean everyMinute) {
+    private void registerClockUpdate() {
         if (mRegistered) {
-            if (mRegisteredEveryMinute == everyMinute) {
-                return;
-            } else {
-                unregisterClockUpdate();
-            }
+            return;
         }
 
         IntentFilter filter = new IntentFilter();
-        if (everyMinute) {
-            filter.addAction(Intent.ACTION_TIME_TICK);
-        }
         filter.addAction(Intent.ACTION_DATE_CHANGED);
         filter.addAction(Intent.ACTION_TIME_CHANGED);
         filter.addAction(Intent.ACTION_TIMEZONE_CHANGED);
@@ -237,15 +227,6 @@
         getContext().registerReceiver(mIntentReceiver, filter, null /* permission*/,
                 null /* scheduler */);
         mRegistered = true;
-        mRegisteredEveryMinute = everyMinute;
-    }
-
-    protected void unregisterClockUpdate() {
-        if (!mRegistered) {
-            return;
-        }
-        getContext().unregisterReceiver(mIntentReceiver);
-        mRegistered = false;
     }
 
     @VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
index 5748ec9b..19980a2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarMobileView.java
@@ -211,6 +211,11 @@
         //TODO: May not be needed. Mobile is always expected to be visible (not a dot)
     }
 
+    @Override
+    public int getVisibleState() {
+        return 0;
+    }
+
     @VisibleForTesting
     public MobileIconState getState() {
         return mState;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java
index c3b4fdd..ca00a5a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarWifiView.java
@@ -146,6 +146,11 @@
         }
     }
 
+    @Override
+    public int getVisibleState() {
+        return mVisibleState;
+    }
+
     private void init() {
         int dualToneLightTheme = Utils.getThemeAttr(mContext, R.attr.lightIconTheme);
         int dualToneDarkTheme = Utils.getThemeAttr(mContext, R.attr.darkIconTheme);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusIconDisplayable.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusIconDisplayable.java
index 6383816..b831b86 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusIconDisplayable.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusIconDisplayable.java
@@ -23,6 +23,7 @@
     void setStaticDrawableColor(int color);
     void setDecorColor(int color);
     void setVisibleState(int state);
+    int getVisibleState();
     boolean isIconVisible();
     default boolean isIconBlocked() {
         return false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridGroupManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridGroupManager.java
index 3ed8cce..3975ec2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridGroupManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HybridGroupManager.java
@@ -37,10 +37,10 @@
     private final NotificationDozeHelper mDozer;
     private final ViewGroup mParent;
 
-    private final float mOverflowNumberSizeDark;
-    private final int mOverflowNumberPaddingDark;
-    private final float mOverflowNumberSize;
-    private final int mOverflowNumberPadding;
+    private float mOverflowNumberSizeDark;
+    private int mOverflowNumberPaddingDark;
+    private float mOverflowNumberSize;
+    private int mOverflowNumberPadding;
 
     private int mOverflowNumberColor;
     private int mOverflowNumberColorDark;
@@ -50,7 +50,10 @@
         mContext = ctx;
         mParent = parent;
         mDozer = new NotificationDozeHelper();
+        initDimens();
+    }
 
+    public void initDimens() {
         Resources res = mContext.getResources();
         mOverflowNumberSize = res.getDimensionPixelSize(
                 R.dimen.group_overflow_number_size);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
index 9fcb090..ee83250 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragment.java
@@ -212,7 +212,7 @@
     }
 
     public void hideClock(boolean animate) {
-        animateHide(mClockView, animate);
+        animateHiddenState(mClockView, View.GONE, animate);
     }
 
     public void showClock(boolean animate) {
@@ -240,21 +240,29 @@
     }
 
     /**
-     * Hides a view.
+     * Animate a view to INVISIBLE or GONE
      */
-    private void animateHide(final View v, boolean animate) {
+    private void animateHiddenState(final View v, int state, boolean animate) {
         v.animate().cancel();
         if (!animate) {
             v.setAlpha(0f);
-            v.setVisibility(View.INVISIBLE);
+            v.setVisibility(state);
             return;
         }
+
         v.animate()
                 .alpha(0f)
                 .setDuration(160)
                 .setStartDelay(0)
                 .setInterpolator(Interpolators.ALPHA_OUT)
-                .withEndAction(() -> v.setVisibility(View.INVISIBLE));
+                .withEndAction(() -> v.setVisibility(state));
+    }
+
+    /**
+     * Hides a view.
+     */
+    private void animateHide(final View v, boolean animate) {
+        animateHiddenState(v, View.INVISIBLE, animate);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
index b650944..b475b64 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelView.java
@@ -2267,7 +2267,7 @@
     }
 
     public void onScreenTurningOn() {
-        mKeyguardStatusView.refreshTime();
+        mKeyguardStatusView.dozeTimeTick();
     }
 
     @Override
@@ -2690,7 +2690,7 @@
     }
 
     public void dozeTimeTick() {
-        mKeyguardStatusView.refreshTime();
+        mKeyguardStatusView.dozeTimeTick();
         mKeyguardBottomArea.dozeTimeTick();
         if (mDarkAmount > 0) {
             positionClockAndNotifications();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java
index 8398879..4538977 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusIconContainer.java
@@ -30,12 +30,12 @@
 import android.util.Log;
 
 import android.view.View;
-import android.view.ViewGroup;
 import com.android.keyguard.AlphaOptimizedLinearLayout;
 import com.android.systemui.R;
 import com.android.systemui.statusbar.StatusIconDisplayable;
+import com.android.systemui.statusbar.stack.AnimationFilter;
+import com.android.systemui.statusbar.stack.AnimationProperties;
 import com.android.systemui.statusbar.stack.ViewState;
-import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 
 /**
@@ -50,8 +50,8 @@
     private static final boolean DEBUG = false;
     private static final boolean DEBUG_OVERFLOW = false;
     // Max 5 status icons including battery
-    private static final int MAX_ICONS = 4;
-    private static final int MAX_DOTS = 3;
+    private static final int MAX_ICONS = 7;
+    private static final int MAX_DOTS = 1;
 
     private int mDotPadding;
     private int mStaticDotDiameter;
@@ -97,7 +97,7 @@
         mDotPadding = getResources().getDimensionPixelSize(R.dimen.overflow_icon_dot_padding);
         int radius = getResources().getDimensionPixelSize(R.dimen.overflow_dot_radius);
         mStaticDotDiameter = 2 * radius;
-        mUnderflowWidth = mIconDotFrameWidth + 2 * (mStaticDotDiameter + mDotPadding);
+        mUnderflowWidth = mIconDotFrameWidth + (MAX_DOTS - 1) * (mStaticDotDiameter + mDotPadding);
     }
 
     @Override
@@ -193,6 +193,7 @@
     public void onViewAdded(View child) {
         super.onViewAdded(child);
         StatusIconState vs = new StatusIconState();
+        vs.justAdded = true;
         child.setTag(R.id.status_bar_view_state_tag, vs);
     }
 
@@ -212,7 +213,6 @@
         float contentStart = getPaddingStart();
         int childCount = getChildCount();
         // Underflow === don't show content until that index
-        int firstUnderflowIndex = -1;
         if (DEBUG) android.util.Log.d(TAG, "calculateIconTranslations: start=" + translationX
                 + " width=" + width + " underflow=" + mNeedsUnderflow);
 
@@ -235,13 +235,13 @@
             translationX -= getViewTotalWidth(child);
         }
 
-        // Show either 1-4 dots, or 3 dots + overflow
+        // Show either 1-MAX_ICONS icons, or (MAX_ICONS - 1) icons + overflow
         int totalVisible = mLayoutStates.size();
         int maxVisible = totalVisible <= MAX_ICONS ? MAX_ICONS : MAX_ICONS - 1;
 
         mUnderflowStart = 0;
         int visible = 0;
-        firstUnderflowIndex = -1;
+        int firstUnderflowIndex = -1;
         for (int i = totalVisible - 1; i >= 0; i--) {
             StatusIconState state = mLayoutStates.get(i);
             // Allow room for underflow if we found we need it in onMeasure
@@ -324,14 +324,52 @@
     public static class StatusIconState extends ViewState {
         /// StatusBarIconView.STATE_*
         public int visibleState = STATE_ICON;
+        public boolean justAdded = true;
 
         @Override
         public void applyToView(View view) {
-            if (view instanceof  StatusIconDisplayable) {
-                StatusIconDisplayable icon = (StatusIconDisplayable) view;
-                icon.setVisibleState(visibleState);
+            if (!(view instanceof StatusIconDisplayable)) {
+                return;
             }
-            super.applyToView(view);
+            StatusIconDisplayable icon = (StatusIconDisplayable) view;
+            AnimationProperties animationProperties = null;
+            boolean animate = false;
+
+            if (justAdded) {
+                super.applyToView(view);
+                animationProperties = ADD_ICON_PROPERTIES;
+                animate = true;
+            } else if (icon.getVisibleState() != visibleState) {
+                animationProperties = DOT_ANIMATION_PROPERTIES;
+                animate = true;
+            }
+
+            icon.setVisibleState(visibleState);
+            if (animate) {
+                animateTo(view, animationProperties);
+            } else {
+                super.applyToView(view);
+            }
+
+            justAdded = false;
         }
     }
+
+    private static final AnimationProperties ADD_ICON_PROPERTIES = new AnimationProperties() {
+        private AnimationFilter mAnimationFilter = new AnimationFilter().animateAlpha();
+
+        @Override
+        public AnimationFilter getAnimationFilter() {
+            return mAnimationFilter;
+        }
+    }.setDuration(200).setDelay(50);
+
+    private static final AnimationProperties DOT_ANIMATION_PROPERTIES = new AnimationProperties() {
+        private AnimationFilter mAnimationFilter = new AnimationFilter().animateX();
+
+        @Override
+        public AnimationFilter getAnimationFilter() {
+            return mAnimationFilter;
+        }
+    }.setDuration(200);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
index e5ab712..8115297 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/stack/NotificationChildrenContainer.java
@@ -121,8 +121,8 @@
     public NotificationChildrenContainer(Context context, AttributeSet attrs, int defStyleAttr,
             int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
-        initDimens();
         mHybridGroupManager = new HybridGroupManager(getContext(), this);
+        initDimens();
         setClipChildren(false);
     }
 
@@ -148,6 +148,7 @@
         mTranslationForHeader = res.getDimensionPixelSize(
                 com.android.internal.R.dimen.notification_content_margin)
                 - mNotificationHeaderMargin;
+        mHybridGroupManager.initDimens();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java
index f1a7183..8034345 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/CarVolumeDialogImpl.java
@@ -19,23 +19,34 @@
 import android.animation.Animator;
 import android.animation.AnimatorInflater;
 import android.animation.AnimatorSet;
+import android.annotation.DrawableRes;
 import android.annotation.Nullable;
 import android.app.Dialog;
 import android.app.KeyguardManager;
+import android.car.Car;
+import android.car.CarNotConnectedException;
+import android.car.media.CarAudioManager;
+import android.car.media.ICarVolumeCallback;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.DialogInterface;
+import android.content.ServiceConnection;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
 import android.graphics.Color;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.PixelFormat;
 import android.graphics.drawable.Drawable;
-import android.media.AudioManager;
-import android.media.AudioSystem;
+import android.media.AudioAttributes;
 import android.os.Debug;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
+import android.util.AttributeSet;
 import android.util.Log;
-import android.util.SparseBooleanArray;
+import android.util.SparseArray;
+import android.util.Xml;
 import android.view.ContextThemeWrapper;
 import android.view.Gravity;
 import android.view.MotionEvent;
@@ -53,626 +64,533 @@
 import androidx.car.widget.PagedListView;
 import androidx.car.widget.SeekbarListItem;
 
+import java.util.Iterator;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.Iterator;
 import java.util.List;
 
-import com.android.systemui.Dependency;
 import com.android.systemui.R;
 import com.android.systemui.plugins.VolumeDialog;
-import com.android.systemui.plugins.VolumeDialogController;
-import com.android.systemui.plugins.VolumeDialogController.State;
-import com.android.systemui.plugins.VolumeDialogController.StreamState;
 
 /**
  * Car version of the volume dialog.
  *
- * A client of VolumeDialogControllerImpl and its state model.
- *
  * Methods ending in "H" must be called on the (ui) handler.
  */
 public class CarVolumeDialogImpl implements VolumeDialog {
-    private static final String TAG = Util.logTag(CarVolumeDialogImpl.class);
+  private static final String TAG = Util.logTag(CarVolumeDialogImpl.class);
 
-    private static final long USER_ATTEMPT_GRACE_PERIOD = 1000;
+  private static final String XML_TAG_VOLUME_ITEMS = "carVolumeItems";
+  private static final String XML_TAG_VOLUME_ITEM = "item";
+  private static final int HOVERING_TIMEOUT = 16000;
+  private static final int NORMAL_TIMEOUT = 3000;
+  private static final int LISTVIEW_ANIMATION_DURATION_IN_MILLIS = 250;
+  private static final int DISMISS_DELAY_IN_MILLIS = 50;
+  private static final int ARROW_FADE_IN_START_DELAY_IN_MILLIS = 100;
 
-    private final Context mContext;
-    private final H mHandler = new H();
-    private final VolumeDialogController mController;
-    private final AudioManager mAudioManager;
+  private final Context mContext;
+  private final H mHandler = new H();
 
-    private Window mWindow;
-    private CustomDialog mDialog;
-    private PagedListView mListView;
-    private ListItemAdapter mPagedListAdapter;
-    private final List<ListItem> mVolumeLineItems = new ArrayList<>();
-    private final List<VolumeRow> mRows = new ArrayList<>();
-    private ConfigurableTexts mConfigurableTexts;
-    private final SparseBooleanArray mDynamic = new SparseBooleanArray();
-    private final KeyguardManager mKeyguard;
-    private final Object mSafetyWarningLock = new Object();
+  private Window mWindow;
+  private CustomDialog mDialog;
+  private PagedListView mListView;
+  private ListItemAdapter mPagedListAdapter;
+  // All the volume items.
+  private final SparseArray<VolumeItem> mVolumeItems = new SparseArray<>();
+  // Available volume items in car audio manager.
+  private final List<VolumeItem> mAvailableVolumeItems = new ArrayList<>();
+  // Volume items in the PagedListView.
+  private final List<ListItem> mVolumeLineItems = new ArrayList<>();
+  private final KeyguardManager mKeyguard;
 
-    private boolean mShowing;
+  private Car mCar;
+  private CarAudioManager mCarAudioManager;
 
-    private boolean mAutomute = VolumePrefs.DEFAULT_ENABLE_AUTOMUTE;
-    private boolean mSilentMode = VolumePrefs.DEFAULT_ENABLE_SILENT_MODE;
-    private State mState;
-    private SafetyWarningDialog mSafetyWarning;
-    private boolean mHovering = false;
-    private boolean mExpanded;
+  private boolean mHovering;
+  private boolean mShowing;
+  private boolean mExpanded;
 
-    public CarVolumeDialogImpl(Context context) {
-        mContext = new ContextThemeWrapper(context, com.android.systemui.R.style.qs_theme);
-        mController = Dependency.get(VolumeDialogController.class);
-        mKeyguard = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
-        mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+  public CarVolumeDialogImpl(Context context) {
+    mContext = new ContextThemeWrapper(context, com.android.systemui.R.style.qs_theme);
+    mKeyguard = (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
+    mCar = Car.createCar(mContext, mServiceConnection);
+  }
+
+  public void init(int windowType, Callback callback) {
+    initDialog();
+
+    mCar.connect();
+  }
+
+  @Override
+  public void destroy() {
+    mHandler.removeCallbacksAndMessages(null);
+
+    cleanupAudioManager();
+    // unregisterVolumeCallback is not being called when disconnect car, so we manually cleanup
+    // audio manager beforehand.
+    mCar.disconnect();
+  }
+
+  private void initDialog() {
+    loadAudioUsageItems();
+    mVolumeLineItems.clear();
+    mDialog = new CustomDialog(mContext);
+
+    mHovering = false;
+    mShowing = false;
+    mExpanded = false;
+    mWindow = mDialog.getWindow();
+    mWindow.requestFeature(Window.FEATURE_NO_TITLE);
+    mWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
+    mWindow.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND
+        | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR);
+    mWindow.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+        | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+        | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
+        | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
+        | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
+        | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
+    mWindow.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY);
+    mWindow.setWindowAnimations(com.android.internal.R.style.Animation_Toast);
+    final WindowManager.LayoutParams lp = mWindow.getAttributes();
+    lp.format = PixelFormat.TRANSLUCENT;
+    lp.setTitle(VolumeDialogImpl.class.getSimpleName());
+    lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
+    lp.windowAnimations = -1;
+    mWindow.setAttributes(lp);
+    mWindow.setLayout(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+
+    mDialog.setCanceledOnTouchOutside(true);
+    mDialog.setContentView(R.layout.car_volume_dialog);
+    mDialog.setOnShowListener(dialog -> {
+      mListView.setTranslationY(-mListView.getHeight());
+      mListView.setAlpha(0);
+      mListView.animate()
+          .alpha(1)
+          .translationY(0)
+          .setDuration(LISTVIEW_ANIMATION_DURATION_IN_MILLIS)
+          .setInterpolator(new SystemUIInterpolators.LogDecelerateInterpolator())
+          .start();
+    });
+    mListView = (PagedListView) mWindow.findViewById(R.id.volume_list);
+    mListView.setOnHoverListener((v, event) -> {
+      int action = event.getActionMasked();
+      mHovering = (action == MotionEvent.ACTION_HOVER_ENTER)
+          || (action == MotionEvent.ACTION_HOVER_MOVE);
+      rescheduleTimeoutH();
+      return true;
+    });
+
+    mPagedListAdapter = new ListItemAdapter(mContext, new ListProvider(mVolumeLineItems),
+        BackgroundStyle.PANEL);
+    mListView.setAdapter(mPagedListAdapter);
+    mListView.setMaxPages(PagedListView.UNLIMITED_PAGES);
+  }
+
+  public void show(int reason) {
+    mHandler.obtainMessage(H.SHOW, reason, 0).sendToTarget();
+  }
+
+  public void dismiss(int reason) {
+    mHandler.obtainMessage(H.DISMISS, reason, 0).sendToTarget();
+  }
+
+  private void showH(int reason) {
+    if (D.BUG) {
+      Log.d(TAG, "showH r=" + Events.DISMISS_REASONS[reason]);
     }
 
-    public void init(int windowType, Callback callback) {
-        initDialog();
+    mHandler.removeMessages(H.SHOW);
+    mHandler.removeMessages(H.DISMISS);
+    rescheduleTimeoutH();
+    // Refresh the data set before showing.
+    mPagedListAdapter.notifyDataSetChanged();
+    if (mShowing) {
+      return;
+    }
+    mShowing = true;
 
-        mController.addCallback(mControllerCallbackH, mHandler);
-        mController.getState();
+    mDialog.show();
+    Events.writeEvent(mContext, Events.EVENT_SHOW_DIALOG, reason, mKeyguard.isKeyguardLocked());
+  }
+
+  protected void rescheduleTimeoutH() {
+    mHandler.removeMessages(H.DISMISS);
+    final int timeout = computeTimeoutH();
+    mHandler.sendMessageDelayed(mHandler
+        .obtainMessage(H.DISMISS, Events.DISMISS_REASON_TIMEOUT, 0), timeout);
+
+    if (D.BUG) {
+      Log.d(TAG, "rescheduleTimeout " + timeout + " " + Debug.getCaller());
+    }
+  }
+
+  private int computeTimeoutH() {
+    return mHovering ? HOVERING_TIMEOUT : NORMAL_TIMEOUT;
+  }
+
+  protected void dismissH(int reason) {
+    if (D.BUG) {
+      Log.d(TAG, "dismissH r=" + Events.DISMISS_REASONS[reason]);
+    }
+
+    mHandler.removeMessages(H.DISMISS);
+    mHandler.removeMessages(H.SHOW);
+    if (!mShowing) {
+      return;
+    }
+
+    mListView.animate().cancel();
+    mShowing = false;
+
+    mListView.setTranslationY(0);
+    mListView.setAlpha(1);
+    mListView.animate()
+        .alpha(0)
+        .translationY(-mListView.getHeight())
+        .setDuration(LISTVIEW_ANIMATION_DURATION_IN_MILLIS)
+        .setInterpolator(new SystemUIInterpolators.LogAccelerateInterpolator())
+        .withEndAction(() -> mHandler.postDelayed(() -> {
+          if (D.BUG) {
+            Log.d(TAG, "mDialog.dismiss()");
+          }
+          mDialog.dismiss();
+        }, DISMISS_DELAY_IN_MILLIS))
+        .start();
+
+    Events.writeEvent(mContext, Events.EVENT_DISMISS_DIALOG, reason);
+  }
+
+  public void dump(PrintWriter writer) {
+    writer.println(VolumeDialogImpl.class.getSimpleName() + " state:");
+    writer.print("  mShowing: "); writer.println(mShowing);
+  }
+
+  private void loadAudioUsageItems() {
+    try (XmlResourceParser parser = mContext.getResources().getXml(R.xml.car_volume_items)) {
+      AttributeSet attrs = Xml.asAttributeSet(parser);
+      int type;
+      // Traverse to the first start tag
+      while ((type=parser.next()) != XmlResourceParser.END_DOCUMENT
+          && type != XmlResourceParser.START_TAG) {
+      }
+
+      if (!XML_TAG_VOLUME_ITEMS.equals(parser.getName())) {
+        throw new RuntimeException("Meta-data does not start with carVolumeItems tag");
+      }
+      int outerDepth = parser.getDepth();
+      int rank = 0;
+      while ((type=parser.next()) != XmlResourceParser.END_DOCUMENT
+          && (type != XmlResourceParser.END_TAG || parser.getDepth() > outerDepth)) {
+        if (type == XmlResourceParser.END_TAG) {
+          continue;
+        }
+        if (XML_TAG_VOLUME_ITEM.equals(parser.getName())) {
+          TypedArray item = mContext.getResources().obtainAttributes(
+              attrs, R.styleable.carVolumeItems_item);
+          int usage = item.getInt(R.styleable.carVolumeItems_item_usage, -1);
+          if (usage >= 0) {
+            VolumeItem volumeItem = new VolumeItem();
+            volumeItem.usage = usage;
+            volumeItem.rank = rank;
+            volumeItem.icon = item.getResourceId(R.styleable.carVolumeItems_item_icon, 0);
+            mVolumeItems.put(usage, volumeItem);
+            rank++;
+          }
+          item.recycle();
+        }
+      }
+    } catch (XmlPullParserException | IOException e) {
+      Log.e(TAG, "Error parsing volume groups configuration", e);
+    }
+  }
+
+  private VolumeItem getVolumeItemForUsages(int[] usages) {
+    int rank = Integer.MAX_VALUE;
+    VolumeItem result = null;
+    for (int usage : usages) {
+      VolumeItem volumeItem = mVolumeItems.get(usage);
+      if (volumeItem.rank < rank) {
+        rank = volumeItem.rank;
+        result = volumeItem;
+      }
+    }
+    return result;
+  }
+
+  private static int getSeekbarValue(CarAudioManager carAudioManager, int volumeGroupId) {
+    try {
+      return carAudioManager.getGroupVolume(volumeGroupId);
+    } catch (CarNotConnectedException e) {
+      Log.e(TAG, "Car is not connected!", e);
+    }
+    return 0;
+  }
+
+  private static int getMaxSeekbarValue(CarAudioManager carAudioManager, int volumeGroupId) {
+    try {
+      return carAudioManager.getGroupMaxVolume(volumeGroupId);
+    } catch (CarNotConnectedException e) {
+      Log.e(TAG, "Car is not connected!", e);
+    }
+    return 0;
+  }
+
+  private SeekbarListItem addSeekbarListItem(VolumeItem volumeItem, int volumeGroupId,
+      int supplementalIconId, @Nullable View.OnClickListener supplementalIconOnClickListener) {
+    SeekbarListItem listItem = new SeekbarListItem(mContext);
+    listItem.setMax(getMaxSeekbarValue(mCarAudioManager, volumeGroupId));
+    int progress = getSeekbarValue(mCarAudioManager, volumeGroupId);
+    listItem.setProgress(progress);
+    listItem.setOnSeekBarChangeListener(
+        new CarVolumeDialogImpl.VolumeSeekBarChangeListener(volumeGroupId, mCarAudioManager));
+    listItem.setPrimaryActionIcon(mContext.getResources().getDrawable(volumeItem.icon));
+    if (supplementalIconId != 0) {
+      Drawable supplementalIcon = mContext.getResources().getDrawable(supplementalIconId);
+      listItem.setSupplementalIcon(supplementalIcon, true,
+          supplementalIconOnClickListener);
+    } else {
+      listItem.setSupplementalEmptyIcon(true);
+    }
+
+    mVolumeLineItems.add(listItem);
+    volumeItem.listItem = listItem;
+    volumeItem.progress = progress;
+    return listItem;
+  }
+
+  private VolumeItem findVolumeItem(SeekbarListItem targetItem) {
+    for (int i = 0; i < mVolumeItems.size(); ++i) {
+      VolumeItem volumeItem = mVolumeItems.valueAt(i);
+      if (volumeItem.listItem == targetItem) {
+        return volumeItem;
+      }
+    }
+    return null;
+  }
+
+  private void cleanupAudioManager() {
+    try {
+      mCarAudioManager.unregisterVolumeCallback(mVolumeChangeCallback.asBinder());
+    } catch (CarNotConnectedException e) {
+      Log.e(TAG, "Car is not connected!", e);
+    }
+    mVolumeLineItems.clear();
+    mCarAudioManager = null;
+  }
+
+  private final class H extends Handler {
+    private static final int SHOW = 1;
+    private static final int DISMISS = 2;
+
+    public H() {
+      super(Looper.getMainLooper());
     }
 
     @Override
-    public void destroy() {
-        mController.removeCallback(mControllerCallbackH);
-        mHandler.removeCallbacksAndMessages(null);
+    public void handleMessage(Message msg) {
+      switch (msg.what) {
+        case SHOW:
+          showH(msg.arg1);
+          break;
+        case DISMISS:
+          dismissH(msg.arg1);
+          break;
+        default:
+      }
+    }
+  }
+
+  private final class CustomDialog extends Dialog implements DialogInterface {
+    public CustomDialog(Context context) {
+      super(context, com.android.systemui.R.style.qs_theme);
     }
 
-    private void initDialog() {
-        mRows.clear();
-        mVolumeLineItems.clear();
-        mDialog = new CustomDialog(mContext);
+    @Override
+    public boolean dispatchTouchEvent(MotionEvent ev) {
+      rescheduleTimeoutH();
+      return super.dispatchTouchEvent(ev);
+    }
 
-        mConfigurableTexts = new ConfigurableTexts(mContext);
-        mHovering = false;
-        mShowing = false;
+    @Override
+    protected void onStart() {
+      super.setCanceledOnTouchOutside(true);
+      super.onStart();
+    }
+
+    @Override
+    protected void onStop() {
+      super.onStop();
+    }
+
+    @Override
+    public boolean onTouchEvent(MotionEvent event) {
+      if (isShowing()) {
+        if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
+          dismissH(Events.DISMISS_REASON_TOUCH_OUTSIDE);
+          return true;
+        }
+      }
+      return false;
+    }
+  }
+
+  private final class ExpandIconListener implements View.OnClickListener {
+    @Override
+    public void onClick(final View v) {
+      mExpanded = !mExpanded;
+      Animator inAnimator;
+      if (mExpanded) {
+        for (int groupId = 0; groupId < mAvailableVolumeItems.size(); ++groupId) {
+          // Adding the items which are not coming from the default item.
+          VolumeItem volumeItem = mAvailableVolumeItems.get(groupId);
+          if (volumeItem.defaultItem) {
+            // Set progress here due to the progress of seekbar may not be updated.
+            volumeItem.listItem.setProgress(volumeItem.progress);
+          } else {
+            addSeekbarListItem(volumeItem, groupId, 0, null);
+          }
+        }
+        inAnimator = AnimatorInflater.loadAnimator(
+            mContext, R.anim.car_arrow_fade_in_rotate_up);
+      } else {
+        // Only keeping the default stream if it is not expended.
+        Iterator itr = mVolumeLineItems.iterator();
+        while (itr.hasNext()) {
+          SeekbarListItem seekbarListItem = (SeekbarListItem) itr.next();
+          VolumeItem volumeItem = findVolumeItem(seekbarListItem);
+          if (!volumeItem.defaultItem) {
+            itr.remove();
+          } else {
+            // Set progress here due to the progress of seekbar may not be updated.
+            seekbarListItem.setProgress(volumeItem.progress);
+          }
+        }
+        inAnimator = AnimatorInflater.loadAnimator(
+            mContext, R.anim.car_arrow_fade_in_rotate_down);
+      }
+
+      Animator outAnimator = AnimatorInflater.loadAnimator(
+          mContext, R.anim.car_arrow_fade_out);
+      inAnimator.setStartDelay(ARROW_FADE_IN_START_DELAY_IN_MILLIS);
+      AnimatorSet animators = new AnimatorSet();
+      animators.playTogether(outAnimator, inAnimator);
+      animators.setTarget(v);
+      animators.start();
+      mPagedListAdapter.notifyDataSetChanged();
+    }
+  }
+
+  private final class VolumeSeekBarChangeListener implements OnSeekBarChangeListener {
+    private final int mVolumeGroupId;
+    private final CarAudioManager mCarAudioManager;
+
+    private VolumeSeekBarChangeListener(int volumeGroupId, CarAudioManager carAudioManager) {
+      mVolumeGroupId = volumeGroupId;
+      mCarAudioManager = carAudioManager;
+    }
+
+    @Override
+    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+      if (!fromUser) {
+        // For instance, if this event is originated from AudioService,
+        // we can ignore it as it has already been handled and doesn't need to be
+        // sent back down again.
+        return;
+      }
+      try {
+        if (mCarAudioManager == null) {
+          Log.w(TAG, "Ignoring volume change event because the car isn't connected");
+          return;
+        }
+        mAvailableVolumeItems.get(mVolumeGroupId).progress = progress;
+        mCarAudioManager.setGroupVolume(mVolumeGroupId, progress, 0);
+      } catch (CarNotConnectedException e) {
+        Log.e(TAG, "Car is not connected!", e);
+      }
+    }
+
+    @Override
+    public void onStartTrackingTouch(SeekBar seekBar) {}
+
+    @Override
+    public void onStopTrackingTouch(SeekBar seekBar) {}
+  }
+
+  private final ICarVolumeCallback mVolumeChangeCallback = new ICarVolumeCallback.Stub() {
+    @Override
+    public void onGroupVolumeChanged(int groupId) {
+      VolumeItem volumeItem = mAvailableVolumeItems.get(groupId);
+      int value = getSeekbarValue(mCarAudioManager, groupId);
+      // Do not update the progress if it is the same as before. When car audio manager sets its
+      // group volume caused by the seekbar progress changed, it also triggers this callback.
+      // Updating the seekbar at the same time could block the continuous seeking.
+      if (value != volumeItem.progress) {
+        volumeItem.listItem.setProgress(value);
+        volumeItem.progress = value;
+        show(Events.SHOW_REASON_VOLUME_CHANGED);
+      }
+    }
+
+    @Override
+    public void onMasterMuteChanged() {
+      // ignored
+    }
+  };
+
+  private final ServiceConnection mServiceConnection = new ServiceConnection() {
+    @Override
+    public void onServiceConnected(ComponentName name, IBinder service) {
+      try {
         mExpanded = false;
-        mWindow = mDialog.getWindow();
-        mWindow.requestFeature(Window.FEATURE_NO_TITLE);
-        mWindow.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
-        mWindow.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND
-            | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR);
-        mWindow.addFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
-            | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
-            | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
-            | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
-            | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
-            | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
-        mWindow.setType(WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY);
-        mWindow.setWindowAnimations(com.android.internal.R.style.Animation_Toast);
-        final WindowManager.LayoutParams lp = mWindow.getAttributes();
-        lp.format = PixelFormat.TRANSLUCENT;
-        lp.setTitle(VolumeDialogImpl.class.getSimpleName());
-        lp.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
-        lp.windowAnimations = -1;
-        mWindow.setAttributes(lp);
-        mWindow.setLayout(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
+        mCarAudioManager = (CarAudioManager) mCar.getCarManager(Car.AUDIO_SERVICE);
+        int volumeGroupCount = mCarAudioManager.getVolumeGroupCount();
+        // Populates volume slider items from volume groups to UI.
+        for (int groupId = 0; groupId < volumeGroupCount; groupId++) {
+          VolumeItem volumeItem = getVolumeItemForUsages(
+              mCarAudioManager.getUsagesForVolumeGroupId(groupId));
+          mAvailableVolumeItems.add(volumeItem);
+          // The first one is the default item.
+          if (groupId == 0) {
+            volumeItem.defaultItem = true;
+            addSeekbarListItem(volumeItem, groupId, R.drawable.car_ic_keyboard_arrow_down,
+                new ExpandIconListener());
+          }
+        }
 
-        mDialog.setCanceledOnTouchOutside(true);
-        mDialog.setContentView(R.layout.car_volume_dialog);
-        mDialog.setOnShowListener(dialog -> {
-            mListView.setTranslationY(-mListView.getHeight());
-            mListView.setAlpha(0);
-            mListView.animate()
-                .alpha(1)
-                .translationY(0)
-                .setDuration(300)
-                .setInterpolator(new SystemUIInterpolators.LogDecelerateInterpolator())
-                .start();
-        });
-        mListView = (PagedListView) mWindow.findViewById(R.id.volume_list);
-        mListView.setOnHoverListener((v, event) -> {
-            int action = event.getActionMasked();
-            mHovering = (action == MotionEvent.ACTION_HOVER_ENTER)
-                || (action == MotionEvent.ACTION_HOVER_MOVE);
-            rescheduleTimeoutH();
-            return true;
-        });
-
-        addSeekbarListItem(addVolumeRow(AudioManager.STREAM_MUSIC, R.drawable.car_ic_music,
-            R.drawable.car_ic_keyboard_arrow_down, true, true),
-            new ExpandIconListener());
-        // We map AudioManager.STREAM_RING to a navigation icon for demo.
-        addVolumeRow(AudioManager.STREAM_RING, R.drawable.car_ic_navigation, 0,
-            true, false);
-        addVolumeRow(AudioManager.STREAM_NOTIFICATION, R.drawable.car_ic_notification_2, 0,
-            true, false);
-
-        mPagedListAdapter = new ListItemAdapter(mContext, new ListProvider(mVolumeLineItems),
-            BackgroundStyle.PANEL);
-        mListView.setAdapter(mPagedListAdapter);
-        mListView.setMaxPages(PagedListView.UNLIMITED_PAGES);
+        // If list is already initiated, update its content.
+        if (mPagedListAdapter != null) {
+          mPagedListAdapter.notifyDataSetChanged();
+        }
+        mCarAudioManager.registerVolumeCallback(mVolumeChangeCallback.asBinder());
+      } catch (CarNotConnectedException e) {
+        Log.e(TAG, "Car is not connected!", e);
+      }
     }
 
-    public void setStreamImportant(int stream, boolean important) {
-        mHandler.obtainMessage(H.SET_STREAM_IMPORTANT, stream, important ? 1 : 0).sendToTarget();
+    /**
+     * This does not get called when service is properly disconnected.
+     * So we need to also handle cleanups in destroy().
+     */
+    @Override
+    public void onServiceDisconnected(ComponentName name) {
+      cleanupAudioManager();
     }
+  };
 
-    public void setAutomute(boolean automute) {
-        if (mAutomute == automute) {
-            return;
-        }
-        mAutomute = automute;
-        mHandler.sendEmptyMessage(H.RECHECK_ALL);
-    }
-
-    public void setSilentMode(boolean silentMode) {
-        if (mSilentMode == silentMode) {
-            return;
-        }
-        mSilentMode = silentMode;
-        mHandler.sendEmptyMessage(H.RECHECK_ALL);
-    }
-
-    private VolumeRow addVolumeRow(int stream, int primaryActionIcon, int supplementalIcon,
-        boolean important, boolean defaultStream) {
-        VolumeRow volumeRow = new VolumeRow();
-        volumeRow.stream = stream;
-        volumeRow.primaryActionIcon = primaryActionIcon;
-        volumeRow.supplementalIcon = supplementalIcon;
-        volumeRow.important = important;
-        volumeRow.defaultStream = defaultStream;
-        volumeRow.listItem = null;
-        mRows.add(volumeRow);
-        return volumeRow;
-    }
-
-    private SeekbarListItem addSeekbarListItem(
-        VolumeRow volumeRow, @Nullable View.OnClickListener supplementalIconOnClickListener) {
-        int volumeMax = mAudioManager.getStreamMaxVolume(volumeRow.stream);
-        int currentVolume = mAudioManager.getStreamVolume(volumeRow.stream);
-        SeekbarListItem listItem =
-            new SeekbarListItem(mContext, volumeMax, currentVolume,
-                new VolumeSeekBarChangeListener(volumeRow), null);
-        Drawable primaryIcon = mContext.getResources().getDrawable(volumeRow.primaryActionIcon);
-        listItem.setPrimaryActionIcon(primaryIcon);
-        if (volumeRow.supplementalIcon != 0) {
-            Drawable supplementalIcon = mContext.getResources()
-                .getDrawable(volumeRow.supplementalIcon);
-            listItem.setSupplementalIcon(supplementalIcon, true,
-                supplementalIconOnClickListener);
-        } else {
-            listItem.setSupplementalEmptyIcon(true);
-        }
-
-        mVolumeLineItems.add(listItem);
-        volumeRow.listItem = listItem;
-
-        return listItem;
-    }
-
-    private static int getImpliedLevel(SeekBar seekBar, int progress) {
-        final int m = seekBar.getMax();
-        final int n = m / 100 - 1;
-        final int level = progress == 0 ? 0
-            : progress == m ? (m / 100) : (1 + (int)((progress / (float) m) * n));
-        return level;
-    }
-
-    public void show(int reason) {
-        mHandler.obtainMessage(H.SHOW, reason, 0).sendToTarget();
-    }
-
-    public void dismiss(int reason) {
-        mHandler.obtainMessage(H.DISMISS, reason, 0).sendToTarget();
-    }
-
-    private void showH(int reason) {
-        if (D.BUG) Log.d(TAG, "showH r=" + Events.DISMISS_REASONS[reason]);
-        mHandler.removeMessages(H.SHOW);
-        mHandler.removeMessages(H.DISMISS);
-        rescheduleTimeoutH();
-        if (mShowing) return;
-        mShowing = true;
-
-        mDialog.show();
-        Events.writeEvent(mContext, Events.EVENT_SHOW_DIALOG, reason, mKeyguard.isKeyguardLocked());
-        mController.notifyVisible(true);
-    }
-
-    protected void rescheduleTimeoutH() {
-        mHandler.removeMessages(H.DISMISS);
-        final int timeout = computeTimeoutH();
-        mHandler.sendMessageDelayed(mHandler
-            .obtainMessage(H.DISMISS, Events.DISMISS_REASON_TIMEOUT, 0), timeout);
-        if (D.BUG) Log.d(TAG, "rescheduleTimeout " + timeout + " " + Debug.getCaller());
-        mController.userActivity();
-    }
-
-    private int computeTimeoutH() {
-        if (mHovering) return 16000;
-        if (mSafetyWarning != null) return 5000;
-        return 3000;
-    }
-
-    protected void dismissH(int reason) {
-        mHandler.removeMessages(H.DISMISS);
-        mHandler.removeMessages(H.SHOW);
-        if (!mShowing) return;
-        mListView.animate().cancel();
-        mShowing = false;
-
-        mListView.setTranslationY(0);
-        mListView.setAlpha(1);
-        mListView.animate()
-            .alpha(0)
-            .translationY(-mListView.getHeight())
-            .setDuration(250)
-            .setInterpolator(new SystemUIInterpolators.LogAccelerateInterpolator())
-            .withEndAction(() -> mHandler.postDelayed(() -> {
-                if (D.BUG) Log.d(TAG, "mDialog.dismiss()");
-                mDialog.dismiss();
-            }, 50))
-            .start();
-
-        Events.writeEvent(mContext, Events.EVENT_DISMISS_DIALOG, reason);
-        mController.notifyVisible(false);
-        synchronized (mSafetyWarningLock) {
-            if (mSafetyWarning != null) {
-                if (D.BUG) Log.d(TAG, "SafetyWarning dismissed");
-                mSafetyWarning.dismiss();
-            }
-        }
-    }
-
-    private void trimObsoleteH() {
-        int initialVolumeItemSize = mVolumeLineItems.size();
-        for (int i = mRows.size() - 1; i >= 0; i--) {
-            final VolumeRow row = mRows.get(i);
-            if (row.ss == null || !row.ss.dynamic) continue;
-            if (!mDynamic.get(row.stream)) {
-                mRows.remove(i);
-                mVolumeLineItems.remove(row.listItem);
-            }
-        }
-
-        if (mVolumeLineItems.size() != initialVolumeItemSize) {
-            mPagedListAdapter.notifyDataSetChanged();
-        }
-    }
-
-    private void onStateChangedH(State state) {
-        mState = state;
-        mDynamic.clear();
-        // add any new dynamic rows
-        for (int i = 0; i < state.states.size(); i++) {
-            final int stream = state.states.keyAt(i);
-            final StreamState ss = state.states.valueAt(i);
-            if (!ss.dynamic) {
-                continue;
-            }
-            mDynamic.put(stream, true);
-            if (findRow(stream) == null) {
-                VolumeRow row = addVolumeRow(stream, R.drawable.ic_volume_remote,
-                    0, true,false);
-                if (mExpanded) {
-                    addSeekbarListItem(row, null);
-                }
-            }
-        }
-
-        for (VolumeRow row : mRows) {
-            updateVolumeRowH(row);
-        }
-    }
-
-    private void updateVolumeRowH(VolumeRow row) {
-        if (D.BUG) Log.d(TAG, "updateVolumeRowH s=" + row.stream);
-        if (mState == null) {
-            return;
-        }
-        final StreamState ss = mState.states.get(row.stream);
-        if (ss == null) {
-            return;
-        }
-        row.ss = ss;
-        if (ss.level == row.requestedLevel) {
-            row.requestedLevel = -1;
-        }
-        // TODO: update Seekbar progress and change the mute icon if necessary.
-    }
-
-    private VolumeRow findRow(int stream) {
-        for (VolumeRow row : mRows) {
-            if (row.stream == stream) {
-                return row;
-            }
-        }
-        return null;
-    }
-
-    private VolumeRow findRow(SeekbarListItem targetItem) {
-        for (VolumeRow row : mRows) {
-            if (row.listItem == targetItem) {
-                return row;
-            }
-        }
-        return null;
-    }
-
-    public void dump(PrintWriter writer) {
-        writer.println(VolumeDialogImpl.class.getSimpleName() + " state:");
-        writer.print("  mShowing: "); writer.println(mShowing);
-        writer.print("  mDynamic: "); writer.println(mDynamic);
-        writer.print("  mAutomute: "); writer.println(mAutomute);
-        writer.print("  mSilentMode: "); writer.println(mSilentMode);
-    }
-
-    private void recheckH(VolumeRow row) {
-        if (row == null) {
-            if (D.BUG) Log.d(TAG, "recheckH ALL");
-            trimObsoleteH();
-            for (VolumeRow r : mRows) {
-                updateVolumeRowH(r);
-            }
-        } else {
-            if (D.BUG) Log.d(TAG, "recheckH " + row.stream);
-            updateVolumeRowH(row);
-        }
-    }
-
-    private void setStreamImportantH(int stream, boolean important) {
-        for (VolumeRow row : mRows) {
-            if (row.stream == stream) {
-                row.important = important;
-                return;
-            }
-        }
-    }
-
-    private void showSafetyWarningH(int flags) {
-        if ((flags & (AudioManager.FLAG_SHOW_UI | AudioManager.FLAG_SHOW_UI_WARNINGS)) != 0
-            || mShowing) {
-            synchronized (mSafetyWarningLock) {
-                if (mSafetyWarning != null) {
-                    return;
-                }
-                mSafetyWarning = new SafetyWarningDialog(mContext, mController.getAudioManager()) {
-                    @Override
-                    protected void cleanUp() {
-                        synchronized (mSafetyWarningLock) {
-                            mSafetyWarning = null;
-                        }
-                        recheckH(null);
-                    }
-                };
-                mSafetyWarning.show();
-            }
-            recheckH(null);
-        }
-        rescheduleTimeoutH();
-    }
-
-    private final VolumeDialogController.Callbacks mControllerCallbackH
-            = new VolumeDialogController.Callbacks() {
-        @Override
-        public void onShowRequested(int reason) {
-            showH(reason);
-        }
-
-        @Override
-        public void onDismissRequested(int reason) {
-            dismissH(reason);
-        }
-
-        @Override
-        public void onScreenOff() {
-            dismissH(Events.DISMISS_REASON_SCREEN_OFF);
-        }
-
-        @Override
-        public void onStateChanged(State state) {
-            onStateChangedH(state);
-        }
-
-        @Override
-        public void onLayoutDirectionChanged(int layoutDirection) {
-            mListView.setLayoutDirection(layoutDirection);
-        }
-
-        @Override
-        public void onConfigurationChanged() {
-            mDialog.dismiss();
-            initDialog();
-            mConfigurableTexts.update();
-        }
-
-        @Override
-        public void onShowVibrateHint() {
-            if (mSilentMode) {
-                mController.setRingerMode(AudioManager.RINGER_MODE_SILENT, false);
-            }
-        }
-
-        @Override
-        public void onShowSilentHint() {
-            if (mSilentMode) {
-                mController.setRingerMode(AudioManager.RINGER_MODE_NORMAL, false);
-            }
-        }
-
-        @Override
-        public void onShowSafetyWarning(int flags) {
-            showSafetyWarningH(flags);
-        }
-
-        @Override
-        public void onAccessibilityModeChanged(Boolean showA11yStream) {
-        }
-    };
-
-    private final class H extends Handler {
-        private static final int SHOW = 1;
-        private static final int DISMISS = 2;
-        private static final int RECHECK = 3;
-        private static final int RECHECK_ALL = 4;
-        private static final int SET_STREAM_IMPORTANT = 5;
-        private static final int RESCHEDULE_TIMEOUT = 6;
-        private static final int STATE_CHANGED = 7;
-
-        public H() {
-            super(Looper.getMainLooper());
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case SHOW: showH(msg.arg1); break;
-                case DISMISS: dismissH(msg.arg1); break;
-                case RECHECK: recheckH((VolumeRow) msg.obj); break;
-                case RECHECK_ALL: recheckH(null); break;
-                case SET_STREAM_IMPORTANT: setStreamImportantH(msg.arg1, msg.arg2 != 0); break;
-                case RESCHEDULE_TIMEOUT: rescheduleTimeoutH(); break;
-                case STATE_CHANGED: onStateChangedH(mState); break;
-            }
-        }
-    }
-
-    private final class CustomDialog extends Dialog implements DialogInterface {
-        public CustomDialog(Context context) {
-            super(context, com.android.systemui.R.style.qs_theme);
-        }
-
-        @Override
-        public boolean dispatchTouchEvent(MotionEvent ev) {
-            rescheduleTimeoutH();
-            return super.dispatchTouchEvent(ev);
-        }
-
-        @Override
-        protected void onStart() {
-            super.setCanceledOnTouchOutside(true);
-            super.onStart();
-        }
-
-        @Override
-        protected void onStop() {
-            super.onStop();
-            mHandler.sendEmptyMessage(H.RECHECK_ALL);
-        }
-
-        @Override
-        public boolean onTouchEvent(MotionEvent event) {
-            if (isShowing()) {
-                if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
-                    dismissH(Events.DISMISS_REASON_TOUCH_OUTSIDE);
-                    return true;
-                }
-            }
-            return false;
-        }
-    }
-
-    private final class VolumeSeekBarChangeListener implements OnSeekBarChangeListener {
-        private final VolumeRow mRow;
-
-        private VolumeSeekBarChangeListener(VolumeRow volumeRow) {
-            mRow = volumeRow;
-        }
-
-        @Override
-        public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
-            if (mRow.ss == null) {
-                return;
-            }
-            if (D.BUG) {
-                Log.d(TAG, AudioSystem.streamToString(mRow.stream)
-                    + " onProgressChanged " + progress + " fromUser=" + fromUser);
-            }
-            if (!fromUser) {
-                return;
-            }
-            if (mRow.ss.levelMin > 0) {
-                final int minProgress = mRow.ss.levelMin;
-                if (progress < minProgress) {
-                    seekBar.setProgress(minProgress);
-                    progress = minProgress;
-                }
-            }
-            final int userLevel = getImpliedLevel(seekBar, progress);
-            if (mRow.ss.level != userLevel || mRow.ss.muted && userLevel > 0) {
-                if (mRow.requestedLevel != userLevel) {
-                    mController.setStreamVolume(mRow.stream, userLevel);
-                    mRow.requestedLevel = userLevel;
-                    Events.writeEvent(mContext, Events.EVENT_TOUCH_LEVEL_CHANGED, mRow.stream,
-                        userLevel);
-                }
-            }
-        }
-
-        @Override
-        public void onStartTrackingTouch(SeekBar seekBar) {
-            if (D.BUG) {
-                Log.d(TAG, "onStartTrackingTouch"+ " " + mRow.stream);
-            }
-            mController.setActiveStream(mRow.stream);
-        }
-
-        @Override
-        public void onStopTrackingTouch(SeekBar seekBar) {
-            if (D.BUG) {
-                Log.d(TAG, "onStopTrackingTouch"+ " " + mRow.stream);
-            }
-            final int userLevel = getImpliedLevel(seekBar, seekBar.getProgress());
-            Events.writeEvent(mContext, Events.EVENT_TOUCH_LEVEL_DONE, mRow.stream, userLevel);
-            if (mRow.ss.level != userLevel) {
-                mHandler.sendMessageDelayed(mHandler.obtainMessage(H.RECHECK, mRow),
-                    USER_ATTEMPT_GRACE_PERIOD);
-            }
-        }
-    }
-
-    private final class ExpandIconListener implements View.OnClickListener {
-        @Override
-        public void onClick(final View v) {
-            mExpanded = !mExpanded;
-            Animator inAnimator;
-            if (mExpanded) {
-                for (VolumeRow row : mRows) {
-                    // Adding the items which are not coming from default stream.
-                    if (!row.defaultStream) {
-                        addSeekbarListItem(row, null);
-                    }
-                }
-                inAnimator = AnimatorInflater.loadAnimator(
-                    mContext, R.anim.car_arrow_fade_in_rotate_up);
-            } else {
-                // Only keeping the default stream if it is not expended.
-                Iterator itr = mVolumeLineItems.iterator();
-                while (itr.hasNext()) {
-                    SeekbarListItem item = (SeekbarListItem) itr.next();
-                    VolumeRow row = findRow(item);
-                    if (!row.defaultStream) {
-                        itr.remove();
-                    }
-                }
-                inAnimator = AnimatorInflater.loadAnimator(
-                    mContext, R.anim.car_arrow_fade_in_rotate_down);
-            }
-
-            Animator outAnimator = AnimatorInflater.loadAnimator(
-                mContext, R.anim.car_arrow_fade_out);
-            inAnimator.setStartDelay(100);
-            AnimatorSet animators = new AnimatorSet();
-            animators.playTogether(outAnimator, inAnimator);
-            animators.setTarget(v);
-            animators.start();
-            mPagedListAdapter.notifyDataSetChanged();
-        }
-    }
-
-    private static class VolumeRow {
-        private int stream;
-        private StreamState ss;
-        private boolean important;
-        private boolean defaultStream;
-        private int primaryActionIcon;
-        private int supplementalIcon;
-        private SeekbarListItem listItem;
-        private int requestedLevel = -1;  // pending user-requested level via progress changed
-    }
-}
\ No newline at end of file
+  /**
+   * Wrapper class which contains information of each volume group.
+   */
+  private static class VolumeItem {
+    private @AudioAttributes.AttributeUsage int usage;
+    private int rank;
+    private boolean defaultItem = false;
+    private @DrawableRes int icon;
+    private SeekbarListItem listItem;
+    private int progress;
+  }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
index 6e5b548..dd55264 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogComponent.java
@@ -103,11 +103,7 @@
     }
 
     private VolumeDialog createCarDefault() {
-        CarVolumeDialogImpl impl = new CarVolumeDialogImpl(mContext);
-        impl.setStreamImportant(AudioManager.STREAM_SYSTEM, false);
-        impl.setAutomute(true);
-        impl.setSilentMode(false);
-        return impl;
+        return new CarVolumeDialogImpl(mContext);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index b5071de..b0e40fc 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -71,6 +71,7 @@
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityManager.AccessibilityServicesStateChangeListener;
+import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.animation.DecelerateInterpolator;
 import android.widget.FrameLayout;
 import android.widget.ImageButton;
@@ -627,35 +628,32 @@
             switch (mState.ringerModeInternal) {
                 case AudioManager.RINGER_MODE_VIBRATE:
                     mRingerIcon.setImageResource(R.drawable.ic_volume_ringer_vibrate);
+                    addAccessibilityDescription(mRingerIcon, RINGER_MODE_VIBRATE,
+                            mContext.getString(R.string.volume_ringer_hint_mute));
                     mRingerIcon.setTag(Events.ICON_STATE_VIBRATE);
                     break;
                 case AudioManager.RINGER_MODE_SILENT:
                     mRingerIcon.setImageResource(R.drawable.ic_volume_ringer_mute);
-                    mRingerIcon.setContentDescription(mContext.getString(
-                            R.string.volume_stream_content_description_unmute,
-                            getStreamLabelH(ss)));
                     mRingerIcon.setTag(Events.ICON_STATE_MUTE);
+                    addAccessibilityDescription(mRingerIcon, RINGER_MODE_SILENT,
+                            mContext.getString(R.string.volume_ringer_hint_unmute));
                     break;
                 case AudioManager.RINGER_MODE_NORMAL:
                 default:
                     boolean muted = (mAutomute && ss.level == 0) || ss.muted;
                     if (!isZenMuted && muted) {
                         mRingerIcon.setImageResource(R.drawable.ic_volume_ringer_mute);
-                        mRingerIcon.setContentDescription(mContext.getString(
-                                R.string.volume_stream_content_description_unmute,
-                                getStreamLabelH(ss)));
+                        addAccessibilityDescription(mRingerIcon, RINGER_MODE_NORMAL,
+                                mContext.getString(R.string.volume_ringer_hint_unmute));
                         mRingerIcon.setTag(Events.ICON_STATE_MUTE);
                     } else {
                         mRingerIcon.setImageResource(R.drawable.ic_volume_ringer);
                         if (mController.hasVibrator()) {
-                            mRingerIcon.setContentDescription(mContext.getString(
-                                    mShowA11yStream
-                                            ? R.string.volume_stream_content_description_vibrate_a11y
-                                            : R.string.volume_stream_content_description_vibrate,
-                                    getStreamLabelH(ss)));
-
+                            addAccessibilityDescription(mRingerIcon, RINGER_MODE_NORMAL,
+                                    mContext.getString(R.string.volume_ringer_hint_vibrate));
                         } else {
-                            mRingerIcon.setContentDescription(getStreamLabelH(ss));
+                            addAccessibilityDescription(mRingerIcon, RINGER_MODE_NORMAL,
+                                    mContext.getString(R.string.volume_ringer_hint_mute));
                         }
                         mRingerIcon.setTag(Events.ICON_STATE_UNMUTE);
                     }
@@ -664,6 +662,31 @@
         }
     }
 
+    private void addAccessibilityDescription(View view, int currState, String hintLabel) {
+        int currStateResId;
+        switch (currState) {
+            case RINGER_MODE_SILENT:
+                currStateResId = R.string.volume_ringer_status_silent;
+                break;
+            case RINGER_MODE_VIBRATE:
+                currStateResId = R.string.volume_ringer_status_vibrate;
+                break;
+            case RINGER_MODE_NORMAL:
+            default:
+                currStateResId = R.string.volume_ringer_status_normal;
+        }
+
+        view.setContentDescription(mContext.getString(currStateResId));
+
+        view.setAccessibilityDelegate(new AccessibilityDelegate() {
+            public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
+                super.onInitializeAccessibilityNodeInfo(host, info);
+                info.addAction(new AccessibilityNodeInfo.AccessibilityAction(
+                                AccessibilityNodeInfo.ACTION_CLICK, hintLabel));
+            }
+        });
+    }
+
     /**
      * Toggles enable state of views in a VolumeRow (not including seekbar or icon)
      * Hides/shows zen icon
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java
index 210764a..17a4fbc 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSliceViewTest.java
@@ -90,6 +90,17 @@
     }
 
     @Test
+    public void refresh_replacesSliceContentAndNotifiesListener() {
+        AtomicBoolean notified = new AtomicBoolean();
+        mKeyguardSliceView.setContentChangeListener((hasHeader)-> {
+            notified.set(true);
+        });
+        mKeyguardSliceView.refresh();
+        Assert.assertTrue("Listener should be notified about slice changes.",
+                notified.get());
+    }
+
+    @Test
     public void getTextColor_whiteTextWhenAOD() {
         // Set text color to red since the default is white and test would always pass
         mKeyguardSliceView.setTextColor(Color.RED);
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.java
new file mode 100644
index 0000000..1d8de2f
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardStatusViewTest.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.keyguard;
+
+import static org.mockito.Mockito.verify;
+
+import android.test.suitebuilder.annotation.SmallTest;
+import android.testing.AndroidTestingRunner;
+import android.testing.TestableLooper.RunWithLooper;
+import android.view.LayoutInflater;
+import android.widget.TextClock;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+
+@SmallTest
+@RunWithLooper(setAsMainLooper = true)
+@RunWith(AndroidTestingRunner.class)
+public class KeyguardStatusViewTest extends SysuiTestCase {
+
+    @Mock
+    KeyguardSliceView mKeyguardSlice;
+    @Mock
+    TextClock mClockView;
+    @InjectMocks
+    KeyguardStatusView mKeyguardStatusView;
+
+    @Before
+    public void setUp() {
+        LayoutInflater layoutInflater = LayoutInflater.from(getContext());
+        mKeyguardStatusView =
+                (KeyguardStatusView) layoutInflater.inflate(R.layout.keyguard_status_view, null);
+        org.mockito.MockitoAnnotations.initMocks(this);
+    }
+
+    @Test
+    public void dozeTimeTick_updatesSlice() {
+        mKeyguardStatusView.dozeTimeTick();
+        verify(mKeyguardSlice).refresh();
+    }
+
+    @Test
+    public void dozeTimeTick_updatesClock() {
+        mKeyguardStatusView.dozeTimeTick();
+        verify(mClockView).refresh();
+    }
+
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
index a45e690..46e2bfb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardSliceProviderTest.java
@@ -85,13 +85,6 @@
     }
 
     @Test
-    public void unregisterClockUpdate() {
-        mProvider.unregisterClockUpdate();
-        Assert.assertFalse("Clock updates should have been unregistered.",
-                mProvider.isRegistered());
-    }
-
-    @Test
     public void returnsValidSlice() {
         Slice slice = mProvider.onBindSlice(mProvider.getUri());
         SliceItem text = SliceQuery.find(slice, android.app.slice.SliceItem.FORMAT_TEXT,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java
index 9e8fa22..231cdf5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CollapsedStatusBarFragmentTest.java
@@ -121,7 +121,7 @@
         fragment.initNotificationIconArea(mMockNotificiationAreaController);
         fragment.disable(StatusBarManager.DISABLE_CLOCK, 0, false);
 
-        assertEquals(View.INVISIBLE, mFragment.getView().findViewById(R.id.clock).getVisibility());
+        assertEquals(View.GONE, mFragment.getView().findViewById(R.id.clock).getVisibility());
 
         fragment.disable(0, 0, false);
 
diff --git a/proto/src/metrics_constants.proto b/proto/src/metrics_constants.proto
index d61f228..1541231 100644
--- a/proto/src/metrics_constants.proto
+++ b/proto/src/metrics_constants.proto
@@ -3967,6 +3967,7 @@
     // Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
     // NOTE: starting on OS P, it also added the following field:
     // Tag FIELD_FLAGS - Flags used to start the session
+    // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode.
     AUTOFILL_SESSION_STARTED = 906;
 
     // An autofill request was processed by a service
@@ -3980,6 +3981,7 @@
     // Type TYPE_CLOSE: Service returned a null response.
     // Tag FIELD_AUTOFILL_NUM_FIELD_CLASSIFICATION_IDS: if service requested field classification,
     // number of entries field ids in the request.
+    // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode.
     AUTOFILL_REQUEST = 907;
 
     // Tag of a field for a package of an autofill service
@@ -3998,6 +4000,7 @@
     // Tag FIELD_AUTOFILL_NUM_DATASETS: The number of datasets shown
     // NOTE: starting on OS P, it also added the following field:
     // Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
+    // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode.
     AUTOFILL_FILL_UI = 910;
 
     // Tag of a field for the length of the filter text
@@ -4005,12 +4008,17 @@
 
     // An autofill authentication succeeded
     // Package: Package of app that was autofilled
+    // Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
+    // NOTE: starting on OS P, it also added the following field:
+    // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode.
     AUTOFILL_AUTHENTICATED = 912;
 
     // An activity was autofilled and all values could be applied
     // Package: Package of app that is autofilled
     // Tag FIELD_AUTOFILL_NUM_VALUES: Number of values that were suggested to be autofilled
     // Tag FIELD_AUTOFILL_NUM_VIEWS_FILLED: Number of views that could be filled
+    // NOTE: starting on OS P, it also added the following field:
+    // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode.
     AUTOFILL_DATASET_APPLIED = 913;
 
     // Tag of a field for the number values to be filled in
@@ -4027,6 +4035,7 @@
     // Tag FIELD_AUTOFILL_NUM_IDS: The number of ids that are saved
     // NOTE: starting on OS P, it also added the following field:
     // Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
+    // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode.
     AUTOFILL_SAVE_UI = 916;
 
     // Tag of a field for the number of saveable ids
@@ -4038,10 +4047,14 @@
     // Type TYPE_FAILURE: The request failed
     // Package: Package of app that was autofilled
     // Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
+    // NOTE: starting on OS P, it also added the following field:
+    // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode.
     AUTOFILL_DATA_SAVE_REQUEST = 918;
 
     // An auto-fill session was finished
     // Package: Package of app that was autofilled
+    // NOTE: starting on OS P, it also added the following field:
+    // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode.
     AUTOFILL_SESSION_FINISHED = 919;
 
     // meta-event: a reader has checkpointed the log here.
@@ -4167,6 +4180,8 @@
     // Package: Real package of the app being autofilled
     // Tag FIELD_AUTOFILL_SERVICE: Package of the autofill service that processed the request
     // TAG FIELD_AUTOFILL_FORGED_COMPONENT_NAME: Component name being forged
+    // NOTE: starting on OS P, it also added the following field:
+    // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode.
     AUTOFILL_FORGED_COMPONENT_ATTEMPT = 948;
 
     // FIELD - The component that an app tried tro forged.
@@ -4624,6 +4639,8 @@
     // OS: O MR
     // Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
     // Tag FIELD_AUTOFILL_PREVIOUS_LENGTH: the previous length of the value
+    // NOTE: starting on OS P, it also added the following field:
+    // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode.
     AUTOFILL_VALUE_RESET = 1124;
 
     // Tag of AUTOFILL_VALUE_RESET
@@ -4634,18 +4651,24 @@
     // Package: Package of app that was autofilled
     // OS: O MR
     // Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
+    // NOTE: starting on OS P, it also added the following field:
+    // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode.
     AUTOFILL_DATASET_AUTHENTICATED = 1126;
 
     // An autofill service provided an invalid dataset authentication
     // Package: Package of app that was autofilled
     // OS: O MR
     // Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
+    // NOTE: starting on OS P, it also added the following field:
+    // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode.
     AUTOFILL_INVALID_DATASET_AUTHENTICATION = 1127;
 
     // An autofill service provided an invalid authentication extra
     // Package: Package of app that was autofilled
     // OS: O MR
     // Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
+    // NOTE: starting on OS P, it also added the following field:
+    // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode.
     AUTOFILL_INVALID_AUTHENTICATION = 1128;
 
     // An autofill service used a custom description (using RemoteViews) in the autofill save UI
@@ -4653,6 +4676,8 @@
     // OS: O MR
     // Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
     // Tag FIELD_AUTOFILL_SAVE_TYPE: Type of save object passed by the service
+    // NOTE: starting on OS P, it also added the following field:
+    // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode.
     AUTOFILL_SAVE_CUSTOM_DESCRIPTION = 1129;
 
     // FIELD - Type of save object passed by the service when the Save UI is shown
@@ -4664,6 +4689,8 @@
     // OS: O MR
     // Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
     // Tag FIELD_AUTOFILL_SAVE_TYPE: Type of save object passed by the service
+    // NOTE: starting on OS P, it also added the following field:
+    // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode.
     AUTOFILL_SAVE_CUSTOM_SUBTITLE = 1131;
 
     // User tapped a link in the custom description of the autofill save UI provided by an autofill service
@@ -4674,6 +4701,8 @@
     // Type TYPE_FAILURE: The link could not launc an activity
     // Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
     // Tag FIELD_AUTOFILL_SAVE_TYPE: Type of save object passed by the service
+    // NOTE: starting on OS P, it also added the following field:
+    // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode.
     AUTOFILL_SAVE_LINK_TAPPED = 1132;
 
     // Result of the validation on save when an autofill service provided a validator
@@ -4684,6 +4713,8 @@
     // Type TYPE_DISMISS: The validation failed
     // Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
     // Tag FIELD_AUTOFILL_SAVE_TYPE: Type of save object passed by the service
+    // NOTE: starting on OS P, it also added the following field:
+    // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode.
     AUTOFILL_SAVE_VALIDATION = 1133;
 
     // Result of an operation in the autofill save UI after the user tapped a link in the custom description
@@ -4693,6 +4724,8 @@
     // Type TYPE_OPEN: The autofill save UI was restored
     // Type TYPE_DISMISS: The autofill save UI was destroyed
     // Type TYPE_FAILURE: An invalid opperation was reported by the app's AutofillManager
+    // NOTE: starting on OS P, it also added the following field:
+    // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode.
     AUTOFILL_PENDING_SAVE_UI_OPERATION = 1134;
 
     // Autofill service called API that disables itself
@@ -4705,6 +4738,8 @@
     // Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
     // Package: Package of the autofill service
     // OS: O MR
+    // NOTE: starting on OS P, it also added the following field:
+    // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode.
     AUTOFILL_UI_LATENCY = 1136;
 
     // Action: the snooze leave-behind was shown after the user clicked the snooze icon
@@ -4872,12 +4907,14 @@
     // Package: Package of app that is autofilled
     // OS: P
     // Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
+    // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode.
     AUTOFILL_EXPLICIT_SAVE_TRIGGER_DEFINITION = 1228;
 
     // The autofill context was commited when the user clicked a view explicitly marked by the
     // service as committing it
     // Package: Package of app that is autofilled
     // OS: P
+    // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode.
     AUTOFILL_SAVE_EXPLICITLY_TRIGGERED = 1229;
 
     // OPEN: Settings > Network & Internet > Mobile network > Wi-Fi calling
@@ -4890,6 +4927,7 @@
     // OS: P
     // Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
     // Tag FIELD_AUTOFILL_DURATION: duration (in ms) that autofill will be disabled
+    // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode.
     AUTOFILL_SERVICE_DISABLED_APP = 1231;
 
     // An autofill service asked to disable autofill for a given activity.
@@ -4898,6 +4936,7 @@
     // Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
     // Tag FIELD_CLASS_NAME: Class name of the activity that is being disabled for autofill
     // Tag FIELD_AUTOFILL_DURATION: duration (in ms) that autofill will be disabled
+    // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode.
     AUTOFILL_SERVICE_DISABLED_ACTIVITY = 1232;
 
     // ACTION: Stop an app and turn on background check
@@ -5109,6 +5148,7 @@
     // OS: P
     // Tag FIELD_AUTOFILL_SERVICE: Package of service that processed the request
     // Tag FIELD_AUTOFILL_MATCH_SCORE: Average score of the matches, in the range of 0 to 100
+    // Type FIELD_AUTOFILL_COMPAT_MODE: package is being autofilled on compatibility mode.
     AUTOFILL_FIELD_CLASSIFICATION_MATCHES = 1273;
 
     // Tag used to report autofill field classification scores
@@ -5775,6 +5815,9 @@
     // OS: P
     ACTION_STORAGE_MIGRATE_LATER = 1413;
 
+    // Tag used to report whether an activity is being autofilled  on compatibility mode.
+    FIELD_AUTOFILL_COMPAT_MODE = 1414;
+
     // ---- End P Constants, all P constants go above this line ----
     // Add new aosp constants above this line.
     // END OF AOSP CONSTANTS
diff --git a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
index e582daa..6ff9539 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceImpl.java
@@ -504,7 +504,7 @@
             sessionId = sRandom.nextInt();
         } while (sessionId == NO_SESSION || mSessions.indexOfKey(sessionId) >= 0);
 
-        assertCallerLocked(componentName);
+        assertCallerLocked(componentName, compatMode);
 
         final Session newSession = new Session(this, mUi, mContext, mHandler, mUserId, mLock,
                 sessionId, uid, activityToken, appCallbackToken, hasCallback, mUiLatencyHistory,
@@ -518,7 +518,7 @@
     /**
      * Asserts the component is owned by the caller.
      */
-    private void assertCallerLocked(@NonNull ComponentName componentName) {
+    private void assertCallerLocked(@NonNull ComponentName componentName, boolean compatMode) {
         final String packageName = componentName.getPackageName();
         final PackageManager pm = mContext.getPackageManager();
         final int callingUid = Binder.getCallingUid();
@@ -536,7 +536,7 @@
                     + ") passed component (" + componentName + ") owned by UID " + packageUid);
             mMetricsLogger.write(
                     Helper.newLogMaker(MetricsEvent.AUTOFILL_FORGED_COMPONENT_ATTEMPT,
-                            callingPackage, getServicePackageName())
+                            callingPackage, getServicePackageName(), compatMode)
                     .addTaggedData(MetricsEvent.FIELD_AUTOFILL_FORGED_COMPONENT_NAME,
                             componentName == null ? "null" : componentName.flattenToShortString()));
 
@@ -774,10 +774,10 @@
             @Nullable ArrayList<String> changedDatasetIds,
             @Nullable ArrayList<AutofillId> manuallyFilledFieldIds,
             @Nullable ArrayList<ArrayList<String>> manuallyFilledDatasetIds,
-            @NonNull String appPackageName) {
+            @NonNull String appPackageName, boolean compatMode) {
         logContextCommittedLocked(sessionId, clientState, selectedDatasets, ignoredDatasets,
                 changedFieldIds, changedDatasetIds, manuallyFilledFieldIds,
-                manuallyFilledDatasetIds, null, null, appPackageName);
+                manuallyFilledDatasetIds, null, null, appPackageName, compatMode);
     }
 
     @GuardedBy("mLock")
@@ -790,7 +790,7 @@
             @Nullable ArrayList<ArrayList<String>> manuallyFilledDatasetIds,
             @Nullable ArrayList<AutofillId> detectedFieldIdsList,
             @Nullable ArrayList<FieldClassification> detectedFieldClassificationsList,
-            @NonNull String appPackageName) {
+            @NonNull String appPackageName, boolean compatMode) {
         if (isValidEventLocked("logDatasetNotSelected()", sessionId)) {
             if (sVerbose) {
                 Slog.v(TAG, "logContextCommitted() with FieldClassification: id=" + sessionId
@@ -800,7 +800,8 @@
                         + ", changedDatasetIds=" + changedDatasetIds
                         + ", manuallyFilledFieldIds=" + manuallyFilledFieldIds
                         + ", detectedFieldIds=" + detectedFieldIdsList
-                        + ", detectedFieldClassifications=" + detectedFieldClassificationsList);
+                        + ", detectedFieldClassifications=" + detectedFieldClassificationsList
+                        + ", compatMode=" + compatMode);
             }
             AutofillId[] detectedFieldsIds = null;
             FieldClassification[] detectedFieldClassifications = null;
@@ -827,7 +828,7 @@
                 final int averageScore = (int) ((totalScore * 100) / totalSize);
                 mMetricsLogger.write(Helper
                         .newLogMaker(MetricsEvent.AUTOFILL_FIELD_CLASSIFICATION_MATCHES,
-                                appPackageName, getServicePackageName())
+                                appPackageName, getServicePackageName(), compatMode)
                         .setCounterValue(numberFields)
                         .addTaggedData(MetricsEvent.FIELD_AUTOFILL_MATCH_SCORE,
                                 averageScore));
@@ -1122,7 +1123,7 @@
     /**
      * Called by {@link Session} when service asked to disable autofill for an app.
      */
-    void disableAutofillForApp(@NonNull String packageName, long duration) {
+    void disableAutofillForApp(@NonNull String packageName, long duration, boolean compatMode) {
         synchronized (mLock) {
             if (mDisabledApps == null) {
                 mDisabledApps = new ArrayMap<>(1);
@@ -1135,7 +1136,7 @@
             mDisabledApps.put(packageName, expiration);
             int intDuration = duration > Integer.MAX_VALUE ? Integer.MAX_VALUE : (int) duration;
             mMetricsLogger.write(Helper.newLogMaker(MetricsEvent.AUTOFILL_SERVICE_DISABLED_APP,
-                    packageName, getServicePackageName())
+                    packageName, getServicePackageName(), compatMode)
                     .addTaggedData(MetricsEvent.FIELD_AUTOFILL_DURATION, intDuration));
         }
     }
@@ -1143,7 +1144,8 @@
     /**
      * Called by {@link Session} when service asked to disable autofill an app.
      */
-    void disableAutofillForActivity(@NonNull ComponentName componentName, long duration) {
+    void disableAutofillForActivity(@NonNull ComponentName componentName, long duration,
+            boolean compatMode) {
         synchronized (mLock) {
             if (mDisabledActivities == null) {
                 mDisabledActivities = new ArrayMap<>(1);
@@ -1160,7 +1162,8 @@
             mMetricsLogger.write(new LogMaker(MetricsEvent.AUTOFILL_SERVICE_DISABLED_ACTIVITY)
                     .setComponentName(componentName)
                     .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE, getServicePackageName())
-                    .addTaggedData(MetricsEvent.FIELD_AUTOFILL_DURATION, intDuration));
+                    .addTaggedData(MetricsEvent.FIELD_AUTOFILL_DURATION, intDuration)
+                    .addTaggedData(MetricsEvent.FIELD_AUTOFILL_COMPAT_MODE, compatMode ? 1 : 0));
         }
     }
 
diff --git a/services/autofill/java/com/android/server/autofill/Helper.java b/services/autofill/java/com/android/server/autofill/Helper.java
index 78526f5..5372d0c 100644
--- a/services/autofill/java/com/android/server/autofill/Helper.java
+++ b/services/autofill/java/com/android/server/autofill/Helper.java
@@ -21,7 +21,6 @@
 import android.app.assist.AssistStructure;
 import android.app.assist.AssistStructure.ViewNode;
 import android.metrics.LogMaker;
-import android.os.Bundle;
 import android.service.autofill.Dataset;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -35,10 +34,7 @@
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.LinkedList;
-import java.util.Objects;
-import java.util.Set;
 
 public final class Helper {
 
@@ -119,6 +115,13 @@
         return log;
     }
 
+    @NonNull
+    public static LogMaker newLogMaker(int category, String packageName,
+            String servicePackageName, boolean compatMode) {
+        return newLogMaker(category, packageName, servicePackageName)
+                .addTaggedData(MetricsEvent.FIELD_AUTOFILL_COMPAT_MODE, compatMode ? 1 : 0);
+    }
+
     public static void printlnRedactedText(@NonNull PrintWriter pw, @Nullable CharSequence text) {
         if (text == null) {
             pw.println("null");
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index c055060..88a679b 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -644,9 +644,10 @@
                 Slog.d(TAG, message.toString());
             }
             if ((flags & FillResponse.FLAG_DISABLE_ACTIVITY_ONLY) != 0) {
-                mService.disableAutofillForActivity(mComponentName, disableDuration);
+                mService.disableAutofillForActivity(mComponentName, disableDuration, mCompatMode);
             } else {
-                mService.disableAutofillForApp(mComponentName.getPackageName(), disableDuration);
+                mService.disableAutofillForApp(mComponentName.getPackageName(), disableDuration,
+                        mCompatMode);
             }
             sessionFinishedState = AutofillManager.STATE_DISABLED_BY_SERVICE;
         }
@@ -1251,7 +1252,7 @@
             mService.logContextCommittedLocked(id, mClientState, mSelectedDatasetIds,
                     ignoredDatasets, changedFieldIds, changedDatasetIds,
                     manuallyFilledFieldIds, manuallyFilledDatasetIds,
-                    mComponentName.getPackageName());
+                    mComponentName.getPackageName(), mCompatMode);
         }
     }
 
@@ -1306,7 +1307,7 @@
                 mService.logContextCommittedLocked(id, mClientState, mSelectedDatasetIds,
                         ignoredDatasets, changedFieldIds, changedDatasetIds,
                         manuallyFilledFieldIds, manuallyFilledDatasetIds,
-                        mComponentName.getPackageName());
+                        mComponentName.getPackageName(), mCompatMode);
                 return;
             }
             final Scores scores = result.getParcelable(EXTRA_SCORES);
@@ -1371,7 +1372,7 @@
             mService.logContextCommittedLocked(id, mClientState, mSelectedDatasetIds,
                     ignoredDatasets, changedFieldIds, changedDatasetIds, manuallyFilledFieldIds,
                     manuallyFilledDatasetIds, detectedFieldIds, detectedFieldClassifications,
-                    mComponentName.getPackageName());
+                    mComponentName.getPackageName(), mCompatMode);
         });
 
         fcStrategy.getScores(callback, algorithm, algorithmArgs, currentValues, userValues);
@@ -1602,7 +1603,7 @@
                 getUiForShowing().showSaveUi(mService.getServiceLabel(), mService.getServiceIcon(),
                         mService.getServicePackageName(), saveInfo, this,
                         mComponentName.getPackageName(), this,
-                        mPendingSaveUi);
+                        mPendingSaveUi, mCompatMode);
                 if (client != null) {
                     try {
                         client.setSaveUiState(id, true);
@@ -2080,7 +2081,7 @@
 
         getUiForShowing().showFillUi(filledId, response, filterText,
                 mService.getServicePackageName(), mComponentName.getPackageName(),
-                mService.getServiceLabel(), mService.getServiceIcon(), this);
+                mService.getServiceLabel(), mService.getServiceIcon(), this, mCompatMode);
 
         synchronized (mLock) {
             if (mUiShownTime == 0) {
@@ -2717,7 +2718,8 @@
     }
 
     private LogMaker newLogMaker(int category, String servicePackageName) {
-        return Helper.newLogMaker(category, mComponentName.getPackageName(), servicePackageName);
+        return Helper.newLogMaker(category, mComponentName.getPackageName(), servicePackageName,
+                mCompatMode);
     }
 
     private void writeLog(int category) {
diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
index ee18dc2..811d87be 100644
--- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
+++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
@@ -170,13 +170,14 @@
     public void showFillUi(@NonNull AutofillId focusedId, @NonNull FillResponse response,
             @Nullable String filterText, @Nullable String servicePackageName,
             @NonNull String packageName, @NonNull CharSequence serviceLabel,
-            @NonNull Drawable serviceIcon, @NonNull AutoFillUiCallback callback) {
+            @NonNull Drawable serviceIcon, @NonNull AutoFillUiCallback callback, boolean compatMode) {
         if (sDebug) {
             final int size = filterText == null ? 0 : filterText.length();
             Slog.d(TAG, "showFillUi(): id=" + focusedId + ", filter=" + size + " chars");
         }
-        final LogMaker log =
-                Helper.newLogMaker(MetricsEvent.AUTOFILL_FILL_UI, packageName, servicePackageName)
+        final LogMaker log = Helper
+                .newLogMaker(MetricsEvent.AUTOFILL_FILL_UI, packageName, servicePackageName,
+                        compatMode)
                 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_FILTERTEXT_LEN,
                         filterText == null ? 0 : filterText.length())
                 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS,
@@ -262,14 +263,16 @@
     public void showSaveUi(@NonNull CharSequence serviceLabel, @NonNull Drawable serviceIcon,
             @Nullable String servicePackageName, @NonNull SaveInfo info,
             @NonNull ValueFinder valueFinder, @NonNull String packageName,
-            @NonNull AutoFillUiCallback callback, @NonNull PendingUi pendingSaveUi) {
+            @NonNull AutoFillUiCallback callback, @NonNull PendingUi pendingSaveUi,
+            boolean compatMode) {
         if (sVerbose) Slog.v(TAG, "showSaveUi() for " + packageName + ": " + info);
         int numIds = 0;
         numIds += info.getRequiredIds() == null ? 0 : info.getRequiredIds().length;
         numIds += info.getOptionalIds() == null ? 0 : info.getOptionalIds().length;
 
-        final LogMaker log =
-                Helper.newLogMaker(MetricsEvent.AUTOFILL_SAVE_UI, packageName, servicePackageName)
+        final LogMaker log = Helper
+                .newLogMaker(MetricsEvent.AUTOFILL_SAVE_UI, packageName, servicePackageName,
+                        compatMode)
                 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_IDS, numIds);
 
         mHandler.post(() -> {
@@ -319,7 +322,7 @@
                     }
                     mMetricsLogger.write(log);
                 }
-            });
+            }, compatMode);
         });
     }
 
diff --git a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
index 80903c1..fa2a0d9 100644
--- a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
@@ -60,6 +60,7 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
 import com.android.server.UiThread;
+import com.android.server.autofill.Helper;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -134,6 +135,7 @@
     private final PendingUi mPendingUi;
     private final String mServicePackageName;
     private final String mPackageName;
+    private final boolean mCompatMode;
 
     private boolean mDestroyed;
 
@@ -141,12 +143,14 @@
            @NonNull CharSequence serviceLabel, @NonNull Drawable serviceIcon,
            @Nullable String servicePackageName, @NonNull String packageName,
            @NonNull SaveInfo info, @NonNull ValueFinder valueFinder,
-           @NonNull OverlayControl overlayControl, @NonNull OnSaveListener listener) {
+           @NonNull OverlayControl overlayControl, @NonNull OnSaveListener listener,
+           boolean compatMode) {
         mPendingUi= pendingUi;
         mListener = new OneTimeListener(listener);
         mOverlayControl = overlayControl;
         mServicePackageName = servicePackageName;
         mPackageName = packageName;
+        mCompatMode = compatMode;
 
         context = new ContextThemeWrapper(context, THEME_ID);
         final LayoutInflater inflater = LayoutInflater.from(context);
@@ -409,14 +413,12 @@
     }
 
     private LogMaker newLogMaker(int category, int saveType) {
-        return newLogMaker(category)
+        return Helper.newLogMaker(category, mPackageName, mServicePackageName, mCompatMode)
                 .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SAVE_TYPE, saveType);
     }
 
     private LogMaker newLogMaker(int category) {
-        return new LogMaker(category)
-                .setPackageName(mPackageName)
-                .addTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE, mServicePackageName);
+        return Helper.newLogMaker(category, mPackageName, mServicePackageName, mCompatMode);
     }
 
     private void writeLog(int category, int saveType) {
@@ -505,6 +507,7 @@
         pw.print(prefix); pw.print("pendingUi: "); pw.println(mPendingUi);
         pw.print(prefix); pw.print("service: "); pw.println(mServicePackageName);
         pw.print(prefix); pw.print("app: "); pw.println(mPackageName);
+        pw.print(prefix); pw.print("compat mode: "); pw.println(mCompatMode);
 
         final View view = mDialog.getWindow().getDecorView();
         final int[] loc = view.getLocationOnScreen();
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index b2cf1b7..36e5340 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -525,6 +525,8 @@
     private static final int SCO_STATE_ACTIVE_INTERNAL = 3;
     // SCO audio deactivation request waiting for headset service to connect
     private static final int SCO_STATE_DEACTIVATE_REQ = 5;
+    // SCO audio deactivation in progress, waiting for Bluetooth audio intent
+    private static final int SCO_STATE_DEACTIVATING = 6;
 
     // SCO audio state is active due to an action in BT handsfree (either voice recognition or
     // in call audio)
@@ -2705,9 +2707,13 @@
         }
 
         public void binderDied() {
+            int oldModeOwnerPid = 0;
             int newModeOwnerPid = 0;
             synchronized(mSetModeDeathHandlers) {
                 Log.w(TAG, "setMode() client died");
+                if (!mSetModeDeathHandlers.isEmpty()) {
+                    oldModeOwnerPid = mSetModeDeathHandlers.get(0).getPid();
+                }
                 int index = mSetModeDeathHandlers.indexOf(this);
                 if (index < 0) {
                     Log.w(TAG, "unregistered setMode() client died");
@@ -2716,8 +2722,8 @@
                 }
             }
             // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all
-            // SCO connections not started by the application changing the mode
-            if (newModeOwnerPid != 0) {
+            // SCO connections not started by the application changing the mode when pid changes
+            if ((newModeOwnerPid != oldModeOwnerPid) && (newModeOwnerPid != 0)) {
                 final long ident = Binder.clearCallingIdentity();
                 disconnectBluetoothSco(newModeOwnerPid);
                 Binder.restoreCallingIdentity(ident);
@@ -2761,17 +2767,21 @@
             return;
         }
 
+        int oldModeOwnerPid = 0;
         int newModeOwnerPid = 0;
         synchronized(mSetModeDeathHandlers) {
+            if (!mSetModeDeathHandlers.isEmpty()) {
+                oldModeOwnerPid = mSetModeDeathHandlers.get(0).getPid();
+            }
             if (mode == AudioSystem.MODE_CURRENT) {
                 mode = mMode;
             }
             newModeOwnerPid = setModeInt(mode, cb, Binder.getCallingPid(), callingPackage);
         }
         // when entering RINGTONE, IN_CALL or IN_COMMUNICATION mode, clear all
-        // SCO connections not started by the application changing the mode
-        if (newModeOwnerPid != 0) {
-             disconnectBluetoothSco(newModeOwnerPid);
+        // SCO connections not started by the application changing the mode when pid changes
+        if ((newModeOwnerPid != oldModeOwnerPid) && (newModeOwnerPid != 0)) {
+            disconnectBluetoothSco(newModeOwnerPid);
         }
     }
 
@@ -3187,28 +3197,17 @@
     }
 
     public void setBluetoothScoOnInt(boolean on, String eventSource) {
-        if (DEBUG_DEVICES) {
-            Log.d(TAG, "setBluetoothScoOnInt: " + on + " " + eventSource);
-        }
+        Log.i(TAG, "setBluetoothScoOnInt: " + on + " " + eventSource);
         if (on) {
             // do not accept SCO ON if SCO audio is not connected
             synchronized (mScoClients) {
-                if (mBluetoothHeadset != null) {
-                    if (mBluetoothHeadsetDevice == null) {
-                        BluetoothDevice activeDevice = mBluetoothHeadset.getActiveDevice();
-                        if (activeDevice != null) {
-                            // setBtScoActiveDevice() might trigger resetBluetoothSco() which
-                            // will call setBluetoothScoOnInt(false, "resetBluetoothSco")
-                            setBtScoActiveDevice(activeDevice);
-                        }
-                    }
-                    if (mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice)
-                            != BluetoothHeadset.STATE_AUDIO_CONNECTED) {
-                        mForcedUseForCommExt = AudioSystem.FORCE_BT_SCO;
-                        Log.w(TAG, "setBluetoothScoOnInt(true) failed because "
-                                + mBluetoothHeadsetDevice + " is not in audio connected mode");
-                        return;
-                    }
+                if ((mBluetoothHeadset != null)
+                        && (mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice)
+                            != BluetoothHeadset.STATE_AUDIO_CONNECTED)) {
+                    mForcedUseForCommExt = AudioSystem.FORCE_BT_SCO;
+                    Log.w(TAG, "setBluetoothScoOnInt(true) failed because "
+                            + mBluetoothHeadsetDevice + " is not in audio connected mode");
+                    return;
                 }
             }
             mForcedUseForComm = AudioSystem.FORCE_BT_SCO;
@@ -3388,9 +3387,8 @@
         public int totalCount() {
             synchronized(mScoClients) {
                 int count = 0;
-                int size = mScoClients.size();
-                for (int i = 0; i < size; i++) {
-                    count += mScoClients.get(i).getCount();
+                for (ScoClient mScoClient : mScoClients) {
+                    count += mScoClient.getCount();
                 }
                 return count;
             }
@@ -3398,128 +3396,161 @@
 
         private void requestScoState(int state, int scoAudioMode) {
             checkScoAudioState();
-            if (totalCount() == 0) {
-                if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) {
-                    // Make sure that the state transitions to CONNECTING even if we cannot initiate
-                    // the connection.
-                    broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTING);
-                    // Accept SCO audio activation only in NORMAL audio mode or if the mode is
-                    // currently controlled by the same client process.
-                    synchronized(mSetModeDeathHandlers) {
-                        if ((mSetModeDeathHandlers.isEmpty() ||
-                                mSetModeDeathHandlers.get(0).getPid() == mCreatorPid) &&
-                                (mScoAudioState == SCO_STATE_INACTIVE ||
-                                 mScoAudioState == SCO_STATE_DEACTIVATE_REQ)) {
-                            if (mScoAudioState == SCO_STATE_INACTIVE) {
-                                mScoAudioMode = scoAudioMode;
-                                if (scoAudioMode == SCO_MODE_UNDEFINED) {
-                                    if (mBluetoothHeadsetDevice != null) {
-                                        mScoAudioMode = new Integer(Settings.Global.getInt(
-                                                                mContentResolver,
-                                                                "bluetooth_sco_channel_"+
-                                                                mBluetoothHeadsetDevice.getAddress(),
-                                                                SCO_MODE_VIRTUAL_CALL));
-                                        if (mScoAudioMode > SCO_MODE_MAX || mScoAudioMode < 0) {
-                                            mScoAudioMode = SCO_MODE_VIRTUAL_CALL;
-                                        }
-                                    } else {
-                                        mScoAudioMode = SCO_MODE_RAW;
-                                    }
-                                }
-                                if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null) {
-                                    boolean status = false;
-                                    if (mScoAudioMode == SCO_MODE_RAW) {
-                                        status = mBluetoothHeadset.connectAudio();
-                                    } else if (mScoAudioMode == SCO_MODE_VIRTUAL_CALL) {
-                                        status = mBluetoothHeadset.startScoUsingVirtualVoiceCall(
-                                                                            mBluetoothHeadsetDevice);
-                                    } else if (mScoAudioMode == SCO_MODE_VR) {
-                                        status = mBluetoothHeadset.startVoiceRecognition(
-                                                                           mBluetoothHeadsetDevice);
-                                    }
-
-                                    if (status) {
-                                        mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
-                                    } else {
-                                        broadcastScoConnectionState(
-                                                AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
-                                    }
-                                } else if (getBluetoothHeadset()) {
-                                    mScoAudioState = SCO_STATE_ACTIVATE_REQ;
-                                }
-                            } else {
-                                mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
-                                broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTED);
-                            }
-                        } else {
-                            broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
-                        }
+            int clientCount = totalCount();
+            if (clientCount != 0) {
+                Log.i(TAG, "requestScoState: state=" + state + ", scoAudioMode=" + scoAudioMode
+                        + ", clientCount=" + clientCount);
+                return;
+            }
+            if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) {
+                // Make sure that the state transitions to CONNECTING even if we cannot initiate
+                // the connection.
+                broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTING);
+                // Accept SCO audio activation only in NORMAL audio mode or if the mode is
+                // currently controlled by the same client process.
+                synchronized(mSetModeDeathHandlers) {
+                    int modeOwnerPid =  mSetModeDeathHandlers.isEmpty()
+                            ? 0 : mSetModeDeathHandlers.get(0).getPid();
+                    if (modeOwnerPid != 0 && (modeOwnerPid != mCreatorPid)) {
+                        Log.w(TAG, "requestScoState: audio mode is not NORMAL and modeOwnerPid "
+                                + modeOwnerPid + " != creatorPid " + mCreatorPid);
+                        broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+                        return;
                     }
-                } else if (state == BluetoothHeadset.STATE_AUDIO_DISCONNECTED &&
-                              (mScoAudioState == SCO_STATE_ACTIVE_INTERNAL ||
-                               mScoAudioState == SCO_STATE_ACTIVATE_REQ)) {
-                    if (mScoAudioState == SCO_STATE_ACTIVE_INTERNAL) {
-                        if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null) {
-                            boolean status = false;
-                            if (mScoAudioMode == SCO_MODE_RAW) {
-                                status = mBluetoothHeadset.disconnectAudio();
-                            } else if (mScoAudioMode == SCO_MODE_VIRTUAL_CALL) {
-                                status = mBluetoothHeadset.stopScoUsingVirtualVoiceCall(
-                                                                        mBluetoothHeadsetDevice);
-                            } else if (mScoAudioMode == SCO_MODE_VR) {
-                                        status = mBluetoothHeadset.stopVoiceRecognition(
-                                                                      mBluetoothHeadsetDevice);
+                    switch (mScoAudioState) {
+                        case SCO_STATE_INACTIVE:
+                            mScoAudioMode = scoAudioMode;
+                            if (scoAudioMode == SCO_MODE_UNDEFINED) {
+                                mScoAudioMode = SCO_MODE_VIRTUAL_CALL;
+                                if (mBluetoothHeadsetDevice != null) {
+                                    mScoAudioMode = Settings.Global.getInt(mContentResolver,
+                                            "bluetooth_sco_channel_"
+                                                    + mBluetoothHeadsetDevice.getAddress(),
+                                            SCO_MODE_VIRTUAL_CALL);
+                                    if (mScoAudioMode > SCO_MODE_MAX || mScoAudioMode < 0) {
+                                        mScoAudioMode = SCO_MODE_VIRTUAL_CALL;
+                                    }
+                                }
                             }
+                            if (mBluetoothHeadset == null) {
+                                if (getBluetoothHeadset()) {
+                                    mScoAudioState = SCO_STATE_ACTIVATE_REQ;
+                                } else {
+                                    Log.w(TAG, "requestScoState: getBluetoothHeadset failed during"
+                                            + " connection, mScoAudioMode=" + mScoAudioMode);
+                                    broadcastScoConnectionState(
+                                            AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+                                }
+                                break;
+                            }
+                            if (mBluetoothHeadsetDevice == null) {
+                                Log.w(TAG, "requestScoState: no active device while connecting,"
+                                        + " mScoAudioMode=" + mScoAudioMode);
+                                broadcastScoConnectionState(
+                                        AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+                                break;
+                            }
+                            if (connectBluetoothScoAudioHelper(mBluetoothHeadset,
+                                    mBluetoothHeadsetDevice, mScoAudioMode)) {
+                                mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
+                            } else {
+                                Log.w(TAG, "requestScoState: connect to " + mBluetoothHeadsetDevice
+                                        + " failed, mScoAudioMode=" + mScoAudioMode);
+                                broadcastScoConnectionState(
+                                        AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+                            }
+                            break;
+                        case SCO_STATE_DEACTIVATING:
+                            mScoAudioState = SCO_STATE_ACTIVATE_REQ;
+                            break;
+                        case SCO_STATE_DEACTIVATE_REQ:
+                            mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
+                            broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_CONNECTED);
+                            break;
+                        default:
+                            Log.w(TAG, "requestScoState: failed to connect in state "
+                                    + mScoAudioState + ", scoAudioMode=" + scoAudioMode);
+                            broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+                            break;
 
-                            if (!status) {
+                    }
+                }
+            } else if (state == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
+                switch (mScoAudioState) {
+                    case SCO_STATE_ACTIVE_INTERNAL:
+                        if (mBluetoothHeadset == null) {
+                            if (getBluetoothHeadset()) {
+                                mScoAudioState = SCO_STATE_DEACTIVATE_REQ;
+                            } else {
+                                Log.w(TAG, "requestScoState: getBluetoothHeadset failed during"
+                                        + " disconnection, mScoAudioMode=" + mScoAudioMode);
                                 mScoAudioState = SCO_STATE_INACTIVE;
                                 broadcastScoConnectionState(
                                         AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
                             }
-                        } else if (getBluetoothHeadset()) {
-                            mScoAudioState = SCO_STATE_DEACTIVATE_REQ;
+                            break;
                         }
-                    } else {
+                        if (mBluetoothHeadsetDevice == null) {
+                            mScoAudioState = SCO_STATE_INACTIVE;
+                            broadcastScoConnectionState(
+                                    AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+                            break;
+                        }
+                        if (disconnectBluetoothScoAudioHelper(mBluetoothHeadset,
+                                mBluetoothHeadsetDevice, mScoAudioMode)) {
+                            mScoAudioState = SCO_STATE_DEACTIVATING;
+                        } else {
+                            mScoAudioState = SCO_STATE_INACTIVE;
+                            broadcastScoConnectionState(
+                                    AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+                        }
+                        break;
+                    case SCO_STATE_ACTIVATE_REQ:
                         mScoAudioState = SCO_STATE_INACTIVE;
                         broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
-                    }
+                        break;
+                    default:
+                        Log.w(TAG, "requestScoState: failed to disconnect in state "
+                                + mScoAudioState + ", scoAudioMode=" + scoAudioMode);
+                        broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
+                        break;
                 }
             }
         }
     }
 
     private void checkScoAudioState() {
-        if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null &&
-                mScoAudioState == SCO_STATE_INACTIVE &&
-                mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice)
-                != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
-            mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
+        synchronized (mScoClients) {
+            if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null &&
+                    mScoAudioState == SCO_STATE_INACTIVE &&
+                    mBluetoothHeadset.getAudioState(mBluetoothHeadsetDevice)
+                            != BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
+                mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
+            }
         }
     }
 
+
     private ScoClient getScoClient(IBinder cb, boolean create) {
         synchronized(mScoClients) {
-            ScoClient client = null;
-            int size = mScoClients.size();
-            for (int i = 0; i < size; i++) {
-                client = mScoClients.get(i);
-                if (client.getBinder() == cb)
-                    return client;
+            for (ScoClient existingClient : mScoClients) {
+                if (existingClient.getBinder() == cb) {
+                    return existingClient;
+                }
             }
             if (create) {
-                client = new ScoClient(cb);
-                mScoClients.add(client);
+                ScoClient newClient = new ScoClient(cb);
+                mScoClients.add(newClient);
+                return newClient;
             }
-            return client;
+            return null;
         }
     }
 
     public void clearAllScoClients(int exceptPid, boolean stopSco) {
         synchronized(mScoClients) {
             ScoClient savedClient = null;
-            int size = mScoClients.size();
-            for (int i = 0; i < size; i++) {
-                ScoClient cl = mScoClients.get(i);
+            for (ScoClient cl : mScoClients) {
                 if (cl.getPid() != exceptPid) {
                     cl.clearCount(stopSco);
                 } else {
@@ -3556,10 +3587,17 @@
                     mScoAudioState == SCO_STATE_DEACTIVATE_EXT_REQ) {
                 if (mBluetoothHeadsetDevice != null) {
                     if (mBluetoothHeadset != null) {
-                        if (!mBluetoothHeadset.stopVoiceRecognition(
-                                mBluetoothHeadsetDevice)) {
-                            sendMsg(mAudioHandler, MSG_BT_HEADSET_CNCT_FAILED,
-                                    SENDMSG_REPLACE, 0, 0, null, 0);
+                        boolean status = disconnectBluetoothScoAudioHelper(mBluetoothHeadset,
+                                mBluetoothHeadsetDevice, SCO_MODE_RAW)
+                                || disconnectBluetoothScoAudioHelper(mBluetoothHeadset,
+                                mBluetoothHeadsetDevice, SCO_MODE_VIRTUAL_CALL)
+                                || disconnectBluetoothScoAudioHelper(mBluetoothHeadset,
+                                mBluetoothHeadsetDevice, SCO_MODE_VR);
+                        if (status) {
+                            mScoAudioState = SCO_STATE_DEACTIVATING;
+                        } else {
+                            clearAllScoClients(exceptPid, false);
+                            mScoAudioState = SCO_STATE_INACTIVE;
                         }
                     } else if (mScoAudioState == SCO_STATE_ACTIVE_EXTERNAL &&
                             getBluetoothHeadset()) {
@@ -3572,6 +3610,34 @@
         }
     }
 
+    private static boolean disconnectBluetoothScoAudioHelper(BluetoothHeadset bluetoothHeadset,
+            BluetoothDevice device, int scoAudioMode) {
+        switch (scoAudioMode) {
+            case SCO_MODE_RAW:
+                return bluetoothHeadset.disconnectAudio();
+            case SCO_MODE_VIRTUAL_CALL:
+                return bluetoothHeadset.stopScoUsingVirtualVoiceCall();
+            case SCO_MODE_VR:
+                return bluetoothHeadset.stopVoiceRecognition(device);
+            default:
+                return false;
+        }
+    }
+
+    private static boolean connectBluetoothScoAudioHelper(BluetoothHeadset bluetoothHeadset,
+            BluetoothDevice device, int scoAudioMode) {
+        switch (scoAudioMode) {
+            case SCO_MODE_RAW:
+                return bluetoothHeadset.connectAudio();
+            case SCO_MODE_VIRTUAL_CALL:
+                return bluetoothHeadset.startScoUsingVirtualVoiceCall();
+            case SCO_MODE_VR:
+                return bluetoothHeadset.startVoiceRecognition(device);
+            default:
+                return false;
+        }
+    }
+
     private void resetBluetoothSco() {
         synchronized(mScoClients) {
             clearAllScoClients(0, false);
@@ -3629,11 +3695,9 @@
         return result;
     }
 
-    void setBtScoActiveDevice(BluetoothDevice btDevice) {
-        if (DEBUG_DEVICES) {
-            Log.d(TAG, "setBtScoActiveDevice(" + btDevice + ")");
-        }
+    private void setBtScoActiveDevice(BluetoothDevice btDevice) {
         synchronized (mScoClients) {
+            Log.i(TAG, "setBtScoActiveDevice: " + mBluetoothHeadsetDevice + " -> " + btDevice);
             final BluetoothDevice previousActiveDevice = mBluetoothHeadsetDevice;
             if (!Objects.equals(btDevice, previousActiveDevice)) {
                 if (!handleBtScoActiveDeviceChange(previousActiveDevice, false)) {
@@ -3713,37 +3777,36 @@
                         boolean status = false;
                         if (mBluetoothHeadsetDevice != null) {
                             switch (mScoAudioState) {
-                            case SCO_STATE_ACTIVATE_REQ:
-                                mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
-                                if (mScoAudioMode == SCO_MODE_RAW) {
-                                    status = mBluetoothHeadset.connectAudio();
-                                } else if (mScoAudioMode == SCO_MODE_VIRTUAL_CALL) {
-                                    status = mBluetoothHeadset.startScoUsingVirtualVoiceCall(
-                                                                        mBluetoothHeadsetDevice);
-                                } else if (mScoAudioMode == SCO_MODE_VR) {
-                                    status = mBluetoothHeadset.startVoiceRecognition(
-                                                                      mBluetoothHeadsetDevice);
-                                }
-                                break;
-                            case SCO_STATE_DEACTIVATE_REQ:
-                                if (mScoAudioMode == SCO_MODE_RAW) {
-                                    status = mBluetoothHeadset.disconnectAudio();
-                                } else if (mScoAudioMode == SCO_MODE_VIRTUAL_CALL) {
-                                    status = mBluetoothHeadset.stopScoUsingVirtualVoiceCall(
-                                                                        mBluetoothHeadsetDevice);
-                                } else if (mScoAudioMode == SCO_MODE_VR) {
-                                    status = mBluetoothHeadset.stopVoiceRecognition(
-                                                                      mBluetoothHeadsetDevice);
-                                }
-                                break;
-                            case SCO_STATE_DEACTIVATE_EXT_REQ:
-                                status = mBluetoothHeadset.stopVoiceRecognition(
-                                        mBluetoothHeadsetDevice);
+                                case SCO_STATE_ACTIVATE_REQ:
+                                    status = connectBluetoothScoAudioHelper(mBluetoothHeadset,
+                                            mBluetoothHeadsetDevice, mScoAudioMode);
+                                    if (status) {
+                                        mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
+                                    }
+                                    break;
+                                case SCO_STATE_DEACTIVATE_REQ:
+                                    status = disconnectBluetoothScoAudioHelper(mBluetoothHeadset,
+                                            mBluetoothHeadsetDevice, mScoAudioMode);
+                                    if (status) {
+                                        mScoAudioState = SCO_STATE_DEACTIVATING;
+                                    }
+                                    break;
+                                case SCO_STATE_DEACTIVATE_EXT_REQ:
+                                    status = disconnectBluetoothScoAudioHelper(mBluetoothHeadset,
+                                            mBluetoothHeadsetDevice, SCO_MODE_RAW) ||
+                                            disconnectBluetoothScoAudioHelper(mBluetoothHeadset,
+                                            mBluetoothHeadsetDevice, SCO_MODE_VIRTUAL_CALL) ||
+                                            disconnectBluetoothScoAudioHelper(mBluetoothHeadset,
+                                            mBluetoothHeadsetDevice, SCO_MODE_VR);
+                                    if (status) {
+                                        mScoAudioState = SCO_STATE_DEACTIVATING;
+                                    }
+                                    break;
                             }
                         }
                         if (!status) {
-                            sendMsg(mAudioHandler, MSG_BT_HEADSET_CNCT_FAILED,
-                                    SENDMSG_REPLACE, 0, 0, null, 0);
+                            mScoAudioState = SCO_STATE_INACTIVE;
+                            broadcastScoConnectionState(AudioManager.SCO_AUDIO_STATE_DISCONNECTED);
                         }
                     }
                 }
@@ -6313,33 +6376,47 @@
                     if (!mScoClients.isEmpty() &&
                             (mScoAudioState == SCO_STATE_ACTIVE_INTERNAL ||
                              mScoAudioState == SCO_STATE_ACTIVATE_REQ ||
-                             mScoAudioState == SCO_STATE_DEACTIVATE_REQ)) {
+                             mScoAudioState == SCO_STATE_DEACTIVATE_REQ ||
+                             mScoAudioState == SCO_STATE_DEACTIVATING)) {
                         broadcast = true;
                     }
                     switch (btState) {
-                    case BluetoothHeadset.STATE_AUDIO_CONNECTED:
-                        scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTED;
-                        if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL &&
-                            mScoAudioState != SCO_STATE_DEACTIVATE_REQ &&
-                            mScoAudioState != SCO_STATE_DEACTIVATE_EXT_REQ) {
-                            mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
-                        }
-                        break;
-                    case BluetoothHeadset.STATE_AUDIO_DISCONNECTED:
-                        scoAudioState = AudioManager.SCO_AUDIO_STATE_DISCONNECTED;
-                        mScoAudioState = SCO_STATE_INACTIVE;
-                        clearAllScoClients(0, false);
-                        break;
-                    case BluetoothHeadset.STATE_AUDIO_CONNECTING:
-                        if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL &&
-                            mScoAudioState != SCO_STATE_DEACTIVATE_REQ &&
-                            mScoAudioState != SCO_STATE_DEACTIVATE_EXT_REQ) {
-                            mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
-                        }
-                    default:
-                        // do not broadcast CONNECTING or invalid state
-                        broadcast = false;
-                        break;
+                        case BluetoothHeadset.STATE_AUDIO_CONNECTED:
+                            scoAudioState = AudioManager.SCO_AUDIO_STATE_CONNECTED;
+                            if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL &&
+                                mScoAudioState != SCO_STATE_DEACTIVATE_REQ &&
+                                mScoAudioState != SCO_STATE_DEACTIVATE_EXT_REQ) {
+                                mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
+                            }
+                            setBluetoothScoOn(true);
+                            break;
+                        case BluetoothHeadset.STATE_AUDIO_DISCONNECTED:
+                            setBluetoothScoOn(false);
+                            scoAudioState = AudioManager.SCO_AUDIO_STATE_DISCONNECTED;
+                            // startBluetoothSco called after stopBluetoothSco
+                            if (mScoAudioState == SCO_STATE_ACTIVATE_REQ) {
+                                if (mBluetoothHeadset != null && mBluetoothHeadsetDevice != null
+                                        && connectBluetoothScoAudioHelper(mBluetoothHeadset,
+                                        mBluetoothHeadsetDevice, mScoAudioMode)) {
+                                    mScoAudioState = SCO_STATE_ACTIVE_INTERNAL;
+                                    broadcast = false;
+                                    break;
+                                }
+                            }
+                            // Tear down SCO if disconnected from external
+                            clearAllScoClients(0, mScoAudioState == SCO_STATE_ACTIVE_INTERNAL);
+                            mScoAudioState = SCO_STATE_INACTIVE;
+                            break;
+                        case BluetoothHeadset.STATE_AUDIO_CONNECTING:
+                            if (mScoAudioState != SCO_STATE_ACTIVE_INTERNAL &&
+                                mScoAudioState != SCO_STATE_DEACTIVATE_REQ &&
+                                mScoAudioState != SCO_STATE_DEACTIVATE_EXT_REQ) {
+                                mScoAudioState = SCO_STATE_ACTIVE_EXTERNAL;
+                            }
+                        default:
+                            // do not broadcast CONNECTING or invalid state
+                            broadcast = false;
+                            break;
                     }
                 }
                 if (broadcast) {
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index af5521d..8bf0945 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -88,11 +88,11 @@
 import static android.os.storage.StorageManager.FLAG_STORAGE_DE;
 import static android.system.OsConstants.O_CREAT;
 import static android.system.OsConstants.O_RDWR;
+
 import static com.android.internal.app.IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE;
 import static com.android.internal.app.IntentForwarderActivity.FORWARD_INTENT_TO_PARENT;
 import static com.android.internal.content.NativeLibraryHelper.LIB64_DIR_NAME;
 import static com.android.internal.content.NativeLibraryHelper.LIB_DIR_NAME;
-import static com.android.internal.util.ArrayUtils.appendElement;
 import static com.android.internal.util.ArrayUtils.appendInt;
 import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
 import static com.android.server.pm.InstructionSets.getDexCodeInstructionSet;
@@ -275,7 +275,6 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.app.IMediaContainerService;
 import com.android.internal.app.ResolverActivity;
-import com.android.internal.app.SuspendedAppActivity;
 import com.android.internal.content.NativeLibraryHelper;
 import com.android.internal.content.PackageHelper;
 import com.android.internal.logging.MetricsLogger;
@@ -411,7 +410,7 @@
     static final boolean DEBUG_DOMAIN_VERIFICATION = false;
     private static final boolean DEBUG_BACKUP = false;
     public static final boolean DEBUG_INSTALL = false;
-    public static final boolean DEBUG_REMOVE = true;
+    public static final boolean DEBUG_REMOVE = false;
     private static final boolean DEBUG_BROADCASTS = false;
     private static final boolean DEBUG_SHOW_INFO = false;
     private static final boolean DEBUG_PACKAGE_INFO = false;
@@ -8482,6 +8481,29 @@
     }
 
     /**
+     * Clear the package profile if this was an upgrade and the package
+     * version was updated.
+     */
+    private void maybeClearProfilesForUpgradesLI(
+            @Nullable PackageSetting originalPkgSetting,
+            @NonNull PackageParser.Package currentPkg) {
+        if (originalPkgSetting == null || !isUpgrade()) {
+          return;
+        }
+        if (originalPkgSetting.versionCode == currentPkg.mVersionCode) {
+          return;
+        }
+
+        clearAppProfilesLIF(currentPkg, UserHandle.USER_ALL);
+        if (DEBUG_INSTALL) {
+            Slog.d(TAG, originalPkgSetting.name
+                  + " clear profile due to version change "
+                  + originalPkgSetting.versionCode + " != "
+                  + currentPkg.mVersionCode);
+        }
+    }
+
+    /**
      *  Traces a package scan.
      *  @see #scanPackageLI(File, int, int, long, UserHandle)
      */
@@ -8763,6 +8785,13 @@
                 (forceCollect && canSkipFullPackageVerification(pkg));
         collectCertificatesLI(pkgSetting, pkg, forceCollect, skipVerify);
 
+        // Reset profile if the application version is changed
+        maybeClearProfilesForUpgradesLI(pkgSetting, pkg);
+
+        /*
+         * A new system app appeared, but we already had a non-system one of the
+         * same name installed earlier.
+         */
         boolean shouldHideSystemApp = false;
         // A new application appeared on /system, but, we already have a copy of
         // the application installed on /data.
@@ -10186,10 +10215,14 @@
 
                 // if this is is a sharedUser, check to see if the new package is signed by a newer
                 // signing certificate than the existing one, and if so, copy over the new details
-                if (signatureCheckPs.sharedUser != null
-                        && pkg.mSigningDetails.hasAncestor(
+                if (signatureCheckPs.sharedUser != null) {
+                    if (pkg.mSigningDetails.hasAncestor(
                                 signatureCheckPs.sharedUser.signatures.mSigningDetails)) {
-                    signatureCheckPs.sharedUser.signatures.mSigningDetails = pkg.mSigningDetails;
+                        signatureCheckPs.sharedUser.signatures.mSigningDetails = pkg.mSigningDetails;
+                    }
+                    if (signatureCheckPs.sharedUser.signaturesChanged == null) {
+                        signatureCheckPs.sharedUser.signaturesChanged = Boolean.FALSE;
+                    }
                 }
             } catch (PackageManagerException e) {
                 if ((parseFlags & PackageParser.PARSE_IS_SYSTEM_DIR) == 0) {
@@ -10198,10 +10231,24 @@
                 // The signature has changed, but this package is in the system
                 // image...  let's recover!
                 pkgSetting.signatures.mSigningDetails = pkg.mSigningDetails;
+
                 // If the system app is part of a shared user we allow that shared user to change
-                // signatures as well in part as part of an OTA.
+                // signatures as well as part of an OTA. We still need to verify that the signatures
+                // are consistent within the shared user for a given boot, so only allow updating
+                // the signatures on the first package scanned for the shared user (i.e. if the
+                // signaturesChanged state hasn't been initialized yet in SharedUserSetting).
                 if (signatureCheckPs.sharedUser != null) {
+                    if (signatureCheckPs.sharedUser.signaturesChanged != null &&
+                        compareSignatures(
+                            signatureCheckPs.sharedUser.signatures.mSigningDetails.signatures,
+                            pkg.mSigningDetails.signatures) != PackageManager.SIGNATURE_MATCH) {
+                        throw new PackageManagerException(
+                                INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES,
+                                "Signature mismatch for shared user: " + pkgSetting.sharedUser);
+                    }
+
                     signatureCheckPs.sharedUser.signatures.mSigningDetails = pkg.mSigningDetails;
+                    signatureCheckPs.sharedUser.signaturesChanged = Boolean.TRUE;
                 }
                 // File a report about this.
                 String msg = "System package " + pkg.packageName
@@ -14031,13 +14078,10 @@
                             + Manifest.permission.MANAGE_USERS);
         }
         final int callingUid = Binder.getCallingUid();
-        mPermissionManager.enforceCrossUserPermission(callingUid, userId,
-                true /* requireFullPermission */, true /* checkShell */,
-                "setPackagesSuspended for user " + userId);
-        if (callingUid != Process.ROOT_UID &&
-                !UserHandle.isSameApp(getPackageUid(callingPackage, 0, userId), callingUid)) {
-            throw new IllegalArgumentException("CallingPackage " + callingPackage + " does not"
-                    + " belong to calling app id " + UserHandle.getAppId(callingUid));
+        if (callingUid != Process.ROOT_UID && callingUid != Process.SYSTEM_UID
+                && getPackageUid(callingPackage, 0, userId) != callingUid) {
+            throw new SecurityException("Calling package " + callingPackage + " in user "
+                    + userId + " does not belong to calling uid " + callingUid);
         }
         if (!PLATFORM_PACKAGE_NAME.equals(callingPackage)
                 && mProtectedPackages.getDeviceOwnerOrProfileOwnerPackage(userId) != null) {
@@ -14164,9 +14208,19 @@
         }
     }
 
-    void onSuspendingPackageRemoved(String packageName, int removedForUser) {
-        final int[] userIds = (removedForUser == UserHandle.USER_ALL) ? sUserManager.getUserIds()
-                : new int[] {removedForUser};
+    /**
+     * Immediately unsuspends any packages suspended by the given package. To be called
+     * when such a package's data is cleared or it is removed from the device.
+     *
+     * <p><b>Should not be used on a frequent code path</b> as it flushes state to disk
+     * synchronously
+     *
+     * @param packageName The package holding {@link Manifest.permission#SUSPEND_APPS} permission
+     * @param affectedUser The user for which the changes are taking place.
+     */
+    void unsuspendForSuspendingPackage(String packageName, int affectedUser) {
+        final int[] userIds = (affectedUser == UserHandle.USER_ALL) ? sUserManager.getUserIds()
+                : new int[] {affectedUser};
         for (int userId : userIds) {
             List<String> affectedPackages = new ArrayList<>();
             synchronized (mPackages) {
@@ -14183,6 +14237,8 @@
                         new String[affectedPackages.size()]);
                 sendMyPackageSuspendedOrUnsuspended(packageArray, false, null, userId);
                 sendPackagesSuspendedForUser(packageArray, userId, false, null);
+                // Write package restrictions immediately to avoid an inconsistent state.
+                mSettings.writePackageRestrictionsLPr(userId);
             }
         }
     }
@@ -18758,7 +18814,7 @@
 
         final int userId = user == null ? UserHandle.USER_ALL : user.getIdentifier();
         if (ps.getPermissionsState().hasPermission(Manifest.permission.SUSPEND_APPS, userId)) {
-            onSuspendingPackageRemoved(packageName, userId);
+            unsuspendForSuspendingPackage(packageName, userId);
         }
 
 
@@ -18899,10 +18955,10 @@
                     true /*notLaunched*/,
                     false /*hidden*/,
                     false /*suspended*/,
-                    null, /*suspendingPackage*/
-                    null, /*dialogMessage*/
-                    null, /*suspendedAppExtras*/
-                    null, /*suspendedLauncherExtras*/
+                    null /*suspendingPackage*/,
+                    null /*dialogMessage*/,
+                    null /*suspendedAppExtras*/,
+                    null /*suspendedLauncherExtras*/,
                     false /*instantApp*/,
                     false /*virtualPreload*/,
                     null /*lastDisableAppCaller*/,
@@ -19089,6 +19145,10 @@
                         if (dsm != null) {
                             dsm.checkMemory();
                         }
+                        if (checkPermission(Manifest.permission.SUSPEND_APPS, packageName, userId)
+                                == PERMISSION_GRANTED) {
+                            unsuspendForSuspendingPackage(packageName, userId);
+                        }
                     }
                 } else {
                     succeeded = false;
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index f8bf9c4..5c6338d 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -734,10 +734,10 @@
                                 true /*notLaunched*/,
                                 false /*hidden*/,
                                 false /*suspended*/,
-                                null, /*suspendingPackage*/
-                                null, /*dialogMessage*/
-                                null, /*suspendedAppExtras*/
-                                null, /*suspendedLauncherExtras*/
+                                null /*suspendingPackage*/,
+                                null /*dialogMessage*/,
+                                null /*suspendedAppExtras*/,
+                                null /*suspendedLauncherExtras*/,
                                 instantApp,
                                 virtualPreload,
                                 null /*lastDisableAppCaller*/,
@@ -1634,10 +1634,10 @@
                                 false /*notLaunched*/,
                                 false /*hidden*/,
                                 false /*suspended*/,
-                                null, /*suspendingPackage*/
-                                null, /*dialogMessage*/
-                                null, /*suspendedAppExtras*/
-                                null, /*suspendedLauncherExtras*/
+                                null /*suspendingPackage*/,
+                                null /*dialogMessage*/,
+                                null /*suspendedAppExtras*/,
+                                null /*suspendedLauncherExtras*/,
                                 false /*instantApp*/,
                                 false /*virtualPreload*/,
                                 null /*lastDisableAppCaller*/,
diff --git a/services/core/java/com/android/server/pm/SharedUserSetting.java b/services/core/java/com/android/server/pm/SharedUserSetting.java
index b6b94f5..ca08415 100644
--- a/services/core/java/com/android/server/pm/SharedUserSetting.java
+++ b/services/core/java/com/android/server/pm/SharedUserSetting.java
@@ -46,6 +46,7 @@
     final ArraySet<PackageSetting> packages = new ArraySet<PackageSetting>();
 
     final PackageSignatures signatures = new PackageSignatures();
+    Boolean signaturesChanged;
 
     SharedUserSetting(String _name, int _pkgFlags, int _pkgPrivateFlags) {
         super(_pkgFlags, _pkgPrivateFlags);
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index f659225..d3526f7 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -1953,7 +1953,7 @@
                     UserManager.DISALLOW_DEBUGGING_FEATURES, callingUid, userId);
         }
         if (userId == UserHandle.getUserId(callingUid)) return;
-        if (callingUid != Process.SYSTEM_UID && callingUid != 0) {
+        if (callingUid != Process.SYSTEM_UID && callingUid != Process.ROOT_UID) {
             if (requireFullPermission) {
                 mContext.enforceCallingOrSelfPermission(
                         android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, message);
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 0ccbb25..a7c203d 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -1303,8 +1303,10 @@
         synchronized (mLock) {
             if (mCurrentUserId == userId) {
                 if (mWaitingForUnlock) {
-                    // If we're switching users, now is when we transition the wallpaper
-                    switchUser(userId, null);
+                    // the desired wallpaper is not direct-boot aware, load it now
+                    final WallpaperData systemWallpaper =
+                            getWallpaperSafeLocked(userId, FLAG_SYSTEM);
+                    switchWallpaper(systemWallpaper, null);
                 }
 
                 // Make sure that the SELinux labeling of all the relevant files is correct.
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 9621edd..c0dc750 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -1510,6 +1510,10 @@
         return (stack != null && stack.isVisible()) ? stack : null;
     }
 
+    boolean hasSplitScreenPrimaryStack() {
+        return getSplitScreenPrimaryStack() != null;
+    }
+
     /**
      * Like {@link #getSplitScreenPrimaryStack}, but also returns the stack if it's currently
      * not visible.
@@ -1613,6 +1617,18 @@
         mService.mWindowsChanged = true;
     }
 
+    /**
+     * In split-screen mode we process the IME containers above the docked divider
+     * rather than directly above their target.
+     */
+    private boolean skipTraverseChild(WindowContainer child) {
+        if (child == mImeWindowsContainers && mService.mInputMethodTarget != null
+                && !hasSplitScreenPrimaryStack()) {
+            return true;
+        }
+        return false;
+    }
+
     @Override
     boolean forAllWindows(ToBooleanFunction<WindowState> callback, boolean traverseTopToBottom) {
         // Special handling so we can process IME windows with #forAllImeWindows above their IME
@@ -1620,11 +1636,10 @@
         if (traverseTopToBottom) {
             for (int i = mChildren.size() - 1; i >= 0; --i) {
                 final DisplayChildWindowContainer child = mChildren.get(i);
-                if (child == mImeWindowsContainers && mService.mInputMethodTarget != null) {
-                    // In this case the Ime windows will be processed above their target so we skip
-                    // here.
+                if (skipTraverseChild(child)) {
                     continue;
                 }
+
                 if (child.forAllWindows(callback, traverseTopToBottom)) {
                     return true;
                 }
@@ -1633,11 +1648,10 @@
             final int count = mChildren.size();
             for (int i = 0; i < count; i++) {
                 final DisplayChildWindowContainer child = mChildren.get(i);
-                if (child == mImeWindowsContainers && mService.mInputMethodTarget != null) {
-                    // In this case the Ime windows will be processed above their target so we skip
-                    // here.
+                if (skipTraverseChild(child)) {
                     continue;
                 }
+
                 if (child.forAllWindows(callback, traverseTopToBottom)) {
                     return true;
                 }
diff --git a/services/core/java/com/android/server/wm/DockedStackDividerController.java b/services/core/java/com/android/server/wm/DockedStackDividerController.java
index c8baced..39a3629 100644
--- a/services/core/java/com/android/server/wm/DockedStackDividerController.java
+++ b/services/core/java/com/android/server/wm/DockedStackDividerController.java
@@ -408,7 +408,7 @@
     }
 
     void positionDockedStackedDivider(Rect frame) {
-        TaskStack stack = mDisplayContent.getSplitScreenPrimaryStack();
+        TaskStack stack = mDisplayContent.getSplitScreenPrimaryStackIgnoringVisibility();
         if (stack == null) {
             // Unfortunately we might end up with still having a divider, even though the underlying
             // stack was already removed. This is because we are on AM thread and the removal of the
diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java
index 85e4ac7..553b4fe 100644
--- a/services/core/java/com/android/server/wm/RecentsAnimationController.java
+++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java
@@ -79,7 +79,7 @@
     public @interface ReorderMode {}
 
     private final WindowManagerService mService;
-    private final IRecentsAnimationRunner mRunner;
+    private IRecentsAnimationRunner mRunner;
     private final RecentsAnimationCallbacks mCallbacks;
     private final ArrayList<TaskAnimationAdapter> mPendingAnimations = new ArrayList<>();
     private final int mDisplayId;
@@ -426,7 +426,10 @@
             removeAnimation(taskAdapter);
         }
 
+        // Clear references to the runner
         unlinkToDeathOfRunner();
+        mRunner = null;
+
         // Clear associated input consumers
         mService.mInputMonitor.updateInputWindowsLw(true /*force*/);
         mService.destroyInputConsumer(INPUT_CONSUMER_RECENTS_ANIMATION);
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 0de3c92..c710c97 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -3997,32 +3997,33 @@
         return false;
     }
 
+    private boolean applyImeWindowsIfNeeded(ToBooleanFunction<WindowState> callback,
+            boolean traverseTopToBottom) {
+        // If this window is the current IME target, so we need to process the IME windows
+        // directly above it. The exception is if we are in split screen
+        // in which case we process the IME at the DisplayContent level to
+        // ensure it is above the docked divider.
+        if (isInputMethodTarget() && !inSplitScreenWindowingMode()) {
+            if (getDisplayContent().forAllImeWindows(callback, traverseTopToBottom)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     private boolean applyInOrderWithImeWindows(ToBooleanFunction<WindowState> callback,
             boolean traverseTopToBottom) {
         if (traverseTopToBottom) {
-            if (isInputMethodTarget()) {
-                // This window is the current IME target, so we need to process the IME windows
-                // directly above it.
-                if (getDisplayContent().forAllImeWindows(callback, traverseTopToBottom)) {
-                    return true;
-                }
-            }
-            if (callback.apply(this)) {
+            if (applyImeWindowsIfNeeded(callback, traverseTopToBottom)
+                    || callback.apply(this)) {
                 return true;
             }
         } else {
-            if (callback.apply(this)) {
+            if (callback.apply(this)
+                    || applyImeWindowsIfNeeded(callback, traverseTopToBottom)) {
                 return true;
             }
-            if (isInputMethodTarget()) {
-                // This window is the current IME target, so we need to process the IME windows
-                // directly above it.
-                if (getDisplayContent().forAllImeWindows(callback, traverseTopToBottom)) {
-                    return true;
-                }
-            }
         }
-
         return false;
     }
 
diff --git a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
index 288f350..b47dbfa 100644
--- a/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
+++ b/services/core/jni/com_android_server_location_GnssLocationProvider.cpp
@@ -38,6 +38,7 @@
 #include <pthread.h>
 #include <string.h>
 #include <cinttypes>
+#include <iomanip>
 
 static jobject mCallbacksObj = NULL;
 
@@ -1692,25 +1693,25 @@
                           << " satellites:: " << std::endl;
         }
 
-        internalState << "constellation: 1=GPS, 2=SBAS, 3=GLO, 4=QZSS, 5=BDS, 6=GAL; "
-                      << "ephemerisType: 0=Eph, 1=Alm, 2=?; "
-                      << "ephemerisSource: 0=Demod, 1=Supl, 2=Server, 3=?; "
-                      << "ephemerisHealth: 0=Good, 1=Bad, 2=?" << std::endl;
+        internalState << "constell: 1=GPS, 2=SBAS, 3=GLO, 4=QZSS, 5=BDS, 6=GAL; "
+                      << "ephType: 0=Eph, 1=Alm, 2=Unk; "
+                      << "ephSource: 0=Demod, 1=Supl, 2=Server, 3=Unk; "
+                      << "ephHealth: 0=Good, 1=Bad, 2=Unk" << std::endl;
         for (size_t i = 0; i < data.satelliteDataArray.size(); i++) {
-            internalState << "svid: " << data.satelliteDataArray[i].svid
-                          << ", constellation: "
+            internalState << "constell: "
                           << static_cast<uint32_t>(data.satelliteDataArray[i].constellation)
-                          << ", ephemerisType: "
-                          << static_cast<uint32_t>(data.satelliteDataArray[i].ephemerisType)
-                          << ", ephemerisSource: "
-                          << static_cast<uint32_t>(data.satelliteDataArray[i].ephemerisSource)
-                          << ", ephemerisHealth: "
-                          << static_cast<uint32_t>(data.satelliteDataArray[i].ephemerisHealth)
-                          << ", serverPredictionIsAvailable: "
+                          << ", svid: " << std::setw(3) << data.satelliteDataArray[i].svid
+                          << ", serverPredAvail: "
                           << data.satelliteDataArray[i].serverPredictionIsAvailable
-                          << ", serverPredictionAgeSeconds: "
+                          << ", serverPredAgeSec: " << std::setw(7)
                           << data.satelliteDataArray[i].serverPredictionAgeSeconds
-                          << ", ephemerisAgeSeconds: "
+                          << ", ephType: "
+                          << static_cast<uint32_t>(data.satelliteDataArray[i].ephemerisType)
+                          << ", ephSource: "
+                          << static_cast<uint32_t>(data.satelliteDataArray[i].ephemerisSource)
+                          << ", ephHealth: "
+                          << static_cast<uint32_t>(data.satelliteDataArray[i].ephemerisHealth)
+                          << ", ephAgeSec: " << std::setw(7)
                           << data.satelliteDataArray[i].ephemerisAgeSeconds << std::endl;
         }
     }
diff --git a/services/tests/servicestests/src/com/android/server/wm/WindowContainerTraversalTests.java b/services/tests/servicestests/src/com/android/server/wm/WindowContainerTraversalTests.java
new file mode 100644
index 0000000..e076399
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/wm/WindowContainerTraversalTests.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.wm;
+
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Matchers.eq;
+
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import org.mockito.Mock;
+
+import java.util.function.Consumer;
+
+/**
+ * Tests for {@link WindowContainer#forAllWindows} and various implementations.
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class WindowContainerTraversalTests extends WindowTestsBase {
+
+    @Test
+    public void testDockedDividerPosition() throws Exception {
+        final WindowState splitScreenWindow = createWindowOnStack(null,
+                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, TYPE_BASE_APPLICATION,
+                mDisplayContent, "splitScreenWindow");
+        final WindowState splitScreenSecondaryWindow = createWindowOnStack(null,
+                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY, ACTIVITY_TYPE_STANDARD,
+                TYPE_BASE_APPLICATION, mDisplayContent, "splitScreenSecondaryWindow");
+
+        sWm.mInputMethodTarget = splitScreenWindow;
+
+        Consumer<WindowState> c = mock(Consumer.class);
+        mDisplayContent.forAllWindows(c, false);
+
+        verify(c).accept(eq(mDockedDividerWindow));
+        verify(c).accept(eq(mImeWindow));
+    }
+}
diff --git a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
index cd524a5..13e3069 100644
--- a/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
+++ b/services/voiceinteraction/java/com/android/server/soundtrigger/SoundTriggerService.java
@@ -46,6 +46,10 @@
 import android.hardware.soundtrigger.SoundTrigger.ModuleProperties;
 import android.hardware.soundtrigger.SoundTrigger.RecognitionConfig;
 import android.hardware.soundtrigger.SoundTrigger.SoundModel;
+import android.media.AudioAttributes;
+import android.media.AudioFormat;
+import android.media.AudioRecord;
+import android.media.MediaRecorder;
 import android.media.soundtrigger.ISoundTriggerDetectionService;
 import android.media.soundtrigger.ISoundTriggerDetectionServiceClient;
 import android.media.soundtrigger.SoundTriggerDetectionService;
@@ -654,8 +658,45 @@
         }
     }
 
-    private interface Operation {
-        void run(int opId, ISoundTriggerDetectionService service) throws RemoteException;
+    /**
+     * A single operation run in a {@link RemoteSoundTriggerDetectionService}.
+     *
+     * <p>Once the remote service is connected either setup + execute or setup + stop is executed.
+     */
+    private static class Operation {
+        private interface ExecuteOp {
+            void run(int opId, ISoundTriggerDetectionService service) throws RemoteException;
+        }
+
+        private final @Nullable Runnable mSetupOp;
+        private final @NonNull ExecuteOp mExecuteOp;
+        private final @Nullable Runnable mDropOp;
+
+        private Operation(@Nullable Runnable setupOp, @NonNull ExecuteOp executeOp,
+                @Nullable Runnable cancelOp) {
+            mSetupOp = setupOp;
+            mExecuteOp = executeOp;
+            mDropOp = cancelOp;
+        }
+
+        private void setup() {
+            if (mSetupOp != null) {
+                mSetupOp.run();
+            }
+        }
+
+        void run(int opId, @NonNull ISoundTriggerDetectionService service) throws RemoteException {
+            setup();
+            mExecuteOp.run(opId, service);
+        }
+
+        void drop() {
+            setup();
+
+            if (mDropOp != null) {
+                mDropOp.run();
+            }
+        }
     }
 
     /**
@@ -902,6 +943,10 @@
         private void runOrAddOperation(Operation op) {
             synchronized (mRemoteServiceLock) {
                 if (mIsDestroyed || mDestroyOnceRunningOpsDone) {
+                    Slog.w(TAG, mPuuid + ": Dropped operation as already destroyed or marked for "
+                            + "destruction");
+
+                    op.drop();
                     return;
                 }
 
@@ -922,9 +967,15 @@
 
                     int opsAdded = mNumOps.getOpsAdded();
                     if (mNumOps.getOpsAdded() >= opsAllowed) {
-                        if (DEBUG || opsAllowed + 10 > opsAdded) {
-                            Slog.w(TAG, mPuuid + ": Dropped operation as too many operations were "
-                                    + "run in last 24 hours");
+                        try {
+                            if (DEBUG || opsAllowed + 10 > opsAdded) {
+                                Slog.w(TAG, mPuuid + ": Dropped operation as too many operations "
+                                        + "were run in last 24 hours");
+                            }
+
+                            op.drop();
+                        } catch (Exception e) {
+                            Slog.e(TAG, mPuuid + ": Could not drop operation", e);
                         }
                     } else {
                         mNumOps.addOp(currentTime);
@@ -972,34 +1023,87 @@
                     + ")");
         }
 
+        /**
+         * Create an AudioRecord enough for starting and releasing the data buffered for the event.
+         *
+         * @param event The event that was received
+         * @return The initialized AudioRecord
+         */
+        private @NonNull AudioRecord createAudioRecordForEvent(
+                @NonNull SoundTrigger.GenericRecognitionEvent event) {
+            AudioAttributes.Builder attributesBuilder = new AudioAttributes.Builder();
+            attributesBuilder.setInternalCapturePreset(MediaRecorder.AudioSource.HOTWORD);
+            AudioAttributes attributes = attributesBuilder.build();
+
+            // Use same AudioFormat processing as in RecognitionEvent.fromParcel
+            AudioFormat originalFormat = event.getCaptureFormat();
+            AudioFormat captureFormat = (new AudioFormat.Builder())
+                    .setChannelMask(originalFormat.getChannelMask())
+                    .setEncoding(originalFormat.getEncoding())
+                    .setSampleRate(originalFormat.getSampleRate())
+                    .build();
+
+            int bufferSize = AudioRecord.getMinBufferSize(
+                    captureFormat.getSampleRate() == AudioFormat.SAMPLE_RATE_UNSPECIFIED
+                            ? AudioFormat.SAMPLE_RATE_HZ_MAX
+                            : captureFormat.getSampleRate(),
+                    captureFormat.getChannelCount() == 2
+                            ? AudioFormat.CHANNEL_IN_STEREO
+                            : AudioFormat.CHANNEL_IN_MONO,
+                    captureFormat.getEncoding());
+
+            return new AudioRecord(attributes, captureFormat, bufferSize,
+                    event.getCaptureSession());
+        }
+
         @Override
         public void onGenericSoundTriggerDetected(SoundTrigger.GenericRecognitionEvent event) {
             if (DEBUG) Slog.v(TAG, mPuuid + ": Generic sound trigger event: " + event);
 
-            runOrAddOperation((opId, service) -> {
-                if (!mRecognitionConfig.allowMultipleTriggers) {
-                    synchronized (mCallbacksLock) {
-                        mCallbacks.remove(mPuuid.getUuid());
-                    }
-                    mDestroyOnceRunningOpsDone = true;
-                }
+            runOrAddOperation(new Operation(
+                    // always execute:
+                    () -> {
+                        if (!mRecognitionConfig.allowMultipleTriggers) {
+                            // Unregister this remoteService once op is done
+                            synchronized (mCallbacksLock) {
+                                mCallbacks.remove(mPuuid.getUuid());
+                            }
+                            mDestroyOnceRunningOpsDone = true;
+                        }
+                    },
+                    // execute if not throttled:
+                    (opId, service) -> service.onGenericRecognitionEvent(mPuuid, opId, event),
+                    // execute if throttled:
+                    () -> {
+                        if (event.isCaptureAvailable()) {
+                            AudioRecord capturedData = createAudioRecordForEvent(event);
 
-                service.onGenericRecognitionEvent(mPuuid, opId, event);
-            });
+                            // Currently we need to start and release the audio record to reset
+                            // the DSP even if we don't want to process the event
+                            capturedData.startRecording();
+                            capturedData.release();
+                        }
+                    }));
         }
 
         @Override
         public void onError(int status) {
             if (DEBUG) Slog.v(TAG, mPuuid + ": onError: " + status);
 
-            runOrAddOperation((opId, service) -> {
-                synchronized (mCallbacksLock) {
-                    mCallbacks.remove(mPuuid.getUuid());
-                }
-                mDestroyOnceRunningOpsDone = true;
-
-                service.onError(mPuuid, opId, status);
-            });
+            runOrAddOperation(
+                    new Operation(
+                            // always execute:
+                            () -> {
+                                // Unregister this remoteService once op is done
+                                synchronized (mCallbacksLock) {
+                                    mCallbacks.remove(mPuuid.getUuid());
+                                }
+                                mDestroyOnceRunningOpsDone = true;
+                            },
+                            // execute if not throttled:
+                            (opId, service) -> service.onError(mPuuid, opId, status),
+                            // nothing to do if throttled
+                            null));
         }
 
         @Override
diff --git a/telecomm/java/android/telecom/TelecomManager.java b/telecomm/java/android/telecom/TelecomManager.java
index 72c67d3..29898ff 100644
--- a/telecomm/java/android/telecom/TelecomManager.java
+++ b/telecomm/java/android/telecom/TelecomManager.java
@@ -1299,13 +1299,18 @@
     }
 
     /**
-     * Ends an ongoing call.
-     * TODO: L-release - need to convert all invocations of ITelecomService#endCall to use this
-     * method (clockwork & gearhead).
-     * @hide
+     * Ends the foreground call on the device.
+     * <p>
+     * If there is a ringing call, calling this method rejects the ringing call.  Otherwise the
+     * foreground call is ended.
+     * <p>
+     * Requires permission {@link android.Manifest.permission#ANSWER_PHONE_CALLS}.
+     *
+     * @return {@code true} if there is a call which will be rejected or terminated, {@code false}
+     * otherwise.
      */
+    @RequiresPermission(Manifest.permission.ANSWER_PHONE_CALLS)
     @SystemApi
-    @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
     public boolean endCall() {
         try {
             if (isServiceConnected()) {